* @output wp-includes/js/media-editor.js
/* global getUserSetting, tinymce, QTags */
// WordPress, TinyMCE, and Media
// -----------------------------
* Stores the editors' `wp.media.controller.Frame` instances.
* A helper mixin function to avoid truthy and falsey values being
* passed as an input that expects booleans. If key is undefined in the map,
* but has a default value, set it.
* @param {Object} attrs Map of props from a shortcode or settings.
* @param {string} key The key within the passed map to check for a value.
* @return {mixed|undefined} The original or coerced value of key within attrs.
wp.media.coerce = function ( attrs, key ) {
if ( _.isUndefined( attrs[ key ] ) && ! _.isUndefined( this.defaults[ key ] ) ) {
attrs[ key ] = this.defaults[ key ];
} else if ( 'true' === attrs[ key ] ) {
} else if ( 'false' === attrs[ key ] ) {
/** @namespace wp.media.string */
* Joins the `props` and `attachment` objects,
* outputting the proper object format based on the
* @param {Object} [props={}] Attachment details (align, link, size, etc).
* @param {Object} attachment The attachment object, media version of Post.
* @return {Object} Joined props
props: function( props, attachment ) {
var link, linkUrl, size, sizes,
defaultProps = wp.media.view.settings.defaultProps;
props = props ? _.clone( props ) : {};
if ( attachment && attachment.type ) {
props.type = attachment.type;
if ( 'image' === props.type ) {
props = _.defaults( props || {}, {
align: defaultProps.align || getUserSetting( 'align', 'none' ),
size: defaultProps.size || getUserSetting( 'imgsize', 'medium' ),
// All attachment-specific settings follow.
props.title = props.title || attachment.title;
link = props.link || defaultProps.link || getUserSetting( 'urlbutton', 'file' );
if ( 'file' === link || 'embed' === link ) {
linkUrl = attachment.url;
} else if ( 'post' === link ) {
linkUrl = attachment.link;
} else if ( 'custom' === link ) {
props.linkUrl = linkUrl || '';
// Format properties for images.
if ( 'image' === attachment.type ) {
props.classes.push( 'wp-image-' + attachment.id );
sizes = attachment.sizes;
size = sizes && sizes[ props.size ] ? sizes[ props.size ] : attachment;
_.extend( props, _.pick( attachment, 'align', 'caption', 'alt' ), {
captionId: 'attachment_' + attachment.id
} else if ( 'video' === attachment.type || 'audio' === attachment.type ) {
_.extend( props, _.pick( attachment, 'title', 'type', 'icon', 'mime' ) );
// Format properties for non-images.
props.title = props.title || attachment.filename;
props.rel = props.rel || 'attachment wp-att-' + attachment.id;
* Create link markup that is suitable for passing to the editor
* @param {Object} props Attachment details (align, link, size, etc).
* @param {Object} attachment The attachment object, media version of Post.
* @return {string} The link markup
link: function( props, attachment ) {
props = wp.media.string.props( props, attachment );
options.attrs.rel = props.rel;
return wp.html.string( options );
* Create an Audio shortcode string that is suitable for passing to the editor
* @param {Object} props Attachment details (align, link, size, etc).
* @param {Object} attachment The attachment object, media version of Post.
* @return {string} The audio shortcode
audio: function( props, attachment ) {
return wp.media.string._audioVideo( 'audio', props, attachment );
* Create a Video shortcode string that is suitable for passing to the editor
* @param {Object} props Attachment details (align, link, size, etc).
* @param {Object} attachment The attachment object, media version of Post.
* @return {string} The video shortcode
video: function( props, attachment ) {
return wp.media.string._audioVideo( 'video', props, attachment );
* Helper function to create a media shortcode string
* @param {string} type The shortcode tag name: 'audio' or 'video'.
* @param {Object} props Attachment details (align, link, size, etc).
* @param {Object} attachment The attachment object, media version of Post.
* @return {string} The media shortcode
_audioVideo: function( type, props, attachment ) {
var shortcode, html, extension;
props = wp.media.string.props( props, attachment );
if ( props.link !== 'embed' ) {
return wp.media.string.link( props );
if ( 'video' === type ) {
if ( attachment.image && -1 === attachment.image.src.indexOf( attachment.icon ) ) {
shortcode.poster = attachment.image.src;
if ( attachment.width ) {
shortcode.width = attachment.width;
if ( attachment.height ) {
shortcode.height = attachment.height;
extension = attachment.filename.split('.').pop();
if ( _.contains( wp.media.view.settings.embedExts, extension ) ) {
shortcode[extension] = attachment.url;
// Render unsupported audio and video files as links.
return wp.media.string.link( props );
html = wp.shortcode.string({
* Create image markup, optionally with a link and/or wrapped in a caption shortcode,
* that is suitable for passing to the editor
* @param {Object} props Attachment details (align, link, size, etc).
* @param {Object} attachment The attachment object, media version of Post.
image: function( props, attachment ) {
options, classes, shortcode, html;
props = wp.media.string.props( props, attachment );
classes = props.classes || [];
img.src = ! _.isUndefined( attachment ) ? attachment.url : props.url;
_.extend( img, _.pick( props, 'width', 'height', 'alt' ) );
// Only assign the align class to the image if we're not printing
// a caption, since the alignment is sent to the shortcode.
if ( props.align && ! props.caption ) {
classes.push( 'align' + props.align );
classes.push( 'size-' + props.size );
img['class'] = _.compact( classes ).join(' ');
// Generate `img` tag options.
// Generate the `a` element options, if they exist.
html = wp.html.string( options );
// Generate the caption shortcode.
shortcode.width = img.width;
shortcode.id = props.captionId;
shortcode.align = 'align' + props.align;
html = wp.shortcode.string({
content: html + ' ' + props.caption
coerce : wp.media.coerce,
edit : function( data, isURL ) {
var frame, props = {}, shortcode;
props.url = data.replace(/<[^>]+>/g, '');
shortcode = wp.shortcode.next( 'embed', data ).shortcode;
props = _.defaults( shortcode.attrs.named, this.defaults );
if ( shortcode.content ) {
props.url = shortcode.content;
shortcode : function( model ) {
var self = this, content;
_.each( this.defaults, function( value, key ) {
model[ key ] = self.coerce( model, key );
if ( value === model[ key ] ) {
return new wp.shortcode({
* @class wp.media.collection
* @param {Object} attributes
wp.media.collection = function(attributes) {
return _.extend(/** @lends wp.media.collection.prototype */{
coerce : wp.media.coerce,
* Retrieve attachments based on the properties of the passed shortcode
* @param {wp.shortcode} shortcode An instance of wp.shortcode().
* @return {wp.media.model.Attachments} A Backbone.Collection containing
* the media items belonging to a collection.
* The query[ this.tag ] property is a Backbone.Model
* containing the 'props' for the collection.
attachments: function( shortcode ) {
var shortcodeString = shortcode.string(),
result = collections[ shortcodeString ],
attrs, args, query, others, self = this;
delete collections[ shortcodeString ];
// Fill the default shortcode attributes.
attrs = _.defaults( shortcode.attrs.named, this.defaults );
args = _.pick( attrs, 'orderby', 'order' );
// Mark the `orderby` override attribute.
if ( undefined !== attrs.orderby ) {
attrs._orderByField = attrs.orderby;
if ( 'rand' === attrs.orderby ) {
attrs._orderbyRandom = true;
// Map the `orderby` attribute to the corresponding model property.
if ( ! attrs.orderby || /^menu_order(?: ID)?$/i.test( attrs.orderby ) ) {
args.orderby = 'menuOrder';
// Map the `ids` param to the correct query args.
args.post__in = attrs.ids.split(',');
args.orderby = 'post__in';
} else if ( attrs.include ) {
args.post__in = attrs.include.split(',');
args.post__not_in = attrs.exclude.split(',');
args.uploadedTo = attrs.id;
// Collect the attributes that were not included in `args`.
others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
_.each( this.defaults, function( value, key ) {
others[ key ] = self.coerce( others, key );
query = wp.media.query( args );
query[ this.tag ] = new Backbone.Model( others );
* Triggered when clicking 'Insert {label}' or 'Update {label}'
* @param {wp.media.model.Attachments} attachments A Backbone.Collection containing
* the media items belonging to a collection.
* The query[ this.tag ] property is a Backbone.Model
* containing the 'props' for the collection.
shortcode: function( attachments ) {
var props = attachments.props.toJSON(),
attrs = _.pick( props, 'orderby', 'order' ),
if ( attachments.type ) {
attrs.type = attachments.type;
if ( attachments[this.tag] ) {
_.extend( attrs, attachments[this.tag].toJSON() );
* Convert all gallery shortcodes to use the `ids` property.
* Ignore `post__in` and `post__not_in`; the attachments in
* the collection will already reflect those properties.
attrs.ids = attachments.pluck('id');
// Copy the `uploadedTo` post ID.
if ( props.uploadedTo ) {
attrs.id = props.uploadedTo;
// Check if the gallery is randomly ordered.
if ( attrs._orderbyRandom ) {
} else if ( attrs._orderByField && 'rand' !== attrs._orderByField ) {
attrs.orderby = attrs._orderByField;
delete attrs._orderbyRandom;
delete attrs._orderByField;
// If the `ids` attribute is set and `orderby` attribute
// is the default value, clear it for cleaner output.
if ( attrs.ids && 'post__in' === attrs.orderby ) {
attrs = this.setDefaults( attrs );
shortcode = new wp.shortcode({
// Use a cloned version of the gallery.
clone = new wp.media.model.Attachments( attachments.models, {
clone[ this.tag ] = attachments[ this.tag ];
collections[ shortcode.string() ] = clone;
* Triggered when double-clicking a collection shortcode placeholder
* @param {string} content Content that is searched for possible
* shortcode markup matching the passed tag name,
* @return {wp.media.view.MediaFrame.Select} A media workflow.
edit: function( content ) {
var shortcode = wp.shortcode.next( this.tag, content ),
defaultPostId = this.defaults.id,
attachments, selection, state;
// Bail if we didn't match the shortcode or all of the content.
if ( ! shortcode || shortcode.content !== content ) {