HEX
Server: Apache/2.4.66 (Debian)
System: Linux 6dfabc3b2241 6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:52:38 UTC 2025 x86_64
User: (1000)
PHP: 8.3.30
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/wp-graphql/src/Mutation/PostObjectDelete.php
<?php

namespace WPGraphQL\Mutation;

use GraphQL\Error\UserError;
use GraphQLRelay\Relay;
use WPGraphQL\Data\PostObjectMutation;
use WPGraphQL\Model\Post;
use WPGraphQL\Utils\Utils;
use WP_Post_Type;

class PostObjectDelete {
	/**
	 * Registers the PostObjectDelete mutation.
	 *
	 * @param \WP_Post_Type $post_type_object The post type of the mutation.
	 *
	 * @return void
	 * @throws \Exception
	 */
	public static function register_mutation( WP_Post_Type $post_type_object ) {
		$mutation_name = 'delete' . ucwords( $post_type_object->graphql_single_name );

		register_graphql_mutation(
			$mutation_name,
			[
				'inputFields'         => self::get_input_fields( $post_type_object ),
				'outputFields'        => self::get_output_fields( $post_type_object ),
				'mutateAndGetPayload' => self::mutate_and_get_payload( $post_type_object, $mutation_name ),
			]
		);
	}

	/**
	 * Defines the mutation input field configuration.
	 *
	 * @param \WP_Post_Type $post_type_object The post type of the mutation.
	 *
	 * @return array<string,array<string,mixed>>
	 */
	public static function get_input_fields( $post_type_object ) {
		return [
			'id'             => [
				'type'        => [
					'non_null' => 'ID',
				],
				'description' => static function () use ( $post_type_object ) {
					// translators: The placeholder is the name of the post's post_type being deleted
					return sprintf( __( 'The ID of the %1$s to delete', 'wp-graphql' ), $post_type_object->graphql_single_name );
				},
			],
			'forceDelete'    => [
				'type'        => 'Boolean',
				'description' => static function () {
					return __( 'Whether the object should be force deleted instead of being moved to the trash', 'wp-graphql' );
				},
			],
			'ignoreEditLock' => [
				'type'        => 'Boolean',
				'description' => static function () {
					return __( 'Override the edit lock when another user is editing the post', 'wp-graphql' );
				},
			],
		];
	}

	/**
	 * Defines the mutation output field configuration.
	 *
	 * @param \WP_Post_Type $post_type_object The post type of the mutation.
	 *
	 * @return array<string,array<string,mixed>>
	 */
	public static function get_output_fields( WP_Post_Type $post_type_object ) {
		return [
			'deletedId'                            => [
				'type'        => 'ID',
				'description' => static function () {
					return __( 'The ID of the deleted object', 'wp-graphql' );
				},
				'resolve'     => static function ( $payload ) {
					/** @var \WPGraphQL\Model\Post $deleted */
					$deleted = $payload['postObject'];

					return ! empty( $deleted->ID ) ? Relay::toGlobalId( 'post', (string) $deleted->ID ) : null;
				},
			],
			$post_type_object->graphql_single_name => [
				'type'        => $post_type_object->graphql_single_name,
				'description' => static function () {
					return __( 'The object before it was deleted', 'wp-graphql' );
				},
				'resolve'     => static function ( $payload ) {
					/** @var \WPGraphQL\Model\Post $deleted */
					$deleted = $payload['postObject'];

					return ! empty( $deleted->ID ) ? $deleted : null;
				},
			],
		];
	}

	/**
	 * Defines the mutation data modification closure.
	 *
	 * @param \WP_Post_Type $post_type_object The post type of the mutation.
	 * @param string        $mutation_name    The mutation name.
	 *
	 * @return callable(array<string,mixed>$input,\WPGraphQL\AppContext $context,\GraphQL\Type\Definition\ResolveInfo $info):array<string,mixed>
	 */
	public static function mutate_and_get_payload( WP_Post_Type $post_type_object, string $mutation_name ) {
		return static function ( $input ) use ( $post_type_object ) {
			// Get the database ID for the post.
			$post_id = Utils::get_database_id_from_id( $input['id'] );

			/**
			 * Stop now if a user isn't allowed to delete a post
			 */
			if ( ! isset( $post_type_object->cap->delete_post ) || ! current_user_can( $post_type_object->cap->delete_post, $post_id ) ) {
				// translators: the $post_type_object->graphql_plural_name placeholder is the name of the object being mutated
				throw new UserError( esc_html( sprintf( __( 'Sorry, you are not allowed to delete %1$s', 'wp-graphql' ), $post_type_object->graphql_plural_name ) ) );
			}

			/**
			 * Check if we should force delete or not
			 */
			$force_delete = ! empty( $input['forceDelete'] ) && true === $input['forceDelete'];

			/**
			 * Get the post object before deleting it
			 */
			$post_before_delete = ! empty( $post_id ) ? get_post( $post_id ) : null;

			if ( empty( $post_before_delete ) ) {
				throw new UserError( esc_html__( 'The post could not be deleted', 'wp-graphql' ) );
			}

			$post_before_delete = new Post( $post_before_delete );

			/**
			 * If the post is already in the trash, and the forceDelete input was not passed,
			 * don't remove from the trash
			 */
			if ( 'trash' === $post_before_delete->status && true !== $force_delete ) {
				// Translators: the first placeholder is the post_type of the object being deleted and the second placeholder is the unique ID of that object
				throw new UserError( esc_html( sprintf( __( 'The %1$s with id %2$s is already in the trash. To remove from the trash, use the forceDelete input', 'wp-graphql' ), $post_type_object->graphql_single_name, $post_id ) ) );
			}

			// If post is locked and the override is not specified, do not allow the edit
			$locked_user_id = PostObjectMutation::check_edit_lock( $post_id, $input );
			if ( false !== $locked_user_id ) {
				$user         = get_userdata( (int) $locked_user_id );
				$display_name = isset( $user->display_name ) ? $user->display_name : 'unknown';
				/* translators: %s: User's display name. */
				throw new UserError( esc_html( sprintf( __( 'You cannot delete this item. %s is currently editing.', 'wp-graphql' ), $display_name ) ) );
			}

			/**
			 * Delete the post
			 */
			$deleted = wp_delete_post( (int) $post_id, $force_delete );

			/**
			 * If the post was moved to the trash, spoof the object's status before returning it
			 */
			$post_before_delete->status = ( false !== $deleted && true !== $force_delete ) ? 'trash' : $post_before_delete->status;

			/**
			 * Return the deletedId and the object before it was deleted
			 */
			return [
				'postObject' => $post_before_delete,
			];
		};
	}
}