PluginProbe
Ultimate Store Kit / 3.1.0
Ultimate Store Kit v3.1.0
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.0, 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 $optionKey = Meta::TEMPLATE_ID . $q->template_type;
70
71 if ($q->post_status == 'publish') {
72 update_option($optionKey, $q->ID);
73 } else {
74 delete_option($optionKey, $q->ID);
75 }
76 }
77 }
78
79 public function trashed_or_delete_post($postId) {
80 if (get_post_type($postId) != Meta::POST_TYPE) {
81 return;
82 }
83
84 if ($template = get_post_meta($postId, Meta::TEMPLATE_TYPE, true)) {
85 delete_option(Meta::TEMPLATE_ID . $template);
86 }
87 }
88
89 public function post_row_actions_filter($actions, $post) {
90
91 global $typenow;
92
93 if ($typenow !== Meta::POST_TYPE) {
94 return $actions;
95 }
96
97 if (isset($actions['edit_with_elementor'])) {
98 unset($actions['edit_with_elementor']);
99 }
100
101 if (get_post_meta($post->ID, Meta::EDIT_WITH, true) == 'gutenberg') {
102 $actions['usk_edit_with_gutenberg'] = sprintf(
103 '<a href="%1$s">%2$s</a>',
104 add_query_arg(['post' => $post->ID, 'action' => 'edit'], admin_url('post.php')),
105 esc_html__('Edit with Gutenberg', 'ultimate-store-kit')
106 );
107 }
108
109 if (get_post_meta($post->ID, Meta::EDIT_WITH, true) == 'elementor') {
110 $actions['usk_edit_with_elementor'] = sprintf(
111 '<a href="%1$s">%2$s</a>',
112 add_query_arg(
113 [
114 'post' => $post->ID,
115 'action' => 'elementor',
116 'usk-template' => 1
117 ],
118 admin_url('post.php')
119 ),
120 esc_html__('Edit with Elementor', 'ultimate-store-kit')
121 );
122 } else {
123 // Always offer Elementor edit option regardless of stored preference
124 $actions['usk_edit_with_elementor'] = sprintf(
125 '<a href="%1$s">%2$s</a>',
126 add_query_arg(
127 [
128 'post' => $post->ID,
129 'action' => 'elementor',
130 'usk-template' => 1
131 ],
132 admin_url('post.php')
133 ),
134 esc_html__('Edit with Elementor', 'ultimate-store-kit')
135 );
136 }
137
138 $editActionLink = sprintf(
139 '<a href="%1$s" data-id="%2$s" >%3$s</a>',
140 'javascript:void(0)',
141 $post->ID,
142 esc_html__('Edit', 'ultimate-store-kit')
143 );
144
145 if (isset($actions['edit']) && apply_filters('ultimate_store_kit_remove_action_edit_link', __return_true())) {
146 unset($actions['edit']);
147 }
148
149 return array_slice($actions, 0, 1, true) + ['usk-edit-action' => $editActionLink] + array_slice($actions, 1, null, true);
150 }
151
152 public function set_post_columns($columns) {
153 return array_slice($columns, 0, 2, true) + ['template_type' => 'Type', 'is_enabled' => 'Status'] + array_slice($columns, 2, null, true);
154 }
155
156 public function set_custom_column_value($column, $post_id) {
157 $templateType = get_post_meta(
158 $post_id,
159 Meta::TEMPLATE_TYPE,
160 true
161 );
162
163 switch ($column) {
164 case 'template_type':
165 $postType = Builder_Template_Helper::getTemplatePostTypeByIndex($templateType);
166 echo esc_html(Builder_Template_Helper::getTemplateByIndex($templateType));
167 if (isset($postType->name)) {
168 echo ' <strong>-- ' . esc_html(ucwords($postType->name)) . '</strong>';
169 }
170 break;
171 case 'is_enabled':
172 echo (Builder_Template_Helper::getTemplateId($templateType) == $post_id ? 'Active' : 'Inactive');
173 break;
174 }
175 }
176
177 public function add_filter() {
178 global $typenow;
179
180 if ($typenow !== Meta::POST_TYPE) {
181 return;
182 }
183
184 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Repopulates the admin list-table filter dropdown; nothing is written.
185 $selected = isset($_GET['type']) ? sanitize_key(wp_unslash($_GET['type'])) : '';
186 ?>
187 <select name="type" id="type">
188 <option value="all" <?php
189 selected('all', $selected); ?>><?php
190 esc_html_e(
191 'Template Type ',
192 'ultimate-store-kit'
193 ); ?></option>
194 <?php
195 $templates = Builder_Template_Helper::templateForSelectDropdown();
196 // It is single
197 if (count($templates) == 1) {
198 $templateKey = array_key_last($templates);
199 $template = $templates[$templateKey];
200 foreach ($template as $key => $item) :
201 $selectValue = "{$templateKey}_$key";
202 ?>
203 <option value="<?php
204 echo esc_attr($selectValue) ?>"><?php
205 echo wp_kses_post($item) ?></option>
206 <?php
207 endforeach;
208 }
209
210 if (count($templates) > 1) {
211 foreach ($templates as $keys => $items) :
212 $label = ucwords(str_replace(
213 ['-', '_'],
214 [' '],
215 $keys
216 ));
217 if (is_array($items)) {
218 ?>
219 <optgroup label="<?php
220 echo esc_attr($label) ?>"><?php
221 foreach ($items as $key => $item) :
222 $itemValue = "{$keys}_$key"
223 ?>
224 <option value="<?php
225 echo esc_attr($itemValue) ?>" <?php
226 selected($key, $selected); ?>><?php
227 echo wp_kses_post($item) ?></option>
228 <?php
229 endforeach;
230 ?>
231 </optgroup>
232 <?php
233 }
234 endforeach;
235 }
236 ?>
237 </select>
238 <?php
239 }
240
241 public function parse_query_filter($query) {
242 global $pagenow, $typenow;
243
244 if ($typenow !== Meta::POST_TYPE) {
245 return;
246 }
247
248 // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.DB.SlowDBQuery -- Admin list-table filter; the value only narrows the query that is already running.
249 if (
250 'edit.php' == $pagenow
251 && isset($_GET['type'])
252 && $_GET['type'] != ''
253 && $_GET['type'] != 'all'
254 ) {
255 $query->query_vars['meta_key'] = Meta::TEMPLATE_TYPE;
256 $query->query_vars['meta_value'] = sanitize_key(wp_unslash($_GET['type']));
257 $query->query_vars['meta_compare'] = '=';
258 }
259 // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.DB.SlowDBQuery
260 }
261
262
263 public function create_builder_template() {
264
265 if (! current_user_can('manage_options')) {
266 wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403);
267 }
268
269 $data = [];
270 // The form arrives as one serialized string, so it is unslashed before parsing
271 // and every field is sanitized individually below — parse_str itself does not
272 // sanitize anything.
273 if (isset($_POST['data'])) {
274 parse_str(wp_unslash($_POST['data']), $data); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Serialized form string; every parsed field is sanitized individually below.
275 }
276
277 $nonce = isset($data['nonce']) ? sanitize_text_field($data['nonce']) : '';
278
279 if (! wp_verify_nonce($nonce, 'usk-builder')) {
280 wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403);
281 }
282
283 $templateId = isset($data['template_id']) ? absint($data['template_id']) : 0;
284 $name = isset($data['template_name']) ? sanitize_text_field($data['template_name']) : '';
285 $type = isset($data['template_type']) ? sanitize_key($data['template_type']) : '';
286 $editWith = isset($data['edit_with']) ? sanitize_key($data['edit_with']) : 'elementor'; //gutenberg
287 $isEnabled = (isset($data['template_status']) && $data['template_status']) == 1 ? 1 : 0;
288
289 // Only these two editors are ever handled below, and the value ends up in a
290 // redirect URL, so anything else falls back to the default.
291 if (! in_array($editWith, ['elementor', 'gutenberg'], true)) {
292 $editWith = 'elementor';
293 }
294
295 $errors = [];
296
297 if (empty($name)) {
298 $errors['template_name'] = 'Field is required';
299 }
300
301 if (empty($type)) {
302 $errors['template_type'] = 'Field is required';
303 } else {
304 if (! Builder_Template_Helper::getTemplateByIndex($type)) {
305 $errors['template_type'] = 'Invalid section';
306 }
307 }
308
309
310 if (count($errors) > 0) {
311 wp_send_json_error(['success' => false, 'errors_arr' => $errors], 422);
312 }
313
314 $page_data = [
315 'post_status' => 'publish',
316 'post_type' => Meta::POST_TYPE,
317 'post_author' => get_current_user_id(),
318 'post_title' => $name,
319 'comment_status' => 'closed',
320 'meta_input' => [
321 Meta::EDIT_WITH => $editWith,
322 Meta::TEMPLATE_TYPE => $type,
323 ],
324 ];
325
326 global $wp_rewrite;
327 $wp_rewrite->flush_rules();
328 $wp_rewrite->init();
329
330 if ($templateId) {
331 $page_data['ID'] = $templateId;
332 }
333
334 $post_id = wp_insert_post($page_data);
335
336 $enabledTemplate = strtolower(Meta::TEMPLATE_ID . $type);
337 if ($isEnabled == 1) {
338 update_option($enabledTemplate, $post_id);
339 } else {
340 if (get_option($enabledTemplate) == $post_id) {
341 delete_option($enabledTemplate);
342 }
343 }
344
345
346 if ($editWith == 'elementor') {
347 if (! get_post_meta($post_id, '_elementor_data')) {
348 update_post_meta($post_id, '_elementor_data', []);
349 }
350
351 if (! $templateId) {
352 update_post_meta($post_id, '_elementor_data', []);
353 }
354 update_post_meta($post_id, '_wp_page_template', 'elementor_header_footer');
355 update_post_meta($post_id, '_elementor_edit_mode', 'builder');
356 update_post_meta($post_id, '_elementor_version', '3.7.1');
357 }
358
359 if ($templateId) {
360 $url = add_query_arg(
361 ['post_type' => Meta::POST_TYPE],
362 admin_url('edit.php')
363 );
364 } else {
365 $url = add_query_arg([
366 'post' => $post_id,
367 'action' => $editWith,
368 ], admin_url('post.php'));
369 }
370
371 wp_send_json_success(['success' => true, 'redirect' => $url]);
372 }
373
374 public function get_builder_template_action() {
375
376 if (! current_user_can('manage_options')) {
377 wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403);
378 }
379
380 $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : '';
381
382 if (! wp_verify_nonce($nonce, 'ultimate_store_kit_builder_nonce')) {
383 wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403);
384 }
385
386 if (isset($_REQUEST['template_id']) && ! empty($_REQUEST['template_id'])) {
387 $templateId = absint($_REQUEST['template_id']);
388 $templateData = get_post($templateId);
389
390 if ($templateData && $templateData->post_type === Meta::POST_TYPE) {
391 $meta = get_post_meta($templateData->ID);
392
393
394 $templateType = isset($meta[Meta::TEMPLATE_TYPE][0]) ? $meta[Meta::TEMPLATE_TYPE][0] : '';
395 $enabledTemplate = strtolower(Meta::TEMPLATE_ID . $templateType);
396 $enabledTemplate = get_option($enabledTemplate);
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