array( 'label' => __( 'Grid', 'equaframe' ), 'desc' => __( 'Uniform tiles of the same shape, in even rows and columns.', 'equaframe' ), ), 'masonry' => array( 'label' => __( 'Masonry', 'equaframe' ), 'desc' => __( 'Images keep their own proportions and stack in columns.', 'equaframe' ), ), 'mosaic' => array( 'label' => __( 'Mosaic', 'equaframe' ), 'desc' => __( 'A tiled collage where some images are larger than others.', 'equaframe' ), ), 'slideshow' => array( 'label' => __( 'Slideshow', 'equaframe' ), 'desc' => __( 'One large image at a time, with arrows, dots or a thumbnail film strip.', 'equaframe' ), ), ); } public static function ratios() { return array( '1/1' => __( 'Square (1:1)', 'equaframe' ), '4/3' => __( 'Landscape (4:3)', 'equaframe' ), '3/2' => __( 'Landscape (3:2)', 'equaframe' ), '16/9' => __( 'Widescreen (16:9)', 'equaframe' ), '3/4' => __( 'Portrait (3:4)', 'equaframe' ), '2/3' => __( 'Portrait (2:3, the same as 4:6)', 'equaframe' ), '4/5' => __( 'Portrait (4:5)', 'equaframe' ), ); } public static function image_sizes() { $sizes = array(); foreach ( get_intermediate_image_sizes() as $size ) { $sizes[ $size ] = ucwords( str_replace( array( '-', '_' ), ' ', $size ) ); } $sizes['full'] = __( 'Full size (original upload)', 'equaframe' ); return $sizes; } /** * Every setting: type, default and limits. */ public static function schema() { if ( null !== self::$schema ) { return self::$schema; } $positions = array( 'top-right', 'top-left', 'bottom-right', 'bottom-left' ); self::$schema = array( // Layout. 'layout' => array( 'type' => 'enum', 'default' => 'grid', 'options' => array_keys( self::layouts() ) ), 'cols_desktop' => array( 'type' => 'int', 'default' => 4, 'min' => 1, 'max' => 12 ), 'cols_tablet' => array( 'type' => 'int', 'default' => 3, 'min' => 1, 'max' => 8 ), 'cols_mobile' => array( 'type' => 'int', 'default' => 1, 'min' => 1, 'max' => 4 ), 'grid_ratio' => array( 'type' => 'enum', 'default' => '4/3', 'options' => array_keys( self::ratios() ) ), 'mosaic_row' => array( 'type' => 'int', 'default' => 180, 'min' => 60, 'max' => 500 ), 'ss_mode' => array( 'type' => 'enum', 'default' => 'single', 'options' => array( 'single', 'filmstrip' ) ), 'ss_ratio' => array( 'type' => 'enum', 'default' => '16/9', 'options' => array_keys( self::ratios() ) ), 'ss_fit' => array( 'type' => 'enum', 'default' => 'contain', 'options' => array( 'contain', 'cover' ) ), 'ss_arrows' => array( 'type' => 'bool', 'default' => true ), 'ss_dots' => array( 'type' => 'bool', 'default' => true ), 'ss_loop' => array( 'type' => 'bool', 'default' => true ), 'ss_caption' => array( 'type' => 'enum', 'default' => 'none', 'options' => array( 'none', 'title', 'caption', 'both' ) ), 'ss_strip_pos' => array( 'type' => 'enum', 'default' => 'bottom', 'options' => array( 'top', 'bottom', 'left', 'right' ) ), 'ss_strip_visible' => array( 'type' => 'int', 'default' => 5, 'min' => 1, 'max' => 12 ), 'ss_strip_visible_mobile' => array( 'type' => 'int', 'default' => 3, 'min' => 1, 'max' => 6 ), // Size and spacing. 'max_width' => array( 'type' => 'int', 'default' => 1200, 'min' => 50, 'max' => 4000 ), 'max_width_unit' => array( 'type' => 'enum', 'default' => 'px', 'options' => array( 'px', '%' ) ), 'align' => array( 'type' => 'enum', 'default' => 'center', 'options' => array( 'left', 'center', 'right' ) ), 'gap' => array( 'type' => 'int', 'default' => 12, 'min' => 0, 'max' => 80 ), 'gap_min' => array( 'type' => 'int', 'default' => 4, 'min' => 0, 'max' => 40 ), 'outer_pad' => array( 'type' => 'bool', 'default' => false ), 'bg_color' => array( 'type' => 'color', 'default' => '', 'allow_empty' => true ), 'image_size' => array( 'type' => 'enum', 'default' => 'large', 'options' => 'image_sizes' ), 'lb_image_size' => array( 'type' => 'enum', 'default' => 'large', 'options' => 'image_sizes' ), // Appearance. 'radius_on' => array( 'type' => 'bool', 'default' => false ), 'radius' => array( 'type' => 'int', 'default' => 8, 'min' => 0, 'max' => 200 ), 'radius_unit' => array( 'type' => 'enum', 'default' => 'px', 'options' => array( 'px', '%' ) ), 'border_on' => array( 'type' => 'bool', 'default' => false ), 'border_width' => array( 'type' => 'int', 'default' => 2, 'min' => 1, 'max' => 20 ), 'border_style' => array( 'type' => 'enum', 'default' => 'solid', 'options' => array( 'solid', 'dashed', 'dotted', 'double', 'groove', 'ridge', 'inset', 'outset' ) ), 'border_color' => array( 'type' => 'color', 'default' => '#333333' ), 'shadow_on' => array( 'type' => 'bool', 'default' => false ), 'shadow_x' => array( 'type' => 'int', 'default' => 0, 'min' => -60, 'max' => 60 ), 'shadow_y' => array( 'type' => 'int', 'default' => 6, 'min' => -60, 'max' => 60 ), 'shadow_blur' => array( 'type' => 'int', 'default' => 16, 'min' => 0, 'max' => 100 ), 'shadow_spread' => array( 'type' => 'int', 'default' => 0, 'min' => -30, 'max' => 50 ), 'shadow_color' => array( 'type' => 'color', 'default' => '#000000' ), 'shadow_opacity' => array( 'type' => 'int', 'default' => 25, 'min' => 0, 'max' => 100 ), 'zoom_on' => array( 'type' => 'bool', 'default' => false ), 'zoom_amount' => array( 'type' => 'int', 'default' => 6, 'min' => 1, 'max' => 25 ), 'ctl_color' => array( 'type' => 'color', 'default' => '#ffffff' ), 'ctl_bg' => array( 'type' => 'color', 'default' => '#111111' ), 'cap_color' => array( 'type' => 'color', 'default' => '#ffffff' ), 'cap_bg' => array( 'type' => 'color', 'default' => '#000000' ), 'cap_bg_opacity' => array( 'type' => 'int', 'default' => 75, 'min' => 0, 'max' => 100 ), // Lightbox. 'lb_on' => array( 'type' => 'bool', 'default' => true ), 'lb_backdrop' => array( 'type' => 'bool', 'default' => true ), 'lb_backdrop_color' => array( 'type' => 'color', 'default' => '#000000' ), 'lb_backdrop_opacity' => array( 'type' => 'int', 'default' => 88, 'min' => 0, 'max' => 100 ), 'lb_close_backdrop' => array( 'type' => 'bool', 'default' => true ), 'lb_close_esc' => array( 'type' => 'bool', 'default' => true ), 'lb_close_icon' => array( 'type' => 'enum', 'default' => 'xmark', 'options' => array_merge( array( 'text' ), EquaFrame_Icons::slugs() ) ), 'lb_close_style' => array( 'type' => 'enum', 'default' => 'circle', 'options' => array( 'circle', 'square', 'plain' ) ), 'lb_close_color' => array( 'type' => 'color', 'default' => '', 'allow_empty' => true ), 'lb_close_bg' => array( 'type' => 'color', 'default' => '', 'allow_empty' => true ), 'lb_close_border' => array( 'type' => 'color', 'default' => '', 'allow_empty' => true ), 'lb_close_pos' => array( 'type' => 'enum', 'default' => 'top-right', 'options' => $positions ), 'lb_show_title' => array( 'type' => 'bool', 'default' => true ), 'lb_show_caption' => array( 'type' => 'bool', 'default' => false ), 'lb_caption_mode' => array( 'type' => 'enum', 'default' => 'panel', 'options' => array( 'panel', 'overlay' ) ), 'lb_announce' => array( 'type' => 'enum', 'default' => 'alt', 'options' => array( 'alt', 'title', 'caption' ) ), ); return self::$schema; } public static function defaults() { $out = array(); foreach ( self::schema() as $key => $def ) { $out[ $key ] = $def['default']; } return $out; } /** * Merge stored settings over the defaults. */ public static function with_defaults( $stored ) { $defaults = self::defaults(); // Drop keys the plugin no longer knows about (for example CSS typed in earlier versions). $settings = wp_parse_args( is_array( $stored ) ? array_intersect_key( $stored, $defaults ) : array(), $defaults ); // Earlier versions stored "x" and "x-bold"; both are now the cross icon. if ( 'text' !== $settings['lb_close_icon'] ) { $settings['lb_close_icon'] = EquaFrame_Icons::resolve( $settings['lb_close_icon'] ); } return $settings; } /** * Sanitize a full settings submission (already unslashed). * Unchecked checkboxes are absent from a submission, so a bool key that is * missing is treated as "off". Every other missing key keeps its current value. */ public static function sanitize( $input, $current = null ) { $input = is_array( $input ) ? $input : array(); $out = self::with_defaults( $current ); foreach ( self::schema() as $key => $def ) { $has = array_key_exists( $key, $input ); switch ( $def['type'] ) { case 'bool': $out[ $key ] = $has && ! empty( $input[ $key ] ) && '0' !== $input[ $key ]; break; case 'int': if ( $has ) { $out[ $key ] = max( $def['min'], min( $def['max'], (int) $input[ $key ] ) ); } break; case 'enum': if ( $has ) { $options = is_array( $def['options'] ) ? $def['options'] : array_keys( self::image_sizes() ); $value = is_scalar( $input[ $key ] ) ? (string) $input[ $key ] : ''; if ( in_array( $value, $options, true ) ) { $out[ $key ] = $value; } } break; case 'color': if ( $has ) { $color = sanitize_hex_color( is_scalar( $input[ $key ] ) ? trim( (string) $input[ $key ] ) : '' ); if ( $color ) { $out[ $key ] = $color; } elseif ( ! empty( $def['allow_empty'] ) ) { $out[ $key ] = ''; } } break; } } // Percent radius only makes sense up to 50%. if ( '%' === $out['radius_unit'] ) { $out['radius'] = min( 50, $out['radius'] ); } // The minimum gap can never exceed the desktop gap. $out['gap_min'] = min( $out['gap_min'], $out['gap'] ); return $out; } /** * Every CSS class the gallery uses, with where it appears. Listed on the Styles & CSS tab. */ public static function style_elements() { $g_all = __( 'All layouts', 'equaframe' ); $g_tiles = __( 'Grid, masonry and mosaic', 'equaframe' ); $g_slide = __( 'Slideshow', 'equaframe' ); $g_light = __( 'Lightbox (larger view)', 'equaframe' ); return array( 'root' => array( 'group' => $g_all, 'class' => '.eqfr-gallery', 'desc' => __( 'The outer wrapper around the whole gallery. Controls maximum width, alignment and the background color that shows between images.', 'equaframe' ) ), 'img' => array( 'group' => $g_all, 'class' => '.eqfr-img', 'desc' => __( 'Every gallery image except the film strip thumbnails. Use for filters, object-fit or object-position.', 'equaframe' ) ), 'list' => array( 'group' => $g_tiles, 'class' => '.eqfr-list', 'desc' => __( 'The list that holds all of the images in the grid, masonry and mosaic layouts. Use it to adjust columns or spacing.', 'equaframe' ) ), 'item' => array( 'group' => $g_tiles, 'class' => '.eqfr-item', 'desc' => __( 'One tile. Border, rounded corners, shadow and the hover zoom clipping live here. Each tile can also have its own ID (set on the Images tab).', 'equaframe' ) ), 'link' => array( 'group' => $g_tiles, 'class' => '.eqfr-link', 'desc' => __( 'The link around an image when the lightbox is on. It receives keyboard focus and opens the larger view.', 'equaframe' ) ), 'ss_stage' => array( 'group' => $g_slide, 'class' => '.eqfr-ss-stage', 'desc' => __( 'The large image area of the slideshow, including its border, rounded corners and shadow.', 'equaframe' ) ), 'slide' => array( 'group' => $g_slide, 'class' => '.eqfr-slide', 'desc' => __( 'A single slide inside the large image area.', 'equaframe' ) ), 'ss_caption' => array( 'group' => $g_slide, 'class' => '.eqfr-ss-caption', 'desc' => __( 'The title/caption bar laid over the bottom of a slide.', 'equaframe' ) ), 'ss_arrow' => array( 'group' => $g_slide, 'class' => '.eqfr-ss-arrow', 'desc' => __( 'Both the previous and next arrow buttons on a slideshow.', 'equaframe' ) ), 'ss_dots' => array( 'group' => $g_slide, 'class' => '.eqfr-ss-dots', 'desc' => __( 'The row of dot buttons below the slideshow image.', 'equaframe' ) ), 'ss_dot' => array( 'group' => $g_slide, 'class' => '.eqfr-ss-dot', 'desc' => __( 'One dot button. The visible dot is drawn by its ::before element; keep the button at least 24px square.', 'equaframe' ) ), 'ss_thumbs' => array( 'group' => $g_slide, 'class' => '.eqfr-ss-thumbs', 'desc' => __( 'The scrollable film strip of thumbnails.', 'equaframe' ) ), 'ss_thumb' => array( 'group' => $g_slide, 'class' => '.eqfr-ss-thumb', 'desc' => __( 'One film strip thumbnail button.', 'equaframe' ) ), 'ss_thumb_img' => array( 'group' => $g_slide, 'class' => '.eqfr-ss-thumb-img', 'desc' => __( 'The picture inside a film strip thumbnail.', 'equaframe' ) ), 'lightbox' => array( 'group' => $g_light, 'class' => '.eqfr-lightbox', 'desc' => __( 'The full-screen dialog (backdrop included) that holds the larger view.', 'equaframe' ) ), 'lb_figure' => array( 'group' => $g_light, 'class' => '.eqfr-lb-figure', 'desc' => __( 'The box that wraps the large image and its caption.', 'equaframe' ) ), 'lb_img' => array( 'group' => $g_light, 'class' => '.eqfr-lb-img', 'desc' => __( 'The large image shown in the lightbox.', 'equaframe' ) ), 'lb_caption' => array( 'group' => $g_light, 'class' => '.eqfr-lb-caption', 'desc' => __( 'The panel or overlay that holds the title and caption.', 'equaframe' ) ), 'lb_title' => array( 'group' => $g_light, 'class' => '.eqfr-lb-title', 'desc' => __( 'The image title inside the caption area.', 'equaframe' ) ), 'lb_text' => array( 'group' => $g_light, 'class' => '.eqfr-lb-text', 'desc' => __( 'The caption text inside the caption area.', 'equaframe' ) ), 'lb_counter' => array( 'group' => $g_light, 'class' => '.eqfr-lb-counter', 'desc' => __( 'The "3 / 10" counter at the top of the lightbox.', 'equaframe' ) ), 'lb_close' => array( 'group' => $g_light, 'class' => '.eqfr-lb-close', 'desc' => __( 'The close button.', 'equaframe' ) ), 'lb_nav' => array( 'group' => $g_light, 'class' => '.eqfr-lb-nav', 'desc' => __( 'Both the previous and next buttons in the lightbox.', 'equaframe' ) ), ); } }