@@ -543,6 +543,7 @@ public function edit_media_item_permissions_check( $request ) {
543543 * Applies edits to a media item and creates a new attachment record.
544544 *
545545 * @since 5.5.0
546+ * @since 6.9.0 Adds flips capability and editable fields for the newly-created attachment post.
546547 *
547548 * @param WP_REST_Request $request Full details about the request.
548549 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
@@ -563,7 +564,7 @@ public function edit_media_item( $request ) {
563564 ) {
564565 return new WP_Error (
565566 'rest_unknown_attachment ' ,
566- __ ( 'Unable to get meta information for file. ' ),
567+ __ ( 'Unable to get meta information for file. ' , ' gutenberg ' ),
567568 array ( 'status ' => 404 )
568569 );
569570 }
@@ -573,7 +574,7 @@ public function edit_media_item( $request ) {
573574 if ( ! in_array ( $ mime_type , $ supported_types , true ) ) {
574575 return new WP_Error (
575576 'rest_cannot_edit_file_type ' ,
576- __ ( 'This type of file cannot be edited. ' ),
577+ __ ( 'This type of file cannot be edited. ' , ' gutenberg ' ),
577578 array ( 'status ' => 400 )
578579 );
579580 }
@@ -584,6 +585,20 @@ public function edit_media_item( $request ) {
584585 } else {
585586 $ modifiers = array ();
586587
588+ if ( isset ( $ request ['flip ' ]['horizontal ' ] ) || isset ( $ request ['flip ' ]['vertical ' ] ) ) {
589+ $ flip_args = array (
590+ 'vertical ' => $ request ['flip ' ]['vertical ' ] ?? 0 ,
591+ 'horizontal ' => $ request ['flip ' ]['horizontal ' ] ?? 0 ,
592+ );
593+
594+ $ modifiers [] = array (
595+ 'type ' => 'flip ' ,
596+ 'args ' => array (
597+ 'flip ' => $ flip_args ,
598+ ),
599+ );
600+ }
601+
587602 if ( ! empty ( $ request ['rotation ' ] ) ) {
588603 $ modifiers [] = array (
589604 'type ' => 'rotate ' ,
@@ -608,7 +623,7 @@ public function edit_media_item( $request ) {
608623 if ( 0 === count ( $ modifiers ) ) {
609624 return new WP_Error (
610625 'rest_image_not_edited ' ,
611- __ ( 'The image was not edited. Edit the image before applying the changes. ' ),
626+ __ ( 'The image was not edited. Edit the image before applying the changes. ' , ' gutenberg ' ),
612627 array ( 'status ' => 400 )
613628 );
614629 }
@@ -629,14 +644,29 @@ public function edit_media_item( $request ) {
629644 if ( is_wp_error ( $ image_editor ) ) {
630645 return new WP_Error (
631646 'rest_unknown_image_file_type ' ,
632- __ ( 'Unable to edit this image. ' ),
647+ __ ( 'Unable to edit this image. ' , ' gutenberg ' ),
633648 array ( 'status ' => 500 )
634649 );
635650 }
636651
637652 foreach ( $ modifiers as $ modifier ) {
638653 $ args = $ modifier ['args ' ];
639654 switch ( $ modifier ['type ' ] ) {
655+ case 'flip ' :
656+ /*
657+ * Flips the current image.
658+ * The vertical flip is the first argument (flip along horizontal axis), the horizontal flip is the second argument (flip along vertical axis).
659+ * See: WP_Image_Editor::flip()
660+ */
661+ $ result = $ image_editor ->flip ( 0 !== (int ) $ args ['flip ' ]['vertical ' ], 0 !== (int ) $ args ['flip ' ]['horizontal ' ] );
662+ if ( is_wp_error ( $ result ) ) {
663+ return new WP_Error (
664+ 'rest_image_flip_failed ' ,
665+ __ ( 'Unable to flip this image. ' , 'gutenberg ' ),
666+ array ( 'status ' => 500 )
667+ );
668+ }
669+ break ;
640670 case 'rotate ' :
641671 // Rotation direction: clockwise vs. counterclockwise.
642672 $ rotate = 0 - $ args ['angle ' ];
@@ -647,7 +677,7 @@ public function edit_media_item( $request ) {
647677 if ( is_wp_error ( $ result ) ) {
648678 return new WP_Error (
649679 'rest_image_rotation_failed ' ,
650- __ ( 'Unable to rotate this image. ' ),
680+ __ ( 'Unable to rotate this image. ' , ' gutenberg ' ),
651681 array ( 'status ' => 500 )
652682 );
653683 }
@@ -669,7 +699,7 @@ public function edit_media_item( $request ) {
669699 if ( is_wp_error ( $ result ) ) {
670700 return new WP_Error (
671701 'rest_image_crop_failed ' ,
672- __ ( 'Unable to crop this image. ' ),
702+ __ ( 'Unable to crop this image. ' , ' gutenberg ' ),
673703 array ( 'status ' => 500 )
674704 );
675705 }
@@ -711,23 +741,30 @@ public function edit_media_item( $request ) {
711741 return $ saved ;
712742 }
713743
714- // Create new attachment post.
715- $ new_attachment_post = array (
716- 'post_mime_type ' => $ saved ['mime-type ' ],
717- 'guid ' => $ uploads ['url ' ] . "/ $ filename " ,
718- 'post_title ' => $ image_name ,
719- 'post_content ' => '' ,
720- );
744+ // Grab original attachment post so we can use it to set defaults.
745+ $ original_attachment_post = get_post ( $ attachment_id );
721746
722- // Copy post_content, post_excerpt, and post_title from the edited image's attachment post.
723- $ attachment_post = get_post ( $ attachment_id );
747+ // Check request fields and assign default values.
748+ $ new_attachment_post = $ this ->prepare_item_for_database ( $ request );
749+ $ new_attachment_post ->post_mime_type = $ saved ['mime-type ' ];
750+ $ new_attachment_post ->guid = $ uploads ['url ' ] . "/ $ filename " ;
724751
725- if ( $ attachment_post ) {
726- $ new_attachment_post ['post_content ' ] = $ attachment_post ->post_content ;
727- $ new_attachment_post ['post_excerpt ' ] = $ attachment_post ->post_excerpt ;
728- $ new_attachment_post ['post_title ' ] = $ attachment_post ->post_title ;
729- }
752+ // Unset ID so wp_insert_attachment generates a new ID.
753+ unset( $ new_attachment_post ->ID );
730754
755+ // Set new attachment post title with fallbacks.
756+ $ new_attachment_post ->post_title = $ new_attachment_post ->post_title ?? $ original_attachment_post ->post_title ?? $ image_name ;
757+
758+ // Set new attachment post caption (post_excerpt).
759+ $ new_attachment_post ->post_excerpt = $ new_attachment_post ->post_excerpt ?? $ original_attachment_post ->post_excerpt ?? '' ;
760+
761+ // Set new attachment post description (post_content) with fallbacks.
762+ $ new_attachment_post ->post_content = $ new_attachment_post ->post_content ?? $ original_attachment_post ->post_content ?? '' ;
763+
764+ // Set post parent if set in request, else the default of `0` (no parent).
765+ $ new_attachment_post ->post_parent = $ new_attachment_post ->post_parent ?? 0 ;
766+
767+ // Insert the new attachment post.
731768 $ new_attachment_id = wp_insert_attachment ( wp_slash ( $ new_attachment_post ), $ saved ['path ' ], 0 , true );
732769
733770 if ( is_wp_error ( $ new_attachment_id ) ) {
@@ -740,8 +777,8 @@ public function edit_media_item( $request ) {
740777 return $ new_attachment_id ;
741778 }
742779
743- // Copy the image alt text from the edited image .
744- $ image_alt = get_post_meta ( $ attachment_id , '_wp_attachment_image_alt ' , true );
780+ // First, try to use the alt text from the request. If not set, copy the image alt text from the original attachment .
781+ $ image_alt = isset ( $ request [ ' alt_text ' ] ) ? sanitize_text_field ( $ request [ ' alt_text ' ] ) : get_post_meta ( $ attachment_id , '_wp_attachment_image_alt ' , true );
745782
746783 if ( ! empty ( $ image_alt ) ) {
747784 // update_post_meta() expects slashed.
@@ -790,6 +827,7 @@ public function edit_media_item( $request ) {
790827 * @param int $new_attachment_id Attachment post ID for the new image.
791828 * @param int $attachment_id Attachment post ID for the edited (parent) image.
792829 */
830+ // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
793831 $ new_image_meta = apply_filters ( 'wp_edited_image_metadata ' , $ new_image_meta , $ new_attachment_id , $ attachment_id );
794832
795833 wp_update_attachment_metadata ( $ new_attachment_id , $ new_image_meta );
@@ -1480,62 +1518,101 @@ protected function check_upload_size( $file ) {
14801518 * Gets the request args for the edit item route.
14811519 *
14821520 * @since 5.5.0
1521+ * @since 6.9.0 Adds flips capability and editable fields for the newly-created attachment post.
14831522 *
14841523 * @return array
14851524 */
14861525 protected function get_edit_media_item_args () {
1487- return array (
1526+ $ args = array (
14881527 'src ' => array (
1489- 'description ' => __ ( 'URL to the edited image file. ' ),
1528+ 'description ' => __ ( 'URL to the edited image file. ' , ' gutenberg ' ),
14901529 'type ' => 'string ' ,
14911530 'format ' => 'uri ' ,
14921531 'required ' => true ,
14931532 ),
1533+ // The `modifiers` param takes precedence over the older format.
14941534 'modifiers ' => array (
1495- 'description ' => __ ( 'Array of image edits. ' ),
1535+ 'description ' => __ ( 'Array of image edits. ' , ' gutenberg ' ),
14961536 'type ' => 'array ' ,
14971537 'minItems ' => 1 ,
14981538 'items ' => array (
1499- 'description ' => __ ( 'Image edit. ' ),
1539+ 'description ' => __ ( 'Image edit. ' , ' gutenberg ' ),
15001540 'type ' => 'object ' ,
15011541 'required ' => array (
15021542 'type ' ,
15031543 'args ' ,
15041544 ),
15051545 'oneOf ' => array (
15061546 array (
1507- 'title ' => __ ( 'Rotation ' ),
1547+ 'title ' => __ ( 'Flip ' , 'gutenberg ' ),
1548+ 'properties ' => array (
1549+ 'type ' => array (
1550+ 'description ' => __ ( 'Flip type. ' , 'gutenberg ' ),
1551+ 'type ' => 'string ' ,
1552+ 'enum ' => array ( 'flip ' ),
1553+ ),
1554+ 'args ' => array (
1555+ 'description ' => __ ( 'Flip arguments. ' , 'gutenberg ' ),
1556+ 'type ' => 'object ' ,
1557+ 'required ' => array (
1558+ 'flip ' ,
1559+ ),
1560+ 'properties ' => array (
1561+ 'flip ' => array (
1562+ 'description ' => __ ( 'Flip direction. [ horizontal, vertical ] 0 for no flip, 1 for flip. ' , 'gutenberg ' ),
1563+ 'type ' => 'object ' ,
1564+ 'required ' => array (
1565+ 'horizontal ' ,
1566+ 'vertical ' ,
1567+ ),
1568+ 'properties ' => array (
1569+ 'horizontal ' => array (
1570+ 'description ' => __ ( 'Horizontal flip direction. 0 for no flip, 1 for flip. ' , 'gutenberg ' ),
1571+ 'type ' => 'number ' ,
1572+ ),
1573+ 'vertical ' => array (
1574+ 'description ' => __ ( 'Vertical flip direction. 0 for no flip, 1 for flip. ' , 'gutenberg ' ),
1575+ 'type ' => 'number ' ,
1576+ ),
1577+ ),
1578+ ),
1579+ ),
1580+ ),
1581+ ),
1582+ ),
1583+ array (
1584+ 'title ' => __ ( 'Rotation ' , 'gutenberg ' ),
15081585 'properties ' => array (
15091586 'type ' => array (
1510- 'description ' => __ ( 'Rotation type. ' ),
1587+ 'description ' => __ ( 'Rotation type. ' , ' gutenberg ' ),
15111588 'type ' => 'string ' ,
15121589 'enum ' => array ( 'rotate ' ),
15131590 ),
15141591 'args ' => array (
1515- 'description ' => __ ( 'Rotation arguments. ' ),
1592+ 'description ' => __ ( 'Rotation arguments. ' , ' gutenberg ' ),
15161593 'type ' => 'object ' ,
15171594 'required ' => array (
15181595 'angle ' ,
15191596 ),
15201597 'properties ' => array (
15211598 'angle ' => array (
1522- 'description ' => __ ( 'Angle to rotate clockwise in degrees. ' ),
1599+ 'description ' => __ ( 'Angle to rotate clockwise in degrees. ' , ' gutenberg ' ),
15231600 'type ' => 'number ' ,
15241601 ),
15251602 ),
15261603 ),
15271604 ),
15281605 ),
15291606 array (
1530- 'title ' => __ ( 'Crop ' ),
1607+ 'title ' => __ ( 'Crop ' , ' gutenberg ' ),
15311608 'properties ' => array (
15321609 'type ' => array (
1533- 'description ' => __ ( 'Crop type. ' ),
1610+ 'description ' => __ ( 'Crop type. ' , ' gutenberg ' ),
15341611 'type ' => 'string ' ,
15351612 'enum ' => array ( 'crop ' ),
15361613 ),
15371614 'args ' => array (
1538- 'description ' => __ ( 'Crop arguments. ' ),
1615+ 'description ' => __ ( 'Crop arguments. ' , ' gutenberg ' ),
15391616 'type ' => 'object ' ,
15401617 'required ' => array (
15411618 'left ' ,
@@ -1545,19 +1622,19 @@ protected function get_edit_media_item_args() {
15451622 ),
15461623 'properties ' => array (
15471624 'left ' => array (
1548- 'description ' => __ ( 'Horizontal position from the left to begin the crop as a percentage of the image width. ' ),
1625+ 'description ' => __ ( 'Horizontal position from the left to begin the crop as a percentage of the image width. ' , ' gutenberg ' ),
15491626 'type ' => 'number ' ,
15501627 ),
15511628 'top ' => array (
1552- 'description ' => __ ( 'Vertical position from the top to begin the crop as a percentage of the image height. ' ),
1629+ 'description ' => __ ( 'Vertical position from the top to begin the crop as a percentage of the image height. ' , ' gutenberg ' ),
15531630 'type ' => 'number ' ,
15541631 ),
15551632 'width ' => array (
1556- 'description ' => __ ( 'Width of the crop as a percentage of the image width. ' ),
1633+ 'description ' => __ ( 'Width of the crop as a percentage of the image width. ' , ' gutenberg ' ),
15571634 'type ' => 'number ' ,
15581635 ),
15591636 'height ' => array (
1560- 'description ' => __ ( 'Height of the crop as a percentage of the image height. ' ),
1637+ 'description ' => __ ( 'Height of the crop as a percentage of the image height. ' , ' gutenberg ' ),
15611638 'type ' => 'number ' ,
15621639 ),
15631640 ),
@@ -1568,37 +1645,65 @@ protected function get_edit_media_item_args() {
15681645 ),
15691646 ),
15701647 'rotation ' => array (
1571- 'description ' => __ ( 'The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead. ' ),
1648+ 'description ' => __ ( 'The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead. ' , ' gutenberg ' ),
15721649 'type ' => 'integer ' ,
15731650 'minimum ' => 0 ,
15741651 'exclusiveMinimum ' => true ,
15751652 'maximum ' => 360 ,
15761653 'exclusiveMaximum ' => true ,
15771654 ),
15781655 'x ' => array (
1579- 'description ' => __ ( 'As a percentage of the image, the x position to start the crop from. DEPRECATED: Use `modifiers` instead. ' ),
1656+ 'description ' => __ ( 'As a percentage of the image, the x position to start the crop from. DEPRECATED: Use `modifiers` instead. ' , ' gutenberg ' ),
15801657 'type ' => 'number ' ,
15811658 'minimum ' => 0 ,
15821659 'maximum ' => 100 ,
15831660 ),
15841661 'y ' => array (
1585- 'description ' => __ ( 'As a percentage of the image, the y position to start the crop from. DEPRECATED: Use `modifiers` instead. ' ),
1662+ 'description ' => __ ( 'As a percentage of the image, the y position to start the crop from. DEPRECATED: Use `modifiers` instead. ' , ' gutenberg ' ),
15861663 'type ' => 'number ' ,
15871664 'minimum ' => 0 ,
15881665 'maximum ' => 100 ,
15891666 ),
15901667 'width ' => array (
1591- 'description ' => __ ( 'As a percentage of the image, the width to crop the image to. DEPRECATED: Use `modifiers` instead. ' ),
1668+ 'description ' => __ ( 'As a percentage of the image, the width to crop the image to. DEPRECATED: Use `modifiers` instead. ' , ' gutenberg ' ),
15921669 'type ' => 'number ' ,
15931670 'minimum ' => 0 ,
15941671 'maximum ' => 100 ,
15951672 ),
15961673 'height ' => array (
1597- 'description ' => __ ( 'As a percentage of the image, the height to crop the image to. DEPRECATED: Use `modifiers` instead. ' ),
1674+ 'description ' => __ ( 'As a percentage of the image, the height to crop the image to. DEPRECATED: Use `modifiers` instead. ' , ' gutenberg ' ),
15981675 'type ' => 'number ' ,
15991676 'minimum ' => 0 ,
16001677 'maximum ' => 100 ,
16011678 ),
16021679 );
1680+
1681+ /*
1682+ * Get the args based on the post schema. This calls `rest_get_endpoint_args_for_schema()`,
1683+ * which also takes care of sanitization and validation.
1684+ */
1685+ $ update_item_args = $ this ->get_endpoint_args_for_item_schema ( WP_REST_Server::EDITABLE );
1686+
1687+ if ( isset ( $ update_item_args ['caption ' ] ) ) {
1688+ $ args ['caption ' ] = $ update_item_args ['caption ' ];
1689+ }
1690+
1691+ if ( isset ( $ update_item_args ['description ' ] ) ) {
1692+ $ args ['description ' ] = $ update_item_args ['description ' ];
1693+ }
1694+
1695+ if ( isset ( $ update_item_args ['title ' ] ) ) {
1696+ $ args ['title ' ] = $ update_item_args ['title ' ];
1697+ }
1698+
1699+ if ( isset ( $ update_item_args ['post ' ] ) ) {
1700+ $ args ['post ' ] = $ update_item_args ['post ' ];
1701+ }
1702+
1703+ if ( isset ( $ update_item_args ['alt_text ' ] ) ) {
1704+ $ args ['alt_text ' ] = $ update_item_args ['alt_text ' ];
1705+ }
1706+
1707+ return $ args ;
16031708 }
16041709}
0 commit comments