PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / 2.3.6
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster v2.3.6
2.6.0 trunk 1.1.0 1.1.4 1.2.1 1.2.2 1.2.3 1.2.5 1.2.9 1.3.1 1.3.2 1.5.0 1.5.1 1.5.4 1.5.7 1.5.8 1.5.9 1.6.2 1.6.5 1.6.8 1.7.2 1.7.4 1.7.5 1.7.9 1.8.1 All 42 releases
sellkit / includes / admin / class-steps.php

class-steps.php in SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster 2.3.6, at includes/admin/class-steps.php

576 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 defined( 'ABSPATH' ) || die();
4
5 /**
6 * Steps class.
7 *
8 * @since 1.1.0
9 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
10 * @SuppressWarnings(PHPMD.CyclomaticComplexity)
11 */
12 class Sellkit_Admin_Steps {
13
14 /**
15 * Class instance.
16 *
17 * @since 1.1.0
18 * @var Sellkit_Admin_Steps
19 */
20 private static $instance = null;
21
22 /**
23 * Class instance.
24 *
25 * @since 1.1.0
26 * @var string
27 */
28 const SELLKIT_STEP_POST_TYPE = 'sellkit_step';
29
30 /**
31 * Get a class instance.
32 *
33 * @since 1.1.0
34 *
35 * @return Sellkit_Admin_Steps Class instance.
36 */
37 public static function get_instance() {
38 if ( null === self::$instance ) {
39 self::$instance = new self();
40 }
41
42 return self::$instance;
43 }
44
45 /**
46 * Class constructor.
47 *
48 * @since 1.1.0
49 */
50 public function __construct() {
51 $this->register_step_post_type();
52
53 add_action( 'wp_ajax_sellkit_steps_get_products', [ $this, 'get_products' ] );
54 add_action( 'wp_ajax_sellkit_steps_get_products_data', [ $this, 'get_products_data' ] );
55 add_action( 'sellkit_funnels_after_create_new_step', [ $this, 'after_create_step_callback' ], 10 );
56 add_action( 'sellkit_funnels_after_edit_step_name_slug', [ $this, 'edit_step_name_and_slug' ], 10 );
57 add_action( 'sellkit_funnels_after_delete_step_post', [ $this, 'delete_related_page' ], 10 );
58 add_action( 'sellkit_funnels_update_steps', [ $this, 'save_steps_data' ], 10, 2 );
59 add_filter( 'template_include', [ $this, 'load_page_template' ], 10 );
60 add_action( 'wp_ajax_sellkit_steps_get_coupons', [ $this, 'get_coupons' ] );
61 add_action( 'wp_loaded', [ $this, 'sellkit_steps_flush_rules' ] );
62 add_action( 'sellkit_after_toggle_status_sellkit-funnels', [ $this, 'update_steps_post_status' ], 10, 2 );
63 add_action( 'sellkit_funnels_after_delete_posts_sellkit-funnels', [ $this, 'remove_steps_pages' ], 10, 1 );
64
65 add_action( 'wp_loaded', [ $this, 'sellkit_set_sellkit_translation_mode' ] );
66 }
67
68 /**
69 * Removes steps pages.
70 *
71 * @since 1.5.4
72 * @return void
73 * @param array $post Posts.
74 */
75 public function remove_steps_pages( $post ) {
76 $nodes = get_post_meta( $post['ID'], 'nodes' );
77
78 if ( empty( $nodes[0] ) ) {
79 return;
80 }
81
82 foreach ( $nodes[0] as $node ) {
83 if ( isset( $node['page_id'] ) ) {
84 wp_delete_post( $node['page_id'] );
85 }
86 }
87 }
88
89 /**
90 * Get default funnels.
91 *
92 * @since 1.1.0
93 */
94 public static function get_default_funnel_steps() {
95 return [
96 'sales-page' => esc_html__( 'Sales Page', 'sellkit' ),
97 'checkout' => esc_html__( 'Checkout', 'sellkit' ),
98 'thankyou' => esc_html__( 'Thank You', 'sellkit' ),
99 'decision' => esc_html__( 'Condition', 'sellkit' ),
100 'upsell' => esc_html__( 'Upsell', 'sellkit' ),
101 'downsell' => esc_html__( 'Downsell', 'sellkit' ),
102 'optin-confirmation' => esc_html__( 'Opt-in Confirmation' ),
103 'optin' => esc_html__( 'Opt-in', 'sellkit' ),
104 ];
105 }
106
107 /**
108 * Save_steps_data.
109 *
110 * @since 1.1.0
111 * @param array $steps Steps.
112 * @param int $funnel_id Funnel Id.
113 */
114 public static function save_steps_data( $steps, $funnel_id ) {
115 foreach ( $steps as $step_number => $step ) {
116 $step['funnel_id'] = $funnel_id;
117 $step['number'] = $step_number;
118
119 if ( array_key_exists( 'page_id', $step ) ) {
120 $translated_step = apply_filters( 'wpml_object_id', $step['page_id'], 'sellkit_step', true );
121
122 if ( $step['page_id'] !== $translated_step ) {
123 update_post_meta( $translated_step, 'step_data', $step );
124 }
125
126 update_post_meta( $step['page_id'], 'step_data', $step );
127 }
128 }
129 }
130
131 /**
132 * Get all coupons.
133 *
134 * @since 1.1.0
135 */
136 public static function get_coupons() {
137 check_ajax_referer( 'sellkit', 'nonce' );
138
139 $input_value = sellkit_htmlspecialchars( INPUT_GET, 'input_value' );
140
141 $filtered_products = [];
142 $args = [
143 'post_type' => 'shop_coupon',
144 'post_status' => 'any',
145 's' => $input_value,
146 ];
147
148 $query = new WP_Query( $args );
149
150 if ( $query->have_posts() ) {
151 while ( $query->have_posts() ) {
152 $query->the_post();
153 $filtered_products[] = [
154 'label' => htmlspecialchars_decode( get_the_title() ),
155 'value' => get_the_ID(),
156 ];
157 }
158 }
159
160 wp_send_json_success( $filtered_products );
161 }
162
163 /**
164 * Get products.
165 *
166 * @since 1.1.0
167 */
168 public static function get_products() {
169 check_ajax_referer( 'sellkit', 'nonce' );
170
171 $input_value = sellkit_htmlspecialchars( INPUT_GET, 'input_value' );
172
173 if ( ! sellkit()->has_valid_dependencies() ) {
174 wp_send_json_error( __( 'Please install and activate WooCommerce.', 'sellkit' ) );
175 }
176
177 $filtered_products = [];
178
179 $args = [
180 'status' => 'any',
181 'orderby' => 'name',
182 'order' => 'ASC',
183 's' => $input_value,
184 'posts_per_page' => -1,
185 ];
186
187 $all_products = wc_get_products( $args );
188
189 foreach ( $all_products as $product ) {
190 if ( $product->get_type() !== 'variable' && $product->get_type() !== 'external' ) {
191 $filtered_products[] = [
192 'label' => $product->get_title(),
193 'value' => $product->get_id(),
194 ];
195 }
196
197 if ( $product->get_type() === 'variable' ) {
198 foreach ( $product->get_available_variations() as $available_variation ) {
199 $attributes = $available_variation['attributes'];
200 $attributes = implode( ', ', array_values( $attributes ) );
201
202 $filtered_products[] = [
203 'label' => "{$product->get_title()} - {$attributes} (#{$available_variation['variation_id']})",
204 'value' => $available_variation['variation_id'],
205 ];
206 }
207 }
208 }
209
210 wp_send_json_success( $filtered_products );
211 }
212
213 /**
214 * Get products.
215 *
216 * @since 1.1.0
217 */
218 public static function get_products_data() {
219 check_ajax_referer( 'sellkit', 'nonce' );
220
221 $filtered_products = [];
222 $products_id = filter_input( INPUT_GET, 'products_id', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
223
224 if ( ! sellkit()->has_valid_dependencies() ) {
225 wp_send_json_error( __( 'Please install and activate WooCommerce.', 'sellkit' ) );
226 }
227
228 if ( empty( $products_id ) ) {
229 wp_send_json_error( __( 'Please send some product ids' ) );
230 }
231
232 $products_id = is_array( $products_id ) ? $products_id : [ $products_id ];
233
234 foreach ( $products_id as $product_id ) {
235 $product = wc_get_product( $product_id );
236
237 if ( empty( $product ) ) {
238 continue;
239 }
240
241 $data = $product->get_data();
242
243 if ( 'trash' === $data['status'] ) {
244 continue;
245 }
246
247 $attributes = $product->get_attributes();
248 $label = $product->get_title();
249
250 if ( $product->get_type() === 'variation' ) {
251 $attributes = implode( ', ', array_values( $attributes ) );
252 $label = "{$product->get_title()} - $attributes (#{$product->get_id()})";
253 }
254
255 $filtered_products[] = [
256 'label' => $label,
257 'id' => $product->get_id(),
258 'title' => $label,
259 'thumbnail' => $product->get_image(),
260 'regular_price' => $product->get_regular_price( 'edit' ),
261 'sale_price' => ( $product->is_on_sale() ) ? $product->get_sale_price() : false,
262 ];
263 }
264
265 wp_send_json_success( $filtered_products );
266 }
267
268 /**
269 * Filter step data before saving.
270 *
271 * @since 1.1.0
272 * @param string $post_id Post id.
273 * @SuppressWarnings(PHPMD.NPathComplexity)
274 */
275 public function after_create_step_callback( $post_id ) {
276 $sub_action_data = sellkit_post( 'sub_action_data' );
277 $origin_node = isset( $sub_action_data['origin_node'] ) ? $sub_action_data['origin_node'] : '';
278 $target_node = ! empty( $sub_action_data['target_node'] ) ? $sub_action_data['target_node'] : null;
279 $target_index = isset( $sub_action_data['target_index'] ) ? $sub_action_data['target_index'] : 0;
280 $nodes = get_post_meta( $post_id, 'nodes', true );
281 $last_node = end( $nodes );
282
283 array_pop( $nodes );
284
285 $new_node_id = wp_insert_post( [
286 'post_type' => self::SELLKIT_STEP_POST_TYPE,
287 'post_title' => $last_node['title'],
288 'post_name' => sanitize_title( $last_node['title'] ),
289 'post_status' => 'publish',
290 ] );
291
292 $current_target = [ 'nodeId' => $target_node ];
293 $last_node['page_id'] = $new_node_id;
294 $last_node['slug'] = get_post_field( 'post_name', $new_node_id );
295 $last_node['targets'] = ! is_null( $target_node ) ? [ $current_target ] : [];
296
297 if (
298 'decision' === $last_node['type']['key'] && ! is_null( $target_node ) ||
299 'upsell' === $last_node['type']['key'] && ! is_null( $target_node ) ||
300 'downsell' === $last_node['type']['key'] && ! is_null( $target_node )
301 ) {
302 $last_node['targets'] = [
303 $current_target,
304 $current_target
305 ];
306 }
307
308 // Adding current target info the the step.
309 $last_node['current_target_index'] = $target_index;
310
311 if ( 'last-node' === $origin_node ) {
312 $first_end_path_node_key = self::get_first_end_path_node_key( $nodes );
313 $last_node['origin_node'] = 'last-node';
314
315 if ( $first_end_path_node_key ) {
316 unset( $nodes[ $first_end_path_node_key ]['origin_node'] );
317 }
318 }
319
320 $node_data = $last_node;
321 $node_data['funnel_id'] = $post_id;
322 $node_data['number'] = count( $nodes );
323
324 update_post_meta( $new_node_id, 'step_data', $node_data );// it should be updated.
325
326 $old_node_keys = array_keys( $nodes );
327 $nodes[ end( $old_node_keys ) + 1 ] = $last_node;
328 $keys = array_keys( $nodes );
329
330 if (
331 'last-node' !== $origin_node && ! empty( $nodes[ $origin_node ] ) &&
332 'decision' !== $nodes[ $origin_node ]['type']['key'] &&
333 'upsell' !== $nodes[ $origin_node ]['type']['key'] &&
334 'downsell' !== $nodes[ $origin_node ]['type']['key']
335 ) {
336 $nodes[ $origin_node ]['targets'] = [ [ 'nodeId' => strval( end( $keys ) ) ] ];
337 }
338
339 if (
340 'last-node' !== $origin_node && isset( $nodes[ $origin_node ]['type'] ) &&
341 (
342 'decision' === $nodes[ $origin_node ]['type']['key'] ||
343 'upsell' === $nodes[ $origin_node ]['type']['key'] ||
344 'downsell' === $nodes[ $origin_node ]['type']['key']
345 )
346 ) {
347 if ( is_array( $nodes[ $origin_node ]['targets'] ) && empty( $nodes[ $origin_node ]['targets'][0] ) ) {
348 $nodes[ $origin_node ]['targets'][0] = ! empty( $target_node ) ? [ 'nodeId' => $target_node ] : null;
349 }
350
351 if ( is_array( $nodes[ $origin_node ]['targets'] ) && empty( $nodes[ $origin_node ]['targets'][1] ) ) {
352 $nodes[ $origin_node ]['targets'][1] = ! empty( $target_node ) ? [ 'nodeId' => $target_node ] : null;
353 }
354
355 $nodes[ $origin_node ]['targets'][ (int) $target_index ] = [ 'nodeId' => strval( end( $old_node_keys ) + 1 ) ];
356 }
357
358 if ( '' === $origin_node && null === $target_node ) {
359 update_post_meta( $post_id, 'nodes', [ 1 => $last_node ] );
360 return;
361 }
362
363 update_post_meta( $post_id, 'nodes', $nodes );
364 }
365
366 /**
367 * Gets last node origin node
368 *
369 * @param array $nodes Nodes.
370 * @since 1.5.0
371 */
372 public static function get_first_end_path_node_key( $nodes ) {
373 foreach ( $nodes as $key => $node ) {
374 if ( ! empty( $node['origin_node'] ) && 'last-node' === $node['origin_node'] ) {
375 return $key;
376 }
377 }
378
379 return false;
380 }
381
382 /**
383 * Loading page templates.
384 *
385 * @since 1.1.0
386 * @param string $template Template name.
387 */
388 public function load_page_template( $template ) {
389
390 global $post;
391
392 if ( 'string' !== gettype( $template ) || ! is_object( $post ) || self::SELLKIT_STEP_POST_TYPE !== $post->post_type ) {
393 return $template;
394 }
395
396 if ( has_blocks( $post ) ) {
397 return $template;
398 }
399
400 remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
401 add_filter( 'next_post_rel_link', '__return_empty_string' );
402 add_filter( 'previous_post_rel_link', '__return_empty_string' );
403
404 $file = sellkit()->plugin_dir() . 'includes/templates/canvas.php';
405
406 if ( ! file_exists( $template ) ) {
407 return $template;
408 }
409
410 return $file;
411 }
412
413 /**
414 * Register step post type.
415 *
416 * @since 1.1.0
417 */
418 public function register_step_post_type() {
419 $labels = array(
420 'name' => esc_html_x( 'Steps', 'flow step general name', 'sellkit' ),
421 'singular_name' => esc_html_x( 'Step', 'flow step singular name', 'sellkit' ),
422 'search_items' => esc_html__( 'Search Steps', 'sellkit' ),
423 'all_items' => esc_html__( 'All Steps', 'sellkit' ),
424 'edit_item' => esc_html__( 'Edit Step', 'sellkit' ),
425 'view_item' => esc_html__( 'View Step', 'sellkit' ),
426 'add_new' => esc_html__( 'Add New', 'sellkit' ),
427 'update_item' => esc_html__( 'Update Step', 'sellkit' ),
428 'add_new_item' => esc_html__( 'Add New', 'sellkit' ),
429 'new_item_name' => esc_html__( 'New Step Name', 'sellkit' ),
430 );
431
432 $args = array(
433 'labels' => $labels,
434 'public' => true,
435 'query_var' => true,
436 'can_export' => true,
437 'exclude_from_search' => true,
438 'show_ui' => true,
439 'show_in_menu' => false,
440 'show_in_admin_bar' => true,
441 'show_in_rest' => true,
442 'publicly_queryable' => true,
443 'supports' => [ 'title', 'editor', 'elementor', 'revisions' ],
444 'capability_type' => 'post',
445 'capabilities' => [
446 'create_posts' => 'do_not_allow', // Prior to Wordpress 4.5, this was false.
447 ],
448 'map_meta_cap' => true,
449 );
450
451 register_post_type( self::SELLKIT_STEP_POST_TYPE, $args );
452 }
453
454 /**
455 * Edit step name or slug.
456 *
457 * @since 1.1.0
458 * @param string $funnel_id Funnel Id.
459 */
460 public function edit_step_name_and_slug( $funnel_id ) {
461 $sub_action_data = sellkit_post( 'sub_action_data' );
462 $title = ! empty( $sub_action_data['title'] ) ? $sub_action_data['title'] : '';
463 $slug = ! empty( $sub_action_data['slug'] ) ? $sub_action_data['slug'] : sanitize_title( $title );
464 $page_id = ! empty( $sub_action_data['page_id'] ) ? $sub_action_data['page_id'] : '';
465 $selected_step = $sub_action_data['selected_step'];
466
467 if ( empty( $page_id ) ) {
468 return;
469 }
470
471 wp_update_post( [
472 'ID' => $page_id,
473 'post_title' => $title,
474 'post_name' => $slug,
475 ] );
476
477 if ( get_post_field( 'post_name', $page_id ) === $slug ) {
478 return;
479 }
480
481 $post_data = get_post_meta( $funnel_id, 'nodes', true );
482 $post_data[ $selected_step ]['slug'] = get_post_field( 'post_name', $page_id );
483
484 update_post_meta( $funnel_id, 'nodes', $post_data );
485 }
486
487 /**
488 * Delete step page id.
489 *
490 * @since 1.1.0
491 */
492 public function delete_related_page() {
493 $sub_action_data = sellkit_post( 'sub_action_data' );
494
495 if ( empty( $sub_action_data['page_id'] ) ) {
496 return;
497 }
498
499 wp_delete_post( $sub_action_data['page_id'], true );
500 }
501
502 /**
503 * Flash rules.
504 *
505 * @since 1.1.0
506 */
507 public function sellkit_steps_flush_rules() {
508 $rules = get_option( 'rewrite_rules' );
509
510 if ( ! isset( $rules['sellkit_step/[^/]+/([^/]+)/?$'] ) ) {
511 flush_rewrite_rules();
512 }
513 }
514
515 /**
516 * Make publish steps page.
517 *
518 * @since 1.1.0
519 * @param string $funnel_id Funnel Id.
520 * @param string $new_status Status.
521 */
522 public function update_steps_post_status( $funnel_id, $new_status ) {
523 $steps = get_post_meta( $funnel_id, 'sellkit_steps', true );
524
525 if ( ! is_array( $steps ) ) {
526 return;
527 }
528
529 foreach ( $steps as $step ) {
530 if ( $step['page_id'] === $funnel_id ) {
531 continue;
532 }
533
534 wp_update_post(array(
535 'ID' => $step['page_id'],
536 'post_status' => $new_status,
537 ) );
538 }
539 }
540
541 /**
542 * Make SellKit custom post types translatable by default and set step_data custom field to "Copy".
543 *
544 * @since 2.3.2
545 */
546 public function sellkit_set_sellkit_translation_mode() {
547 if ( function_exists( 'wpml_load_settings_helper' ) ) {
548 global $sitepress;
549
550 if ( $sitepress instanceof SitePress ) {
551 $settings_helper = wpml_load_settings_helper();
552
553 $post_types = [
554 'sellkit_step',
555 'sellkit-discount',
556 'sellkit-coupon',
557 'sellkit-alert',
558 ];
559
560 foreach ( $post_types as $post_type ) {
561 $settings_helper->set_post_type_translatable( $post_type );
562 $settings_helper->set_post_type_translation_unlocked_option( $post_type, true );
563
564 if ( class_exists( '\WPML\Settings\PostType\Automatic' ) ) {
565 \WPML\Settings\PostType\Automatic::set( $post_type, true );
566 }
567 }
568
569 do_action( 'wpml_save_custom_field_translation_option', 'step_data', WPML_COPY_CUSTOM_FIELD );
570 }
571 }
572 }
573 }
574
575 Sellkit_Admin_Steps::get_instance();
576