<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[51145] trunk/src: Media: Restore AJAX response data shape in media library.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/51145">51145</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/51145","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>joedolson</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2021-06-14 20:49:05 +0000 (Mon, 14 Jun 2021)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Media: Restore AJAX response data shape in media library.

Restore the original shape of the AJAX response data in the media library after removing infinite scroll, and pass total number of attachments in the response headers `X-WP-Total` and `X-WP-TotalPages`. 

Improve backwards compatibility for plugins intercepting the ajax response. Headers match the structure and count calculation used in REST API responses.

Fix an issue with hiding the spinner after the load is completed and ensure that the load more view is created when changing tabs in the media library modal.

Follow up to <a href="https://core.trac.wordpress.org/changeset/50829">[50829]</a>.

props adamsilverstein, spacedmonkey, joedolson.
Fixes <a href="https://core.trac.wordpress.org/ticket/50105">#50105</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcjs_enqueueswputiljs">trunk/src/js/_enqueues/wp/util.js</a></li>
<li><a href="#trunksrcjsmediamodelsattachmentsjs">trunk/src/js/media/models/attachments.js</a></li>
<li><a href="#trunksrcjsmediamodelsqueryjs">trunk/src/js/media/models/query.js</a></li>
<li><a href="#trunksrcjsmediaviewsattachmentsbrowserjs">trunk/src/js/media/views/attachments/browser.js</a></li>
<li><a href="#trunksrcwpadminincludesajaxactionsphp">trunk/src/wp-admin/includes/ajax-actions.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcjs_enqueueswputiljs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/js/_enqueues/wp/util.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/js/_enqueues/wp/util.js 2021-06-14 20:38:38 UTC (rev 51144)
+++ trunk/src/js/_enqueues/wp/util.js   2021-06-14 20:49:05 UTC (rev 51145)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -115,7 +115,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                        if ( _.isObject( response ) && ! _.isUndefined( response.success ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                deferred[ response.success ? 'resolveWith' : 'rejectWith' ]( this, [response.data] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                         deferred[ response.success ? 'resolveWith' : 'rejectWith' ]( deferred.jqXHR, [response.data] );
</ins><span class="cx" style="display: block; padding: 0 10px">                                         } else {
</span><span class="cx" style="display: block; padding: 0 10px">                                                deferred.rejectWith( this, [response] );
</span><span class="cx" style="display: block; padding: 0 10px">                                        }
</span></span></pre></div>
<a id="trunksrcjsmediamodelsattachmentsjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/js/media/models/attachments.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/js/media/models/attachments.js  2021-06-14 20:38:38 UTC (rev 51144)
+++ trunk/src/js/media/models/attachments.js    2021-06-14 20:49:05 UTC (rev 51145)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -375,21 +375,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * passing through the JSON response. We override this to add attributes to
</span><span class="cx" style="display: block; padding: 0 10px">         * the collection items.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @since 5.8.0 The response returns the attachments under `response.attachments` and
-        *              `response.totalAttachments` holds the total number of attachments found.
-        *
</del><span class="cx" style="display: block; padding: 0 10px">          * @param {Object|Array} response The raw response Object/Array.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param {Object} xhr
</span><span class="cx" style="display: block; padding: 0 10px">         * @return {Array} The array of model attributes to be added to the collection
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        parse: function( response, xhr ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! _.isArray( response.attachments ) ) {
-                       response = [ response.attachments ];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! _.isArray( response ) ) {
+                         response = [response];
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               this.totalAttachments = parseInt( response.totalAttachments, 10 );
-
-               return _.map( response.attachments, function( attrs ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return _.map( response, function( attrs ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         var id, attachment, newAttributes;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( attrs instanceof Backbone.Model ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -409,9 +403,24 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return attachment;
</span><span class="cx" style="display: block; padding: 0 10px">                });
</span><span class="cx" style="display: block; padding: 0 10px">        },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       // Customize fetch so we can extract the total post count from the response headers.
+       fetch: function(options) {
+               var collection = this;
+               var fetched = Backbone.Collection.prototype.fetch.call(this, options)
+                       .done( function() {
+                               if ( this.hasOwnProperty( 'getResponseHeader' ) ) {
+                                       collection.totalAttachments = parseInt( this.getResponseHeader( 'X-WP-Total' ), 10 );
+                               } else {
+                                       collection.totalAttachments = 0;
+                               }
+                       } );
+               return fetched;
+       },
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * If the collection is a query, create and mirror an Attachments Query collection.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * 
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @access private
</span><span class="cx" style="display: block; padding: 0 10px">         * @param {Boolean} refresh Deprecated, refresh parameter no longer used.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span></span></pre></div>
<a id="trunksrcjsmediamodelsqueryjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/js/media/models/query.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/js/media/models/query.js        2021-06-14 20:38:38 UTC (rev 51144)
+++ trunk/src/js/media/models/query.js  2021-06-14 20:49:05 UTC (rev 51145)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -113,9 +113,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                options.remove = false;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return this._more = this.fetch( options ).done( function( response ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        var attachments = response.attachments;
-
-                       if ( _.isEmpty( attachments ) || -1 === this.args.posts_per_page || attachments.length < this.args.posts_per_page ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( _.isEmpty( response ) || -1 === query.args.posts_per_page || response.length < query.args.posts_per_page ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 query._hasMore = false;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                });
</span></span></pre></div>
<a id="trunksrcjsmediaviewsattachmentsbrowserjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/js/media/views/attachments/browser.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/js/media/views/attachments/browser.js   2021-06-14 20:38:38 UTC (rev 51144)
+++ trunk/src/js/media/views/attachments/browser.js     2021-06-14 20:49:05 UTC (rev 51145)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -88,6 +88,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                this.updateContent();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                this.updateLoadMoreView();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        this.$el.addClass( 'hide-sidebar' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -635,11 +636,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                });
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                view.loadMoreSpinner.show();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               this.collection.more().done( function() {
-                       // Within done(), `this` is the returned collection.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         this.collection.once( 'attachments:received', function() {
</ins><span class="cx" style="display: block; padding: 0 10px">                         view.loadMoreSpinner.hide();
</span><span class="cx" style="display: block; padding: 0 10px">                } );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                this.collection.more();
</ins><span class="cx" style="display: block; padding: 0 10px">         },
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span></span></pre></div>
<a id="trunksrcwpadminincludesajaxactionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/ajax-actions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/ajax-actions.php      2021-06-14 20:38:38 UTC (rev 51144)
+++ trunk/src/wp-admin/includes/ajax-actions.php        2021-06-14 20:49:05 UTC (rev 51145)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2990,17 +2990,27 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @param array $query An array of query variables.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        $query = apply_filters( 'ajax_query_attachments_args', $query );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $query = new WP_Query( $query );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $attachments_query = new WP_Query( $query );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $posts = array_map( 'wp_prepare_attachment_for_js', $attachments_query->posts );
</ins><span class="cx" style="display: block; padding: 0 10px">         $posts = array_filter( $posts );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        $total_posts = $attachments_query->found_posts;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $result = array(
-               'attachments'      => $posts,
-               'totalAttachments' => $query->found_posts,
-       );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $total_posts < 1 ) {
+               // Out-of-bounds, run the query again without LIMIT for total count.
+               unset( $query['paged'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        wp_send_json_success( $result );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $count_query = new WP_Query();
+               $count_query->query( $query_args );
+               $total_posts = $count_query->found_posts;
+       }
+
+       $max_pages = ceil( $total_posts / (int) $attachments_query->query['posts_per_page'] );
+
+       header( 'X-WP-Total: ' . (int) $total_posts );
+       header( 'X-WP-TotalPages: ' . (int) $max_pages );
+
+       wp_send_json_success( $posts );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre>
</div>
</div>

</body>
</html>