-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Rich image editing capabilities to Gutenberg #21024
Changes from all commits
5e8769b
720e57f
62e168d
f3656b1
03025d5
baab84e
cc476db
11a940a
4d79072
cc32ad5
5366dfd
c07405c
a1bb170
9c895cd
d372d3c
b96eeef
a1cf03e
6db2f6c
d5c73ee
0de6203
da7c016
c35716d
94e090c
43a5b67
0a382f0
3a821d0
b200192
24c763a
1f780dd
cd318eb
cb46a13
4b4037e
401f705
ef8ac13
284f16f
2340e58
4a320a9
96d7bd8
65b4638
ff50131
91f5825
f2cf507
8843284
fe7411d
edaef9d
d318808
3c9246e
16b0d54
27f77de
6066802
674d40d
fc648d8
4006c43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
<?php | ||
/** | ||
* Start: Include for phase 2 | ||
* REST API: WP_REST_Menus_Controller class | ||
* | ||
* @package WordPress | ||
* @subpackage REST_API | ||
*/ | ||
|
||
/** | ||
* Image editor | ||
*/ | ||
include_once __DIR__ . '/image-editor/class-image-editor.php'; | ||
|
||
/** | ||
* Controller which provides REST API endpoints for image editing. | ||
* | ||
* @since 7.x ? | ||
* | ||
* @see WP_REST_Controller | ||
*/ | ||
class WP_REST_Image_Editor_Controller extends WP_REST_Controller { | ||
|
||
/** | ||
* Constructs the controller. | ||
* | ||
* @since 7.x ? | ||
* @access public | ||
*/ | ||
public function __construct() { | ||
$this->namespace = '__experimental'; | ||
$this->rest_base = '/richimage/(?P<mediaID>[\d]+)'; | ||
$this->editor = new Image_Editor(); | ||
} | ||
|
||
/** | ||
* Registers the necessary REST API routes. | ||
* | ||
* @since 7.x ? | ||
* @access public | ||
*/ | ||
public function register_routes() { | ||
register_rest_route( | ||
$this->namespace, | ||
$this->rest_base . '/rotate', | ||
array( | ||
array( | ||
'methods' => WP_REST_Server::EDITABLE, | ||
'callback' => array( $this, 'rotate_image' ), | ||
'permission_callback' => array( $this, 'permission_callback' ), | ||
'args' => array( | ||
'angle' => array( | ||
'type' => 'integer', | ||
'required' => true, | ||
), | ||
), | ||
), | ||
) | ||
); | ||
|
||
register_rest_route( | ||
$this->namespace, | ||
$this->rest_base . '/flip', | ||
array( | ||
array( | ||
'methods' => WP_REST_Server::EDITABLE, | ||
'callback' => array( $this, 'flip_image' ), | ||
'permission_callback' => array( $this, 'permission_callback' ), | ||
'args' => array( | ||
'direction' => array( | ||
'type' => 'enum', | ||
'enum' => array( 'vertical', 'horizontal' ), | ||
'required' => true, | ||
), | ||
), | ||
), | ||
) | ||
); | ||
|
||
register_rest_route( | ||
$this->namespace, | ||
$this->rest_base . '/crop', | ||
array( | ||
array( | ||
'methods' => WP_REST_Server::EDITABLE, | ||
'callback' => array( $this, 'crop_image' ), | ||
'permission_callback' => array( $this, 'permission_callback' ), | ||
'args' => array( | ||
'cropX' => array( | ||
'type' => 'float', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
'minimum' => 0, | ||
'required' => true, | ||
), | ||
'cropY' => array( | ||
'type' => 'float', | ||
'minimum' => 0, | ||
'required' => true, | ||
), | ||
'cropWidth' => array( | ||
'type' => 'float', | ||
'minimum' => 1, | ||
'required' => true, | ||
), | ||
'cropHeight' => array( | ||
'type' => 'float', | ||
'minimum' => 1, | ||
'required' => true, | ||
), | ||
), | ||
), | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* Checks if the user has permissions to make the request. | ||
* | ||
* @since 7.x ? | ||
* @access public | ||
* | ||
* @param WP_REST_Request $request Full details about the request. | ||
* @return true|WP_Error True if the request has read access, WP_Error object otherwise. | ||
*/ | ||
public function permission_callback( $request ) { | ||
$params = $request->get_params(); | ||
|
||
if ( ! current_user_can( 'edit_post', $params['mediaID'] ) ) { | ||
return new WP_Error( 'rest_cannot_edit_image', __( 'Sorry, you are not allowed to edit images.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Rotates an image. | ||
* | ||
* @since 7.x ? | ||
* @access public | ||
* | ||
* @param WP_REST_Request $request Full details about the request. | ||
* @return array|WP_Error If successful image JSON for the modified image, otherwise a WP_Error. | ||
*/ | ||
public function rotate_image( $request ) { | ||
$params = $request->get_params(); | ||
|
||
$modifier = new Image_Editor_Rotate( $params['angle'] ); | ||
|
||
return $this->editor->modify_image( $params['mediaID'], $modifier ); | ||
} | ||
|
||
/** | ||
* Flips/mirrors an image. | ||
* | ||
* @since 7.x ? | ||
* @access public | ||
* | ||
* @param WP_REST_Request $request Full details about the request. | ||
* @return array|WP_Error If successful image JSON for the modified image, otherwise a WP_Error. | ||
*/ | ||
public function flip_image( $request ) { | ||
$params = $request->get_params(); | ||
|
||
$modifier = new Image_Editor_Flip( $params['direction'] ); | ||
|
||
return $this->editor->modify_image( $params['mediaID'], $modifier ); | ||
} | ||
|
||
/** | ||
* Crops an image. | ||
* | ||
* @since 7.x ? | ||
* @access public | ||
* | ||
* @param WP_REST_Request $request Full details about the request. | ||
* @return array|WP_Error If successful image JSON for the modified image, otherwise a WP_Error. | ||
*/ | ||
public function crop_image( $request ) { | ||
$params = $request->get_params(); | ||
|
||
$modifier = new Image_Editor_Crop( $params['cropX'], $params['cropY'], $params['cropWidth'], $params['cropHeight'] ); | ||
|
||
return $this->editor->modify_image( $params['mediaID'], $modifier ); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
<?php | ||
/** | ||
* Start: Include for phase 2 | ||
* Image Editor: Image_Editor_Crop class | ||
* | ||
* @package gutenberg | ||
* @since 7.x ? | ||
*/ | ||
|
||
/** | ||
* Crop image modifier. | ||
*/ | ||
class Image_Editor_Crop extends Image_Editor_Modifier { | ||
/** | ||
* Pixels from the left for the crop. | ||
* | ||
* @var integer | ||
*/ | ||
private $crop_x = 0; | ||
|
||
/** | ||
* Pixels from the top for the crop. | ||
* | ||
* @var integer | ||
*/ | ||
private $crop_y = 0; | ||
|
||
/** | ||
* Width in pixels for the crop. | ||
* | ||
* @var integer | ||
*/ | ||
private $width = 0; | ||
|
||
/** | ||
* Height in pixels for the crop. | ||
* | ||
* @var integer | ||
*/ | ||
private $height = 0; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* Will populate object properties from the provided arguments. | ||
* | ||
* @param integer $crop_x Pixels from the left for the crop. | ||
* @param integer $crop_y Pixels from the top for the crop. | ||
* @param integer $width Width in pixels for the crop. | ||
* @param integer $height Height in pixels for the crop. | ||
*/ | ||
public function __construct( $crop_x, $crop_y, $width, $height ) { | ||
$this->crop_x = floatval( $crop_x ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we using |
||
$this->crop_y = floatval( $crop_y ); | ||
$this->width = floatval( $width ); | ||
$this->height = floatval( $height ); | ||
} | ||
|
||
/** | ||
* Update the image metadata with the modifier. | ||
* | ||
* @access public | ||
* | ||
* @param array $meta Metadata to update. | ||
* @return array Updated metadata. | ||
*/ | ||
public function apply_to_meta( $meta ) { | ||
$meta['cropX'] = $this->crop_x; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these based off an existing pattern that means these need to be |
||
$meta['cropY'] = $this->crop_y; | ||
$meta['cropWidth'] = $this->width; | ||
$meta['cropHeight'] = $this->height; | ||
|
||
return $meta; | ||
} | ||
|
||
/** | ||
* Apply the modifier to the image | ||
* | ||
* @access public | ||
* | ||
* @param WP_Image_Editor $image Image editor. | ||
* @return bool|WP_Error True on success, WP_Error object or false on failure. | ||
*/ | ||
public function apply_to_image( $image ) { | ||
$size = $image->get_size(); | ||
|
||
$crop_x = round( ( $size['width'] * $this->crop_x ) / 100.0 ); | ||
$crop_y = round( ( $size['height'] * $this->crop_y ) / 100.0 ); | ||
$width = round( ( $size['width'] * $this->width ) / 100.0 ); | ||
$height = round( ( $size['height'] * $this->height ) / 100.0 ); | ||
|
||
return $image->crop( $crop_x, $crop_y, $width, $height ); | ||
} | ||
|
||
/** | ||
* Gets the new filename based on metadata. | ||
* | ||
* @access public | ||
* | ||
* @param array $meta Image metadata. | ||
* @return string Filename for the edited image. | ||
*/ | ||
public static function get_filename( $meta ) { | ||
if ( isset( $meta['cropWidth'] ) && $meta['cropWidth'] > 0 ) { | ||
$target_file = sprintf( 'crop-%d-%d-%d-%d', round( $meta['cropX'], 2 ), round( $meta['cropY'], 2 ), round( $meta['cropWidth'], 2 ), round( $meta['cropHeight'], 2 ) ); | ||
|
||
// We need to change the original name to include the crop. This way if it's cropped again we won't clash. | ||
$meta['original_name'] = $target_file; | ||
|
||
return $target_file; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Gets the default metadata for the crop modifier. | ||
* | ||
* @access public | ||
* | ||
* @return array Default metadata. | ||
*/ | ||
public static function get_default_meta() { | ||
return array(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enum
isn't a type, it should instead bestring
.