resetTemplateCache(); } public function resetTemplateCache() { global $wpdb; $postType = Meta::POST_TYPE; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Rebuilds the template cache; this is the call that populates it. $query = $wpdb->get_results($wpdb->prepare( "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_type, {$wpdb->posts}.post_status, {$wpdb->postmeta}.meta_value as template_type FROM {$wpdb->posts} LEFT JOIN {$wpdb->postmeta} ON {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID WHERE 1=1 AND {$wpdb->posts}.post_type = %s AND {$wpdb->postmeta}.meta_key = %s ORDER BY {$wpdb->posts}.post_date DESC", $postType, '_ultimate_store_kit_template_type' )); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching foreach ($query as $q) { if (! $q->template_type) { continue; } if ($q->post_status == 'publish') { Meta::update_template_option($q->template_type, $q->ID); } else { Meta::delete_template_option($q->template_type); } } } public function trashed_or_delete_post($postId) { if (get_post_type($postId) != Meta::POST_TYPE) { return; } if ($template = get_post_meta($postId, Meta::TEMPLATE_TYPE, true)) { Meta::delete_template_option($template); } } public function post_row_actions_filter($actions, $post) { global $typenow; if ($typenow !== Meta::POST_TYPE) { return $actions; } if (isset($actions['edit_with_elementor'])) { unset($actions['edit_with_elementor']); } if (get_post_meta($post->ID, Meta::EDIT_WITH, true) == 'gutenberg') { $actions['usk_edit_with_gutenberg'] = sprintf( '%2$s', add_query_arg(['post' => $post->ID, 'action' => 'edit'], admin_url('post.php')), esc_html__('Edit with Gutenberg', 'ultimate-store-kit') ); } if (get_post_meta($post->ID, Meta::EDIT_WITH, true) == 'elementor') { $actions['usk_edit_with_elementor'] = sprintf( '%2$s', add_query_arg( [ 'post' => $post->ID, 'action' => 'elementor', 'usk-template' => 1 ], admin_url('post.php') ), esc_html__('Edit with Elementor', 'ultimate-store-kit') ); } else { // Always offer Elementor edit option regardless of stored preference $actions['usk_edit_with_elementor'] = sprintf( '%2$s', add_query_arg( [ 'post' => $post->ID, 'action' => 'elementor', 'usk-template' => 1 ], admin_url('post.php') ), esc_html__('Edit with Elementor', 'ultimate-store-kit') ); } $editActionLink = sprintf( '%3$s', 'javascript:void(0)', $post->ID, esc_html__('Edit', 'ultimate-store-kit') ); if (isset($actions['edit']) && apply_filters('ultimate_store_kit_remove_action_edit_link', __return_true())) { unset($actions['edit']); } return array_slice($actions, 0, 1, true) + ['usk-edit-action' => $editActionLink] + array_slice($actions, 1, null, true); } public function set_post_columns($columns) { return array_slice($columns, 0, 2, true) + [ 'template_type' => esc_html__('Type', 'ultimate-store-kit'), 'is_enabled' => esc_html__('Status', 'ultimate-store-kit') ] + array_slice($columns, 2, null, true); } public function set_custom_column_value($column, $post_id) { $templateType = get_post_meta( $post_id, Meta::TEMPLATE_TYPE, true ); switch ($column) { case 'template_type': $postType = Builder_Template_Helper::getTemplatePostTypeByIndex($templateType); echo esc_html(Builder_Template_Helper::getTemplateByIndex($templateType)); if (isset($postType->name)) { echo ' -- ' . esc_html(ucwords($postType->name)) . ''; } break; case 'is_enabled': echo Builder_Template_Helper::getTemplateId($templateType) == $post_id ? esc_html__('Active', 'ultimate-store-kit') : esc_html__('Inactive', 'ultimate-store-kit'); break; } } public function add_filter() { global $typenow; if ($typenow !== Meta::POST_TYPE) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list-table filter; the value only re-selects the current dropdown option. $selected = isset($_GET['type']) ? sanitize_text_field(wp_unslash($_GET['type'])) : ''; ?> query_vars['meta_key'] = Meta::TEMPLATE_TYPE; $query->query_vars['meta_value'] = $requested_type; $query->query_vars['meta_compare'] = '='; } } // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.DB.SlowDBQuery } public function create_builder_template() { if (! current_user_can('manage_options')) { wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403); } $data = []; // The form arrives as one serialized string, so it is unslashed before parsing // and every field is sanitized individually below — parse_str itself does not // sanitize anything. if (isset($_POST['data'])) { parse_str(wp_unslash($_POST['data']), $data); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Serialized form string; every parsed field is sanitized individually below. } $nonce = isset($data['nonce']) ? sanitize_text_field($data['nonce']) : ''; if (! wp_verify_nonce($nonce, 'usk-builder')) { wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403); } $templateId = isset($data['template_id']) ? absint($data['template_id']) : 0; $name = isset($data['template_name']) ? sanitize_text_field($data['template_name']) : ''; $type = isset($data['template_type']) ? sanitize_text_field($data['template_type']) : ''; $editWith = isset($data['edit_with']) ? sanitize_key($data['edit_with']) : 'elementor'; //gutenberg $isEnabled = (isset($data['template_status']) && $data['template_status']) == 1 ? 1 : 0; // Only these two editors are ever handled below, and the value ends up in a // redirect URL, so anything else falls back to the default. if (! in_array($editWith, ['elementor', 'gutenberg'], true)) { $editWith = 'elementor'; } $errors = []; if (empty($name)) { $errors['template_name'] = 'Field is required'; } if (empty($type)) { $errors['template_type'] = 'Field is required'; } else { if (! Builder_Template_Helper::getTemplateByIndex($type)) { $errors['template_type'] = 'Invalid section'; } } if (count($errors) > 0) { wp_send_json_error(['success' => false, 'errors_arr' => $errors], 422); } $page_data = [ 'post_status' => 'publish', 'post_type' => Meta::POST_TYPE, 'post_author' => get_current_user_id(), 'post_title' => $name, 'comment_status' => 'closed', 'meta_input' => [ Meta::EDIT_WITH => $editWith, Meta::TEMPLATE_TYPE => $type, ], ]; global $wp_rewrite; $wp_rewrite->flush_rules(); $wp_rewrite->init(); if ($templateId) { $page_data['ID'] = $templateId; } $post_id = wp_insert_post($page_data); if ($isEnabled == 1) { Meta::update_template_option($type, $post_id); } else { if (Meta::get_template_option($type) == $post_id) { Meta::delete_template_option($type); } } if ($editWith == 'elementor') { if (! get_post_meta($post_id, '_elementor_data')) { update_post_meta($post_id, '_elementor_data', []); } if (! $templateId) { update_post_meta($post_id, '_elementor_data', []); } update_post_meta($post_id, '_wp_page_template', 'elementor_header_footer'); update_post_meta($post_id, '_elementor_edit_mode', 'builder'); update_post_meta($post_id, '_elementor_version', '3.7.1'); } if ($templateId) { $url = add_query_arg( ['post_type' => Meta::POST_TYPE], admin_url('edit.php') ); } else { $url = add_query_arg([ 'post' => $post_id, 'action' => $editWith, ], admin_url('post.php')); } wp_send_json_success(['success' => true, 'redirect' => $url]); } public function get_builder_template_action() { if (! current_user_can('manage_options')) { wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403); } $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : ''; if (! wp_verify_nonce($nonce, 'ultimate_store_kit_builder_nonce')) { wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403); } if (isset($_REQUEST['template_id']) && ! empty($_REQUEST['template_id'])) { $templateId = absint($_REQUEST['template_id']); $templateData = get_post($templateId); if ($templateData && $templateData->post_type === Meta::POST_TYPE) { $meta = get_post_meta($templateData->ID); $templateType = isset($meta[Meta::TEMPLATE_TYPE][0]) ? $meta[Meta::TEMPLATE_TYPE][0] : ''; $enabledTemplate = Meta::get_template_option($templateType); wp_send_json_success([ 'id' => $templateData->ID, 'name' => $templateData->post_title, 'type' => $templateType, 'status' => is_numeric($enabledTemplate) ? 1 : 0, ]); } } wp_send_json_error(['success' => false, 'errors_arr' => ['template' => 'Template not found']], 404); } public function add_modal_html($hook_suffix) { require_once 'modal/modal.php'; } public function enqueue_scripts($hook_suffix) { if (in_array($hook_suffix, ['edit.php', 'post-new.php'])) { $screen = get_current_screen(); if (is_object($screen) && Meta::POST_TYPE == $screen->post_type) { wp_enqueue_style('ultimate-store-kit-builder', BDTUSK_ASSETS_URL . 'admin/others/css/ultimate-builder.css', [], BDTUSK_VER); wp_enqueue_script('ultimate-store-kit-builder', BDTUSK_ASSETS_URL . 'admin/others/js/ultimate-builder.js', ['jquery', 'wp-i18n'], BDTUSK_VER, true); wp_set_script_translations('ultimate-store-kit-builder', 'ultimate-store-kit'); wp_localize_script('ultimate-store-kit-builder', 'UltimateStoreKitConfigBuilder', [ 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ultimate_store_kit_builder_nonce'), 'resturl' => rest_url('usk/v1/'), ]); } } } public function add_admin_menu() { add_submenu_page( 'ultimate-store-kit', esc_html__('Template Builder', 'ultimate-store-kit'), esc_html__('Template Builder', 'ultimate-store-kit'), 'manage_options', 'edit.php?post_type=' . Meta::POST_TYPE ); } public function registered_post_type() { $labels = [ 'name' => _x('Template Items', 'post type general name', 'ultimate-store-kit'), 'singular_name' => _x('Template Item', 'post type singular name', 'ultimate-store-kit'), 'menu_name' => _x('Template Manager', 'admin menu', 'ultimate-store-kit'), 'name_admin_bar' => _x('Template Manager', 'add new on admin bar', 'ultimate-store-kit'), 'add_new' => _x('Add New', 'template_manager', 'ultimate-store-kit'), 'add_new_item' => __('Add New Template', 'ultimate-store-kit'), 'new_item' => __('New Template', 'ultimate-store-kit'), 'edit_item' => __('Edit Template', 'ultimate-store-kit'), 'view_item' => __('View Template', 'ultimate-store-kit'), 'all_items' => __('All Templates', 'ultimate-store-kit'), 'search_items' => __('Search Templates', 'ultimate-store-kit'), 'parent_item_colon' => __('Parent Template:', 'ultimate-store-kit'), 'not_found' => __('No Template found.', 'ultimate-store-kit'), 'not_found_in_trash' => __('No Template found in Trash.', 'ultimate-store-kit'), ]; $args = [ 'labels' => $labels, 'description' => __('Description.', 'ultimate-store-kit'), 'taxonomies' => [], 'hierarchical' => false, 'public' => true, 'show_in_menu' => false, 'show_ui' => true, 'show_in_admin_bar' => true, 'menu_position' => null, 'menu_icon' => null, 'publicly_queryable' => true, 'supports' => ['title', 'editor', 'elementor'], 'exclude_from_search' => true, 'has_archive' => false, 'query_var' => true, 'can_export' => true, 'rewrite' => false, 'show_in_nav_menus' => false, 'capability_type' => 'post', // 'rest_base' => $this->getPostType(), // 'register_meta_box_cb' => [ $this, 'register_meta_box_cb' ], ]; register_post_type(Meta::POST_TYPE, $args); // Fix WPML integration with Elementor if (function_exists('icl_object_id')) { add_filter('wpml_pb_elementor_get_data', [$this, 'fix_wpml_elementor_data'], 10, 2); } } /** * Simple fix for WPML and Elementor integration */ public function fix_wpml_elementor_data() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reads the post being edited so WPML/Elementor metadata can be normalised. $post_id = isset($_REQUEST['post']) ? absint($_REQUEST['post']) : 0; if ($post_id && get_post_type($post_id) === Meta::POST_TYPE) { $meta_data = get_post_meta($post_id, '_elementor_data', true); // If metadata exists but is in array format, convert it to JSON string if (is_array($meta_data)) { update_post_meta($post_id, '_elementor_data', wp_json_encode($meta_data)); } } } } Builder_Cpt::instance()->init_hooks();