| @@ -45,16 +45,22 @@ | ||
| 45 | 45 | global $wpdb; |
| 46 | 46 | |
| 47 | 47 | $postType = Meta::POST_TYPE; |
| 48 | 48 | |
| 49 | - $query = $wpdb->get_results("SELECT {$wpdb->posts}.ID,{$wpdb->posts}.post_type, {$wpdb->posts}.post_status, {$wpdb->postmeta}.meta_value as template_type | |
| 50 | -FROM $wpdb->posts | |
| 51 | - LEFT JOIN $wpdb->postmeta | |
| 52 | - ON {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID | |
| 53 | -WHERE 1=1 | |
| 54 | -AND {$wpdb->posts}.post_type ='{$postType}' | |
| 55 | -AND {$wpdb->postmeta}.meta_key ='_ultimate_store_kit_template_type' | |
| 56 | -ORDER BY {$wpdb->posts}.post_date DESC"); | |
| 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 | |
| 57 | 63 | |
| 58 | 64 | foreach ($query as $q) { |
| 59 | 65 | if (! $q->template_type) { |
| 60 | 66 | continue; |
| @@ -59,14 +65,12 @@ | ||
| 59 | 65 | if (! $q->template_type) { |
| 60 | 66 | continue; |
| 61 | 67 | } |
| 62 | 68 | |
| 63 | - $optionKey = Meta::TEMPLATE_ID . $q->template_type; | |
| 64 | - | |
| 65 | 69 | if ($q->post_status == 'publish') { |
| 66 | - update_option($optionKey, $q->ID); | |
| 70 | + Meta::update_template_option($q->template_type, $q->ID); | |
| 67 | 71 | } else { |
| 68 | - delete_option($optionKey, $q->ID); | |
| 72 | + Meta::delete_template_option($q->template_type); | |
| 69 | 73 | } |
| 70 | 74 | } |
| 71 | 75 | } |
| 72 | 76 | |
| @@ -75,9 +79,9 @@ | ||
| 75 | 79 | return; |
| 76 | 80 | } |
| 77 | 81 | |
| 78 | 82 | if ($template = get_post_meta($postId, Meta::TEMPLATE_TYPE, true)) { |
| 79 | - delete_option(Meta::TEMPLATE_ID . $template); | |
| 83 | + Meta::delete_template_option($template); | |
| 80 | 84 | } |
| 81 | 85 | } |
| 82 | 86 | |
| 83 | 87 | public function post_row_actions_filter($actions, $post) { |
| @@ -143,9 +147,12 @@ | ||
| 143 | 147 | return array_slice($actions, 0, 1, true) + ['usk-edit-action' => $editActionLink] + array_slice($actions, 1, null, true); |
| 144 | 148 | } |
| 145 | 149 | |
| 146 | 150 | public function set_post_columns($columns) { |
| 147 | - return array_slice($columns, 0, 2, true) + ['template_type' => 'Type', 'is_enabled' => 'Status'] + array_slice($columns, 2, null, true); | |
| 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); | |
| 148 | 155 | } |
| 149 | 156 | |
| 150 | 157 | public function set_custom_column_value($column, $post_id) { |
| 151 | 158 | $templateType = get_post_meta( |
| @@ -156,13 +163,17 @@ | ||
| 156 | 163 | |
| 157 | 164 | switch ($column) { |
| 158 | 165 | case 'template_type': |
| 159 | 166 | $postType = Builder_Template_Helper::getTemplatePostTypeByIndex($templateType); |
| 160 | - $postTypeLabel = isset($postType->name) ? ' <strong>-- ' . ucwords($postType->name) . '</strong>' : ''; | |
| 161 | - echo Builder_Template_Helper::getTemplateByIndex($templateType) . $postTypeLabel; | |
| 167 | + echo esc_html(Builder_Template_Helper::getTemplateByIndex($templateType)); | |
| 168 | + if (isset($postType->name)) { | |
| 169 | + echo ' <strong>-- ' . esc_html(ucwords($postType->name)) . '</strong>'; | |
| 170 | + } | |
| 162 | 171 | break; |
| 163 | 172 | case 'is_enabled': |
| 164 | - echo (Builder_Template_Helper::getTemplateId($templateType) == $post_id ? 'Active' : 'Inactive'); | |
| 173 | + echo Builder_Template_Helper::getTemplateId($templateType) == $post_id | |
| 174 | + ? esc_html__('Active', 'ultimate-store-kit') | |
| 175 | + : esc_html__('Inactive', 'ultimate-store-kit'); | |
| 165 | 176 | break; |
| 166 | 177 | } |
| 167 | 178 | } |
| 168 | 179 | |
| @@ -172,9 +183,10 @@ | ||
| 172 | 183 | if ($typenow !== Meta::POST_TYPE) { |
| 173 | 184 | return; |
| 174 | 185 | } |
| 175 | 186 | |
| 176 | - $selected = isset($_GET['type']) ? sanitize_key($_GET['type']) : ''; | |
| 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'])) : ''; | |
| 177 | 189 | ?> |
| 178 | 190 | <select name="type" id="type"> |
| 179 | 191 | <option value="all" <?php |
| 180 | 192 | selected('all', $selected); ?>><?php |
| @@ -235,8 +247,9 @@ | ||
| 235 | 247 | if ($typenow !== Meta::POST_TYPE) { |
| 236 | 248 | return; |
| 237 | 249 | } |
| 238 | 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. | |
| 239 | 252 | if ( |
| 240 | 253 | 'edit.php' == $pagenow |
| 241 | 254 | && isset($_GET['type']) |
| 242 | 255 | && $_GET['type'] != '' |
| @@ -241,12 +254,17 @@ | ||
| 241 | 254 | && isset($_GET['type']) |
| 242 | 255 | && $_GET['type'] != '' |
| 243 | 256 | && $_GET['type'] != 'all' |
| 244 | 257 | ) { |
| 245 | - $query->query_vars['meta_key'] = Meta::TEMPLATE_TYPE; | |
| 246 | - $query->query_vars['meta_value'] = sanitize_key($_GET['type']); | |
| 247 | - $query->query_vars['meta_compare'] = '='; | |
| 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 | + } | |
| 248 | 265 | } |
| 266 | + // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.DB.SlowDBQuery | |
| 249 | 267 | } |
| 250 | 268 | |
| 251 | 269 | |
| 252 | 270 | public function create_builder_template() { |
| @@ -254,20 +272,34 @@ | ||
| 254 | 272 | if (! current_user_can('manage_options')) { |
| 255 | 273 | wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403); |
| 256 | 274 | } |
| 257 | 275 | |
| 258 | - parse_str($_POST['data'], $data); | |
| 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 | + } | |
| 259 | 283 | |
| 260 | - if (! wp_verify_nonce($data['nonce'], 'usk-builder')) { | |
| 284 | + $nonce = isset($data['nonce']) ? sanitize_text_field($data['nonce']) : ''; | |
| 285 | + | |
| 286 | + if (! wp_verify_nonce($nonce, 'usk-builder')) { | |
| 261 | 287 | wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403); |
| 262 | 288 | } |
| 263 | 289 | |
| 264 | - $templateId = isset($data['template_id']) ? trim($data['template_id']) : ''; | |
| 265 | - $name = isset($data['template_name']) ? trim($data['template_name']) : ''; | |
| 266 | - $type = isset($data['template_type']) ? trim($data['template_type']) : ''; | |
| 267 | - $editWith = isset($data['edit_with']) ? trim($data['edit_with']) : 'elementor'; //gutenberg | |
| 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 | |
| 268 | 294 | $isEnabled = (isset($data['template_status']) && $data['template_status']) == 1 ? 1 : 0; |
| 269 | 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 | + | |
| 270 | 302 | $errors = []; |
| 271 | 303 | |
| 272 | 304 | if (empty($name)) { |
| 273 | 305 | $errors['template_name'] = 'Field is required'; |
| @@ -307,14 +339,13 @@ | ||
| 307 | 339 | } |
| 308 | 340 | |
| 309 | 341 | $post_id = wp_insert_post($page_data); |
| 310 | 342 | |
| 311 | - $enabledTemplate = strtolower(Meta::TEMPLATE_ID . $type); | |
| 312 | 343 | if ($isEnabled == 1) { |
| 313 | - update_option($enabledTemplate, $post_id); | |
| 344 | + Meta::update_template_option($type, $post_id); | |
| 314 | 345 | } else { |
| 315 | - if (get_option($enabledTemplate) == $post_id) { | |
| 316 | - delete_option($enabledTemplate); | |
| 346 | + if (Meta::get_template_option($type) == $post_id) { | |
| 347 | + Meta::delete_template_option($type); | |
| 317 | 348 | } |
| 318 | 349 | } |
| 319 | 350 | |
| 320 | 351 | |
| @@ -351,9 +382,11 @@ | ||
| 351 | 382 | if (! current_user_can('manage_options')) { |
| 352 | 383 | wp_send_json_error(['success' => false, 'errors_arr' => ['permission' => 'Permission denied']], 403); |
| 353 | 384 | } |
| 354 | 385 | |
| 355 | - if (! wp_verify_nonce($_REQUEST['nonce'], 'ultimate_store_kit_builder_nonce')) { | |
| 386 | + $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : ''; | |
| 387 | + | |
| 388 | + if (! wp_verify_nonce($nonce, 'ultimate_store_kit_builder_nonce')) { | |
| 356 | 389 | wp_send_json_error(['success' => false, 'errors_arr' => ['nonce' => 'Invalid nonce']], 403); |
| 357 | 390 | } |
| 358 | 391 | |
| 359 | 392 | if (isset($_REQUEST['template_id']) && ! empty($_REQUEST['template_id'])) { |
| @@ -364,10 +397,9 @@ | ||
| 364 | 397 | $meta = get_post_meta($templateData->ID); |
| 365 | 398 | |
| 366 | 399 | |
| 367 | 400 | $templateType = isset($meta[Meta::TEMPLATE_TYPE][0]) ? $meta[Meta::TEMPLATE_TYPE][0] : ''; |
| 368 | - $enabledTemplate = strtolower(Meta::TEMPLATE_ID . $templateType); | |
| 369 | - $enabledTemplate = get_option($enabledTemplate); | |
| 401 | + $enabledTemplate = Meta::get_template_option($templateType); | |
| 370 | 402 | |
| 371 | 403 | wp_send_json_success([ |
| 372 | 404 | 'id' => $templateData->ID, |
| 373 | 405 | 'name' => $templateData->post_title, |
| @@ -389,9 +421,9 @@ | ||
| 389 | 421 | $screen = get_current_screen(); |
| 390 | 422 | |
| 391 | 423 | if (is_object($screen) && Meta::POST_TYPE == $screen->post_type) { |
| 392 | 424 | wp_enqueue_style('ultimate-store-kit-builder', BDTUSK_ASSETS_URL . 'admin/others/css/ultimate-builder.css', [], BDTUSK_VER); |
| 393 | - wp_enqueue_script('ultimate-store-kit-builder', BDTUSK_ASSETS_URL . 'admin/others/js/ultimate-builder.js', ['jquery', 'wp-i18n'], 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); | |
| 394 | 426 | wp_set_script_translations('ultimate-store-kit-builder', 'ultimate-store-kit'); |
| 395 | 427 | |
| 396 | 428 | wp_localize_script('ultimate-store-kit-builder', 'UltimateStoreKitConfigBuilder', [ |
| 397 | 429 | 'ajaxurl' => admin_url('admin-ajax.php'), |
| @@ -466,10 +498,12 @@ | ||
| 466 | 498 | /** |
| 467 | 499 | * Simple fix for WPML and Elementor integration |
| 468 | 500 | */ |
| 469 | 501 | public function fix_wpml_elementor_data() { |
| 470 | - if (isset($_REQUEST['post']) && get_post_type($_REQUEST['post']) === Meta::POST_TYPE) { | |
| 471 | - $post_id = (int) $_REQUEST['post']; | |
| 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) { | |
| 472 | 506 | $meta_data = get_post_meta($post_id, '_elementor_data', true); |
| 473 | 507 | |
| 474 | 508 | // If metadata exists but is in array format, convert it to JSON string |
| 475 | 509 | if (is_array($meta_data)) { |