attachment IDs, in order), * same shape as ACF's Gallery field. */ class Gallery_Field { const NONCE_ACTION = 'mapfield_spots_save_gallery'; const NONCE_NAME = 'mapfield_spots_gallery_nonce'; // Cropped square for admin picker + front-end card thumbnails; lightbox uses core's 'large'. const THUMB_SIZE = 'mapfield_spots_gallery_thumb'; public function __construct() { add_action( 'init', array( $this, 'register_image_size' ) ); add_action( 'init', array( $this, 'register_meta' ), 20 ); add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) ); add_action( 'save_post', array( $this, 'save' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); } public function register_image_size(): void { add_image_size( self::THUMB_SIZE, 200, 200, true ); } private function managed_post_types_with_gallery(): array { $types = array(); foreach ( Map_Types::get_all() as $slug => $config ) { if ( ! empty( $config['fields']['gallery'] ) ) { $types[] = CPT::post_type_slug( $slug ); } } return $types; } public function register_meta(): void { foreach ( Map_Types::get_all() as $slug => $config ) { register_post_meta( CPT::post_type_slug( $slug ), '_mapfield_spots_gallery', array( 'type' => 'array', 'single' => true, 'show_in_rest' => array( 'schema' => array( 'type' => 'array', 'items' => array( 'type' => 'integer' ), ), ), 'sanitize_callback' => array( self::class, 'sanitize_ids' ), 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); } } public static function sanitize_ids( $value ): array { return array_values( array_unique( array_filter( array_map( 'absint', (array) $value ) ) ) ); } public function add_meta_box(): void { foreach ( Map_Types::get_all() as $slug => $config ) { if ( empty( $config['fields']['gallery'] ) ) { continue; } // 'normal': admin.css's grid flows this into the same two-column // layout as Description/Contact (Location is the full-row exception). add_meta_box( 'mfs-gallery', __( 'Gallery', 'mapfield-spots' ), array( $this, 'render' ), CPT::post_type_slug( $slug ), 'normal', 'default' ); } } public function render( \WP_Post $post ): void { wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME ); $ids = self::sanitize_ids( get_post_meta( $post->ID, '_mapfield_spots_gallery', true ) ); ?> post_type, $this->managed_post_types_with_gallery(), true ) ) { return; } // wp.media + jquery-ui-sortable are both bundled with core. wp_enqueue_media(); wp_enqueue_style( 'mapfield-spots-admin', MAPFIELD_SPOTS_URL . 'assets/admin.css', array( 'mapfield-spots-palette' ), MAPFIELD_SPOTS_VERSION ); wp_enqueue_script( 'mapfield-spots-gallery-field', MAPFIELD_SPOTS_URL . 'assets/gallery-field.js', array( 'jquery-ui-sortable' ), MAPFIELD_SPOTS_VERSION, true ); wp_localize_script( 'mapfield-spots-gallery-field', 'MapfieldSpotsGallery', array( 'title' => __( 'Select images', 'mapfield-spots' ), 'button' => __( 'Add to gallery', 'mapfield-spots' ), 'remove' => __( 'Remove image', 'mapfield-spots' ), ) ); } }