PluginProbe
Ultimate Store Kit / 3.1.2
Ultimate Store Kit v3.1.2
3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.0.7 3.0.5 3.0.4 3.0.3 3.0.2 trunk 1.5.0 1.5.1 1.5.2 1.6.1 1.6.2 1.6.3 1.6.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 92 releases
ultimate-store-kit / includes / builder / builder-cpt.php

builder-cpt.php in Ultimate Store Kit 3.1.2, at includes/builder/builder-cpt.php

512 lines 16.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UltimateStoreKit\Includes\Builder;
4
5 use UltimateStoreKit\Base\Singleton;
6
7 if (! defined('ABSPATH')) {
8 exit;
9 }
10
11 class Builder_Cpt {
12
13 use Singleton;
14
15 public function init_hooks() {
16 $builderCpt = Meta::POST_TYPE;
17
18 add_action('init', [$this, 'registered_post_type']);
19 add_action('admin_footer', [$this, 'add_modal_html'], 1);
20 add_action('delete_post', [$this, 'trashed_or_delete_post'], 10, 2);
21 add_action('trashed_post', [$this, 'trashed_or_delete_post'], 10, 1);
22
23 add_action('wp_ajax_ultimate_store_kit_builder_create_template', [$this, 'create_builder_template']);
24 add_action('wp_ajax_ultimate_store_kit_builder_get_edit_template', [$this, 'get_builder_template_action']);
25 add_filter("manage_{$builderCpt}_posts_columns", [$this, 'set_post_columns']);
26 add_action("manage_{$builderCpt}_posts_custom_column", [$this, 'set_custom_column_value'], 10, 2);
27 add_filter('post_row_actions', [$this, 'post_row_actions_filter'], 20, 2);
28
29 // Simple WPML fix
30 if (function_exists('icl_object_id')) {
31 add_filter('elementor/editor/before_enqueue_scripts', [$this, 'fix_wpml_elementor_data'], 1);
32 }
33
34 if (is_admin()) {
35 add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts'], 1);
36 add_action('admin_menu', [$this, 'add_admin_menu'], 202);
37 add_action('restrict_manage_posts', [$this, 'add_filter']);
38 add_filter('parse_query', [$this, 'parse_query_filter']);
39 }
40
41 // $this->resetTemplateCache();
42 }
43
44 public function resetTemplateCache() {
45 global $wpdb;
46
47 $postType = Meta::POST_TYPE;
48
49 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Rebuilds the template cache; this is the call that populates it.
50 $query = $wpdb->get_results($wpdb->prepare(
51 "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_type, {$wpdb->posts}.post_status, {$wpdb->postmeta}.meta_value as template_type
52 FROM {$wpdb->posts}
53 LEFT JOIN {$wpdb->postmeta}
54 ON {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID
55 WHERE 1=1
56 AND {$wpdb->posts}.post_type = %s
57 AND {$wpdb->postmeta}.meta_key = %s
58 ORDER BY {$wpdb->posts}.post_date DESC",
59 $postType,
60 '_ultimate_store_kit_template_type'
61 ));
62 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
63
64 foreach ($query as $q) {
65 if (! $q->template_type) {
66 continue;
67 }
68
69 if ($q->post_status == 'publish') {
70 Meta::update_template_option($q->template_type, $q->ID);
71 } else {
72 Meta::delete_template_option($q->template_type);
73 }
74 }
75 }
76
77 public function trashed_or_delete_post($postId) {
78 if (get_post_type($postId) != Meta::POST_TYPE) {
79 return;
80 }
81
82 if ($template = get_post_meta($postId, Meta::TEMPLATE_TYPE, true)) {
83 Meta::delete_template_option($template);
84 }
85 }
86
87 public function post_row_actions_filter($actions, $post) {
88
89 global $typenow;
90
91 if ($typenow !== Meta::POST_TYPE) {
92 return $actions;
93 }
94
95 if (isset($actions['edit_with_elementor'])) {
96 unset($actions['edit_with_elementor']);
97 }
98
99 if (get_post_meta($post->ID, Meta::EDIT_WITH, true) == 'gutenberg') {
100 $actions['usk_edit_with_gutenberg'] = sprintf(
101 '<a href="%1$s">%2$s</a>',
102 add_query_arg(['post' => $post->ID, 'action' => 'edit'], admin_url('post.php')),
103 esc_html__('Edit with Gutenberg', 'ultimate-store-kit')
104 );
105 }
106
107 if (get_post_meta($post->ID, Meta::EDIT_WITH, true) == 'elementor') {
108 $actions['usk_edit_with_elementor'] = sprintf(
109 '<a href="%1$s">%2$s</a>',
110 add_query_arg(
111 [
112 'post' => $post->ID,
113 'action' => 'elementor',
114 'usk-template' => 1
115 ],
116 admin_url('post.php')
117 ),
118 esc_html__('Edit with Elementor', 'ultimate-store-kit')
119 );
120 } else {
121 // Always offer Elementor edit option regardless of stored preference
122 $actions['usk_edit_with_elementor'] = sprintf(
123 '<a href="%1$s">%2$s</a>',
124 add_query_arg(
125 [
126 'post' => $post->ID,
127 'action' => 'elementor',
128 'usk-template' => 1
129 ],
130 admin_url('post.php')
131 ),
132 esc_html__('Edit with Elementor', 'ultimate-store-kit')
133 );
134 }
135
136 $editActionLink = sprintf(
137 '<a href="%1$s" data-id="%2$s" >%3$s</a>',
138 'javascript:void(0)',
139 $post->ID,
140 esc_html__('Edit', 'ultimate-store-kit')
141 );
142
143 if (isset($actions['edit']) && apply_filters('ultimate_store_kit_remove_action_edit_link', __return_true())) {
144 unset($actions['edit']);
145 }
146
147 return array_slice($actions, 0, 1, true) + ['usk-edit-action' => $editActionLink] + array_slice($actions, 1, null, true);
148 }
149
150 public function set_post_columns($columns) {
151 return array_slice($columns, 0, 2, true) + ['template_type' => 'Type', 'is_enabled' => 'Status'] + array_slice($columns, 2, null, true);
152 }
153
154 public function set_custom_column_value($column, $post_id) {
155 $templateType = get_post_meta(
156 $post_id,
157 Meta::TEMPLATE_TYPE,
158 true
159 );
160
161 switch ($column) {
162 case 'template_type':
163 $postType = Builder_Template_Helper::getTemplatePostTypeByIndex($templateType);
164 echo esc_html(Builder_Template_Helper::getTemplateByIndex($templateType));
165 if (isset($postType->name)) {
166 echo ' <strong>-- ' . esc_html(ucwords($postType->name)) . '</strong>';
167 }
168 break;
169 case 'is_enabled':
170 echo (Builder_Template_Helper::getTemplateId($templateType) == $post_id ? 'Active' : 'Inactive');
171 break;
172 }
173 }
174
175 public function add_filter() {
176 global $typenow;
177
178 if ($typenow !== Meta::POST_TYPE) {
179 return;
180 }
181
182 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list-table filter; the value only re-selects the current dropdown option.
183 $selected = isset($_GET['type']) ? sanitize_text_field(wp_unslash($_GET['type'])) : '';
184 ?>
185 <select name="type" id="type">
186 <option value="all" <?php
187 selected('all', $selected); ?>><?php
188 esc_html_e(
189 'Template Type ',
190 'ultimate-store-kit'
191 ); ?></option>
192 <?php
193 $templates = Builder_Template_Helper::templateForSelectDropdown();
194 // It is single
195 if (count($templates) == 1) {
196 $templateKey = array_key_last($templates);
197 $template = $templates[$templateKey];
198 foreach ($template as $key => $item) :
199 $selectValue = "{$templateKey}_$key";
200 ?>
201 <option value="<?php
202 echo esc_attr($selectValue) ?>"><?php
203 echo wp_kses_post($item) ?></option>
204 <?php
205 endforeach;
206 }
207
208 if (count($templates) > 1) {
209 foreach ($templates as $keys => $items) :
210 $label = ucwords(str_replace(
211 ['-', '_'],
212 [' '],
213 $keys
214 ));
215 if (is_array($items)) {
216 ?>
217 <optgroup label="<?php
218 echo esc_attr($label) ?>"><?php
219 foreach ($items as $key => $item) :
220 $itemValue = "{$keys}_$key"
221 ?>
222 <option value="<?php
223 echo esc_attr($itemValue) ?>" <?php
224 selected($key, $selected); ?>><?php
225 echo wp_kses_post($item) ?></option>
226 <?php
227 endforeach;
228 ?>
229 </optgroup>
230 <?php
231 }
232 endforeach;
233 }
234 ?>
235 </select>
236 <?php
237 }
238
239 public function parse_query_filter($query) {
240 global $pagenow, $typenow;
241
242 if ($typenow !== Meta::POST_TYPE) {
243 return;
244 }
245
246 // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.DB.SlowDBQuery -- Admin list-table filter; the value only narrows the query that is already running.
247 if (
248 'edit.php' == $pagenow
249 && isset($_GET['type'])
250 && $_GET['type'] != ''
251 && $_GET['type'] != 'all'
252 ) {
253 $requested_type = sanitize_text_field(wp_unslash($_GET['type']));
254
255 if (Builder_Template_Helper::getTemplateByIndex($requested_type)) {
256 $query->query_vars['meta_key'] = Meta::TEMPLATE_TYPE;
257 $query->query_vars['meta_value'] = $requested_type;
258 $query->query_vars['meta_compare'] = '=';
259 }
260 }
261 // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.DB.SlowDBQuery
262 }
263
264
265 public function create_builder_template() {
266
267 if (! current_user_can('manage_options')) {
268 wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403);
269 }
270
271 $data = [];
272 // The form arrives as one serialized string, so it is unslashed before parsing
273 // and every field is sanitized individually below — parse_str itself does not
274 // sanitize anything.
275 if (isset($_POST['data'])) {
276 parse_str(wp_unslash($_POST['data']), $data); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Serialized form string; every parsed field is sanitized individually below.
277 }
278
279 $nonce = isset($data['nonce']) ? sanitize_text_field($data['nonce']) : '';
280
281 if (! wp_verify_nonce($nonce, 'usk-builder')) {
282 wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403);
283 }
284
285 $templateId = isset($data['template_id']) ? absint($data['template_id']) : 0;
286 $name = isset($data['template_name']) ? sanitize_text_field($data['template_name']) : '';
287 $type = isset($data['template_type']) ? sanitize_text_field($data['template_type']) : '';
288 $editWith = isset($data['edit_with']) ? sanitize_key($data['edit_with']) : 'elementor'; //gutenberg
289 $isEnabled = (isset($data['template_status']) && $data['template_status']) == 1 ? 1 : 0;
290
291 // Only these two editors are ever handled below, and the value ends up in a
292 // redirect URL, so anything else falls back to the default.
293 if (! in_array($editWith, ['elementor', 'gutenberg'], true)) {
294 $editWith = 'elementor';
295 }
296
297 $errors = [];
298
299 if (empty($name)) {
300 $errors['template_name'] = 'Field is required';
301 }
302
303 if (empty($type)) {
304 $errors['template_type'] = 'Field is required';
305 } else {
306 if (! Builder_Template_Helper::getTemplateByIndex($type)) {
307 $errors['template_type'] = 'Invalid section';
308 }
309 }
310
311
312 if (count($errors) > 0) {
313 wp_send_json_error(['success' => false, 'errors_arr' => $errors], 422);
314 }
315
316 $page_data = [
317 'post_status' => 'publish',
318 'post_type' => Meta::POST_TYPE,
319 'post_author' => get_current_user_id(),
320 'post_title' => $name,
321 'comment_status' => 'closed',
322 'meta_input' => [
323 Meta::EDIT_WITH => $editWith,
324 Meta::TEMPLATE_TYPE => $type,
325 ],
326 ];
327
328 global $wp_rewrite;
329 $wp_rewrite->flush_rules();
330 $wp_rewrite->init();
331
332 if ($templateId) {
333 $page_data['ID'] = $templateId;
334 }
335
336 $post_id = wp_insert_post($page_data);
337
338 if ($isEnabled == 1) {
339 Meta::update_template_option($type, $post_id);
340 } else {
341 if (Meta::get_template_option($type) == $post_id) {
342 Meta::delete_template_option($type);
343 }
344 }
345
346
347 if ($editWith == 'elementor') {
348 if (! get_post_meta($post_id, '_elementor_data')) {
349 update_post_meta($post_id, '_elementor_data', []);
350 }
351
352 if (! $templateId) {
353 update_post_meta($post_id, '_elementor_data', []);
354 }
355 update_post_meta($post_id, '_wp_page_template', 'elementor_header_footer');
356 update_post_meta($post_id, '_elementor_edit_mode', 'builder');
357 update_post_meta($post_id, '_elementor_version', '3.7.1');
358 }
359
360 if ($templateId) {
361 $url = add_query_arg(
362 ['post_type' => Meta::POST_TYPE],
363 admin_url('edit.php')
364 );
365 } else {
366 $url = add_query_arg([
367 'post' => $post_id,
368 'action' => $editWith,
369 ], admin_url('post.php'));
370 }
371
372 wp_send_json_success(['success' => true, 'redirect' => $url]);
373 }
374
375 public function get_builder_template_action() {
376
377 if (! current_user_can('manage_options')) {
378 wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403);
379 }
380
381 $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : '';
382
383 if (! wp_verify_nonce($nonce, 'ultimate_store_kit_builder_nonce')) {
384 wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403);
385 }
386
387 if (isset($_REQUEST['template_id']) && ! empty($_REQUEST['template_id'])) {
388 $templateId = absint($_REQUEST['template_id']);
389 $templateData = get_post($templateId);
390
391 if ($templateData && $templateData->post_type === Meta::POST_TYPE) {
392 $meta = get_post_meta($templateData->ID);
393
394
395 $templateType = isset($meta[Meta::TEMPLATE_TYPE][0]) ? $meta[Meta::TEMPLATE_TYPE][0] : '';
396 $enabledTemplate = Meta::get_template_option($templateType);
397
398 wp_send_json_success([
399 'id' => $templateData->ID,
400 'name' => $templateData->post_title,
401 'type' => $templateType,
402 'status' => is_numeric($enabledTemplate) ? 1 : 0,
403 ]);
404 }
405 }
406
407 wp_send_json_error(['success' => false, 'errors_arr' => ['template' => 'Template not found']], 404);
408 }
409
410 public function add_modal_html($hook_suffix) {
411 require_once 'modal/modal.php';
412 }
413
414 public function enqueue_scripts($hook_suffix) {
415 if (in_array($hook_suffix, ['edit.php', 'post-new.php'])) {
416 $screen = get_current_screen();
417
418 if (is_object($screen) && Meta::POST_TYPE == $screen->post_type) {
419 wp_enqueue_style('ultimate-store-kit-builder', BDTUSK_ASSETS_URL . 'admin/others/css/ultimate-builder.css', [], BDTUSK_VER);
420 wp_enqueue_script('ultimate-store-kit-builder', BDTUSK_ASSETS_URL . 'admin/others/js/ultimate-builder.js', ['jquery', 'wp-i18n'], BDTUSK_VER, true);
421 wp_set_script_translations('ultimate-store-kit-builder', 'ultimate-store-kit');
422
423 wp_localize_script('ultimate-store-kit-builder', 'UltimateStoreKitConfigBuilder', [
424 'ajaxurl' => admin_url('admin-ajax.php'),
425 'nonce' => wp_create_nonce('ultimate_store_kit_builder_nonce'),
426 'resturl' => rest_url('usk/v1/'),
427 ]);
428 }
429 }
430 }
431
432
433 public function add_admin_menu() {
434 add_submenu_page(
435 'ultimate-store-kit',
436 esc_html__('Template Builder', 'ultimate-store-kit'),
437 esc_html__('Template Builder', 'ultimate-store-kit'),
438 'manage_options',
439 'edit.php?post_type=' . Meta::POST_TYPE
440 );
441 }
442
443 public function registered_post_type() {
444 $labels = [
445 'name' => _x('Template Items', 'post type general name', 'ultimate-store-kit'),
446 'singular_name' => _x('Template Item', 'post type singular name', 'ultimate-store-kit'),
447 'menu_name' => _x('Template Manager', 'admin menu', 'ultimate-store-kit'),
448 'name_admin_bar' => _x('Template Manager', 'add new on admin bar', 'ultimate-store-kit'),
449 'add_new' => _x('Add New', 'template_manager', 'ultimate-store-kit'),
450 'add_new_item' => __('Add New Template', 'ultimate-store-kit'),
451 'new_item' => __('New Template', 'ultimate-store-kit'),
452 'edit_item' => __('Edit Template', 'ultimate-store-kit'),
453 'view_item' => __('View Template', 'ultimate-store-kit'),
454 'all_items' => __('All Templates', 'ultimate-store-kit'),
455 'search_items' => __('Search Templates', 'ultimate-store-kit'),
456 'parent_item_colon' => __('Parent Template:', 'ultimate-store-kit'),
457 'not_found' => __('No Template found.', 'ultimate-store-kit'),
458 'not_found_in_trash' => __('No Template found in Trash.', 'ultimate-store-kit'),
459 ];
460
461 $args = [
462 'labels' => $labels,
463 'description' => __('Description.', 'ultimate-store-kit'),
464 'taxonomies' => [],
465 'hierarchical' => false,
466 'public' => true,
467 'show_in_menu' => false,
468 'show_ui' => true,
469 'show_in_admin_bar' => true,
470 'menu_position' => null,
471 'menu_icon' => null,
472 'publicly_queryable' => true,
473 'supports' => ['title', 'editor', 'elementor'],
474 'exclude_from_search' => true,
475 'has_archive' => false,
476 'query_var' => true,
477 'can_export' => true,
478 'rewrite' => false,
479 'show_in_nav_menus' => false,
480 'capability_type' => 'post',
481 // 'rest_base' => $this->getPostType(),
482 // 'register_meta_box_cb' => [ $this, 'register_meta_box_cb' ],
483 ];
484
485 register_post_type(Meta::POST_TYPE, $args);
486
487 // Fix WPML integration with Elementor
488 if (function_exists('icl_object_id')) {
489 add_filter('wpml_pb_elementor_get_data', [$this, 'fix_wpml_elementor_data'], 10, 2);
490 }
491 }
492
493 /**
494 * Simple fix for WPML and Elementor integration
495 */
496 public function fix_wpml_elementor_data() {
497 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reads the post being edited so WPML/Elementor metadata can be normalised.
498 $post_id = isset($_REQUEST['post']) ? absint($_REQUEST['post']) : 0;
499
500 if ($post_id && get_post_type($post_id) === Meta::POST_TYPE) {
501 $meta_data = get_post_meta($post_id, '_elementor_data', true);
502
503 // If metadata exists but is in array format, convert it to JSON string
504 if (is_array($meta_data)) {
505 update_post_meta($post_id, '_elementor_data', wp_json_encode($meta_data));
506 }
507 }
508 }
509 }
510
511 Builder_Cpt::instance()->init_hooks();
512