<!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>[40353] trunk: Invalidate term query caches when setting or deleting term relationships.</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 { 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/40353">40353</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/40353","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>boonebgorges</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2017-03-30 16:49:47 +0000 (Thu, 30 Mar 2017)</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'>Invalidate term query caches when setting or deleting term relationships.

Prior to 4.7, term relationships - as set by `wp_set_object_terms()` or
`wp_remove_object_terms()` - did not affect the term query cache. The
introduction of the 'object_ids' parameter in 4.7 means that the query
cache must be aware of object-term relationships. As such, the
'last_changed' incrementor is now invalidated when term relationships
are modified.

This bug only reared its head when delaying term counting, because term
counting performs its own term query cache invalidation.

Props mboynes.
Fixes <a href="https://core.trac.wordpress.org/ticket/40306">#40306</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludestaxonomyphp">trunk/src/wp-includes/taxonomy.php</a></li>
<li><a href="#trunktestsphpunitteststermgetTheTermsphp">trunk/tests/phpunit/tests/term/getTheTerms.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludestaxonomyphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/taxonomy.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/taxonomy.php        2017-03-30 04:35:09 UTC (rev 40352)
+++ trunk/src/wp-includes/taxonomy.php  2017-03-30 16:49:47 UTC (rev 40353)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2282,6 +2282,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">        wp_cache_delete( $object_id, $taxonomy . '_relationships' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        wp_cache_delete( 'last_changed', 'terms' );
</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">         * Fires after an object's terms have been set.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2376,6 +2377,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                wp_cache_delete( $object_id, $taxonomy . '_relationships' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                wp_cache_delete( 'last_changed', 'terms' );
</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">                 * Fires immediately after an object-term relationship is deleted.
</span></span></pre></div>
<a id="trunktestsphpunitteststermgetTheTermsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/term/getTheTerms.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/term/getTheTerms.php    2017-03-30 04:35:09 UTC (rev 40352)
+++ trunk/tests/phpunit/tests/term/getTheTerms.php      2017-03-30 16:49:47 UTC (rev 40353)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -191,4 +191,66 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( $num_queries, $wpdb->num_queries );
</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">+
+       /**
+        * @ticket 40306
+        */
+       public function test_term_cache_should_be_invalidated_on_set_object_terms() {
+               register_taxonomy( 'wptests_tax', 'post' );
+
+               // Temporarily disable term counting, which performs its own cache invalidation.
+               wp_defer_term_counting( true );
+
+               // Create Test Category.
+               $term_id = self::factory()->term->create( array(
+                       'taxonomy' => 'wptests_tax',
+               ) );
+
+               $post_id = self::factory()->post->create();
+
+               // Prime cache.
+               get_the_terms( $post_id, 'wptests_tax' );
+
+               wp_set_object_terms( $post_id, $term_id, 'wptests_tax' );
+
+               $terms = get_the_terms( $post_id, 'wptests_tax' );
+
+               // Re-activate term counting so this doesn't affect other tests.
+               wp_defer_term_counting( false );
+
+               $this->assertTrue( is_array( $terms ) );
+               $this->assertSame( array( $term_id ), wp_list_pluck( $terms, 'term_id' ) );
+       }
+
+       /**
+        * @ticket 40306
+        */
+       public function test_term_cache_should_be_invalidated_on_remove_object_terms() {
+               register_taxonomy( 'wptests_tax', 'post' );
+
+               // Create Test Category.
+               $term_ids = self::factory()->term->create_many( 2, array(
+                       'taxonomy' => 'wptests_tax',
+               ) );
+
+               $post_id = self::factory()->post->create();
+
+               wp_set_object_terms( $post_id, $term_ids, 'wptests_tax' );
+
+               // Prime cache.
+               get_the_terms( $post_id, 'wptests_tax' );
+
+               // Temporarily disable term counting, which performs its own cache invalidation.
+               wp_defer_term_counting( true );
+
+               wp_remove_object_terms( $post_id, $term_ids[0], 'wptests_tax' );
+
+               $terms = get_the_terms( $post_id, 'wptests_tax' );
+
+               // Re-activate term counting so this doesn't affect other tests.
+               wp_defer_term_counting( false );
+
+               $this->assertTrue( is_array( $terms ) );
+               $this->assertSame( array( $term_ids[1] ), wp_list_pluck( $terms, 'term_id' ) );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>