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

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