PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / trunk
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster vtrunk
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 / components / class-list-table.php

class-list-table.php in SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster trunk, at includes/admin/components/class-list-table.php

582 lines 15.4 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 use Elementor\Plugin;
6 use Sellkit\Admin\Funnel\Importer\Ajax_Handler;
7 use Sellkit\Global_Checkout\Checkout;
8
9 /**
10 * List Table component class.
11 *
12 * @since 1.1.0
13 * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
14 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
15 */
16 class Sellkit_Admin_List_Table {
17
18 /**
19 * Class instance.
20 *
21 * @since 1.1.0
22 * @var Sellkit_Admin_List_Table
23 */
24 private static $instance = null;
25
26 /**
27 * Get a class instance.
28 *
29 * @since 1.1.0
30 *
31 * @return Sellkit_Admin_List_Table Class instance.
32 */
33 public static function get_instance() {
34 if ( null === self::$instance ) {
35 self::$instance = new self();
36 }
37
38 return self::$instance;
39 }
40
41 /**
42 * Class constructor.
43 *
44 * @since 1.1.0
45 */
46 public function __construct() {
47 add_action( 'wp_ajax_sellkit_list_table_posts', [ $this, 'get_posts' ] );
48 add_action( 'wp_ajax_sellkit_list_table_post', [ $this, 'handle_post' ] );
49 }
50
51 /**
52 * Get posts.
53 *
54 * @since 1.1.0
55 */
56 public function get_posts() {
57 check_ajax_referer( 'sellkit', 'nonce' );
58
59 // Sanitize.
60 $post_type = sellkit_htmlspecialchars( INPUT_POST, 'post_type' );
61 $paged = filter_input( INPUT_POST, 'paged', FILTER_SANITIZE_NUMBER_INT );
62 $posts_per_page = filter_input( INPUT_POST, 'posts_per_page', FILTER_SANITIZE_NUMBER_INT );
63 $search_args = filter_input( INPUT_POST, 'args', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
64 $page = filter_input( INPUT_POST, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
65 $list_status = isset( $_POST['list_status'] ) ? sanitize_text_field( wp_unslash( $_POST['list_status'] ) ) : 'all';
66 if ( ! in_array( $list_status, [ 'all', 'publish', 'draft' ], true ) ) {
67 $list_status = 'all';
68 }
69
70 /**
71 * Filter List Table query arguments.
72 *
73 * @since 1.1.0
74 *
75 * @param array $args The query arguments.
76 */
77 $args = apply_filters( "sellkit_list_table_{$post_type}_args", [
78 'post_type' => $post_type,
79 'paged' => $paged,
80 'posts_per_page' => ! empty( $posts_per_page ) ? $posts_per_page : 20,
81 'orderby' => 'ID',
82 'order' => 'DESC',
83 ] );
84
85 if ( is_array( $search_args ) && ! empty( $search_args ) ) {
86 foreach ( $search_args as $search_arg ) {
87 $args[ $search_arg['param'] ] = $search_arg['value'];
88 }
89 }
90
91 $not_in = $this->get_list_table_post__not_in( $post_type, $page );
92 if ( ! empty( $not_in ) ) {
93 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Exclude global checkout funnel from list.
94 $args['post__not_in'] = $not_in;
95 }
96
97 if ( 'publish' === $list_status ) {
98 $args['post_status'] = 'publish';
99 } elseif ( 'draft' === $list_status ) {
100 $args['post_status'] = 'draft';
101 } else {
102 $args['post_status'] = [ 'publish', 'draft', 'pending', 'private', 'future' ];
103 }
104
105 // Query.
106 $query = new \WP_Query( $args );
107
108 /**
109 * Filter List Table query posts.
110 *
111 * @since 1.1.0
112 *
113 * @param array $args The taxonomy arguments.
114 */
115 $posts = apply_filters( "sellkit_list_table_{$post_type}_posts", $query->posts );
116 $posts = $this->prepare_posts_for_list_table( $posts );
117
118 /**
119 * Filter List Table columns.
120 *
121 * @since 1.1.0
122 *
123 * @param array $args The columns headings and values.
124 */
125 $columns = apply_filters( "sellkit_list_table_{$post_type}_columns", [
126 'labels' => [],
127 'values' => [],
128 ], $posts );
129
130 $list_counts = $this->get_list_status_counts( $post_type, $page );
131
132 // Send response.
133 wp_send_json_success( [
134 'posts' => $posts,
135 'max_num_pages' => $query->max_num_pages,
136 'columns' => $columns,
137 'list_counts' => $list_counts,
138 ] );
139 }
140
141 /**
142 * Post IDs excluded from list queries (e.g. global checkout funnel).
143 *
144 * @since 2.4.0
145 *
146 * @param string $post_type Post type.
147 * @param string $page Admin page slug context.
148 * @return int[]
149 */
150 private function get_list_table_post__not_in( $post_type, $page ) {
151 if ( 'sellkit-funnels' === $post_type && 'checkout' !== $page ) {
152 $global_checkout_id = (int) get_option( Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, 0 );
153 if ( $global_checkout_id > 0 ) {
154 return [ $global_checkout_id ];
155 }
156 }
157 return [];
158 }
159
160 /**
161 * Counts for All / Active / Inactive list filters (respects post__not_in).
162 *
163 * @since 2.4.0
164 *
165 * @param string $post_type Post type.
166 * @param string $page Admin page slug context.
167 * @return array{all:int,publish:int,draft:int}
168 */
169 private function get_list_status_counts( $post_type, $page ) {
170 $not_in = $this->get_list_table_post__not_in( $post_type, $page );
171 $base = [
172 'post_type' => $post_type,
173 'posts_per_page' => -1,
174 'fields' => 'ids',
175 'no_found_rows' => false,
176 ];
177 if ( ! empty( $not_in ) ) {
178 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
179 $base['post__not_in'] = $not_in;
180 }
181
182 $statuses_all = [ 'publish', 'draft', 'pending', 'private', 'future' ];
183 $q_all = new \WP_Query( array_merge( $base, [ 'post_status' => $statuses_all ] ) );
184 $q_pub = new \WP_Query( array_merge( $base, [ 'post_status' => 'publish' ] ) );
185 $q_dra = new \WP_Query( array_merge( $base, [ 'post_status' => 'draft' ] ) );
186
187 return [
188 'all' => (int) $q_all->found_posts,
189 'publish' => (int) $q_pub->found_posts,
190 'draft' => (int) $q_dra->found_posts,
191 ];
192 }
193
194 /**
195 * Add formatted published / modified dates for list table rows (JSON).
196 *
197 * @since 2.4.0
198 *
199 * @param array $posts Posts from WP_Query.
200 * @return array
201 */
202 private function prepare_posts_for_list_table( $posts ) {
203 if ( empty( $posts ) || ! is_array( $posts ) ) {
204 return $posts;
205 }
206
207 $date_time_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
208
209 foreach ( $posts as $key => $post ) {
210 if ( ! $post instanceof \WP_Post ) {
211 continue;
212 }
213
214 $row = array_merge(
215 $post->to_array(),
216 [
217 'custom_date' => get_the_date( 'M d, Y', $post ),
218 'custom_date_with_time' => mysql2date( $date_time_format, $post->post_date ),
219 'custom_modified_date' => get_the_modified_date( 'M d, Y', $post ),
220 'custom_modified_date_with_time' => mysql2date( $date_time_format, $post->post_modified ),
221 ]
222 );
223
224 $posts[ $key ] = (object) $row;
225 }
226
227 return $posts;
228 }
229
230 /**
231 * Handle post actions.
232 *
233 * @since 1.1.0
234 */
235 public function handle_post() {
236 check_ajax_referer( 'sellkit', 'nonce' );
237
238 // Sanitize.
239 $post = filter_input( INPUT_POST, 'post', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
240 $sub_action = sellkit_htmlspecialchars( INPUT_POST, 'sub_action' );
241
242 if ( is_array( $post ) && count( $post ) < 3 ) {
243 $post = (array) get_post( $post['ID'] );
244 }
245
246 call_user_func( [ $this, "handle_post_{$sub_action}" ], $post );
247 }
248
249 /**
250 * Handle post toggle status.
251 *
252 * @since 1.1.0
253 * @param array $post Post Array.
254 */
255 private function handle_post_toggle_status( $post ) {
256 // Format proper post status.
257 $post_status = ( 'publish' === $post['post_status'] ) ? 'draft' : 'publish';
258
259 // Update post status.
260 $result = wp_update_post( [
261 'ID' => $post['ID'],
262 'post_status' => $post_status,
263 ], true );
264
265 do_action( "sellkit_after_toggle_status_{$post['post_type']}", $post['ID'], $post_status );
266
267 // Handle error.
268 if ( is_wp_error( $result ) ) {
269 wp_send_json_error( $result );
270 }
271
272 // Send success response.
273 wp_send_json_success( $result );
274 }
275
276 /**
277 * Duplicate post.
278 *
279 * @since 1.1.0
280 * @param array $post Post Array.
281 */
282 private function handle_post_duplicate_post( $post ) {
283 $main_post_id = $post['ID'];
284
285 unset( $post['ID'] );
286
287 $post['post_title'] = sprintf( '%1s - %2s', esc_html( $post['post_title'] ), esc_html__( 'Clone', 'sellkit' ) );
288 $post['post_status'] = 'draft';
289
290 $post_id = wp_insert_post( $post );
291
292 $main_post_meta = get_post_custom( $main_post_id );
293
294 foreach ( $main_post_meta as $key => $value ) {
295 update_post_meta( $post_id, $key, maybe_unserialize( $value[0] ) );
296 }
297
298 // Handle error.
299 if ( is_wp_error( $post_id ) ) {
300 wp_send_json_error( $post_id );
301 }
302
303 // Send success response.
304 wp_send_json_success( $post_id );
305 }
306
307 /**
308 * Handle post remove.
309 *
310 * @since 1.1.0
311 * @param array $post Post array.
312 */
313 private function handle_post_remove( $post ) {
314 do_action( 'sellkit_funnels_after_delete_posts_' . $post['post_type'], $post );
315 // Delete post.
316 $result = wp_delete_post( $post['ID'] );
317
318 // Handle error.
319 if ( ! is_object( $result ) ) {
320 wp_send_json_error( $result );
321 }
322
323 // Send success response.
324 wp_send_json_success( $result );
325 }
326
327 /**
328 * Duplicate funnel item.
329 *
330 * @since 1.5.0
331 * @param array $post Post Array.
332 */
333 private function handle_post_duplicate_single_funnel( $post ) {
334 $post_id = $post['ID'];
335 $parent_title = sprintf( '%1s - %2s', esc_html( $post['post_title'] ), esc_html__( 'Clone', 'sellkit' ) );
336 $funnel_data = get_post_meta( $post_id, 'nodes', true );
337 $nodes = true;
338
339 if ( empty( $funnel_data ) ) {
340 $funnel_data = get_post_meta( $post_id, 'sellkit_steps', true );
341 $nodes = false;
342 }
343
344 $funnel_slug = get_post_meta( $post_id, 'sellkit_post_slug', true );
345
346 // Let's create parent funnel post first.
347 $new_funnel_id = wp_insert_post( [
348 'post_title' => $parent_title,
349 'post_status' => 'draft',
350 'post_type' => 'sellkit-funnels',
351 'meta_input' => [
352 'sellkit_post_slug' => $funnel_slug,
353 ],
354 ] );
355
356 // Loop through old parent data and create steps posts of new funnel.
357 foreach ( $funnel_data as $key => $funnel_item ) {
358 $old_id = $funnel_item['page_id'];
359
360 // Create steps.
361 $title = $funnel_item['title'];
362 $args = [
363 'post_type' => 'sellkit_step',
364 'post_title' => esc_html( $title ),
365 'post_status' => get_post_status( $old_id ),
366 ];
367
368 $args = $this->handle_post_content( $args, $old_id );
369
370 $new_id = wp_insert_post( $args );
371
372 // Update new parent funnel data with new steps id.
373 $funnel_data[ $key ]['page_id'] = $new_id;
374 $funnel_data[ $key ]['funnel_id'] = $new_funnel_id;
375
376 // Add old steps meta data to new steps.
377 $old_data = get_post_meta( $old_id );
378
379 foreach ( $old_data as $key => $data ) {
380 if ( 'step_data' === $key ) {
381 $data[0] = str_replace( $post_id, $new_funnel_id, $data[0] );
382 }
383
384 if ( '_elementor_data' === $key && '[' === substr( $data[0], 0, 1 ) ) {
385 update_post_meta( $new_id, $key, maybe_unserialize( wp_slash( $data[0] ) ) );
386
387 continue;
388 }
389
390 update_post_meta( $new_id, $key, maybe_unserialize( $data[0] ) );
391 }
392
393 if ( class_exists( 'Elementor\Plugin' ) ) {
394 // Also save post in elementor way. after all it's elementor post.
395 $document = Plugin::$instance->documents->get( $new_id );
396 $document->save( [] );
397 }
398 }
399
400 // At the end update parent main meta.
401 if ( false === $nodes ) {
402 update_post_meta( $new_funnel_id, 'sellkit_steps', $funnel_data );
403 } else {
404 update_post_meta( $new_funnel_id, 'nodes', $funnel_data );
405 }
406
407 wp_send_json_success();
408 }
409
410 /**
411 * Handle post content for templates without elementor.
412 *
413 * @param array $args funnel nodes arguments.
414 * @param integer $old_id node id.
415 * @since 1.5.0
416 * @return array
417 */
418 private function handle_post_content( $args, $old_id ) {
419 if ( empty( $old_id ) ) {
420 return $args;
421 }
422
423 $is_elementor = get_post_meta( $old_id, '_elementor_data', true );
424
425 if ( ! empty( $is_elementor ) ) {
426 return $args;
427 }
428
429 $post_content = get_post( $old_id );
430 $content = $post_content->post_content;
431
432 return array_merge( $args, [ 'post_content' => $content ] );
433 }
434
435 /**
436 * Export single funnel.
437 *
438 * @since 1.5.0
439 * @param array $post Post Array.
440 */
441 private function export_single_funnel( $post ) {
442 $post_id = $post['ID'];
443 $data['funnel']['id'] = $post_id;
444 $data['funnel']['name'] = $post['post_name'];
445 $data['funnel']['title'] = $post['post_title'];
446 $data['funnel']['post_type'] = $post['post_type'];
447 $data['funnel']['post_status'] = $post['post_status'];
448 $data['funnel']['sellkit_steps'] = get_post_meta( $post_id, 'nodes', true );
449
450 // Integration with flowchart.
451 if ( empty( $data['funnel']['sellkit_steps'] ) ) {
452 $data['funnel']['sellkit_steps'] = get_post_meta( $post_id, 'sellkit_steps', true );
453 }
454
455 // Check for steps.
456 if ( ! is_array( $data['funnel']['sellkit_steps'] ) ) {
457 return $data;
458 }
459
460 foreach ( $data['funnel']['sellkit_steps'] as $step ) {
461 if ( ! array_key_exists( 'page_id', $step ) ) {
462 continue;
463 }
464
465 $id = $step['page_id'];
466 $post = get_post( $id );
467 $export_data = get_post_meta( $id );
468
469 $data['step'][ $id ]['ID'] = $post->ID;
470 $data['step'][ $id ]['name'] = $post->post_name;
471 $data['step'][ $id ]['title'] = $post->post_title;
472 $data['step'][ $id ]['post_type'] = $post->post_type;
473 $data['step'][ $id ]['post_status'] = $post->post_status;
474 $data['step'][ $id ]['post_content'] = $post->post_content;
475 $data['step'][ $id ]['page_id'] = $id;
476
477 foreach ( $export_data as $key => $meta ) {
478 if ( '_elementor_data' === $key && defined( 'ELEMENTOR_VERSION' ) ) {
479 $document = Plugin::$instance->documents->get( $id );
480 $template_data = $document->get_export_data();
481
482 $data['step'][ $id ]['meta'][ $key ] = wp_json_encode( $template_data['content'] );
483 continue;
484 }
485
486 $data['step'][ $id ]['meta'][ $key ] = $meta[0];
487 }
488 }
489
490 return $data;
491 }
492
493 /**
494 * Handle exporting single funnel by ajax.
495 *
496 * @since 1.5.0
497 * @param array $post Post Array.
498 */
499 private function handle_post_export_single_funnel( $post ) {
500 $data = [];
501 $data['type'] = 'single';
502 $data['data'] = $this->export_single_funnel( $post );
503
504 wp_send_json_success( $data );
505 }
506
507 /**
508 * Handle exporting single funnel by ajax.
509 *
510 * @since 1.5.0
511 */
512 private function handle_post_export_all_funnel() {
513 $data = [];
514 $data['type'] = 'multi';
515
516 $args = [
517 'post_type' => 'sellkit-funnels',
518 'posts_per_page' => -1,
519 ];
520
521 $posts = new \WP_Query( $args );
522 $posts = $posts->posts;
523
524 if ( empty( $posts ) ) {
525 wp_send_json_error();
526 }
527
528 $global_checkout_id = intval( get_option( Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, 0 ) );
529
530 foreach ( $posts as $post ) {
531 if ( $post->ID === $global_checkout_id ) {
532 continue;
533 }
534
535 $post = (array) $post;
536
537 $data['data'][] = $this->export_single_funnel( $post );
538 }
539
540 wp_send_json_success( $data );
541 }
542
543 /**
544 * Handle import funnel.
545 *
546 * @since 1.5.0
547 */
548 private function handle_post_import_funnel() {
549 $json = filter_input( INPUT_POST, 'funnel', FILTER_DEFAULT );
550 $page = filter_input( INPUT_POST, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
551 $data = json_decode( $json );
552
553 if ( 'multi' === $data->type && 'checkout' === $page ) {
554 wp_send_json_error( esc_html__( 'You can not import multiple funnels to the global checkout.', 'sellkit' ) );
555 }
556
557 $ajax_handler = new Ajax_Handler();
558
559 if ( 'single' === $data->type ) {
560 $funnel_data = $data->data->funnel;
561 $steps_data = $data->data->step;
562 $funnel_id = $ajax_handler->import_funnel_by_data( $funnel_data, $steps_data );
563 }
564
565 if ( 'multi' === $data->type ) {
566 foreach ( $data->data as $item ) {
567 $funnel_data = $item->funnel;
568 $steps_data = $item->step;
569 $funnel_id = $ajax_handler->import_funnel_by_data( $funnel_data, $steps_data );
570 }
571 }
572
573 if ( 'checkout' === $page && (int) $funnel_id > 0 ) {
574 update_option( Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, $funnel_id );
575 }
576
577 wp_send_json_success();
578 }
579 }
580
581 Sellkit_Admin_List_Table::get_instance();
582