PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / 2.3.0
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster v2.3.0
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-posts.php

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

492 lines 12.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 * Forms class.
7 *
8 * @since 1.1.0
9 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
10 * @SuppressWarnings(PHPMD.NPathComplexity)
11 */
12 class Sellkit_Admin_Posts {
13
14 /**
15 * Class instance.
16 *
17 * @since 1.1.0
18 * @var Sellkit_Admin_Posts
19 */
20 private static $instance = null;
21
22 /**
23 * Sub Action.
24 *
25 * @since 1.1.0
26 * @var string
27 */
28 public $sub_action = '';
29
30 /**
31 * Page.
32 *
33 * @since 1.7.4
34 * @var string
35 */
36 public $page;
37
38 /**
39 * Get a class instance.
40 *
41 * @since 1.1.0
42 *
43 * @return Sellkit_Admin_Posts Class instance.
44 */
45 public static function get_instance() {
46 if ( null === self::$instance ) {
47 self::$instance = new self();
48 }
49
50 return self::$instance;
51 }
52
53 /**
54 * Class constructor.
55 *
56 * @since 1.1.0
57 */
58 public function __construct() {
59 add_action( 'wp_ajax_sellkit_post_save', [ $this, 'save_post' ] );
60 add_action( 'wp_ajax_sellkit_post_get', [ $this, 'get_post' ] );
61 add_action( 'wp_ajax_sellkit_post_remove', [ $this, 'remove_post' ] );
62 }
63
64 /**
65 * Remove post.
66 *
67 * @since 1.1.0
68 */
69 public function remove_post() {
70 check_ajax_referer( 'sellkit', 'nonce' );
71
72 $post_id = sellkit_htmlspecialchars( INPUT_POST, 'post_id' );
73
74 $result = wp_delete_post( $post_id );
75
76 if ( empty( $result ) ) {
77 wp_send_json_error( __( 'something went wrong', 'sellkit' ) );
78 }
79
80 wp_send_json_success( __( 'The process has been completed.', 'sellkit' ) );
81 }
82
83 /**
84 * Get post data.
85 *
86 * @since 1.1.0
87 */
88 public function get_post() {
89 check_ajax_referer( 'sellkit', 'nonce' );
90
91 $post_id = sellkit_htmlspecialchars( INPUT_GET, 'post_id' );
92 $page = sellkit_htmlspecialchars( INPUT_GET, 'page' );
93
94 $post_data = get_post_meta( $post_id );
95
96 // Get post meta (without key) returns array by default, Now converts them to singles.
97 $post_data = array_map( function( $meta ) {
98 return $meta[0];
99 }, $post_data );
100
101 if ( ! empty( $post_data['conditions'] ) ) {
102 $post_data['conditions'] = unserialize( $post_data['conditions'] ); // phpcs:ignore WordPress.PHP
103 }
104
105 if ( ! empty( $post_data['filters'] ) ) {
106 $post_data['filters'] = unserialize( $post_data['filters'] ); // phpcs:ignore WordPress.PHP
107 }
108
109 if ( ! empty( $post_data['sellkit_steps'] ) ) {
110 $post_data['sellkit_steps'] = unserialize( $post_data['sellkit_steps'] ); // phpcs:ignore WordPress.PHP
111 }
112
113 if ( ! empty( $post_data['nodes'] ) ) {
114 delete_post_meta( $post_id, 'sellkit_steps' );
115 unset( $post_data['sellkit_steps'] );
116
117 $new_node = $this->check_product_status( $post_data['nodes'] );
118
119 $post_data['nodes'] = $new_node;
120 }
121
122 $post_data['data_is_converted'] = false;
123
124 if ( empty( $post_data['nodes'] ) && ! empty( $post_data['sellkit_steps'] ) ) {
125 $nodes = self::convert_steps_to_nodes( $post_data['sellkit_steps'] );
126
127 update_post_meta( $post_id, 'nodes', $nodes );
128 delete_post_meta( $post_id, 'sellkit_steps' );
129 unset( $post_data['sellkit_steps'] );
130
131 $post_data['nodes'] = $nodes;
132 $post_data['data_is_converted'] = true;
133 }
134
135 $post_data['sellkit_post_title'] = html_entity_decode( get_the_title( $post_id ) );
136 $post_data['sellkit_post_slug'] = get_post_field( 'post_name', $post_id );
137 $post_data['sellkit_post_status'] = get_post_status( $post_id );
138
139 // Unset sales-page when user exploring global checkout page.
140 if ( 'checkout' === $page && ! empty( $post_data['nodes'] ) ) {
141 foreach ( $post_data['nodes'] as $key => $step ) {
142 if ( is_array( $step['type'] ) && 'sales-page' === $step['type']['key'] ) {
143 unset( $post_data['nodes']->$key );
144 }
145
146 if ( is_object( $step['type'] ) && 'sales-page' === $step['type']->key ) {
147 unset( $post_data['nodes']->$key );
148 }
149 }
150 }
151
152 wp_send_json_success( [ 'post_data' => $post_data ] );
153 }
154
155 /**
156 * Converts steps to nodes.
157 *
158 * @since 1.5.0
159 * @param array $steps Steps.
160 */
161 public static function convert_steps_to_nodes( $steps ) {
162 $nodes = [];
163 $final_steps = $steps;
164
165 $first_key = array_key_first( $steps );
166
167 if ( empty( $steps[ $first_key ]['targets'] ) ) {
168 $final_steps = self::add_decision_node_before_upsells( $steps );
169 }
170
171 foreach ( $final_steps as $key => $step ) {
172 $nodes[ $key ] = $step;
173
174 if ( ! empty( $step['type']->key ) && 'decision' !== $step['type']->key ) {
175 $nodes[ $key ]['targets'] = count( $final_steps ) > $key + 1 ? [ [ 'nodeId' => strval( $key + 1 ) ] ] : [];
176 }
177
178 if ( ! empty( $step['type']->key ) && 'upsell' === $step['type']->key && count( $final_steps ) > $key + 1 ) {
179 $nodes[ $key ]['targets'] = [
180 [
181 'nodeId' => strval( $key + 1 )
182 ],
183 [
184 'nodeId' => strval( $key + 1 )
185 ]
186 ];
187 }
188
189 if ( ! empty( $step['type']->key ) && 'decision' === $step['type']->key ) {
190 if ( is_object( $step['targets'][0] ) ) {
191 $step['targets'][0] = (array) $step['targets'][0];
192 }
193
194 if ( is_object( $step['targets'][1] ) ) {
195 $step['targets'][1] = (array) $step['targets'][1];
196 }
197
198 if ( empty( $final_steps[ $step['targets'][0]['nodeId'] ] ) ) {
199 $nodes[ $key ]['targets'][0] = '';
200 }
201
202 if ( empty( $final_steps[ $step['targets'][1]['nodeId'] ] ) ) {
203 $nodes[ $key ]['targets'][1] = '';
204 }
205 }
206
207 $nodes[ $key ]['jsx'] = $step['title'];
208 }
209
210 return (object) $nodes;
211 }
212
213 /**
214 * Adds decision node before upsells.
215 *
216 * @since 1.5.0
217 * @param array $steps Steps.
218 */
219 public static function add_decision_node_before_upsells( $steps ) {
220 $final_steps = [];
221 $upsell_num = 0;
222
223 foreach ( $steps as $key => $step ) {
224 $step['type'] = (object) $step['type'];
225 $step['data'] = ! empty( $step['data'] ) ? (object) $step['data'] : null;
226
227 if ( ! empty( $step['type']->key ) && 'upsell' === $step['type']->key ) {
228 $new_node_id = wp_insert_post( [
229 'post_type' => Sellkit_Admin_Steps::SELLKIT_STEP_POST_TYPE,
230 'post_title' => __( 'Decision', 'sellkit' ),
231 'post_name' => sanitize_title( __( 'Decision', 'sellkit' ) ),
232 'post_status' => 'publish',
233 ] );
234
235 $type_object = new stdClass();
236 $type_object->title = esc_html__( 'Decision', 'sellkit' );
237 $type_object->key = 'decision';
238
239 $decision_node = [
240 'title' => esc_html__( 'Decision', 'sellkit' ),
241 'type' => $type_object,
242 'current_target_index' => 0,
243 'page_id' => $new_node_id,
244 'slug' => get_post_field( 'post_name', $new_node_id ),
245 'funnel_id' => $step['funnel_id'],
246 'targets' => [
247 [
248 'nodeId' => strval( $key + 1 + $upsell_num )
249 ],
250 [
251 'nodeId' => strval( $key + 2 + $upsell_num )
252 ]
253 ],
254 'data' => [
255 'conditions' => ! empty( $step['data']->conditions ) ? $step['data']->conditions : []
256 ]
257 ];
258
259 update_post_meta( $new_node_id, 'step_data', $decision_node );
260
261 $final_steps[] = $decision_node;
262
263 $upsell_num++;
264 }
265
266 $final_steps[] = $step;
267 }
268
269 return $final_steps;
270 }
271
272 /**
273 * Save post.
274 *
275 * @since 1.1.0
276 */
277 public function save_post() {
278 check_ajax_referer( 'sellkit', 'nonce' );
279
280 $fields = sellkit_post( 'fields' );
281 $post_id = sellkit_htmlspecialchars( INPUT_POST, 'post_id' );
282 $post_type = sellkit_htmlspecialchars( INPUT_POST, 'post_type' );
283 $this->sub_action = sellkit_htmlspecialchars( INPUT_POST, 'sub_action' );
284 $this->page = sellkit_htmlspecialchars( INPUT_POST, 'page' );
285
286 $sanitized_fields = $this->sanitize_fields( $fields );
287
288 $this->validate_fields( $sanitized_fields );
289
290 $target_post_id = $this->handle_save_post( $sanitized_fields, $post_type, $post_id );
291
292 if ( empty( $target_post_id ) ) {
293 wp_send_json_error( [ __( 'something went wrong', 'sellkit' ) ] );
294 }
295
296 if ( $post_id ) {
297 wp_send_json_success( [
298 'post_id' => $post_id,
299 'message' => __( 'The update process has been completed.', 'sellkit' ),
300 ] );
301 }
302
303 wp_send_json_success( [
304 'post_id' => $target_post_id,
305 'message' => __( 'The creation process has been completed.', 'sellkit' ),
306 ] );
307 }
308
309 /**
310 * Update or add new post.
311 *
312 * @param array $data All data.
313 * @param string $post_type The post type.
314 * @param bool $post_id The post Id.
315 * @return bool|integer
316 *
317 * @since 1.1.0
318 * @SuppressWarnings(PHPMD.NPathComplexity)
319 */
320 private function handle_save_post( $data, $post_type, $post_id = false ) {
321 $title = sanitize_text_field( $data['sellkit_post_title'] );
322 $slug = ! empty( $data['sellkit_post_slug'] ) ? sanitize_text_field( $data['sellkit_post_slug'] ) : '';
323 $post_status = 'publish' === $data['sellkit_post_status'] ? 'publish' : 'draft';
324
325 if ( isset( $data['sellkit_global_checkout_header_footer_templates'] ) && 'checkout' === $this->page ) {
326 unset( $data['sellkit_global_checkout_header_footer_templates'] );
327 }
328
329 $post_args = [
330 'post_title' => $title,
331 'post_name' => $slug ?: sanitize_title( $title ),
332 'post_type' => $post_type,
333 'post_status' => $post_status,
334 ];
335
336 if ( ! $post_id ) {
337 $new_post_id = wp_insert_post( $post_args );
338 }
339
340 if ( $post_id ) {
341 $post_args = array_merge( $post_args, [ 'ID' => $post_id ] );
342 $new_post_id = wp_update_post( $post_args );
343
344 do_action( "sellkit_posts_after_saving_$post_type", $post_id );
345 }
346
347 if ( empty( $new_post_id ) || is_wp_error( $new_post_id ) ) {
348 return false;
349 }
350
351 foreach ( $data as $name => $value ) {
352 if ( 'sellkit_post_title' === $name || 'sellkit_post_status' === $name ) {
353 continue;
354 }
355
356 update_post_meta( $new_post_id, $name, $value );
357
358 if ( 'sellkit_steps' === $name || 'nodes' === $name ) {
359 do_action( 'sellkit_funnels_update_steps', $value, $new_post_id );
360 }
361 }
362
363 if ( empty( $data['conditions'] ) ) {
364 delete_post_meta( $new_post_id, 'conditions' );
365 }
366
367 do_action( "sellkit_funnels_after_$this->sub_action", $post_id );
368
369 if ( empty( $data['sellkit_steps'] ) ) {
370 delete_post_meta( $new_post_id, 'sellkit_steps' );
371 }
372
373 if ( empty( $data['nodes'] ) ) {
374 delete_post_meta( $new_post_id, 'nodes' );
375 }
376
377 if ( 'checkout' === $this->page ) {
378 update_option( 'sellkit_global_checkout_id', $new_post_id );
379 }
380
381 return $new_post_id;
382 }
383
384 /**
385 * Sanitize fields.
386 *
387 * @param array $fields Fields.
388 *
389 * @return array
390 *
391 * @since 1.1.0
392 */
393 private function sanitize_fields( $fields ) {
394 $new_fields = [];
395
396 foreach ( $fields as $key => $field ) {
397 if ( is_array( $fields[ $key ] ) ) {
398 $new_fields[ $key ] = $this->sanitize_fields( $fields[ $key ] );
399 }
400
401 $field_value = sanitize_meta( $key, $field, 'post' );
402
403 if ( ! is_array( $fields[ $key ] ) ) {
404 $new_fields[ $key ] = $field_value;
405 }
406 }
407
408 if ( ! empty( $new_fields['conditions'] ) ) {
409 $new_fields['conditions'] = sellkit_condition_row_sanitization( $new_fields['conditions'] );
410 }
411
412 if ( ! empty( $new_fields['filters'] ) ) {
413 $new_fields['filters'] = sellkit_filter_row_sanitization( $new_fields['filters'] );
414 }
415
416 return $new_fields;
417 }
418
419 /**
420 * Validate fields.
421 *
422 * @param array $fields Fields.
423 *
424 * @since 1.1.0
425 */
426 public function validate_fields( $fields ) {
427 $errors = [];
428 if ( empty( $fields['sellkit_post_title'] ) ) {
429 $errors[] = __( 'Post title should not be empty.', 'sellkit' );
430 }
431
432 if ( empty( $errors ) ) {
433 return;
434 }
435
436 wp_send_json_error( $errors );
437 }
438
439 /**
440 * Check product status
441 *
442 * @param mixed $nodes List of node data.
443 * @since 1.7.9
444 * @return array
445 */
446 public function check_product_status( $nodes ) {
447 $nodes = (object) unserialize( $nodes ); // phpcs:ignore WordPress.PHP
448 $new_node = [];
449
450 if ( ! class_exists( 'WooCommerce' ) ) {
451 return $new_node;
452 }
453
454 foreach ( $nodes as $index => $node ) {
455 if ( ! empty( $node['page_id'] ) ) {
456 $node['link'] = get_the_permalink( $node['page_id'] );
457 }
458
459 if ( 'checkout' !== $node['type']['key'] ) {
460 $new_node[ $index ] = $node;
461 continue;
462 }
463
464 if ( ! empty( $node['data']['products'] ) && ! empty( $node['data']['products']['list'] ) ) {
465 foreach ( $node['data']['products']['list'] as $key => $product ) {
466 $product_obj = wc_get_product( $key );
467
468 if ( empty( $product_obj ) ) {
469 unset( $node['data']['products']['list'][ $key ] );
470 }
471
472 $data = ! empty( $product_obj ) ? $product_obj->get_data() : [];
473
474 if ( ! empty( $data['status'] ) && 'trash' === $data['status'] ) {
475 unset( $node['data']['products']['list'][ $key ] );
476 }
477 }
478 }
479
480 $new_node[ $index ] = $node;
481 }
482
483 if ( empty( $new_node ) ) {
484 return $node;
485 }
486
487 return $new_node;
488 }
489 }
490
491 Sellkit_Admin_Posts::get_instance();
492