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

wp-graphql / wp-graphql / 14716683875

28 Apr 2025 07:58PM UTC coverage: 84.287% (+1.6%) from 82.648%
14716683875

push

github

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

15905 of 18870 relevant lines covered (84.29%)

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

233
                        $file_contents = file_get_contents( $input['filePath'] );
4✔
234

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

243
                        /**
244
                         * URL data for the mediaItem, timeout value is the default, see:
245
                         * https://developer.wordpress.org/reference/functions/download_url/
246
                         */
247
                        $timeout_seconds = 300;
4✔
248
                        $temp_file       = download_url( $uploaded_file_url, $timeout_seconds );
4✔
249

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

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

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

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

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

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

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

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

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

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

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

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

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

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

337
                        /**
338
                         * Check if the wp_generate_attachment_metadata method exists and include it if not.
339
                         *
340
                         * @phpstan-ignore requireOnce.fileNotFound
341
                         */
342
                        require_once ABSPATH . 'wp-admin/includes/image.php';
4✔
343

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

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

357
                        return [
4✔
358
                                'postObjectId' => $attachment_id,
4✔
359
                        ];
4✔
360
                };
593✔
361
        }
362
}
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