PluginProbe
Gutenberg / 8.5.1
Gutenberg v8.5.1
24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 All 403 releases
gutenberg / lib / class-wp-rest-image-editor-controller.php

class-wp-rest-image-editor-controller.php in Gutenberg 8.5.1, at lib/class-wp-rest-image-editor-controller.php

294 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Start: Include for phase 2
4 * REST API: WP_REST_Image_Editor_Controller class
5 *
6 * @package WordPress
7 * @subpackage REST_API
8 */
9
10 /**
11 * Controller which provides REST API endpoints for image editing.
12 *
13 * @since 7.x ?
14 *
15 * @see WP_REST_Controller
16 */
17 class WP_REST_Image_Editor_Controller extends WP_REST_Controller {
18
19 /**
20 * Constructs the controller.
21 *
22 * @since 7.x ?
23 * @access public
24 */
25 public function __construct() {
26 $this->namespace = 'wp/v2';
27 $this->rest_base = 'media';
28 }
29
30 /**
31 * Registers the necessary REST API routes.
32 *
33 * @since 7.x ?
34 * @access public
35 */
36 public function register_routes() {
37 register_rest_route(
38 $this->namespace,
39 '/' . $this->rest_base . '/(?P<id>[\d]+)/edit',
40 array(
41 array(
42 'methods' => WP_REST_Server::EDITABLE,
43 'callback' => array( $this, 'apply_edits' ),
44 'permission_callback' => array( $this, 'permission_callback' ),
45 'args' => array(
46 'rotation' => array(
47 'type' => 'integer',
48 ),
49
50 // Crop values are in percents.
51 'x' => array(
52 'type' => 'number',
53 'minimum' => 0,
54 'maximum' => 100,
55 ),
56 'y' => array(
57 'type' => 'number',
58 'minimum' => 0,
59 'maximum' => 100,
60 ),
61 'width' => array(
62 'type' => 'number',
63 'minimum' => 0,
64 'maximum' => 100,
65 ),
66 'height' => array(
67 'type' => 'number',
68 'minimum' => 0,
69 'maximum' => 100,
70 ),
71 ),
72 ),
73 )
74 );
75 }
76
77 /**
78 * Checks if the user has permissions to make the request.
79 *
80 * @since 7.x ?
81 * @access public
82 *
83 * @param WP_REST_Request $request Full details about the request.
84 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
85 */
86 public function permission_callback( $request ) {
87 if ( ! current_user_can( 'edit_post', $request['id'] ) ) {
88 $error = __( 'Sorry, you are not allowed to edit images.', 'gutenberg' );
89 return new WP_Error( 'rest_cannot_edit_image', $error, array( 'status' => rest_authorization_required_code() ) );
90 }
91
92 if ( ! current_user_can( 'upload_files' ) ) {
93 return new WP_Error( 'rest_cannot_edit_image', __( 'Sorry, you are not allowed to upload media on this site.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) );
94 }
95
96 return true;
97 }
98
99 /**
100 * Applies all edits in one go.
101 *
102 * @since 7.x ?
103 * @access public
104 *
105 * @param WP_REST_Request $request Full details about the request.
106 * @return WP_REST_Response|WP_Error If successful image JSON for the modified image, otherwise a WP_Error.
107 */
108 public function apply_edits( $request ) {
109 require_once ABSPATH . 'wp-admin/includes/image.php';
110
111 $attachment_id = $request['id'];
112
113 // This also confirms the attachment is an image.
114 $image_file = wp_get_original_image_path( $attachment_id );
115 $image_meta = wp_get_attachment_metadata( $attachment_id );
116
117 if ( ! $image_meta || ! $image_file ) {
118 $error = __( 'Unable to get meta information for file.', 'gutenberg' );
119 return new WP_Error( 'rest_unknown_attachment', $error, array( 'status' => 404 ) );
120 }
121
122 $supported_types = array( 'image/jpeg', 'image/png', 'image/gif' );
123 $mime_type = get_post_mime_type( $attachment_id );
124 if ( ! in_array( $mime_type, $supported_types, true ) ) {
125 return new WP_Error(
126 'rest_cannot_edit_file_type',
127 __( 'This type of file cannot be edited.', 'gutenberg' ),
128 array( 'status' => 400 )
129 );
130 }
131
132 // Check if we need to do anything.
133 $rotate = 0;
134 $crop = false;
135
136 if ( ! empty( $request['rotation'] ) ) {
137 // Rotation direction: clockwise vs. counter clockwise.
138 $rotate = 0 - (int) $request['rotation'];
139 }
140
141 if ( isset( $request['x'], $request['y'], $request['width'], $request['height'] ) ) {
142 $crop = true;
143 }
144
145 if ( ! $rotate && ! $crop ) {
146 $error = __( 'The image was not edited. Edit the image before applying the changes.', 'gutenberg' );
147 return new WP_Error( 'rest_image_not_edited', $error, array( 'status' => 400 ) );
148 }
149
150 // If the file doesn't exist, attempt a URL fopen on the src link.
151 // This can occur with certain file replication plugins.
152 // Keep the original file path to get a modified name later.
153 $image_file_to_edit = $image_file;
154 if ( ! file_exists( $image_file_to_edit ) ) {
155 $image_file_to_edit = _load_image_to_edit_path( $attachment_id );
156 }
157
158 $image_editor = wp_get_image_editor( $image_file_to_edit );
159
160 if ( is_wp_error( $image_editor ) ) {
161 // This image cannot be edited.
162 $error = __( 'Unable to edit this image.', 'gutenberg' );
163 return new WP_Error( 'rest_unknown_image_file_type', $error, array( 'status' => 500 ) );
164 }
165
166 if ( 0 !== $rotate ) {
167 $result = $image_editor->rotate( $rotate );
168
169 if ( is_wp_error( $result ) ) {
170 $error = __( 'Unable to rotate this image.', 'gutenberg' );
171 return new WP_Error( 'rest_image_rotation_failed', $error, array( 'status' => 500 ) );
172 }
173 }
174
175 if ( $crop ) {
176 $size = $image_editor->get_size();
177
178 $crop_x = round( ( $size['width'] * floatval( $request['x'] ) ) / 100.0 );
179 $crop_y = round( ( $size['height'] * floatval( $request['y'] ) ) / 100.0 );
180 $width = round( ( $size['width'] * floatval( $request['width'] ) ) / 100.0 );
181 $height = round( ( $size['height'] * floatval( $request['height'] ) ) / 100.0 );
182
183 $result = $image_editor->crop( $crop_x, $crop_y, $width, $height );
184
185 if ( is_wp_error( $result ) ) {
186 $error = __( 'Unable to crop this image.', 'gutenberg' );
187 return new WP_Error( 'rest_image_crop_failed', $error, array( 'status' => 500 ) );
188 }
189 }
190
191 // Calculate the file name.
192 $image_ext = pathinfo( $image_file, PATHINFO_EXTENSION );
193 $image_name = wp_basename( $image_file, ".{$image_ext}" );
194
195 // Do not append multiple `-edited` to the file name.
196 // The user may be editing a previously edited image.
197 if ( preg_match( '/-edited(-\d+)?$/', $image_name ) ) {
198 // Remove any `-1`, `-2`, etc. `wp_unique_filename()` will add the proper number.
199 $image_name = preg_replace( '/-edited(-\d+)?$/', '-edited', $image_name );
200 } else {
201 // Append `-edited` before the extension.
202 $image_name .= '-edited';
203 }
204
205 $filename = "{$image_name}.{$image_ext}";
206
207 // Create the uploads sub-directory if needed.
208 $uploads = wp_upload_dir();
209
210 // Make the file name unique in the (new) upload directory.
211 $filename = wp_unique_filename( $uploads['path'], $filename );
212
213 // Save to disk.
214 $saved = $image_editor->save( $uploads['path'] . "/$filename" );
215
216 if ( is_wp_error( $saved ) ) {
217 return $saved;
218 }
219
220 // Create new attachment post.
221 $attachment_post = array(
222 'post_mime_type' => $saved['mime-type'],
223 'guid' => $uploads['url'] . "/$filename",
224 'post_title' => $filename,
225 'post_content' => '',
226 );
227
228 $new_attachment_id = wp_insert_attachment( wp_slash( $attachment_post ), $saved['path'], 0, true );
229
230 if ( is_wp_error( $new_attachment_id ) ) {
231 if ( 'db_update_error' === $new_attachment_id->get_error_code() ) {
232 $new_attachment_id->add_data( array( 'status' => 500 ) );
233 } else {
234 $new_attachment_id->add_data( array( 'status' => 400 ) );
235 }
236
237 return $new_attachment_id;
238 }
239
240 // Generate image sub-sizes and meta.
241 $new_image_meta = wp_generate_attachment_metadata( $new_attachment_id, $saved['path'] );
242
243 // Copy the EXIF metadata from the original attachment if not generated for the edited image.
244 if ( ! empty( $image_meta['image_meta'] ) ) {
245 $empty_image_meta = true;
246
247 if ( isset( $new_image_meta['image_meta'] ) && is_array( $new_image_meta['image_meta'] ) ) {
248 $empty_image_meta = empty( array_filter( array_values( $new_image_meta['image_meta'] ) ) );
249 }
250
251 if ( $empty_image_meta ) {
252 $new_image_meta['image_meta'] = $image_meta['image_meta'];
253 }
254 }
255
256 // Reset orientation. At this point the image is edited and orientation is correct.
257 if ( ! empty( $new_image_meta['image_meta']['orientation'] ) ) {
258 $new_image_meta['image_meta']['orientation'] = 1;
259 }
260
261 // The attachment_id may change if the site is exported and imported.
262 $new_image_meta['parent_image'] = array(
263 'attachment_id' => $attachment_id,
264 // Path to the originally uploaded image file relative to the uploads directory.
265 'file' => _wp_relative_upload_path( $image_file ),
266 );
267
268 /**
269 * Filters the updated attachment meta data.
270 *
271 * @since 5.5.0
272 *
273 * @param array $data Array of updated attachment meta data.
274 * @param int $new_attachment_id Attachment post ID.
275 * @param int $attachment_id Original Attachment post ID.
276 */
277 $new_image_meta = apply_filters( 'wp_edited_attachment_metadata', $new_image_meta, $new_attachment_id, $attachment_id );
278
279 wp_update_attachment_metadata( $new_attachment_id, $new_image_meta );
280
281 $path = '/wp/v2/media/' . $new_attachment_id;
282 $new_request = new WP_REST_Request( 'GET', $path );
283 $new_request->set_query_params( array( 'context' => 'edit' ) );
284 $response = rest_do_request( $new_request );
285
286 if ( ! $response->is_error() ) {
287 $response->set_status( 201 );
288 $response->header( 'Location', rest_url( $path ) );
289 }
290
291 return $response;
292 }
293 }
294