wp/wp-includes/class-wp-navigation-fallback.php
changeset 21 48c4eec2b7e6
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
       
     1 <?php
       
     2 /**
       
     3  * WP_Navigation_Fallback class
       
     4  *
       
     5  * Manages fallback behavior for Navigation menus.
       
     6  *
       
     7  * @package WordPress
       
     8  * @subpackage Navigation
       
     9  * @since 6.3.0
       
    10  */
       
    11 
       
    12 /**
       
    13  * Manages fallback behavior for Navigation menus.
       
    14  *
       
    15  * @access public
       
    16  * @since 6.3.0
       
    17  */
       
    18 class WP_Navigation_Fallback {
       
    19 
       
    20 	/**
       
    21 	 * Updates the wp_navigation custom post type schema, in order to expose
       
    22 	 * additional fields in the embeddable links of WP_REST_Navigation_Fallback_Controller.
       
    23 	 *
       
    24 	 * The Navigation Fallback endpoint may embed the full Navigation Menu object
       
    25 	 * into the response as the `self` link. By default, the Posts Controller
       
    26 	 * will only expose a limited subset of fields but the editor requires
       
    27 	 * additional fields to be available in order to utilize the menu.
       
    28 	 *
       
    29 	 * Used with the `rest_wp_navigation_item_schema` hook.
       
    30 	 *
       
    31 	 * @since 6.4.0
       
    32 	 *
       
    33 	 * @param array $schema The schema for the `wp_navigation` post.
       
    34 	 * @return array The modified schema.
       
    35 	 */
       
    36 	public static function update_wp_navigation_post_schema( $schema ) {
       
    37 		// Expose top level fields.
       
    38 		$schema['properties']['status']['context']  = array_merge( $schema['properties']['status']['context'], array( 'embed' ) );
       
    39 		$schema['properties']['content']['context'] = array_merge( $schema['properties']['content']['context'], array( 'embed' ) );
       
    40 
       
    41 		/*
       
    42 		 * Exposes sub properties of content field.
       
    43 		 * These sub properties aren't exposed by the posts controller by default,
       
    44 		 * for requests where context is `embed`.
       
    45 		 *
       
    46 		 * @see WP_REST_Posts_Controller::get_item_schema()
       
    47 		 */
       
    48 		$schema['properties']['content']['properties']['raw']['context']           = array_merge( $schema['properties']['content']['properties']['raw']['context'], array( 'embed' ) );
       
    49 		$schema['properties']['content']['properties']['rendered']['context']      = array_merge( $schema['properties']['content']['properties']['rendered']['context'], array( 'embed' ) );
       
    50 		$schema['properties']['content']['properties']['block_version']['context'] = array_merge( $schema['properties']['content']['properties']['block_version']['context'], array( 'embed' ) );
       
    51 
       
    52 		/*
       
    53 		 * Exposes sub properties of title field.
       
    54 		 * These sub properties aren't exposed by the posts controller by default,
       
    55 		 * for requests where context is `embed`.
       
    56 		 *
       
    57 		 * @see WP_REST_Posts_Controller::get_item_schema()
       
    58 		 */
       
    59 		$schema['properties']['title']['properties']['raw']['context'] = array_merge( $schema['properties']['title']['properties']['raw']['context'], array( 'embed' ) );
       
    60 
       
    61 		return $schema;
       
    62 	}
       
    63 
       
    64 	/**
       
    65 	 * Gets (and/or creates) an appropriate fallback Navigation Menu.
       
    66 	 *
       
    67 	 * @since 6.3.0
       
    68 	 *
       
    69 	 * @return WP_Post|null the fallback Navigation Post or null.
       
    70 	 */
       
    71 	public static function get_fallback() {
       
    72 		/**
       
    73 		 * Filters whether or not a fallback should be created.
       
    74 		 *
       
    75 		 * @since 6.3.0
       
    76 		 *
       
    77 		 * @param bool $create Whether to create a fallback navigation menu. Default true.
       
    78 		 */
       
    79 		$should_create_fallback = apply_filters( 'wp_navigation_should_create_fallback', true );
       
    80 
       
    81 		$fallback = static::get_most_recently_published_navigation();
       
    82 
       
    83 		if ( $fallback || ! $should_create_fallback ) {
       
    84 			return $fallback;
       
    85 		}
       
    86 
       
    87 		$fallback = static::create_classic_menu_fallback();
       
    88 
       
    89 		if ( $fallback && ! is_wp_error( $fallback ) ) {
       
    90 			// Return the newly created fallback post object which will now be the most recently created navigation menu.
       
    91 			return $fallback instanceof WP_Post ? $fallback : static::get_most_recently_published_navigation();
       
    92 		}
       
    93 
       
    94 		$fallback = static::create_default_fallback();
       
    95 
       
    96 		if ( $fallback && ! is_wp_error( $fallback ) ) {
       
    97 			// Return the newly created fallback post object which will now be the most recently created navigation menu.
       
    98 			return $fallback instanceof WP_Post ? $fallback : static::get_most_recently_published_navigation();
       
    99 		}
       
   100 
       
   101 		return null;
       
   102 	}
       
   103 
       
   104 	/**
       
   105 	 * Finds the most recently published `wp_navigation` post type.
       
   106 	 *
       
   107 	 * @since 6.3.0
       
   108 	 *
       
   109 	 * @return WP_Post|null the first non-empty Navigation or null.
       
   110 	 */
       
   111 	private static function get_most_recently_published_navigation() {
       
   112 
       
   113 		$parsed_args = array(
       
   114 			'post_type'              => 'wp_navigation',
       
   115 			'no_found_rows'          => true,
       
   116 			'update_post_meta_cache' => false,
       
   117 			'update_post_term_cache' => false,
       
   118 			'order'                  => 'DESC',
       
   119 			'orderby'                => 'date',
       
   120 			'post_status'            => 'publish',
       
   121 			'posts_per_page'         => 1,
       
   122 		);
       
   123 
       
   124 		$navigation_post = new WP_Query( $parsed_args );
       
   125 
       
   126 		if ( count( $navigation_post->posts ) > 0 ) {
       
   127 			return $navigation_post->posts[0];
       
   128 		}
       
   129 
       
   130 		return null;
       
   131 	}
       
   132 
       
   133 	/**
       
   134 	 * Creates a Navigation Menu post from a Classic Menu.
       
   135 	 *
       
   136 	 * @since 6.3.0
       
   137 	 *
       
   138 	 * @return int|WP_Error The post ID of the default fallback menu or a WP_Error object.
       
   139 	 */
       
   140 	private static function create_classic_menu_fallback() {
       
   141 		// See if we have a classic menu.
       
   142 		$classic_nav_menu = static::get_fallback_classic_menu();
       
   143 
       
   144 		if ( ! $classic_nav_menu ) {
       
   145 			return new WP_Error( 'no_classic_menus', __( 'No Classic Menus found.' ) );
       
   146 		}
       
   147 
       
   148 		// If there is a classic menu then convert it to blocks.
       
   149 		$classic_nav_menu_blocks = WP_Classic_To_Block_Menu_Converter::convert( $classic_nav_menu );
       
   150 
       
   151 		if ( is_wp_error( $classic_nav_menu_blocks ) ) {
       
   152 			return $classic_nav_menu_blocks;
       
   153 		}
       
   154 
       
   155 		if ( empty( $classic_nav_menu_blocks ) ) {
       
   156 			return new WP_Error( 'cannot_convert_classic_menu', __( 'Unable to convert Classic Menu to blocks.' ) );
       
   157 		}
       
   158 
       
   159 		// Create a new navigation menu from the classic menu.
       
   160 		$classic_menu_fallback = wp_insert_post(
       
   161 			array(
       
   162 				'post_content' => $classic_nav_menu_blocks,
       
   163 				'post_title'   => $classic_nav_menu->name,
       
   164 				'post_name'    => $classic_nav_menu->slug,
       
   165 				'post_status'  => 'publish',
       
   166 				'post_type'    => 'wp_navigation',
       
   167 			),
       
   168 			true // So that we can check whether the result is an error.
       
   169 		);
       
   170 
       
   171 		return $classic_menu_fallback;
       
   172 	}
       
   173 
       
   174 	/**
       
   175 	 * Determines the most appropriate classic navigation menu to use as a fallback.
       
   176 	 *
       
   177 	 * @since 6.3.0
       
   178 	 *
       
   179 	 * @return WP_Term|null The most appropriate classic navigation menu to use as a fallback.
       
   180 	 */
       
   181 	private static function get_fallback_classic_menu() {
       
   182 		$classic_nav_menus = wp_get_nav_menus();
       
   183 
       
   184 		if ( ! $classic_nav_menus || is_wp_error( $classic_nav_menus ) ) {
       
   185 			return null;
       
   186 		}
       
   187 
       
   188 		$nav_menu = static::get_nav_menu_at_primary_location();
       
   189 
       
   190 		if ( $nav_menu ) {
       
   191 			return $nav_menu;
       
   192 		}
       
   193 
       
   194 		$nav_menu = static::get_nav_menu_with_primary_slug( $classic_nav_menus );
       
   195 
       
   196 		if ( $nav_menu ) {
       
   197 			return $nav_menu;
       
   198 		}
       
   199 
       
   200 		return static::get_most_recently_created_nav_menu( $classic_nav_menus );
       
   201 	}
       
   202 
       
   203 
       
   204 	/**
       
   205 	 * Sorts the classic menus and returns the most recently created one.
       
   206 	 *
       
   207 	 * @since 6.3.0
       
   208 	 *
       
   209 	 * @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects.
       
   210 	 * @return WP_Term The most recently created classic nav menu.
       
   211 	 */
       
   212 	private static function get_most_recently_created_nav_menu( $classic_nav_menus ) {
       
   213 		usort(
       
   214 			$classic_nav_menus,
       
   215 			static function ( $a, $b ) {
       
   216 				return $b->term_id - $a->term_id;
       
   217 			}
       
   218 		);
       
   219 
       
   220 		return $classic_nav_menus[0];
       
   221 	}
       
   222 
       
   223 	/**
       
   224 	 * Returns the classic menu with the slug `primary` if it exists.
       
   225 	 *
       
   226 	 * @since 6.3.0
       
   227 	 *
       
   228 	 * @param WP_Term[] $classic_nav_menus Array of classic nav menu term objects.
       
   229 	 * @return WP_Term|null The classic nav menu with the slug `primary` or null.
       
   230 	 */
       
   231 	private static function get_nav_menu_with_primary_slug( $classic_nav_menus ) {
       
   232 		foreach ( $classic_nav_menus as $classic_nav_menu ) {
       
   233 			if ( 'primary' === $classic_nav_menu->slug ) {
       
   234 				return $classic_nav_menu;
       
   235 			}
       
   236 		}
       
   237 
       
   238 		return null;
       
   239 	}
       
   240 
       
   241 
       
   242 	/**
       
   243 	 * Gets the classic menu assigned to the `primary` navigation menu location
       
   244 	 * if it exists.
       
   245 	 *
       
   246 	 * @since 6.3.0
       
   247 	 *
       
   248 	 * @return WP_Term|null The classic nav menu assigned to the `primary` location or null.
       
   249 	 */
       
   250 	private static function get_nav_menu_at_primary_location() {
       
   251 		$locations = get_nav_menu_locations();
       
   252 
       
   253 		if ( isset( $locations['primary'] ) ) {
       
   254 			$primary_menu = wp_get_nav_menu_object( $locations['primary'] );
       
   255 
       
   256 			if ( $primary_menu ) {
       
   257 				return $primary_menu;
       
   258 			}
       
   259 		}
       
   260 
       
   261 		return null;
       
   262 	}
       
   263 
       
   264 	/**
       
   265 	 * Creates a default Navigation Block Menu fallback.
       
   266 	 *
       
   267 	 * @since 6.3.0
       
   268 	 *
       
   269 	 * @return int|WP_Error The post ID of the default fallback menu or a WP_Error object.
       
   270 	 */
       
   271 	private static function create_default_fallback() {
       
   272 
       
   273 		$default_blocks = static::get_default_fallback_blocks();
       
   274 
       
   275 		// Create a new navigation menu from the fallback blocks.
       
   276 		$default_fallback = wp_insert_post(
       
   277 			array(
       
   278 				'post_content' => $default_blocks,
       
   279 				'post_title'   => _x( 'Navigation', 'Title of a Navigation menu' ),
       
   280 				'post_name'    => 'navigation',
       
   281 				'post_status'  => 'publish',
       
   282 				'post_type'    => 'wp_navigation',
       
   283 			),
       
   284 			true // So that we can check whether the result is an error.
       
   285 		);
       
   286 
       
   287 		return $default_fallback;
       
   288 	}
       
   289 
       
   290 	/**
       
   291 	 * Gets the rendered markup for the default fallback blocks.
       
   292 	 *
       
   293 	 * @since 6.3.0
       
   294 	 *
       
   295 	 * @return string default blocks markup to use a the fallback.
       
   296 	 */
       
   297 	private static function get_default_fallback_blocks() {
       
   298 		$registry = WP_Block_Type_Registry::get_instance();
       
   299 
       
   300 		// If `core/page-list` is not registered then use empty blocks.
       
   301 		return $registry->is_registered( 'core/page-list' ) ? '<!-- wp:page-list /-->' : '';
       
   302 	}
       
   303 }