$status, 'search' => sanitize_text_field( (string) $search ), 'paged' => max( 1, absint( $paged ) ), ); } /** * Query a page of images. * * @param array{status: string, search: string, paged: int} $filters Filters. * @return array{items: array>, total: int, pages: int, paged: int, status: string, search: string} */ public static function query( $filters ) { $args = self::base_args( $filters['status'] ); $paged = (int) $filters['paged']; $args['posts_per_page'] = self::PER_PAGE; $args['paged'] = $paged; $args['orderby'] = array( 'date' => 'DESC', 'ID' => 'DESC', ); if ( '' !== $filters['search'] ) { $args['s'] = $filters['search']; } $query = new \WP_Query( $args ); $pages = max( 1, (int) $query->max_num_pages ); if ( $paged > $pages && (int) $query->found_posts > 0 ) { $filters['paged'] = $pages; return self::query( $filters ); } $items = array(); foreach ( $query->posts as $post ) { if ( $post instanceof \WP_Post ) { $item = self::item_from_post( $post ); if ( null !== $item ) { $items[] = $item; } } } $pages = max( 1, (int) $query->max_num_pages ); if ( $paged > $pages ) { $paged = $pages; } return array( 'items' => $items, 'total' => (int) $query->found_posts, 'pages' => $pages, 'paged' => $paged, 'status' => $filters['status'], 'search' => $filters['search'], ); } /** * Next pending attachment IDs after a cursor. * * @param int $after_id Return IDs greater than this one. * @param int $limit Maximum IDs to return. * @return int[] */ public static function pending_ids( $after_id, $limit ) { self::$after_id = max( 0, absint( $after_id ) ); add_filter( 'posts_where', array( self::class, 'where_after_id' ), 10, 2 ); $args = self::base_args( 'pending' ); $args['posts_per_page'] = min( 50, max( 1, absint( $limit ) ) ); $args['fields'] = 'ids'; $args['orderby'] = 'ID'; $args['order'] = 'ASC'; $args['no_found_rows'] = true; $args['flipwebp_cursor'] = 1; $query = new \WP_Query( $args ); remove_filter( 'posts_where', array( self::class, 'where_after_id' ), 10 ); self::$after_id = 0; $ids = array(); foreach ( $query->posts as $post_id ) { $post_id = absint( $post_id ); if ( $post_id && current_user_can( 'edit_post', $post_id ) ) { $ids[] = $post_id; } } return $ids; } /** * Restrict a cursor query to IDs greater than the last processed ID. * * @param string $where Existing WHERE clause. * @param \WP_Query $query Query. * @return string */ public static function where_after_id( $where, $query ) { if ( ! $query->get( 'flipwebp_cursor' ) || self::$after_id < 1 ) { return $where; } global $wpdb; $where .= $wpdb->prepare( ' AND %i.ID > %d', $wpdb->posts, self::$after_id ); return $where; } /** * Public row data for one attachment. * * @param int $attachment_id Attachment ID. * @return array|null */ public static function item( $attachment_id ) { $post = get_post( (int) $attachment_id ); if ( ! $post instanceof \WP_Post ) { return null; } return self::item_from_post( $post ); } /** * Admin URL for a library page. * * @param string $status Filter. * @param string $search Search text. * @param int $paged Page number. * @return string */ public static function page_url( $status, $search, $paged ) { $args = array( 'page' => 'flipwebp', 'status' => sanitize_key( (string) $status ), 'paged' => max( 1, absint( $paged ) ), ); $search = sanitize_text_field( (string) $search ); if ( '' !== $search ) { $args['s'] = $search; } return add_query_arg( $args, admin_url( 'upload.php' ) ); } /** * Short status text for the Media Library column. * * @param int $attachment_id Attachment ID. * @return string */ public static function status_label_for( $attachment_id ) { $item = self::item( (int) $attachment_id ); if ( null === $item ) { return ''; } return (string) $item['statusLabel']; } /** * @param \WP_Post $post Attachment. * @return array|null */ private static function item_from_post( $post ) { if ( 'attachment' !== $post->post_type || 'trash' === $post->post_status ) { return null; } if ( ! Capabilities::can_convert( $post->ID ) ) { return null; } $mime = (string) get_post_mime_type( $post ); if ( ! Converter::is_manageable_mime( $mime ) ) { return null; } $meta = wp_get_attachment_metadata( $post->ID ); $width = ( is_array( $meta ) && ! empty( $meta['width'] ) ) ? (int) $meta['width'] : 0; $height = ( is_array( $meta ) && ! empty( $meta['height'] ) ) ? (int) $meta['height'] : 0; $status = (string) get_post_meta( $post->ID, '_flipwebp_status', true ); $reason = (string) get_post_meta( $post->ID, '_flipwebp_skip_reason', true ); $saved = (int) get_post_meta( $post->ID, '_flipwebp_saved_bytes', true ); $file = get_attached_file( $post->ID ); $filename = is_string( $file ) && '' !== $file ? wp_basename( $file ) : wp_basename( (string) $post->guid ); if ( 'converted' === $status ) { $key = 'converted'; $label = __( 'Converted', 'flipwebp' ); } elseif ( 'skipped' === $status && 'animated' === $reason ) { $key = 'skipped'; $label = __( 'Animation kept', 'flipwebp' ); } elseif ( 'skipped' === $status ) { $key = 'skipped'; $label = __( 'Original kept', 'flipwebp' ); } elseif ( 'image/webp' === $mime ) { $key = 'webp'; $label = __( 'WebP', 'flipwebp' ); } else { $key = 'pending'; $label = __( 'Not converted', 'flipwebp' ); } $thumb = wp_get_attachment_image_url( $post->ID, 'thumbnail' ); if ( ! is_string( $thumb ) || '' === $thumb ) { $thumb = wp_get_attachment_image_url( $post->ID, 'full' ); } $title = get_the_title( $post ); if ( '' === $title ) { $title = $filename; } return array( 'id' => (int) $post->ID, 'title' => $title, 'filename' => $filename, 'mime' => $mime, 'mimeLabel' => self::mime_label( $mime ), 'dimensions' => ( $width > 0 && $height > 0 ) ? $width . ' × ' . $height : '—', 'sizeLabel' => size_format( max( 0, self::current_bytes( $post->ID ) ), 1 ), 'savedLabel' => $saved > 0 ? size_format( $saved, 1 ) : '—', 'status' => $key, 'statusLabel' => $label, 'actionLabel' => ( 'image/webp' === $mime || 'converted' === $status ) ? __( 'Recompress', 'flipwebp' ) : __( 'Convert', 'flipwebp' ), 'confirmUrl' => self::action_url( $post->ID, 'confirm' ), 'restoreUrl' => Backup::exists( $post->ID ) ? self::action_url( $post->ID, 'restore' ) : '', 'thumb' => is_string( $thumb ) ? $thumb : '', ); } /** * @param string $status Filter. * @return array */ private static function base_args( $status ) { $args = array( 'post_type' => 'attachment', 'post_status' => array( 'inherit', 'private' ), 'flipwebp_library' => 1, 'ignore_sticky_posts' => true, 'update_post_term_cache' => false, 'post_mime_type' => array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp' ), ); if ( ! Capabilities::can_see_all() ) { $args['author'] = get_current_user_id(); } if ( 'pending' === $status ) { $args['post_mime_type'] = array( 'image/jpeg', 'image/png', 'image/gif' ); $args['meta_query'] = array( 'relation' => 'OR', array( 'key' => '_flipwebp_status', 'compare' => 'NOT EXISTS', ), array( 'key' => '_flipwebp_status', 'value' => array( 'converted', 'skipped' ), 'compare' => 'NOT IN', ), ); } elseif ( 'converted' === $status ) { $args['meta_query'] = array( array( 'key' => '_flipwebp_status', 'value' => 'converted', ), ); } return $args; } /** * @param int $attachment_id Attachment ID. * @param string $action confirm or restore. * @return string */ private static function action_url( $attachment_id, $action ) { return wp_nonce_url( add_query_arg( array( 'page' => 'flipwebp', $action => (int) $attachment_id, ), admin_url( 'upload.php' ) ), 'flipwebp_' . $action . '_' . (int) $attachment_id ); } /** * @param string $mime MIME type. * @return string */ private static function mime_label( $mime ) { switch ( $mime ) { case 'image/jpeg': return 'JPEG'; case 'image/png': return 'PNG'; case 'image/gif': return 'GIF'; case 'image/webp': return 'WebP'; default: return ''; } } /** * Current bytes used by the attachment and its generated sizes. * * @param int $attachment_id Attachment ID. * @return int */ private static function current_bytes( $attachment_id ) { return Filesystem::total_size( self::files( $attachment_id ) ); } /** * Local files belonging to an attachment. * * @param int $attachment_id Attachment ID. * @return string[] */ public static function files( $attachment_id ) { $files = array(); $main = get_attached_file( $attachment_id ); if ( is_string( $main ) && '' !== $main ) { $files[] = $main; } if ( function_exists( 'wp_get_original_image_path' ) ) { $original = wp_get_original_image_path( $attachment_id ); if ( is_string( $original ) && '' !== $original ) { $files[] = $original; } } $meta = wp_get_attachment_metadata( $attachment_id ); if ( is_string( $main ) && is_array( $meta ) && ! empty( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) { $dir = dirname( $main ); foreach ( $meta['sizes'] as $size ) { if ( ! is_array( $size ) || empty( $size['file'] ) || ! is_string( $size['file'] ) ) { continue; } if ( ! Filesystem::is_safe_basename( $size['file'] ) ) { continue; } $files[] = path_join( $dir, $size['file'] ); } } return $files; } }