├── .eslintignore ├── .eslintrc ├── .gitignore ├── .gitmodules ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── README.md ├── css └── customize-image-gallery-control.css ├── customize-image-gallery-control.php ├── js └── customize-image-gallery-control.js ├── package.json ├── php ├── class-control.php └── class-plugin.php ├── phpcs.ruleset.xml ├── tests ├── php │ ├── test-class-control.php │ └── test-class-plugin.php └── test-customize-image-gallery-control.php └── wp-assets └── screenshot-1.png /.eslintignore: -------------------------------------------------------------------------------- 1 | dev-lib/.eslintignore -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | dev-lib/.eslintrc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dev-lib"] 2 | path = dev-lib 3 | url = https://github.com/xwp/wp-dev-lib.git 4 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | dev-lib/.jscsrc -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/node_modules/** 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | dev-lib/.jshintrc -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: 4 | - php 5 | - node_js 6 | 7 | php: 8 | - 5.4 9 | - 7.0 10 | 11 | env: 12 | - WP_VERSION=latest WP_MULTISITE=0 13 | - WP_VERSION=latest WP_MULTISITE=1 14 | - WP_VERSION=trunk WP_MULTISITE=0 15 | - WP_VERSION=trunk WP_MULTISITE=1 16 | 17 | install: 18 | - export DEV_LIB_PATH=dev-lib 19 | - if [ ! -e "$DEV_LIB_PATH" ] && [ -L .travis.yml ]; then export DEV_LIB_PATH=$( dirname $( readlink .travis.yml ) ); fi 20 | - if [ ! -e "$DEV_LIB_PATH" ]; then git clone https://github.com/xwp/wp-dev-lib.git $DEV_LIB_PATH; fi 21 | - source $DEV_LIB_PATH/travis.install.sh 22 | 23 | script: 24 | - source $DEV_LIB_PATH/travis.script.sh 25 | 26 | after_script: 27 | - source $DEV_LIB_PATH/travis.after_script.sh 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Customize Image Gallery Control 2 | 3 | Adds Customizer support for image gallery control type. 4 | 5 | ## Description ## 6 | Adds new Customizer control type `image_gallery` by extending WP_Customize_Control class. Allows choosing multiple images from Media Library, saves the ID-s as an array. 7 | Displays icons of the images in Customizer. 8 | 9 | Requires PHP 5.3+ 10 | 11 | ## Screenshots ## 12 | 13 | ![Image Gallery field in Customizer](wp-assets/screenshot-1.png) 14 | -------------------------------------------------------------------------------- /css/customize-image-gallery-control.css: -------------------------------------------------------------------------------- 1 | .image-gallery-attachments { 2 | background: #fff; 3 | float: left; 4 | clear: both; 5 | } 6 | 7 | .image-gallery-thumbnail-wrapper { 8 | float: left; 9 | width: 19%; 10 | height: 45px; 11 | padding: 0.5%; 12 | overflow: hidden; 13 | } 14 | 15 | .image-gallery-thumbnail-wrapper img { 16 | height: auto; 17 | width: 100%; 18 | float: left; 19 | } 20 | -------------------------------------------------------------------------------- /customize-image-gallery-control.php: -------------------------------------------------------------------------------- 1 | =' ) ) { 16 | require_once __DIR__ . '/php/class-plugin.php'; 17 | $class = 'CustomizeImageGalleryControl\\Plugin'; 18 | $customize_image_gallery_control_plugin = new $class(); 19 | add_action( 'plugins_loaded', array( $customize_image_gallery_control_plugin, 'init' ) ); 20 | } else { 21 | if ( defined( 'WP_CLI' ) ) { 22 | WP_CLI::warning( _customize_image_gallery_control_php_version_text() ); 23 | } else { 24 | add_action( 'admin_notices', '_customize_image_gallery_control_php_version_error' ); 25 | } 26 | } 27 | 28 | /** 29 | * Admin notice for incompatible versions of PHP. 30 | */ 31 | function _customize_image_gallery_control_php_version_error() { 32 | printf( '

%s

