PluginProbe
WPGraphQL / 2.22.2
WPGraphQL v2.22.2
2.22.3 2.22.2 2.22.1 2.22.0 2.21.1 2.21.0 2.20.0 2.19.0 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.1 2.14.0 2.13.0 2.2.0 2.3.0 2.3.3 2.3.6 2.3.8 2.5.0 2.5.1 2.5.2 2.5.3 All 177 releases
wp-graphql / src / Deprecated.php

Deprecated.php in WPGraphQL 2.22.2, at src/Deprecated.php

464 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class for handling deprecated functionality.
4 *
5 * Entirely deprecated classes can be relocated to the `deprecated/` directory, but things still need to be hooked into WordPress.
6 *
7 * @package WPGraphQL
8 */
9
10 namespace WPGraphQL;
11
12 use GraphQL\Error\UserError;
13 use GraphQLRelay\Relay;
14 use WPGraphQL\Data\Connection\PostObjectConnectionResolver;
15 use WPGraphQL\Model\Post;
16 use WPGraphQL\Type\Union\MenuItemObjectUnion;
17 use WPGraphQL\Type\Union\PostObjectUnion;
18 use WPGraphQL\Type\Union\TermObjectUnion;
19 use WPGraphQL\Type\WPObjectType;
20
21 /**
22 * Class - Deprecated
23 */
24 final class Deprecated {
25 /**
26 * The class constructor.
27 */
28 public function __construct() {}
29
30 /**
31 * Register the deprecated functionality.
32 */
33 public function register(): void {
34 $this->filters();
35 // We want to defer the action until after the schema is registered.
36 add_action( 'graphql_register_types', [ $this, 'register_deprecated_types' ] );
37 }
38
39 /**
40 * Handles deprecated filters.
41 */
42 private function filters(): void {
43 /**
44 * The `graphql_object_type_interfaces` filter
45 *
46 * @deprecated 1.4.1
47 * @todo Remove in 3.0.0
48 */
49 add_filter(
50 'graphql_type_interfaces',
51 static function ( $interfaces, $config, $type ) {
52 if ( ! $type instanceof WPObjectType || ! has_filter( 'graphql_object_type_interfaces' ) ) {
53 return $interfaces;
54 }
55
56 /**
57 * @deprecated
58 *
59 * @param string[] $interfaces List of interfaces applied to the Object Type
60 * @param array<string,mixed> $config The config for the Object Type
61 * @param \WPGraphQL\Type\WPInterfaceType|\WPGraphQL\Type\WPObjectType $type The Type instance
62 */
63 return apply_filters_deprecated( 'graphql_object_type_interfaces', [ $interfaces, $config, $type ], '1.4.1', 'graphql_type_interfaces', __( 'This will be removed in the next major release of WPGraphQL.', 'wp-graphql' ) );
64 },
65 10,
66 3
67 );
68
69 /**
70 * The `graphql_return_modeled_data` filter.
71 *
72 * @deprecated 1.7.0
73 * @todo Remove in 3.0.0
74 */
75 add_filter(
76 'graphql_model_prepare_fields',
77 static function ( $fields, $model_name, $data, $visibility, $owner, $current_user ) {
78 if ( ! has_filter( 'graphql_return_modeled_data' ) ) {
79 return $fields;
80 }
81
82 /**
83 * @param array<string,mixed> $fields The array of fields for the model
84 * @param string $model_name Name of the model the filter is currently being executed in
85 * @param string $visibility The visibility setting for this piece of data
86 * @param ?int $owner The user ID for the owner of this piece of data
87 * @param \WP_User $current_user The current user for the session
88 *
89 * @deprecated 1.7.0 use "graphql_model_prepare_fields" filter instead, which passes additional context to the filter
90 */
91 return apply_filters_deprecated(
92 'graphql_return_modeled_data',
93 [ $fields, $model_name, $visibility, $owner, $current_user ],
94 '1.7.0',
95 'graphql_model_prepare_fields',
96 __( 'This will be removed in the next major release of WPGraphQL.', 'wp-graphql' )
97 );
98 },
99 10,
100 6
101 );
102 }
103
104 /**
105 * Registers deprecated graphql types.
106 */
107 public function register_deprecated_types(): void {
108 MenuItemObjectUnion::register_type(); /* @phpstan-ignore staticMethod.deprecatedClass */
109 PostObjectUnion::register_type(); /* @phpstan-ignore staticMethod.deprecatedClass */
110 TermObjectUnion::register_type(); /* @phpstan-ignore staticMethod.deprecatedClass */
111
112 $this->graphql_post_types();
113 $this->menu_item_connected_object();
114 $this->send_password_reset_email_user();
115 }
116
117 /**
118 * The `MenuItem` connectedObject field.
119 *
120 * @todo remove in 3.0.0
121 */
122 private function menu_item_connected_object(): void {
123 register_graphql_field(
124 'MenuItem',
125 'connectedObject',
126 [
127 'type' => 'MenuItemObjectUnion',
128 'deprecationReason' => static function () {
129 return __( 'Deprecated in favor of the connectedNode field', 'wp-graphql' );
130 },
131 'description' => static function () {
132 return __( 'The object connected to this menu item.', 'wp-graphql' );
133 },
134 'resolve' => static function ( $menu_item, array $args, AppContext $context, $info ) {
135 $object_id = intval( get_post_meta( $menu_item->menuItemId, '_menu_item_object_id', true ) );
136 $object_type = get_post_meta( $menu_item->menuItemId, '_menu_item_type', true );
137
138 switch ( $object_type ) {
139 // Post object
140 case 'post_type':
141 $resolved_object = $context->get_loader( 'post' )->load_deferred( $object_id );
142 break;
143
144 // Taxonomy term
145 case 'taxonomy':
146 $resolved_object = $context->get_loader( 'term' )->load_deferred( $object_id );
147 break;
148 default:
149 $resolved_object = null;
150 break;
151 }
152
153 /**
154 * @todo Remove in 3.0.0.
155 *
156 * @param \WP_Post|\WP_Term $resolved_object Post or term connected to MenuItem
157 * @param array<string,mixed> $args Array of arguments input in the field as part of the GraphQL query
158 * @param \WPGraphQL\AppContext $context Object containing app context that gets passed down the resolve tree
159 * @param \GraphQL\Type\Definition\ResolveInfo $info Info about fields passed down the resolve tree
160 * @param int $object_id Post or term ID of connected object
161 * @param string $object_type Type of connected object ("post_type" or "taxonomy")
162 *
163 * @since 0.0.30
164 */
165 return apply_filters_deprecated(
166 'graphql_resolve_menu_item',
167 [
168 $resolved_object,
169 $args,
170 $context,
171 $info,
172 $object_id,
173 $object_type,
174 ],
175 '1.22.0',
176 'graphql_pre_resolve_menu_item_connected_node',
177 __( 'This will be removed in the next version of WPGraphQL. Use the `graphql_pre_resolve_menu_item_connected_node` filter on `connectedNode` instead.', 'wp-graphql' )
178 );
179 },
180 ],
181 );
182 }
183
184 /**
185 * Registers deprecated Post Type data to the schema
186 */
187 private function graphql_post_types(): void {
188 $allowed_post_types = \WPGraphQL::get_allowed_post_types( 'objects', [ 'graphql_register_root_field' => true ] );
189
190 foreach ( $allowed_post_types as $post_type_object ) {
191 $this->post_type_by_field( $post_type_object );
192 $this->register_deprecated_post_type_parents( $post_type_object );
193 $this->register_deprecated_post_type_previews( $post_type_object );
194 }
195 }
196
197 /**
198 * Register deprecated {PostType}By fields
199 *
200 * @todo remove in 3.0.0
201 *
202 * @param \WP_Post_Type $post_type_object The post type object to register the field for.
203 */
204 private function post_type_by_field( $post_type_object ): void {
205 $post_by_args = [
206 'id' => [
207 'type' => 'ID',
208 'description' => static function () use ( $post_type_object ) {
209 return sprintf(
210 // translators: %s is the post type's GraphQL name.
211 __( 'Get the %s object by its global ID', 'wp-graphql' ),
212 $post_type_object->graphql_single_name
213 );
214 },
215 ],
216 $post_type_object->graphql_single_name . 'Id' => [
217 'type' => 'Int',
218 'description' => static function () use ( $post_type_object ) {
219 return sprintf(
220 // translators: %s is the post type's GraphQL name.
221 __( 'Get the %s by its database ID', 'wp-graphql' ),
222 $post_type_object->graphql_single_name
223 );
224 },
225 ],
226 'uri' => [
227 'type' => 'String',
228 'description' => static function () use ( $post_type_object ) {
229 return sprintf(
230 // translators: %s is the post type's GraphQL name.
231 __( 'Get the %s by its uri', 'wp-graphql' ),
232 $post_type_object->graphql_single_name
233 );
234 },
235 ],
236 ];
237
238 if ( false === $post_type_object->hierarchical ) {
239 $post_by_args['slug'] = [
240 'type' => 'String',
241 'description' => static function () use ( $post_type_object ) {
242 return sprintf(
243 // translators: %s is the post type's GraphQL name.
244 __( 'Get the %s by its slug (only available for non-hierarchical types)', 'wp-graphql' ),
245 $post_type_object->graphql_single_name
246 );
247 },
248 ];
249 }
250
251 /**
252 * @deprecated Deprecated in favor of single node entry points
253 */
254 register_graphql_field(
255 'RootQuery',
256 $post_type_object->graphql_single_name . 'By',
257 [
258 'type' => $post_type_object->graphql_single_name,
259 'deprecationReason' => static function () {
260 return __( 'Deprecated in favor of using the single entry point for this type with ID and IDType fields. For example, instead of postBy( id: "" ), use post(id: "" idType: "")', 'wp-graphql' );
261 },
262 'description' => static function () use ( $post_type_object ) {
263 return sprintf(
264 // translators: %s is the post type's GraphQL name.
265 __( 'A %s object', 'wp-graphql' ),
266 $post_type_object->graphql_single_name
267 );
268 },
269 'args' => $post_by_args,
270 'resolve' => static function ( $source, array $args, $context ) use ( $post_type_object ) {
271 $post_id = 0;
272
273 if ( ! empty( $args['id'] ) ) {
274 $id_components = Relay::fromGlobalId( $args['id'] );
275 if ( empty( $id_components['id'] ) || empty( $id_components['type'] ) ) {
276 throw new UserError( esc_html__( 'The "id" is invalid', 'wp-graphql' ) );
277 }
278 $post_id = absint( $id_components['id'] );
279 } elseif ( ! empty( $args[ lcfirst( $post_type_object->graphql_single_name . 'Id' ) ] ) ) {
280 $id = $args[ lcfirst( $post_type_object->graphql_single_name . 'Id' ) ];
281 $post_id = absint( $id );
282 } elseif ( ! empty( $args['uri'] ) ) {
283 return $context->node_resolver->resolve_uri(
284 $args['uri'],
285 [
286 'post_type' => $post_type_object->name,
287 'archive' => false,
288 'nodeType' => 'ContentNode',
289 ]
290 );
291 } elseif ( ! empty( $args['slug'] ) ) {
292 $slug = esc_html( $args['slug'] );
293
294 return $context->node_resolver->resolve_uri(
295 $slug,
296 [
297 'name' => $slug,
298 'post_type' => $post_type_object->name,
299 'nodeType' => 'ContentNode',
300 ]
301 );
302 }
303
304 return $context->get_loader( 'post' )->load_deferred( $post_id )->then(
305 static function ( $post ) use ( $post_type_object ) {
306
307 // if the post type object isn't an instance of WP_Post_Type, return
308 if ( ! $post_type_object instanceof \WP_Post_Type ) {
309 return null;
310 }
311
312 // if the post isn't an instance of a Post model, return
313 if ( ! $post instanceof Post ) {
314 return null;
315 }
316
317 if ( ! isset( $post->post_type ) || ! in_array(
318 $post->post_type,
319 [
320 'revision',
321 $post_type_object->name,
322 ],
323 true
324 ) ) {
325 return null;
326 }
327
328 return $post;
329 }
330 );
331 },
332 ]
333 );
334 }
335
336 /**
337 * Register deprecated Post Type connections.
338 *
339 * @todo remove in 3.0.0
340 *
341 * @param \WP_Post_Type $post_type_object The post type object to register the connection for.
342 */
343 private function register_deprecated_post_type_parents( \WP_Post_Type $post_type_object ): void {
344 if ( $post_type_object->hierarchical || in_array( $post_type_object->name, [ 'attachment', 'revision' ], true ) ) {
345 return;
346 }
347
348 // Ancestors
349 register_graphql_connection(
350 [
351 'fromType' => $post_type_object->graphql_single_name,
352 'toType' => $post_type_object->graphql_single_name,
353 'fromFieldName' => 'ancestors',
354 'description' => static function () {
355 return __( 'The ancestors of the content node.', 'wp-graphql' );
356 },
357 'deprecationReason' => static function () {
358 return __( 'This content type is not hierarchical and typically will not have ancestors', 'wp-graphql' );
359 },
360 'resolve' => static function () {
361 return null;
362 },
363 ]
364 );
365
366 // Parent
367 register_graphql_connection(
368 [
369 'fromType' => $post_type_object->graphql_single_name,
370 'toType' => $post_type_object->graphql_single_name,
371 'fromFieldName' => 'parent',
372 'oneToOne' => true,
373 'description' => static function () {
374 return __( 'The parent of the content node.', 'wp-graphql' );
375 },
376 'deprecationReason' => static function () {
377 return __( 'This content type is not hierarchical and typically will not have a parent', 'wp-graphql' );
378 },
379 'resolve' => static function () {
380 return null;
381 },
382 ]
383 );
384 }
385
386 /**
387 * Register deprecated Post Type previews.
388 *
389 * @todo remove in 3.0.0
390 * @param \WP_Post_Type $post_type_object The post type object to register the preview for.
391 */
392 private function register_deprecated_post_type_previews( \WP_Post_Type $post_type_object ): void {
393 if ( in_array( $post_type_object->name, [ 'attachment', 'revision' ], true ) ) {
394 return;
395 }
396
397 register_graphql_connection(
398 [
399 'fromType' => $post_type_object->graphql_single_name,
400 'toType' => $post_type_object->graphql_single_name,
401 'fromFieldName' => 'preview',
402 'connectionTypeName' => ucfirst( $post_type_object->graphql_single_name ) . 'ToPreviewConnection',
403 'oneToOne' => true,
404 'deprecationReason' => ( true === $post_type_object->publicly_queryable || true === $post_type_object->public ) ? null
405 : sprintf(
406 // translators: %s is the post type's GraphQL name.
407 __( 'The "%s" Type is not publicly queryable and does not support previews. This field will be removed in the future.', 'wp-graphql' ),
408 \WPGraphQL\Utils\Utils::format_type_name( $post_type_object->graphql_single_name )
409 ),
410 'resolve' => static function ( Post $post, $args, $context, $info ) {
411 if ( $post->isRevision ) {
412 return null;
413 }
414
415 if ( empty( $post->previewRevisionDatabaseId ) ) {
416 return null;
417 }
418
419 $resolver = new PostObjectConnectionResolver( $post, $args, $context, $info, 'revision' );
420 $resolver->set_query_arg( 'p', $post->previewRevisionDatabaseId );
421
422 return $resolver->one_to_one()->get_connection();
423 },
424 ]
425 );
426 }
427
428 /**
429 * SendPasswordResetEmail.user output field\
430 *
431 * @todo remove in 3.0.0
432 */
433 public function send_password_reset_email_user(): void {
434 register_graphql_field(
435 'SendPasswordResetEmailPayload',
436 'user',
437 [
438 'type' => 'User',
439 'description' => static function () {
440 return __( 'The user that the password reset email was sent to', 'wp-graphql' );
441 },
442 'deprecationReason' => static function () {
443 return __( 'This field will be removed in a future version of WPGraphQL', 'wp-graphql' );
444 },
445 'resolve' => static function ( $payload, $args, AppContext $context ) {
446 // The `sendPasswordResetEmail` mutation deliberately obfuscates whether the supplied
447 // username/email matches a real account (it always returns `success: true`) to prevent
448 // user enumeration. The internal `$payload['id']` is only populated on a real match, so
449 // resolving it for everyone would turn this field into an enumeration oracle: an
450 // unauthenticated caller could test arbitrary emails/logins and learn which ones exist
451 // (and link them to a user identity). Only expose the user to requesters who already have
452 // the capability to list users; everyone else gets null regardless of whether the account
453 // exists, preserving the mutation's anti-enumeration guarantee.
454 if ( empty( $payload['id'] ) || ! current_user_can( 'list_users' ) ) {
455 return null;
456 }
457
458 return $context->get_loader( 'user' )->load_deferred( $payload['id'] );
459 },
460 ],
461 );
462 }
463 }
464