diff --git a/src/wp-admin/includes/class-wp-media-list-table.php b/src/wp-admin/includes/class-wp-media-list-table.php index 7ac2ac3649d..292d6b5a520 100644 --- a/src/wp-admin/includes/class-wp-media-list-table.php +++ b/src/wp-admin/includes/class-wp-media-list-table.php @@ -182,16 +182,17 @@ protected function get_bulk_actions() { if ( MEDIA_TRASH ) { if ( $this->is_trash ) { $actions['untrash'] = __( 'Restore' ); - $actions['delete'] = __( 'Delete permanently' ); + $actions['delete'] = __( 'Delete permanently' ); } else { $actions['trash'] = __( 'Move to Trash' ); } } else { + $actions['edit'] = __( 'Edit' ); $actions['delete'] = __( 'Delete permanently' ); } if ( $this->detached ) { - $actions['attach'] = __( 'Attach' ); + $actions['attach'] = __( 'Attach to post' ); } return $actions; @@ -356,6 +357,9 @@ public function get_columns() { } /* translators: Column name. */ + $posts_columns['thumbnail'] = _x( 'Featured Image', 'column name' ); + $posts_columns['used_in'] = _x( 'Used In', 'column name' ); + if ( ! $this->detached ) { $posts_columns['parent'] = _x( 'Uploaded to', 'column name' ); @@ -389,11 +393,13 @@ public function get_columns() { */ protected function get_sortable_columns() { return array( - 'title' => 'title', - 'author' => 'author', - 'parent' => 'parent', - 'comments' => 'comment_count', - 'date' => array( 'date', true ), + 'title' => 'title', + 'author' => 'author', + 'parent' => 'parent', + 'thumbnail' => 'thumbnail', + 'used_in' => 'used_in', + 'comments' => 'comment_count', + 'date' => array( 'date', true ), ); } @@ -552,6 +558,89 @@ public function column_date( $post ) { echo apply_filters( 'media_date_column_time', $h_time, $post, 'date' ); } + /** + * Handles the thumbnail column output. + * + * @since CP-2.2.0 + * + * @param WP_Post $post The current WP_Post object. + */ + public function column_thumbnail( $post ) { + $user_can_edit = current_user_can( 'edit_post', $post->ID ); + + // Get all post types except attachments and revisions. + $parent_types = get_post_types(); + unset( $parent_types['attachment'] ); + unset( $parent_types['revision'] ); + + // Set output variable. + $output = ''; + + foreach ( $parent_types as $parent_type ) { + + // Get all posts where this attachment is the featured image. + $relationship_ids = cp_get_object_relationship_ids( $post->ID, 'thumbnail', $parent_type ); + + if ( ! empty( $relationship_ids ) ) { + foreach ( $relationship_ids as $relationship_id ) { + if ( absint( $relationship_id ) !== 0 ) { + $ancestor = get_post( $relationship_id ); + $ancestor_type_obj = get_post_type_object( $ancestor->post_type ); + $title = _draft_or_post_title( $relationship_id ); + + if ( $ancestor_type_obj->show_ui && current_user_can( 'edit_post', $relationship_id ) ) { + $output .= '' . esc_html( $title ) . '
'; + } else { + $output .= $title . '
'; + } + } + } + } + } + return $output; + } + + /** + * Handles the used-in column output. + * + * @since CP-2.2.0 + * + * @param WP_Post $post The current WP_Post object. + */ + public function column_used_in( $post ) { + $user_can_edit = current_user_can( 'edit_post', $post->ID ); + + // Get all post types except attachments and revisions. + $parent_types = get_post_types(); + unset( $parent_types['attachment'] ); + unset( $parent_types['revision'] ); + + // Set output variable. + $output = ''; + + foreach ( $parent_types as $parent_type ) { + + // Get all posts where this attachment is used in the content. + $parent_ids = cp_get_object_relationship_ids( $post->ID, 'attachment', $parent_type ); + + if ( ! empty( $parent_ids ) ) { + foreach ( $parent_ids as $parent_id ) { + if ( absint( $parent_id ) !== 0 ) { + $parent_type_obj = get_post_type_object( $parent_type ); + $title = _draft_or_post_title( $parent_id ); + + if ( $parent_type_obj->show_ui && current_user_can( 'edit_post', $parent_id ) ) { + $output .= '' . esc_html( $title ) . '
'; + } else { + $output .= $title . '
'; + } + } + } + } + } + return $output; + } + /** * Handles the parent column output. * diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 81f4eea6aab..32910c68040 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5059,6 +5059,165 @@ function attachment_url_to_postid( $url ) { return (int) apply_filters( 'attachment_url_to_postid', $post_id, $url ); } +/** + * Gets an attachment ID from its URL. + * + * Based on https://stackoverflow.com/a/50335619 + * + * @since CP-2.2.0 + * + * @param string $url URL of media file. + * + * @return int $attachment_id on success, 0 on failure. + */ +function cp_get_attachment_id_from_url( $url ) { + + $attachment_id = 0; + $dir = wp_upload_dir(); + + if ( 0 === strpos( $url, $dir['baseurl'] . '/' ) ) { // Is URL in uploads directory? + + $file = basename( $url ); + + $args = array( + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'fields' => 'ids', + 'meta_query' => array( + array( + 'value' => $file, + 'compare' => 'LIKE', + 'key' => '_wp_attachment_metadata', + ), + ), + ); + $post_ids = get_posts( $args ); + + if ( ! empty( $post_ids ) ) { + foreach ( $post_ids as $post_id ) { + $meta = wp_get_attachment_metadata( $post_id ); + + $original_file = basename( $meta['file'] ); + $cropped_image_files = wp_list_pluck( $meta['sizes'], 'file' ); + + if ( $original_file === $file || in_array( $file, $cropped_image_files ) ) { + $attachment_id = $post_id; + break; + } + } + } + } + return $attachment_id; +} + +/** + * Creates a relationship between a post, page, or custom post and an attachment. + * + * @since CP-2.2.0 + * + * @param int $post_id Post ID. + * @param WP_Post $post Post object. + * @param bool $update Whether this is an existing post being updated. + */ +function cp_create_post_attachment_relationship( $post_id, $post, $update ) { + + // Don't run for updates because we have another function for that. + if ( $update ) { + return; + } + + // Create empty array of attachment IDs. + $attachment_ids = array(); + + // Get all hyperlinks included in a post. + $link_strings = wp_extract_urls( $post->post_content ); + + // Get the attachment ID (if it exists) for each URL. + if ( ! empty( $link_strings ) ) { + foreach ( $link_strings as $link_string ) { + $attachment_id = cp_get_attachment_id_from_url( $link_string ); + + // Filter out all hyperlinks that do not point to the uploads folder. + if ( absint( $attachment_id ) !== 0 ) { + $attachment_ids[] = $attachment_id; + } + } + } + + // Create a relationship between the post and the attachment. + if ( ! empty( $attachment_ids ) ) { + foreach ( $attachment_ids as $attachment_id ) { + cp_add_object_relationship( $attachment_id, 'attachment', $post->post_type, $post_id ); + } + } +} + +/** + * Updates the relationship between a post, page, or custom post and an attachment. + * + * @since CP-2.2.0 + * + * @param int $post_id Post ID. + * @param WP_Post $post_after Post object following the update. + * @param WP_Post $post_before Post object before the update. + * @param string $post_type Post type. + */ +function cp_update_post_attachment_relationship( $post_id, $post_after, $post_before, $post_type ) { + + // Create empty array of attachment IDs after the post was updated. + $new_attachment_ids = array(); + + // Get all hyperlinks included in a post. + $link_strings = wp_extract_urls( $post_after->post_content ); + + // Get the attachment ID (if it exists) for each URL. + if ( ! empty( $link_strings ) ) { + foreach ( $link_strings as $link_string ) { + $new_attachment_id = cp_get_attachment_id_from_url( $link_string ); + + // Filter out all hyperlinks that do not point to the uploads folder. + if ( absint( $new_attachment_id ) !== 0 ) { + $new_attachment_ids[] = $new_attachment_id; + } + } + } + + // Update a relationship between the post and the attachment. + if ( ! empty( $new_attachment_ids ) ) { + foreach ( $new_attachment_ids as $new_attachment_id ) { + cp_add_object_relationship( $new_attachment_id, 'attachment', $post_type, $post_id ); + } + } + + // Create empty array of attachment IDs before the post was updated. + $old_attachment_ids = array(); + + // Get previous hyperlinks included in the post before it was updated. + $old_link_strings = wp_extract_urls( $post_before->post_content ); + + // Get the attachment ID (if it exists) for each URL. + if ( ! empty( $old_link_strings ) ) { + foreach ( $old_link_strings as $old_link_string ) { + $old_attachment_id = cp_get_attachment_id_from_url( $old_link_string ); + + // Filter out all hyperlinks that do not point to the uploads folder. + if ( absint( $old_attachment_id ) !== 0 ) { + $old_attachment_ids[] = $old_attachment_id; + } + } + } + + // Identify the hyperlinks from the old array that are not present in the updated array. + $removed_attachment_ids = array_diff( $old_attachment_ids, $new_attachment_ids ); + + // Remove the relationship between the post and the attachments that are no longer used in it. + if ( ! empty( $removed_attachment_ids ) ) { + foreach ( $removed_attachment_ids as $removed_attachment_id ) { + cp_delete_object_relationship( $removed_attachment_id, 'attachment', $post_type, $post_id ); + } + } +} + /** * Returns the URLs for CSS files used in an iframe-sandbox'd TinyMCE media view. * @@ -5197,7 +5356,7 @@ function wp_show_heic_upload_error( $plupload_settings ) { * @param array $image_info Optional. Extended image information (passed by reference). * @return array|false Array of image information or false on failure. */ -function wp_getimagesize( $filename, ?array &$image_info = null ) { +function wp_getimagesize( $filename, array &$image_info = null ) { // Don't silence errors when in debug mode, unless running unit tests. if ( defined( 'WP_DEBUG' ) && WP_DEBUG && ! defined( 'WP_RUN_CORE_TESTS' ) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 51e70f1e594..4a86ca6dc8c 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -4465,6 +4465,18 @@ function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true ) $post_after = get_post( $post_id ); + /** + * Updates the relationship between the post and any attachment. + * + * @since CP-2.2.0 + * + * @param int $post_id Post ID. + * @param WP_Post $post_after Post object following the update. + * @param WP_Post $post_before Post object before the update. + * @param string $post->post_type Post type. + */ + cp_update_post_attachment_relationship( $post_id, $post_after, $post_before, $post->post_type ); + /** * Fires once an existing post has been updated. * @@ -4496,6 +4508,17 @@ function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true ) */ do_action( "save_post_{$post->post_type}", $post_id, $post, $update ); + /** + * Creates a relationship between the post and any attachment. + * + * @since CP-2.2.0 + * + * @param int $post_id Post ID. + * @param WP_Post $post Post object. + * @param bool $update Whether this is an existing post being updated. + */ + cp_create_post_attachment_relationship( $post_id, $post, $update ); + /** * Fires once a post has been saved. * @@ -7502,8 +7525,32 @@ function set_post_thumbnail( $post, $thumbnail_id ) { $thumbnail_id = absint( $thumbnail_id ); if ( $post && $thumbnail_id && get_post( $thumbnail_id ) ) { if ( wp_get_attachment_image( $thumbnail_id, 'thumbnail' ) ) { + + /** + * Creates a relationship between a post, page, or custom post and its thumbnail. + * + * @since CP-2.2.0 + * + * @param int $thumbnail_id Thumbnail ID. + * @param string $post->post_type Name of post type. + * @param int $post->ID Post ID. + */ + cp_add_object_relationship( $thumbnail_id, 'thumbnail', $post->post_type, $post->ID ); + return update_post_meta( $post->ID, '_thumbnail_id', $thumbnail_id ); } else { + + /** + * Deletes a relationship between a post, page, or custom post and its thumbnail. + * + * @since CP-2.2.0 + * + * @param int $thumbnail_id Thumbnail ID. + * @param string $post->post_type Name of post type. + * @param int $post->ID Post ID. + */ + cp_delete_object_relationship( $thumbnail_id, 'thumbnail', $post->post_type, $post->ID ); + return delete_post_meta( $post->ID, '_thumbnail_id' ); } } @@ -7521,6 +7568,19 @@ function set_post_thumbnail( $post, $thumbnail_id ) { function delete_post_thumbnail( $post ) { $post = get_post( $post ); if ( $post ) { + + /** + * Deletes a relationship between a post, page, or custom post and its thumbnail. + * + * @since CP-2.2.0 + * + * @param int $thumbnail_id Thumbnail ID. + * @param string $post->post_type Name of post type. + * @param int $post->ID Post ID. + */ + $thumbnail_id = get_post_thumbnail_id( $post ); + cp_delete_object_relationship( $thumbnail_id, 'thumbnail', $post->post_type, $post->ID ); + return delete_post_meta( $post->ID, '_thumbnail_id' ); } return false; diff --git a/src/wp-includes/version.php b/src/wp-includes/version.php index 6f86be8c341..26d869c53f0 100644 --- a/src/wp-includes/version.php +++ b/src/wp-includes/version.php @@ -54,7 +54,7 @@ * * @global int $cp_db_version */ -$cp_db_version = 1438; +$cp_db_version = 1446; /** * Holds the TinyMCE version.