[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/customize/ -> class-wp-customize-nav-menu-item-setting.php (source)

   1  <?php
   2  /**
   3   * Customize API: WP_Customize_Nav_Menu_Item_Setting class
   4   *
   5   * @package WordPress
   6   * @subpackage Customize
   7   * @since 4.4.0
   8   */
   9  
  10  /**
  11   * Customize Setting to represent a nav_menu.
  12   *
  13   * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and
  14   * the IDs for the nav_menu_items associated with the nav menu.
  15   *
  16   * @since 4.3.0
  17   *
  18   * @see WP_Customize_Setting
  19   */
  20  class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting {
  21  
  22      const ID_PATTERN = '/^nav_menu_item\[(?P<id>-?\d+)\]$/';
  23  
  24      const POST_TYPE = 'nav_menu_item';
  25  
  26      const TYPE = 'nav_menu_item';
  27  
  28      /**
  29       * Setting type.
  30       *
  31       * @since 4.3.0
  32       * @var string
  33       */
  34      public $type = self::TYPE;
  35  
  36      /**
  37       * Default setting value.
  38       *
  39       * @since 4.3.0
  40       * @var array
  41       *
  42       * @see wp_setup_nav_menu_item()
  43       */
  44      public $default = array(
  45          // The $menu_item_data for wp_update_nav_menu_item().
  46          'object_id'        => 0,
  47          'object'           => '', // Taxonomy name.
  48          'menu_item_parent' => 0, // A.K.A. menu-item-parent-id; note that post_parent is different, and not included.
  49          'position'         => 0, // A.K.A. menu_order.
  50          'type'             => 'custom', // Note that type_label is not included here.
  51          'title'            => '',
  52          'url'              => '',
  53          'target'           => '',
  54          'attr_title'       => '',
  55          'description'      => '',
  56          'classes'          => '',
  57          'xfn'              => '',
  58          'status'           => 'publish',
  59          'original_title'   => '',
  60          'nav_menu_term_id' => 0, // This will be supplied as the $menu_id arg for wp_update_nav_menu_item().
  61          '_invalid'         => false,
  62      );
  63  
  64      /**
  65       * Default transport.
  66       *
  67       * @since 4.3.0
  68       * @since 4.5.0 Default changed to 'refresh'
  69       * @var string
  70       */
  71      public $transport = 'refresh';
  72  
  73      /**
  74       * The post ID represented by this setting instance. This is the db_id.
  75       *
  76       * A negative value represents a placeholder ID for a new menu not yet saved.
  77       *
  78       * @since 4.3.0
  79       * @var int
  80       */
  81      public $post_id;
  82  
  83      /**
  84       * Storage of pre-setup menu item to prevent wasted calls to wp_setup_nav_menu_item().
  85       *
  86       * @since 4.3.0
  87       * @var array|null
  88       */
  89      protected $value;
  90  
  91      /**
  92       * Previous (placeholder) post ID used before creating a new menu item.
  93       *
  94       * This value will be exported to JS via the customize_save_response filter
  95       * so that JavaScript can update the settings to refer to the newly-assigned
  96       * post ID. This value is always negative to indicate it does not refer to
  97       * a real post.
  98       *
  99       * @since 4.3.0
 100       * @var int
 101       *
 102       * @see WP_Customize_Nav_Menu_Item_Setting::update()
 103       * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
 104       */
 105      public $previous_post_id;
 106  
 107      /**
 108       * When previewing or updating a menu item, this stores the previous nav_menu_term_id
 109       * which ensures that we can apply the proper filters.
 110       *
 111       * @since 4.3.0
 112       * @var int
 113       */
 114      public $original_nav_menu_term_id;
 115  
 116      /**
 117       * Whether or not update() was called.
 118       *
 119       * @since 4.3.0
 120       * @var bool
 121       */
 122      protected $is_updated = false;
 123  
 124      /**
 125       * Status for calling the update method, used in customize_save_response filter.
 126       *
 127       * See {@see 'customize_save_response'}.
 128       *
 129       * When status is inserted, the placeholder post ID is stored in $previous_post_id.
 130       * When status is error, the error is stored in $update_error.
 131       *
 132       * @since 4.3.0
 133       * @var string updated|inserted|deleted|error
 134       *
 135       * @see WP_Customize_Nav_Menu_Item_Setting::update()
 136       * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
 137       */
 138      public $update_status;
 139  
 140      /**
 141       * Any error object returned by wp_update_nav_menu_item() when setting is updated.
 142       *
 143       * @since 4.3.0
 144       * @var WP_Error
 145       *
 146       * @see WP_Customize_Nav_Menu_Item_Setting::update()
 147       * @see WP_Customize_Nav_Menu_Item_Setting::amend_customize_save_response()
 148       */
 149      public $update_error;
 150  
 151      /**
 152       * Constructor.
 153       *
 154       * Any supplied $args override class property defaults.
 155       *
 156       * @since 4.3.0
 157       *
 158       * @param WP_Customize_Manager $manager Bootstrap Customizer instance.
 159       * @param string               $id      An specific ID of the setting. Can be a
 160       *                                      theme mod or option name.
 161       * @param array                $args    Optional. Setting arguments.
 162       *
 163       * @throws Exception If $id is not valid for this setting type.
 164       */
 165  	public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) {
 166          if ( empty( $manager->nav_menus ) ) {
 167              throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' );
 168          }
 169  
 170          if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) {
 171              throw new Exception( "Illegal widget setting ID: $id" );
 172          }
 173  
 174          $this->post_id = intval( $matches['id'] );
 175          add_action( 'wp_update_nav_menu_item', array( $this, 'flush_cached_value' ), 10, 2 );
 176  
 177          parent::__construct( $manager, $id, $args );
 178  
 179          // Ensure that an initially-supplied value is valid.
 180          if ( isset( $this->value ) ) {
 181              $this->populate_value();
 182              foreach ( array_diff( array_keys( $this->default ), array_keys( $this->value ) ) as $missing ) {
 183                  throw new Exception( "Supplied nav_menu_item value missing property: $missing" );
 184              }
 185          }
 186  
 187      }
 188  
 189      /**
 190       * Clear the cached value when this nav menu item is updated.
 191       *
 192       * @since 4.3.0
 193       *
 194       * @param int $menu_id       The term ID for the menu.
 195       * @param int $menu_item_id  The post ID for the menu item.
 196       */
 197  	public function flush_cached_value( $menu_id, $menu_item_id ) {
 198          unset( $menu_id );
 199          if ( $menu_item_id === $this->post_id ) {
 200              $this->value = null;
 201          }
 202      }
 203  
 204      /**
 205       * Get the instance data for a given nav_menu_item setting.
 206       *
 207       * @since 4.3.0
 208       *
 209       * @see wp_setup_nav_menu_item()
 210       *
 211       * @return array|false Instance data array, or false if the item is marked for deletion.
 212       */
 213  	public function value() {
 214          if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) {
 215              $undefined  = new stdClass(); // Symbol.
 216              $post_value = $this->post_value( $undefined );
 217  
 218              if ( $undefined === $post_value ) {
 219                  $value = $this->_original_value;
 220              } else {
 221                  $value = $post_value;
 222              }
 223              if ( ! empty( $value ) && empty( $value['original_title'] ) ) {
 224                  $value['original_title'] = $this->get_original_title( (object) $value );
 225              }
 226          } elseif ( isset( $this->value ) ) {
 227              $value = $this->value;
 228          } else {
 229              $value = false;
 230  
 231              // Note that a ID of less than one indicates a nav_menu not yet inserted.
 232              if ( $this->post_id > 0 ) {
 233                  $post = get_post( $this->post_id );
 234                  if ( $post && self::POST_TYPE === $post->post_type ) {
 235                      $is_title_empty = empty( $post->post_title );
 236                      $value          = (array) wp_setup_nav_menu_item( $post );
 237                      if ( $is_title_empty ) {
 238                          $value['title'] = '';
 239                      }
 240                  }
 241              }
 242  
 243              if ( ! is_array( $value ) ) {
 244                  $value = $this->default;
 245              }
 246  
 247              // Cache the value for future calls to avoid having to re-call wp_setup_nav_menu_item().
 248              $this->value = $value;
 249              $this->populate_value();
 250              $value = $this->value;
 251          }
 252  
 253          if ( ! empty( $value ) && empty( $value['type_label'] ) ) {
 254              $value['type_label'] = $this->get_type_label( (object) $value );
 255          }
 256  
 257          return $value;
 258      }
 259  
 260      /**
 261       * Get original title.
 262       *
 263       * @since 4.7.0
 264       *
 265       * @param object $item Nav menu item.
 266       * @return string The original title.
 267       */
 268  	protected function get_original_title( $item ) {
 269          $original_title = '';
 270          if ( 'post_type' === $item->type && ! empty( $item->object_id ) ) {
 271              $original_object = get_post( $item->object_id );
 272              if ( $original_object ) {
 273                  /** This filter is documented in wp-includes/post-template.php */
 274                  $original_title = apply_filters( 'the_title', $original_object->post_title, $original_object->ID );
 275  
 276                  if ( '' === $original_title ) {
 277                      /* translators: %d: ID of a post */
 278                      $original_title = sprintf( __( '#%d (no title)' ), $original_object->ID );
 279                  }
 280              }
 281          } elseif ( 'taxonomy' === $item->type && ! empty( $item->object_id ) ) {
 282              $original_term_title = get_term_field( 'name', $item->object_id, $item->object, 'raw' );
 283              if ( ! is_wp_error( $original_term_title ) ) {
 284                  $original_title = $original_term_title;
 285              }
 286          } elseif ( 'post_type_archive' === $item->type ) {
 287              $original_object = get_post_type_object( $item->object );
 288              if ( $original_object ) {
 289                  $original_title = $original_object->labels->archives;
 290              }
 291          }
 292          $original_title = html_entity_decode( $original_title, ENT_QUOTES, get_bloginfo( 'charset' ) );
 293          return $original_title;
 294      }
 295  
 296      /**
 297       * Get type label.
 298       *
 299       * @since 4.7.0
 300       *
 301       * @param object $item Nav menu item.
 302       * @returns string The type label.
 303       */
 304  	protected function get_type_label( $item ) {
 305          if ( 'post_type' === $item->type ) {
 306              $object = get_post_type_object( $item->object );
 307              if ( $object ) {
 308                  $type_label = $object->labels->singular_name;
 309              } else {
 310                  $type_label = $item->object;
 311              }
 312          } elseif ( 'taxonomy' === $item->type ) {
 313              $object = get_taxonomy( $item->object );
 314              if ( $object ) {
 315                  $type_label = $object->labels->singular_name;
 316              } else {
 317                  $type_label = $item->object;
 318              }
 319          } elseif ( 'post_type_archive' === $item->type ) {
 320              $type_label = __( 'Post Type Archive' );
 321          } else {
 322              $type_label = __( 'Custom Link' );
 323          }
 324          return $type_label;
 325      }
 326  
 327      /**
 328       * Ensure that the value is fully populated with the necessary properties.
 329       *
 330       * Translates some properties added by wp_setup_nav_menu_item() and removes others.
 331       *
 332       * @since 4.3.0
 333       *
 334       * @see WP_Customize_Nav_Menu_Item_Setting::value()
 335       */
 336  	protected function populate_value() {
 337          if ( ! is_array( $this->value ) ) {
 338              return;
 339          }
 340  
 341          if ( isset( $this->value['menu_order'] ) ) {
 342              $this->value['position'] = $this->value['menu_order'];
 343              unset( $this->value['menu_order'] );
 344          }
 345          if ( isset( $this->value['post_status'] ) ) {
 346              $this->value['status'] = $this->value['post_status'];
 347              unset( $this->value['post_status'] );
 348          }
 349  
 350          if ( ! isset( $this->value['original_title'] ) ) {
 351              $this->value['original_title'] = $this->get_original_title( (object) $this->value );
 352          }
 353  
 354          if ( ! isset( $this->value['nav_menu_term_id'] ) && $this->post_id > 0 ) {
 355              $menus = wp_get_post_terms(
 356                  $this->post_id,
 357                  WP_Customize_Nav_Menu_Setting::TAXONOMY,
 358                  array(
 359                      'fields' => 'ids',
 360                  )
 361              );
 362              if ( ! empty( $menus ) ) {
 363                  $this->value['nav_menu_term_id'] = array_shift( $menus );
 364              } else {
 365                  $this->value['nav_menu_term_id'] = 0;
 366              }
 367          }
 368  
 369          foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) {
 370              if ( ! is_int( $this->value[ $key ] ) ) {
 371                  $this->value[ $key ] = intval( $this->value[ $key ] );
 372              }
 373          }
 374          foreach ( array( 'classes', 'xfn' ) as $key ) {
 375              if ( is_array( $this->value[ $key ] ) ) {
 376                  $this->value[ $key ] = implode( ' ', $this->value[ $key ] );
 377              }
 378          }
 379  
 380          if ( ! isset( $this->value['title'] ) ) {
 381              $this->value['title'] = '';
 382          }
 383  
 384          if ( ! isset( $this->value['_invalid'] ) ) {
 385              $this->value['_invalid'] = false;
 386              $is_known_invalid        = (
 387                  ( ( 'post_type' === $this->value['type'] || 'post_type_archive' === $this->value['type'] ) && ! post_type_exists( $this->value['object'] ) )
 388                  ||
 389                  ( 'taxonomy' === $this->value['type'] && ! taxonomy_exists( $this->value['object'] ) )
 390              );
 391              if ( $is_known_invalid ) {
 392                  $this->value['_invalid'] = true;
 393              }
 394          }
 395  
 396          // Remove remaining properties available on a setup nav_menu_item post object which aren't relevant to the setting value.
 397          $irrelevant_properties = array(
 398              'ID',
 399              'comment_count',
 400              'comment_status',
 401              'db_id',
 402              'filter',
 403              'guid',
 404              'ping_status',
 405              'pinged',
 406              'post_author',
 407              'post_content',
 408              'post_content_filtered',
 409              'post_date',
 410              'post_date_gmt',
 411              'post_excerpt',
 412              'post_mime_type',
 413              'post_modified',
 414              'post_modified_gmt',
 415              'post_name',
 416              'post_parent',
 417              'post_password',
 418              'post_title',
 419              'post_type',
 420              'to_ping',
 421          );
 422          foreach ( $irrelevant_properties as $property ) {
 423              unset( $this->value[ $property ] );
 424          }
 425      }
 426  
 427      /**
 428       * Handle previewing the setting.
 429       *
 430       * @since 4.3.0
 431       * @since 4.4.0 Added boolean return value.
 432       *
 433       * @see WP_Customize_Manager::post_value()
 434       *
 435       * @return bool False if method short-circuited due to no-op.
 436       */
 437  	public function preview() {
 438          if ( $this->is_previewed ) {
 439              return false;
 440          }
 441  
 442          $undefined      = new stdClass();
 443          $is_placeholder = ( $this->post_id < 0 );
 444          $is_dirty       = ( $undefined !== $this->post_value( $undefined ) );
 445          if ( ! $is_placeholder && ! $is_dirty ) {
 446              return false;
 447          }
 448  
 449          $this->is_previewed              = true;
 450          $this->_original_value           = $this->value();
 451          $this->original_nav_menu_term_id = $this->_original_value['nav_menu_term_id'];
 452          $this->_previewed_blog_id        = get_current_blog_id();
 453  
 454          add_filter( 'wp_get_nav_menu_items', array( $this, 'filter_wp_get_nav_menu_items' ), 10, 3 );
 455  
 456          $sort_callback = array( __CLASS__, 'sort_wp_get_nav_menu_items' );
 457          if ( ! has_filter( 'wp_get_nav_menu_items', $sort_callback ) ) {
 458              add_filter( 'wp_get_nav_menu_items', array( __CLASS__, 'sort_wp_get_nav_menu_items' ), 1000, 3 );
 459          }
 460  
 461          // @todo Add get_post_metadata filters for plugins to add their data.
 462  
 463          return true;
 464      }
 465  
 466      /**
 467       * Filters the wp_get_nav_menu_items() result to supply the previewed menu items.
 468       *
 469       * @since 4.3.0
 470       *
 471       * @see wp_get_nav_menu_items()
 472       *
 473       * @param WP_Post[] $items An array of menu item post objects.
 474       * @param WP_Term   $menu  The menu object.
 475       * @param array     $args  An array of arguments used to retrieve menu item objects.
 476       * @return WP_Post[] Array of menu item objects.
 477       */
 478  	public function filter_wp_get_nav_menu_items( $items, $menu, $args ) {
 479          $this_item                = $this->value();
 480          $current_nav_menu_term_id = null;
 481          if ( isset( $this_item['nav_menu_term_id'] ) ) {
 482              $current_nav_menu_term_id = $this_item['nav_menu_term_id'];
 483              unset( $this_item['nav_menu_term_id'] );
 484          }
 485  
 486          $should_filter = (
 487              $menu->term_id === $this->original_nav_menu_term_id
 488              ||
 489              $menu->term_id === $current_nav_menu_term_id
 490          );
 491          if ( ! $should_filter ) {
 492              return $items;
 493          }
 494  
 495          // Handle deleted menu item, or menu item moved to another menu.
 496          $should_remove = (
 497              false === $this_item
 498              ||
 499              ( isset( $this_item['_invalid'] ) && true === $this_item['_invalid'] )
 500              ||
 501              (
 502                  $this->original_nav_menu_term_id === $menu->term_id
 503                  &&
 504                  $current_nav_menu_term_id !== $this->original_nav_menu_term_id
 505              )
 506          );
 507          if ( $should_remove ) {
 508              $filtered_items = array();
 509              foreach ( $items as $item ) {
 510                  if ( $item->db_id !== $this->post_id ) {
 511                      $filtered_items[] = $item;
 512                  }
 513              }
 514              return $filtered_items;
 515          }
 516  
 517          $mutated       = false;
 518          $should_update = (
 519              is_array( $this_item )
 520              &&
 521              $current_nav_menu_term_id === $menu->term_id
 522          );
 523          if ( $should_update ) {
 524              foreach ( $items as $item ) {
 525                  if ( $item->db_id === $this->post_id ) {
 526                      foreach ( get_object_vars( $this->value_as_wp_post_nav_menu_item() ) as $key => $value ) {
 527                          $item->$key = $value;
 528                      }
 529                      $mutated = true;
 530                  }
 531              }
 532  
 533              // Not found so we have to append it..
 534              if ( ! $mutated ) {
 535                  $items[] = $this->value_as_wp_post_nav_menu_item();
 536              }
 537          }
 538  
 539          return $items;
 540      }
 541  
 542      /**
 543       * Re-apply the tail logic also applied on $items by wp_get_nav_menu_items().
 544       *
 545       * @since 4.3.0
 546       *
 547       * @see wp_get_nav_menu_items()
 548       *
 549       * @param WP_Post[] $items An array of menu item post objects.
 550       * @param WP_Term   $menu  The menu object.
 551       * @param array     $args  An array of arguments used to retrieve menu item objects.
 552       * @return WP_Post[] Array of menu item objects.
 553       */
 554  	public static function sort_wp_get_nav_menu_items( $items, $menu, $args ) {
 555          // @todo We should probably re-apply some constraints imposed by $args.
 556          unset( $args['include'] );
 557  
 558          // Remove invalid items only in front end.
 559          if ( ! is_admin() ) {
 560              $items = array_filter( $items, '_is_valid_nav_menu_item' );
 561          }
 562  
 563          if ( ARRAY_A === $args['output'] ) {
 564              $items = wp_list_sort(
 565                  $items,
 566                  array(
 567                      $args['output_key'] => 'ASC',
 568                  )
 569              );
 570              $i     = 1;
 571  
 572              foreach ( $items as $k => $item ) {
 573                  $items[ $k ]->{$args['output_key']} = $i++;
 574              }
 575          }
 576  
 577          return $items;
 578      }
 579  
 580      /**
 581       * Get the value emulated into a WP_Post and set up as a nav_menu_item.
 582       *
 583       * @since 4.3.0
 584       *
 585       * @return WP_Post With wp_setup_nav_menu_item() applied.
 586       */
 587  	public function value_as_wp_post_nav_menu_item() {
 588          $item = (object) $this->value();
 589          unset( $item->nav_menu_term_id );
 590  
 591          $item->post_status = $item->status;
 592          unset( $item->status );
 593  
 594          $item->post_type  = 'nav_menu_item';
 595          $item->menu_order = $item->position;
 596          unset( $item->position );
 597  
 598          if ( empty( $item->original_title ) ) {
 599              $item->original_title = $this->get_original_title( $item );
 600          }
 601          if ( empty( $item->title ) && ! empty( $item->original_title ) ) {
 602              $item->title = $item->original_title;
 603          }
 604          if ( $item->title ) {
 605              $item->post_title = $item->title;
 606          }
 607  
 608          $item->ID    = $this->post_id;
 609          $item->db_id = $this->post_id;
 610          $post        = new WP_Post( (object) $item );
 611  
 612          if ( empty( $post->post_author ) ) {
 613              $post->post_author = get_current_user_id();
 614          }
 615  
 616          if ( ! isset( $post->type_label ) ) {
 617              $post->type_label = $this->get_type_label( $post );
 618          }
 619  
 620          // Ensure nav menu item URL is set according to linked object.
 621          if ( 'post_type' === $post->type && ! empty( $post->object_id ) ) {
 622              $post->url = get_permalink( $post->object_id );
 623          } elseif ( 'taxonomy' === $post->type && ! empty( $post->object ) && ! empty( $post->object_id ) ) {
 624              $post->url = get_term_link( (int) $post->object_id, $post->object );
 625          } elseif ( 'post_type_archive' === $post->type && ! empty( $post->object ) ) {
 626              $post->url = get_post_type_archive_link( $post->object );
 627          }
 628          if ( is_wp_error( $post->url ) ) {
 629              $post->url = '';
 630          }
 631  
 632          /** This filter is documented in wp-includes/nav-menu.php */
 633          $post->attr_title = apply_filters( 'nav_menu_attr_title', $post->attr_title );
 634  
 635          /** This filter is documented in wp-includes/nav-menu.php */
 636          $post->description = apply_filters( 'nav_menu_description', wp_trim_words( $post->description, 200 ) );
 637  
 638          /** This filter is documented in wp-includes/nav-menu.php */
 639          $post = apply_filters( 'wp_setup_nav_menu_item', $post );
 640  
 641          return $post;
 642      }
 643  
 644      /**
 645       * Sanitize an input.
 646       *
 647       * Note that parent::sanitize() erroneously does wp_unslash() on $value, but
 648       * we remove that in this override.
 649       *
 650       * @since 4.3.0
 651       *
 652       * @param array $menu_item_value The value to sanitize.
 653       * @return array|false|null|WP_Error Null or WP_Error if an input isn't valid. False if it is marked for deletion.
 654       *                                   Otherwise the sanitized value.
 655       */
 656  	public function sanitize( $menu_item_value ) {
 657          // Menu is marked for deletion.
 658          if ( false === $menu_item_value ) {
 659              return $menu_item_value;
 660          }
 661  
 662          // Invalid.
 663          if ( ! is_array( $menu_item_value ) ) {
 664              return null;
 665          }
 666  
 667          $default                     = array(
 668              'object_id'        => 0,
 669              'object'           => '',
 670              'menu_item_parent' => 0,
 671              'position'         => 0,
 672              'type'             => 'custom',
 673              'title'            => '',
 674              'url'              => '',
 675              'target'           => '',
 676              'attr_title'       => '',
 677              'description'      => '',
 678              'classes'          => '',
 679              'xfn'              => '',
 680              'status'           => 'publish',
 681              'original_title'   => '',
 682              'nav_menu_term_id' => 0,
 683              '_invalid'         => false,
 684          );
 685          $menu_item_value             = array_merge( $default, $menu_item_value );
 686          $menu_item_value             = wp_array_slice_assoc( $menu_item_value, array_keys( $default ) );
 687          $menu_item_value['position'] = intval( $menu_item_value['position'] );
 688  
 689          foreach ( array( 'object_id', 'menu_item_parent', 'nav_menu_term_id' ) as $key ) {
 690              // Note we need to allow negative-integer IDs for previewed objects not inserted yet.
 691              $menu_item_value[ $key ] = intval( $menu_item_value[ $key ] );
 692          }
 693  
 694          foreach ( array( 'type', 'object', 'target' ) as $key ) {
 695              $menu_item_value[ $key ] = sanitize_key( $menu_item_value[ $key ] );
 696          }
 697  
 698          foreach ( array( 'xfn', 'classes' ) as $key ) {
 699              $value = $menu_item_value[ $key ];
 700              if ( ! is_array( $value ) ) {
 701                  $value = explode( ' ', $value );
 702              }
 703              $menu_item_value[ $key ] = implode( ' ', array_map( 'sanitize_html_class', $value ) );
 704          }
 705  
 706          $menu_item_value['original_title'] = sanitize_text_field( $menu_item_value['original_title'] );
 707  
 708          // Apply the same filters as when calling wp_insert_post().
 709  
 710          /** This filter is documented in wp-includes/post.php */
 711          $menu_item_value['title'] = wp_unslash( apply_filters( 'title_save_pre', wp_slash( $menu_item_value['title'] ) ) );
 712  
 713          /** This filter is documented in wp-includes/post.php */
 714          $menu_item_value['attr_title'] = wp_unslash( apply_filters( 'excerpt_save_pre', wp_slash( $menu_item_value['attr_title'] ) ) );
 715  
 716          /** This filter is documented in wp-includes/post.php */
 717          $menu_item_value['description'] = wp_unslash( apply_filters( 'content_save_pre', wp_slash( $menu_item_value['description'] ) ) );
 718  
 719          if ( '' !== $menu_item_value['url'] ) {
 720              $menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] );
 721              if ( '' === $menu_item_value['url'] ) {
 722                  return new WP_Error( 'invalid_url', __( 'Invalid URL.' ) ); // Fail sanitization if URL is invalid.
 723              }
 724          }
 725          if ( 'publish' !== $menu_item_value['status'] ) {
 726              $menu_item_value['status'] = 'draft';
 727          }
 728  
 729          $menu_item_value['_invalid'] = (bool) $menu_item_value['_invalid'];
 730  
 731          /** This filter is documented in wp-includes/class-wp-customize-setting.php */
 732          return apply_filters( "customize_sanitize_{$this->id}", $menu_item_value, $this );
 733      }
 734  
 735      /**
 736       * Creates/updates the nav_menu_item post for this setting.
 737       *
 738       * Any created menu items will have their assigned post IDs exported to the client
 739       * via the {@see 'customize_save_response'} filter. Likewise, any errors will be
 740       * exported to the client via the customize_save_response() filter.
 741       *
 742       * To delete a menu, the client can send false as the value.
 743       *
 744       * @since 4.3.0
 745       *
 746       * @see wp_update_nav_menu_item()
 747       *
 748       * @param array|false $value The menu item array to update. If false, then the menu item will be deleted
 749       *                           entirely. See WP_Customize_Nav_Menu_Item_Setting::$default for what the value
 750       *                           should consist of.
 751       * @return null|void
 752       */
 753  	protected function update( $value ) {
 754          if ( $this->is_updated ) {
 755              return;
 756          }
 757  
 758          $this->is_updated = true;
 759          $is_placeholder   = ( $this->post_id < 0 );
 760          $is_delete        = ( false === $value );
 761  
 762          // Update the cached value.
 763          $this->value = $value;
 764  
 765          add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) );
 766  
 767          if ( $is_delete ) {
 768              // If the current setting post is a placeholder, a delete request is a no-op.
 769              if ( $is_placeholder ) {
 770                  $this->update_status = 'deleted';
 771              } else {
 772                  $r = wp_delete_post( $this->post_id, true );
 773  
 774                  if ( false === $r ) {
 775                      $this->update_error  = new WP_Error( 'delete_failure' );
 776                      $this->update_status = 'error';
 777                  } else {
 778                      $this->update_status = 'deleted';
 779                  }
 780                  // @todo send back the IDs for all associated nav menu items deleted, so these settings (and controls) can be removed from Customizer?
 781              }
 782          } else {
 783  
 784              // Handle saving menu items for menus that are being newly-created.
 785              if ( $value['nav_menu_term_id'] < 0 ) {
 786                  $nav_menu_setting_id = sprintf( 'nav_menu[%s]', $value['nav_menu_term_id'] );
 787                  $nav_menu_setting    = $this->manager->get_setting( $nav_menu_setting_id );
 788  
 789                  if ( ! $nav_menu_setting || ! ( $nav_menu_setting instanceof WP_Customize_Nav_Menu_Setting ) ) {
 790                      $this->update_status = 'error';
 791                      $this->update_error  = new WP_Error( 'unexpected_nav_menu_setting' );
 792                      return;
 793                  }
 794  
 795                  if ( false === $nav_menu_setting->save() ) {
 796                      $this->update_status = 'error';
 797                      $this->update_error  = new WP_Error( 'nav_menu_setting_failure' );
 798                      return;
 799                  }
 800  
 801                  if ( $nav_menu_setting->previous_term_id !== intval( $value['nav_menu_term_id'] ) ) {
 802                      $this->update_status = 'error';
 803                      $this->update_error  = new WP_Error( 'unexpected_previous_term_id' );
 804                      return;
 805                  }
 806  
 807                  $value['nav_menu_term_id'] = $nav_menu_setting->term_id;
 808              }
 809  
 810              // Handle saving a nav menu item that is a child of a nav menu item being newly-created.
 811              if ( $value['menu_item_parent'] < 0 ) {
 812                  $parent_nav_menu_item_setting_id = sprintf( 'nav_menu_item[%s]', $value['menu_item_parent'] );
 813                  $parent_nav_menu_item_setting    = $this->manager->get_setting( $parent_nav_menu_item_setting_id );
 814  
 815                  if ( ! $parent_nav_menu_item_setting || ! ( $parent_nav_menu_item_setting instanceof WP_Customize_Nav_Menu_Item_Setting ) ) {
 816                      $this->update_status = 'error';
 817                      $this->update_error  = new WP_Error( 'unexpected_nav_menu_item_setting' );
 818                      return;
 819                  }
 820  
 821                  if ( false === $parent_nav_menu_item_setting->save() ) {
 822                      $this->update_status = 'error';
 823                      $this->update_error  = new WP_Error( 'nav_menu_item_setting_failure' );
 824                      return;
 825                  }
 826  
 827                  if ( $parent_nav_menu_item_setting->previous_post_id !== intval( $value['menu_item_parent'] ) ) {
 828                      $this->update_status = 'error';
 829                      $this->update_error  = new WP_Error( 'unexpected_previous_post_id' );
 830                      return;
 831                  }
 832  
 833                  $value['menu_item_parent'] = $parent_nav_menu_item_setting->post_id;
 834              }
 835  
 836              // Insert or update menu.
 837              $menu_item_data = array(
 838                  'menu-item-object-id'   => $value['object_id'],
 839                  'menu-item-object'      => $value['object'],
 840                  'menu-item-parent-id'   => $value['menu_item_parent'],
 841                  'menu-item-position'    => $value['position'],
 842                  'menu-item-type'        => $value['type'],
 843                  'menu-item-title'       => $value['title'],
 844                  'menu-item-url'         => $value['url'],
 845                  'menu-item-description' => $value['description'],
 846                  'menu-item-attr-title'  => $value['attr_title'],
 847                  'menu-item-target'      => $value['target'],
 848                  'menu-item-classes'     => $value['classes'],
 849                  'menu-item-xfn'         => $value['xfn'],
 850                  'menu-item-status'      => $value['status'],
 851              );
 852  
 853              $r = wp_update_nav_menu_item(
 854                  $value['nav_menu_term_id'],
 855                  $is_placeholder ? 0 : $this->post_id,
 856                  wp_slash( $menu_item_data )
 857              );
 858  
 859              if ( is_wp_error( $r ) ) {
 860                  $this->update_status = 'error';
 861                  $this->update_error  = $r;
 862              } else {
 863                  if ( $is_placeholder ) {
 864                      $this->previous_post_id = $this->post_id;
 865                      $this->post_id          = $r;
 866                      $this->update_status    = 'inserted';
 867                  } else {
 868                      $this->update_status = 'updated';
 869                  }
 870              }
 871          }
 872  
 873      }
 874  
 875      /**
 876       * Export data for the JS client.
 877       *
 878       * @since 4.3.0
 879       *
 880       * @see WP_Customize_Nav_Menu_Item_Setting::update()
 881       *
 882       * @param array $data Additional information passed back to the 'saved' event on `wp.customize`.
 883       * @return array Save response data.
 884       */
 885  	public function amend_customize_save_response( $data ) {
 886          if ( ! isset( $data['nav_menu_item_updates'] ) ) {
 887              $data['nav_menu_item_updates'] = array();
 888          }
 889  
 890          $data['nav_menu_item_updates'][] = array(
 891              'post_id'          => $this->post_id,
 892              'previous_post_id' => $this->previous_post_id,
 893              'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
 894              'status'           => $this->update_status,
 895          );
 896          return $data;
 897      }
 898  }


Generated: Mon Jul 22 01:00:03 2019 Cross-referenced by PHPXref 0.7.1