Changeset 38810 for trunk/src/wp-includes/class-wp-customize-manager.php
- Timestamp:
- 10/18/2016 08:04:36 PM (9 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/class-wp-customize-manager.php
r38765 r38810 131 131 132 132 /** 133 * Return value of check_ajax_referer() in customize_preview_init() method.134 *135 * @since 3.5.0136 * @access protected137 * @var false|int138 */139 protected $nonce_tick;140 141 /**142 133 * Panel types that may be rendered from JS templates. 143 134 * … … 194 185 195 186 /** 187 * Messenger channel. 188 * 189 * @since 4.7.0 190 * @access protected 191 * @var string 192 */ 193 protected $messenger_channel; 194 195 /** 196 196 * Unsanitized values for Customize Settings parsed from $_POST['customized']. 197 197 * … … 201 201 202 202 /** 203 * Changeset UUID. 204 * 205 * @since 4.7.0 206 * @access private 207 * @var string 208 */ 209 private $_changeset_uuid; 210 211 /** 212 * Changeset post ID. 213 * 214 * @since 4.7.0 215 * @access private 216 * @var int|false 217 */ 218 private $_changeset_post_id; 219 220 /** 221 * Changeset data loaded from a customize_changeset post. 222 * 223 * @since 4.7.0 224 * @access private 225 * @var array 226 */ 227 private $_changeset_data; 228 229 /** 203 230 * Constructor. 204 231 * 205 232 * @since 3.4.0 206 */ 207 public function __construct() { 233 * @since 4.7.0 Added $args param. 234 * 235 * @param array $args { 236 * Args. 237 * 238 * @type string $changeset_uuid Changeset UUID, the post_name for the customize_changeset post containing the customized state. Defaults to new UUID. 239 * @type string $theme Theme to be previewed (for theme switch). Defaults to customize_theme or theme query params. 240 * @type string $messenger_channel Messenger channel. Defaults to customize_messenger_channel query param. 241 * } 242 */ 243 public function __construct( $args = array() ) { 244 245 $args = array_merge( 246 array_fill_keys( array( 'changeset_uuid', 'theme', 'messenger_channel' ), null ), 247 $args 248 ); 249 250 // Note that the UUID format will be validated in the setup_theme() method. 251 if ( ! isset( $args['changeset_uuid'] ) ) { 252 $args['changeset_uuid'] = wp_generate_uuid4(); 253 } 254 255 // The theme and messenger_channel should be supplied via $args, but they are also looked at in the $_REQUEST global here for back-compat. 256 if ( ! isset( $args['theme'] ) ) { 257 if ( isset( $_REQUEST['customize_theme'] ) ) { 258 $args['theme'] = wp_unslash( $_REQUEST['customize_theme'] ); 259 } elseif ( isset( $_REQUEST['theme'] ) ) { // Deprecated. 260 $args['theme'] = wp_unslash( $_REQUEST['theme'] ); 261 } 262 } 263 if ( ! isset( $args['messenger_channel'] ) && isset( $_REQUEST['customize_messenger_channel'] ) ) { 264 $args['messenger_channel'] = sanitize_key( wp_unslash( $_REQUEST['customize_messenger_channel'] ) ); 265 } 266 267 $this->original_stylesheet = get_stylesheet(); 268 $this->theme = wp_get_theme( $args['theme'] ); 269 $this->messenger_channel = $args['messenger_channel']; 270 $this->_changeset_uuid = $args['changeset_uuid']; 271 208 272 require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' ); 209 273 require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' ); … … 272 336 } 273 337 274 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );275 276 338 add_action( 'setup_theme', array( $this, 'setup_theme' ) ); 277 339 add_action( 'wp_loaded', array( $this, 'wp_loaded' ) ); 278 279 // Run wp_redirect_status late to make sure we override the status last.280 add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );281 340 282 341 // Do not spawn cron (especially the alternate cron) while running the Customizer. … … 341 400 */ 342 401 protected function wp_die( $ajax_message, $message = null ) { 343 if ( $this->doing_ajax() || isset( $_POST['customized'] )) {402 if ( $this->doing_ajax() ) { 344 403 wp_die( $ajax_message ); 345 404 } … … 349 408 } 350 409 410 if ( $this->messenger_channel ) { 411 ob_start(); 412 wp_enqueue_scripts(); 413 wp_print_scripts( array( 'customize-base' ) ); 414 415 $settings = array( 416 'messengerArgs' => array( 417 'channel' => $this->messenger_channel, 418 'url' => wp_customize_url(), 419 ), 420 'error' => $ajax_message, 421 ); 422 ?> 423 <script> 424 ( function( api, settings ) { 425 var preview = new api.Messenger( settings.messengerArgs ); 426 preview.send( 'iframe-loading-error', settings.error ); 427 } )( wp.customize, <?php echo wp_json_encode( $settings ) ?> ); 428 </script> 429 <?php 430 $message .= ob_get_clean(); 431 } 432 351 433 wp_die( $message ); 352 434 } … … 356 438 * 357 439 * @since 3.4.0 358 * 359 * @return string 440 * @deprecated 4.7.0 441 * 442 * @return callable Die handler. 360 443 */ 361 444 public function wp_die_handler() { 445 _deprecated_function( __METHOD__, '4.7.0' ); 446 362 447 if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) { 363 448 return '_ajax_wp_die_handler'; … … 375 460 */ 376 461 public function setup_theme() { 377 send_origin_headers(); 378 379 $doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) ); 380 if ( is_admin() && ! $doing_ajax_or_is_customized ) { 381 auth_redirect(); 382 } elseif ( $doing_ajax_or_is_customized && ! is_user_logged_in() ) { 383 $this->wp_die( 0, __( 'You must be logged in to complete this action.' ) ); 384 } 385 386 show_admin_bar( false ); 387 388 if ( ! current_user_can( 'customize' ) ) { 389 $this->wp_die( -1, __( 'Sorry, you are not allowed to customize this site.' ) ); 390 } 391 392 $this->original_stylesheet = get_stylesheet(); 393 394 $this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null ); 462 global $pagenow; 463 464 // Check permissions for customize.php access since this method is called before customize.php can run any code, 465 if ( 'customize.php' === $pagenow && ! current_user_can( 'customize' ) ) { 466 if ( ! is_user_logged_in() ) { 467 auth_redirect(); 468 } else { 469 wp_die( 470 '<h1>' . __( 'Cheatin’ uh?' ) . '</h1>' . 471 '<p>' . __( 'Sorry, you are not allowed to customize this site.' ) . '</p>', 472 403 473 ); 474 } 475 return; 476 } 477 478 if ( ! preg_match( '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/', $this->_changeset_uuid ) ) { 479 $this->wp_die( -1, __( 'Invalid changeset UUID' ) ); 480 } 481 482 /* 483 * If unauthenticated then require a valid changeset UUID to load the preview. 484 * In this way, the UUID serves as a secret key. If the messenger channel is present, 485 * then send unauthenticated code to prompt re-auth. 486 */ 487 if ( ! current_user_can( 'customize' ) && ! $this->changeset_post_id() ) { 488 $this->wp_die( $this->messenger_channel ? 0 : -1, __( 'Non-existent changeset UUID.' ) ); 489 } 490 491 if ( ! headers_sent() ) { 492 send_origin_headers(); 493 } 494 495 // Hide the admin bar if we're embedded in the customizer iframe. 496 if ( $this->messenger_channel ) { 497 show_admin_bar( false ); 498 } 395 499 396 500 if ( $this->is_theme_active() ) { … … 508 612 509 613 /** 614 * Get the changeset UUID. 615 * 616 * @since 4.7.0 617 * @access public 618 * 619 * @return string UUID. 620 */ 621 public function changeset_uuid() { 622 return $this->_changeset_uuid; 623 } 624 625 /** 510 626 * Get the theme being customized. 511 627 * … … 604 720 do_action( 'customize_register', $this ); 605 721 606 if ( $this->is_preview() && ! is_admin() ) 722 /* 723 * Note that settings must be previewed here even outside the customizer preview 724 * and also in the customizer pane itself. This is to enable loading an existing 725 * changeset into the customizer. Previewing the settings only has to be prevented 726 * in the case of a customize_save action because then update_option() 727 * may short-circuit because it will detect that there are no changes to 728 * make. 729 */ 730 if ( ! $this->doing_ajax( 'customize_save' ) ) { 731 foreach ( $this->settings as $setting ) { 732 $setting->preview(); 733 } 734 } 735 736 if ( $this->is_preview() && ! is_admin() ) { 607 737 $this->customize_preview_init(); 738 } 608 739 } 609 740 … … 615 746 * 616 747 * @since 3.4.0 617 * 618 * @param $status 748 * @deprecated 4.7.0 749 * 750 * @param int $status Status. 619 751 * @return int 620 752 */ 621 753 public function wp_redirect_status( $status ) { 622 if ( $this->is_preview() && ! is_admin() ) 754 _deprecated_function( __FUNCTION__, '4.7.0' ); 755 756 if ( $this->is_preview() && ! is_admin() ) { 623 757 return 200; 758 } 624 759 625 760 return $status; … … 627 762 628 763 /** 629 * Parse the incoming $_POST['customized'] JSON data and store the unsanitized 630 * settings for subsequent post_value() lookups. 764 * Find the changeset post ID for a given changeset UUID. 765 * 766 * @since 4.7.0 767 * @access public 768 * 769 * @param string $uuid Changeset UUID. 770 * @return int|null Returns post ID on success and null on failure. 771 */ 772 public function find_changeset_post_id( $uuid ) { 773 $cache_group = 'customize_changeset_post'; 774 $changeset_post_id = wp_cache_get( $uuid, $cache_group ); 775 if ( $changeset_post_id && 'customize_changeset' === get_post_type( $changeset_post_id ) ) { 776 return $changeset_post_id; 777 } 778 779 $changeset_post_query = new WP_Query( array( 780 'post_type' => 'customize_changeset', 781 'post_status' => get_post_stati(), 782 'name' => $uuid, 783 'number' => 1, 784 'no_found_rows' => true, 785 'cache_results' => true, 786 'update_post_meta_cache' => false, 787 'update_term_meta_cache' => false, 788 ) ); 789 if ( ! empty( $changeset_post_query->posts ) ) { 790 // Note: 'fields'=>'ids' is not being used in order to cache the post object as it will be needed. 791 $changeset_post_id = $changeset_post_query->posts[0]->ID; 792 wp_cache_set( $this->_changeset_uuid, $changeset_post_id, $cache_group ); 793 return $changeset_post_id; 794 } 795 796 return null; 797 } 798 799 /** 800 * Get the changeset post id for the loaded changeset. 801 * 802 * @since 4.7.0 803 * @access public 804 * 805 * @return int|null Post ID on success or null if there is no post yet saved. 806 */ 807 public function changeset_post_id() { 808 if ( ! isset( $this->_changeset_post_id ) ) { 809 $post_id = $this->find_changeset_post_id( $this->_changeset_uuid ); 810 if ( ! $post_id ) { 811 $post_id = false; 812 } 813 $this->_changeset_post_id = $post_id; 814 } 815 if ( false === $this->_changeset_post_id ) { 816 return null; 817 } 818 return $this->_changeset_post_id; 819 } 820 821 /** 822 * Get the data stored in a changeset post. 823 * 824 * @since 4.7.0 825 * @access protected 826 * 827 * @param int $post_id Changeset post ID. 828 * @return array|WP_Error Changeset data or WP_Error on error. 829 */ 830 protected function get_changeset_post_data( $post_id ) { 831 if ( ! $post_id ) { 832 return new WP_Error( 'empty_post_id' ); 833 } 834 $changeset_post = get_post( $post_id ); 835 if ( ! $changeset_post ) { 836 return new WP_Error( 'missing_post' ); 837 } 838 if ( 'customize_changeset' !== $changeset_post->post_type ) { 839 return new WP_Error( 'wrong_post_type' ); 840 } 841 $changeset_data = json_decode( $changeset_post->post_content, true ); 842 if ( function_exists( 'json_last_error' ) && json_last_error() ) { 843 return new WP_Error( 'json_parse_error', '', json_last_error() ); 844 } 845 if ( ! is_array( $changeset_data ) ) { 846 return new WP_Error( 'expected_array' ); 847 } 848 return $changeset_data; 849 } 850 851 /** 852 * Get changeset data. 853 * 854 * @since 4.7.0 855 * @access public 856 * 857 * @return array Changeset data. 858 */ 859 public function changeset_data() { 860 if ( isset( $this->_changeset_data ) ) { 861 return $this->_changeset_data; 862 } 863 $changeset_post_id = $this->changeset_post_id(); 864 if ( ! $changeset_post_id ) { 865 $this->_changeset_data = array(); 866 } else { 867 $data = $this->get_changeset_post_data( $changeset_post_id ); 868 if ( ! is_wp_error( $data ) ) { 869 $this->_changeset_data = $data; 870 } else { 871 $this->_changeset_data = array(); 872 } 873 } 874 return $this->_changeset_data; 875 } 876 877 /** 878 * Get dirty pre-sanitized setting values in the current customized state. 879 * 880 * The returned array consists of a merge of three sources: 881 * 1. If the theme is not currently active, then the base array is any stashed 882 * theme mods that were modified previously but never published. 883 * 2. The values from the current changeset, if it exists. 884 * 3. If the user can customize, the values parsed from the incoming 885 * `$_POST['customized']` JSON data. 886 * 4. Any programmatically-set post values via `WP_Customize_Manager::set_post_value()`. 887 * 888 * The name "unsanitized_post_values" is a carry-over from when the customized 889 * state was exclusively sourced from `$_POST['customized']`. Nevertheless, 890 * the value returned will come from the current changeset post and from the 891 * incoming post data. 631 892 * 632 893 * @since 4.1.1 633 * 894 * @since 4.7.0 Added $args param and merging with changeset values and stashed theme mods. 895 * 896 * @param array $args { 897 * Args. 898 * 899 * @type bool $exclude_changeset Whether the changeset values should also be excluded. Defaults to false. 900 * @type bool $exclude_post_data Whether the post input values should also be excluded. Defaults to false when lacking the customize capability. 901 * } 634 902 * @return array 635 903 */ 636 public function unsanitized_post_values() { 637 if ( ! isset( $this->_post_values ) ) { 638 if ( isset( $_POST['customized'] ) ) { 639 $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true ); 640 } 641 if ( empty( $this->_post_values ) ) { // if not isset or if JSON error 642 $this->_post_values = array(); 643 } 644 } 645 if ( empty( $this->_post_values ) ) { 646 return array(); 647 } else { 648 return $this->_post_values; 649 } 650 } 651 652 /** 653 * Returns the sanitized value for a given setting from the request's POST data. 904 public function unsanitized_post_values( $args = array() ) { 905 $args = array_merge( 906 array( 907 'exclude_changeset' => false, 908 'exclude_post_data' => ! current_user_can( 'customize' ), 909 ), 910 $args 911 ); 912 913 $values = array(); 914 915 // Let default values be from the stashed theme mods if doing a theme switch and if no changeset is present. 916 if ( ! $this->is_theme_active() ) { 917 $stashed_theme_mods = get_option( 'customize_stashed_theme_mods' ); 918 $stylesheet = $this->get_stylesheet(); 919 if ( isset( $stashed_theme_mods[ $stylesheet ] ) ) { 920 $values = array_merge( $values, wp_list_pluck( $stashed_theme_mods[ $stylesheet ], 'value' ) ); 921 } 922 } 923 924 if ( ! $args['exclude_changeset'] ) { 925 foreach ( $this->changeset_data() as $setting_id => $setting_params ) { 926 if ( ! array_key_exists( 'value', $setting_params ) ) { 927 continue; 928 } 929 if ( isset( $setting_params['type'] ) && 'theme_mod' === $setting_params['type'] ) { 930 931 // Ensure that theme mods values are only used if they were saved under the current theme. 932 $namespace_pattern = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/'; 933 if ( preg_match( $namespace_pattern, $setting_id, $matches ) && $this->get_stylesheet() === $matches['stylesheet'] ) { 934 $values[ $matches['setting_id'] ] = $setting_params['value']; 935 } 936 } else { 937 $values[ $setting_id ] = $setting_params['value']; 938 } 939 } 940 } 941 942 if ( ! $args['exclude_post_data'] ) { 943 if ( ! isset( $this->_post_values ) ) { 944 if ( isset( $_POST['customized'] ) ) { 945 $post_values = json_decode( wp_unslash( $_POST['customized'] ), true ); 946 } else { 947 $post_values = array(); 948 } 949 if ( is_array( $post_values ) ) { 950 $this->_post_values = $post_values; 951 } else { 952 $this->_post_values = array(); 953 } 954 } 955 $values = array_merge( $values, $this->_post_values ); 956 } 957 return $values; 958 } 959 960 /** 961 * Returns the sanitized value for a given setting from the current customized state. 962 * 963 * The name "post_value" is a carry-over from when the customized state was exclusively 964 * sourced from `$_POST['customized']`. Nevertheless, the value returned will come 965 * from the current changeset post and from the incoming post data. 654 966 * 655 967 * @since 3.4.0 … … 685 997 686 998 /** 687 * Override a setting's (unsanitized) value as found in any incoming $_POST['customized']. 999 * Override a setting's value in the current customized state. 1000 * 1001 * The name "post_value" is a carry-over from when the customized state was 1002 * exclusively sourced from `$_POST['customized']`. 688 1003 * 689 1004 * @since 4.2.0 … … 694 1009 */ 695 1010 public function set_post_value( $setting_id, $value ) { 696 $this->unsanitized_post_values(); 1011 $this->unsanitized_post_values(); // Populate _post_values from $_POST['customized']. 697 1012 $this->_post_values[ $setting_id ] = $value; 698 1013 … … 734 1049 */ 735 1050 public function customize_preview_init() { 736 $this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' ); 1051 1052 /* 1053 * Now that Customizer previews are loaded into iframes via GET requests 1054 * and natural URLs with transaction UUIDs added, we need to ensure that 1055 * the responses are never cached by proxies. In practice, this will not 1056 * be needed if the user is logged-in anyway. But if anonymous access is 1057 * allowed then the auth cookies would not be sent and WordPress would 1058 * not send no-cache headers by default. 1059 */ 1060 if ( ! headers_sent() ) { 1061 nocache_headers(); 1062 header( 'X-Robots: noindex, nofollow, noarchive' ); 1063 } 1064 add_action( 'wp_head', 'wp_no_robots' ); 1065 add_filter( 'wp_headers', array( $this, 'filter_iframe_security_headers' ) ); 1066 1067 /* 1068 * If preview is being served inside the customizer preview iframe, and 1069 * if the user doesn't have customize capability, then it is assumed 1070 * that the user's session has expired and they need to re-authenticate. 1071 */ 1072 if ( $this->messenger_channel && ! current_user_can( 'customize' ) ) { 1073 $this->wp_die( -1, __( 'Unauthorized. You may remove the customize_messenger_channel param to preview as frontend.' ) ); 1074 return; 1075 } 737 1076 738 1077 $this->prepare_controls(); 739 1078 1079 add_filter( 'wp_redirect', array( $this, 'add_state_query_params' ) ); 1080 740 1081 wp_enqueue_script( 'customize-preview' ); 741 add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );742 add_action( 'wp_head', array( $this, 'customize_preview_base' ) );743 1082 add_action( 'wp_head', array( $this, 'customize_preview_loading_style' ) ); 744 1083 add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 ); 745 add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );746 add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );747 748 foreach ( $this->settings as $setting ) {749 $setting->preview();750 }751 1084 752 1085 /** … … 762 1095 763 1096 /** 1097 * Filter the X-Frame-Options and Content-Security-Policy headers to ensure frontend can load in customizer. 1098 * 1099 * @since 4.7.0 1100 * @access public 1101 * 1102 * @param array $headers Headers. 1103 * @return array Headers. 1104 */ 1105 public function filter_iframe_security_headers( $headers ) { 1106 $customize_url = admin_url( 'customize.php' ); 1107 $headers['X-Frame-Options'] = 'ALLOW-FROM ' . $customize_url; 1108 $headers['Content-Security-Policy'] = 'frame-ancestors ' . preg_replace( '#^(\w+://[^/]+).+?$#', '$1', $customize_url ); 1109 return $headers; 1110 } 1111 1112 /** 1113 * Add customize state query params to a given URL if preview is allowed. 1114 * 1115 * @since 4.7.0 1116 * @access public 1117 * @see wp_redirect() 1118 * @see WP_Customize_Manager::get_allowed_url() 1119 * 1120 * @param string $url URL. 1121 * @return string URL. 1122 */ 1123 public function add_state_query_params( $url ) { 1124 $parsed_original_url = wp_parse_url( $url ); 1125 $is_allowed = false; 1126 foreach ( $this->get_allowed_urls() as $allowed_url ) { 1127 $parsed_allowed_url = wp_parse_url( $allowed_url ); 1128 $is_allowed = ( 1129 $parsed_allowed_url['scheme'] === $parsed_original_url['scheme'] 1130 && 1131 $parsed_allowed_url['host'] === $parsed_original_url['host'] 1132 && 1133 0 === strpos( $parsed_original_url['path'], $parsed_allowed_url['path'] ) 1134 ); 1135 if ( $is_allowed ) { 1136 break; 1137 } 1138 } 1139 1140 if ( $is_allowed ) { 1141 $query_params = array( 1142 'customize_changeset_uuid' => $this->changeset_uuid(), 1143 ); 1144 if ( ! $this->is_theme_active() ) { 1145 $query_params['customize_theme'] = $this->get_stylesheet(); 1146 } 1147 if ( $this->messenger_channel ) { 1148 $query_params['customize_messenger_channel'] = $this->messenger_channel; 1149 } 1150 $url = add_query_arg( $query_params, $url ); 1151 } 1152 1153 return $url; 1154 } 1155 1156 /** 764 1157 * Prevent sending a 404 status when returning the response for the customize 765 1158 * preview, since it causes the jQuery Ajax to fail. Send 200 instead. 766 1159 * 767 1160 * @since 4.0.0 1161 * @deprecated 4.7.0 768 1162 * @access public 769 1163 */ 770 1164 public function customize_preview_override_404_status() { 771 if ( is_404() ) { 772 status_header( 200 ); 773 } 1165 _deprecated_function( __METHOD__, '4.7.0' ); 774 1166 } 775 1167 … … 778 1170 * 779 1171 * @since 3.4.0 1172 * @deprecated 4.7.0 780 1173 */ 781 1174 public function customize_preview_base() { 782 ?><base href="<?php echo home_url( '/' ); ?>" /><?php1175 _deprecated_function( __METHOD__, '4.7.0' ); 783 1176 } 784 1177 … … 810 1203 pointer-events: none !important; 811 1204 } 1205 form.customize-unpreviewable, 1206 form.customize-unpreviewable input, 1207 form.customize-unpreviewable select, 1208 form.customize-unpreviewable button, 1209 a.customize-unpreviewable, 1210 area.customize-unpreviewable { 1211 cursor: not-allowed !important; 1212 } 812 1213 </style><?php 813 1214 } … … 819 1220 */ 820 1221 public function customize_preview_settings() { 821 $setting_validities = $this->validate_setting_values( $this->unsanitized_post_values() ); 1222 $post_values = $this->unsanitized_post_values( array( 'exclude_changeset' => true ) ); 1223 $setting_validities = $this->validate_setting_values( $post_values ); 822 1224 $exported_setting_validities = array_map( array( $this, 'prepare_setting_validity_for_js' ), $setting_validities ); 823 1225 1226 // Note that the REQUEST_URI is not passed into home_url() since this breaks subdirectory installs. 1227 $self_url = empty( $_SERVER['REQUEST_URI'] ) ? home_url( '/' ) : esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ); 1228 $state_query_params = array( 1229 'customize_theme', 1230 'customize_changeset_uuid', 1231 'customize_messenger_channel', 1232 ); 1233 $self_url = remove_query_arg( $state_query_params, $self_url ); 1234 1235 $allowed_urls = $this->get_allowed_urls(); 1236 $allowed_hosts = array(); 1237 foreach ( $allowed_urls as $allowed_url ) { 1238 $parsed = wp_parse_url( $allowed_url ); 1239 if ( empty( $parsed['host'] ) ) { 1240 continue; 1241 } 1242 $host = $parsed['host']; 1243 if ( ! empty( $parsed['port'] ) ) { 1244 $host .= ':' . $parsed['port']; 1245 } 1246 $allowed_hosts[] = $host; 1247 } 824 1248 $settings = array( 1249 'changeset' => array( 1250 'uuid' => $this->_changeset_uuid, 1251 ), 1252 'timeouts' => array( 1253 'selectiveRefresh' => 250, 1254 'keepAliveSend' => 1000, 1255 ), 825 1256 'theme' => array( 826 1257 'stylesheet' => $this->get_stylesheet(), … … 828 1259 ), 829 1260 'url' => array( 830 'self' => empty( $_SERVER['REQUEST_URI'] ) ? home_url( '/' ) : esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ), 1261 'self' => $self_url, 1262 'allowed' => array_map( 'esc_url_raw', $this->get_allowed_urls() ), 1263 'allowedHosts' => array_unique( $allowed_hosts ), 1264 'isCrossDomain' => $this->is_cross_domain(), 831 1265 ), 832 'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),1266 'channel' => $this->messenger_channel, 833 1267 'activePanels' => array(), 834 1268 'activeSections' => array(), 835 1269 'activeControls' => array(), 836 1270 'settingValidities' => $exported_setting_validities, 837 'nonce' => $this->get_nonces(),1271 'nonce' => current_user_can( 'customize' ) ? $this->get_nonces() : array(), 838 1272 'l10n' => array( 839 1273 'shiftClickToEdit' => __( 'Shift-click to edit this element.' ), 1274 'linkUnpreviewable' => __( 'This link is not live-previewable.' ), 1275 'formUnpreviewable' => __( 'This form is not live-previewable.' ), 840 1276 ), 841 '_dirty' => array_keys( $ this->unsanitized_post_values()),1277 '_dirty' => array_keys( $post_values ), 842 1278 ); 843 1279 … … 893 1329 * 894 1330 * @since 3.4.0 1331 * @deprecated 4.7.0 895 1332 */ 896 1333 public function customize_preview_signature() { 897 echo 'WP_CUSTOMIZER_SIGNATURE';1334 _deprecated_function( __METHOD__, '4.7.0' ); 898 1335 } 899 1336 … … 902 1339 * 903 1340 * @since 3.4.0 1341 * @deprecated 4.7.0 904 1342 * 905 1343 * @param mixed $return Value passed through for {@see 'wp_die_handler'} filter. … … 907 1345 */ 908 1346 public function remove_preview_signature( $return = null ) { 909 remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000);1347 _deprecated_function( __METHOD__, '4.7.0' ); 910 1348 911 1349 return $return; … … 994 1432 * 995 1433 * @param array $setting_values Mapping of setting IDs to values to validate and sanitize. 1434 * @param array $options { 1435 * Options. 1436 * 1437 * @type bool $validate_existence Whether a setting's existence will be checked. 1438 * @type bool $validate_capability Whether the setting capability will be checked. 1439 * } 996 1440 * @return array Mapping of setting IDs to return value of validate method calls, either `true` or `WP_Error`. 997 1441 */ 998 public function validate_setting_values( $setting_values ) { 1442 public function validate_setting_values( $setting_values, $options = array() ) { 1443 $options = wp_parse_args( $options, array( 1444 'validate_capability' => false, 1445 'validate_existence' => false, 1446 ) ); 1447 999 1448 $validities = array(); 1000 1449 foreach ( $setting_values as $setting_id => $unsanitized_value ) { 1001 1450 $setting = $this->get_setting( $setting_id ); 1002 if ( ! $setting || is_null( $unsanitized_value ) ) { 1451 if ( ! $setting ) { 1452 if ( $options['validate_existence'] ) { 1453 $validities[ $setting_id ] = new WP_Error( 'unrecognized', __( 'Setting does not exist or is unrecognized.' ) ); 1454 } 1003 1455 continue; 1004 1456 } 1005 $validity = $setting->validate( $unsanitized_value ); 1457 if ( is_null( $unsanitized_value ) ) { 1458 continue; 1459 } 1460 if ( $options['validate_capability'] && ! current_user_can( $setting->capability ) ) { 1461 $validity = new WP_Error( 'unauthorized', __( 'Unauthorized to modify setting due to capability.' ) ); 1462 } else { 1463 $validity = $setting->validate( $unsanitized_value ); 1464 } 1006 1465 if ( ! is_wp_error( $validity ) ) { 1007 1466 /** This filter is documented in wp-includes/class-wp-customize-setting.php */ … … 1057 1516 1058 1517 /** 1059 * Switch the theme and trigger the save() method on each setting. 1060 * 1061 * @since 3.4.0 1518 * Handle customize_save WP Ajax request to save/update a changeset. 1519 * 1520 * @since 3.4.0 1521 * @since 4.7.0 The semantics of this method have changed to update a changeset, optionally to also change the status and other attributes. 1062 1522 */ 1063 1523 public function save() { 1524 if ( ! is_user_logged_in() ) { 1525 wp_send_json_error( 'unauthenticated' ); 1526 } 1527 1064 1528 if ( ! $this->is_preview() ) { 1065 1529 wp_send_json_error( 'not_preview' ); … … 1071 1535 } 1072 1536 1537 $changeset_post_id = $this->changeset_post_id(); 1538 if ( $changeset_post_id && in_array( get_post_status( $changeset_post_id ), array( 'publish', 'trash' ) ) ) { 1539 wp_send_json_error( 'changeset_already_published' ); 1540 } 1541 1542 if ( empty( $changeset_post_id ) ) { 1543 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->create_posts ) ) { 1544 wp_send_json_error( 'cannot_create_changeset_post' ); 1545 } 1546 } else { 1547 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) ) { 1548 wp_send_json_error( 'cannot_edit_changeset_post' ); 1549 } 1550 } 1551 1552 if ( ! empty( $_POST['customize_changeset_data'] ) ) { 1553 $input_changeset_data = json_decode( wp_unslash( $_POST['customize_changeset_data'] ), true ); 1554 if ( ! is_array( $input_changeset_data ) ) { 1555 wp_send_json_error( 'invalid_customize_changeset_data' ); 1556 } 1557 } else { 1558 $input_changeset_data = array(); 1559 } 1560 1561 // Validate title. 1562 $changeset_title = null; 1563 if ( isset( $_POST['customize_changeset_title'] ) ) { 1564 $changeset_title = sanitize_text_field( wp_unslash( $_POST['customize_changeset_title'] ) ); 1565 } 1566 1567 // Validate changeset status param. 1568 $is_publish = null; 1569 $changeset_status = null; 1570 if ( isset( $_POST['customize_changeset_status'] ) ) { 1571 $changeset_status = wp_unslash( $_POST['customize_changeset_status'] ); 1572 if ( ! get_post_status_object( $changeset_status ) || ! in_array( $changeset_status, array( 'draft', 'pending', 'publish', 'future' ), true ) ) { 1573 wp_send_json_error( 'bad_customize_changeset_status', 400 ); 1574 } 1575 $is_publish = ( 'publish' === $changeset_status || 'future' === $changeset_status ); 1576 if ( $is_publish ) { 1577 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) { 1578 wp_send_json_error( 'changeset_publish_unauthorized', 403 ); 1579 } 1580 if ( false === has_action( 'transition_post_status', '_wp_customize_publish_changeset' ) ) { 1581 wp_send_json_error( 'missing_publish_callback', 500 ); 1582 } 1583 } 1584 } 1585 1586 /* 1587 * Validate changeset date param. Date is assumed to be in local time for 1588 * the WP if in MySQL format (YYYY-MM-DD HH:MM:SS). Otherwise, the date 1589 * is parsed with strtotime() so that ISO date format may be supplied 1590 * or a string like "+10 minutes". 1591 */ 1592 $changeset_date_gmt = null; 1593 if ( isset( $_POST['customize_changeset_date'] ) ) { 1594 $changeset_date = wp_unslash( $_POST['customize_changeset_date'] ); 1595 if ( preg_match( '/^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/', $changeset_date ) ) { 1596 $mm = substr( $changeset_date, 5, 2 ); 1597 $jj = substr( $changeset_date, 8, 2 ); 1598 $aa = substr( $changeset_date, 0, 4 ); 1599 $valid_date = wp_checkdate( $mm, $jj, $aa, $changeset_date ); 1600 if ( ! $valid_date ) { 1601 wp_send_json_error( 'bad_customize_changeset_date', 400 ); 1602 } 1603 $changeset_date_gmt = get_gmt_from_date( $changeset_date ); 1604 } else { 1605 $timestamp = strtotime( $changeset_date ); 1606 if ( ! $timestamp ) { 1607 wp_send_json_error( 'bad_customize_changeset_date', 400 ); 1608 } 1609 $changeset_date_gmt = gmdate( 'Y-m-d H:i:s', $timestamp ); 1610 } 1611 $now = gmdate( 'Y-m-d H:i:59' ); 1612 1613 $is_future_dated = ( mysql2date( 'U', $changeset_date_gmt, false ) > mysql2date( 'U', $now, false ) ); 1614 if ( ! $is_future_dated ) { 1615 wp_send_json_error( 'not_future_date', 400 ); // Only future dates are allowed. 1616 } 1617 1618 if ( ! $this->is_theme_active() && ( 'future' === $changeset_status || $is_future_dated ) ) { 1619 wp_send_json_error( 'cannot_schedule_theme_switches', 400 ); // This should be allowed in the future, when theme is a regular setting. 1620 } 1621 $will_remain_auto_draft = ( ! $changeset_status && ( ! $changeset_post_id || 'auto-draft' === get_post_status( $changeset_post_id ) ) ); 1622 if ( $changeset_date && $will_remain_auto_draft ) { 1623 wp_send_json_error( 'cannot_supply_date_for_auto_draft_changeset', 400 ); 1624 } 1625 } 1626 1627 $r = $this->save_changeset_post( array( 1628 'status' => $changeset_status, 1629 'title' => $changeset_title, 1630 'date_gmt' => $changeset_date_gmt, 1631 'data' => $input_changeset_data, 1632 ) ); 1633 if ( is_wp_error( $r ) ) { 1634 $response = $r->get_error_data(); 1635 } else { 1636 $response = $r; 1637 1638 // Note that if the changeset status was publish, then it will get set to trash if revisions are not supported. 1639 $response['changeset_status'] = get_post_status( $this->changeset_post_id() ); 1640 if ( $is_publish && 'trash' === $response['changeset_status'] ) { 1641 $response['changeset_status'] = 'publish'; 1642 } 1643 1644 if ( 'publish' === $response['changeset_status'] ) { 1645 $response['next_changeset_uuid'] = wp_generate_uuid4(); 1646 } 1647 } 1648 1649 if ( isset( $response['setting_validities'] ) ) { 1650 $response['setting_validities'] = array_map( array( $this, 'prepare_setting_validity_for_js' ), $response['setting_validities'] ); 1651 } 1652 1653 /** 1654 * Filters response data for a successful customize_save Ajax request. 1655 * 1656 * This filter does not apply if there was a nonce or authentication failure. 1657 * 1658 * @since 4.2.0 1659 * 1660 * @param array $response Additional information passed back to the 'saved' 1661 * event on `wp.customize`. 1662 * @param WP_Customize_Manager $this WP_Customize_Manager instance. 1663 */ 1664 $response = apply_filters( 'customize_save_response', $response, $this ); 1665 1666 if ( is_wp_error( $r ) ) { 1667 wp_send_json_error( $response ); 1668 } else { 1669 wp_send_json_success( $response ); 1670 } 1671 } 1672 1673 /** 1674 * Save the post for the loaded changeset. 1675 * 1676 * @since 4.7.0 1677 * @access public 1678 * 1679 * @param array $args { 1680 * Args for changeset post. 1681 * 1682 * @type array $data Optional additional changeset data. Values will be merged on top of any existing post values. 1683 * @type string $status Post status. Optional. If supplied, the save will be transactional and a post revision will be allowed. 1684 * @type string $title Post title. Optional. 1685 * @type string $date_gmt Date in GMT. Optional. 1686 * } 1687 * 1688 * @return array|WP_Error Returns array on success and WP_Error with array data on error. 1689 */ 1690 function save_changeset_post( $args = array() ) { 1691 1692 $args = array_merge( 1693 array( 1694 'status' => null, 1695 'title' => null, 1696 'data' => array(), 1697 'date_gmt' => null, 1698 ), 1699 $args 1700 ); 1701 1702 $changeset_post_id = $this->changeset_post_id(); 1703 1704 // The request was made via wp.customize.previewer.save(). 1705 $update_transactionally = (bool) $args['status']; 1706 $allow_revision = (bool) $args['status']; 1707 1708 // Amend post values with any supplied data. 1709 foreach ( $args['data'] as $setting_id => $setting_params ) { 1710 if ( array_key_exists( 'value', $setting_params ) ) { 1711 $this->set_post_value( $setting_id, $setting_params['value'] ); // Add to post values so that they can be validated and sanitized. 1712 } 1713 } 1714 1715 // Note that in addition to post data, this will include any stashed theme mods. 1716 $post_values = $this->unsanitized_post_values( array( 1717 'exclude_changeset' => true, 1718 'exclude_post_data' => false, 1719 ) ); 1720 $this->add_dynamic_settings( array_keys( $post_values ) ); // Ensure settings get created even if they lack an input value. 1721 1073 1722 /** 1074 1723 * Fires before save validation happens. … … 1085 1734 1086 1735 // Validate settings. 1087 $setting_validities = $this->validate_setting_values( $this->unsanitized_post_values() ); 1736 $setting_validities = $this->validate_setting_values( $post_values, array( 1737 'validate_capability' => true, 1738 'validate_existence' => true, 1739 ) ); 1088 1740 $invalid_setting_count = count( array_filter( $setting_validities, 'is_wp_error' ) ); 1089 $exported_setting_validities = array_map( array( $this, 'prepare_setting_validity_for_js' ), $setting_validities ); 1090 if ( $invalid_setting_count > 0 ) { 1741 1742 /* 1743 * Short-circuit if there are invalid settings the update is transactional. 1744 * A changeset update is transactional when a status is supplied in the request. 1745 */ 1746 if ( $update_transactionally && $invalid_setting_count > 0 ) { 1091 1747 $response = array( 1092 'setting_validities' => $ exported_setting_validities,1748 'setting_validities' => $setting_validities, 1093 1749 'message' => sprintf( _n( 'There is %s invalid setting.', 'There are %s invalid settings.', $invalid_setting_count ), number_format_i18n( $invalid_setting_count ) ), 1094 1750 ); 1095 1096 /** This filter is documented in wp-includes/class-wp-customize-manager.php */ 1097 $response = apply_filters( 'customize_save_response', $response, $this ); 1098 wp_send_json_error( $response ); 1099 } 1100 1101 // Do we have to switch themes? 1102 if ( ! $this->is_theme_active() ) { 1103 // Temporarily stop previewing the theme to allow switch_themes() 1104 // to operate properly. 1751 return new WP_Error( 'transaction_fail', '', $response ); 1752 } 1753 1754 $response = array( 1755 'setting_validities' => $setting_validities, 1756 ); 1757 1758 // Obtain/merge data for changeset. 1759 $original_changeset_data = $this->get_changeset_post_data( $changeset_post_id ); 1760 $data = $original_changeset_data; 1761 if ( is_wp_error( $data ) ) { 1762 $data = array(); 1763 } 1764 1765 // Ensure that all post values are included in the changeset data. 1766 foreach ( $post_values as $setting_id => $post_value ) { 1767 if ( ! isset( $args['data'][ $setting_id ] ) ) { 1768 $args['data'][ $setting_id ] = array(); 1769 } 1770 if ( ! isset( $args['data'][ $setting_id ]['value'] ) ) { 1771 $args['data'][ $setting_id ]['value'] = $post_value; 1772 } 1773 } 1774 1775 foreach ( $args['data'] as $setting_id => $setting_params ) { 1776 $setting = $this->get_setting( $setting_id ); 1777 if ( ! $setting || ! $setting->check_capabilities() ) { 1778 continue; 1779 } 1780 1781 // Skip updating changeset for invalid setting values. 1782 if ( isset( $setting_validities[ $setting_id ] ) && is_wp_error( $setting_validities[ $setting_id ] ) ) { 1783 continue; 1784 } 1785 1786 $changeset_setting_id = $setting_id; 1787 if ( 'theme_mod' === $setting->type ) { 1788 $changeset_setting_id = sprintf( '%s::%s', $this->get_stylesheet(), $setting_id ); 1789 } 1790 1791 if ( null === $setting_params ) { 1792 // Remove setting from changeset entirely. 1793 unset( $data[ $changeset_setting_id ] ); 1794 } else { 1795 // Merge any additional setting params that have been supplied with the existing params. 1796 if ( ! isset( $data[ $changeset_setting_id ] ) ) { 1797 $data[ $changeset_setting_id ] = array(); 1798 } 1799 $data[ $changeset_setting_id ] = array_merge( 1800 $data[ $changeset_setting_id ], 1801 $setting_params, 1802 array( 'type' => $setting->type ) 1803 ); 1804 } 1805 } 1806 1807 $filter_context = array( 1808 'uuid' => $this->changeset_uuid(), 1809 'title' => $args['title'], 1810 'status' => $args['status'], 1811 'date_gmt' => $args['date_gmt'], 1812 'post_id' => $changeset_post_id, 1813 'previous_data' => is_wp_error( $original_changeset_data ) ? array() : $original_changeset_data, 1814 'manager' => $this, 1815 ); 1816 1817 /** 1818 * Filters the settings' data that will be persisted into the changeset. 1819 * 1820 * Plugins may amend additional data (such as additional meta for settings) into the changeset with this filter. 1821 * 1822 * @since 4.7.0 1823 * 1824 * @param array $data Updated changeset data, mapping setting IDs to arrays containing a $value item and optionally other metadata. 1825 * @param array $context { 1826 * Filter context. 1827 * 1828 * @type string $uuid Changeset UUID. 1829 * @type string $title Requested title for the changeset post. 1830 * @type string $status Requested status for the changeset post. 1831 * @type string $date_gmt Requested date for the changeset post in MySQL format and GMT timezone. 1832 * @type int|false $post_id Post ID for the changeset, or false if it doesn't exist yet. 1833 * @type array $previous_data Previous data contained in the changeset. 1834 * @type WP_Customize_Manager $manager Manager instance. 1835 * } 1836 */ 1837 $data = apply_filters( 'customize_changeset_save_data', $data, $filter_context ); 1838 1839 // Switch theme if publishing changes now. 1840 if ( 'publish' === $args['status'] && ! $this->is_theme_active() ) { 1841 // Temporarily stop previewing the theme to allow switch_themes() to operate properly. 1105 1842 $this->stop_previewing_theme(); 1106 1843 switch_theme( $this->get_stylesheet() ); … … 1109 1846 } 1110 1847 1848 // Gather the data for wp_insert_post()/wp_update_post(). 1849 $json_options = 0; 1850 if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) { 1851 $json_options |= JSON_UNESCAPED_SLASHES; // Introduced in PHP 5.4. This is only to improve readability as slashes needn't be escaped in storage. 1852 } 1853 $json_options |= JSON_PRETTY_PRINT; // Also introduced in PHP 5.4, but WP defines constant for back compat. See WP Trac #30139. 1854 $post_array = array( 1855 'post_content' => wp_json_encode( $data, $json_options ), 1856 ); 1857 if ( $args['title'] ) { 1858 $post_array['post_title'] = $args['title']; 1859 } 1860 if ( $changeset_post_id ) { 1861 $post_array['ID'] = $changeset_post_id; 1862 } else { 1863 $post_array['post_type'] = 'customize_changeset'; 1864 $post_array['post_name'] = $this->changeset_uuid(); 1865 $post_array['post_status'] = 'auto-draft'; 1866 } 1867 if ( $args['status'] ) { 1868 $post_array['post_status'] = $args['status']; 1869 } 1870 if ( $args['date_gmt'] ) { 1871 $post_array['post_date_gmt'] = $args['date_gmt']; 1872 $post_array['post_date'] = get_date_from_gmt( $args['date_gmt'] ); 1873 } 1874 1875 $this->store_changeset_revision = $allow_revision; 1876 add_filter( 'wp_save_post_revision_post_has_changed', array( $this, '_filter_revision_post_has_changed' ), 5, 3 ); 1877 1878 // Update the changeset post. The publish_customize_changeset action will cause the settings in the changeset to be saved via WP_Customize_Setting::save(). 1879 $has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) ); 1880 if ( $has_kses ) { 1881 kses_remove_filters(); // Prevent KSES from corrupting JSON in post_content. 1882 } 1883 1884 // Note that updating a post with publish status will trigger WP_Customize_Manager::publish_changeset_values(). 1885 if ( $changeset_post_id ) { 1886 $post_array['edit_date'] = true; // Prevent date clearing. 1887 $r = wp_update_post( wp_slash( $post_array ), true ); 1888 } else { 1889 $r = wp_insert_post( wp_slash( $post_array ), true ); 1890 if ( ! is_wp_error( $r ) ) { 1891 $this->_changeset_post_id = $r; // Update cached post ID for the loaded changeset. 1892 } 1893 } 1894 if ( $has_kses ) { 1895 kses_init_filters(); 1896 } 1897 $this->_changeset_data = null; // Reset so WP_Customize_Manager::changeset_data() will re-populate with updated contents. 1898 1899 remove_filter( 'wp_save_post_revision_post_has_changed', array( $this, '_filter_revision_post_has_changed' ) ); 1900 1901 if ( is_wp_error( $r ) ) { 1902 $response['changeset_post_save_failure'] = $r->get_error_code(); 1903 return new WP_Error( 'changeset_post_save_failure', '', $response ); 1904 } 1905 1906 return $response; 1907 } 1908 1909 /** 1910 * Whether a changeset revision should be made. 1911 * 1912 * @since 4.7.0 1913 * @access private 1914 * @var bool 1915 */ 1916 protected $store_changeset_revision; 1917 1918 /** 1919 * Filters whether a changeset has changed to create a new revision. 1920 * 1921 * Note that this will not be called while a changeset post remains in auto-draft status. 1922 * 1923 * @since 4.7.0 1924 * @access private 1925 * 1926 * @param bool $post_has_changed Whether the post has changed. 1927 * @param WP_Post $last_revision The last revision post object. 1928 * @param WP_Post $post The post object. 1929 * 1930 * @return bool Whether a revision should be made. 1931 */ 1932 public function _filter_revision_post_has_changed( $post_has_changed, $last_revision, $post ) { 1933 unset( $last_revision ); 1934 if ( 'customize_changeset' === $post->post_type ) { 1935 $post_has_changed = $this->store_changeset_revision; 1936 } 1937 return $post_has_changed; 1938 } 1939 1940 /** 1941 * Publish changeset values. 1942 * 1943 * This will the values contained in a changeset, even changesets that do not 1944 * correspond to current manager instance. This is called by 1945 * `_wp_customize_publish_changeset()` when a customize_changeset post is 1946 * transitioned to the `publish` status. As such, this method should not be 1947 * called directly and instead `wp_publish_post()` should be used. 1948 * 1949 * Please note that if the settings in the changeset are for a non-activated 1950 * theme, the theme must first be switched to (via `switch_theme()`) before 1951 * invoking this method. 1952 * 1953 * @since 4.7.0 1954 * @access private 1955 * @see _wp_customize_publish_changeset() 1956 * 1957 * @param int $changeset_post_id ID for customize_changeset post. Defaults to the changeset for the current manager instance. 1958 * @return true|WP_Error True or error info. 1959 */ 1960 public function _publish_changeset_values( $changeset_post_id ) { 1961 $publishing_changeset_data = $this->get_changeset_post_data( $changeset_post_id ); 1962 if ( is_wp_error( $publishing_changeset_data ) ) { 1963 return $publishing_changeset_data; 1964 } 1965 1966 $changeset_post = get_post( $changeset_post_id ); 1967 1968 /* 1969 * Temporarily override the changeset context so that it will be read 1970 * in calls to unsanitized_post_values() and so that it will be available 1971 * on the $wp_customize object passed to hooks during the save logic. 1972 */ 1973 $previous_changeset_post_id = $this->_changeset_post_id; 1974 $this->_changeset_post_id = $changeset_post_id; 1975 $previous_changeset_uuid = $this->_changeset_uuid; 1976 $this->_changeset_uuid = $changeset_post->post_name; 1977 $previous_changeset_data = $this->_changeset_data; 1978 $this->_changeset_data = $publishing_changeset_data; 1979 1980 // Ensure that other theme mods are stashed. 1981 $other_theme_mod_settings = array(); 1982 if ( did_action( 'switch_theme' ) ) { 1983 $namespace_pattern = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/'; 1984 $matches = array(); 1985 foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) { 1986 $is_other_theme_mod = ( 1987 isset( $setting_params['value'] ) 1988 && 1989 isset( $setting_params['type'] ) 1990 && 1991 'theme_mod' === $setting_params['type'] 1992 && 1993 preg_match( $namespace_pattern, $raw_setting_id, $matches ) 1994 && 1995 $this->get_stylesheet() !== $matches['stylesheet'] 1996 ); 1997 if ( $is_other_theme_mod ) { 1998 if ( ! isset( $other_theme_mod_settings[ $matches['stylesheet'] ] ) ) { 1999 $other_theme_mod_settings[ $matches['stylesheet'] ] = array(); 2000 } 2001 $other_theme_mod_settings[ $matches['stylesheet'] ][ $matches['setting_id'] ] = $setting_params; 2002 } 2003 } 2004 } 2005 2006 $changeset_setting_values = $this->unsanitized_post_values( array( 2007 'exclude_post_data' => true, 2008 'exclude_changeset' => false, 2009 ) ); 2010 $changeset_setting_ids = array_keys( $changeset_setting_values ); 2011 $this->add_dynamic_settings( $changeset_setting_ids ); 2012 1111 2013 /** 1112 2014 * Fires once the theme has switched in the Customizer, but before settings … … 1115 2017 * @since 3.4.0 1116 2018 * 1117 * @param WP_Customize_Manager $ thisWP_Customize_Manager instance.2019 * @param WP_Customize_Manager $manager WP_Customize_Manager instance. 1118 2020 */ 1119 2021 do_action( 'customize_save', $this ); 1120 2022 1121 foreach ( $this->settings as $setting ) { 1122 $setting->save(); 2023 /* 2024 * Ensure that all settings will allow themselves to be saved. Note that 2025 * this is safe because the setting would have checked the capability 2026 * when the setting value was written into the changeset. So this is why 2027 * an additional capability check is not required here. 2028 */ 2029 $original_setting_capabilities = array(); 2030 foreach ( $changeset_setting_ids as $setting_id ) { 2031 $setting = $this->get_setting( $setting_id ); 2032 if ( $setting ) { 2033 $original_setting_capabilities[ $setting->id ] = $setting->capability; 2034 $setting->capability = 'exist'; 2035 } 2036 } 2037 2038 foreach ( $changeset_setting_ids as $setting_id ) { 2039 $setting = $this->get_setting( $setting_id ); 2040 if ( $setting ) { 2041 $setting->save(); 2042 } 2043 } 2044 2045 // Update the stashed theme mod settings, removing the active theme's stashed settings, if activated. 2046 if ( did_action( 'switch_theme' ) ) { 2047 $this->update_stashed_theme_mod_settings( $other_theme_mod_settings ); 1123 2048 } 1124 2049 … … 1128 2053 * @since 3.6.0 1129 2054 * 1130 * @param WP_Customize_Manager $ thisWP_Customize_Manager instance.2055 * @param WP_Customize_Manager $manager WP_Customize_Manager instance. 1131 2056 */ 1132 2057 do_action( 'customize_save_after', $this ); 1133 2058 1134 $data = array( 1135 'setting_validities' => $exported_setting_validities, 1136 ); 1137 1138 /** 1139 * Filters response data for a successful customize_save Ajax request. 1140 * 1141 * This filter does not apply if there was a nonce or authentication failure. 1142 * 1143 * @since 4.2.0 1144 * 1145 * @param array $data Additional information passed back to the 'saved' 1146 * event on `wp.customize`. 1147 * @param WP_Customize_Manager $this WP_Customize_Manager instance. 1148 */ 1149 $response = apply_filters( 'customize_save_response', $data, $this ); 1150 wp_send_json_success( $response ); 2059 // Restore original capabilities. 2060 foreach ( $original_setting_capabilities as $setting_id => $capability ) { 2061 $setting = $this->get_setting( $setting_id ); 2062 if ( $setting ) { 2063 $setting->capability = $capability; 2064 } 2065 } 2066 2067 // Restore original changeset data. 2068 $this->_changeset_data = $previous_changeset_data; 2069 $this->_changeset_post_id = $previous_changeset_post_id; 2070 $this->_changeset_uuid = $previous_changeset_uuid; 2071 2072 return true; 2073 } 2074 2075 /** 2076 * Update stashed theme mod settings. 2077 * 2078 * @since 4.7.0 2079 * @access private 2080 * 2081 * @param array $inactive_theme_mod_settings Mapping of stylesheet to arrays of theme mod settings. 2082 * @return array|false Returns array of updated stashed theme mods or false if the update failed or there were no changes. 2083 */ 2084 protected function update_stashed_theme_mod_settings( $inactive_theme_mod_settings ) { 2085 $stashed_theme_mod_settings = get_option( 'customize_stashed_theme_mods' ); 2086 if ( empty( $stashed_theme_mod_settings ) ) { 2087 $stashed_theme_mod_settings = array(); 2088 } 2089 2090 // Delete any stashed theme mods for the active theme since since they would have been loaded and saved upon activation. 2091 unset( $stashed_theme_mod_settings[ $this->get_stylesheet() ] ); 2092 2093 // Merge inactive theme mods with the stashed theme mod settings. 2094 foreach ( $inactive_theme_mod_settings as $stylesheet => $theme_mod_settings ) { 2095 if ( ! isset( $stashed_theme_mod_settings[ $stylesheet ] ) ) { 2096 $stashed_theme_mod_settings[ $stylesheet ] = array(); 2097 } 2098 2099 $stashed_theme_mod_settings[ $stylesheet ] = array_merge( 2100 $stashed_theme_mod_settings[ $stylesheet ], 2101 $theme_mod_settings 2102 ); 2103 } 2104 2105 $autoload = false; 2106 $result = update_option( 'customize_stashed_theme_mods', $stashed_theme_mod_settings, $autoload ); 2107 if ( ! $result ) { 2108 return false; 2109 } 2110 return $stashed_theme_mod_settings; 1151 2111 } 1152 2112 … … 1692 2652 1693 2653 /** 2654 * Determines whether the admin and the frontend are on different domains. 2655 * 2656 * @since 4.7.0 2657 * @access public 2658 * 2659 * @return bool Whether cross-domain. 2660 */ 2661 public function is_cross_domain() { 2662 $admin_origin = wp_parse_url( admin_url() ); 2663 $home_origin = wp_parse_url( home_url() ); 2664 $cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) ); 2665 return $cross_domain; 2666 } 2667 2668 /** 2669 * Get URLs allowed to be previewed. 2670 * 2671 * If the front end and the admin are served from the same domain, load the 2672 * preview over ssl if the Customizer is being loaded over ssl. This avoids 2673 * insecure content warnings. This is not attempted if the admin and front end 2674 * are on different domains to avoid the case where the front end doesn't have 2675 * ssl certs. Domain mapping plugins can allow other urls in these conditions 2676 * using the customize_allowed_urls filter. 2677 * 2678 * @since 4.7.0 2679 * @access public 2680 * 2681 * @returns array Allowed URLs. 2682 */ 2683 public function get_allowed_urls() { 2684 $allowed_urls = array( home_url( '/' ) ); 2685 2686 if ( is_ssl() && ! $this->is_cross_domain() ) { 2687 $allowed_urls[] = home_url( '/', 'https' ); 2688 } 2689 2690 /** 2691 * Filters the list of URLs allowed to be clicked and followed in the Customizer preview. 2692 * 2693 * @since 3.4.0 2694 * 2695 * @param array $allowed_urls An array of allowed URLs. 2696 */ 2697 $allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) ); 2698 2699 return $allowed_urls; 2700 } 2701 2702 /** 2703 * Get messenger channel. 2704 * 2705 * @since 4.7.0 2706 * @access public 2707 * 2708 * @return string Messenger channel. 2709 */ 2710 public function get_messenger_channel() { 2711 return $this->messenger_channel; 2712 } 2713 2714 /** 1694 2715 * Set URL to link the user to when closing the Customizer. 1695 2716 * … … 1800 2821 */ 1801 2822 public function customize_pane_settings() { 1802 /*1803 * If the front end and the admin are served from the same domain, load the1804 * preview over ssl if the Customizer is being loaded over ssl. This avoids1805 * insecure content warnings. This is not attempted if the admin and front end1806 * are on different domains to avoid the case where the front end doesn't have1807 * ssl certs. Domain mapping plugins can allow other urls in these conditions1808 * using the customize_allowed_urls filter.1809 */1810 1811 $allowed_urls = array( home_url( '/' ) );1812 $admin_origin = parse_url( admin_url() );1813 $home_origin = parse_url( home_url() );1814 $cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );1815 1816 if ( is_ssl() && ! $cross_domain ) {1817 $allowed_urls[] = home_url( '/', 'https' );1818 }1819 1820 /**1821 * Filters the list of URLs allowed to be clicked and followed in the Customizer preview.1822 *1823 * @since 3.4.01824 *1825 * @param array $allowed_urls An array of allowed URLs.1826 */1827 $allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );1828 2823 1829 2824 $login_url = add_query_arg( array( … … 1832 2827 ), wp_login_url() ); 1833 2828 2829 // Ensure dirty flags are set for modified settings. 2830 foreach ( array_keys( $this->unsanitized_post_values() ) as $setting_id ) { 2831 $setting = $this->get_setting( $setting_id ); 2832 if ( $setting ) { 2833 $setting->dirty = true; 2834 } 2835 } 2836 1834 2837 // Prepare Customizer settings to pass to JavaScript. 1835 2838 $settings = array( 2839 'changeset' => array( 2840 'uuid' => $this->changeset_uuid(), 2841 'status' => $this->changeset_post_id() ? get_post_status( $this->changeset_post_id() ) : '', 2842 ), 2843 'timeouts' => array( 2844 'windowRefresh' => 250, 2845 'changesetAutoSave' => AUTOSAVE_INTERVAL * 1000, 2846 'keepAliveCheck' => 2500, 2847 'reflowPaneContents' => 100, 2848 'previewFrameSensitivity' => 2000, 2849 ), 1836 2850 'theme' => array( 1837 2851 'stylesheet' => $this->get_stylesheet(), … … 1843 2857 'activated' => esc_url_raw( home_url( '/' ) ), 1844 2858 'ajax' => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ), 1845 'allowed' => array_map( 'esc_url_raw', $ allowed_urls),1846 'isCrossDomain' => $ cross_domain,2859 'allowed' => array_map( 'esc_url_raw', $this->get_allowed_urls() ), 2860 'isCrossDomain' => $this->is_cross_domain(), 1847 2861 'home' => esc_url_raw( home_url( '/' ) ), 1848 2862 'login' => esc_url_raw( $login_url ), … … 2338 3352 */ 2339 3353 public function register_dynamic_settings() { 2340 $this->add_dynamic_settings( array_keys( $this->unsanitized_post_values() ) ); 3354 $setting_ids = array_keys( $this->unsanitized_post_values() ); 3355 $this->add_dynamic_settings( $setting_ids ); 2341 3356 } 2342 3357
Note: See TracChangeset
for help on using the changeset viewer.