PluginProbe
Ultimate Store Kit / trunk
Ultimate Store Kit vtrunk
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 trunk, at includes/builder/builder-cpt.php

517 lines 16.7 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) + [
152 'template_type' => esc_html__('Type', 'ultimate-store-kit'),
153 'is_enabled' => esc_html__('Status', 'ultimate-store-kit')
154 ] + array_slice($columns, 2, null, true);
155 }
156
157 public function set_custom_column_value($column, $post_id) {
158 $templateType = get_post_meta(
159 $post_id,
160 Meta::TEMPLATE_TYPE,
161 true
162 );
163
164 switch ($column) {
165 case 'template_type':
166 $postType = Builder_Template_Helper::getTemplatePostTypeByIndex($templateType);
167 echo esc_html(Builder_Template_Helper::getTemplateByIndex($templateType));
168 if (isset($postType->name)) {
169 echo ' <strong>-- ' . esc_html(ucwords($postType->name)) . '</strong>';
170 }
171 break;
172 case 'is_enabled':
173 echo Builder_Template_Helper::getTemplateId($templateType) == $post_id
174 ? esc_html__('Active', 'ultimate-store-kit')
175 : esc_html__('Inactive', 'ultimate-store-kit');
176 break;
177 }
178 }
179
180 public function add_filter() {
181 global $typenow;
182
183 if ($typenow !== Meta::POST_TYPE) {
184 return;
185 }
186
187 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list-table filter; the value only re-selects the current dropdown option.
188 $selected = isset($_GET['type']) ? sanitize_text_field(wp_unslash($_GET['type'])) : '';
189 ?>
190 <select name="type" id="type">
191 <option value="all" <?php
192 selected('all', $selected); ?>><?php
193 esc_html_e(
194 'Template Type ',
195 'ultimate-store-kit'
196 ); ?></option>
197 <?php
198 $templates = Builder_Template_Helper::templateForSelectDropdown();
199 // It is single
200 if (count($templates) == 1) {
201 $templateKey = array_key_last($templates);
202 $template = $templates[$templateKey];
203 foreach ($template as $key => $item) :
204 $selectValue = "{$templateKey}_$key";
205 ?>
206 <option value="<?php
207 echo esc_attr($selectValue) ?>"><?php
208 echo wp_kses_post($item) ?></option>
209 <?php
210 endforeach;
211 }
212
213 if (count($templates) > 1) {
214 foreach ($templates as $keys => $items) :
215 $label = ucwords(str_replace(
216 ['-', '_'],
217 [' '],
218 $keys
219 ));
220 if (is_array($items)) {
221 ?>
222 <optgroup label="<?php
223 echo esc_attr($label) ?>"><?php
224 foreach ($items as $key => $item) :
225 $itemValue = "{$keys}_$key"
226 ?>
227 <option value="<?php
228 echo esc_attr($itemValue) ?>" <?php
229 selected($key, $selected); ?>><?php
230 echo wp_kses_post($item) ?></option>
231 <?php
232 endforeach;
233 ?>
234 </optgroup>
235 <?php
236 }
237 endforeach;
238 }
239 ?>
240 </select>
241 <?php
242 }
243
244 public function parse_query_filter($query) {
245 global $pagenow, $typenow;
246
247 if ($typenow !== Meta::POST_TYPE) {
248 return;
249 }
250
251 // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.DB.SlowDBQuery -- Admin list-table filter; the value only narrows the query that is already running.
252 if (
253 'edit.php' == $pagenow
254 && isset($_GET['type'])
255 && $_GET['type'] != ''
256 && $_GET['type'] != 'all'
257 ) {
258 $requested_type = sanitize_text_field(wp_unslash($_GET['type']));
259
260 if (Builder_Template_Helper::getTemplateByIndex($requested_type)) {
261 $query->query_vars['meta_key'] = Meta::TEMPLATE_TYPE;
262 $query->query_vars['meta_value'] = $requested_type;
263 $query->query_vars['meta_compare'] = '=';
264 }
265 }
266 // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.DB.SlowDBQuery
267 }
268
269
270 public function create_builder_template() {
271
272 if (! current_user_can('manage_options')) {
273 wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403);
274 }
275
276 $data = [];
277 // The form arrives as one serialized string, so it is unslashed before parsing
278 // and every field is sanitized individually below — parse_str itself does not
279 // sanitize anything.
280 if (isset($_POST['data'])) {
281 parse_str(wp_unslash($_POST['data']), $data); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Serialized form string; every parsed field is sanitized individually below.
282 }
283
284 $nonce = isset($data['nonce']) ? sanitize_text_field($data['nonce']) : '';
285
286 if (! wp_verify_nonce($nonce, 'usk-builder')) {
287 wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403);
288 }
289
290 $templateId = isset($data['template_id']) ? absint($data['template_id']) : 0;
291 $name = isset($data['template_name']) ? sanitize_text_field($data['template_name']) : '';
292 $type = isset($data['template_type']) ? sanitize_text_field($data['template_type']) : '';
293 $editWith = isset($data['edit_with']) ? sanitize_key($data['edit_with']) : 'elementor'; //gutenberg
294 $isEnabled = (isset($data['template_status']) && $data['template_status']) == 1 ? 1 : 0;
295
296 // Only these two editors are ever handled below, and the value ends up in a
297 // redirect URL, so anything else falls back to the default.
298 if (! in_array($editWith, ['elementor', 'gutenberg'], true)) {
299 $editWith = 'elementor';
300 }
301
302 $errors = [];
303
304 if (empty($name)) {
305 $errors['template_name'] = 'Field is required';
306 }
307
308 if (empty($type)) {
309 $errors['template_type'] = 'Field is required';
310 } else {
311 if (! Builder_Template_Helper::getTemplateByIndex($type)) {
312 $errors['template_type'] = 'Invalid section';
313 }
314 }
315
316
317 if (count($errors) > 0) {
318 wp_send_json_error(['success' => false, 'errors_arr' => $errors], 422);
319 }
320
321 $page_data = [
322 'post_status' => 'publish',
323 'post_type' => Meta::POST_TYPE,
324 'post_author' => get_current_user_id(),
325 'post_title' => $name,
326 'comment_status' => 'closed',
327 'meta_input' => [
328 Meta::EDIT_WITH => $editWith,
329 Meta::TEMPLATE_TYPE => $type,
330 ],
331 ];
332
333 global $wp_rewrite;
334 $wp_rewrite->flush_rules();
335 $wp_rewrite->init();
336
337 if ($templateId) {
338 $page_data['ID'] = $templateId;
339 }
340
341 $post_id = wp_insert_post($page_data);
342
343 if ($isEnabled == 1) {
344 Meta::update_template_option($type, $post_id);
345 } else {
346 if (Meta::get_template_option($type) == $post_id) {
347 Meta::delete_template_option($type);
348 }
349 }
350
351
352 if ($editWith == 'elementor') {
353 if (! get_post_meta($post_id, '_elementor_data')) {
354 update_post_meta($post_id, '_elementor_data', []);
355 }
356
357 if (! $templateId) {
358 update_post_meta($post_id, '_elementor_data', []);
359 }
360 update_post_meta($post_id, '_wp_page_template', 'elementor_header_footer');
361 update_post_meta($post_id, '_elementor_edit_mode', 'builder');
362 update_post_meta($post_id, '_elementor_version', '3.7.1');
363 }
364
365 if ($templateId) {
366 $url = add_query_arg(
367 ['post_type' => Meta::POST_TYPE],
368 admin_url('edit.php')
369 );
370 } else {
371 $url = add_query_arg([
372 'post' => $post_id,
373 'action' => $editWith,
374 ], admin_url('post.php'));
375 }
376
377 wp_send_json_success(['success' => true, 'redirect' => $url]);
378 }
379
380 public function get_builder_template_action() {
381
382 if (! current_user_can('manage_options')) {
383 wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403);
384 }
385
386 $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : '';
387
388 if (! wp_verify_nonce($nonce, 'ultimate_store_kit_builder_nonce')) {
389 wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403);
390 }
391
392 if (isset($_REQUEST['template_id']) && ! empty($_REQUEST['template_id'])) {
393 $templateId = absint($_REQUEST['template_id']);
394 $templateData = get_post($templateId);
395
396 if ($templateData && $templateData->post_type === Meta::POST_TYPE) {
397 $meta = get_post_meta($templateData->ID);
398
399
400 $templateType = isset($meta[Meta::TEMPLATE_TYPE][0]) ? $meta[Meta::TEMPLATE_TYPE][0] : '';
401 $enabledTemplate = Meta::get_template_option($templateType);
402
403 wp_send_json_success([
404 'id' => $templateData->ID,
405 'name' => $templateData->post_title,
406 'type' => $templateType,
407 'status' => is_numeric($enabledTemplate) ? 1 : 0,
408 ]);
409 }
410 }
411
412 wp_send_json_error(['success' => false, 'errors_arr' => ['template' => 'Template not found']], 404);
413 }
414
415 public function add_modal_html($hook_suffix) {
416 require_once 'modal/modal.php';
417 }
418
419 public function enqueue_scripts($hook_suffix) {
420 if (in_array($hook_suffix, ['edit.php', 'post-new.php'])) {
421 $screen = get_current_screen();
422
423 if (is_object($screen) && Meta::POST_TYPE == $screen->post_type) {
424 wp_enqueue_style('ultimate-store-kit-builder', BDTUSK_ASSETS_URL . 'admin/others/css/ultimate-builder.css', [], BDTUSK_VER);
425 wp_enqueue_script('ultimate-store-kit-builder', BDTUSK_ASSETS_URL . 'admin/others/js/ultimate-builder.js', ['jquery', 'wp-i18n'], BDTUSK_VER, true);
426 wp_set_script_translations('ultimate-store-kit-builder', 'ultimate-store-kit');
427
428 wp_localize_script('ultimate-store-kit-builder', 'UltimateStoreKitConfigBuilder', [
429 'ajaxurl' => admin_url('admin-ajax.php'),
430 'nonce' => wp_create_nonce('ultimate_store_kit_builder_nonce'),
431 'resturl' => rest_url('usk/v1/'),
432 ]);
433 }
434 }
435 }
436
437
438 public function add_admin_menu() {
439 add_submenu_page(
440 'ultimate-store-kit',
441 esc_html__('Template Builder', 'ultimate-store-kit'),
442 esc_html__('Template Builder', 'ultimate-store-kit'),
443 'manage_options',
444 'edit.php?post_type=' . Meta::POST_TYPE
445 );
446 }
447
448 public function registered_post_type() {
449 $labels = [
450 'name' => _x('Template Items', 'post type general name', 'ultimate-store-kit'),
451 'singular_name' => _x('Template Item', 'post type singular name', 'ultimate-store-kit'),
452 'menu_name' => _x('Template Manager', 'admin menu', 'ultimate-store-kit'),
453 'name_admin_bar' => _x('Template Manager', 'add new on admin bar', 'ultimate-store-kit'),
454 'add_new' => _x('Add New', 'template_manager', 'ultimate-store-kit'),
455 'add_new_item' => __('Add New Template', 'ultimate-store-kit'),
456 'new_item' => __('New Template', 'ultimate-store-kit'),
457 'edit_item' => __('Edit Template', 'ultimate-store-kit'),
458 'view_item' => __('View Template', 'ultimate-store-kit'),
459 'all_items' => __('All Templates', 'ultimate-store-kit'),
460 'search_items' => __('Search Templates', 'ultimate-store-kit'),
461 'parent_item_colon' => __('Parent Template:', 'ultimate-store-kit'),
462 'not_found' => __('No Template found.', 'ultimate-store-kit'),
463 'not_found_in_trash' => __('No Template found in Trash.', 'ultimate-store-kit'),
464 ];
465
466 $args = [
467 'labels' => $labels,
468 'description' => __('Description.', 'ultimate-store-kit'),
469 'taxonomies' => [],
470 'hierarchical' => false,
471 'public' => true,
472 'show_in_menu' => false,
473 'show_ui' => true,
474 'show_in_admin_bar' => true,
475 'menu_position' => null,
476 'menu_icon' => null,
477 'publicly_queryable' => true,
478 'supports' => ['title', 'editor', 'elementor'],
479 'exclude_from_search' => true,
480 'has_archive' => false,
481 'query_var' => true,
482 'can_export' => true,
483 'rewrite' => false,
484 'show_in_nav_menus' => false,
485 'capability_type' => 'post',
486 // 'rest_base' => $this->getPostType(),
487 // 'register_meta_box_cb' => [ $this, 'register_meta_box_cb' ],
488 ];
489
490 register_post_type(Meta::POST_TYPE, $args);
491
492 // Fix WPML integration with Elementor
493 if (function_exists('icl_object_id')) {
494 add_filter('wpml_pb_elementor_get_data', [$this, 'fix_wpml_elementor_data'], 10, 2);
495 }
496 }
497
498 /**
499 * Simple fix for WPML and Elementor integration
500 */
501 public function fix_wpml_elementor_data() {
502 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reads the post being edited so WPML/Elementor metadata can be normalised.
503 $post_id = isset($_REQUEST['post']) ? absint($_REQUEST['post']) : 0;
504
505 if ($post_id && get_post_type($post_id) === Meta::POST_TYPE) {
506 $meta_data = get_post_meta($post_id, '_elementor_data', true);
507
508 // If metadata exists but is in array format, convert it to JSON string
509 if (is_array($meta_data)) {
510 update_post_meta($post_id, '_elementor_data', wp_json_encode($meta_data));
511 }
512 }
513 }
514 }
515
516 Builder_Cpt::instance()->init_hooks();
517