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-posts.php

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

474 lines 11.6 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 ) {
141 foreach ( $post_data['nodes'] as $key => $step ) {
142 if ( 'sales-page' === $step['type']->key ) {
143 unset( $post_data['nodes']->$key );
144 }
145 }
146 }
147
148 wp_send_json_success( [ 'post_data' => $post_data ] );
149 }
150
151 /**
152 * Converts steps to nodes.
153 *
154 * @since 1.5.0
155 * @param array $steps Steps.
156 */
157 public static function convert_steps_to_nodes( $steps ) {
158 $nodes = [];
159 $final_steps = $steps;
160
161 if ( empty( $steps[0]['targets'] ) ) {
162 $final_steps = self::add_decision_node_before_upsells( $steps );
163 }
164
165 foreach ( $final_steps as $key => $step ) {
166 $nodes[ $key ] = $step;
167
168 if ( ! empty( $step['type']->key ) && 'decision' !== $step['type']->key ) {
169 $nodes[ $key ]['targets'] = count( $final_steps ) > $key + 1 ? [ [ 'nodeId' => strval( $key + 1 ) ] ] : [];
170 }
171
172 if ( ! empty( $step['type']->key ) && 'upsell' === $step['type']->key && count( $final_steps ) > $key + 1 ) {
173 $nodes[ $key ]['targets'] = [
174 [
175 'nodeId' => strval( $key + 1 )
176 ],
177 [
178 'nodeId' => strval( $key + 1 )
179 ]
180 ];
181 }
182
183 if ( ! empty( $step['type']->key ) && 'decision' === $step['type']->key ) {
184 if ( is_object( $step['targets'][0] ) ) {
185 $step['targets'][0] = (array) $step['targets'][0];
186 }
187
188 if ( is_object( $step['targets'][1] ) ) {
189 $step['targets'][1] = (array) $step['targets'][1];
190 }
191
192 if ( empty( $final_steps[ $step['targets'][0]['nodeId'] ] ) ) {
193 $nodes[ $key ]['targets'][0] = '';
194 }
195
196 if ( empty( $final_steps[ $step['targets'][1]['nodeId'] ] ) ) {
197 $nodes[ $key ]['targets'][1] = '';
198 }
199 }
200
201 $nodes[ $key ]['jsx'] = $step['title'];
202 }
203
204 return (object) $nodes;
205 }
206
207 /**
208 * Adds decision node before upsells.
209 *
210 * @since 1.5.0
211 * @param array $steps Steps.
212 */
213 public static function add_decision_node_before_upsells( $steps ) {
214 $final_steps = [];
215 $upsell_num = 0;
216
217 foreach ( $steps as $key => $step ) {
218 $step['type'] = (object) $step['type'];
219 $step['data'] = (object) $step['data'];
220
221 if ( ! empty( $step['type']->key ) && 'upsell' === $step['type']->key ) {
222 $new_node_id = wp_insert_post( [
223 'post_type' => Sellkit_Admin_Steps::SELLKIT_STEP_POST_TYPE,
224 'post_title' => __( 'Decision', 'sellkit' ),
225 'post_name' => sanitize_title( __( 'Decision', 'sellkit' ) ),
226 'post_status' => 'publish',
227 ] );
228
229 $type_object = new stdClass();
230 $type_object->title = esc_html__( 'Decision', 'sellkit' );
231 $type_object->key = 'decision';
232
233 $decision_node = [
234 'title' => esc_html__( 'Decision', 'sellkit' ),
235 'type' => $type_object,
236 'current_target_index' => 0,
237 'page_id' => $new_node_id,
238 'slug' => get_post_field( 'post_name', $new_node_id ),
239 'funnel_id' => $step['funnel_id'],
240 'targets' => [
241 [
242 'nodeId' => strval( $key + 1 + $upsell_num )
243 ],
244 [
245 'nodeId' => strval( $key + 2 + $upsell_num )
246 ]
247 ],
248 'data' => [
249 'conditions' => ! empty( $step['data']->conditions ) ? $step['data']->conditions : []
250 ]
251 ];
252
253 update_post_meta( $new_node_id, 'step_data', $decision_node );
254
255 $final_steps[] = $decision_node;
256
257 $upsell_num++;
258 }
259
260 $final_steps[] = $step;
261 }
262
263 return $final_steps;
264 }
265
266 /**
267 * Save post.
268 *
269 * @since 1.1.0
270 */
271 public function save_post() {
272 check_ajax_referer( 'sellkit', 'nonce' );
273
274 $fields = sellkit_post( 'fields' );
275 $post_id = sellkit_htmlspecialchars( INPUT_POST, 'post_id' );
276 $post_type = sellkit_htmlspecialchars( INPUT_POST, 'post_type' );
277 $this->sub_action = sellkit_htmlspecialchars( INPUT_POST, 'sub_action' );
278 $this->page = sellkit_htmlspecialchars( INPUT_POST, 'page' );
279
280 $sanitized_fields = $this->sanitize_fields( $fields );
281
282 $this->validate_fields( $sanitized_fields );
283
284 $target_post_id = $this->handle_save_post( $sanitized_fields, $post_type, $post_id );
285
286 if ( empty( $target_post_id ) ) {
287 wp_send_json_error( [ __( 'something went wrong', 'sellkit' ) ] );
288 }
289
290 if ( $post_id ) {
291 wp_send_json_success( [
292 'post_id' => $post_id,
293 'message' => __( 'The update process has been completed.', 'sellkit' ),
294 ] );
295 }
296
297 wp_send_json_success( [
298 'post_id' => $target_post_id,
299 'message' => __( 'The creation process has been completed.', 'sellkit' ),
300 ] );
301 }
302
303 /**
304 * Update or add new post.
305 *
306 * @param array $data All data.
307 * @param string $post_type The post type.
308 * @param bool $post_id The post Id.
309 * @return bool|integer
310 *
311 * @since 1.1.0
312 * @SuppressWarnings(PHPMD.NPathComplexity)
313 */
314 private function handle_save_post( $data, $post_type, $post_id = false ) {
315 $title = sanitize_text_field( $data['sellkit_post_title'] );
316 $slug = ! empty( $data['sellkit_post_slug'] ) ? sanitize_text_field( $data['sellkit_post_slug'] ) : '';
317 $post_status = 'publish' === $data['sellkit_post_status'] ? 'publish' : 'draft';
318
319 $post_args = [
320 'post_title' => $title,
321 'post_name' => $slug ?: sanitize_title( $title ),
322 'post_type' => $post_type,
323 'post_status' => $post_status,
324 ];
325
326 if ( ! $post_id ) {
327 $new_post_id = wp_insert_post( $post_args );
328 }
329
330 if ( $post_id ) {
331 $post_args = array_merge( $post_args, [ 'ID' => $post_id ] );
332 $new_post_id = wp_update_post( $post_args );
333
334 do_action( "sellkit_posts_after_saving_$post_type", $post_id );
335 }
336
337 if ( empty( $new_post_id ) || is_wp_error( $new_post_id ) ) {
338 return false;
339 }
340
341 foreach ( $data as $name => $value ) {
342 if ( 'sellkit_post_title' === $name || 'sellkit_post_status' === $name ) {
343 continue;
344 }
345
346 update_post_meta( $new_post_id, $name, $value );
347
348 if ( 'sellkit_steps' === $name || 'nodes' === $name ) {
349 do_action( 'sellkit_funnels_update_steps', $value, $new_post_id );
350 }
351 }
352
353 if ( empty( $data['conditions'] ) ) {
354 delete_post_meta( $new_post_id, 'conditions' );
355 }
356
357 do_action( "sellkit_funnels_after_$this->sub_action", $post_id );
358
359 if ( empty( $data['sellkit_steps'] ) ) {
360 delete_post_meta( $new_post_id, 'sellkit_steps' );
361 }
362
363 if ( empty( $data['nodes'] ) ) {
364 delete_post_meta( $new_post_id, 'nodes' );
365 }
366
367 if ( 'checkout' === $this->page ) {
368 update_option( 'sellkit_global_checkout_id', $new_post_id );
369 }
370
371 return $new_post_id;
372 }
373
374 /**
375 * Sanitize fields.
376 *
377 * @param array $fields Fields.
378 *
379 * @return array
380 *
381 * @since 1.1.0
382 */
383 private function sanitize_fields( $fields ) {
384 $new_fields = [];
385
386 foreach ( $fields as $key => $field ) {
387 if ( is_array( $fields[ $key ] ) ) {
388 $new_fields[ $key ] = $this->sanitize_fields( $fields[ $key ] );
389 }
390
391 $field_value = sanitize_meta( $key, $field, 'post' );
392
393 if ( ! is_array( $fields[ $key ] ) ) {
394 $new_fields[ $key ] = $field_value;
395 }
396 }
397
398 if ( ! empty( $new_fields['conditions'] ) ) {
399 $new_fields['conditions'] = sellkit_condition_row_sanitization( $new_fields['conditions'] );
400 }
401
402 if ( ! empty( $new_fields['filters'] ) ) {
403 $new_fields['filters'] = sellkit_filter_row_sanitization( $new_fields['filters'] );
404 }
405
406 return $new_fields;
407 }
408
409 /**
410 * Validate fields.
411 *
412 * @param array $fields Fields.
413 *
414 * @since 1.1.0
415 */
416 public function validate_fields( $fields ) {
417 $errors = [];
418 if ( empty( $fields['sellkit_post_title'] ) ) {
419 $errors[] = __( 'Post title should not be empty.', 'sellkit' );
420 }
421
422 if ( empty( $errors ) ) {
423 return;
424 }
425
426 wp_send_json_error( $errors );
427 }
428
429 /**
430 * Check product status
431 *
432 * @param mixed $nodes List of node data.
433 * @since 1.7.9
434 * @return array
435 */
436 public function check_product_status( $nodes ) {
437 $nodes = (object) unserialize( $nodes ); // phpcs:ignore WordPress.PHP
438 $new_node = [];
439
440 foreach ( $nodes as $index => $node ) {
441 if ( 'checkout' !== $node['type']['key'] ) {
442 $new_node[ $index ] = $node;
443 continue;
444 }
445
446 if ( ! empty( $node['data']['products'] ) ) {
447 foreach ( $node['data']['products']['list'] as $key => $product ) {
448 $product_obj = wc_get_product( $key );
449
450 if ( empty( $product_obj ) ) {
451 unset( $node['data']['products']['list'][ $key ] );
452 }
453
454 $data = ! empty( $product_obj ) ? $product_obj->get_data() : [];
455
456 if ( ! empty( $data['status'] ) && 'trash' === $data['status'] ) {
457 unset( $node['data']['products']['list'][ $key ] );
458 }
459 }
460 }
461
462 $new_node[ $index ] = $node;
463 }
464
465 if ( empty( $new_node ) ) {
466 return $node;
467 }
468
469 return $new_node;
470 }
471 }
472
473 Sellkit_Admin_Posts::get_instance();
474