PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.6.6
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.6.6
2.6.6 2.6.5 2.6.4 2.6.3 2.6.2 2.6.1 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.4.0 2.3.3 2.3.2 2.3.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 All 128 releases
← All changes | includes/admin/class-api.php +596 -60 2.6.22.6.6 View file →
@@ -837,9 +837,9 @@
837 837 $email = isset( $_POST['email'] ) ? strtolower( sanitize_email( wp_unslash( $_POST['email'] ) ) ) : false;
838 838
839 839 /**Condtion user for user logout & expire token*/
840 840 if ( 'Token is Expired' === $status || 'Authorization Token not found' === $status ) {
841 - delete_transient( 'wdkit_auth_' . $email );
841 + delete_transient( 'wdkit_auth_' . wdesignkit_cloud_session_key( $email ) );
842 842 // Clear stored license data when token expires so banner shows again
843 843 delete_option( 'wdkit_licence_data' );
844 844 }
845 845
@@ -851,9 +851,9 @@
851 851 if ( ! empty( $response['credits']['wdkit_licence'] ) && is_array( $response['credits']['wdkit_licence'] ) ) {
852 852 $wdkit_licence = $response['credits']['wdkit_licence'];
853 853 // Handle serialized data
854 854 if ( is_string( $wdkit_licence ) && is_serialized( $wdkit_licence ) ) {
855 - $wdkit_licence = unserialize( $wdkit_licence );
855 + $wdkit_licence = unserialize( $wdkit_licence, array( 'allowed_classes' => false ) );
856 856 }
857 857 if ( ! empty( $wdkit_licence ) && is_array( $wdkit_licence ) ) {
858 858 update_option( 'wdkit_licence_data', $wdkit_licence );
859 859 }
@@ -884,8 +884,16 @@
884 884 $credits = ! empty( $response['credits']['widget_limit']['meta_value'] ) ? $response['credits']['widget_limit']['meta_value'] : 10;
885 885 $server_list = ! empty( $response['widgettemplate'] ) ? $response['widgettemplate'] : array();
886 886 $db_builder_list = ! empty( $response['widgetbuilder'] ) ? $response['widgetbuilder'] : array();
887 887
888 + // Whether this call actually carried the server widget list that activation state is
889 + // derived from. Captured before the loops below, which unset() matched $server_list
890 + // entries as they go. wdkit_meta_data() calls this method with array(), and without
891 + // this flag that call rebuilt $db_widget from local widgets only — every one of which
892 + // is forced 'active' further down — and then wrote the empty result over
893 + // wkit_deactivate_widgets, erasing every deactivation the user had made.
894 + $has_server_widgets = ! empty( $server_list );
895 +
888 896 $placeholderimg = WDKIT_URL . 'assets/images/placeholder.jpg';
889 897
890 898 $local_list = $this->wdkit_get_local_widgets();
891 899
@@ -971,13 +979,31 @@
971 979 );
972 980 }
973 981 }
974 982
975 - $get_db_widget = get_option( 'wkit_deactivate_widgets', array() );
976 - if ( empty( $get_db_widget ) ) {
977 - add_option( 'wkit_deactivate_widgets', $db_widget, '', 'yes' );
978 - } else {
979 - update_option( 'wkit_deactivate_widgets', $db_widget );
983 + // Only persist activation state when the server list it is derived from was actually
984 + // supplied. See $has_server_widgets above.
985 + if ( $has_server_widgets ) {
986 + // update_option() creates the row when it is missing, so it covers both cases.
987 + // The previous add_option()/update_option() split was chosen on empty( $option ),
988 + // but wdkit_db_widgetlist() creates this row as an empty array on every install —
989 + // so the empty branch ran while the row already existed, and add_option() is a
990 + // no-op for an existing option. Deactivating from the My Widgets screen was
991 + // therefore silently discarded on effectively every site. Autoload stays 'yes',
992 + // matching the original add_option() call and wdkit_db_widgetlist().
993 + update_option( 'wkit_deactivate_widgets', $db_widget, 'yes' );
994 +
995 + // The cached widget registry bakes in wkit_deactivate_widgets membership and is
996 + // stored as a no-expiry transient, so it never self-heals. Without this the
997 + // loaders kept registering a widget the user had just switched off (and kept
998 + // hiding one they had switched back on) until the transient was flushed by hand.
999 + // The write above is not per-builder — one save can change any builder's set, and
1000 + // a widget can move between builders — so clear all four.
1001 + if ( function_exists( 'wdesignkit_invalidate_widget_registry' ) ) {
1002 + foreach ( array( 'elementor', 'gutenberg', 'gutenberg_core', 'bricks' ) as $builder_slug ) {
1003 + wdesignkit_invalidate_widget_registry( $builder_slug );
1004 + }
1005 + }
980 1006 }
981 1007
982 1008 return $final;
983 1009 }
@@ -1114,8 +1140,24 @@
1114 1140 }
1115 1141
1116 1142 $response = WDesignKit_Data_Query::get_data( 'save_template', $args );
1117 1143
1144 + /**
1145 + * The cloud call can come back as a WP_Error (timeout, DNS, refused) or with an
1146 + * empty / unparsable body, which json_decode()s to null. Forwarding that as-is
1147 + * makes admin-ajax answer with a literal `null` that the editor then reads
1148 + * `.id` off, killing the whole app. Normalise it to the failure shape used above.
1149 + */
1150 + if ( is_wp_error( $response ) || ! is_array( $response ) ) {
1151 + $response = array(
1152 + 'id' => 0,
1153 + 'editpage' => '',
1154 + 'message' => esc_html__( 'Template Not Saved !', 'wdesignkit' ),
1155 + 'description' => is_wp_error( $response ) ? $response->get_error_message() : esc_html__( 'Could not reach the WDesignKit server. Please try again.', 'wdesignkit' ),
1156 + 'success' => false,
1157 + );
1158 + }
1159 +
1118 1160 wp_send_json( $response );
1119 1161 wp_die();
1120 1162 }
1121 1163
@@ -1139,9 +1181,11 @@
1139 1181 'success' => false,
1140 1182 );
1141 1183 } else {
1142 1184 $temp_content = str_replace( '\\', '', $temp_content );
1143 - $temp_content = wp_remote_get( $temp_content )['body'];
1185 + // SSRF guard (CWE-918): validate the resolved host before fetching a caller-supplied URL.
1186 + $fetched = wdesignkit_safe_remote_get( $temp_content );
1187 + $temp_content = is_wp_error( $fetched ) ? '' : wp_remote_retrieve_body( $fetched );
1144 1188 $temp_content = base64_encode( $temp_content );
1145 1189
1146 1190 $args = array(
1147 1191 'token' => $token,
@@ -1237,8 +1281,235 @@
1237 1281 * Get Elementor Global color and Typography.
1238 1282 *
1239 1283 * @since 1.1.16
1240 1284 */
1285 + /**
1286 + * Kit settings holding The Plus Addons' own globals.
1287 + *
1288 + * These sit in the Elementor kit's `_elementor_page_settings` alongside Elementor's
1289 + * system_colors / system_typography, but the save flow only ever collected the four
1290 + * Elementor keys. Widgets reference an entry in these lists by its `_id` through a
1291 + * `tp_global_preset` setting, so a template saved without them travels with the
1292 + * reference but not the definition - which is why imported sections come in missing
1293 + * their button styling, radii and shadows.
1294 + *
1295 + * @since 2.6.4
1296 + *
1297 + * @return array Kit setting keys.
1298 + */
1299 + private function wdkit_tp_global_kit_keys() {
1300 + return array(
1301 + 'tp_global_button_style_list',
1302 + 'tp_global_dimensions_list',
1303 + 'tp_global_box_shadow_list',
1304 + 'tp_global_gradient_list',
1305 + 'tp_global_gsap_list',
1306 + 'tp_global_scroll_animation_list',
1307 + 'tp_text_global_gsap_list',
1308 + 'tp_image_global_gsap_list',
1309 + );
1310 + }
1311 +
1312 + /**
1313 + * Merge incoming Plus globals into the active kit, keyed by `_id`.
1314 + *
1315 + * Entries are matched on their `_id`, never on position: an existing entry is always
1316 + * left as it is, and only genuinely new ones are appended. That matters because
1317 + * widgets - and the entries themselves, a button style points at dimension and shadow
1318 + * entries - resolve by `_id`. Renumbering or overwriting would repoint references on
1319 + * the destination site's own content.
1320 + *
1321 + * @since 2.6.4
1322 + *
1323 + * @param array $incoming Lists captured with the template.
1324 + * @return bool True when the kit was changed.
1325 + */
1326 + /**
1327 + * Global colour / typography ids this site already defines.
1328 + *
1329 + * @since 2.6.4
1330 + *
1331 + * @param array $kit_meta Kit `_elementor_page_settings`.
1332 + * @return array{color:array<string,bool>,typography:array<string,bool>}
1333 + */
1334 + private function wdkit_known_global_ids( $kit_meta ) {
1335 + $known = array(
1336 + 'color' => array(),
1337 + 'typography' => array(),
1338 + );
1339 +
1340 + $sources = array(
1341 + 'color' => array( 'system_colors', 'custom_colors' ),
1342 + 'typography' => array( 'system_typography', 'custom_typography' ),
1343 + );
1344 +
1345 + foreach ( $sources as $kind => $keys ) {
1346 + foreach ( $keys as $key ) {
1347 + if ( empty( $kit_meta[ $key ] ) || ! is_array( $kit_meta[ $key ] ) ) {
1348 + continue;
1349 + }
1350 +
1351 + foreach ( $kit_meta[ $key ] as $entry ) {
1352 + if ( ! empty( $entry['_id'] ) ) {
1353 + $known[ $kind ][ $entry['_id'] ] = true;
1354 + }
1355 + }
1356 + }
1357 + }
1358 +
1359 + return $known;
1360 + }
1361 +
1362 + /**
1363 + * Make one incoming Plus global's colour / font references resolvable here.
1364 + *
1365 + * A Plus global can point at an Elementor global: the "Primary Button" entry holds
1366 + * `__globals__: { text_color: "globals/colors?id=72e09b4", … }`, which The Plus Addons
1367 + * turns into `var(--e-global-color-72e09b4)`. Elementor only emits that variable for ids
1368 + * present in the kit, so on a site without `72e09b4` the button renders with no colour.
1369 + *
1370 + * Two cases, and the difference is deliberate:
1371 + *
1372 + * - The site ALREADY defines that id — leave the reference alone. The button then picks
1373 + * up the destination's own colour, which is the point of a global. Their palette is
1374 + * never read from or written to beyond this check.
1375 + * - The site does NOT define it — write the captured value straight into the entry and
1376 + * drop the reference, so it renders as designed.
1377 + *
1378 + * Nothing is ever added to the user's global colours or fonts. An earlier version injected
1379 + * the missing definitions into their palette, which made the reference resolve but grew
1380 + * their Site Settings by every colour an imported template happened to use.
1381 + *
1382 + * @since 2.6.4
1383 + *
1384 + * @param array $entry One repeater entry.
1385 + * @param array $refs Definitions captured with the template.
1386 + * @param array $known Ids this site defines, from wdkit_known_global_ids().
1387 + * @return array Entry, with unresolvable references replaced by their values.
1388 + */
1389 + private function wdkit_resolve_entry_globals( $entry, $refs, $known ) {
1390 + if ( empty( $entry['__globals__'] ) || ! is_array( $entry['__globals__'] ) ) {
1391 + return $entry;
1392 + }
1393 +
1394 + foreach ( $entry['__globals__'] as $control => $ref ) {
1395 + if ( ! is_string( $ref ) || false === strpos( $ref, 'id=' ) ) {
1396 + continue;
1397 + }
1398 +
1399 + if ( false !== strpos( $ref, 'globals/colors' ) ) {
1400 + $kind = 'color';
1401 + } elseif ( false !== strpos( $ref, 'globals/typography' ) ) {
1402 + $kind = 'typography';
1403 + } else {
1404 + continue;
1405 + }
1406 +
1407 + $id = substr( $ref, strpos( $ref, 'id=' ) + 3 );
1408 + if ( '' === $id || isset( $known[ $kind ][ $id ] ) ) {
1409 + // Defined here already — their value wins.
1410 + continue;
1411 + }
1412 +
1413 + $definition = null;
1414 + foreach ( ( $refs[ $kind ] ?? array() ) as $candidate ) {
1415 + if ( is_array( $candidate ) && ( $candidate['_id'] ?? '' ) === $id ) {
1416 + $definition = $candidate;
1417 + break;
1418 + }
1419 + }
1420 +
1421 + if ( null === $definition ) {
1422 + // Nothing captured for it, so leave the reference rather than blank the field.
1423 + continue;
1424 + }
1425 +
1426 + if ( 'color' === $kind ) {
1427 + if ( empty( $definition['color'] ) ) {
1428 + continue;
1429 + }
1430 +
1431 + $entry[ $control ] = $definition['color'];
1432 + } else {
1433 + // A typography global expands into its own set of controls: the reference is
1434 + // held under e.g. `typography_typography`, and each definition key replaces
1435 + // that suffix — `typography_font_family`, `typography_font_weight`, and so on.
1436 + foreach ( $definition as $def_key => $def_value ) {
1437 + if ( '_id' === $def_key || 'title' === $def_key ) {
1438 + continue;
1439 + }
1440 +
1441 + $entry[ str_replace( 'typography_typography', $def_key, $control ) ] = $def_value;
1442 + }
1443 + }
1444 +
1445 + unset( $entry['__globals__'][ $control ] );
1446 + }
1447 +
1448 + return $entry;
1449 + }
1450 +
1451 + private function wdkit_merge_tp_globals( $incoming, $refs = array() ) {
1452 + if ( empty( $incoming ) || ! is_array( $incoming ) ) {
1453 + return false;
1454 + }
1455 +
1456 + $kit_id = get_option( 'elementor_active_kit' );
1457 + if ( empty( $kit_id ) ) {
1458 + return false;
1459 + }
1460 +
1461 + $kit_meta = get_post_meta( $kit_id, '_elementor_page_settings', true );
1462 + if ( ! is_array( $kit_meta ) ) {
1463 + $kit_meta = array();
1464 + }
1465 +
1466 + // Which global ids this site already defines. The Plus Addons turns a reference into
1467 + // var(--e-global-color-<_id>), and Elementor only emits that variable for ids in the
1468 + // kit — so a reference the destination does not define resolves to nothing at all.
1469 + $known = $this->wdkit_known_global_ids( $kit_meta );
1470 +
1471 + $changed = false;
1472 +
1473 + foreach ( $this->wdkit_tp_global_kit_keys() as $key ) {
1474 + if ( empty( $incoming[ $key ] ) || ! is_array( $incoming[ $key ] ) ) {
1475 + continue;
1476 + }
1477 +
1478 + $existing = ( ! empty( $kit_meta[ $key ] ) && is_array( $kit_meta[ $key ] ) ) ? $kit_meta[ $key ] : array();
1479 +
1480 + $seen = array();
1481 + foreach ( $existing as $entry ) {
1482 + if ( ! empty( $entry['_id'] ) ) {
1483 + $seen[ $entry['_id'] ] = true;
1484 + }
1485 + }
1486 +
1487 + foreach ( $incoming[ $key ] as $entry ) {
1488 + if ( ! is_array( $entry ) || empty( $entry['_id'] ) || isset( $seen[ $entry['_id'] ] ) ) {
1489 + continue;
1490 + }
1491 +
1492 + // Only ever rewrite the entry being added — never one already in the kit.
1493 + $existing[] = $this->wdkit_resolve_entry_globals( $entry, $refs, $known );
1494 + $seen[ $entry['_id'] ] = true;
1495 + $changed = true;
1496 + }
1497 +
1498 + $kit_meta[ $key ] = array_values( $existing );
1499 + }
1500 +
1501 + if ( $changed ) {
1502 + update_post_meta( $kit_id, '_elementor_page_settings', $kit_meta );
1503 +
1504 + // Writing kit meta directly does not rebuild the kit stylesheet, so the
1505 + // merged globals would never reach the frontend.
1506 + $this->wdkit_regenerate_elementor_kit_css();
1507 + }
1508 +
1509 + return $changed;
1510 + }
1511 +
1241 1512 protected function wdkit_get_global_val() {
1242 1513
1243 1514 $builder = isset( $_POST['builder'] ) ? strtolower( sanitize_text_field( $_POST['builder'] ) ) : '';
1244 1515
@@ -2521,34 +2792,56 @@
2521 2792 $error_message = $response->get_error_message();
2522 2793
2523 2794 $result = $this->tpae_set_response( false, 'oops', 'oops', '' );
2524 2795 } else {
2525 - $theme_info = unserialize( $response['body'] );
2796 + // api.wordpress.org's theme_information response is a serialized stdClass
2797 + // (accessed below via ->name / ->download_link). allowed_classes => false
2798 + // blocks stdClass too, turning it into an __PHP_Incomplete_Class whose
2799 + // properties silently don't exist — allow only stdClass, still refusing any
2800 + // other (potentially dangerous) class the payload might reference.
2801 + $theme_info = unserialize( $response['body'], array( 'allowed_classes' => array( 'stdClass' ) ) );
2526 2802 $theme_name = $theme_info->name;
2527 2803 $theme_zip_url = $theme_info->download_link;
2528 2804
2529 - global $wp_filesystem;
2530 - // Install the theme
2531 - $theme = wp_remote_get( $theme_zip_url, array( 'timeout' => 30 ) );
2805 + // SSRF guard (CWE-918): validate the resolved host before fetching the ZIP
2806 + // referenced by the external theme_info response.
2807 + if ( ! wdesignkit_validate_external_url( $theme_zip_url ) ) {
2808 + return array(
2809 + 'message' => esc_html__( 'Theme Not Activated !', 'wdesignkit' ),
2810 + 'description' => esc_html__( 'The theme package URL is not allowed.', 'wdesignkit' ),
2811 + 'status' => 'inactive',
2812 + 'success' => false,
2813 + );
2814 + }
2532 2815
2533 2816 if ( ! function_exists( 'WP_Filesystem' ) ) {
2534 2817 require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' );
2535 2818 }
2536 2819
2820 + require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/class-wp-upgrader.php' );
2821 + require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/theme.php' );
2822 +
2537 2823 WP_Filesystem();
2538 2824
2539 2825 $active_theme = wp_get_theme();
2540 2826 $theme_name = $active_theme->get( 'Name' );
2541 2827
2542 - $wp_filesystem->put_contents( WP_CONTENT_DIR . '/themes/' . $theme_slug . '.zip', $theme['body'] );
2543 - $zip = new ZipArchive();
2544 - if ( $zip->open( WP_CONTENT_DIR . '/themes/' . $theme_slug . '.zip' ) === true ) {
2545 - $zip->extractTo( WP_CONTENT_DIR . '/themes/' );
2546 - $zip->close();
2828 + // Install via WordPress core's Theme_Upgrader instead of manually fetching and
2829 + // ZipArchive::extractTo()'ing the remote package: core already performs the
2830 + // standard download -> unpack -> validate-package-structure -> move-into-place
2831 + // flow (including cleanup on failure) used for every trusted theme install.
2832 + $upgrader = new Theme_Upgrader( new Automatic_Upgrader_Skin() );
2833 + $install = $upgrader->install( $theme_zip_url );
2834 +
2835 + if ( is_wp_error( $install ) || ! $install ) {
2836 + return array(
2837 + 'message' => esc_html__( 'Theme Not Activated !', 'wdesignkit' ),
2838 + 'description' => is_wp_error( $install ) ? $install->get_error_message() : esc_html__( 'Theme could not be installed.', 'wdesignkit' ),
2839 + 'status' => 'inactive',
2840 + 'success' => false,
2841 + );
2547 2842 }
2548 2843
2549 - $wp_filesystem->delete( WP_CONTENT_DIR . '/themes/' . $theme_slug . '.zip' );
2550 -
2551 2844 $activate_result = switch_theme( $name );
2552 2845
2553 2846 if ( ! is_wp_error( $activate_result ) ) {
2554 2847 $response = array(
@@ -2746,9 +3039,9 @@
2746 3039
2747 3040 if ( ! empty( $meta_data ) ) {
2748 3041 foreach ( $meta_data as $meta_key => $meta_val ) {
2749 3042 if ( ! empty( $meta_val[0] ) && is_serialized( $meta_val[0] ) ) {
2750 - $meta_val[0] = maybe_unserialize( $meta_val[0] );
3043 + $meta_val[0] = unserialize( $meta_val[0], array( 'allowed_classes' => false ) );
2751 3044 }
2752 3045
2753 3046 if ( get_post_meta( get_the_ID(), $meta_key, true ) === '' ) {
2754 3047 add_post_meta( get_the_ID(), $meta_key, $meta_val[0] );
@@ -2759,8 +3052,34 @@
2759 3052 }
2760 3053 }
2761 3054 }
2762 3055
3056 + /**
3057 + * Fires after a template has been imported from the cloud.
3058 + *
3059 + * WDesignKit's templates live in the cloud, so nothing local records that an import
3060 + * happened — there is no post type, no option, nothing to count after the fact. This is the
3061 + * only moment the information exists.
3062 + *
3063 + * @since 2.6.4
3064 + *
3065 + * @param string $kind 'single' or 'kit'.
3066 + * @param string $builder Builder the template was imported for, e.g. 'elementor'.
3067 + * @param int $count How many templates this import brought in.
3068 + */
3069 + // Only a completed import counts. The cloud's failure shape for this endpoint family sets
3070 + // content => 'error' (see the sibling check in wdkit_import_kit_template() above) — that is
3071 + // non-empty, so the previous `||` fired the counter on failed imports too. Require success
3072 + // AND an absent/non-'error' content instead.
3073 + if ( ! empty( $response['success'] ) && ( ! isset( $response['content'] ) || 'error' !== $response['content'] ) ) {
3074 + do_action(
3075 + 'wdkit_template_imported',
3076 + 'import_kit_template' === $api_type ? 'kit' : 'single',
3077 + isset( $_POST['builder'] ) ? sanitize_key( wp_unslash( $_POST['builder'] ) ) : '',
3078 + 1
3079 + );
3080 + }
3081 +
2763 3082 wp_send_json( $response );
2764 3083 wp_die();
2765 3084 }
2766 3085
@@ -2920,11 +3239,17 @@
2920 3239 return 0;
2921 3240 }
2922 3241
2923 3242 $fixed = 0;
3243 + $ids = array_unique( array_map( 'intval', $page_ids ) );
2924 3244
2925 - foreach ( array_unique( array_map( 'intval', $page_ids ) ) as $post_id ) {
3245 + // Primes the meta cache for the whole batch in one query, so the
3246 + // get_post_meta() call below hits the cache instead of issuing one query
3247 + // per imported page.
3248 + update_meta_cache( 'post', $ids );
2926 3249
3250 + foreach ( $ids as $post_id ) {
3251 +
2927 3252 if ( ! $post_id ) {
2928 3253 continue;
2929 3254 }
2930 3255
@@ -3581,8 +3906,28 @@
3581 3906 $output['description'] = $response['description'];
3582 3907 $output['data'] = $result;
3583 3908 $output['success'] = $response['success'];
3584 3909
3910 + // Counts the IMPORT ACTION, not what it brought in. A kit import always counts as 1 kit,
3911 + // no matter how many blocks/pages that kit contains — confirmed live: a single gutenberg
3912 + // kit import recorded total=680, kinds.kit=680, because $template_ids for that call was a
3913 + // 680-element array of the kit's own blocks and count( $template_ids ) counted every one of
3914 + // them. A page-kit's *size* is not tracking's concern; "was a kit imported" is.
3915 + //
3916 + // The 'single' branch keeps a defensive fallback for the one shape this endpoint's own
3917 + // $template_ids reliably takes when it is not a kit — a single {id, name, slug, thumb...}
3918 + // object — where count() would likewise count JSON keys instead of "1 template imported".
3919 + if ( ! empty( $output['success'] ) ) {
3920 + $is_kit = ( '' !== $website_kit );
3921 + $import_count = $is_kit ? 1 : ( isset( $template_ids['id'] ) ? 1 : ( is_array( $template_ids ) ? count( $template_ids ) : 1 ) );
3922 + do_action(
3923 + 'wdkit_template_imported',
3924 + $is_kit ? 'kit' : 'single',
3925 + sanitize_key( $builder ),
3926 + $import_count
3927 + );
3928 + }
3929 +
3585 3930 wp_send_json( $output );
3586 3931 wp_die();
3587 3932 }
3588 3933
@@ -3934,8 +4279,21 @@
3934 4279 }
3935 4280
3936 4281 if ( ! empty( $data ) && ! empty( $template_id ) && ! empty( $post_type ) && current_user_can( 'manage_options' ) ) {
3937 4282 $post_content = $data;
4283 + // Restore The Plus Addons' globals before the page is built, so the widgets'
4284 + // tp_global_preset references resolve as soon as it renders. Done here rather
4285 + // than in the save-template UI's confirmation dialog so that every import path
4286 + // - the library, the abilities, the theme builder - gets it.
4287 + if ( isset( $post_content->tp_globals ) && ! empty( $post_content->tp_globals ) ) {
4288 + $this->wdkit_merge_tp_globals(
4289 + json_decode( wp_json_encode( $post_content->tp_globals ), true ),
4290 + isset( $post_content->tp_global_refs )
4291 + ? json_decode( wp_json_encode( $post_content->tp_global_refs ), true )
4292 + : array()
4293 + );
4294 + }
4295 +
3938 4296 $post_title = isset( $post_content->title ) ? sanitize_text_field( $post_content->title ) : '';
3939 4297 $post_slug = isset( $post_content->slug ) ? sanitize_text_field( $post_content->slug ) : '';
3940 4298 $file_type = isset( $post_content->file_type ) ? sanitize_text_field( $post_content->file_type ) : '';
3941 4299 $content = isset( $post_content->content ) ? wp_slash( $post_content->content ) : '';
@@ -3982,9 +4340,9 @@
3982 4340 );
3983 4341 wp_die();
3984 4342 }
3985 4343
3986 - if ( ! empty( $thumb_image ) ) {
4344 + if ( ! empty( $thumb_image ) && wdesignkit_validate_external_url( $thumb_image ) ) {
3987 4345 // $featured_image_url = esc_url_raw( $thumb_image );
3988 4346 $tmp = download_url( $thumb_image );
3989 4347 if ( is_wp_error( $tmp ) ) {
3990 4348 error_log( 'Image download failed: ' . esc_html( $tmp->get_error_message() ) );
@@ -4020,9 +4378,9 @@
4020 4378 $custom_meta = isset( $post_content->custom_meta ) ? json_decode( wp_json_encode( $post_content->custom_meta ), true ) : '';
4021 4379 if ( ! empty( $custom_meta ) ) {
4022 4380 foreach ( $custom_meta as $meta_key => $meta_val ) {
4023 4381 if ( isset( $meta_val[0] ) && ! empty( $meta_val[0] ) && is_serialized( $meta_val[0] ) ) {
4024 - $meta_val[0] = maybe_unserialize( $meta_val[0] );
4382 + $meta_val[0] = unserialize( $meta_val[0], array( 'allowed_classes' => false ) );
4025 4383 }
4026 4384
4027 4385 if ( '' === get_post_meta( $inserted_post, $meta_key, true ) && isset( $meta_val[0] ) ) {
4028 4386 add_post_meta( $inserted_post, $meta_key, $meta_val[0] );
@@ -4054,8 +4412,15 @@
4054 4412 Tpgb_Library()->remove_backend_dir_files();
4055 4413 }
4056 4414
4057 4415 clean_post_cache( $inserted_post );
4416 +
4417 + // This whole method imports exactly one section per call — unlike
4418 + // wdkit_import_template()/wdkit_import_kit_template(), it never fired this hook
4419 + // at all, so single-section imports (Header/Footer/CTA/etc., a primary import
4420 + // path per the Template Type sidebar) were invisible to tracking entirely.
4421 + do_action( 'wdkit_template_imported', 'single', sanitize_key( $editor ), 1 );
4422 +
4058 4423 wp_send_json(
4059 4424 array(
4060 4425 $temp_id => $temp_detail,
4061 4426 'description' => 'Yay! Your Section has been Successfully Imported.',
@@ -4111,9 +4476,9 @@
4111 4476 }
4112 4477
4113 4478 $inserted_id = $new_document->get_main_id();
4114 4479
4115 - if ( ! empty( $thumb_image ) ) {
4480 + if ( ! empty( $thumb_image ) && wdesignkit_validate_external_url( $thumb_image ) ) {
4116 4481 // $featured_image_url = esc_url_raw( $thumb_image );
4117 4482 $tmp = download_url( $thumb_image );
4118 4483 if ( is_wp_error( $tmp ) ) {
4119 4484 error_log( 'Image download failed: ' . esc_html( $tmp->get_error_message() ) );
@@ -4167,9 +4532,9 @@
4167 4532 $custom_meta = isset( $post_content->custom_meta ) ? json_decode( wp_json_encode( $post_content->custom_meta ), true ) : '';
4168 4533 if ( ! empty( $custom_meta ) ) {
4169 4534 foreach ( $custom_meta as $meta_key => $meta_val ) {
4170 4535 if ( ! empty( $meta_val[0] ) && is_serialized( $meta_val[0] ) ) {
4171 - $meta_val[0] = maybe_unserialize( $meta_val[0] );
4536 + $meta_val[0] = unserialize( $meta_val[0], array( 'allowed_classes' => false ) );
4172 4537 }
4173 4538 if ( '' === get_post_meta( $inserted_id, $meta_key, true ) ) {
4174 4539 add_post_meta( $inserted_id, $meta_key, $meta_val[0] );
4175 4540 }
@@ -4193,8 +4558,12 @@
4193 4558 }
4194 4559
4195 4560 \Elementor\Plugin::$instance->files_manager->clear_cache();
4196 4561
4562 + // See the matching note in the Gutenberg branch above — this method never
4563 + // fired the tracking hook for either editor.
4564 + do_action( 'wdkit_template_imported', 'single', 'elementor', 1 );
4565 +
4197 4566 wp_send_json(
4198 4567 array(
4199 4568 $temp_id => $temp_detail,
4200 4569 'content' => $temp_con,
@@ -4330,8 +4699,12 @@
4330 4699 }
4331 4700
4332 4701 $image_url = esc_url_raw( $_POST['image_url'] );
4333 4702
4703 + if ( ! wdesignkit_validate_external_url( $image_url ) ) {
4704 + wp_send_json_error( 'Image could not be downloaded.' );
4705 + }
4706 +
4334 4707 $tmp_file = download_url( $image_url );
4335 4708 if ( is_wp_error( $tmp_file ) ) {
4336 4709 wp_send_json_error( 'Image could not be downloaded.' );
4337 4710 }
@@ -4367,9 +4740,21 @@
4367 4740
4368 4741 $upload_dir = wp_upload_dir();
4369 4742 $result_urls = array();
4370 4743
4744 + $colour_index = 0;
4371 4745 foreach ( $img_colors as $name => $rgb ) {
4746 + // $name is a key from the posted colours payload and went straight into the output
4747 + // filename, so traversal sequences in it steered imagepng() outside the upload
4748 + // directory (CWE-22, ClickUp 86d41ced6). sanitize_file_name() flattens it to one
4749 + // path segment; a key made only of dots/separators sanitizes to empty, so fall back
4750 + // to a positional index rather than writing to a bare "colored--<time>.png".
4751 + ++$colour_index;
4752 + $safe_name = sanitize_file_name( (string) $name );
4753 + if ( '' === $safe_name ) {
4754 + $safe_name = 'colour-' . $colour_index;
4755 + }
4756 +
4372 4757 $new = imagecreatetruecolor( $width, $height );
4373 4758 imagesavealpha( $new, true );
4374 4759 imagealphablending( $new, false );
4375 4760
@@ -4395,9 +4780,9 @@
4395 4780 imagesetpixel( $new, $x, $y, $color );
4396 4781 }
4397 4782 }
4398 4783
4399 - $filename = 'colored-' . $name . '-' . time() . '.png';
4784 + $filename = 'colored-' . $safe_name . '-' . time() . '.png';
4400 4785 $filepath = $upload_dir['path'] . '/' . $filename;
4401 4786
4402 4787 imagepng( $new, $filepath );
4403 4788 imagedestroy( $new );
@@ -4712,12 +5097,73 @@
4712 5097 }
4713 5098
4714 5099 $response = json_decode( wp_json_encode( $response['data'] ), true );
4715 5100
5101 + $this->wdkit_cache_cloud_usage( $response );
5102 +
4716 5103 wp_send_json( $response );
4717 5104 wp_die();
4718 5105 }
4719 5106
5107 + /**
5108 + * Caches the storage / credit figures this response carried.
5109 + *
5110 + * This handler is the ONLY place those numbers ever exist on the site: the cloud endpoint
5111 + * authenticates with a user token that only a logged-in dashboard request carries, so the
5112 + * analytics heartbeat — which runs on cron with no user at all — can never fetch them itself.
5113 + * Caching them here is what lets Posimyth_Tracker_WDK report them, and it reports the cache's
5114 + * age alongside so a stale reading is recognisable as one.
5115 + *
5116 + * Field names are probed rather than assumed: the cloud has renamed these before, and the
5117 + * licence ability already carries six spellings of its own key field for the same reason. An
5118 + * unrecognised shape simply caches nothing rather than storing a wrong number.
5119 + *
5120 + * Only the figures are kept. No token, no account id, no email — the analytics consent copy
5121 + * promises non-sensitive data only, and this is read by the payload builder.
5122 + *
5123 + * @since 2.6.4
5124 + *
5125 + * @param mixed $data Decoded `data` object from the credits endpoint.
5126 + * @return void
5127 + */
5128 + private function wdkit_cache_cloud_usage( $data ) {
5129 + if ( ! is_array( $data ) ) {
5130 + return;
5131 + }
5132 +
5133 + $pick = static function ( $source, array $fields ) {
5134 + foreach ( $fields as $field ) {
5135 + if ( isset( $source[ $field ] ) && is_numeric( $source[ $field ] ) ) {
5136 + return (float) $source[ $field ];
5137 + }
5138 + }
5139 + return null;
5140 + };
5141 +
5142 + $usage = array(
5143 + 'storage_used' => $pick( $data, array( 'used_storage', 'storage_used', 'used_space' ) ),
5144 + 'storage_total' => $pick( $data, array( 'total_storage', 'storage_total', 'storage', 'total_space' ) ),
5145 + 'credit_used' => $pick( $data, array( 'used_credit', 'credit_used', 'used_credits' ) ),
5146 + 'credit_total' => $pick( $data, array( 'total_credit', 'credit_total', 'credits', 'real_credit' ) ),
5147 + );
5148 +
5149 + $usage = array_filter(
5150 + $usage,
5151 + static function ( $value ) {
5152 + return null !== $value;
5153 + }
5154 + );
5155 +
5156 + if ( empty( $usage ) ) {
5157 + return;
5158 + }
5159 +
5160 + $usage['cached_at'] = gmdate( 'Y-m-d H:i:s' );
5161 +
5162 + // Not autoloaded: read once a week by the heartbeat, never on a front-end request.
5163 + update_option( 'wdkit_cloud_usage', $usage, false );
5164 + }
5165 +
4720 5166 public function wdkit_nxt_thembuilder_reset() {
4721 5167 $post_id = isset( $_POST['post_id'] ) ? sanitize_text_field( $_POST['post_id'] ) : '';
4722 5168 $sections_layout = get_post_meta( $post_id, 'nxt-hooks-layout-sections', true );
4723 5169
@@ -5050,10 +5496,35 @@
5050 5496 'description' => esc_html__( 'widget JSON file not found.', 'wdesignkit' ),
5051 5497 );
5052 5498 }
5053 5499
5054 - $json_path = WDKIT_BUILDER_PATH . "/{$widget_type}/{$folder_name}/{$file_name}";
5500 + // Read-side twin of the write and delete traversals fixed in 86d41cckh / 86d41ccz2: all
5501 + // three segments arrive from $_POST with only wp_unslash() applied — which strips
5502 + // nothing path-relevant — so "../" in any of them walked out of the builder directory
5503 + // and this handler returned the decoded contents of any .json file the web server user
5504 + // could read (CWE-22, ClickUp 86d41zaun).
5505 + $safe_path = wdesignkit_widget_path_guard( $widget_type, $folder_name, $file_name );
5055 5506
5507 + if ( false === $safe_path || '' === $safe_path['folder'] || '' === $safe_path['file'] ) {
5508 + return array(
5509 + 'success' => false,
5510 + 'message' => esc_html__( 'Widget JSON not found', 'wdesignkit' ),
5511 + 'description' => esc_html__( 'Invalid widget path.', 'wdesignkit' ),
5512 + );
5513 + }
5514 +
5515 + $json_path = $safe_path['base'];
5516 +
5517 + // Re-check the resolved file: the component guard above cannot see a symlink. Returns
5518 + // false for a path that does not exist, which is the same answer we want anyway.
5519 + if ( ! wdesignkit_path_inside_builder_dir( "$json_path.json" ) ) {
5520 + return array(
5521 + 'success' => false,
5522 + 'message' => esc_html__( 'Widget JSON not found', 'wdesignkit' ),
5523 + 'description' => esc_html__( 'widget JSON file not found.', 'wdesignkit' ),
5524 + );
5525 + }
5526 +
5056 5527 $json_data = wp_json_file_decode( "$json_path.json" );
5057 5528 if ( ! empty( $json_data ) ) {
5058 5529 $result = (object) array(
5059 5530 'success' => true,
@@ -5079,9 +5550,9 @@
5079 5550 *
5080 5551 * @since 1.0.0
5081 5552 */
5082 5553 protected function wdkit_download_widget() {
5083 - $data = ! empty( $_POST['widget_info'] ) ? $this->wdkit_sanitizer_bypass( $_POST, 'widget_info', 'none' ) : '';
5554 + $data = ! empty( $_POST['widget_info'] ) ? $this->wdkit_extract_post_field( $_POST, 'widget_info', 'none' ) : '';
5084 5555 $data = json_decode( stripslashes( $data ) );
5085 5556
5086 5557 $array_data = array(
5087 5558 'token' => isset( $data->token ) ? sanitize_text_field( $data->token ) : '',
@@ -5149,14 +5620,34 @@
5149 5620 if ( ! is_array( $json_data ) ) {
5150 5621 $json_data = json_decode( $json_data, true );
5151 5622 }
5152 5623
5153 - $title = ! empty( $json_data['widget_data']['widgetdata']['name'] ) ? sanitize_text_field( $json_data['widget_data']['widgetdata']['name'] ) : '';
5154 - $builder = ! empty( $json_data['widget_data']['widgetdata']['type'] ) ? sanitize_text_field( $json_data['widget_data']['widgetdata']['type'] ) : '';
5155 - $w_uniq = ! empty( $json_data['widget_data']['widgetdata']['widget_id'] ) ? sanitize_text_field( $json_data['widget_data']['widgetdata']['widget_id'] ) : '';
5624 + // Sanitize as filenames before use in the widget path (CWE-22): sanitize_file_name()
5625 + // on name/id and sanitize_key() + allowlist on the builder strip path separators and
5626 + // dots so a crafted cloud response cannot escape WDKIT_BUILDER_PATH.
5627 + $title = ! empty( $json_data['widget_data']['widgetdata']['name'] ) ? sanitize_file_name( $json_data['widget_data']['widgetdata']['name'] ) : '';
5628 + $builder = ! empty( $json_data['widget_data']['widgetdata']['type'] ) ? sanitize_key( $json_data['widget_data']['widgetdata']['type'] ) : '';
5629 + $w_uniq = ! empty( $json_data['widget_data']['widgetdata']['widget_id'] ) ? sanitize_file_name( $json_data['widget_data']['widgetdata']['widget_id'] ) : '';
5156 5630
5157 - $folder_name = str_replace( ' ', '-', $title ) . '_' . $w_uniq;
5158 - $file_name = str_replace( ' ', '_', $title ) . '_' . $w_uniq;
5631 + $allowed_builders = array( 'elementor', 'gutenberg', 'gutenberg_core', 'bricks' );
5632 + if ( '' === $title || '' === $w_uniq || ! in_array( $builder, $allowed_builders, true ) ) {
5633 + $responce = (object) array(
5634 + 'success' => false,
5635 + 'message' => esc_html__( 'Operation Failed!', 'wdesignkit' ),
5636 + 'description' => esc_html__( 'Invalid widget path.', 'wdesignkit' ),
5637 + );
5638 +
5639 + wp_send_json( $responce );
5640 + wp_die();
5641 + }
5642 +
5643 + // Canonical helpers replace spaces BEFORE sanitize_file_name(). $title above is
5644 + // already sanitized, which collapsed spaces to hyphens and left the underscore pass
5645 + // with nothing to do — a multi-word title wrote "My-Widget_id.json" next to the
5646 + // "My_Widget_id.php" the builder's save path writes. The loader pairs the two by
5647 + // swapping .php for .json, so the widget was silently dropped (ClickUp 86d41cck5).
5648 + $folder_name = wdesignkit_widget_folder_name( $title, $w_uniq );
5649 + $file_name = wdesignkit_widget_file_name( $title, $w_uniq );
5159 5650 $builder_type_path = WDKIT_BUILDER_PATH . "/{$builder}/";
5160 5651
5161 5652 if ( ! is_dir( $builder_type_path ) ) {
5162 5653 wp_mkdir_p( $builder_type_path );
@@ -5166,15 +5657,27 @@
5166 5657 wp_mkdir_p( $builder_type_path . $folder_name );
5167 5658 }
5168 5659
5169 5660 if ( ! empty( $img_url ) ) {
5170 - $img_body = wp_remote_get( $img_url );
5171 - $img_ext = pathinfo( $img_url )['extension'];
5661 + // SSRF guard (CWE-918): validate the resolved host before fetching.
5662 + $img_body = wdesignkit_safe_remote_get( $img_url );
5663 + if ( ! is_wp_error( $img_body ) ) {
5664 + // The remote extension was written verbatim here, so a cloud response naming a
5665 + // ".php" image put executable PHP in the builder directory (CWE-434,
5666 + // ClickUp 86d41cczd). An empty return means the bytes are not an image.
5667 + $img_ext = wdesignkit_safe_image_extension( $img_url, $img_body['body'] );
5172 5668
5173 - $wp_filesystem->put_contents( WDKIT_BUILDER_PATH . "/$builder/$folder_name/$file_name.$img_ext", $img_body['body'] );
5174 - $json_data['widget_data']['widgetdata']['w_image'] = WDKIT_SERVER_PATH . "/$builder/$folder_name/$file_name.$img_ext";
5669 + if ( '' !== $img_ext ) {
5670 + $wp_filesystem->put_contents( WDKIT_BUILDER_PATH . "/$builder/$folder_name/$file_name.$img_ext", $img_body['body'] );
5671 + $json_data['widget_data']['widgetdata']['w_image'] = WDKIT_SERVER_PATH . "/$builder/$folder_name/$file_name.$img_ext";
5672 + }
5673 + }
5175 5674 }
5176 5675
5676 + if ( function_exists( 'wdesignkit_invalidate_widget_registry' ) ) {
5677 + wdesignkit_invalidate_widget_registry( $builder );
5678 + }
5679 +
5177 5680 // Bug E fix (part 2): success was hardcoded false on the successful download path — always reported failure.
5178 5681 $result = (object) array(
5179 5682 'success' => true,
5180 5683 'message' => ! empty( $response['message'] ) ? $response['message'] : esc_html__( 'no message', 'wdesignkit' ),
@@ -5192,9 +5695,9 @@
5192 5695 *
5193 5696 * @since 1.0.0
5194 5697 */
5195 5698 protected function wdkit_add_widget() {
5196 - $data = ! empty( $_POST['widget_info'] ) ? $this->wdkit_sanitizer_bypass( $_POST, 'widget_info', 'none' ) : '';
5699 + $data = ! empty( $_POST['widget_info'] ) ? $this->wdkit_extract_post_field( $_POST, 'widget_info', 'none' ) : '';
5197 5700 $data = base64_decode( $data );
5198 5701 $data = json_decode( $data );
5199 5702
5200 5703 $title = isset( $data->title ) ? sanitize_text_field( $data->title ) : '';
@@ -5203,9 +5706,11 @@
5203 5706 $w_image = isset( $data->w_image ) ? esc_url_raw( $data->w_image ) : '';
5204 5707
5205 5708 if ( ! empty( $w_image ) ) {
5206 5709 $w_image = str_replace( '\\', '', $w_image );
5207 - $w_image = wp_remote_get( $w_image )['body'];
5710 + // SSRF guard (CWE-918): validate the resolved host before fetching.
5711 + $fetched = wdesignkit_safe_remote_get( $w_image );
5712 + $w_image = is_wp_error( $fetched ) ? '' : wp_remote_retrieve_body( $fetched );
5208 5713 }
5209 5714
5210 5715 $array_data = array(
5211 5716 'token' => isset( $data->token ) ? sanitize_text_field( $data->token ) : '',
@@ -5245,22 +5750,51 @@
5245 5750 $img_url = ! empty( $response['data']['imgurl'] ) ? $response['data']['imgurl'] : '';
5246 5751
5247 5752 if ( ! empty( $img_url ) && 'error' !== $res ) {
5248 5753
5249 - $img_body = wp_remote_get( $img_url );
5250 - $img_ext = pathinfo( $img_url )['extension'];
5251 - include_once ABSPATH . 'wp-admin/includes/file.php';
5252 - \WP_Filesystem();
5253 - global $wp_filesystem;
5254 - $folder_name = str_replace( ' ', '-', $title ) . '_' . $w_uniq;
5255 - $file_name = str_replace( ' ', '_', $title ) . '_' . $w_uniq;
5256 - $file_path = WDKIT_BUILDER_PATH . "/$builder/$folder_name/$file_name";
5754 + // SSRF guard (CWE-918): validate the resolved host before fetching.
5755 + $img_body = wdesignkit_safe_remote_get( $img_url );
5756 + if ( ! is_wp_error( $img_body ) ) {
5757 + // Verified against the payload rather than trusted from the URL (CWE-434,
5758 + // ClickUp 86d41cczd); '' means the bytes are not an image we accept.
5759 + $img_ext = wdesignkit_safe_image_extension( $img_url, $img_body['body'] );
5760 + include_once ABSPATH . 'wp-admin/includes/file.php';
5761 + \WP_Filesystem();
5762 + global $wp_filesystem;
5763 + // Canonical helpers, so the JSON read and the image write here address the same
5764 + // base name every other writer uses (ClickUp 86d41cck5). They also apply
5765 + // sanitize_file_name(), which $title and $w_uniq had not been through.
5766 + $folder_name = wdesignkit_widget_folder_name( $title, $w_uniq );
5767 + $file_name = wdesignkit_widget_file_name( $title, $w_uniq );
5257 5768
5258 - $u_r_l = wp_json_file_decode( "$file_path.json" );
5259 - $u_r_l->widget_data->widgetdata->w_image = WDKIT_SERVER_PATH . "/$builder/$folder_name/$file_name.$img_ext";
5769 + // $builder reaches here with only sanitize_text_field() applied and no
5770 + // allowlist, so it was a live traversal segment in this path (CWE-22,
5771 + // ClickUp 86d41cckh). Unlike the download handler earlier in this file, this
5772 + // one had neither the builder allowlist nor a containment check.
5773 + $safe_path = wdesignkit_widget_path_guard( $builder, $folder_name, $file_name );
5774 + if ( false === $safe_path || ! wdesignkit_path_inside_builder_dir( $safe_path['dir'] ) ) {
5775 + wp_send_json(
5776 + (object) array(
5777 + 'success' => false,
5778 + 'message' => esc_html__( 'Operation Failed!', 'wdesignkit' ),
5779 + 'description' => esc_html__( 'Invalid widget path.', 'wdesignkit' ),
5780 + )
5781 + );
5782 + wp_die();
5783 + }
5260 5784
5261 - $wp_filesystem->put_contents( "$file_path.json", wp_json_encode( $u_r_l ) );
5262 - $wp_filesystem->put_contents( "$file_path.$img_ext", $img_body['body'] );
5785 + $builder = $safe_path['builder'];
5786 + $file_path = $safe_path['base'];
5787 +
5788 + $u_r_l = wp_json_file_decode( "$file_path.json" );
5789 +
5790 + if ( '' !== $img_ext ) {
5791 + $u_r_l->widget_data->widgetdata->w_image = WDKIT_SERVER_PATH . "/$builder/$folder_name/$file_name.$img_ext";
5792 + $wp_filesystem->put_contents( "$file_path.$img_ext", $img_body['body'] );
5793 + }
5794 +
5795 + $wp_filesystem->put_contents( "$file_path.json", wp_json_encode( $u_r_l ) );
5796 + }
5263 5797 }
5264 5798
5265 5799 wp_send_json( $response );
5266 5800 wp_die();
@@ -5360,9 +5894,9 @@
5360 5894 'template' => isset( $get_setting['template'] ) ? $get_setting['template'] : true,
5361 5895 'gutenberg_builder' => isset( $get_setting['gutenberg_builder'] ) ? $get_setting['gutenberg_builder'] : true,
5362 5896 'gutenberg_core_builder' => isset( $get_setting['gutenberg_core_builder'] ) ? $get_setting['gutenberg_core_builder'] : false,
5363 5897 'elementor_builder' => isset( $get_setting['elementor_builder'] ) ? $get_setting['elementor_builder'] : true,
5364 - 'bricks_builder' => isset( $get_setting['bricks_builder'] ) ? $get_setting['bricks_builder'] : false,
5898 + 'bricks_builder' => isset( $get_setting['bricks_builder'] ) ? $get_setting['bricks_builder'] : true,
5365 5899 'gutenberg_template' => isset( $get_setting['gutenberg_template'] ) ? $get_setting['gutenberg_template'] : true,
5366 5900 'elementor_template' => isset( $get_setting['elementor_template'] ) ? $get_setting['elementor_template'] : true,
5367 5901 'code_snippet' => isset( $get_setting['code_snippet'] ) ? $get_setting['code_snippet'] : true,
5368 5902 'cross_copy_paste' => isset( $get_setting['cross_copy_paste'] ) ? $get_setting['cross_copy_paste'] : false,
@@ -5472,21 +6006,21 @@
5472 6006 if ( ! empty( $response['data'] ) ) {
5473 6007 $response = json_decode( wp_json_encode( $response['data'] ), true );
5474 6008
5475 6009 if ( ! empty( $response['data']['tpae_licence'] ) && is_serialized( $response['data']['tpae_licence'] ) ) {
5476 - $response['data']['tpae_licence'] = unserialize( $response['data']['tpae_licence'] );
6010 + $response['data']['tpae_licence'] = unserialize( $response['data']['tpae_licence'], array( 'allowed_classes' => false ) );
5477 6011 }
5478 6012
5479 6013 if ( ! empty( $response['data']['tpag_licence'] ) && is_serialized( $response['data']['tpag_licence'] ) ) {
5480 - $response['data']['tpag_licence'] = unserialize( $response['data']['tpag_licence'] );
6014 + $response['data']['tpag_licence'] = unserialize( $response['data']['tpag_licence'], array( 'allowed_classes' => false ) );
5481 6015 }
5482 6016
5483 6017 if ( ! empty( $response['data']['uichemy_licence'] ) && is_serialized( $response['data']['uichemy_licence'] ) ) {
5484 - $response['data']['uichemy_licence'] = unserialize( $response['data']['uichemy_licence'] );
6018 + $response['data']['uichemy_licence'] = unserialize( $response['data']['uichemy_licence'], array( 'allowed_classes' => false ) );
5485 6019 }
5486 6020
5487 6021 if ( ! empty( $response['data']['wdkit_licence'] ) && is_serialized( $response['data']['wdkit_licence'] ) ) {
5488 - $response['data']['wdkit_licence'] = unserialize( $response['data']['wdkit_licence'] );
6022 + $response['data']['wdkit_licence'] = unserialize( $response['data']['wdkit_licence'], array( 'allowed_classes' => false ) );
5489 6023
5490 6024 // Store WDesignKit license status locally for quick access
5491 6025 if ( ! empty( $response['data']['wdkit_licence'] ) && is_array( $response['data']['wdkit_licence'] ) ) {
5492 6026 update_option( 'wdkit_licence_data', $response['data']['wdkit_licence'] );
@@ -5493,9 +6027,9 @@
5493 6027 }
5494 6028 }
5495 6029
5496 6030 if ( ! empty( $response['data']['wdkit_licence_extra'] ) && is_serialized( $response['data']['wdkit_licence_extra'] ) ) {
5497 - $response['data']['wdkit_licence_extra'] = unserialize( $response['data']['wdkit_licence_extra'] );
6031 + $response['data']['wdkit_licence_extra'] = unserialize( $response['data']['wdkit_licence_extra'], array( 'allowed_classes' => false ) );
5498 6032 }
5499 6033 }
5500 6034
5501 6035 wp_send_json( $response );
@@ -5680,9 +6214,9 @@
5680 6214 $token = $this->wdkit_login_user_token( $email );
5681 6215 $args = array( 'token' => $token );
5682 6216
5683 6217 if ( 'session' !== $logout_type ) {
5684 - delete_transient( 'wdkit_auth_' . $email );
6218 + delete_transient( 'wdkit_auth_' . wdesignkit_cloud_session_key( $email ) );
5685 6219 // Clear stored license data on logout so banner shows again
5686 6220 delete_option( 'wdkit_licence_data' );
5687 6221 $response = WDesignKit_Data_Query::get_data( 'logout', $args );
5688 6222 }
@@ -5702,9 +6236,9 @@
5702 6236 */
5703 6237 protected function wdkit_login_user_token( $email = '' ) {
5704 6238
5705 6239 if ( ! empty( $email ) ) {
5706 - $user_key = strstr( $email, '@', true );
6240 + $user_key = wdesignkit_cloud_session_key( $email );
5707 6241 $get_login = get_transient( 'wdkit_auth_' . $user_key );
5708 6242
5709 6243 if ( ! empty( $get_login ) && ! empty( $get_login['token'] ) ) {
5710 6244 return $get_login['token'];
@@ -5722,9 +6256,9 @@
5722 6256 * @param string $data send all post data.
5723 6257 * @param string $type store text data.
5724 6258 * @param string $condition store text data.
5725 6259 */
5726 - protected function wdkit_sanitizer_bypass( $data, $type, $condition = 'none' ) {
6260 + protected function wdkit_extract_post_field( $data, $type, $condition = 'none' ) {
5727 6261
5728 6262 if ( 'none' === $condition ) {
5729 6263 return $data[ $type ];
5730 6264 } elseif ( 'cr_widget' === $condition ) {
@@ -5729,8 +6263,10 @@
5729 6263 return $data[ $type ];
5730 6264 } elseif ( 'cr_widget' === $condition ) {
5731 6265 return $data[ $type ];
5732 6266 }
6267 +
6268 + return null;
5733 6269 }
5734 6270
5735 6271
5736 6272 /**