From 1f1ba00b2ba4a2fd4c27e9bcf1ff723071c434d3 Mon Sep 17 00:00:00 2001 From: jasonbahl Date: Wed, 14 Jun 2017 14:34:51 -0600 Subject: [PATCH 1/6] #6 TermObjectMutations This adds initial crud mutations for term objects --- .../PostObject/Mutation/PostObjectCreate.php | 3 +- .../Mutation/PostObjectMutation.php | 8 +- .../PostObject/Mutation/PostObjectUpdate.php | 33 +++- src/Type/RootMutationType.php | 21 ++- .../TermObject/Mutation/TermObjectCreate.php | 151 +++++++++++++++++ .../TermObject/Mutation/TermObjectDelete.php | 0 .../Mutation/TermObjectMutation.php | 159 ++++++++++++++++++ .../TermObject/Mutation/TermObjectUpdate.php | 81 +++++++++ 8 files changed, 441 insertions(+), 15 deletions(-) create mode 100644 src/Type/TermObject/Mutation/TermObjectCreate.php create mode 100644 src/Type/TermObject/Mutation/TermObjectDelete.php create mode 100644 src/Type/TermObject/Mutation/TermObjectMutation.php create mode 100644 src/Type/TermObject/Mutation/TermObjectUpdate.php diff --git a/src/Type/PostObject/Mutation/PostObjectCreate.php b/src/Type/PostObject/Mutation/PostObjectCreate.php index 2376312cde..9befe5756c 100644 --- a/src/Type/PostObject/Mutation/PostObjectCreate.php +++ b/src/Type/PostObject/Mutation/PostObjectCreate.php @@ -3,7 +3,6 @@ namespace WPGraphQL\Type\PostObject\Mutation; use GraphQLRelay\Relay; -use WPGraphQL\Type\PostObject\PostObjectMutation; use WPGraphQL\Types; /** @@ -89,7 +88,7 @@ public static function mutate( \WP_Post_Type $post_type_object ) { /** * Insert the post and retrieve the ID */ - $post_id = wp_insert_post( $post_args, true ); + $post_id = wp_insert_post( wp_slash( (array) $post_args ), true ); /** * Throw an exception if the post failed to create diff --git a/src/Type/PostObject/Mutation/PostObjectMutation.php b/src/Type/PostObject/Mutation/PostObjectMutation.php index 0382d76bfd..051699d0a4 100644 --- a/src/Type/PostObject/Mutation/PostObjectMutation.php +++ b/src/Type/PostObject/Mutation/PostObjectMutation.php @@ -1,6 +1,6 @@ graphql_single_name ); - self::$mutation[ $post_type_object->graphql_single_name ] = Relay::mutationWithClientMutationId( array( + self::$mutation[ $post_type_object->graphql_single_name ] = Relay::mutationWithClientMutationId([ 'name' => esc_html( $mutation_name ), // translators: The placeholder is the name of the post type being updated 'description' => sprintf( esc_html__( 'Updates %1$s objects', 'wp-graphql' ), $post_type_object->graphql_single_name ), 'inputFields' => self::input_fields( $post_type_object ), - 'outputFields' => array( - $post_type_object->graphql_single_name => array( + 'outputFields' => [ + $post_type_object->graphql_single_name => [ 'type' => Types::post_object( $post_type_object->name ), 'resolve' => function( $payload ) { return get_post( $payload['postObjectId'] ); }, - ), - ), + ], + ], 'mutateAndGetPayload' => function( $input ) use ( $post_type_object, $mutation_name ) { $id_parts = ! empty( $input['id'] ) ? Relay::fromGlobalId( $input['id'] ) : null; @@ -104,7 +103,7 @@ public static function mutate( \WP_Post_Type $post_type_object ) { /** * Insert the post and retrieve the ID */ - $post_id = wp_update_post( $post_args, true ); + $post_id = wp_update_post( wp_slash( (array) $post_args ), true ); /** * Throw an exception if the post failed to update @@ -125,6 +124,17 @@ public static function mutate( \WP_Post_Type $post_type_object ) { throw new \Exception( __( 'The object failed to update', 'wp-graphql' ) ); } + /** + * Fires after a single term is created or updated via a GraphQL mutation + * + * The dynamic portion of the hook name, `$taxonomy->name` refers to the taxonomy of the term being mutated + * + * @param int $post_id Inserted post ID + * @param array $args The args used to insert the term + * @param string $mutation_name The name of the mutation being performed + */ + do_action( "graphql_insert_{$post_type_object->name}", $post_id, $post_args, $mutation_name ); + /** * This updates additional data not part of the posts table (postmeta, terms, other relations, etc) * @@ -138,7 +148,7 @@ public static function mutate( \WP_Post_Type $post_type_object ) { ]; }, - ) ); + ]); return self::$mutation[ $post_type_object->graphql_single_name ]; @@ -148,6 +158,13 @@ public static function mutate( \WP_Post_Type $post_type_object ) { } + /** + * Add the id as a nonNull field for update mutations + * + * @param \WP_Post_Type $post_type_object + * + * @return array + */ private static function input_fields( $post_type_object ) { /** diff --git a/src/Type/RootMutationType.php b/src/Type/RootMutationType.php index 288e171ac3..5b321ba5b4 100755 --- a/src/Type/RootMutationType.php +++ b/src/Type/RootMutationType.php @@ -2,11 +2,11 @@ namespace WPGraphQL\Type; -use GraphQL\Type\Definition\ResolveInfo; -use WPGraphQL\AppContext; use WPGraphQL\Type\PostObject\Mutation\PostObjectCreate; use WPGraphQL\Type\PostObject\Mutation\PostObjectDelete; use WPGraphQL\Type\PostObject\Mutation\PostObjectUpdate; +use WPGraphQL\Type\TermObject\Mutation\TermObjectCreate; +use WPGraphQL\Type\TermObject\Mutation\TermObjectUpdate; /** * Class RootMutationType @@ -66,6 +66,7 @@ private static function fields() { $fields = []; $allowed_post_types = \WPGraphQL::$allowed_post_types; + $allowed_taxonomies = \WPGraphQL::$allowed_taxonomies; if ( ! empty( $allowed_post_types ) && is_array( $allowed_post_types ) ) { foreach ( $allowed_post_types as $post_type ) { @@ -88,6 +89,22 @@ private static function fields() { } // End foreach(). } // End if(). + if ( ! empty( $allowed_taxonomies ) && is_array( $allowed_taxonomies ) ) { + foreach ( $allowed_taxonomies as $taxonomy ) { + + /** + * Get the taxonomy object to pass down to the schema + */ + $taxonomy_object = get_taxonomy( $taxonomy ); + + /** + * Root mutation for single term objects (of the specified taxonomy) + */ + $fields[ 'create' . ucwords( $taxonomy_object->graphql_single_name ) ] = TermObjectCreate::mutate( $taxonomy_object ); + $fields[ 'update' . ucwords( $taxonomy_object->graphql_single_name ) ] = TermObjectUpdate::mutate( $taxonomy_object ); + } + } // End if(). + self::$fields = $fields; } // End if(). diff --git a/src/Type/TermObject/Mutation/TermObjectCreate.php b/src/Type/TermObject/Mutation/TermObjectCreate.php new file mode 100644 index 0000000000..4eb80784bf --- /dev/null +++ b/src/Type/TermObject/Mutation/TermObjectCreate.php @@ -0,0 +1,151 @@ +graphql_single_name ) && empty( self::$mutation[ $taxonomy->graphql_single_name ] ) ) : + + /** + * Set the name of the mutation being performed + */ + $mutation_name = 'create' . ucwords( $taxonomy->graphql_single_name ); + + self::$mutation[ $taxonomy->graphql_single_name ] = Relay::mutationWithClientMutationId( [ + 'name' => esc_html( $mutation_name ), + // translators: The placeholder is the name of the object type + 'description' => sprintf( esc_html__( 'Create %1$s objects', 'wp-graphql' ), $taxonomy->name ), + 'inputFields' => self::input_fields( $taxonomy ), + 'outputFields' => [ + $taxonomy->graphql_single_name => [ + 'type' => Types::term_object( $taxonomy->name ), + 'resolve' => function( $payload ) use ( $taxonomy ) { + return get_term( $payload['id'], $taxonomy->name ); + }, + ], + ], + 'mutateAndGetPayload' => function( $input ) use ( $taxonomy, $mutation_name ) { + + /** + * Throw an exception if there's no input + */ + if ( ( empty( $taxonomy->name ) ) || ( empty( $input ) || ! is_array( $input ) ) ) { + throw new \Exception( __( 'Mutation not processed. There was no input for the mutation or the taxonomy was invalid', 'wp-graphql' ) ); + } + + /** + * Ensure the user can edit_terms + */ + if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) { + // translators: the $taxonomy->graphql_plural_name placeholder is the name of the object being mutated + throw new \Exception( sprintf( __( 'Sorry, you are not allowed to create %1$s', 'wp-graphql' ), $taxonomy->graphql_plural_name ) ); + } + + /** + * Prepare the object for insertion + */ + $args = TermObjectMutation::prepare_object( $input, $taxonomy, $mutation_name ); + + /** + * Ensure a name was provided + */ + if ( empty( $args['name'] ) ) { + // Translators: The placeholder is the name of the taxonomy of the term being mutated + throw new \Exception( sprintf( __( 'A name is required to create a %1$s' ), $taxonomy->name ) ); + } + + /** + * Insert the term + */ + $term = wp_insert_term( wp_slash( $args['name'] ), $taxonomy->name, wp_slash( (array) $args ) ); + + /** + * If it was an error, return the message as an exception + */ + if ( is_wp_error( $term ) ) { + $error_message = $term->get_error_message(); + if ( ! empty( $error_message ) ) { + throw new \Exception( esc_html( $error_message ) ); + } else { + throw new \Exception( __( 'The object failed to update but no error was provided', 'wp-graphql' ) ); + } + } + + /** + * If the response to creating the term didn't respond with a term_id, throw an exception + */ + if ( empty( $term['term_id'] ) ) { + throw new \Exception( __( 'The object failed to create', 'wp-graphql' ) ); + } + + /** + * Fires after a single term is created or updated via a GraphQL mutation + * + * The dynamic portion of the hook name, `$taxonomy->name` refers to the taxonomy of the term being mutated + * + * @param int $term_id Inserted term object + * @param array $args The args used to insert the term + * @param string $mutation_name The name of the mutation being performed + */ + do_action( "graphql_insert_{$taxonomy->name}", $term['term_id'], $args, $mutation_name ); + + return [ + 'id' => $term['term_id'], + ]; + + }, + ] ); + + return self::$mutation[ $taxonomy->graphql_single_name ]; + + endif; // End if(). + + return self::$mutation; + + } + + /** + * Add the name as a nonNull field for create mutations + * + * @param \WP_Taxonomy $taxonomy + * + * @return array + */ + private static function input_fields( $taxonomy ) { + + /** + * Add name as a non_null field for term creation + */ + return array_merge( + [ + 'name' => [ + 'type' => Types::non_null( Types::string() ), + // Translators: The placeholder is the name of the taxonomy for the object being mutated + 'description' => sprintf( __( 'The name of the %1$s object to mutate', 'wp-graphql' ), $taxonomy->name ), + ], + ], + TermObjectMutation::input_fields( $taxonomy ) + ); + + } + +} diff --git a/src/Type/TermObject/Mutation/TermObjectDelete.php b/src/Type/TermObject/Mutation/TermObjectDelete.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Type/TermObject/Mutation/TermObjectMutation.php b/src/Type/TermObject/Mutation/TermObjectMutation.php new file mode 100644 index 0000000000..82bd919dd4 --- /dev/null +++ b/src/Type/TermObject/Mutation/TermObjectMutation.php @@ -0,0 +1,159 @@ +name ) && empty( self::$input_fields[ $taxonomy->name ] ) ) { + + $input_fields = [ + 'name' => [ + 'type' => Types::string(), + // Translators: The placeholder is the name of the taxonomy for the object being mutated + 'description' => sprintf( __( 'The name of the %1$s object to mutate', 'wp-graphql' ), $taxonomy->name ), + ], + 'aliasOf' => [ + 'type' => Types::string(), + // Translators: The placeholder is the name of the taxonomy for the object being mutated + 'description' => sprintf( __( 'The slug that the %1$s will be an alias of', 'wp-graphql' ), $taxonomy->name ), + ], + 'description' => [ + 'type' => Types::string(), + // Translators: The placeholder is the name of the taxonomy for the object being mutated + 'description' => sprintf( __( 'The description of the %1$s object', 'wp-graphql' ), $taxonomy->name ), + ], + 'slug' => [ + 'type' => Types::string(), + 'description' => __( 'If this argument exists then the slug will be checked to see if it is not an existing valid term. If that check succeeds (it is not a valid term), then it is added and the term id is given. If it fails, then a check is made to whether the taxonomy is hierarchical and the parent argument is not empty. If the second check succeeds, the term will be inserted and the term id will be given. If the slug argument is empty, then it will be calculated from the term name.' ), + ], + ]; + + /** + * Add a parentId field to hierarchical taxonomies to allow parents to be set + */ + if ( true === $taxonomy->hierarchical ) { + $input_fields['parentId'] = [ + 'type' => Types::id(), + // Translators: The placeholder is the name of the taxonomy for the object being mutated + 'description' => sprintf( __( 'The ID of the %1$s that should be set as the parent', 'wp-graphql' ), $taxonomy->name ), + ]; + } + + /** + * Filter the mutation input fields for the object type + * + * @param array $input_fields The array of input fields + * @param \WP_Taxonomy The taxonomy of the Term object being mutated + */ + self::$input_fields[ $taxonomy->name ] = apply_filters( 'graphql_term_object_mutation_input_fields', $input_fields, $taxonomy ); + + } // End if(). + + return ! empty( self::$input_fields[ $taxonomy->name ] ) ? self::$input_fields[ $taxonomy->name ] : null; + + } + + /** + * This prepares the object to be mutated – ensures data is safe to be saved, + * and mapped from input args to WordPress $args + * + * @param array $input The input from the GraphQL Request + * @param \WP_Taxonomy $taxonomy The Taxonomy object for the type of term being mutated + * @param string $mutation_name The name of the mutation (create, update, etc) + * + * @throws \Exception + * + * @return mixed + */ + public static function prepare_object( $input, \WP_Taxonomy $taxonomy, $mutation_name ) { + + /** + * Set the taxonomy for insert + */ + $insert_args['taxonomy'] = $taxonomy->name; + + /** + * Prepare the data for inserting the term + */ + if ( ! empty( $input['aliasOf'] ) ) { + $insert_args['alias_of'] = $input['aliasOf']; + } + + if ( ! empty( $input['name'] ) ) { + $insert_args['name'] = esc_sql( $input['name'] ); + } + + if ( ! empty( $input['description'] ) ) { + $insert_args['description'] = esc_sql( $input['description'] ); + } + + if ( ! empty( $input['slug'] ) ) { + $insert_args['slug'] = esc_sql( $input['slug'] ); + } + + /** + * If the parentId argument was entered, we need to validate that it's actually a legit term that can + * be set as a parent + */ + if ( ! empty( $input['parentId'] ) ) { + + /** + * Convert parent ID to WordPress ID + */ + $parent_id_parts = ! empty( $input['parentId'] ) ? Relay::fromGlobalId( $input['parentId'] ) : null; + + /** + * If the term + */ + if ( is_array( $parent_id_parts ) && ! empty( $parent_id_parts[1] ) && is_int( $parent_id_parts[1] ) ) { + $parent_id = $parent_id_parts[1]; + + $parent_term = get_term( absint( $parent_id, $taxonomy->name ) ); + + if ( ! $parent_term || is_wp_error( $parent_term ) ) { + throw new \Exception( __( 'The parent does not exist', 'wp-graphql' ) ); + } + + // Otherwise set the parent as the parent term's ID + $insert_args['parent'] = $parent_term->term_id; + + } else { + throw new \Exception( __( 'The parent ID is not a valid ID', 'wp-graphql' ) ); + } // End if(). + } + + /** + * Filter the $insert_args + * + * @param array $insert_args The array of input args that will be passed to the functions that insert terms + * @param array $input The data that was entered as input for the mutation + * @param \WP_Taxonomy $taxonomy The taxonomy object of the term being mutated + * @param string $mutation_name The name of the mutation being performed (create, edit, etc) + */ + $insert_args = apply_filters( 'graphql_term_object_insert_term_args', $insert_args, $input, $taxonomy, $mutation_name ); + + /** + * Return the $args + */ + return $insert_args; + + } + +} \ No newline at end of file diff --git a/src/Type/TermObject/Mutation/TermObjectUpdate.php b/src/Type/TermObject/Mutation/TermObjectUpdate.php new file mode 100644 index 0000000000..2e4f2bd958 --- /dev/null +++ b/src/Type/TermObject/Mutation/TermObjectUpdate.php @@ -0,0 +1,81 @@ +graphql_single_name ) && empty( self::$mutation[ $taxonomy->graphql_single_name ] ) ) : + + $mutation_name = 'update' . ucwords( $taxonomy->graphql_single_name ); + + self::$mutation[ $taxonomy->graphql_single_name ] = Relay::mutationWithClientMutationId( [ + 'name' => esc_html( $mutation_name ), + // translators: The placeholder is the name of the post type being updated + 'description' => sprintf( esc_html__( 'Updates %1$s objects', 'wp-graphql' ), $taxonomy->graphql_single_name ), + 'inputFields' => self::input_fields( $taxonomy ), + 'outputFields' => [ + $taxonomy->graphql_single_name => [ + 'type' => Types::term_object( $taxonomy->name ), + 'resolve' => function( $payload ) use ( $taxonomy ) { + return get_term( $payload['postObjectId'], $taxonomy->name ); + }, + ], + ], + 'mutateAndGetPayload' => function( $input ) use ( $taxonomy, $mutation_name ) { + + }, + ] ); + + return self::$mutation[ $taxonomy->graphql_single_name ]; + + endif; // End if(). + + return self::$mutation; + + } + + /** + * Add the id as an optional field for update mutations + * + * @param \WP_Taxonomy $taxonomy + * + * @return array + */ + private static function input_fields( $taxonomy ) { + + /** + * Add name as a non_null field for term creation + */ + return array_merge( + [ + 'name' => [ + 'type' => Types::string(), + // Translators: The placeholder is the name of the taxonomy for the object being mutated + 'description' => sprintf( __( 'The name of the %1$s object to mutate', 'wp-graphql' ), $taxonomy->name ), + ], + ], + TermObjectMutation::input_fields( $taxonomy ) + ); + + } + +} From 00799662f0a785d350d95c17de27acfb4a496069 Mon Sep 17 00:00:00 2001 From: jasonbahl Date: Wed, 14 Jun 2017 16:06:47 -0600 Subject: [PATCH 2/6] #6-TermObjectMutations This adds initial CRUD mutations for TermObjects examples: mutation{ createCategory(input:{ name:"GraphQLCat 2" clientMutationId:"SomeID" }){ clientMutationId category{ name id link } } } mutation { updateCategory(input:{ id:"Y2F0ZWdvcnk6MjA0", clientMutationId:"someId" description:"Some Updated Description, dawg" }){ category{ description id link slug } } } mutation{ deleteCategory( input:{ id:"Y2F0ZWdvcnk6MjAy" clientMutationId:"SomeId" } ){ category{ id link name } } } --- .../PostObject/Mutation/PostObjectDelete.php | 5 +- .../Mutation/PostObjectMutation.php | 8 +- .../PostObject/Mutation/PostObjectUpdate.php | 7 +- src/Type/RootMutationType.php | 2 + .../TermObject/Mutation/TermObjectDelete.php | 127 ++++++++++++++++++ .../Mutation/TermObjectMutation.php | 4 +- .../TermObject/Mutation/TermObjectUpdate.php | 93 ++++++++++++- vendor/composer/ClassLoader.php | 46 +++++-- vendor/composer/LICENSE | 2 +- vendor/composer/autoload_classmap.php | 7 +- vendor/composer/autoload_real.php | 2 +- vendor/composer/autoload_static.php | 7 +- 12 files changed, 287 insertions(+), 23 deletions(-) diff --git a/src/Type/PostObject/Mutation/PostObjectDelete.php b/src/Type/PostObject/Mutation/PostObjectDelete.php index 2b69137e05..c9929a2a05 100644 --- a/src/Type/PostObject/Mutation/PostObjectDelete.php +++ b/src/Type/PostObject/Mutation/PostObjectDelete.php @@ -53,7 +53,7 @@ public static function mutate( \WP_Post_Type $post_type_object ) { 'outputFields' => [ 'deletedId' => [ 'type' => Types::id(), - 'description' => __( 'The object before it was deleted', 'wp-graphql' ), + 'description' => __( 'The ID of the deleted object', 'wp-graphql' ), 'resolve' => function( $payload ) use ( $post_type_object ) { $deleted = (object) $payload['postObject']; return ! empty( $deleted->ID ) ? Relay::toGlobalId( $post_type_object->name, absint( $deleted->ID ) ) : null; @@ -74,7 +74,8 @@ public static function mutate( \WP_Post_Type $post_type_object ) { * Throw an exception if there's no input */ if ( ( empty( $post_type_object->name ) ) || ( empty( $input ) || ! is_array( $input ) ) ) { - throw new \Exception( __( 'Mutation not processed. There was no input for the mutation or the post_type_object was invalid', 'wp-graphql' ) ); + // Translators: The placeholder is the name of the post type for the object being deleted + throw new \Exception( sprintf( __( 'Mutation not processed. There was no input for the mutation or the %1$s was invalid', 'wp-graphql' ), $post_type_object->graphql_single_name ) ); } /** diff --git a/src/Type/PostObject/Mutation/PostObjectMutation.php b/src/Type/PostObject/Mutation/PostObjectMutation.php index 051699d0a4..411fe53d29 100644 --- a/src/Type/PostObject/Mutation/PostObjectMutation.php +++ b/src/Type/PostObject/Mutation/PostObjectMutation.php @@ -144,8 +144,8 @@ public static function prepare_post_object( $input, $post_type_object, $mutation * NOTE: These are organized in the same order as: https://developer.wordpress.org/reference/functions/wp_insert_post/ */ $author_id_parts = ! empty( $input['authorId'] ) ? Relay::fromGlobalId( $input['authorId'] ) : null; - if ( is_array( $author_id_parts ) && ! empty( $author_id_parts[1] ) && is_int( $author_id_parts[1] ) ) { - $insert_post_args['post_author'] = absint( $author_id_parts[1] ); + if ( is_array( $author_id_parts ) && ! empty( $author_id_parts['id'] ) && is_int( $author_id_parts['id'] ) ) { + $insert_post_args['post_author'] = absint( $author_id_parts['id'] ); } if ( ! empty( $input['date'] ) && false !== strtotime( $input['date'] ) ) { @@ -205,8 +205,8 @@ public static function prepare_post_object( $input, $post_type_object, $mutation } $parent_id_parts = ! empty( $input['parentId'] ) ? Relay::fromGlobalId( $input['parentId'] ) : null; - if ( is_array( $parent_id_parts ) && ! empty( $parent_id_parts[1] ) && is_int( $parent_id_parts[1] ) ) { - $insert_post_args['post_parent'] = absint( $parent_id_parts[1] ); + if ( is_array( $parent_id_parts ) && ! empty( $parent_id_parts['id'] ) && is_int( $parent_id_parts['id'] ) ) { + $insert_post_args['post_parent'] = absint( $parent_id_parts['id'] ); } else { throw new \Exception( __( 'The parent ID is not a valid ID', 'wp-graphql' ) ); } // End if(). diff --git a/src/Type/PostObject/Mutation/PostObjectUpdate.php b/src/Type/PostObject/Mutation/PostObjectUpdate.php index b6d8a85dfe..9d358e7696 100644 --- a/src/Type/PostObject/Mutation/PostObjectUpdate.php +++ b/src/Type/PostObject/Mutation/PostObjectUpdate.php @@ -56,7 +56,7 @@ public static function mutate( \WP_Post_Type $post_type_object ) { /** * If there's no existing post, throw an exception */ - if ( empty( $id_parts['id'] ) || false === $existing_post ) { + if ( empty( $id_parts['id'] ) || false === $existing_post || $id_parts['type'] !== $post_type_object->name ) { // translators: the placeholder is the name of the type of post being updated throw new \Exception( sprintf( __( 'No %1$s could be found to update', 'wp-graphql' ), $post_type_object->graphql_single_name ) ); } @@ -79,7 +79,7 @@ public static function mutate( \WP_Post_Type $post_type_object ) { * make sure they have permission to edit others posts */ $author_id_parts = ! empty( $input['authorId'] ) ? Relay::fromGlobalId( $input['authorId'] ) : null; - if ( ! empty( $author_id_parts[0] ) && get_current_user_id() !== $author_id_parts[0] && ! current_user_can( $post_type_object->cap->edit_others_posts ) ) { + if ( ! empty( $author_id_parts['id'] ) && get_current_user_id() !== $author_id_parts['id'] && ! current_user_can( $post_type_object->cap->edit_others_posts ) ) { // translators: the $post_type_object->graphql_single_name placeholder is the name of the object being mutated throw new \Exception( sprintf( __( 'Sorry, you are not allowed to update %1$s as this user.', 'wp-graphql' ), $post_type_object->graphql_plural_name ) ); } @@ -143,6 +143,9 @@ public static function mutate( \WP_Post_Type $post_type_object ) { */ PostObjectMutation::update_additional_post_object_data( $post_id, $input, $post_type_object, $mutation_name ); + /** + * Return the payload + */ return [ 'postObjectId' => $post_id, ]; diff --git a/src/Type/RootMutationType.php b/src/Type/RootMutationType.php index 5b321ba5b4..ccaa5a5b13 100755 --- a/src/Type/RootMutationType.php +++ b/src/Type/RootMutationType.php @@ -5,6 +5,7 @@ use WPGraphQL\Type\PostObject\Mutation\PostObjectCreate; use WPGraphQL\Type\PostObject\Mutation\PostObjectDelete; use WPGraphQL\Type\PostObject\Mutation\PostObjectUpdate; +use WPGraphQL\Type\PostObject\Mutation\TermObjectDelete; use WPGraphQL\Type\TermObject\Mutation\TermObjectCreate; use WPGraphQL\Type\TermObject\Mutation\TermObjectUpdate; @@ -102,6 +103,7 @@ private static function fields() { */ $fields[ 'create' . ucwords( $taxonomy_object->graphql_single_name ) ] = TermObjectCreate::mutate( $taxonomy_object ); $fields[ 'update' . ucwords( $taxonomy_object->graphql_single_name ) ] = TermObjectUpdate::mutate( $taxonomy_object ); + $fields[ 'delete' . ucwords( $taxonomy_object->graphql_single_name ) ] = TermObjectDelete::mutate( $taxonomy_object ); } } // End if(). diff --git a/src/Type/TermObject/Mutation/TermObjectDelete.php b/src/Type/TermObject/Mutation/TermObjectDelete.php index e69de29bb2..3e8e537d8d 100644 --- a/src/Type/TermObject/Mutation/TermObjectDelete.php +++ b/src/Type/TermObject/Mutation/TermObjectDelete.php @@ -0,0 +1,127 @@ +graphql_single_name ) && empty( self::$mutation[ $taxonomy->graphql_single_name ] ) ) : + + /** + * Set the name of the mutation being performed + */ + $mutation_name = 'delete' . ucwords( $taxonomy->graphql_single_name ); + + self::$mutation[ $taxonomy->graphql_single_name ] = Relay::mutationWithClientMutationId([ + 'name' => esc_html( $mutation_name ), + // Translators: The placeholder is the taxonomy name of the term being deleted + 'description' => sprintf( esc_html__( 'Delete %1$s objects', 'wp-graphql' ), $taxonomy->graphql_single_name ), + 'inputFields' => [ + 'id' => [ + 'type' => Types::non_null( Types::id() ), + // translators: The placeholder is the name of the taxonomy for the term being deleted + 'description' => sprintf( __( 'The ID of the %1$s to delete', 'wp-graphql' ), $taxonomy->graphql_single_name ), + ], + ], + 'outputFields' => [ + 'deletedId' => [ + 'type' => Types::id(), + 'description' => __( 'The ID of the deleted object', 'wp-graphql' ), + 'resolve' => function( $payload ) use ( $taxonomy ) { + $deleted = (object) $payload['termObject']; + return ! empty( $deleted->term_id ) ? Relay::toGlobalId( $taxonomy->name, $deleted->term_id ) : null; + }, + ], + $taxonomy->graphql_single_name => [ + 'type' => Types::term_object( $taxonomy->name ), + 'description' => __( 'The object before it was deleted', 'wp-graphql' ), + 'resolve' => function( $payload ) use ( $taxonomy ) { + $deleted = (object) $payload['termObject']; + return ! empty( $deleted ) ? $deleted : null; + }, + ], + ], + 'mutateAndGetPayload' => function( $input ) use ( $taxonomy, $mutation_name ) { + + if ( empty( $taxonomy->name ) || empty( $input ) || ! is_array( $input ) ) { + // Translators: The placeholder is the name of the taxonomy for the term being deleted + throw new \Exception( sprintf( __( 'Mutation not processed. There was no input for the mutation or the %1$s was invalid', 'wp-graphql' ), $taxonomy->graphql_single_name ) ); + } + + $id_parts = Relay::fromGlobalId( $input['id'] ); + if ( ! empty( $id_parts['id'] ) && absint( $id_parts['id'] ) ) { + $term_id = absint( $id_parts['id'] ); + } else { + // Translators: The placeholder is the name of the taxonomy for the term being deleted + throw new \Exception( sprintf( __( 'The ID for the %1$s was not valid', 'wp-graphql' ), $taxonomy->graphql_single_name ) ); + } + + /** + * Get the term before deleting it + */ + $term_object = get_term( $term_id, $taxonomy->name ); + + /** + * Ensure the user can delete terms of this taxonomy + */ + if ( ! current_user_can( 'delete_term', $term_object->term_id ) ) { + // Translators: The placeholder is the name of the taxonomy for the term being deleted + throw new \Exception( sprintf( __( 'You do not have permission to delete %1$s', 'wp-graphql' ), $taxonomy->graphql_plural_name ) ); + } + + /** + * Delete the term and get the response + */ + $deleted = wp_delete_term( $term_id, $taxonomy->name ); + + /** + * If there was an error deleting the term, get the error message and return it + */ + if ( is_wp_error( $deleted ) ) { + $error_message = $deleted->get_error_message(); + if ( ! empty( $error_message ) ) { + throw new \Exception( esc_html( $error_message ) ); + } else { + // Translators: The placeholder is the name of the taxonomy for the term being deleted + throw new \Exception( sprintf( __( 'The %1$s failed to delete but no error was provided', 'wp-graphql' ), $taxonomy->name ) ); + } + } + + /** + * Return the term object that was retrieved prior to deletion + */ + return [ + 'termObject' => $term_object, + ]; + + }, + ]); + + return self::$mutation[ $taxonomy->graphql_single_name ]; + + endif; // End if(). + + return self::$mutation; + + } +} + + diff --git a/src/Type/TermObject/Mutation/TermObjectMutation.php b/src/Type/TermObject/Mutation/TermObjectMutation.php index 82bd919dd4..e4d519f56d 100644 --- a/src/Type/TermObject/Mutation/TermObjectMutation.php +++ b/src/Type/TermObject/Mutation/TermObjectMutation.php @@ -122,8 +122,8 @@ public static function prepare_object( $input, \WP_Taxonomy $taxonomy, $mutation /** * If the term */ - if ( is_array( $parent_id_parts ) && ! empty( $parent_id_parts[1] ) && is_int( $parent_id_parts[1] ) ) { - $parent_id = $parent_id_parts[1]; + if ( is_array( $parent_id_parts ) && ! empty( $parent_id_parts['id'] ) && is_int( $parent_id_parts['id'] ) ) { + $parent_id = $parent_id_parts['id']; $parent_term = get_term( absint( $parent_id, $taxonomy->name ) ); diff --git a/src/Type/TermObject/Mutation/TermObjectUpdate.php b/src/Type/TermObject/Mutation/TermObjectUpdate.php index 2e4f2bd958..93f177f73b 100644 --- a/src/Type/TermObject/Mutation/TermObjectUpdate.php +++ b/src/Type/TermObject/Mutation/TermObjectUpdate.php @@ -36,12 +36,98 @@ public static function mutate( \WP_Taxonomy $taxonomy ) { $taxonomy->graphql_single_name => [ 'type' => Types::term_object( $taxonomy->name ), 'resolve' => function( $payload ) use ( $taxonomy ) { - return get_term( $payload['postObjectId'], $taxonomy->name ); + return get_term( $payload['term_id'], $taxonomy->name ); }, ], ], 'mutateAndGetPayload' => function( $input ) use ( $taxonomy, $mutation_name ) { + if ( empty( $input['id'] ) ) { + // Translators: The placeholder is the name of the taxonomy for the term being edited + throw new \Exception( sprintf( __( 'ID is required to update the %1$s', 'wp-graphql' ), $taxonomy->graphql_single_name ) ); + } + + /** + * Get the ID parts + */ + $id_parts = ! empty( $input['id'] ) ? Relay::fromGlobalId( $input['id'] ) : null; + + /** + * Ensure the type for the Global ID matches the type being mutated + */ + if ( empty( $id_parts['type'] ) || $taxonomy->name !== $id_parts['type'] ) { + // Translators: The placeholder is the name of the taxonomy for the term being edited + throw new \Exception( sprintf( __( 'The ID passed is not for a %1$s object', 'wp-graphql' ), $taxonomy->graphql_single_name ) ); + } + + /** + * Get the existing term + */ + $existing_term = get_term( absint( $id_parts['id'] ), $taxonomy->name ); + + /** + * If there was an error getting the existing term, return the error message + */ + if ( is_wp_error( $existing_term ) ) { + $error_message = $existing_term->get_error_message(); + if ( ! empty( $error_message ) ) { + throw new \Exception( esc_html( $error_message ) ); + } else { + // Translators: The placeholder is the name of the taxonomy for the term being deleted + throw new \Exception( sprintf( __( 'The %1$s failed to update', 'wp-graphql' ), $taxonomy->name ) ); + } + } + + /** + * Ensure the user has permission to edit terms + */ + if ( ! current_user_can( 'edit_term', $existing_term->term_id ) ) { + // Translators: The placeholder is the name of the taxonomy for the term being deleted + throw new \Exception( sprintf( __( 'You do not have permission to update %1$s', 'wp-graphql' ), $taxonomy->graphql_plural_name ) ); + } + + /** + * Prepare the $args for mutation + */ + $args = TermObjectMutation::prepare_object( $input, $taxonomy, $mutation_name ); + + if ( ! empty( $args ) ) { + + /** + * Update the term + */ + $update = wp_update_term( $existing_term->term_id, $taxonomy->name, wp_slash( (array) $args ) ); + + /** + * Respond with any errors + */ + if ( is_wp_error( $update ) ) { + $error_message = $update->get_error_message(); + if ( ! empty( $error_message ) ) { + throw new \Exception( esc_html( $error_message ) ); + } else { + // Translators: The placeholder is the name of the taxonomy for the term being deleted + throw new \Exception( sprintf( __( 'The %1$s failed to update', 'wp-graphql' ), $taxonomy->name ) ); + } + } + } + + /** + * Fires an action when a term is updated via a GraphQL Mutation + * + * @param int $term_id The ID of the term object that was mutated + * @param array $args The args used to update the term + * @param string $mutation_name The name of the mutation being performed (create, update, delete, etc) + */ + do_action( "graphql_update_{$taxonomy->name}", $existing_term->term_id, $args, $mutation_name ); + + /** + * Return the payload + */ + return [ + 'term_id' => $existing_term->term_id, + ]; + }, ] ); @@ -72,6 +158,11 @@ private static function input_fields( $taxonomy ) { // Translators: The placeholder is the name of the taxonomy for the object being mutated 'description' => sprintf( __( 'The name of the %1$s object to mutate', 'wp-graphql' ), $taxonomy->name ), ], + 'id' => [ + 'type' => Types::non_null( Types::id() ), + // Translators: The placeholder is the taxonomy of the term being updated + 'description' => sprintf( __( 'The ID of the %1$s object to update', 'wp-graphql' ), $taxonomy->graphql_single_name ), + ], ], TermObjectMutation::input_fields( $taxonomy ) ); diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index ac67d302a1..2c72175e77 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -55,6 +55,7 @@ class ClassLoader private $classMap = array(); private $classMapAuthoritative = false; private $missingClasses = array(); + private $apcuPrefix; public function getPrefixes() { @@ -271,6 +272,26 @@ public function isClassMapAuthoritative() return $this->classMapAuthoritative; } + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + /** * Registers this instance as an autoloader. * @@ -313,11 +334,6 @@ public function loadClass($class) */ public function findFile($class) { - // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 - if ('\\' == $class[0]) { - $class = substr($class, 1); - } - // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; @@ -325,6 +341,12 @@ public function findFile($class) if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } $file = $this->findFileWithExtension($class, '.php'); @@ -333,6 +355,10 @@ public function findFile($class) $file = $this->findFileWithExtension($class, '.hh'); } + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; @@ -348,9 +374,13 @@ private function findFileWithExtension($class, $ext) $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { - foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { - if (0 === strpos($class, $prefix)) { - foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + foreach ($this->prefixDirsPsr4[$search] as $dir) { + $length = $this->prefixLengthsPsr4[$first][$search]; if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE index 1a28124886..f27399a042 100644 --- a/vendor/composer/LICENSE +++ b/vendor/composer/LICENSE @@ -1,5 +1,5 @@ -Copyright (c) 2016 Nils Adermann, Jordi Boggiano +Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 3a095eef11..024b8835c0 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -174,6 +174,7 @@ 'WPGraphQL\\Type\\Enum\\PostTypeEnumType' => $baseDir . '/src/Type/Enum/PostTypeEnumType.php', 'WPGraphQL\\Type\\Enum\\RelationEnumType' => $baseDir . '/src/Type/Enum/RelationEnumType.php', 'WPGraphQL\\Type\\Enum\\TaxonomyEnumType' => $baseDir . '/src/Type/Enum/TaxonomyEnumType.php', + 'WPGraphQL\\Type\\MediaItem\\MediaItemType' => $baseDir . '/src/Type/MediaItem/MediaItemType.php', 'WPGraphQL\\Type\\Plugin\\Connection\\PluginConnectionDefinition' => $baseDir . '/src/Type/Plugin/Connection/PluginConnectionDefinition.php', 'WPGraphQL\\Type\\Plugin\\Connection\\PluginConnectionResolver' => $baseDir . '/src/Type/Plugin/Connection/PluginConnectionResolver.php', 'WPGraphQL\\Type\\Plugin\\PluginQuery' => $baseDir . '/src/Type/Plugin/PluginQuery.php', @@ -184,8 +185,9 @@ 'WPGraphQL\\Type\\PostObject\\Connection\\PostObjectConnectionResolver' => $baseDir . '/src/Type/PostObject/Connection/PostObjectConnectionResolver.php', 'WPGraphQL\\Type\\PostObject\\Mutation\\PostObjectCreate' => $baseDir . '/src/Type/PostObject/Mutation/PostObjectCreate.php', 'WPGraphQL\\Type\\PostObject\\Mutation\\PostObjectDelete' => $baseDir . '/src/Type/PostObject/Mutation/PostObjectDelete.php', + 'WPGraphQL\\Type\\PostObject\\Mutation\\PostObjectMutation' => $baseDir . '/src/Type/PostObject/Mutation/PostObjectMutation.php', 'WPGraphQL\\Type\\PostObject\\Mutation\\PostObjectUpdate' => $baseDir . '/src/Type/PostObject/Mutation/PostObjectUpdate.php', - 'WPGraphQL\\Type\\PostObject\\PostObjectMutation' => $baseDir . '/src/Type/PostObject/Mutation/PostObjectMutation.php', + 'WPGraphQL\\Type\\PostObject\\Mutation\\TermObjectDelete' => $baseDir . '/src/Type/TermObject/Mutation/TermObjectDelete.php', 'WPGraphQL\\Type\\PostObject\\PostObjectQuery' => $baseDir . '/src/Type/PostObject/PostObjectQuery.php', 'WPGraphQL\\Type\\PostObject\\PostObjectType' => $baseDir . '/src/Type/PostObject/PostObjectType.php', 'WPGraphQL\\Type\\PostType\\PostTypeType' => $baseDir . '/src/Type/PostType/PostTypeType.php', @@ -195,6 +197,9 @@ 'WPGraphQL\\Type\\TermObject\\Connection\\TermObjectConnectionArgs' => $baseDir . '/src/Type/TermObject/Connection/TermObjectConnectionArgs.php', 'WPGraphQL\\Type\\TermObject\\Connection\\TermObjectConnectionDefinition' => $baseDir . '/src/Type/TermObject/Connection/TermObjectConnectionDefinition.php', 'WPGraphQL\\Type\\TermObject\\Connection\\TermObjectConnectionResolver' => $baseDir . '/src/Type/TermObject/Connection/TermObjectConnectionResolver.php', + 'WPGraphQL\\Type\\TermObject\\Mutation\\TermObjectCreate' => $baseDir . '/src/Type/TermObject/Mutation/TermObjectCreate.php', + 'WPGraphQL\\Type\\TermObject\\Mutation\\TermObjectMutation' => $baseDir . '/src/Type/TermObject/Mutation/TermObjectMutation.php', + 'WPGraphQL\\Type\\TermObject\\Mutation\\TermObjectUpdate' => $baseDir . '/src/Type/TermObject/Mutation/TermObjectUpdate.php', 'WPGraphQL\\Type\\TermObject\\TermObjectQuery' => $baseDir . '/src/Type/TermObject/TermObjectQuery.php', 'WPGraphQL\\Type\\TermObject\\TermObjectType' => $baseDir . '/src/Type/TermObject/TermObjectType.php', 'WPGraphQL\\Type\\Theme\\Connection\\ThemeConnectionDefinition' => $baseDir . '/src/Type/Theme/Connection/ThemeConnectionDefinition.php', diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 6442f51183..c2d3f90c4a 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -23,7 +23,7 @@ public static function getLoader() self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit1e072e261675e0b8440f1da532408811', 'loadClassLoader')); - $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION'); + $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index fcd0769047..e28d1d549c 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -201,6 +201,7 @@ class ComposerStaticInit1e072e261675e0b8440f1da532408811 'WPGraphQL\\Type\\Enum\\PostTypeEnumType' => __DIR__ . '/../..' . '/src/Type/Enum/PostTypeEnumType.php', 'WPGraphQL\\Type\\Enum\\RelationEnumType' => __DIR__ . '/../..' . '/src/Type/Enum/RelationEnumType.php', 'WPGraphQL\\Type\\Enum\\TaxonomyEnumType' => __DIR__ . '/../..' . '/src/Type/Enum/TaxonomyEnumType.php', + 'WPGraphQL\\Type\\MediaItem\\MediaItemType' => __DIR__ . '/../..' . '/src/Type/MediaItem/MediaItemType.php', 'WPGraphQL\\Type\\Plugin\\Connection\\PluginConnectionDefinition' => __DIR__ . '/../..' . '/src/Type/Plugin/Connection/PluginConnectionDefinition.php', 'WPGraphQL\\Type\\Plugin\\Connection\\PluginConnectionResolver' => __DIR__ . '/../..' . '/src/Type/Plugin/Connection/PluginConnectionResolver.php', 'WPGraphQL\\Type\\Plugin\\PluginQuery' => __DIR__ . '/../..' . '/src/Type/Plugin/PluginQuery.php', @@ -211,8 +212,9 @@ class ComposerStaticInit1e072e261675e0b8440f1da532408811 'WPGraphQL\\Type\\PostObject\\Connection\\PostObjectConnectionResolver' => __DIR__ . '/../..' . '/src/Type/PostObject/Connection/PostObjectConnectionResolver.php', 'WPGraphQL\\Type\\PostObject\\Mutation\\PostObjectCreate' => __DIR__ . '/../..' . '/src/Type/PostObject/Mutation/PostObjectCreate.php', 'WPGraphQL\\Type\\PostObject\\Mutation\\PostObjectDelete' => __DIR__ . '/../..' . '/src/Type/PostObject/Mutation/PostObjectDelete.php', + 'WPGraphQL\\Type\\PostObject\\Mutation\\PostObjectMutation' => __DIR__ . '/../..' . '/src/Type/PostObject/Mutation/PostObjectMutation.php', 'WPGraphQL\\Type\\PostObject\\Mutation\\PostObjectUpdate' => __DIR__ . '/../..' . '/src/Type/PostObject/Mutation/PostObjectUpdate.php', - 'WPGraphQL\\Type\\PostObject\\PostObjectMutation' => __DIR__ . '/../..' . '/src/Type/PostObject/Mutation/PostObjectMutation.php', + 'WPGraphQL\\Type\\PostObject\\Mutation\\TermObjectDelete' => __DIR__ . '/../..' . '/src/Type/TermObject/Mutation/TermObjectDelete.php', 'WPGraphQL\\Type\\PostObject\\PostObjectQuery' => __DIR__ . '/../..' . '/src/Type/PostObject/PostObjectQuery.php', 'WPGraphQL\\Type\\PostObject\\PostObjectType' => __DIR__ . '/../..' . '/src/Type/PostObject/PostObjectType.php', 'WPGraphQL\\Type\\PostType\\PostTypeType' => __DIR__ . '/../..' . '/src/Type/PostType/PostTypeType.php', @@ -222,6 +224,9 @@ class ComposerStaticInit1e072e261675e0b8440f1da532408811 'WPGraphQL\\Type\\TermObject\\Connection\\TermObjectConnectionArgs' => __DIR__ . '/../..' . '/src/Type/TermObject/Connection/TermObjectConnectionArgs.php', 'WPGraphQL\\Type\\TermObject\\Connection\\TermObjectConnectionDefinition' => __DIR__ . '/../..' . '/src/Type/TermObject/Connection/TermObjectConnectionDefinition.php', 'WPGraphQL\\Type\\TermObject\\Connection\\TermObjectConnectionResolver' => __DIR__ . '/../..' . '/src/Type/TermObject/Connection/TermObjectConnectionResolver.php', + 'WPGraphQL\\Type\\TermObject\\Mutation\\TermObjectCreate' => __DIR__ . '/../..' . '/src/Type/TermObject/Mutation/TermObjectCreate.php', + 'WPGraphQL\\Type\\TermObject\\Mutation\\TermObjectMutation' => __DIR__ . '/../..' . '/src/Type/TermObject/Mutation/TermObjectMutation.php', + 'WPGraphQL\\Type\\TermObject\\Mutation\\TermObjectUpdate' => __DIR__ . '/../..' . '/src/Type/TermObject/Mutation/TermObjectUpdate.php', 'WPGraphQL\\Type\\TermObject\\TermObjectQuery' => __DIR__ . '/../..' . '/src/Type/TermObject/TermObjectQuery.php', 'WPGraphQL\\Type\\TermObject\\TermObjectType' => __DIR__ . '/../..' . '/src/Type/TermObject/TermObjectType.php', 'WPGraphQL\\Type\\Theme\\Connection\\ThemeConnectionDefinition' => __DIR__ . '/../..' . '/src/Type/Theme/Connection/ThemeConnectionDefinition.php', From 1dbe02471e487fd624e32a0c72aac905391d793b Mon Sep 17 00:00:00 2001 From: jasonbahl Date: Thu, 15 Jun 2017 08:10:37 -0600 Subject: [PATCH 3/6] #6 - TermObject Mutations - Removed an unnecessary exception that was added in my last commit --- src/Type/PostObject/Mutation/PostObjectMutation.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Type/PostObject/Mutation/PostObjectMutation.php b/src/Type/PostObject/Mutation/PostObjectMutation.php index 411fe53d29..649d563b1f 100644 --- a/src/Type/PostObject/Mutation/PostObjectMutation.php +++ b/src/Type/PostObject/Mutation/PostObjectMutation.php @@ -207,9 +207,7 @@ public static function prepare_post_object( $input, $post_type_object, $mutation $parent_id_parts = ! empty( $input['parentId'] ) ? Relay::fromGlobalId( $input['parentId'] ) : null; if ( is_array( $parent_id_parts ) && ! empty( $parent_id_parts['id'] ) && is_int( $parent_id_parts['id'] ) ) { $insert_post_args['post_parent'] = absint( $parent_id_parts['id'] ); - } else { - throw new \Exception( __( 'The parent ID is not a valid ID', 'wp-graphql' ) ); - } // End if(). + } if ( ! empty( $input['menuOrder'] ) ) { $insert_post_args['menu_order'] = $input['menuOrder']; From 28062e0fc5c129cfb3ed23647e49603f6a4e04ed Mon Sep 17 00:00:00 2001 From: jasonbahl Date: Thu, 15 Jun 2017 10:25:55 -0600 Subject: [PATCH 4/6] #6 TermObjectMutations - Adding unit tests --- tests/test-term-object-mutations.php | 209 +++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 tests/test-term-object-mutations.php diff --git a/tests/test-term-object-mutations.php b/tests/test-term-object-mutations.php new file mode 100644 index 0000000000..82b7c498fe --- /dev/null +++ b/tests/test-term-object-mutations.php @@ -0,0 +1,209 @@ +category_name = 'Test Category'; + $this->tag_name = 'Test Tag'; + $this->description = 'Test Term Description'; + $this->client_mutation_id = 'someUniqueId'; + + $this->admin = $this->factory->user->create([ + 'role' => 'administrator', + ]); + + $this->subscriber = $this->factory->user->create([ + 'role' => 'subscriber', + ]); + + parent::setUp(); + + } + + public function tearDown() { + parent::tearDown(); + } + + /** + * Function that executes the mutation + */ + public function createCategoryMutation() { + + $mutation = ' + mutation createCategory( $clientMutationId:String!, $title:String!, $content:String! ) { + createCategory( + input: { + clientMutationId: $clientMutationId + name: $title + description: $description + } + ) { + clientMutationId + category { + name + description + } + } + } + '; + + $variables = wp_json_encode([ + 'clientMutationId' => $this->client_mutation_id, + 'name' => $this->category_name, + 'description' => $this->description, + ]); + + return do_graphql_request( $mutation, 'createCategory', $variables ); + + } + + /** + * Function that executes the mutation + */ + public function createTagMutation() { + + $mutation = ' + mutation createTag( $clientMutationId:String!, $title:String!, $content:String! ) { + createTag( + input: { + clientMutationId: $clientMutationId + name: $title + description: $description + } + ) { + clientMutationId + postTag { + name + description + } + } + } + '; + + $variables = wp_json_encode([ + 'clientMutationId' => $this->client_mutation_id, + 'name' => $this->tag_name, + 'description' => $this->description, + ]); + + return do_graphql_request( $mutation, 'createTag', $variables ); + + } + + /** + * Test creating a category + */ + public function testCreateCategory() { + + /** + * Set the current user as a subscriber, who deosn't have permission + * to create terms + */ + wp_set_current_user( $this->admin ); + + /** + * Run the mutation + */ + $actual = $this->createCategoryMutation(); + + $expected = [ + 'data' => [ + 'clientMutationId' => $this->client_mutation_id, + 'category' => [ + 'name' => $this->tag_name, + 'description' => $this->description, + ], + ], + ]; + + $this->assertEquals( $actual, $expected ); + } + + /** + * Test creating a tag + */ + public function testCreateTag() { + + /** + * Set the current user as a subscriber, who deosn't have permission + * to create terms + */ + wp_set_current_user( $this->admin ); + + /** + * Run the mutation + */ + $actual = $this->createTagMutation(); + + $expected = [ + 'data' => [ + 'clientMutationId' => $this->client_mutation_id, + 'postTag' => [ + 'name' => $this->tag_name, + 'description' => $this->description, + ], + ], + ]; + + $this->assertEquals( $actual, $expected ); + } + + /** + * Test creating a tag without proper capabilitites + */ + public function testCreateTagWithoutProperCapabilities() { + + /** + * Set the current user as a subscriber, who deosn't have permission + * to create terms + */ + wp_set_current_user( $this->subscriber ); + + /** + * Run the mutation + */ + $actual = $this->createTagMutation(); + + /** + * We're asserting that this will properly return an error + * because this user doesn't have permissions to create a term as a + * subscriber + */ + $this->assertNotEmpty( $actual['errors'] ); + + } + + /** + * Test creating a category without proper capabilitites + */ + public function testCreateCategoryWithoutProperCapabilities() { + + /** + * Set the current user as a subscriber, who deosn't have permission + * to create terms + */ + wp_set_current_user( $this->subscriber ); + + /** + * Run the mutation + */ + $actual = $this->createCategoryMutation(); + + /** + * We're asserting that this will properly return an error + * because this user doesn't have permissions to create a term as a + * subscriber + */ + $this->assertNotEmpty( $actual['errors'] ); + + } + +} From 4cb681045a79f7c23d1f3577a67a976b36dfb45f Mon Sep 17 00:00:00 2001 From: jasonbahl Date: Thu, 15 Jun 2017 11:43:36 -0600 Subject: [PATCH 5/6] #6 TermObjectMutations - Adding unit tests - Fixed a bug in termObjectDelete that allowed terms of other types to be deleted if their ID was passed instead of an ID of the type being deleted --- .../TermObject/Mutation/TermObjectDelete.php | 8 + tests/test-term-object-mutations.php | 282 ++++++++++++++++-- 2 files changed, 259 insertions(+), 31 deletions(-) diff --git a/src/Type/TermObject/Mutation/TermObjectDelete.php b/src/Type/TermObject/Mutation/TermObjectDelete.php index 3e8e537d8d..159dc0428f 100644 --- a/src/Type/TermObject/Mutation/TermObjectDelete.php +++ b/src/Type/TermObject/Mutation/TermObjectDelete.php @@ -74,6 +74,14 @@ public static function mutate( \WP_Taxonomy $taxonomy ) { throw new \Exception( sprintf( __( 'The ID for the %1$s was not valid', 'wp-graphql' ), $taxonomy->graphql_single_name ) ); } + /** + * Ensure the type for the Global ID matches the type being mutated + */ + if ( empty( $id_parts['type'] ) || $taxonomy->name !== $id_parts['type'] ) { + // Translators: The placeholder is the name of the taxonomy for the term being edited + throw new \Exception( sprintf( __( 'The ID passed is not for a %1$s object', 'wp-graphql' ), $taxonomy->graphql_single_name ) ); + } + /** * Get the term before deleting it */ diff --git a/tests/test-term-object-mutations.php b/tests/test-term-object-mutations.php index 82b7c498fe..457479c14d 100644 --- a/tests/test-term-object-mutations.php +++ b/tests/test-term-object-mutations.php @@ -5,6 +5,7 @@ class Test_Term_Object_Mutations extends WP_UnitTestCase { public $category_name; public $tag_name; public $description; + public $description_update; public $client_mutation_id; public $admin; public $subscriber; @@ -14,6 +15,7 @@ public function setUp() { $this->category_name = 'Test Category'; $this->tag_name = 'Test Tag'; $this->description = 'Test Term Description'; + $this->description_update = 'Description Update'; $this->client_mutation_id = 'someUniqueId'; $this->admin = $this->factory->user->create([ @@ -38,16 +40,17 @@ public function tearDown() { public function createCategoryMutation() { $mutation = ' - mutation createCategory( $clientMutationId:String!, $title:String!, $content:String! ) { + mutation createCategory( $clientMutationId:String!, $name:String!, $description:String ) { createCategory( input: { clientMutationId: $clientMutationId - name: $title + name: $name description: $description } ) { clientMutationId category { + id name description } @@ -71,16 +74,17 @@ public function createCategoryMutation() { public function createTagMutation() { $mutation = ' - mutation createTag( $clientMutationId:String!, $title:String!, $content:String! ) { - createTag( + mutation createTag( $clientMutationId:String!, $name:String!, $description:String ) { + createPostTag( input: { clientMutationId: $clientMutationId - name: $title + name: $name description: $description } ) { clientMutationId postTag { + id name description } @@ -98,13 +102,144 @@ public function createTagMutation() { } + /** + * Update Tag + * + * @param string $id The ID of the term to update + * @return array + */ + public function updateTagMutation( $id ) { + + $mutation = ' + mutation updateTag( $clientMutationId:String!, $id:ID! $description:String ) { + updatePostTag( + input: { + clientMutationId: $clientMutationId + description: $description + id: $id + } + ) { + clientMutationId + postTag { + id + name + description + } + } + } + '; + + $variables = wp_json_encode([ + 'clientMutationId' => $this->client_mutation_id, + 'id' => $id, + 'description' => $this->description_update, + ]); + + return do_graphql_request( $mutation, 'updateTag', $variables ); + + } + + public function deleteTagMutation( $id ) { + + $mutation = ' + mutation deleteTag( $clientMutationId:String!, $id:ID! ) { + deletePostTag( + input: { + id: $id + clientMutationId: $clientMutationId + } + ) { + clientMutationId + postTag { + id + name + } + } + } + '; + + $variables = wp_json_encode([ + 'id' => $id, + 'clientMutationId' => $this->client_mutation_id, + ]); + + return do_graphql_request( $mutation, 'deleteTag', $variables ); + + } + + /** + * Update Category + * + * @param string $id The ID of the term to update + * @return array + */ + public function updateCategoryMutation( $id ) { + + $mutation = ' + mutation updateCategory( $clientMutationId:String!, $id:ID! $description:String ) { + updateCategory( + input: { + clientMutationId: $clientMutationId + description: $description + id: $id + } + ) { + clientMutationId + category { + id + name + description + } + } + } + '; + + $variables = wp_json_encode([ + 'clientMutationId' => $this->client_mutation_id, + 'id' => $id, + 'description' => $this->description_update, + ]); + + return do_graphql_request( $mutation, 'updateCategory', $variables ); + + } + + public function deleteCategoryMutation( $id ) { + + $mutation = ' + mutation deleteCategory( $clientMutationId:String!, $id:ID! ) { + deleteCategory( + input: { + id: $id + clientMutationId: $clientMutationId + } + ) { + clientMutationId + category { + id + name + } + } + } + '; + + $variables = wp_json_encode([ + 'id' => $id, + 'clientMutationId' => $this->client_mutation_id, + ]); + + return do_graphql_request( $mutation, 'deleteCategory', $variables ); + + } + + /** * Test creating a category */ - public function testCreateCategory() { + public function testCategoryMutations() { /** - * Set the current user as a subscriber, who deosn't have permission + * Set the current user as a subscriber, who doesn't have permission * to create terms */ wp_set_current_user( $this->admin ); @@ -114,26 +249,68 @@ public function testCreateCategory() { */ $actual = $this->createCategoryMutation(); - $expected = [ - 'data' => [ - 'clientMutationId' => $this->client_mutation_id, - 'category' => [ - 'name' => $this->tag_name, - 'description' => $this->description, - ], - ], - ]; - - $this->assertEquals( $actual, $expected ); + /** + * Assert that the created tag is what it should be + */ + $this->assertEquals( $actual['data']['createCategory']['clientMutationId'], $this->client_mutation_id ); + $this->assertNotEmpty( $actual['data']['createCategory']['category']['id'] ); + $id_parts = \GraphQLRelay\Relay::fromGlobalId( $actual['data']['createCategory']['category']['id'] ); + $this->assertEquals( $id_parts['type'], 'category' ); + $this->assertNotEmpty( $id_parts['id'] ); + $this->assertEquals( $actual['data']['createCategory']['category']['name'], $this->category_name ); + $this->assertEquals( $actual['data']['createCategory']['category']['description'], $this->description ); + + /** + * Try to update a Tag using a category ID, which should return errors + */ + $try_to_update_tag = $this->updateTagMutation( $actual['data']['createCategory']['category']['id'] ); + $this->assertNotEmpty( $try_to_update_tag['errors'] ); + + /** + * Try to update a Tag using a category ID, which should return errors + */ + $try_to_delete_tag = $this->deleteTagMutation( $actual['data']['createCategory']['category']['id'] ); + $this->assertNotEmpty( $try_to_delete_tag['errors'] ); + + /** + * Run the update mutation with the ID of the created tag + */ + $updated_category = $this->updateCategoryMutation( $actual['data']['createCategory']['category']['id'] ); + + /** + * Make some assertions on the response + */ + $this->assertEquals( $updated_category['data']['updateCategory']['clientMutationId'], $this->client_mutation_id ); + $this->assertNotEmpty( $updated_category['data']['updateCategory']['category']['id'] ); + $id_parts = \GraphQLRelay\Relay::fromGlobalId( $updated_category['data']['updateCategory']['category']['id'] ); + $this->assertEquals( $id_parts['type'], 'category' ); + $this->assertNotEmpty( $id_parts['id'] ); + $this->assertEquals( $updated_category['data']['updateCategory']['category']['name'], $this->category_name ); + $this->assertEquals( $updated_category['data']['updateCategory']['category']['description'], $this->description_update ); + + /** + * Delete the tag + */ + $deleted_category = $this->deleteCategoryMutation( $updated_category['data']['updateCategory']['category']['id'] ); + + /** + * Make some assertions on the response + */ + $this->assertNotEmpty( $deleted_category ); + $this->assertEquals( $deleted_category['data']['deleteCategory']['clientMutationId'], $this->client_mutation_id ); + $id_parts = \GraphQLRelay\Relay::fromGlobalId( $deleted_category['data']['deleteCategory']['category']['id'] ); + $this->assertEquals( $id_parts['type'], 'category' ); + $this->assertNotEmpty( $id_parts['id'] ); + $this->assertEquals( $deleted_category['data']['deleteCategory']['category']['name'], $this->category_name ); } /** * Test creating a tag */ - public function testCreateTag() { + public function testTagMutations() { /** - * Set the current user as a subscriber, who deosn't have permission + * Set the current user as a subscriber, who doesn't have permission * to create terms */ wp_set_current_user( $this->admin ); @@ -143,17 +320,60 @@ public function testCreateTag() { */ $actual = $this->createTagMutation(); - $expected = [ - 'data' => [ - 'clientMutationId' => $this->client_mutation_id, - 'postTag' => [ - 'name' => $this->tag_name, - 'description' => $this->description, - ], - ], - ]; - - $this->assertEquals( $actual, $expected ); + /** + * Assert that the created tag is what it should be + */ + $this->assertEquals( $actual['data']['createPostTag']['clientMutationId'], $this->client_mutation_id ); + $this->assertNotEmpty( $actual['data']['createPostTag']['postTag']['id'] ); + $id_parts = \GraphQLRelay\Relay::fromGlobalId( $actual['data']['createPostTag']['postTag']['id'] ); + $this->assertEquals( $id_parts['type'], 'post_tag' ); + $this->assertNotEmpty( $id_parts['id'] ); + $this->assertEquals( $actual['data']['createPostTag']['postTag']['name'], $this->tag_name ); + $this->assertEquals( $actual['data']['createPostTag']['postTag']['description'], $this->description ); + + /** + * Try to update a Cagegory using a Tag ID, which should return errors + */ + $try_to_update_tag = $this->updateCategoryMutation( $actual['data']['createPostTag']['category']['id'] ); + $this->assertNotEmpty( $try_to_update_tag['errors'] ); + + /** + * Try to update a Category using a Tag ID, which should return errors + */ + $try_to_delete_tag = $this->deleteCategoryMutation( $actual['data']['createCategory']['category']['id'] ); + $this->assertNotEmpty( $try_to_delete_tag['errors'] ); + + /** + * Run the update mutation with the ID of the created tag + */ + $updated_tag = $this->updateTagMutation( $actual['data']['createPostTag']['postTag']['id'] ); + + /** + * Make some assertions on the response + */ + $this->assertEquals( $updated_tag['data']['updatePostTag']['clientMutationId'], $this->client_mutation_id ); + $this->assertNotEmpty( $updated_tag['data']['updatePostTag']['postTag']['id'] ); + $id_parts = \GraphQLRelay\Relay::fromGlobalId( $updated_tag['data']['updatePostTag']['postTag']['id'] ); + $this->assertEquals( $id_parts['type'], 'post_tag' ); + $this->assertNotEmpty( $id_parts['id'] ); + $this->assertEquals( $updated_tag['data']['updatePostTag']['postTag']['name'], $this->tag_name ); + $this->assertEquals( $updated_tag['data']['updatePostTag']['postTag']['description'], $this->description_update ); + + /** + * Delete the tag + */ + $deleted_tag = $this->deleteTagMutation( $updated_tag['data']['updatePostTag']['postTag']['id'] ); + + /** + * Make some assertions on the response + */ + $this->assertNotEmpty( $deleted_tag ); + $this->assertEquals( $deleted_tag['data']['deletePostTag']['clientMutationId'], $this->client_mutation_id ); + $id_parts = \GraphQLRelay\Relay::fromGlobalId( $deleted_tag['data']['deletePostTag']['postTag']['id'] ); + $this->assertEquals( $id_parts['type'], 'post_tag' ); + $this->assertNotEmpty( $id_parts['id'] ); + $this->assertEquals( $deleted_tag['data']['deletePostTag']['postTag']['name'], $this->tag_name ); + } /** From 5c54f391412c48556baf5129702511c22945700c Mon Sep 17 00:00:00 2001 From: jasonbahl Date: Thu, 15 Jun 2017 11:46:34 -0600 Subject: [PATCH 6/6] #6 TermObjectMutations - Adding unit tests - Fixed a bug in termObjectDelete that allowed terms of other types to be deleted if their ID was passed instead of an ID of the type being deleted --- tests/test-term-object-mutations.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test-term-object-mutations.php b/tests/test-term-object-mutations.php index 457479c14d..76042050c2 100644 --- a/tests/test-term-object-mutations.php +++ b/tests/test-term-object-mutations.php @@ -334,14 +334,14 @@ public function testTagMutations() { /** * Try to update a Cagegory using a Tag ID, which should return errors */ - $try_to_update_tag = $this->updateCategoryMutation( $actual['data']['createPostTag']['category']['id'] ); - $this->assertNotEmpty( $try_to_update_tag['errors'] ); + $try_to_update_category = $this->updateCategoryMutation( $actual['data']['createPostTag']['postTag']['id'] ); + $this->assertNotEmpty( $try_to_update_category['errors'] ); /** * Try to update a Category using a Tag ID, which should return errors */ - $try_to_delete_tag = $this->deleteCategoryMutation( $actual['data']['createCategory']['category']['id'] ); - $this->assertNotEmpty( $try_to_delete_tag['errors'] ); + $try_to_delete_category = $this->deleteCategoryMutation( $actual['data']['createPostTag']['postTag']['id'] ); + $this->assertNotEmpty( $try_to_delete_category['errors'] ); /** * Run the update mutation with the ID of the created tag