Changeset 32806 for trunk/src/wp-includes/class-wp-customize-setting.php
- Timestamp:
- 06/16/2015 10:07:08 PM (9 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/class-wp-customize-setting.php
r32767 r32806 631 631 } 632 632 } 633 634 /** 635 * Customize Setting to represent a nav_menu. 636 * 637 * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and 638 * the IDs for the nav_menu_items associated with the nav menu. 639 * 640 * @since 4.3.0 641 * 642 * @see wp_get_nav_menu_items() 643 * @see WP_Customize_Setting 644 */ 645 class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { 646 647 const ID_PATTERN = '/^nav_menu_item\[(?P<id>-?\d+)\]$/'; 648 649 const POST_TYPE = 'nav_menu_item'; 650 651 const TYPE = 'nav_menu_item'; 652 653 /** 654 * Setting type. 655 * 656 * @since 4.3.0 657 * 658 * @var string 659 */ 660 public $type = self::TYPE; 661 662 /** 663 * Default setting value. 664 * 665 * @since 4.3.0 666 * 667 * @see wp_setup_nav_menu_item() 668 * @var array 669 */ 670 public $default = array( 671 // The $menu_item_data for wp_update_nav_menu_item(). 672 'object_id' => 0, 673 'object' => '', // Taxonomy name. 674 'menu_item_parent' => 0, // A.K.A. menu-item-parent-id; note that post_parent is different, and not included. 675 'position' => 0, // A.K.A. menu_order. 676 'type' => 'custom', // Note that type_label is not included here. 677 'title' => '', 678 'url' => '', 679 'target' => '', 680 'attr_title' => '', 681 'description' => '', 682 'classes' => '', 683 'xfn' => '', 684 'status' => 'publish', 685 'original_title' => '', 686 'nav_menu_term_id' => 0, // This will be supplied as the $menu_id arg for wp_update_nav_menu_item(). 687 // @todo also expose invalid? 688 ); 689 690 /** 691 * Default transport. 692 * 693 * @since 4.3.0 694 * 695 * @var string 696 */ 697 public $transport = 'postMessage'; 698 699 /** 700 * The post ID represented by this setting instance. This is the db_id. 701 * 702 * A negative value represents a placeholder ID for a new menu not yet saved. 703 * 704 * @todo Should this be $db_id, and also use this for WP_Customize_Nav_Menu_Setting::$term_id 705 * 706 * @since 4.3.0 707 * 708 * @var int 709 */ 710 public $post_id; 711 712 /** 713 * Previous (placeholder) post ID used before creating a new menu item. 714 * 715 * This value will be exported to JS via the customize_save_response filter 716 * so that JavaScript can update the settings to refer to the newly-assigned 717 * post ID. This value is always negative to indicate it does not refer to 718 * a real post. 719 * 720 * @since 4.3.0 721 * 722 * @see WP_Customize_Nav_Menu_Item_Setting::update() 723 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 724 * 725 * @var int 726 */ 727 public $previous_post_id; 728 729 /** 730 * When previewing or updating a menu item, this stores the previous nav_menu_term_id 731 * which ensures that we can apply the proper filters. 732 * 733 * @since 4.3.0 734 * 735 * @var int 736 */ 737 public $original_nav_menu_term_id; 738 739 /** 740 * Whether or not preview() was called. 741 * 742 * @since 4.3.0 743 * 744 * @var bool 745 */ 746 protected $is_previewed = false; 747 748 /** 749 * Whether or not update() was called. 750 * 751 * @since 4.3.0 752 * 753 * @var bool 754 */ 755 protected $is_updated = false; 756 757 /** 758 * Status for calling the update method, used in customize_save_response filter. 759 * 760 * When status is inserted, the placeholder post ID is stored in $previous_post_id. 761 * When status is error, the error is stored in $update_error. 762 * 763 * @since 4.3.0 764 * 765 * @see WP_Customize_Nav_Menu_Item_Setting::update() 766 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 767 * 768 * @var string updated|inserted|deleted|error 769 */ 770 public $update_status; 771 772 /** 773 * Any error object returned by wp_update_nav_menu_item() when setting is updated. 774 * 775 * @since 4.3.0 776 * 777 * @see WP_Customize_Nav_Menu_Item_Setting::update() 778 * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response() 779 * 780 * @var WP_Error 781 */ 782 public $update_error; 783 784 /** 785 * Constructor. 786 * 787 * Any supplied $args override class property defaults. 788 * 789 * @since 4.3.0 790 * 791 * @param WP_Customize_Manager $manager Manager instance. 792 * @param string $id An specific ID of the setting. Can be a 793 * theme mod or option name. 794 * @param array $args Optional. Setting arguments. 795 * @throws Exception If $id is not valid for this setting type. 796 */ 797 public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { 798 if ( empty( $manager->nav_menus ) ) { 799 throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' ); 800 } 801 802 if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) { 803 throw new Exception( "Illegal widget setting ID: $id" ); 804 } 805 806 $this->post_id = intval( $matches['id'] ); 807 808 $menu = $this->value(); 809 $this->original_nav_menu_term_id = $menu['nav_menu_term_id']; 810 811 parent::__construct( $manager, $id, $args ); 812 } 813 814 /** 815 * Get the instance data for a given widget setting. 816 * 817 * @since 4.3.0 818 * 819 * @see wp_setup_nav_menu_item() 820 * 821 * @return array 822 */ 823 public function value() { 824 if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) { 825 $undefined = new stdClass(); // Symbol. 826 $post_value = $this->post_value( $undefined ); 827 828 if ( $undefined === $post_value ) { 829 $value = $this->_original_value; 830 } else { 831 $value = $post_value; 832 } 833 } else { 834 $value = false; 835 836 // Note that a ID of less than one indicates a nav_menu not yet inserted. 837 if ( $this->post_id > 0 ) { 838 $post = get_post( $this->post_id ); 839 if ( $post && self::POST_TYPE === $post->post_type ) { 840 $item = wp_setup_nav_menu_item( $post ); 841 $value = wp_array_slice_assoc( 842 (array) $item, 843 array_keys( $this->default ) 844 ); 845 $value['position'] = $item->menu_order; 846 $value['status'] = $item->post_status; 847 $value['original_title'] = ''; 848 849 $menus = wp_get_post_terms( $post->ID, WP_Customize_Nav_Menu_Setting::TAXONOMY, array( 850 'fields' => 'ids', 851 ) ); 852 853 if ( ! empty( $menus ) ) { 854 $value['nav_menu_term_id'] = array_shift( $menus ); 855 } else { 856 $value['nav_menu_term_id'] = 0; 857 } 858 859 if ( 'post_type' === $value['type'] ) { 860 $original_title = get_the_title( $value['object_id'] ); 861 } else if ( 'taxonomy' === $value['type'] ) { 862 $original_title = get_term_field( 'name', $value['object_id'], $value['object'], 'raw' ); 863 if ( is_wp_error( $original_title ) ) { 864 $original_title = ''; 865 } 866 } 867 868 if ( ! empty( $original_title ) ) { 869 $value['original_title'] = $original_title; 870 } 871 } 872 } 873 874 if ( ! is_array( $value ) ) { 875 $value = $this->default; 876 } 877 } 878 879 if ( is_array( $value ) ) { 880 foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { 881 $value[ $key ] = intval( $value[ $key ] ); 882 } 883 } 884 885 return $value; 886 } 887 888 /** 889 * Handle previewing the setting. 890 * 891 * @since 4.3.0 892 * 893 * @see WP_Customize_Manager::post_value() 894 */ 895 public function preview() { 896 if ( $this->is_previewed ) { 897 return; 898 } 899 900 $this->is_previewed = true; 901 $this->_original_value = $this->value(); 902 $this->original_nav_menu_term_id = $this->_original_value['nav_menu_term_id']; 903 $this->_previewed_blog_id = get_current_blog_id(); 904 905 add_filter( 'wp_get_nav_menu_items', array( $this, 'filter_wp_get_nav_menu_items' ), 10, 3 ); 906 907 $sort_callback = array( __CLASS__, 'sort_wp_get_nav_menu_items' ); 908 if ( ! has_filter( 'wp_get_nav_menu_items', $sort_callback ) ) { 909 add_filter( 'wp_get_nav_menu_items', array( __CLASS__, 'sort_wp_get_nav_menu_items' ), 1000, 3 ); 910 } 911 912 // @todo Add get_post_metadata filters for plugins to add their data. 913 } 914 915 /** 916 * Filter the wp_get_nav_menu_items() result to supply the previewed menu items. 917 * 918 * @since 4.3.0 919 * 920 * @see wp_get_nav_menu_items() 921 * 922 * @param array $items An array of menu item post objects. 923 * @param object $menu The menu object. 924 * @param array $args An array of arguments used to retrieve menu item objects. 925 * @return array Array of menu items, 926 */ 927 function filter_wp_get_nav_menu_items( $items, $menu, $args ) { 928 $this_item = $this->value(); 929 $current_nav_menu_term_id = $this_item['nav_menu_term_id']; 930 unset( $this_item['nav_menu_term_id'] ); 931 932 $should_filter = ( 933 $menu->term_id === $this->original_nav_menu_term_id 934 || 935 $menu->term_id === $current_nav_menu_term_id 936 ); 937 if ( ! $should_filter ) { 938 return $items; 939 } 940 941 // Handle deleted menu item, or menu item moved to another menu. 942 $should_remove = ( 943 false === $this_item 944 || 945 ( 946 $this->original_nav_menu_term_id === $menu->term_id 947 && 948 $current_nav_menu_term_id !== $this->original_nav_menu_term_id 949 ) 950 ); 951 if ( $should_remove ) { 952 $filtered_items = array(); 953 foreach ( $items as $item ) { 954 if ( $item->db_id !== $this->post_id ) { 955 $filtered_items[] = $item; 956 } 957 } 958 return $filtered_items; 959 } 960 961 $mutated = false; 962 $should_update = ( 963 is_array( $this_item ) 964 && 965 $current_nav_menu_term_id === $menu->term_id 966 ); 967 if ( $should_update ) { 968 foreach ( $items as $item ) { 969 if ( $item->db_id === $this->post_id ) { 970 foreach ( get_object_vars( $this->value_as_wp_post_nav_menu_item() ) as $key => $value ) { 971 $item->$key = $value; 972 } 973 $mutated = true; 974 } 975 } 976 977 // Not found so we have to append it.. 978 if ( ! $mutated ) { 979 $items[] = $this->value_as_wp_post_nav_menu_item(); 980 } 981 } 982 983 return $items; 984 } 985 986 /** 987 * Re-apply the tail logic also applied on $items by wp_get_nav_menu_items(). 988 * 989 * @since 4.3.0 990 * 991 * @see wp_get_nav_menu_items() 992 * 993 * @param array $items An array of menu item post objects. 994 * @param object $menu The menu object. 995 * @param array $args An array of arguments used to retrieve menu item objects. 996 * @return array Array of menu items, 997 */ 998 static function sort_wp_get_nav_menu_items( $items, $menu, $args ) { 999 // @todo We should probably re-apply some constraints imposed by $args. 1000 unset( $args['include'] ); 1001 1002 // Remove invalid items only in frontend. 1003 if ( ! is_admin() ) { 1004 $items = array_filter( $items, '_is_valid_nav_menu_item' ); 1005 } 1006 1007 if ( ARRAY_A === $args['output'] ) { 1008 $GLOBALS['_menu_item_sort_prop'] = $args['output_key']; 1009 usort( $items, '_sort_nav_menu_items' ); 1010 $i = 1; 1011 1012 foreach ( $items as $k => $item ) { 1013 $items[ $k ]->$args['output_key'] = $i++; 1014 } 1015 } 1016 1017 return $items; 1018 } 1019 1020 /** 1021 * Get the value emulated into a WP_Post and set up as a nav_menu_item. 1022 * 1023 * @since 4.3.0 1024 * 1025 * @return WP_Post With {@see wp_setup_nav_menu_item()} applied. 1026 */ 1027 public function value_as_wp_post_nav_menu_item() { 1028 $item = (object) $this->value(); 1029 unset( $item->nav_menu_term_id ); 1030 1031 $item->post_status = $item->status; 1032 unset( $item->status ); 1033 1034 $item->post_type = 'nav_menu_item'; 1035 $item->menu_order = $item->position; 1036 unset( $item->position ); 1037 1038 $item->post_author = get_current_user_id(); 1039 1040 if ( $item->title ) { 1041 $item->post_title = $item->title; 1042 } 1043 1044 $item->ID = $this->post_id; 1045 $post = new WP_Post( (object) $item ); 1046 $post = wp_setup_nav_menu_item( $post ); 1047 1048 return $post; 1049 } 1050 1051 /** 1052 * Sanitize an input. 1053 * 1054 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but 1055 * we remove that in this override. 1056 * 1057 * @since 4.3.0 1058 * 1059 * @param array $menu_item_value The value to sanitize. 1060 * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. Otherwise the sanitized value. 1061 */ 1062 public function sanitize( $menu_item_value ) { 1063 // Menu is marked for deletion. 1064 if ( false === $menu_item_value ) { 1065 return $menu_item_value; 1066 } 1067 1068 // Invalid. 1069 if ( ! is_array( $menu_item_value ) ) { 1070 return null; 1071 } 1072 1073 $default = array( 1074 'object_id' => 0, 1075 'object' => '', 1076 'menu_item_parent' => 0, 1077 'position' => 0, 1078 'type' => 'custom', 1079 'title' => '', 1080 'url' => '', 1081 'target' => '', 1082 'attr_title' => '', 1083 'description' => '', 1084 'classes' => '', 1085 'xfn' => '', 1086 'status' => 'publish', 1087 'original_title' => '', 1088 'nav_menu_term_id' => 0, 1089 ); 1090 $menu_item_value = array_merge( $default, $menu_item_value ); 1091 $menu_item_value = wp_array_slice_assoc( $menu_item_value, array_keys( $default ) ); 1092 $menu_item_value['position'] = max( 0, intval( $menu_item_value['position'] ) ); 1093 1094 foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) { 1095 // Note we need to allow negative-integer IDs for previewed objects not inserted yet. 1096 $menu_item_value[ $key ] = intval( $menu_item_value[ $key ] ); 1097 } 1098 1099 foreach ( array( 'type', 'object', 'target' ) as $key ) { 1100 $menu_item_value[ $key ] = sanitize_key( $menu_item_value[ $key ] ); 1101 } 1102 1103 foreach ( array( 'xfn', 'classes' ) as $key ) { 1104 $value = $menu_item_value[ $key ]; 1105 if ( ! is_array( $value ) ) { 1106 $value = explode( ' ', $value ); 1107 } 1108 $menu_item_value[ $key ] = implode( ' ', array_map( 'sanitize_html_class', $value ) ); 1109 } 1110 1111 foreach ( array( 'title', 'attr_title', 'description', 'original_title' ) as $key ) { 1112 // @todo Should esc_attr() the attr_title as well? 1113 $menu_item_value[ $key ] = sanitize_text_field( $menu_item_value[ $key ] ); 1114 } 1115 1116 $menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] ); 1117 if ( ! get_post_status_object( $menu_item_value['status'] ) ) { 1118 $menu_item_value['status'] = 'publish'; 1119 } 1120 1121 /** This filter is documented in wp-includes/class-wp-customize-setting.php */ 1122 return apply_filters( "customize_sanitize_{$this->id}", $menu_item_value, $this ); 1123 } 1124 1125 /** 1126 * Create/update the nav_menu_item post for this setting. 1127 * 1128 * Any created menu items will have their assigned post IDs exported to the client 1129 * via the customize_save_response filter. Likewise, any errors will be exported 1130 * to the client via the customize_save_response() filter. 1131 * 1132 * To delete a menu, the client can send false as the value. 1133 * 1134 * @since 4.3.0 1135 * 1136 * @see wp_update_nav_menu_item() 1137 * 1138 * @param array|false $value The menu item array to update. If false, then the menu item will be deleted entirely. 1139 * See {@see WP_Customize_Nav_Menu_Item_Setting::$default} for what the value should 1140 * consist of. 1141 * @return void 1142 */ 1143 protected function update( $value ) { 1144 if ( $this->is_updated ) { 1145 return; 1146 } 1147 1148 $this->is_updated = true; 1149 $is_placeholder = ( $this->post_id < 0 ); 1150 $is_delete = ( false === $value ); 1151 1152 add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) ); 1153 1154 if ( $is_delete ) { 1155 // If the current setting post is a placeholder, a delete request is a no-op. 1156 if ( $is_placeholder ) { 1157 $this->update_status = 'deleted'; 1158 } else { 1159 $r = wp_delete_post( $this->post_id, true ); 1160 1161 if ( false === $r ) { 1162 $this->update_error = new WP_Error( 'delete_failure' ); 1163 $this->update_status = 'error'; 1164 } else { 1165 $this->update_status = 'deleted'; 1166 } 1167 // @todo send back the IDs for all associated nav menu items deleted, so these settings (and controls) can be removed from Customizer? 1168 } 1169 } else { 1170 1171 // Handle saving menu items for menus that are being newly-created. 1172 if ( $value['nav_menu_term_id'] < 0 ) { 1173 $nav_menu_setting_id = sprintf( 'nav_menu[%s]', $value['nav_menu_term_id'] ); 1174 $nav_menu_setting = $this->manager->get_setting( $nav_menu_setting_id ); 1175 1176 if ( ! $nav_menu_setting || ! ( $nav_menu_setting instanceof WP_Customize_Nav_Menu_Setting ) ) { 1177 $this->update_status = 'error'; 1178 $this->update_error = new WP_Error( 'unexpected_nav_menu_setting' ); 1179 return; 1180 } 1181 1182 if ( false === $nav_menu_setting->save() ) { 1183 $this->update_status = 'error'; 1184 $this->update_error = new WP_Error( 'nav_menu_setting_failure' ); 1185 } 1186 1187 if ( $nav_menu_setting->previous_term_id !== intval( $value['nav_menu_term_id'] ) ) { 1188 $this->update_status = 'error'; 1189 $this->update_error = new WP_Error( 'unexpected_previous_term_id' ); 1190 return; 1191 } 1192 1193 $value['nav_menu_term_id'] = $nav_menu_setting->term_id; 1194 } 1195 1196 // Handle saving a nav menu item that is a child of a nav menu item being newly-created. 1197 if ( $value['menu_item_parent'] < 0 ) { 1198 $parent_nav_menu_item_setting_id = sprintf( 'nav_menu_item[%s]', $value['menu_item_parent'] ); 1199 $parent_nav_menu_item_setting = $this->manager->get_setting( $parent_nav_menu_item_setting_id ); 1200 1201 if ( ! $parent_nav_menu_item_setting || ! ( $parent_nav_menu_item_setting instanceof WP_Customize_Nav_Menu_Item_Setting ) ) { 1202 $this->update_status = 'error'; 1203 $this->update_error = new WP_Error( 'unexpected_nav_menu_item_setting' ); 1204 return; 1205 } 1206 1207 if ( false === $parent_nav_menu_item_setting->save() ) { 1208 $this->update_status = 'error'; 1209 $this->update_error = new WP_Error( 'nav_menu_item_setting_failure' ); 1210 } 1211 1212 if ( $parent_nav_menu_item_setting->previous_post_id !== intval( $value['menu_item_parent'] ) ) { 1213 $this->update_status = 'error'; 1214 $this->update_error = new WP_Error( 'unexpected_previous_post_id' ); 1215 return; 1216 } 1217 1218 $value['menu_item_parent'] = $parent_nav_menu_item_setting->post_id; 1219 } 1220 1221 // Insert or update menu. 1222 $menu_item_data = array( 1223 'menu-item-object-id' => $value['object_id'], 1224 'menu-item-object' => $value['object'], 1225 'menu-item-parent-id' => $value['menu_item_parent'], 1226 'menu-item-position' => $value['position'], 1227 'menu-item-type' => $value['type'], 1228 'menu-item-title' => $value['title'], 1229 'menu-item-url' => $value['url'], 1230 'menu-item-description' => $value['description'], 1231 'menu-item-attr-title' => $value['attr_title'], 1232 'menu-item-target' => $value['target'], 1233 'menu-item-classes' => $value['classes'], 1234 'menu-item-xfn' => $value['xfn'], 1235 'menu-item-status' => $value['status'], 1236 ); 1237 1238 $r = wp_update_nav_menu_item( 1239 $value['nav_menu_term_id'], 1240 $is_placeholder ? 0 : $this->post_id, 1241 $menu_item_data 1242 ); 1243 1244 if ( is_wp_error( $r ) ) { 1245 $this->update_status = 'error'; 1246 $this->update_error = $r; 1247 } else { 1248 if ( $is_placeholder ) { 1249 $this->previous_post_id = $this->post_id; 1250 $this->post_id = $r; 1251 $this->update_status = 'inserted'; 1252 } else { 1253 $this->update_status = 'updated'; 1254 } 1255 } 1256 } 1257 1258 } 1259 1260 /** 1261 * Export data for the JS client. 1262 * 1263 * @since 4.3.0 1264 * 1265 * @see WP_Customize_Nav_Menu_Item_Setting::update() 1266 * 1267 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`. 1268 * @return array 1269 */ 1270 function amend_customize_save_response( $data ) { 1271 if ( ! isset( $data['nav_menu_item_updates'] ) ) { 1272 $data['nav_menu_item_updates'] = array(); 1273 } 1274 1275 $data['nav_menu_item_updates'][] = array( 1276 'post_id' => $this->post_id, 1277 'previous_post_id' => $this->previous_post_id, 1278 'error' => $this->update_error ? $this->update_error->get_error_code() : null, 1279 'status' => $this->update_status, 1280 ); 1281 1282 return $data; 1283 } 1284 } 1285 1286 /** 1287 * Customize Setting to represent a nav_menu. 1288 * 1289 * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and 1290 * the IDs for the nav_menu_items associated with the nav menu. 1291 * 1292 * @since 4.3.0 1293 * 1294 * @see wp_get_nav_menu_object() 1295 * @see WP_Customize_Setting 1296 */ 1297 class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { 1298 1299 const ID_PATTERN = '/^nav_menu\[(?P<id>-?\d+)\]$/'; 1300 1301 const TAXONOMY = 'nav_menu'; 1302 1303 const TYPE = 'nav_menu'; 1304 1305 /** 1306 * Setting type. 1307 * 1308 * @since 4.3.0 1309 * 1310 * @var string 1311 */ 1312 public $type = self::TYPE; 1313 1314 /** 1315 * Default setting value. 1316 * 1317 * @since 4.3.0 1318 * 1319 * @see wp_get_nav_menu_object() 1320 * 1321 * @var array 1322 */ 1323 public $default = array( 1324 'name' => '', 1325 'description' => '', 1326 'parent' => 0, 1327 'auto_add' => false, 1328 ); 1329 1330 /** 1331 * Default transport. 1332 * 1333 * @since 4.3.0 1334 * 1335 * @var string 1336 */ 1337 public $transport = 'postMessage'; 1338 1339 /** 1340 * The term ID represented by this setting instance. 1341 * 1342 * A negative value represents a placeholder ID for a new menu not yet saved. 1343 * 1344 * @since 4.3.0 1345 * 1346 * @var int 1347 */ 1348 public $term_id; 1349 1350 /** 1351 * Previous (placeholder) term ID used before creating a new menu. 1352 * 1353 * This value will be exported to JS via the customize_save_response filter 1354 * so that JavaScript can update the settings to refer to the newly-assigned 1355 * term ID. This value is always negative to indicate it does not refer to 1356 * a real term. 1357 * 1358 * @since 4.3.0 1359 * 1360 * @see WP_Customize_Nav_Menu_Setting::update() 1361 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() 1362 * 1363 * @var int 1364 */ 1365 public $previous_term_id; 1366 1367 /** 1368 * Whether or not preview() was called. 1369 * 1370 * @since 4.3.0 1371 * 1372 * @var bool 1373 */ 1374 protected $is_previewed = false; 1375 1376 /** 1377 * Whether or not update() was called. 1378 * 1379 * @since 4.3.0 1380 * 1381 * @var bool 1382 */ 1383 protected $is_updated = false; 1384 1385 /** 1386 * Status for calling the update method, used in customize_save_response filter. 1387 * 1388 * When status is inserted, the placeholder term ID is stored in $previous_term_id. 1389 * When status is error, the error is stored in $update_error. 1390 * 1391 * @since 4.3.0 1392 * 1393 * @see WP_Customize_Nav_Menu_Setting::update() 1394 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() 1395 * 1396 * @var string updated|inserted|deleted|error 1397 */ 1398 public $update_status; 1399 1400 /** 1401 * Any error object returned by wp_update_nav_menu_object() when setting is updated. 1402 * 1403 * @since 4.3.0 1404 * 1405 * @see WP_Customize_Nav_Menu_Setting::update() 1406 * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response() 1407 * 1408 * @var WP_Error 1409 */ 1410 public $update_error; 1411 1412 /** 1413 * Constructor. 1414 * 1415 * Any supplied $args override class property defaults. 1416 * 1417 * @since 4.3.0 1418 * 1419 * @param WP_Customize_Manager $manager Manager instance. 1420 * @param string $id An specific ID of the setting. Can be a 1421 * theme mod or option name. 1422 * @param array $args Optional. Setting arguments. 1423 * @throws Exception If $id is not valid for this setting type. 1424 */ 1425 public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) { 1426 if ( empty( $manager->nav_menus ) ) { 1427 throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' ); 1428 } 1429 1430 if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) { 1431 throw new Exception( "Illegal widget setting ID: $id" ); 1432 } 1433 1434 $this->term_id = intval( $matches['id'] ); 1435 1436 parent::__construct( $manager, $id, $args ); 1437 } 1438 1439 /** 1440 * Get the instance data for a given widget setting. 1441 * 1442 * @since 4.3.0 1443 * 1444 * @see wp_get_nav_menu_object() 1445 * 1446 * @return array 1447 */ 1448 public function value() { 1449 if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) { 1450 $undefined = new stdClass(); // Symbol. 1451 $post_value = $this->post_value( $undefined ); 1452 1453 if ( $undefined === $post_value ) { 1454 $value = $this->_original_value; 1455 } else { 1456 $value = $post_value; 1457 } 1458 } else { 1459 $value = false; 1460 1461 // Note that a term_id of less than one indicates a nav_menu not yet inserted. 1462 if ( $this->term_id > 0 ) { 1463 $term = wp_get_nav_menu_object( $this->term_id ); 1464 1465 if ( $term ) { 1466 $value = wp_array_slice_assoc( (array) $term, array_keys( $this->default ) ); 1467 1468 $nav_menu_options = (array) get_option( 'nav_menu_options', array() ); 1469 $value['auto_add'] = false; 1470 1471 if ( isset( $nav_menu_options['auto_add'] ) && is_array( $nav_menu_options['auto_add'] ) ) { 1472 $value['auto_add'] = in_array( $term->term_id, $nav_menu_options['auto_add'] ); 1473 } 1474 } 1475 } 1476 1477 if ( ! is_array( $value ) ) { 1478 $value = $this->default; 1479 } 1480 } 1481 return $value; 1482 } 1483 1484 /** 1485 * Handle previewing the setting. 1486 * 1487 * @since 4.3.0 1488 * 1489 * @see WP_Customize_Manager::post_value() 1490 */ 1491 public function preview() { 1492 if ( $this->is_previewed ) { 1493 return; 1494 } 1495 1496 $this->is_previewed = true; 1497 $this->_original_value = $this->value(); 1498 $this->_previewed_blog_id = get_current_blog_id(); 1499 1500 add_filter( 'wp_get_nav_menu_object', array( $this, 'filter_wp_get_nav_menu_object' ), 10, 2 ); 1501 add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) ); 1502 add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) ); 1503 } 1504 1505 /** 1506 * Filter the wp_get_nav_menu_object() result to supply the previewed menu object. 1507 * 1508 * Requesting a nav_menu object by anything but ID is not supported. 1509 * 1510 * @since 4.3.0 1511 * 1512 * @see wp_get_nav_menu_object() 1513 * 1514 * @param object|null $menu_obj Object returned by wp_get_nav_menu_object(). 1515 * @param string $menu_id ID of the nav_menu term. Requests by slug or name will be ignored. 1516 * @return object|null 1517 */ 1518 function filter_wp_get_nav_menu_object( $menu_obj, $menu_id ) { 1519 $ok = ( 1520 get_current_blog_id() === $this->_previewed_blog_id 1521 && 1522 is_int( $menu_id ) 1523 && 1524 $menu_id === $this->term_id 1525 ); 1526 if ( ! $ok ) { 1527 return $menu_obj; 1528 } 1529 1530 $setting_value = $this->value(); 1531 1532 // Handle deleted menus. 1533 if ( false === $setting_value ) { 1534 return false; 1535 } 1536 1537 // Handle sanitization failure by preventing short-circuiting. 1538 if ( null === $setting_value ) { 1539 return $menu_obj; 1540 } 1541 1542 $menu_obj = (object) array_merge( array( 1543 'term_id' => $this->term_id, 1544 'term_taxonomy_id' => $this->term_id, 1545 'slug' => sanitize_title( $setting_value['name'] ), 1546 'count' => 0, 1547 'term_group' => 0, 1548 'taxonomy' => self::TAXONOMY, 1549 'filter' => 'raw', 1550 ), $setting_value ); 1551 1552 return $menu_obj; 1553 } 1554 1555 /** 1556 * Filter the nav_menu_options option to include this menu's auto_add preference. 1557 * 1558 * @since 4.3.0 1559 * 1560 * @param array $nav_menu_options Nav menu options including auto_add. 1561 * @return array 1562 */ 1563 function filter_nav_menu_options( $nav_menu_options ) { 1564 if ( $this->_previewed_blog_id !== get_current_blog_id() ) { 1565 return $nav_menu_options; 1566 } 1567 1568 $menu = $this->value(); 1569 $nav_menu_options = $this->filter_nav_menu_options_value( 1570 $nav_menu_options, 1571 $this->term_id, 1572 false === $menu ? false : $menu['auto_add'] 1573 ); 1574 1575 return $nav_menu_options; 1576 } 1577 1578 /** 1579 * Sanitize an input. 1580 * 1581 * Note that parent::sanitize() erroneously does wp_unslash() on $value, but 1582 * we remove that in this override. 1583 * 1584 * @since 4.3.0 1585 * 1586 * @param array $value The value to sanitize. 1587 * @return array|false|null Null if an input isn't valid. False if it is marked for deletion. Otherwise the sanitized value. 1588 */ 1589 public function sanitize( $value ) { 1590 // Menu is marked for deletion. 1591 if ( false === $value ) { 1592 return $value; 1593 } 1594 1595 // Invalid. 1596 if ( ! is_array( $value ) ) { 1597 return null; 1598 } 1599 1600 $default = array( 1601 'name' => '', 1602 'description' => '', 1603 'parent' => 0, 1604 'auto_add' => false, 1605 ); 1606 $value = array_merge( $default, $value ); 1607 $value = wp_array_slice_assoc( $value, array_keys( $default ) ); 1608 1609 $value['name'] = trim( esc_html( $value['name'] ) ); // This sanitization code is used in wp-admin/nav-menus.php. 1610 $value['description'] = sanitize_text_field( $value['description'] ); 1611 $value['parent'] = max( 0, intval( $value['parent'] ) ); 1612 $value['auto_add'] = ! empty( $value['auto_add'] ); 1613 1614 /** This filter is documented in wp-includes/class-wp-customize-setting.php */ 1615 return apply_filters( "customize_sanitize_{$this->id}", $value, $this ); 1616 } 1617 1618 /** 1619 * Create/update the nav_menu term for this setting. 1620 * 1621 * Any created menus will have their assigned term IDs exported to the client 1622 * via the customize_save_response filter. Likewise, any errors will be exported 1623 * to the client via the customize_save_response() filter. 1624 * 1625 * To delete a menu, the client can send false as the value. 1626 * 1627 * @since 4.3.0 1628 * 1629 * @see wp_update_nav_menu_object() 1630 * 1631 * @param array|false $value { 1632 * The value to update. Note that slug cannot be updated via wp_update_nav_menu_object(). 1633 * If false, then the menu will be deleted entirely. 1634 * 1635 * @type string $name The name of the menu to save. 1636 * @type string $description The term description. Default empty string. 1637 * @type int $parent The id of the parent term. Default 0. 1638 * @type bool $auto_add Whether pages will auto_add to this menu. Default false. 1639 * } 1640 * @return void 1641 */ 1642 protected function update( $value ) { 1643 if ( $this->is_updated ) { 1644 return; 1645 } 1646 1647 $this->is_updated = true; 1648 $is_placeholder = ( $this->term_id < 0 ); 1649 $is_delete = ( false === $value ); 1650 1651 add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) ); 1652 1653 $auto_add = null; 1654 if ( $is_delete ) { 1655 // If the current setting term is a placeholder, a delete request is a no-op. 1656 if ( $is_placeholder ) { 1657 $this->update_status = 'deleted'; 1658 } else { 1659 $r = wp_delete_nav_menu( $this->term_id ); 1660 1661 if ( is_wp_error( $r ) ) { 1662 $this->update_status = 'error'; 1663 $this->update_error = $r; 1664 } else { 1665 $this->update_status = 'deleted'; 1666 $auto_add = false; 1667 } 1668 } 1669 } else { 1670 // Insert or update menu. 1671 $menu_data = wp_array_slice_assoc( $value, array( 'description', 'parent' ) ); 1672 if ( isset( $value['name'] ) ) { 1673 $menu_data['menu-name'] = $value['name']; 1674 } 1675 1676 $r = wp_update_nav_menu_object( $is_placeholder ? 0 : $this->term_id, $menu_data ); 1677 if ( is_wp_error( $r ) ) { 1678 $this->update_status = 'error'; 1679 $this->update_error = $r; 1680 } else { 1681 if ( $is_placeholder ) { 1682 $this->previous_term_id = $this->term_id; 1683 $this->term_id = $r; 1684 $this->update_status = 'inserted'; 1685 } else { 1686 $this->update_status = 'updated'; 1687 } 1688 1689 $auto_add = $value['auto_add']; 1690 } 1691 } 1692 1693 if ( null !== $auto_add ) { 1694 $nav_menu_options = $this->filter_nav_menu_options_value( 1695 (array) get_option( 'nav_menu_options', array() ), 1696 $this->term_id, 1697 $auto_add 1698 ); 1699 update_option( 'nav_menu_options', $nav_menu_options ); 1700 } 1701 1702 // Make sure that new menus assigned to nav menu locations use their new IDs. 1703 if ( 'inserted' === $this->update_status ) { 1704 foreach ( $this->manager->settings() as $setting ) { 1705 if ( ! preg_match( '/^nav_menu_locations\[/', $setting->id ) ) { 1706 continue; 1707 } 1708 1709 $post_value = $setting->post_value( null ); 1710 if ( ! is_null( $post_value ) && $this->previous_term_id === intval( $post_value ) ) { 1711 $this->manager->set_post_value( $setting->id, $this->term_id ); 1712 $setting->save(); 1713 } 1714 } 1715 } 1716 } 1717 1718 /** 1719 * Update a nav_menu_options array. 1720 * 1721 * @since 4.3.0 1722 * 1723 * @see WP_Customize_Nav_Menu_Setting::filter_nav_menu_options() 1724 * @see WP_Customize_Nav_Menu_Setting::update() 1725 * 1726 * @param array $nav_menu_options Array as returned by get_option( 'nav_menu_options' ). 1727 * @param int $menu_id The term ID for the given menu. 1728 * @param bool $auto_add Whether to auto-add or not. 1729 * @return array 1730 */ 1731 protected function filter_nav_menu_options_value( $nav_menu_options, $menu_id, $auto_add ) { 1732 $nav_menu_options = (array) $nav_menu_options; 1733 if ( ! isset( $nav_menu_options['auto_add'] ) ) { 1734 $nav_menu_options['auto_add'] = array(); 1735 } 1736 1737 $i = array_search( $menu_id, $nav_menu_options['auto_add'] ); 1738 if ( $auto_add && false === $i ) { 1739 array_push( $nav_menu_options['auto_add'], $this->term_id ); 1740 } else if ( ! $auto_add && false !== $i ) { 1741 array_splice( $nav_menu_options['auto_add'], $i, 1 ); 1742 } 1743 1744 return $nav_menu_options; 1745 } 1746 1747 /** 1748 * Export data for the JS client. 1749 * 1750 * @since 4.3.0 1751 * 1752 * @see WP_Customize_Nav_Menu_Setting::update() 1753 * 1754 * @param array $data Additional information passed back to the 'saved' event on `wp.customize`. 1755 * @return array 1756 */ 1757 function amend_customize_save_response( $data ) { 1758 if ( ! isset( $data['nav_menu_updates'] ) ) { 1759 $data['nav_menu_updates'] = array(); 1760 } 1761 1762 $data['nav_menu_updates'][] = array( 1763 'term_id' => $this->term_id, 1764 'previous_term_id' => $this->previous_term_id, 1765 'error' => $this->update_error ? $this->update_error->get_error_code() : null, 1766 'status' => $this->update_status, 1767 ); 1768 1769 return $data; 1770 } 1771 }
Note: See TracChangeset
for help on using the changeset viewer.