(function($, undefined){ /** * acf.newMediaPopup * * description * * @date 10/1/18 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ acf.newMediaPopup = function( args ){ // args var popup = null; var args = acf.parseArgs(args, { mode: 'select', // 'select', 'edit' title: '', // 'Upload Image' button: '', // 'Select Image' type: '', // 'image', '' field: false, // field instance allowedTypes: '', // '.jpg, .png, etc' library: 'all', // 'all', 'uploadedTo' multiple: false, // false, true, 'add' attachment: 0, // the attachment to edit autoOpen: true, // open the popup automatically open: function(){}, // callback after close select: function(){}, // callback after select close: function(){} // callback after close }); // initialize if( args.mode == 'edit' ) { popup = new acf.models.EditMediaPopup( args ); } else { popup = new acf.models.SelectMediaPopup( args ); } // open popup (allow frame customization before opening) if( args.autoOpen ) { setTimeout(function(){ popup.open(); }, 1); } // action acf.doAction('new_media_popup', popup); // return return popup; }; /** * getPostID * * description * * @date 10/1/18 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ var getPostID = function() { var postID = acf.get('post_id'); return $.isNumeric(postID) ? postID : 0; } /** * acf.getMimeTypes * * description * * @date 11/1/18 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ acf.getMimeTypes = function(){ return this.get('mimeTypes'); }; acf.getMimeType = function( name ){ // vars var allTypes = acf.getMimeTypes(); // search if( allTypes[name] !== undefined ) { return allTypes[name]; } // some types contain a mixed key such as "jpg|jpeg|jpe" for( var key in allTypes ) { if( key.indexOf(name) !== -1 ) { return allTypes[key]; } } // return return false; }; /** * MediaPopup * * description * * @date 10/1/18 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ var MediaPopup = acf.Model.extend({ id: 'MediaPopup', data: {}, defaults: {}, frame: false, setup: function( props ){ $.extend(this.data, props); }, initialize: function(){ // vars var options = this.getFrameOptions(); // add states this.addFrameStates( options ); // create frame var frame = wp.media( options ); // add args reference frame.acf = this; // add events this.addFrameEvents( frame, options ); // strore frame this.frame = frame; }, open: function(){ this.frame.open(); }, close: function(){ this.frame.close(); }, remove: function(){ this.frame.detach(); this.frame.remove(); }, getFrameOptions: function(){ // vars var options = { title: this.get('title'), multiple: this.get('multiple'), library: {}, states: [] }; // type if( this.get('type') ) { options.library.type = this.get('type'); } // type if( this.get('library') === 'uploadedTo' ) { options.library.uploadedTo = getPostID(); } // attachment if( this.get('attachment') ) { options.library.post__in = [ this.get('attachment') ]; } // button if( this.get('button') ) { options.button = { text: this.get('button') }; } // return return options; }, addFrameStates: function( options ){ // create query var Query = wp.media.query( options.library ); // add _acfuploader // this is super wack! // if you add _acfuploader to the options.library args, new uploads will not be added to the library view. // this has been traced back to the wp.media.model.Query initialize function (which can't be overriden) // Adding any custom args will cause the Attahcments to not observe the uploader queue // To bypass this security issue, we add in the args AFTER the Query has been initialized // options.library._acfuploader = settings.field; if( this.get('field') && acf.isset(Query, 'mirroring', 'args') ) { Query.mirroring.args._acfuploader = this.get('field'); } // add states options.states.push( // main state new wp.media.controller.Library({ library: Query, multiple: this.get('multiple'), title: this.get('title'), priority: 20, filterable: 'all', editable: true, allowLocalEdits: true }) ); // edit image functionality (added in WP 3.9) if( acf.isset(wp, 'media', 'controller', 'EditImage') ) { options.states.push( new wp.media.controller.EditImage() ); } }, addFrameEvents: function( frame, options ){ // log all events //frame.on('all', function( e ) { // console.log( 'frame all: %o', e ); //}); // add class frame.on('open',function() { this.$el.closest('.media-modal').addClass('acf-media-modal -' + this.acf.get('mode') ); }, frame); // edit image view // source: media-views.js:2410 editImageContent() frame.on('content:render:edit-image', function(){ var image = this.state().get('image'); var view = new wp.media.view.EditImage({ model: image, controller: this }).render(); this.content.set( view ); // after creating the wrapper view, load the actual editor via an ajax call view.loadEditor(); }, frame); // update toolbar button //frame.on( 'toolbar:create:select', function( toolbar ) { // toolbar.view = new wp.media.view.Toolbar.Select({ // text: frame.options._button, // controller: this // }); //}, frame ); // on select frame.on('select', function() { // vars var selection = frame.state().get('selection'); // if selecting images if( selection ) { // loop selection.each(function( attachment, i ){ frame.acf.get('select').apply( frame.acf, [attachment, i] ); }); } }); // on close frame.on('close',function(){ // callback and remove setTimeout(function(){ frame.acf.get('close').apply( frame.acf ); frame.acf.remove(); }, 1); }); } }); /** * acf.models.SelectMediaPopup * * description * * @date 10/1/18 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ acf.models.SelectMediaPopup = MediaPopup.extend({ id: 'SelectMediaPopup', setup: function( props ){ // default button if( !props.button ) { props.button = acf._x('Select', 'verb'); } // parent MediaPopup.prototype.setup.apply(this, arguments); }, addFrameEvents: function( frame, options ){ // plupload // adds _acfuploader param to validate uploads if( acf.isset(_wpPluploadSettings, 'defaults', 'multipart_params') ) { // add _acfuploader so that Uploader will inherit _wpPluploadSettings.defaults.multipart_params._acfuploader = this.get('field'); // remove acf_field so future Uploaders won't inherit frame.on('open', function(){ delete _wpPluploadSettings.defaults.multipart_params._acfuploader; }); } // browse frame.on('content:activate:browse', function(){ // vars var toolbar = false; // populate above vars making sure to allow for failure // perhaps toolbar does not exist because the frame open is Upload Files try { toolbar = frame.content.get().toolbar; } catch(e) { console.log(e); return; } // callback frame.acf.customizeFilters.apply(frame.acf, [toolbar]); }); // parent MediaPopup.prototype.addFrameEvents.apply(this, arguments); }, customizeFilters: function( toolbar ){ // vars var filters = toolbar.get('filters'); // image if( this.get('type') == 'image' ) { // update all filters.filters.all.text = acf.__('All images'); // remove some filters delete filters.filters.audio; delete filters.filters.video; delete filters.filters.image; // update all filters to show images $.each(filters.filters, function( i, filter ){ filter.props.type = filter.props.type || 'image'; }); } // specific types if( this.get('allowedTypes') ) { // convert ".jpg, .png" into ["jpg", "png"] var allowedTypes = this.get('allowedTypes').split(' ').join('').split('.').join('').split(','); // loop allowedTypes.map(function( name ){ // get type var mimeType = acf.getMimeType( name ); // bail early if no type if( !mimeType ) return; // create new filter var newFilter = { text: mimeType, props: { status: null, type: mimeType, uploadedTo: null, orderby: 'date', order: 'DESC' }, priority: 20 }; // append filters.filters[ mimeType ] = newFilter; }); } // uploaded to post if( this.get('library') === 'uploadedTo' ) { // vars var uploadedTo = this.frame.options.library.uploadedTo; // remove some filters delete filters.filters.unattached; delete filters.filters.uploaded; // add uploadedTo to filters $.each(filters.filters, function( i, filter ){ filter.text += ' (' + acf.__('Uploaded to this post') + ')'; filter.props.uploadedTo = uploadedTo; }); } // add _acfuploader to filters var field = this.get('field'); $.each(filters.filters, function( k, filter ){ filter.props._acfuploader = field; }); // add _acfuplaoder to search var search = toolbar.get('search'); search.model.attributes._acfuploader = field; // render (custom function added to prototype) if( filters.renderFilters ) { filters.renderFilters(); } } }); /** * acf.models.EditMediaPopup * * description * * @date 10/1/18 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ acf.models.EditMediaPopup = MediaPopup.extend({ id: 'SelectMediaPopup', setup: function( props ){ // default button if( !props.button ) { props.button = acf._x('Update', 'verb'); } // parent MediaPopup.prototype.setup.apply(this, arguments); }, addFrameEvents: function( frame, options ){ // add class frame.on('open',function() { // add class this.$el.closest('.media-modal').addClass('acf-expanded'); // set to browse if( this.content.mode() != 'browse' ) { this.content.mode('browse'); } // set selection var state = this.state(); var selection = state.get('selection'); var attachment = wp.media.attachment( frame.acf.get('attachment') ); selection.add( attachment ); }, frame); // parent MediaPopup.prototype.addFrameEvents.apply(this, arguments); } }); /** * customizePrototypes * * description * * @date 11/1/18 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ var customizePrototypes = new acf.Model({ id: 'customizePrototypes', wait: 'ready', initialize: function(){ // bail early if no media views if( !acf.isset(window, 'wp', 'media', 'view') ) { return; } // fix bug where CPT without "editor" does not set post.id setting which then prevents uploadedTo from working var postID = getPostID(); if( postID && acf.isset(wp, 'media', 'view', 'settings', 'post') ) { wp.media.view.settings.post.id = postID; } // customize this.customizeAttachmentsButton(); this.customizeAttachmentsRouter(); this.customizeAttachmentFilters(); this.customizeAttachmentCompat(); this.customizeAttachmentLibrary(); }, customizeAttachmentsButton: function(){ // validate if( !acf.isset(wp, 'media', 'view', 'Button') ) { return; } // Extend var Button = wp.media.view.Button; wp.media.view.Button = Button.extend({ // Fix bug where "Select" button appears blank after editing an image. // Do this by simplifying Button initialize function and avoid deleting this.options. initialize: function() { var options = _.defaults( this.options, this.defaults ); this.model = new Backbone.Model( options ); this.listenTo( this.model, 'change', this.render ); } }); }, customizeAttachmentsRouter: function(){ // validate if( !acf.isset(wp, 'media', 'view', 'Router') ) { return; } // vars var Parent = wp.media.view.Router; // extend wp.media.view.Router = Parent.extend({ addExpand: function(){ // vars var $a = $([ '', '' + acf.__('Expand Details') + '', '' + acf.__('Collapse Details') + '', '' ].join('')); // add events $a.on('click', function( e ){ e.preventDefault(); var $div = $(this).closest('.media-modal'); if( $div.hasClass('acf-expanded') ) { $div.removeClass('acf-expanded'); } else { $div.addClass('acf-expanded'); } }); // append this.$el.append( $a ); }, initialize: function(){ // initialize Parent.prototype.initialize.apply( this, arguments ); // add buttons this.addExpand(); // return return this; } }); }, customizeAttachmentFilters: function(){ // validate if( !acf.isset(wp, 'media', 'view', 'AttachmentFilters', 'All') ) { return; } // vars var Parent = wp.media.view.AttachmentFilters.All; // renderFilters // copied from media-views.js:6939 Parent.prototype.renderFilters = function(){ // Build `' ).val( value ).html( filter.text )[0], priority: filter.priority || 50 }; }, this ).sortBy('priority').pluck('el').value() ); }; }, customizeAttachmentCompat: function(){ // validate if( !acf.isset(wp, 'media', 'view', 'AttachmentCompat') ) { return; } // vars var AttachmentCompat = wp.media.view.AttachmentCompat; var timeout = false; // extend wp.media.view.AttachmentCompat = AttachmentCompat.extend({ render: function() { // WP bug // When multiple media frames exist on the same page (WP content, WYSIWYG, image, file ), // WP creates multiple instances of this AttachmentCompat view. // Each instance will attempt to render when a new modal is created. // Use a property to avoid this and only render once per instance. if( this.rendered ) { return this; } // render HTML AttachmentCompat.prototype.render.apply( this, arguments ); // when uploading, render is called twice. // ignore first render by checking for #acf-form-data element if( !this.$('#acf-form-data').length ) { return this; } // clear timeout clearTimeout( timeout ); // setTimeout timeout = setTimeout($.proxy(function(){ this.rendered = true; acf.doAction('append', this.$el); }, this), 50); // return return this; }, save: function( event ) { var data = {}; if ( event ) { event.preventDefault(); } //_.each( this.$el.serializeArray(), function( pair ) { // data[ pair.name ] = pair.value; //}); // Serialize data more thoroughly to allow chckbox inputs to save. data = acf.serializeForAjax(this.$el); this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); } }); }, customizeAttachmentLibrary: function(){ // validate if( !acf.isset(wp, 'media', 'view', 'Attachment', 'Library') ) { return; } // vars var AttachmentLibrary = wp.media.view.Attachment.Library; // extend wp.media.view.Attachment.Library = AttachmentLibrary.extend({ render: function() { // vars var popup = acf.isget(this, 'controller', 'acf'); var attributes = acf.isget(this, 'model', 'attributes'); // check vars exist to avoid errors if( popup && attributes ) { // show errors if( attributes.acf_errors ) { this.$el.addClass('acf-disabled'); } // disable selected var selected = popup.get('selected'); if( selected && selected.indexOf(attributes.id) > -1 ) { this.$el.addClass('acf-selected'); } } // render return AttachmentLibrary.prototype.render.apply( this, arguments ); }, /* * toggleSelection * * This function is called before an attachment is selected * A good place to check for errors and prevent the 'select' function from being fired * * @type function * @date 29/09/2016 * @since 5.4.0 * * @param options (object) * @return n/a */ toggleSelection: function( options ) { // vars // source: wp-includes/js/media-views.js:2880 var collection = this.collection, selection = this.options.selection, model = this.model, single = selection.single(); // vars var frame = this.controller; var errors = acf.isget(this, 'model', 'attributes', 'acf_errors'); var $sidebar = frame.$el.find('.media-frame-content .media-sidebar'); // remove previous error $sidebar.children('.acf-selection-error').remove(); // show attachment details $sidebar.children().removeClass('acf-hidden'); // add message if( frame && errors ) { // vars var filename = acf.isget(this, 'model', 'attributes', 'filename'); // hide attachment details // Gallery field continues to show previously selected attachment... $sidebar.children().addClass('acf-hidden'); // append message $sidebar.prepend([ '