3, 'editor_script_handles' => array( 'afsd-im-folder-gallery' ), 'render_callback' => array( self::class, 'block' ), // The same supports as the editor script, so the server knows the attributes they add (align). 'supports' => array( 'html' => false, 'align' => array( 'wide', 'full' ), ), 'attributes' => array( 'folder' => array( 'type' => 'integer', 'default' => 0, ), 'columns' => array( 'type' => 'integer', 'default' => 3, ), 'size' => array( 'type' => 'string', 'default' => 'thumbnail', ), 'link' => array( 'type' => 'string', 'default' => 'file', ), 'order' => array( 'type' => 'string', 'default' => 'newest', ), 'subfolders' => array( 'type' => 'boolean', 'default' => false, ), 'max' => array( 'type' => 'integer', 'default' => 50, ), ), ) ); add_action( 'enqueue_block_editor_assets', array( self::class, 'editor_data' ) ); } /** * Give the block editor every folder, with whether it is switched on for galleries, and the picture sizes, for people * who may edit posts. */ public static function editor_data(): void { if ( ! current_user_can( 'edit_posts' ) ) { return; } $folders = array(); foreach ( Library::terms( Library::FOLDER ) as $term ) { $folders[] = array( 'id' => (int) $term->term_id, 'name' => $term->name, 'shown' => self::shown( (int) $term->term_id ), ); } $data = array( 'folders' => $folders, 'sizes' => self::sizes(), ); wp_add_inline_script( 'afsd-im-folder-gallery', 'window.afsdIMFolderGallery=' . wp_json_encode( $data ) . ';', 'before' ); } /** * Whether a folder is switched on for website galleries. * * @param int $term_id The folder. * @return bool True when it is. */ public static function shown( int $term_id ): bool { return '1' === (string) get_term_meta( $term_id, self::META, true ); } /** Image sizes WordPress knows on this site, plus the full original. */ private static function sizes(): array { $out = array(); foreach ( get_intermediate_image_sizes() as $size ) { $out[] = $size; } $out[] = 'full'; return array_values( array_unique( $out ) ); } /** * Switch a folder on or off for website galleries. Only people who may manage media folders. * * @param int $term_id The folder. * @param bool $on True to switch it on. * @return bool True when the change was saved. */ public static function set_shown( int $term_id, bool $on ): bool { $im_reviewed_owner_lock = Reviewed_Store::enter(); try { if ( ! current_user_can( 'upload_files' ) ) { return false; } $term = get_term( $term_id, Library::FOLDER ); if ( ! $term || is_wp_error( $term ) ) { return false; } // Both return false when nothing had to change; the saved state read back is what counts. if ( $on ) { Reviewed_Store::update_term_meta( $term_id, self::META, '1' ); } else { Reviewed_Store::delete_term_meta( $term_id, self::META ); } return self::shown( $term_id ) === $on; } finally { Reviewed_Store::leave( $im_reviewed_owner_lock ); } } /** * The shortcode's gallery. * * @param mixed $atts The shortcode's attributes. * @return string The gallery's HTML. */ public static function shortcode( $atts ): string { $defaults = array( 'folder' => 0, 'columns' => 3, 'size' => 'thumbnail', 'link' => 'file', 'order' => 'newest', 'subfolders' => 'no', 'max' => 50, ); $atts = shortcode_atts( $defaults, is_array( $atts ) ? $atts : array(), self::SHORTCODE ); $atts['subfolders'] = in_array( strtolower( (string) $atts['subfolders'] ), array( '1', 'yes', 'true' ), true ); return self::render( $atts ); } /** * The block's gallery, in the block's wrapper. * * @param mixed $attributes The block's attributes. * @return string The gallery's HTML, or an empty string. */ public static function block( $attributes ): string { $html = self::render( is_array( $attributes ) ? $attributes : array() ); return '' === $html ? '' : '
' . $html . '
'; } /** * Attachment IDs of the gallery, newest first by default. Bounded; only images. * * @param int $folder The folder. * @param bool $subfolders Whether to include its subfolders. * @param string $order The order (newest, oldest or title). * @param int $max The most images. * @return array Attachment IDs. */ public static function ids( int $folder, bool $subfolders, string $order, int $max ): array { $folders = array( $folder ); if ( $subfolders ) { $children = get_term_children( $folder, Library::FOLDER ); if ( is_array( $children ) ) { foreach ( $children as $child ) { if ( self::shown( (int) $child ) ) { $folders[] = (int) $child; } } } } $sorts = array( 'newest' => array( 'date', 'DESC' ), 'oldest' => array( 'date', 'ASC' ), 'title' => array( 'title', 'ASC' ), ); $sort = $sorts[ $order ] ?? array( 'date', 'DESC' ); return array_map( 'intval', get_posts( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'fields' => 'ids', 'numberposts' => max( 1, min( self::MAX, $max ) ), 'orderby' => $sort[0], 'order' => $sort[1], 'no_found_rows' => true, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- One bounded attachment query for the folder the page shows; folder membership is a taxonomy relationship. 'tax_query' => array( array( 'taxonomy' => Library::FOLDER, 'field' => 'term_id', 'terms' => $folders, 'include_children' => false, ), ), ) ) ); } /** * A folder's gallery in WordPress's own gallery markup, its images linked to the file, the attachment page or nothing. * * @param array $a Folder, columns, size, link, subfolders, order and the most images. * @return string The gallery's HTML, or a note for media managers. */ public static function render( array $a ): string { $folder = absint( $a['folder'] ?? 0 ); $term = $folder ? get_term( $folder, Library::FOLDER ) : null; if ( ! $term || is_wp_error( $term ) ) { return self::note( __( 'Choose an Image Manager folder for this gallery.', 'affstackdev-image-manager' ) ); } if ( ! self::shown( $folder ) ) { /* translators: %s: folder name. */ return self::note( sprintf( __( 'The folder %s is not shown in website galleries, so visitors see nothing here. Switch it on in Image Manager: Library, Folders and collections.', 'affstackdev-image-manager' ), $term->name ) ); } $order = is_string( $a['order'] ?? null ) ? $a['order'] : 'newest'; $ids = self::ids( $folder, ! empty( $a['subfolders'] ), $order, (int) ( $a['max'] ?? 50 ) ); if ( ! $ids ) { /* translators: %s: folder name. */ return self::note( sprintf( __( 'The folder %s has no pictures yet.', 'affstackdev-image-manager' ), $term->name ) ); } $size = is_string( $a['size'] ?? null ) && in_array( $a['size'], self::sizes(), true ) ? $a['size'] : 'thumbnail'; $link = is_string( $a['link'] ?? null ) && in_array( $a['link'], array( 'file', 'attachment', 'none' ), true ) ? $a['link'] : 'file'; $columns = max( 1, min( 9, (int) ( $a['columns'] ?? 3 ) ) ); // WordPress's own gallery markup: themes style it, WordPress lazy-loads it, Image Manager delivery applies. return (string) gallery_shortcode( array( 'include' => implode( ',', $ids ), 'orderby' => 'post__in', 'columns' => $columns, 'size' => $size, 'link' => 'attachment' === $link ? '' : $link, ) ); } /** * Only people who manage media see why a gallery is empty; visitors see nothing. * * @param string $text Why the gallery is empty. * @return string The note's HTML for media managers, an empty string for others. */ private static function note( string $text ): string { return current_user_can( 'upload_files' ) ? '' : ''; } }