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

wp-graphql / wp-graphql / 18790791685

24 Oct 2025 08:03PM UTC coverage: 83.207% (-1.4%) from 84.575%
18790791685

push

github

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

2 of 4 new or added lines in 2 files covered. (50.0%)

189 existing lines in 10 files now uncovered.

16143 of 19401 relevant lines covered (83.21%)

257.79 hits per line

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

88.65
/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() {
613✔
19
                register_graphql_mutation(
613✔
20
                        'createMediaItem',
613✔
21
                        [
613✔
22
                                'inputFields'         => self::get_input_fields(),
613✔
23
                                'outputFields'        => self::get_output_fields(),
613✔
24
                                'mutateAndGetPayload' => self::mutate_and_get_payload(),
613✔
25
                        ]
613✔
26
                );
613✔
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() {
613✔
35
                return [
613✔
36
                        'altText'       => [
613✔
37
                                'type'        => 'String',
613✔
38
                                'description' => static function () {
613✔
39
                                        return __( 'Alternative text to display when mediaItem is not displayed', 'wp-graphql' );
15✔
40
                                },
613✔
41
                        ],
613✔
42
                        'authorId'      => [
613✔
43
                                'type'        => 'ID',
613✔
44
                                'description' => static function () {
613✔
45
                                        return __( 'The userId to assign as the author of the mediaItem', 'wp-graphql' );
15✔
46
                                },
613✔
47
                        ],
613✔
48
                        'caption'       => [
613✔
49
                                'type'        => 'String',
613✔
50
                                'description' => static function () {
613✔
51
                                        return __( 'The caption for the mediaItem', 'wp-graphql' );
15✔
52
                                },
613✔
53
                        ],
613✔
54
                        'commentStatus' => [
613✔
55
                                'type'        => 'String',
613✔
56
                                'description' => static function () {
613✔
57
                                        return __( 'The comment status for the mediaItem', 'wp-graphql' );
15✔
58
                                },
613✔
59
                        ],
613✔
60
                        'date'          => [
613✔
61
                                'type'        => 'String',
613✔
62
                                'description' => static function () {
613✔
63
                                        return __( 'The date of the mediaItem', 'wp-graphql' );
15✔
64
                                },
613✔
65
                        ],
613✔
66
                        'dateGmt'       => [
613✔
67
                                'type'        => 'String',
613✔
68
                                'description' => static function () {
613✔
69
                                        return __( 'The date (in GMT zone) of the mediaItem', 'wp-graphql' );
15✔
70
                                },
613✔
71
                        ],
613✔
72
                        'description'   => [
613✔
73
                                'type'        => 'String',
613✔
74
                                'description' => static function () {
613✔
75
                                        return __( 'Description of the mediaItem', 'wp-graphql' );
15✔
76
                                },
613✔
77
                        ],
613✔
78
                        'filePath'      => [
613✔
79
                                'type'        => 'String',
613✔
80
                                'description' => static function () {
613✔
81
                                        return __( 'The file name of the mediaItem', 'wp-graphql' );
15✔
82
                                },
613✔
83
                        ],
613✔
84
                        'fileType'      => [
613✔
85
                                'type'        => 'MimeTypeEnum',
613✔
86
                                'description' => static function () {
613✔
87
                                        return __( 'The file type of the mediaItem', 'wp-graphql' );
15✔
88
                                },
613✔
89
                        ],
613✔
90
                        'slug'          => [
613✔
91
                                'type'        => 'String',
613✔
92
                                'description' => static function () {
613✔
93
                                        return __( 'The slug of the mediaItem', 'wp-graphql' );
15✔
94
                                },
613✔
95
                        ],
613✔
96
                        'status'        => [
613✔
97
                                'type'        => 'MediaItemStatusEnum',
613✔
98
                                'description' => static function () {
613✔
99
                                        return __( 'The status of the mediaItem', 'wp-graphql' );
15✔
100
                                },
613✔
101
                        ],
613✔
102
                        'title'         => [
613✔
103
                                'type'        => 'String',
613✔
104
                                'description' => static function () {
613✔
105
                                        return __( 'The title of the mediaItem', 'wp-graphql' );
15✔
106
                                },
613✔
107
                        ],
613✔
108
                        'pingStatus'    => [
613✔
109
                                'type'        => 'String',
613✔
110
                                'description' => static function () {
613✔
111
                                        return __( 'The ping status for the mediaItem', 'wp-graphql' );
15✔
112
                                },
613✔
113
                        ],
613✔
114
                        'parentId'      => [
613✔
115
                                'type'        => 'ID',
613✔
116
                                'description' => static function () {
613✔
117
                                        return __( 'The ID of the parent object', 'wp-graphql' );
15✔
118
                                },
613✔
119
                        ],
613✔
120
                ];
613✔
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() {
613✔
129
                return [
613✔
130
                        'mediaItem' => [
613✔
131
                                'type'        => 'MediaItem',
613✔
132
                                'description' => static function () {
613✔
133
                                        return __( 'The MediaItem object mutation type.', 'wp-graphql' );
15✔
134
                                },
613✔
135
                                'resolve'     => static function ( $payload, $args, AppContext $context ) {
613✔
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
                                },
613✔
142
                        ],
613✔
143
                ];
613✔
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() {
613✔
152
                return static function ( $input, AppContext $context, ResolveInfo $info ) {
613✔
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   = $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✔
UNCOV
237
                                $uploaded_file     = wp_upload_bits( $file_name, null, $file_contents );
×
UNCOV
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
                        $temp_file       = download_url( $uploaded_file_url, $timeout_seconds );
4✔
247

248
                        /**
249
                         * Handle the error from download_url if it occurs
250
                         */
251
                        if ( is_wp_error( $temp_file ) ) {
4✔
UNCOV
252
                                throw new UserError( esc_html__( 'Sorry, the URL for this file is invalid, it must be a valid URL', 'wp-graphql' ) );
×
253
                        }
254

255
                        /**
256
                         * Build the file data for side loading
257
                         */
258
                        $file_data = [
4✔
259
                                'name'     => $file_name,
4✔
260
                                'type'     => ! empty( $input['fileType'] ) ? $input['fileType'] : wp_check_filetype( $temp_file ),
4✔
261
                                'tmp_name' => $temp_file,
4✔
262
                                'error'    => 0,
4✔
263
                                'size'     => (int) filesize( $temp_file ),
4✔
264
                        ];
4✔
265

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

275
                        /**
276
                         * Insert the mediaItem and retrieve it's data
277
                         */
278
                        $file = wp_handle_sideload( $file_data, $overrides );
4✔
279

280
                        /**
281
                         * Handle the error from wp_handle_sideload if it occurs
282
                         */
283
                        if ( ! empty( $file['error'] ) ) {
4✔
UNCOV
284
                                throw new UserError( esc_html__( 'Sorry, the URL for this file is invalid, it must be a path to the mediaItem file', 'wp-graphql' ) );
×
285
                        }
286

287
                        /**
288
                         * Insert the mediaItem object and get the ID
289
                         */
290
                        $media_item_args = MediaItemMutation::prepare_media_item( $input, $post_type_object, 'createMediaItem', $file );
4✔
291

292
                        /**
293
                         * Get the post parent and if it's not set, set it to 0
294
                         */
295
                        $attachment_parent_id = ! empty( $media_item_args['post_parent'] ) ? $media_item_args['post_parent'] : 0;
4✔
296

297
                        /**
298
                         * Stop now if a user isn't allowed to edit the parent post
299
                         */
300
                        $parent = get_post( $attachment_parent_id );
4✔
301

302
                        if ( null !== $parent ) {
4✔
303
                                $post_parent_type = get_post_type_object( $parent->post_type );
2✔
304

305
                                if ( empty( $post_parent_type ) ) {
2✔
UNCOV
306
                                        throw new UserError( esc_html__( 'The parent of the Media Item is of an invalid type', 'wp-graphql' ) );
×
307
                                }
308

309
                                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✔
UNCOV
310
                                        throw new UserError( esc_html__( 'Sorry, you are not allowed to upload mediaItems assigned to this parent node', 'wp-graphql' ) );
×
311
                                }
312
                        }
313

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

326
                        if ( is_wp_error( $attachment_id ) ) {
4✔
UNCOV
327
                                $error_message = $attachment_id->get_error_message();
×
UNCOV
328
                                if ( ! empty( $error_message ) ) {
×
329
                                        throw new UserError( esc_html( $error_message ) );
×
330
                                }
331

UNCOV
332
                                throw new UserError( esc_html__( 'The media item failed to create but no error was provided', 'wp-graphql' ) );
×
333
                        }
334

335
                        /**
336
                         * Check if the wp_generate_attachment_metadata method exists and include it if not.
337
                         */
338
                        require_once ABSPATH . 'wp-admin/includes/image.php';
4✔
339

340
                        /**
341
                         * Generate and update the mediaItem's metadata.
342
                         * If we make it this far the file and attachment
343
                         * have been validated and we will not receive any errors
344
                         */
345
                        $attachment_data = wp_generate_attachment_metadata( $attachment_id, $file['file'] );
4✔
346
                        wp_update_attachment_metadata( $attachment_id, $attachment_data );
4✔
347

348
                        /**
349
                         * Update alt text postmeta for mediaItem
350
                         */
351
                        MediaItemMutation::update_additional_media_item_data( $attachment_id, $input, $post_type_object, 'createMediaItem', $context, $info );
4✔
352

353
                        return [
4✔
354
                                'postObjectId' => $attachment_id,
4✔
355
                        ];
4✔
356
                };
613✔
357
        }
358
}
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