| 1 |
<?php |
| 2 |
namespace WPGraphQL\Data\Connection; |
| 3 |
|
| 4 |
use GraphQL\Type\Definition\ResolveInfo; |
| 5 |
use WPGraphQL\AppContext; |
| 6 |
use WPGraphQL\Utils\Utils; |
| 7 |
|
| 8 |
/** |
| 9 |
* Class MenuItemConnectionResolver |
| 10 |
* |
| 11 |
* @package WPGraphQL\Data\Connection |
| 12 |
*/ |
| 13 |
class MenuItemConnectionResolver extends PostObjectConnectionResolver { |
| 14 |
|
| 15 |
/** |
| 16 |
* {@inheritDoc} |
| 17 |
*/ |
| 18 |
public function __construct( $source, array $args, AppContext $context, ResolveInfo $info ) { |
| 19 |
parent::__construct( $source, $args, $context, $info, 'nav_menu_item' ); |
| 20 |
} |
| 21 |
|
| 22 |
/** |
| 23 |
* {@inheritDoc} |
| 24 |
*/ |
| 25 |
protected function prepare_query_args( array $args ): array { |
| 26 |
/** |
| 27 |
* Prepare for later use |
| 28 |
*/ |
| 29 |
$last = ! empty( $args['last'] ) ? $args['last'] : null; |
| 30 |
|
| 31 |
$menu_locations = get_theme_mod( 'nav_menu_locations' ); |
| 32 |
|
| 33 |
$query_args = parent::prepare_query_args( $args ); |
| 34 |
$query_args['orderby'] = 'menu_order'; |
| 35 |
$query_args['order'] = isset( $last ) ? 'DESC' : 'ASC'; |
| 36 |
|
| 37 |
if ( isset( $args['where']['parentDatabaseId'] ) ) { |
| 38 |
$query_args['meta_key'] = '_menu_item_menu_item_parent'; |
| 39 |
$query_args['meta_value'] = (int) $args['where']['parentDatabaseId']; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value |
| 40 |
} |
| 41 |
|
| 42 |
if ( ! empty( $args['where']['parentId'] ) || ( isset( $args['where']['parentId'] ) && 0 === (int) $args['where']['parentId'] ) ) { |
| 43 |
$query_args['meta_key'] = '_menu_item_menu_item_parent'; |
| 44 |
$query_args['meta_value'] = $args['where']['parentId']; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value |
| 45 |
} |
| 46 |
|
| 47 |
// Get unique list of location term IDs as the default limitation of locations to allow public queries for. |
| 48 |
// Public queries should only be allowed to query for Menu Items assigned to a Menu Location. |
| 49 |
$locations = is_array( $menu_locations ) && ! empty( $menu_locations ) ? array_unique( array_values( $menu_locations ) ) : []; |
| 50 |
|
| 51 |
// Whether a location was explicitly requested via the `location` where arg. |
| 52 |
$has_explicit_location = ! empty( $args['where']['location'] ); |
| 53 |
|
| 54 |
// If the location argument is set, set the argument to the input argument |
| 55 |
if ( $has_explicit_location ) { |
| 56 |
$locations = isset( $menu_locations[ $args['where']['location'] ] ) ? [ $menu_locations[ $args['where']['location'] ] ] : []; // We use an empty array to prevent fetching all media items if the location has no items assigned. |
| 57 |
|
| 58 |
} elseif ( current_user_can( 'edit_theme_options' ) ) { |
| 59 |
// If the $locations are NOT set, let a user with proper capability query all menu items. |
| 60 |
$locations = null; |
| 61 |
} |
| 62 |
|
| 63 |
/** |
| 64 |
* Filter whether the menu item connection should be restricted to items |
| 65 |
* assigned to a registered menu location. |
| 66 |
* |
| 67 |
* By default, public (unauthenticated) requests are limited to menu items |
| 68 |
* assigned to a menu location. Return `false` to allow public requests to |
| 69 |
* query menu items belonging to menus that are not assigned to any location |
| 70 |
* (e.g. when using the "Make Menus and Menu Items public" recipe, which also |
| 71 |
* flips the MenuItem Model privacy gate via `graphql_data_is_private`). |
| 72 |
* |
| 73 |
* An explicit `location` where arg always restricts to that location and is |
| 74 |
* unaffected by this filter. |
| 75 |
* |
| 76 |
* @param bool $restrict_to_locations Whether to restrict the connection to assigned menu locations. Default true. |
| 77 |
* @param int[]|null $locations The menu location term IDs the connection is being restricted to, or null when unrestricted (privileged user). |
| 78 |
* @param array<string,mixed> $args The GraphQL args passed to the resolver. |
| 79 |
* @param array<string,mixed> $unfiltered_args Array of arguments input in the field as part of the GraphQL query. |
| 80 |
* |
| 81 |
* @hookGroup connections |
| 82 |
* @since 2.18.0 |
| 83 |
*/ |
| 84 |
$restrict_to_locations = $has_explicit_location || apply_filters( 'graphql_menu_item_connection_restrict_to_locations', true, $locations, $args, $this->get_unfiltered_args() ); |
| 85 |
|
| 86 |
// Only query for menu items in assigned locations. |
| 87 |
if ( $restrict_to_locations && isset( $locations ) ) { |
| 88 |
|
| 89 |
// unset the location arg |
| 90 |
// we don't need this passed as a taxonomy parameter to wp_query |
| 91 |
unset( $query_args['location'] ); |
| 92 |
|
| 93 |
$query_args['tax_query'][] = [ |
| 94 |
'taxonomy' => 'nav_menu', |
| 95 |
'field' => 'term_id', |
| 96 |
'terms' => $locations, |
| 97 |
'include_children' => false, |
| 98 |
'operator' => 'IN', |
| 99 |
]; |
| 100 |
} |
| 101 |
|
| 102 |
return $query_args; |
| 103 |
} |
| 104 |
|
| 105 |
/** |
| 106 |
* {@inheritDoc} |
| 107 |
*/ |
| 108 |
protected function prepare_args( array $args ): array { |
| 109 |
if ( ! empty( $args['where'] ) ) { |
| 110 |
// Ensure all IDs are converted to database IDs. |
| 111 |
foreach ( $args['where'] as $input_key => $input_value ) { |
| 112 |
if ( empty( $input_value ) ) { |
| 113 |
continue; |
| 114 |
} |
| 115 |
|
| 116 |
switch ( $input_key ) { |
| 117 |
case 'parentId': |
| 118 |
$args['where'][ $input_key ] = Utils::get_database_id_from_id( $input_value ); |
| 119 |
break; |
| 120 |
} |
| 121 |
} |
| 122 |
} |
| 123 |
|
| 124 |
/** |
| 125 |
* |
| 126 |
* Filters the GraphQL args before they are used in get_query_args(). |
| 127 |
* |
| 128 |
* @param array<string,mixed> $args The GraphQL args passed to the resolver. |
| 129 |
* @param array<string,mixed> $unfiltered_args Array of arguments input in the field as part of the GraphQL query. |
| 130 |
* |
| 131 |
* @hookGroup connections |
| 132 |
* @since 1.11.0 |
| 133 |
*/ |
| 134 |
return apply_filters( 'graphql_menu_item_connection_args', $args, $this->get_unfiltered_args() ); |
| 135 |
} |
| 136 |
} |
| 137 |
|