PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / 1.5.4
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster v1.5.4
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 1.5.4, at includes/admin/class-steps.php

518 lines 14.2 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
66 /**
67 * Removes steps pages.
68 *
69 * @since 1.5.4
70 * @return void
71 * @param array $post Posts.
72 */
73 public function remove_steps_pages( $post ) {
74 $nodes = get_post_meta( $post['ID'], 'nodes' );
75
76 if ( empty( $nodes[0] ) ) {
77 return;
78 }
79
80 foreach ( $nodes[0] as $node ) {
81 wp_delete_post( $node['page_id'] );
82 }
83 }
84
85 /**
86 * Get default funnels.
87 *
88 * @since 1.1.0
89 */
90 public static function get_default_funnel_steps() {
91 return [
92 'sales-page' => esc_html__( 'Sales Page', 'sellkit' ),
93 'checkout' => esc_html__( 'Checkout', 'sellkit' ),
94 'thankyou' => esc_html__( 'Thank You', 'sellkit' ),
95 'decision' => esc_html__( 'Decision', 'sellkit' ),
96 'upsell' => esc_html__( 'Upsell', 'sellkit' ),
97 'downsell' => esc_html__( 'Downsell', 'sellkit' ),
98 'optin-confirmation' => esc_html__( 'Opt-in Confirmation' ),
99 'optin' => esc_html__( 'Opt-in', 'sellkit' ),
100 ];
101 }
102
103 /**
104 * Save_steps_data.
105 *
106 * @since 1.1.0
107 * @param array $steps Steps.
108 * @param int $funnel_id Funnel Id.
109 */
110 public static function save_steps_data( $steps, $funnel_id ) {
111 foreach ( $steps as $step_number => $step ) {
112 $step['funnel_id'] = $funnel_id;
113 $step['number'] = $step_number;
114
115 update_post_meta( $step['page_id'], 'step_data', $step );
116 }
117 }
118
119 /**
120 * Get all coupons.
121 *
122 * @since 1.1.0
123 */
124 public static function get_coupons() {
125 check_ajax_referer( 'sellkit', 'nonce' );
126
127 $input_value = sellkit_htmlspecialchars( INPUT_GET, 'input_value' );
128
129 $filtered_products = [];
130 $args = [
131 'post_type' => 'shop_coupon',
132 'post_status' => 'any',
133 's' => $input_value,
134 ];
135
136 $query = new WP_Query( $args );
137
138 if ( $query->have_posts() ) {
139 while ( $query->have_posts() ) {
140 $query->the_post();
141 $filtered_products[] = [
142 'label' => htmlspecialchars_decode( get_the_title() ),
143 'value' => get_the_ID(),
144 ];
145 }
146 }
147
148 wp_send_json_success( $filtered_products );
149 }
150
151 /**
152 * Get products.
153 *
154 * @since 1.1.0
155 */
156 public static function get_products() {
157 check_ajax_referer( 'sellkit', 'nonce' );
158
159 $input_value = sellkit_htmlspecialchars( INPUT_GET, 'input_value' );
160
161 if ( ! sellkit()->has_valid_dependencies() ) {
162 wp_send_json_error( __( 'Please install and activate WooCommerce.', 'sellkit' ) );
163 }
164
165 $filtered_products = [];
166
167 $args = [
168 'status' => 'any',
169 'orderby' => 'name',
170 'order' => 'ASC',
171 's' => $input_value,
172 'posts_per_page' => -1,
173 ];
174
175 $all_products = wc_get_products( $args );
176
177 foreach ( $all_products as $product ) {
178 if ( $product->get_type() !== 'variable' && $product->get_type() !== 'external' ) {
179 $filtered_products[] = [
180 'label' => $product->get_title(),
181 'value' => $product->get_id(),
182 ];
183 }
184
185 if ( $product->get_type() === 'variable' ) {
186 foreach ( $product->get_available_variations() as $available_variation ) {
187 $attributes = $available_variation['attributes'];
188 $attributes = implode( ', ', array_values( $attributes ) );
189
190 $filtered_products[] = [
191 'label' => "{$product->get_title()} - {$attributes} (#{$available_variation['variation_id']})",
192 'value' => $available_variation['variation_id'],
193 ];
194 }
195 }
196 }
197
198 wp_send_json_success( $filtered_products );
199 }
200
201 /**
202 * Get products.
203 *
204 * @since 1.1.0
205 */
206 public static function get_products_data() {
207 check_ajax_referer( 'sellkit', 'nonce' );
208
209 $filtered_products = [];
210 $products_id = filter_input( INPUT_GET, 'products_id', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
211
212 if ( ! sellkit()->has_valid_dependencies() ) {
213 wp_send_json_error( __( 'Please install and activate WooCommerce.', 'sellkit' ) );
214 }
215
216 if ( empty( $products_id ) ) {
217 wp_send_json_error( __( 'Please send some product ids' ) );
218 }
219
220 $products_id = is_array( $products_id ) ? $products_id : [ $products_id ];
221
222 foreach ( $products_id as $product_id ) {
223 $product = wc_get_product( $product_id );
224
225 if ( empty( $product ) ) {
226 wp_send_json_error( __( 'Product does not exist', 'sellkit' ) );
227 continue;
228 }
229
230 $attributes = $product->get_attributes();
231 $label = $product->get_title();
232
233 if ( $product->get_type() === 'variation' ) {
234 $attributes = implode( ', ', array_values( $attributes ) );
235 $label = "{$product->get_title()} - $attributes (#{$product->get_id()})";
236 }
237
238 $filtered_products[] = [
239 'label' => $label,
240 'id' => $product->get_id(),
241 'title' => $label,
242 'thumbnail' => $product->get_image(),
243 'regular_price' => $product->get_regular_price( 'edit' ),
244 'sale_price' => ( $product->is_on_sale() ) ? $product->get_sale_price() : false,
245 ];
246 }
247
248 wp_send_json_success( $filtered_products );
249 }
250
251 /**
252 * Filter step data before saving.
253 *
254 * @since 1.1.0
255 * @param string $post_id Post id.
256 * @SuppressWarnings(PHPMD.NPathComplexity)
257 */
258 public function after_create_step_callback( $post_id ) {
259 $sub_action_data = sellkit_post( 'sub_action_data' );
260 $origin_node = isset( $sub_action_data['origin_node'] ) ? $sub_action_data['origin_node'] : '';
261 $target_node = ! empty( $sub_action_data['target_node'] ) ? $sub_action_data['target_node'] : null;
262 $target_index = isset( $sub_action_data['target_index'] ) ? $sub_action_data['target_index'] : 0;
263 $nodes = get_post_meta( $post_id, 'nodes', true );
264 $last_node = end( $nodes );
265
266 array_pop( $nodes );
267
268 $new_node_id = wp_insert_post( [
269 'post_type' => self::SELLKIT_STEP_POST_TYPE,
270 'post_title' => $last_node['title'],
271 'post_name' => sanitize_title( $last_node['title'] ),
272 'post_status' => 'publish',
273 ] );
274
275 $current_target = [ 'nodeId' => $target_node ];
276 $last_node['page_id'] = $new_node_id;
277 $last_node['slug'] = get_post_field( 'post_name', $new_node_id );
278 $last_node['targets'] = ! is_null( $target_node ) ? [ $current_target ] : [];
279
280 if (
281 'decision' === $last_node['type']['key'] && ! is_null( $target_node ) ||
282 'upsell' === $last_node['type']['key'] && ! is_null( $target_node ) ||
283 'downsell' === $last_node['type']['key'] && ! is_null( $target_node )
284 ) {
285 $last_node['targets'] = [
286 $current_target,
287 $current_target
288 ];
289 }
290
291 // Adding current target info the the step.
292 $last_node['current_target_index'] = $target_index;
293
294 if ( 'last-node' === $origin_node ) {
295 $first_end_path_node_key = self::get_first_end_path_node_key( $nodes );
296 $last_node['origin_node'] = 'last-node';
297
298 if ( $first_end_path_node_key ) {
299 unset( $nodes[ $first_end_path_node_key ]['origin_node'] );
300 }
301 }
302
303 $node_data = $last_node;
304 $node_data['funnel_id'] = $post_id;
305 $node_data['number'] = count( $nodes );
306
307 update_post_meta( $new_node_id, 'step_data', $node_data );// it should be updated.
308
309 $old_node_keys = array_keys( $nodes );
310 $nodes[ end( $old_node_keys ) + 1 ] = $last_node;
311 $keys = array_keys( $nodes );
312
313 if (
314 'last-node' !== $origin_node &&
315 'decision' !== $nodes[ $origin_node ]['type']['key'] &&
316 'upsell' !== $nodes[ $origin_node ]['type']['key'] &&
317 'downsell' !== $nodes[ $origin_node ]['type']['key']
318 ) {
319 $nodes[ $origin_node ]['targets'] = [ [ 'nodeId' => strval( end( $keys ) ) ] ];
320 }
321
322 if (
323 'last-node' !== $origin_node &&
324 (
325 'decision' === $nodes[ $origin_node ]['type']['key'] ||
326 'upsell' === $nodes[ $origin_node ]['type']['key'] ||
327 'downsell' === $nodes[ $origin_node ]['type']['key']
328 )
329 ) {
330 if ( is_array( $nodes[ $origin_node ]['targets'] ) && empty( $nodes[ $origin_node ]['targets'][0] ) ) {
331 $nodes[ $origin_node ]['targets'][0] = ! empty( $target_node ) ? [ 'nodeId' => $target_node ] : null;
332 }
333
334 if ( is_array( $nodes[ $origin_node ]['targets'] ) && empty( $nodes[ $origin_node ]['targets'][1] ) ) {
335 $nodes[ $origin_node ]['targets'][1] = ! empty( $target_node ) ? [ 'nodeId' => $target_node ] : null;
336 }
337
338 $nodes[ $origin_node ]['targets'][ (int) $target_index ] = [ 'nodeId' => strval( end( $old_node_keys ) + 1 ) ];
339 }
340
341 if ( '' === $origin_node && null === $target_node ) {
342 update_post_meta( $post_id, 'nodes', [ 1 => $last_node ] );
343 return;
344 }
345
346 update_post_meta( $post_id, 'nodes', $nodes );
347 }
348
349 /**
350 * Gets last node origin node
351 *
352 * @param array $nodes Nodes.
353 * @since 1.5.0
354 */
355 public static function get_first_end_path_node_key( $nodes ) {
356 foreach ( $nodes as $key => $node ) {
357 if ( ! empty( $node['origin_node'] ) && 'last-node' === $node['origin_node'] ) {
358 return $key;
359 }
360 }
361
362 return false;
363 }
364
365 /**
366 * Loading page templates.
367 *
368 * @since 1.1.0
369 * @param string $template Template name.
370 */
371 public function load_page_template( $template ) {
372
373 global $post;
374
375 if ( 'string' !== gettype( $template ) || ! is_object( $post ) || self::SELLKIT_STEP_POST_TYPE !== $post->post_type ) {
376 return $template;
377 }
378
379 remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
380 add_filter( 'next_post_rel_link', '__return_empty_string' );
381 add_filter( 'previous_post_rel_link', '__return_empty_string' );
382
383 $file = sellkit()->plugin_dir() . 'includes/templates/canvas.php';
384
385 if ( ! file_exists( $template ) ) {
386 return $template;
387 }
388
389 return $file;
390 }
391
392 /**
393 * Register step post type.
394 *
395 * @since 1.1.0
396 */
397 public function register_step_post_type() {
398 $labels = array(
399 'name' => esc_html_x( 'Steps', 'flow step general name', 'sellkit' ),
400 'singular_name' => esc_html_x( 'Step', 'flow step singular name', 'sellkit' ),
401 'search_items' => esc_html__( 'Search Steps', 'sellkit' ),
402 'all_items' => esc_html__( 'All Steps', 'sellkit' ),
403 'edit_item' => esc_html__( 'Edit Step', 'sellkit' ),
404 'view_item' => esc_html__( 'View Step', 'sellkit' ),
405 'add_new' => esc_html__( 'Add New', 'sellkit' ),
406 'update_item' => esc_html__( 'Update Step', 'sellkit' ),
407 'add_new_item' => esc_html__( 'Add New', 'sellkit' ),
408 'new_item_name' => esc_html__( 'New Step Name', 'sellkit' ),
409 );
410
411 $args = array(
412 'labels' => $labels,
413 'public' => true,
414 'query_var' => true,
415 'can_export' => true,
416 'exclude_from_search' => true,
417 'show_ui' => true,
418 'show_in_menu' => false,
419 'show_in_admin_bar' => true,
420 'show_in_rest' => true,
421 'publicly_queryable' => true,
422 'supports' => [ 'title', 'editor', 'elementor', 'revisions' ],
423 'capability_type' => 'post',
424 'capabilities' => [
425 'create_posts' => 'do_not_allow', // Prior to Wordpress 4.5, this was false.
426 ],
427 'map_meta_cap' => true,
428 );
429
430 register_post_type( self::SELLKIT_STEP_POST_TYPE, $args );
431 }
432
433 /**
434 * Edit step name or slug.
435 *
436 * @since 1.1.0
437 * @param string $funnel_id Funnel Id.
438 */
439 public function edit_step_name_and_slug( $funnel_id ) {
440 $sub_action_data = sellkit_post( 'sub_action_data' );
441 $title = ! empty( $sub_action_data['title'] ) ? $sub_action_data['title'] : '';
442 $slug = ! empty( $sub_action_data['slug'] ) ? $sub_action_data['slug'] : sanitize_title( $title );
443 $page_id = ! empty( $sub_action_data['page_id'] ) ? $sub_action_data['page_id'] : '';
444 $selected_step = $sub_action_data['selected_step'];
445
446 if ( empty( $page_id ) ) {
447 return;
448 }
449
450 wp_update_post( [
451 'ID' => $page_id,
452 'post_title' => $title,
453 'post_name' => $slug,
454 ] );
455
456 if ( get_post_field( 'post_name', $page_id ) === $slug ) {
457 return;
458 }
459
460 $post_data = get_post_meta( $funnel_id, 'nodes', true );
461 $post_data[ $selected_step ]['slug'] = get_post_field( 'post_name', $page_id );
462
463 update_post_meta( $funnel_id, 'nodes', $post_data );
464 }
465
466 /**
467 * Delete step page id.
468 *
469 * @since 1.1.0
470 */
471 public function delete_related_page() {
472 $sub_action_data = sellkit_post( 'sub_action_data' );
473
474 if ( empty( $sub_action_data['page_id'] ) ) {
475 return;
476 }
477
478 wp_delete_post( $sub_action_data['page_id'], true );
479 }
480
481 /**
482 * Flash rules.
483 *
484 * @since 1.1.0
485 */
486 public function sellkit_steps_flush_rules() {
487 $rules = get_option( 'rewrite_rules' );
488
489 if ( ! isset( $rules['sellkit_step/[^/]+/([^/]+)/?$'] ) ) {
490 flush_rewrite_rules();
491 }
492 }
493
494 /**
495 * Make publish steps page.
496 *
497 * @since 1.1.0
498 * @param string $funnel_id Funnel Id.
499 * @param string $new_status Status.
500 */
501 public function update_steps_post_status( $funnel_id, $new_status ) {
502 $steps = get_post_meta( $funnel_id, 'sellkit_steps', true );
503
504 foreach ( $steps as $step ) {
505 if ( $step['page_id'] === $funnel_id ) {
506 continue;
507 }
508
509 wp_update_post(array(
510 'ID' => $step['page_id'],
511 'post_status' => $new_status,
512 ) );
513 }
514 }
515 }
516
517 Sellkit_Admin_Steps::get_instance();
518