', esc_html( _customize_image_gallery_control_php_version_text() ) ); 33 | } 34 | 35 | /** 36 | * String describing the minimum PHP version. 37 | * 38 | * @return string 39 | */ 40 | function _customize_image_gallery_control_php_version_text() { 41 | return __( 'Customize Image Gallery Control plugin error: Your version of PHP is too old to run this plugin. You must be running PHP 5.3 or higher.', 'customize-image-gallery-control' ); 42 | } 43 | -------------------------------------------------------------------------------- /js/customize-image-gallery-control.js: -------------------------------------------------------------------------------- 1 | /* global wp, JSON */ 2 | /* eslint consistent-this: [ "error", "control" ] */ 3 | /* eslint complexity: ["error", 8] */ 4 | 5 | (function( api, $ ) { 6 | 'use strict'; 7 | 8 | /** 9 | * An image gallery control. 10 | * 11 | * @class 12 | * @augments wp.customize.Control 13 | * @augments wp.customize.Class 14 | */ 15 | api.ImageGalleryControl = api.Control.extend({ 16 | initialize: function( id, options ) { 17 | var control = this, args; 18 | 19 | args = options || {}; 20 | 21 | if ( ! args.params.file_type ) { 22 | args.params.file_type = 'image'; 23 | } 24 | 25 | if ( ! args.params.type ) { 26 | args.params.type = 'image_gallery'; 27 | } 28 | if ( ! args.params.content ) { 29 | args.params.content = $( '
  • ' ); 30 | args.params.content.attr( 'id', 'customize-control-' + id.replace( /]/g, '' ).replace( /\[/g, '-' ) ); 31 | args.params.content.attr( 'class', 'customize-control customize-control-' + args.params.type ); 32 | } 33 | 34 | if ( ! args.params.attachments ) { 35 | args.params.attachments = []; 36 | } 37 | 38 | api.Control.prototype.initialize.call( control, id, args ); 39 | }, 40 | 41 | /** 42 | * When the control's DOM structure is ready, 43 | * set up internal event bindings. 44 | */ 45 | ready: function() { 46 | var control = this; 47 | // Shortcut so that we don't have to use _.bind every time we add a callback. 48 | _.bindAll( control, 'openFrame', 'select' ); 49 | 50 | /** 51 | * Set gallery data and render content. 52 | */ 53 | function setGalleryDataAndRenderContent() { 54 | 55 | var value = control.setting.get(); 56 | control.setAttachmentsData( value ).done( function() { 57 | control.renderContent(); 58 | control.setupSortable(); 59 | } ); 60 | } 61 | 62 | // Ensure attachment data is initially set. 63 | setGalleryDataAndRenderContent(); 64 | 65 | // Update the attachment data and re-render the control when the setting changes. 66 | control.setting.bind( setGalleryDataAndRenderContent ); 67 | 68 | // Bind events. 69 | control.container.on( 'click keydown', '.upload-button', control.openFrame ); 70 | }, 71 | 72 | /** 73 | * Fetch attachment data for rendering in control content. 74 | * 75 | * @param {Array} value Setting value, array of attachment ids. 76 | * @returns {*} 77 | */ 78 | setAttachmentsData: function( value ) { 79 | var control = this, 80 | promises = []; 81 | 82 | control.params.attachments = []; 83 | 84 | _.each( value, function( id, index ) { 85 | var hasAttachmentData = new $.Deferred(); 86 | wp.media.attachment( id ).fetch().done( function() { 87 | control.params.attachments[ index ] = this.attributes; 88 | hasAttachmentData.resolve(); 89 | } ); 90 | promises.push( hasAttachmentData ); 91 | } ); 92 | 93 | return $.when.apply( undefined, promises ).promise(); 94 | }, 95 | 96 | /** 97 | * Open the media modal. 98 | */ 99 | openFrame: function( event ) { 100 | event.preventDefault(); 101 | 102 | if ( ! this.frame ) { 103 | this.initFrame(); 104 | } 105 | 106 | this.frame.open(); 107 | }, 108 | 109 | /** 110 | * Initiate the media modal select frame. 111 | * Save it for using later in case needed. 112 | */ 113 | initFrame: function() { 114 | 115 | var control = this, preSelectImages; 116 | this.frame = wp.media({ 117 | 118 | button: { 119 | text: control.params.button_labels.frame_button 120 | }, 121 | states: [ 122 | new wp.media.controller.Library({ 123 | title: control.params.button_labels.frame_title, 124 | library: wp.media.query({ type: control.params.file_type }), 125 | multiple: 'add' 126 | }) 127 | ] 128 | }); 129 | 130 | /** 131 | * Pre-select images according to saved settings. 132 | */ 133 | preSelectImages = function() { 134 | var selection, ids, attachment, library; 135 | 136 | library = control.frame.state().get( 'library' ); 137 | selection = control.frame.state().get( 'selection' ); 138 | 139 | ids = control.setting.get(); 140 | 141 | // Sort the selected images to top when opening media modal. 142 | library.comparator = function( a, b ) { 143 | var hasA = true === this.mirroring.get( a.cid ), 144 | hasB = true === this.mirroring.get( b.cid ); 145 | 146 | if ( ! hasA && hasB ) { 147 | return -1; 148 | } else if ( hasA && ! hasB ) { 149 | return 1; 150 | } else { 151 | return 0; 152 | } 153 | }; 154 | 155 | _.each( ids, function( id ) { 156 | attachment = wp.media.attachment( id ); 157 | selection.add( attachment ? [ attachment ] : [] ); 158 | library.add( attachment ? [ attachment ] : [] ); 159 | }); 160 | }; 161 | control.frame.on( 'open', preSelectImages ); 162 | control.frame.on( 'select', control.select ); 163 | }, 164 | 165 | /** 166 | * Callback for selecting attachments. 167 | */ 168 | select: function() { 169 | 170 | var control = this, attachments, attachmentIds; 171 | 172 | attachments = control.frame.state().get( 'selection' ).toJSON(); 173 | control.params.attachments = attachments; 174 | 175 | attachmentIds = control.getAttachmentIds( attachments ); 176 | control.setSettingValues( attachmentIds ); 177 | }, 178 | 179 | /** 180 | * Get array of attachment id-s from attachment objects. 181 | * 182 | * @param {Array} attachments Attachments. 183 | * @returns {Array} 184 | */ 185 | getAttachmentIds: function( attachments ) { 186 | var ids = [], i; 187 | for ( i in attachments ) { 188 | ids.push( attachments[ i ].id ); 189 | } 190 | return ids; 191 | }, 192 | 193 | /** 194 | * Set setting values. 195 | * 196 | * @param {Array} values Array of ids. 197 | */ 198 | setSettingValues: function( values ) { 199 | var control = this; 200 | control.setting.set( values ); 201 | }, 202 | 203 | /** 204 | * Setup sortable. 205 | * 206 | * @returns {void} 207 | */ 208 | setupSortable: function() { 209 | var control = this, 210 | list = $( '.image-gallery-attachments' ); 211 | list.sortable({ 212 | items: '.image-gallery-thumbnail-wrapper', 213 | tolerance: 'pointer', 214 | stop: function() { 215 | var selectedValues = []; 216 | list.find( '.image-gallery-thumbnail-wrapper' ).each( function() { 217 | var id; 218 | id = parseInt( $( this ).data( 'postId' ), 10 ); 219 | selectedValues.push( id ); 220 | } ); 221 | control.setSettingValues( selectedValues ); 222 | } 223 | }); 224 | } 225 | 226 | }); 227 | 228 | api.controlConstructor['image_gallery'] = api.ImageGalleryControl; 229 | 230 | })( wp.customize, jQuery ); 231 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customize-image-gallery-control", 3 | "version": "0.1.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/xwp/wp-customize-image-gallery-control.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/xwp/wp-customize-image-gallery-control/issues" 14 | }, 15 | "homepage": "https://github.com/xwp/wp-customize-image-gallery-control#readme", 16 | "devDependencies": { 17 | "eslint": "^2.13.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /php/class-control.php: -------------------------------------------------------------------------------- 1 | button_labels = wp_parse_args( $this->button_labels, array( 50 | 'select' => __( 'Select Images', 'customize-image-gallery-control' ), 51 | 'change' => __( 'Modify Gallery', 'customize-image-gallery-control' ), 52 | 'default' => __( 'Default', 'customize-image-gallery-control' ), 53 | 'remove' => __( 'Remove', 'customize-image-gallery-control' ), 54 | 'placeholder' => __( 'No images selected', 'customize-image-gallery-control' ), 55 | 'frame_title' => __( 'Select Gallery Images', 'customize-image-gallery-control' ), 56 | 'frame_button' => __( 'Choose Images', 'customize-image-gallery-control' ), 57 | ) ); 58 | } 59 | 60 | /** 61 | * An Underscore (JS) template for this control's content (but not its container). 62 | * 63 | * Class variables for this control class are available in the `data` JS object; 64 | * export custom variables by overriding {@see WP_Customize_Control::to_json()}. 65 | * 66 | * @see WP_Customize_Control::print_template() 67 | */ 68 | protected function content_template() { 69 | $data = $this->json(); 70 | ?> 71 | <# 72 | 73 | _.defaults( data, ); 74 | data.input_id = 'input-' + String( Math.random() ); 75 | #> 76 | 77 | <# if ( data.attachments ) { #> 78 | 85 | <# } #> 86 |
    87 | 88 |
    89 |
    90 | 91 | json['label'] = html_entity_decode( $this->label, ENT_QUOTES, get_bloginfo( 'charset' ) ); 109 | $this->json['file_type'] = $this->file_type; 110 | $this->json['button_labels'] = $this->button_labels; 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /php/class-plugin.php: -------------------------------------------------------------------------------- 1 | version = $matches[1]; 32 | } 33 | } 34 | 35 | /** 36 | * Initialize. 37 | */ 38 | function init() { 39 | 40 | add_action( 'wp_default_styles', array( $this, 'register_styles' ), 100 ); 41 | add_action( 'wp_default_scripts', array( $this, 'register_scripts' ), 100 ); 42 | add_action( 'customize_register', array( $this, 'customize_register' ), 9 ); 43 | add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts' ) ); 44 | } 45 | 46 | /** 47 | * Load theme and plugin compatibility classes. 48 | * 49 | * @param \WP_Customize_Manager $wp_customize Manager. 50 | */ 51 | function customize_register( \WP_Customize_Manager $wp_customize ) { 52 | require_once __DIR__ . '/class-control.php'; 53 | $wp_customize->register_control_type( __NAMESPACE__ . '\\Control' ); 54 | } 55 | 56 | /** 57 | * Register styles. 58 | * 59 | * @param \WP_Styles $wp_styles Styles. 60 | */ 61 | public function register_styles( \WP_Styles $wp_styles ) { 62 | 63 | $handle = 'customize-image-gallery-control'; 64 | $src = plugins_url( 'css/customize-image-gallery-control.css', dirname( __FILE__ ) ); 65 | $deps = array( 'customize-controls' ); 66 | $wp_styles->add( $handle, $src, $deps, $this->version ); 67 | } 68 | 69 | /** 70 | * Register scripts. 71 | * 72 | * @param \WP_Scripts $wp_scripts Scripts. 73 | */ 74 | public function register_scripts( \WP_Scripts $wp_scripts ) { 75 | $handle = 'customize-image-gallery-control'; 76 | $src = plugins_url( 'js/customize-image-gallery-control.js', dirname( __FILE__ ) ); 77 | $deps = array( 'jquery', 'customize-controls' ); 78 | $in_footer = 1; 79 | $wp_scripts->add( $handle, $src, $deps, $this->version, $in_footer ); 80 | } 81 | 82 | /** 83 | * Enqueue controls scripts. 84 | */ 85 | public function customize_controls_enqueue_scripts() { 86 | wp_enqueue_script( 'customize-image-gallery-control' ); 87 | wp_enqueue_style( 'customize-image-gallery-control' ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /phpcs.ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 0 13 | 14 | 15 | */dev-lib/* 16 | */node_modules/* 17 | 18 | -------------------------------------------------------------------------------- /tests/php/test-class-control.php: -------------------------------------------------------------------------------- 1 | wp_customizer = new \WP_Customize_Manager(); 38 | $this->plugin = new Plugin(); 39 | $this->plugin->customize_register( $this->wp_customizer ); 40 | } 41 | /** 42 | * Test constructor 43 | */ 44 | public function test_construct() { 45 | $control = new Control( $this->wp_customizer, 'no-setting', array() ); 46 | $this->assertTrue( 'image_gallery' === $control->type ); 47 | $this->assertTrue( 'image' === $control->file_type ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/php/test-class-plugin.php: -------------------------------------------------------------------------------- 1 | init(); 25 | 26 | $this->assertEquals( 100, has_action( 'wp_default_styles', array( $plugin, 'register_styles' ) ) ); 27 | $this->assertEquals( 100, has_action( 'wp_default_scripts', array( $plugin, 'register_scripts' ) ) ); 28 | $this->assertEquals( 9, has_action( 'customize_register', array( $plugin, 'customize_register' ) ) ); 29 | $this->assertEquals( 10, has_action( 'customize_controls_enqueue_scripts', array( $plugin, 'customize_controls_enqueue_scripts' ) ) ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/test-customize-image-gallery-control.php: -------------------------------------------------------------------------------- 1 | assertContains( '
    ', $buffer ); 27 | } 28 | 29 | /** 30 | * Test PHP version error text. 31 | * 32 | * @see customize_snapshots_php_version_text() 33 | */ 34 | function test_customize_image_gallery_control_php_version_text() { 35 | $this->assertContains( 'Customize Image Gallery Control plugin error:', _customize_image_gallery_control_php_version_text() ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /wp-assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-customize-image-gallery-control/edcbdfbb1abfba110868aa34d0cbf4ad4bef77fd/wp-assets/screenshot-1.png --------------------------------------------------------------------------------