Make WordPress Core

Changeset 46422


Ignore:
Timestamp:
10/07/2019 05:04:49 PM (5 years ago)
Author:
azaozz
Message:

REST API: Add support for continuing the post-processing of images after upload. Flow:

  1. POST /wp/v2/media.
  2. If the upload failed (HTTP 500 error), look for a response header with X-WP-Upload-Attachment-ID header that contains the newly created attachment ID.
  3. POST /wp/v2/media/{id}/post-process with { "action": "create-image-subsizes" }. This request may still fail, but it will save its progress.
  4. On continued failure, DELETE /wp/v2/media/{id} to give up on the upload and instruct the user to resize their image before uploading.

Props TimothyBlynJacobs.
Fixes #47987.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php

    r45932 r46422  
    1717class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
    1818
     19    public function register_routes() {
     20        parent::register_routes();
     21        register_rest_route(
     22            $this->namespace,
     23            '/' . $this->rest_base . '/(?P<id>[\d]+)/post-process',
     24            array(
     25                'methods'             => WP_REST_Server::CREATABLE,
     26                'callback'            => array( $this, 'post_process_item' ),
     27                'permission_callback' => array( $this, 'post_process_item_permissions_check' ),
     28                'args'                => array(
     29                    'id'     => array(
     30                        'description' => __( 'Unique identifier for the object.' ),
     31                        'type'        => 'integer',
     32                    ),
     33                    'action' => array(
     34                        'type'     => 'string',
     35                        'enum'     => array( 'create-image-subsizes' ),
     36                        'required' => true,
     37                    ),
     38                ),
     39            )
     40        );
     41    }
     42
    1943    /**
    2044     * Determines the allowed query_vars for a get_items() response and
     
    101125        }
    102126
     127        $insert = $this->insert_attachment( $request );
     128
     129        if ( is_wp_error( $insert ) ) {
     130            return $insert;
     131        }
     132
     133        // Extract by name.
     134        $attachment_id = $insert['attachment_id'];
     135        $file          = $insert['file'];
     136
     137        if ( isset( $request['alt_text'] ) ) {
     138            update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
     139        }
     140
     141        $attachment    = get_post( $attachment_id );
     142        $fields_update = $this->update_additional_fields_for_object( $attachment, $request );
     143
     144        if ( is_wp_error( $fields_update ) ) {
     145            return $fields_update;
     146        }
     147
     148        $request->set_param( 'context', 'edit' );
     149
     150        /**
     151         * Fires after a single attachment is completely created or updated via the REST API.
     152         *
     153         * @since 5.0.0
     154         *
     155         * @param WP_Post         $attachment Inserted or updated attachment object.
     156         * @param WP_REST_Request $request    Request object.
     157         * @param bool            $creating   True when creating an attachment, false when updating.
     158         */
     159        do_action( 'rest_after_insert_attachment', $attachment, $request, true );
     160
     161        if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
     162            // Set a custom header with the attachment_id.
     163            // Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
     164            header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
     165        }
     166
     167        // Include admin function to get access to wp_generate_attachment_metadata().
     168        require_once ABSPATH . 'wp-admin/includes/media.php';
     169
     170        // Post-process the upload (create image sub-sizes, make PDF thumbnalis, etc.) and insert attachment meta.
     171        // At this point the server may run out of resources and post-processing of uploaded images may fail.
     172        wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
     173
     174        $response = $this->prepare_item_for_response( $attachment, $request );
     175        $response = rest_ensure_response( $response );
     176        $response->set_status( 201 );
     177        $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $attachment_id ) ) );
     178
     179        return $response;
     180    }
     181
     182    /**
     183     * Inserts the attachment post in the database. Does not update the attachment meta.
     184     *
     185     * @since 5.3.0
     186     *
     187     * @param WP_REST_Request $request
     188     * @return array|WP_Error
     189     */
     190    protected function insert_attachment( $request ) {
    103191        // Get the file via $_FILES or raw data.
    104192        $files   = $request->get_file_params();
     
    139227        }
    140228
    141         $attachment                 = $this->prepare_item_for_database( $request );
     229        $attachment = $this->prepare_item_for_database( $request );
     230
    142231        $attachment->post_mime_type = $type;
    143232        $attachment->guid           = $url;
     
    156245                $id->add_data( array( 'status' => 400 ) );
    157246            }
     247
    158248            return $id;
    159249        }
     
    173263        do_action( 'rest_insert_attachment', $attachment, $request, true );
    174264
    175         // Include admin function to get access to wp_generate_attachment_metadata().
    176         require_once ABSPATH . 'wp-admin/includes/media.php';
    177 
    178         wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
    179 
    180         if ( isset( $request['alt_text'] ) ) {
    181             update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
    182         }
    183 
    184         $fields_update = $this->update_additional_fields_for_object( $attachment, $request );
    185 
    186         if ( is_wp_error( $fields_update ) ) {
    187             return $fields_update;
    188         }
    189 
    190         $request->set_param( 'context', 'edit' );
    191 
    192         /**
    193          * Fires after a single attachment is completely created or updated via the REST API.
    194          *
    195          * @since 5.0.0
    196          *
    197          * @param WP_Post         $attachment Inserted or updated attachment object.
    198          * @param WP_REST_Request $request    Request object.
    199          * @param bool            $creating   True when creating an attachment, false when updating.
    200          */
    201         do_action( 'rest_after_insert_attachment', $attachment, $request, true );
    202 
    203         $response = $this->prepare_item_for_response( $attachment, $request );
    204         $response = rest_ensure_response( $response );
    205         $response->set_status( 201 );
    206         $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );
    207 
    208         return $response;
     265        return array(
     266            'attachment_id' => $id,
     267            'file'          => $file,
     268        );
    209269    }
    210270
     
    252312
    253313        return $response;
     314    }
     315
     316    /**
     317     * Performs post processing on an attachment.
     318     *
     319     * @since 5.3.0
     320     *
     321     * @param WP_REST_Request $request
     322     * @return WP_REST_Response|WP_Error
     323     */
     324    public function post_process_item( $request ) {
     325        switch ( $request['action'] ) {
     326            case 'create-image-subsizes':
     327                require_once ABSPATH . 'wp-admin/includes/image.php';
     328                wp_update_image_subsizes( $request['id'] );
     329                break;
     330        }
     331
     332        $request['context'] = 'edit';
     333
     334        return $this->prepare_item_for_response( get_post( $request['id'] ), $request );
     335    }
     336
     337    /**
     338     * Checks if a given request can perform post processing on an attachment.
     339     *
     340     * @sicne 5.3.0
     341     *
     342     * @param WP_REST_Request $request Full details about the request.
     343     * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
     344     */
     345    public function post_process_item_permissions_check( $request ) {
     346        return $this->update_item_permissions_check( $request );
    254347    }
    255348
     
    379472        if ( in_array( 'source_url', $fields, true ) ) {
    380473            $data['source_url'] = wp_get_attachment_url( $post->ID );
     474        }
     475
     476        if ( in_array( 'missing_image_sizes', $fields, true ) ) {
     477            require_once ABSPATH . 'wp-admin/includes/image.php';
     478            $data['missing_image_sizes'] = array_keys( wp_get_missing_image_subsizes( $post->ID ) );
    381479        }
    382480
     
    515613        );
    516614
     615        $schema['properties']['missing_image_sizes'] = array(
     616            'description' => __( 'List of the missing image sizes of the attachment.' ),
     617            'type'        => 'array',
     618            'items'       => array( 'type' => 'string' ),
     619            'context'     => array( 'edit' ),
     620            'readonly'    => true,
     621        );
     622
    517623        unset( $schema['properties']['password'] );
    518624
  • trunk/tests/phpunit/tests/rest-api/rest-attachments-controller.php

    r44933 r46422  
    13281328        $data       = $response->get_data();
    13291329        $properties = $data['schema']['properties'];
    1330         $this->assertEquals( 26, count( $properties ) );
     1330        $this->assertEquals( 27, count( $properties ) );
    13311331        $this->assertArrayHasKey( 'author', $properties );
    13321332        $this->assertArrayHasKey( 'alt_text', $properties );
     
    13611361        $this->assertArrayHasKey( 'rendered', $properties['title']['properties'] );
    13621362        $this->assertArrayHasKey( 'type', $properties );
     1363        $this->assertArrayHasKey( 'missing_image_sizes', $properties );
    13631364    }
    13641365
  • trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php

    r46206 r46422  
    100100            '/wp/v2/media',
    101101            '/wp/v2/media/(?P<id>[\\d]+)',
     102            '/wp/v2/media/(?P<id>[\\d]+)/post-process',
    102103            '/wp/v2/blocks',
    103104            '/wp/v2/blocks/(?P<id>[\d]+)',
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r46308 r46422  
    23002300                            "description": "Whether to bypass trash and force deletion.",
    23012301                            "type": "boolean"
     2302                        }
     2303                    }
     2304                }
     2305            ]
     2306        },
     2307        "/wp/v2/media/(?P<id>[\\d+])/post-process": {
     2308            "namespace": "wp/v2",
     2309            "methods": [
     2310                "POST"
     2311            ],
     2312            "endpoints": [
     2313                {
     2314                    "methods": [
     2315                        "POST"
     2316                    ],
     2317                    "args": {
     2318                        "id": {
     2319                            "required": false,
     2320                            "description": "Unique identifier for the object.",
     2321                            "type": "integer"
     2322                        },
     2323                        "action": {
     2324                            "required": true,
     2325                            "enum": [
     2326                                "create-image-subsizes"
     2327                            ],
     2328                            "type": "string"
    23022329                        }
    23032330                    }
Note: See TracChangeset for help on using the changeset viewer.