• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wp-graphql / wp-graphql / 19508918426

19 Nov 2025 04:36PM UTC coverage: 83.612% (+0.4%) from 83.228%
19508918426

push

github

actions-user
release: merge develop into master for v2.5.2

16235 of 19417 relevant lines covered (83.61%)

257.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.24
/src/Mutation/MediaItemCreate.php
1
<?php
2

3
namespace WPGraphQL\Mutation;
4

5
use GraphQL\Error\UserError;
6
use GraphQL\Type\Definition\ResolveInfo;
7
use WPGraphQL\AppContext;
8
use WPGraphQL\Data\MediaItemMutation;
9
use WPGraphQL\Utils\Utils;
10

11
class MediaItemCreate {
12
        /**
13
         * Registers the MediaItemCreate mutation.
14
         *
15
         * @return void
16
         * @throws \Exception
17
         */
18
        public static function register_mutation() {
614✔
19
                register_graphql_mutation(
614✔
20
                        'createMediaItem',
614✔
21
                        [
614✔
22
                                'inputFields'         => self::get_input_fields(),
614✔
23
                                'outputFields'        => self::get_output_fields(),
614✔
24
                                'mutateAndGetPayload' => self::mutate_and_get_payload(),
614✔
25
                        ]
614✔
26
                );
614✔
27
        }
28

29
        /**
30
         * Defines the mutation input field configuration.
31
         *
32
         * @return array<string,array<string,mixed>>
33
         */
34
        public static function get_input_fields() {
614✔
35
                return [
614✔
36
                        'altText'       => [
614✔
37
                                'type'        => 'String',
614✔
38
                                'description' => static function () {
614✔
39
                                        return __( 'Alternative text to display when mediaItem is not displayed', 'wp-graphql' );
15✔
40
                                },
614✔
41
                        ],
614✔
42
                        'authorId'      => [
614✔
43
                                'type'        => 'ID',
614✔
44
                                'description' => static function () {
614✔
45
                                        return __( 'The userId to assign as the author of the mediaItem', 'wp-graphql' );
15✔
46
                                },
614✔
47
                        ],
614✔
48
                        'caption'       => [
614✔
49
                                'type'        => 'String',
614✔
50
                                'description' => static function () {
614✔
51
                                        return __( 'The caption for the mediaItem', 'wp-graphql' );
15✔
52
                                },
614✔
53
                        ],
614✔
54
                        'commentStatus' => [
614✔
55
                                'type'        => 'String',
614✔
56
                                'description' => static function () {
614✔
57
                                        return __( 'The comment status for the mediaItem', 'wp-graphql' );
15✔
58
                                },
614✔
59
                        ],
614✔
60
                        'date'          => [
614✔
61
                                'type'        => 'String',
614✔
62
                                'description' => static function () {
614✔
63
                                        return __( 'The date of the mediaItem', 'wp-graphql' );
15✔
64
                                },
614✔
65
                        ],
614✔
66
                        'dateGmt'       => [
614✔
67
                                'type'        => 'String',
614✔
68
                                'description' => static function () {
614✔
69
                                        return __( 'The date (in GMT zone) of the mediaItem', 'wp-graphql' );
15✔
70
                                },
614✔
71
                        ],
614✔
72
                        'description'   => [
614✔
73
                                'type'        => 'String',
614✔
74
                                'description' => static function () {
614✔
75
                                        return __( 'Description of the mediaItem', 'wp-graphql' );
15✔
76
                                },
614✔
77
                        ],
614✔
78
                        'filePath'      => [
614✔
79
                                'type'        => 'String',
614✔
80
                                'description' => static function () {
614✔
81
                                        return __( 'The file name of the mediaItem', 'wp-graphql' );
15✔
82
                                },
614✔
83
                        ],
614✔
84
                        'fileType'      => [
614✔
85
                                'type'        => 'MimeTypeEnum',
614✔
86
                                'description' => static function () {
614✔
87
                                        return __( 'The file type of the mediaItem', 'wp-graphql' );
15✔
88
                                },
614✔
89
                        ],
614✔
90
                        'slug'          => [
614✔
91
                                'type'        => 'String',
614✔
92
                                'description' => static function () {
614✔
93
                                        return __( 'The slug of the mediaItem', 'wp-graphql' );
15✔
94
                                },
614✔
95
                        ],
614✔
96
                        'status'        => [
614✔
97
                                'type'        => 'MediaItemStatusEnum',
614✔
98
                                'description' => static function () {
614✔
99
                                        return __( 'The status of the mediaItem', 'wp-graphql' );
15✔
100
                                },
614✔
101
                        ],
614✔
102
                        'title'         => [
614✔
103
                                'type'        => 'String',
614✔
104
                                'description' => static function () {
614✔
105
                                        return __( 'The title of the mediaItem', 'wp-graphql' );
15✔
106
                                },
614✔
107
                        ],
614✔
108
                        'pingStatus'    => [
614✔
109
                                'type'        => 'String',
614✔
110
                                'description' => static function () {
614✔
111
                                        return __( 'The ping status for the mediaItem', 'wp-graphql' );
15✔
112
                                },
614✔
113
                        ],
614✔
114
                        'parentId'      => [
614✔
115
                                'type'        => 'ID',
614✔
116
                                'description' => static function () {
614✔
117
                                        return __( 'The ID of the parent object', 'wp-graphql' );
15✔
118
                                },
614✔
119
                        ],
614✔
120
                ];
614✔
121
        }
122

123
        /**
124
         * Defines the mutation output field configuration.
125
         *
126
         * @return array<string,array<string,mixed>>
127
         */
128
        public static function get_output_fields() {
614✔
129
                return [
614✔
130
                        'mediaItem' => [
614✔
131
                                'type'        => 'MediaItem',
614✔
132
                                'description' => static function () {
614✔
133
                                        return __( 'The MediaItem object mutation type.', 'wp-graphql' );
15✔
134
                                },
614✔
135
                                'resolve'     => static function ( $payload, $args, AppContext $context ) {
614✔
136
                                        if ( empty( $payload['postObjectId'] ) || ! absint( $payload['postObjectId'] ) ) {
7✔
137
                                                return null;
×
138
                                        }
139

140
                                        return $context->get_loader( 'post' )->load_deferred( $payload['postObjectId'] );
7✔
141
                                },
614✔
142
                        ],
614✔
143
                ];
614✔
144
        }
145

146
        /**
147
         * Defines the mutation data modification closure.
148
         *
149
         * @return callable(array<string,mixed>$input,\WPGraphQL\AppContext $context,\GraphQL\Type\Definition\ResolveInfo $info):array<string,mixed>
150
         */
151
        public static function mutate_and_get_payload() {
614✔
152
                return static function ( $input, AppContext $context, ResolveInfo $info ) {
614✔
153
                        /**
154
                         * Stop now if a user isn't allowed to upload a mediaItem
155
                         */
156
                        if ( ! current_user_can( 'upload_files' ) ) {
12✔
157
                                throw new UserError( esc_html__( 'Sorry, you are not allowed to upload mediaItems', 'wp-graphql' ) );
1✔
158
                        }
159

160
                        $post_type_object = get_post_type_object( 'attachment' );
11✔
161
                        if ( empty( $post_type_object ) ) {
11✔
162
                                throw new UserError( esc_html__( 'The Media Item could not be created', 'wp-graphql' ) );
×
163
                        }
164

165
                        /**
166
                         * If the mediaItem being created is being assigned to another user that's not the current user, make sure
167
                         * the current user has permission to edit others mediaItems
168
                         */
169
                        if ( ! empty( $input['authorId'] ) ) {
11✔
170
                                // Ensure authorId is a valid databaseId.
171
                                $input['authorId'] = Utils::get_database_id_from_id( $input['authorId'] );
8✔
172

173
                                // Bail if can't edit other users' attachments.
174
                                if ( get_current_user_id() !== $input['authorId'] && ( ! isset( $post_type_object->cap->edit_others_posts ) || ! current_user_can( $post_type_object->cap->edit_others_posts ) ) ) {
8✔
175
                                        throw new UserError( esc_html__( 'Sorry, you are not allowed to create mediaItems as this user', 'wp-graphql' ) );
5✔
176
                                }
177
                        }
178

179
                        /**
180
                         * Set the file name, whether it's a local file or from a URL.
181
                         * Then set the url for the uploaded file
182
                         */
183
                        $file_name           = basename( $input['filePath'] );
7✔
184
                        $uploaded_file_url   = (string) $input['filePath'];
7✔
185
                        $sanitized_file_path = sanitize_file_name( $input['filePath'] );
7✔
186

187
                        // Check that the filetype is allowed
188
                        $check_file = wp_check_filetype( $sanitized_file_path );
7✔
189

190
                        // if the file doesn't pass the check, throw an error
191
                        if ( ! $check_file['ext'] || ! $check_file['type'] || ! wp_http_validate_url( $uploaded_file_url ) ) {
7✔
192
                                // translators: %s is the file path.
193
                                throw new UserError( esc_html( sprintf( __( 'Invalid filePath "%s"', 'wp-graphql' ), $input['filePath'] ) ) );
3✔
194
                        }
195

196
                        $protocol = wp_parse_url( $input['filePath'], PHP_URL_SCHEME );
4✔
197

198
                        // prevent the filePath from being submitted with a non-allowed protocols
199
                        $allowed_protocols = [ 'https', 'http', 'file' ];
4✔
200

201
                        /**
202
                         * Filter the allowed protocols for the mutation
203
                         *
204
                         * @param string[]                             $allowed_protocols The allowed protocols for filePaths to be submitted
205
                         * @param mixed                                $protocol          The current protocol of the filePath
206
                         * @param array<string,mixed>                  $input             The input of the current mutation
207
                         * @param \WPGraphQL\AppContext                $context           The context of the current request
208
                         * @param \GraphQL\Type\Definition\ResolveInfo $info              The ResolveInfo of the current field
209
                         */
210
                        $allowed_protocols = apply_filters( 'graphql_media_item_create_allowed_protocols', $allowed_protocols, $protocol, $input, $context, $info );
4✔
211

212
                        if ( ! in_array( $protocol, $allowed_protocols, true ) ) {
4✔
213
                                throw new UserError(
×
214
                                        esc_html(
×
215
                                                sprintf(
×
216
                                                        // translators: %1$s is the protocol, %2$s is the list of allowed protocols.
217
                                                        __( 'Invalid protocol. "%1$s". Only "%2$s" allowed.', 'wp-graphql' ),
×
218
                                                        $protocol,
×
219
                                                        implode( '", "', $allowed_protocols )
×
220
                                                )
×
221
                                        )
×
222
                                );
×
223
                        }
224

225
                        /**
226
                         * Require the file.php file from wp-admin. This file includes the
227
                         * download_url and wp_handle_sideload methods.
228
                         */
229
                        require_once ABSPATH . 'wp-admin/includes/file.php';
4✔
230

231
                        $file_contents = file_get_contents( $input['filePath'] );
4✔
232

233
                        /**
234
                         * If the mediaItem file is from a local server, use wp_upload_bits before saving it to the uploads folder
235
                         */
236
                        if ( 'file' === wp_parse_url( $input['filePath'], PHP_URL_SCHEME ) && ! empty( $file_contents ) ) {
4✔
237
                                $uploaded_file     = wp_upload_bits( $file_name, null, $file_contents );
×
238
                                $uploaded_file_url = ( empty( $uploaded_file['error'] ) ? $uploaded_file['url'] : null );
×
239
                        }
240

241
                        /**
242
                         * URL data for the mediaItem, timeout value is the default, see:
243
                         * https://developer.wordpress.org/reference/functions/download_url/
244
                         */
245
                        $timeout_seconds = 300;
4✔
246

247
                        // Ensure uploaded_file_url is a string (it may be null if wp_upload_bits failed)
248
                        if ( empty( $uploaded_file_url ) || ! is_string( $uploaded_file_url ) ) {
4✔
249
                                throw new UserError( esc_html__( 'Sorry, the URL for this file is invalid, it must be a valid URL', 'wp-graphql' ) );
×
250
                        }
251

252
                        $temp_file = download_url( $uploaded_file_url, $timeout_seconds );
4✔
253

254
                        /**
255
                         * Handle the error from download_url if it occurs
256
                         */
257
                        if ( is_wp_error( $temp_file ) ) {
4✔
258
                                throw new UserError( esc_html__( 'Sorry, the URL for this file is invalid, it must be a valid URL', 'wp-graphql' ) );
×
259
                        }
260

261
                        /**
262
                         * Build the file data for side loading
263
                         */
264
                        $file_data = [
4✔
265
                                'name'     => $file_name,
4✔
266
                                'type'     => ! empty( $input['fileType'] ) ? $input['fileType'] : wp_check_filetype( $temp_file ),
4✔
267
                                'tmp_name' => $temp_file,
4✔
268
                                'error'    => 0,
4✔
269
                                'size'     => (int) filesize( $temp_file ),
4✔
270
                        ];
4✔
271

272
                        /**
273
                         * Tells WordPress to not look for the POST form fields that would normally be present as
274
                         * we downloaded the file from a remote server, so there will be no form fields
275
                         * The default is true
276
                         */
277
                        $overrides = [
4✔
278
                                'test_form' => false,
4✔
279
                        ];
4✔
280

281
                        /**
282
                         * Insert the mediaItem and retrieve it's data
283
                         */
284
                        $file = wp_handle_sideload( $file_data, $overrides );
4✔
285

286
                        /**
287
                         * Handle the error from wp_handle_sideload if it occurs
288
                         */
289
                        if ( ! empty( $file['error'] ) ) {
4✔
290
                                throw new UserError( esc_html__( 'Sorry, the URL for this file is invalid, it must be a path to the mediaItem file', 'wp-graphql' ) );
×
291
                        }
292

293
                        /**
294
                         * Insert the mediaItem object and get the ID
295
                         */
296
                        $media_item_args = MediaItemMutation::prepare_media_item( $input, $post_type_object, 'createMediaItem', $file );
4✔
297

298
                        /**
299
                         * Get the post parent and if it's not set, set it to 0
300
                         */
301
                        $attachment_parent_id = ! empty( $media_item_args['post_parent'] ) ? $media_item_args['post_parent'] : 0;
4✔
302

303
                        /**
304
                         * Stop now if a user isn't allowed to edit the parent post
305
                         */
306
                        $parent = get_post( $attachment_parent_id );
4✔
307

308
                        if ( null !== $parent ) {
4✔
309
                                $post_parent_type = get_post_type_object( $parent->post_type );
2✔
310

311
                                if ( empty( $post_parent_type ) ) {
2✔
312
                                        throw new UserError( esc_html__( 'The parent of the Media Item is of an invalid type', 'wp-graphql' ) );
×
313
                                }
314

315
                                if ( 'attachment' !== $post_parent_type->name && ( ! isset( $post_parent_type->cap->edit_post ) || ! current_user_can( $post_parent_type->cap->edit_post, $attachment_parent_id ) ) ) {
2✔
316
                                        throw new UserError( esc_html__( 'Sorry, you are not allowed to upload mediaItems assigned to this parent node', 'wp-graphql' ) );
×
317
                                }
318
                        }
319

320
                        /**
321
                         * Insert the mediaItem
322
                         *
323
                         * Required Argument defaults are set in the main MediaItemMutation.php if they aren't set
324
                         * by the user during input, they are:
325
                         * post_title (pulled from file if not entered)
326
                         * post_content (empty string if not entered)
327
                         * post_status (inherit if not entered)
328
                         * post_mime_type (pulled from the file if not entered in the mutation)
329
                         */
330
                        $attachment_id = wp_insert_attachment( $media_item_args, $file['file'], $attachment_parent_id, true );
4✔
331

332
                        if ( is_wp_error( $attachment_id ) ) {
4✔
333
                                $error_message = $attachment_id->get_error_message();
×
334
                                if ( ! empty( $error_message ) ) {
×
335
                                        throw new UserError( esc_html( $error_message ) );
×
336
                                }
337

338
                                throw new UserError( esc_html__( 'The media item failed to create but no error was provided', 'wp-graphql' ) );
×
339
                        }
340

341
                        /**
342
                         * Check if the wp_generate_attachment_metadata method exists and include it if not.
343
                         */
344
                        require_once ABSPATH . 'wp-admin/includes/image.php';
4✔
345

346
                        /**
347
                         * Generate and update the mediaItem's metadata.
348
                         * If we make it this far the file and attachment
349
                         * have been validated and we will not receive any errors
350
                         */
351
                        $attachment_data = wp_generate_attachment_metadata( $attachment_id, $file['file'] );
4✔
352
                        wp_update_attachment_metadata( $attachment_id, $attachment_data );
4✔
353

354
                        /**
355
                         * Update alt text postmeta for mediaItem
356
                         */
357
                        MediaItemMutation::update_additional_media_item_data( $attachment_id, $input, $post_type_object, 'createMediaItem', $context, $info );
4✔
358

359
                        return [
4✔
360
                                'postObjectId' => $attachment_id,
4✔
361
                        ];
4✔
362
                };
614✔
363
        }
364
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc