0 ) { Reviewed_Store::update_post_meta( (int) $id, self::BYTES, self::bytes_of( (int) $id, $data ) ); } return $data; } /** * The size to sort by. * * @param int $id The attachment. * @param array $meta Its metadata. * @return int Bytes, or -1. */ private static function bytes_of( int $id, array $meta ): int { if ( isset( $meta['filesize'] ) && is_numeric( $meta['filesize'] ) && (int) $meta['filesize'] >= 0 ) { return (int) $meta['filesize']; } $file = get_attached_file( $id ); return is_string( $file ) && '' !== $file && is_file( $file ) ? (int) wp_filesize( $file ) : -1; } /** * Note the size of attachments not measured yet (uploads from before 1.9.0), oldest first, at most $limit. The * caller holds the owner lock. * * @param int $limit At most this many. * @return bool Whether more remain. */ public static function measure( int $limit = 300 ): bool { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- one bounded batch when the file-size sort is chosen; it must read the current rows. $ids = $wpdb->get_col( $wpdb->prepare( "SELECT p.ID FROM %i p LEFT JOIN %i m ON m.post_id = p.ID AND m.meta_key = %s WHERE p.post_type = 'attachment' AND p.post_status = 'inherit' AND m.meta_id IS NULL ORDER BY p.ID ASC LIMIT %d", $wpdb->posts, $wpdb->postmeta, self::BYTES, $limit + 1 ) ); $ids = array_map( 'intval', (array) $ids ); foreach ( array_slice( $ids, 0, $limit ) as $id ) { $meta = wp_get_attachment_metadata( $id ); Reviewed_Store::update_post_meta( $id, self::BYTES, self::bytes_of( $id, is_array( $meta ) ? $meta : array() ) ); } return count( $ids ) > $limit; } /** * The library's file-type filter choices. * * @return array Filter key => translated name. */ public static function types(): array { return array( 'all' => __( 'All file types', 'affstackdev-image-manager' ), 'image' => __( 'All images', 'affstackdev-image-manager' ), 'jpeg' => __( 'JPEG images', 'affstackdev-image-manager' ), 'png' => __( 'PNG images', 'affstackdev-image-manager' ), 'webp' => __( 'WebP images', 'affstackdev-image-manager' ), 'avif' => __( 'AVIF images', 'affstackdev-image-manager' ), 'gif' => __( 'GIF images', 'affstackdev-image-manager' ), 'svg' => __( 'SVG images', 'affstackdev-image-manager' ), 'heic' => __( 'HEIC / HEIF images', 'affstackdev-image-manager' ), 'tiff' => __( 'TIFF images', 'affstackdev-image-manager' ), 'bmp' => __( 'BMP images', 'affstackdev-image-manager' ), 'pdf' => __( 'PDF documents', 'affstackdev-image-manager' ), 'document' => __( 'Documents', 'affstackdev-image-manager' ), 'video' => __( 'Video', 'affstackdev-image-manager' ), 'audio' => __( 'Audio', 'affstackdev-image-manager' ), 'archive' => __( 'Archives', 'affstackdev-image-manager' ), ); } /** * WordPress's saved MIME type is authoritative, not the attachment title. * * @param string $type A filter key from types(). * @return string|string[] The MIME type or types to query, or an empty string for all. */ public static function mime_filter( string $type ) { $types = array( 'image' => 'image', 'video' => 'video', 'audio' => 'audio', 'jpeg' => array( 'image/jpeg', 'image/pjpeg' ), 'png' => 'image/png', 'webp' => 'image/webp', 'avif' => 'image/avif', 'gif' => 'image/gif', 'svg' => 'image/svg+xml', 'heic' => array( 'image/heic', 'image/heif', 'image/heic-sequence', 'image/heif-sequence' ), 'tiff' => 'image/tiff', 'bmp' => array( 'image/bmp', 'image/x-ms-bmp' ), 'pdf' => 'application/pdf', // Preserve the earlier Documents filter, including its ZIP matches. 'document' => array( 'application/pdf', 'text/plain', 'text/csv', 'application/rtf', 'application/zip', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation', ), 'archive' => array( 'application/zip', 'application/x-zip-compressed', 'application/x-rar-compressed', 'application/vnd.rar', 'application/x-7z-compressed', 'application/gzip', 'application/x-gzip', 'application/x-tar' ), ); return $types[ $type ] ?? ''; } /** * The library's sort choices; the file-size sorts only on MySQL and MariaDB. * * @return array Sort key => translated name. */ public static function sorts(): array { $sorts = array( 'newest' => __( 'Newest first', 'affstackdev-image-manager' ), 'oldest' => __( 'Oldest first', 'affstackdev-image-manager' ), 'title_asc' => __( 'Title: A to Z', 'affstackdev-image-manager' ), 'title_desc' => __( 'Title: Z to A', 'affstackdev-image-manager' ), 'file_type' => __( 'File type, then title', 'affstackdev-image-manager' ), 'modified' => __( 'Recently updated', 'affstackdev-image-manager' ), ); // 1.9.0: by each file's size (Media_Display::BYTES); MySQL and MariaDB only. global $wpdb; if ( empty( $wpdb->is_sqlite ) ) { $sorts['size_desc'] = __( 'File size: largest first', 'affstackdev-image-manager' ); $sorts['size_asc'] = __( 'File size: smallest first', 'affstackdev-image-manager' ); } return $sorts; } /** * Sort the matching library before pagination; never change another query. * * @param array $args WP_Query arguments for the library page. * @param string $sort A sort key from sorts(). * @return \WP_Query The query, run. */ public static function query( array $args, string $sort ): \WP_Query { $query = new \WP_Query(); $order = static function ( $orderby, $candidate ) use ( $query, $sort ) { if ( $candidate !== $query ) { return $orderby; } global $wpdb; $table = $wpdb->prepare( '%i', $wpdb->posts ); switch ( $sort ) { case 'size_desc': case 'size_asc': // The size Image Manager keeps for each attachment; files whose size is not known come last. $bytes = $wpdb->prepare( '(SELECT MAX(CAST(b.meta_value AS SIGNED)) FROM %i b WHERE b.post_id = %i.ID AND b.meta_key = %s)', $wpdb->postmeta, $wpdb->posts, self::BYTES ); return "COALESCE($bytes, -1) < 0 ASC, $bytes " . ( 'size_asc' === $sort ? 'ASC' : 'DESC' ) . ", $table.ID DESC"; case 'file_type': return "$table.post_mime_type ASC, $table.post_title ASC, $table.ID ASC"; case 'title_asc': return "$table.post_title ASC, $table.ID ASC"; case 'title_desc': return "$table.post_title DESC, $table.ID DESC"; case 'oldest': return "$table.post_date ASC, $table.ID ASC"; case 'modified': return "$table.post_modified DESC, $table.ID DESC"; default: return "$table.post_date DESC, $table.ID DESC"; } }; add_filter( 'posts_orderby', $order, 20, 2 ); try { $query->query( $args ); } finally { remove_filter( 'posts_orderby', $order, 20 ); } return $query; } /** * Read a saved raster preview only. No rendering, file probing, or PDF download. * * @param int $id The PDF attachment. * @param array $meta Its metadata. * @param bool $details True for the larger preview of the details screen. * @return array The preview's address, width and height, or an empty array when WordPress saved none that fits. */ public static function pdf_preview( int $id, array $meta, bool $details = false ): array { $sizes = isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ? $meta['sizes'] : array(); $limit = $details ? 1200 : 320; $preferred = $details ? array( 'medium', 'thumbnail', 'large', 'full' ) : array( 'thumbnail', 'medium', 'full' ); foreach ( $preferred as $name ) { $size = $sizes[ $name ] ?? null; if ( ! is_array( $size ) || ! isset( $size['width'], $size['height'], $size['file'] ) || ! is_numeric( $size['width'] ) || ! is_numeric( $size['height'] ) || ! is_string( $size['file'] ) ) { continue; } $width = (int) $size['width']; $height = (int) $size['height']; if ( min( $width, $height ) < 1 || max( $width, $height ) > $limit ) { continue; } if ( basename( $size['file'] ) !== $size['file'] || ! preg_match( '/\.(jpe?g|png|webp|avif|gif)$/iD', $size['file'] ) ) { continue; } $preview = wp_get_attachment_image_src( $id, $name, false ); if ( ! is_array( $preview ) || ! isset( $preview[0], $preview[1], $preview[2] ) || ! is_string( $preview[0] ) ) { continue; } $path = wp_parse_url( $preview[0], PHP_URL_PATH ); // A document URL must never reach an , including through a filter. if ( ! is_string( $path ) || ! preg_match( '/\.(jpe?g|png|webp|avif|gif)$/iD', $path ) ) { continue; } if ( min( (int) $preview[1], (int) $preview[2] ) < 1 || max( (int) $preview[1], (int) $preview[2] ) > $limit ) { continue; } return array( $preview[0], (int) $preview[1], (int) $preview[2] ); } return array(); } }