: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Sets the content in an iframe for all view nodes tied to this view instance.
* @param {string} head HTML string to be added to the head of the document.
* @param {string} body HTML string to be added to the body of the document.
* @param {Function} callback A callback. Optional.
* @param {boolean} rendered Only set for (un)rendered nodes. Optional.
setIframes: function( head, body, callback, rendered ) {
if ( body.indexOf( '[' ) !== -1 && body.indexOf( ']' ) !== -1 ) {
var shortcodesRegExp = new RegExp( '\\[\\/?(?:' + window.mceViewL10n.shortcodes.join( '|' ) + ')[^\\]]*?\\]', 'g' );
// Escape tags inside shortcode previews.
body = body.replace( shortcodesRegExp, function( match ) {
return match.replace( /</g, '<' ).replace( />/g, '>' );
this.getNodes( function( editor, node ) {
bodyClasses = editor.getBody().className || '',
editorHead = editor.getDoc().getElementsByTagName( 'head' )[0],
iframe, iframeWin, iframeDoc, MutationObserver, observer, i, block;
tinymce.each( dom.$( 'link[rel="stylesheet"]', editorHead ), function( link ) {
if ( link.href && link.href.indexOf( 'skins/lightgray/content.min.css' ) === -1 &&
link.href.indexOf( 'skins/wordpress/wp-content.css' ) === -1 ) {
styles += dom.getOuterHTML( link );
if ( self.iframeHeight ) {
height: self.iframeHeight
editor.undoManager.transact( function() {
iframe = dom.add( node, 'iframe', {
/* jshint scripturl: true */
src: tinymce.Env.ie ? 'javascript:""' : '',
allowTransparency: 'true',
'class': 'wpview-sandbox',
height: self.iframeHeight
dom.add( node, 'span', { 'class': 'mce-shim' } );
dom.add( node, 'span', { 'class': 'wpview-end' } );
* Bail if the iframe node is not attached to the DOM.
* Happens when the view is dragged in the editor.
* There is a browser restriction when iframes are moved in the DOM. They get emptied.
* The iframe will be rerendered after dropping the view node at the new location.
if ( ! iframe.contentWindow ) {
iframeWin = iframe.contentWindow;
iframeDoc = iframeWin.document;
'<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' +
'background: transparent;' +
'body#wpview-iframe-sandbox {' +
'background: transparent;' +
'padding: 1px 0 !important;' +
'margin: -1px 0 0 !important;' +
'body#wpview-iframe-sandbox:before,' +
'body#wpview-iframe-sandbox:after {' +
'<body id="wpview-iframe-sandbox" class="' + bodyClasses + '">' +
// Make sure the iframe still exists.
if ( iframe.contentWindow ) {
self.iframeHeight = $( iframeDoc.body ).height();
if ( $iframe.height() !== self.iframeHeight ) {
$iframe.height( self.iframeHeight );
if ( self.iframeHeight ) {
observer = new MutationObserver( _.debounce( resize, 100 ) );
observer.observe( iframeDoc.body, {
$( iframeWin ).on( 'load', resize );
MutationObserver = iframeWin.MutationObserver || iframeWin.WebKitMutationObserver || iframeWin.MozMutationObserver;
if ( MutationObserver ) {
if ( ! iframeDoc.body ) {
iframeDoc.addEventListener( 'DOMContentLoaded', addObserver, false );
for ( i = 1; i < 6; i++ ) {
setTimeout( resize, i * 700 );
callback && callback.call( self, editor, node );
* Sets a loader for all view nodes tied to this view instance.
setLoader: function( dashicon ) {
'<div class="loading-placeholder">' +
'<div class="dashicons dashicons-' + ( dashicon || 'admin-media' ) + '"></div>' +
'<div class="wpview-loading"><ins></ins></div>' +
* Sets an error for all view nodes tied to this view instance.
* @param {string} message The error message to set.
* @param {string} dashicon A dashicon ID. Optional. {@link https://developer.wordpress.org/resource/dashicons/}
setError: function( message, dashicon ) {
'<div class="wpview-error">' +
'<div class="dashicons dashicons-' + ( dashicon || 'no' ) + '"></div>' +
'<p>' + message + '</p>' +
* Tries to find a text match in a given string.
* @param {string} content The string to scan.
match: function( content ) {
var match = shortcode.next( this.type, content );
shortcode: match.shortcode
* Update the text of a given view node.
* @param {string} text The new text.
* @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
* @param {HTMLElement} node The view node to update.
* @param {boolean} force Recreate the instance. Optional.
update: function( text, editor, node, force ) {
_.find( views, function( view, type ) {
var match = view.prototype.match( text );
$( node ).data( 'rendered', false );
editor.dom.setAttrib( node, 'data-wpview-text', encodeURIComponent( text ) );
wp.mce.views.createInstance( type, text, match.options, force ).render();
editor.selection.select( node );
* Remove a given view node from the DOM.
* @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
* @param {HTMLElement} node The view node to remove.
remove: function( editor, node ) {
this.unbindNode.call( this, editor, node );
editor.dom.remove( node );
} )( window, window.wp, window.wp.shortcode, window.jQuery );
* The WordPress core TinyMCE views.
* Views for the gallery, audio, video, playlist and embed shortcodes,
* and a view for embeddable URLs.
( function( window, views, media, $ ) {
var base, gallery, av, embed,
schema, parser, serializer;
function verifyHTML( string ) {
if ( ! window.tinymce ) {
return string.replace( /<[^>]+>/g, '' );
if ( ! string || ( string.indexOf( '<' ) === -1 && string.indexOf( '>' ) === -1 ) ) {
schema = schema || new window.tinymce.html.Schema( settings );
parser = parser || new window.tinymce.html.DomParser( settings, schema );
serializer = serializer || new window.tinymce.html.Serializer( settings, schema );
return serializer.serialize( parser.parse( string, { forced_root_block: false } ) );
edit: function( text, update ) {
frame = media[ type ].edit( text );
this.pausePlayers && this.pausePlayers();
_.each( this.state, function( state ) {
frame.state( state ).on( 'update', function( selection ) {
update( media[ type ].shortcode( selection ).string(), type === 'gallery' );
frame.on( 'close', function() {
gallery = _.extend( {}, base, {
state: [ 'gallery-edit' ],
template: media.template( 'editor-gallery' ),
var attachments = media.gallery.attachments( this.shortcode, media.view.settings.post.id ),
attrs = this.shortcode.attrs.named,
attachments = attachments.toJSON();
_.each( attachments, function( attachment ) {
if ( attachment.sizes ) {
if ( attrs.size && attachment.sizes[ attrs.size ] ) {
attachment.thumbnail = attachment.sizes[ attrs.size ];
} else if ( attachment.sizes.thumbnail ) {
attachment.thumbnail = attachment.sizes.thumbnail;
} else if ( attachment.sizes.full ) {
attachment.thumbnail = attachment.sizes.full;
self.render( self.template( {
attachments: attachments,
columns: attrs.columns ? parseInt( attrs.columns, 10 ) : media.galleryDefaults.columns
.fail( function( jqXHR, textStatus ) {
self.setError( textStatus );
av = _.extend( {}, base, {
action: 'parse-media-shortcode',
var self = this, maxwidth = null;
this.shortcode = media.embed.shortcode( {
// Obtain the target width for the embed.
maxwidth = self.editor.getBody().clientWidth;
wp.ajax.post( this.action, {
post_ID: media.view.settings.post.id,
type: this.shortcode.tag,
shortcode: this.shortcode.string(),
.done( function( response ) {
.fail( function( response ) {
self.setError( response.message || response.statusText, 'admin-media' );
this.getEditors( function( editor ) {
editor.on( 'wpview-selected', function() {
pausePlayers: function() {
this.getNodes( function( editor, node, content ) {
var win = $( 'iframe.wpview-sandbox', content ).get( 0 );
if ( win && ( win = win.contentWindow ) && win.mejs ) {
_.each( win.mejs.players, function( player ) {
embed = _.extend( {}, av, {
edit: function( text, update ) {
var frame = media.embed.edit( text, this.url ),
frame.state( 'embed' ).props.on( 'change:url', function( model, url ) {
if ( url && model.get( 'url' ) ) {
frame.state( 'embed' ).metadata = model.toJSON();
frame.state( 'embed' ).on( 'select', function() {
var data = frame.state( 'embed' ).metadata;
update( media.embed.shortcode( data ).string() );
frame.on( 'close', function() {
views.register( 'gallery', _.extend( {}, gallery ) );
views.register( 'audio', _.extend( {}, av, {
state: [ 'audio-details' ]
views.register( 'video', _.extend( {}, av, {
state: [ 'video-details' ]
views.register( 'playlist', _.extend( {}, av, {
state: [ 'playlist-edit', 'video-playlist-edit' ]
views.register( 'embed', _.extend( {}, embed ) );
views.register( 'embedURL', _.extend( {}, embed, {
match: function( content ) {
// There may be a "bookmark" node next to the URL...
var re = /(^|<p>(?:<span data-mce-type="bookmark"[^>]+>\s*<\/span>)?)(https?:\/\/[^\s"]+?)((?:<span data-mce-type="bookmark"[^>]+>\s*<\/span>)?<\/p>\s*|$)/gi;
var match = re.exec( content );
index: match.index + match[1].length,
} )( window, window.wp.mce.views, window.wp.media, window.jQuery );