forked from Automattic/lightweight-term-count-update
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlightweight-term-count-update.php
320 lines (286 loc) · 10.3 KB
/
lightweight-term-count-update.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
<?php
/**
* Plugin Name: Lightweight Term Count Update
* Plugin URI: https://github.com/Automattic/lightweight-term-count-update
* Description: Makes the _update_term_count_on_transition_post_status action very fast
* Author: Automattic, Alley Interactive
* Author URI: https://automattic.com/
* Text Domain: lightweight-term-count-update
* Domain Path: /languages
* Version: 0.1.0
*
* @package Lightweight_Term_Count_Update
*/
/**
* Main plugin class.
*/
class LTCU_Plugin {
/**
* Post statuses which should be counted in term post counting. By default
* this is [ 'publish' ], but it can be altered via the
* `ltcu_counted_statuses` filter.
*
* @var array
*/
public $counted_statuses = array( 'publish' );
/**
* Store the terms that have been incremented or decremented to avoid
* duplicated efforts.
*
* @var array
*/
public $counted_terms = array();
/**
* Store the singleton instance.
*
* @var My_Singleton
*/
private static $instance;
/**
* Build the object.
*
* @access private
*/
private function __construct() {
/* Don't do anything, needs to be initialized via instance() method */
}
/**
* Get the Singleton instance.
* @return My_Singleton
*/
public static function instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new static();
self::$instance->setup();
}
return self::$instance;
}
/**
* Setup the singleton.
*/
public function setup() {
// Prevent core from counting terms.
wp_defer_term_counting( true );
remove_action( 'transition_post_status', '_update_term_count_on_transition_post_status' );
/**
* Filter the statuses that should be counted, to allow for custom post
* statuses that are otherwise equivalent to 'publish'.
*
* @param array $counted_statuses The statuses that should be counted.
* Defaults to ['publish'].
*/
$this->counted_statuses = apply_filters( 'ltcu_counted_statuses', $this->counted_statuses );
add_action( 'transition_post_status', array( $this, 'transition_post_status' ), 10, 3 );
add_action( 'added_term_relationship', array( $this, 'added_term_relationship' ), 10, 3 );
add_action( 'deleted_term_relationships', array( $this, 'deleted_term_relationships' ), 10, 3 );
/**
* Possibly recount posts for a term once it's been edited.
*/
add_action( 'edit_term', array( $this, 'maybe_recount_posts_for_term' ), 10, 3 );
}
/**
* When a term relationship is added, increment the term count.
*
* @see {LTCU_Plugin::handle_term_relationship_change()}
*
* @param int $object_id Object ID.
* @param int $tt_id Single term taxonomy ID.
* @param string $taxonomy Taxonomy slug.
*/
public function added_term_relationship( $object_id, $tt_id, $taxonomy ) {
$this->handle_term_relationship_change( $object_id, (array) $tt_id, $taxonomy, 'increment' );
}
/**
* When a term relationship is deleted, decrement the term count.
*
* @see {LTCU_Plugin::handle_term_relationship_change()}
*
* @param int $object_id Object ID.
* @param array $tt_ids Array of term taxonomy IDs.
* @param string $taxonomy Taxonomy slug.
*/
public function deleted_term_relationships( $object_id, $tt_ids, $taxonomy ) {
$this->handle_term_relationship_change( $object_id, $tt_ids, $taxonomy, 'decrement' );
}
/**
* Update term counts when term relationships are added or deleted.
*
* @see {LTCU_Plugin::added_term_relationship()}
* @see {LTCU_Plugin::deleted_term_relationships()}
*
* @param int $object_id Object ID.
* @param array $tt_ids Array of term taxonomy IDs.
* @param string $taxonomy Taxonomy slug.
* @param string $transition_type Transition type (increment or decrement).
*/
protected function handle_term_relationship_change( $object_id, $tt_ids, $taxonomy, $transition_type ) {
$post = get_post( $object_id );
if ( ! $post || ! is_object_in_taxonomy( $post->post_type, $taxonomy ) ) {
// If this object isn't a post, we can jump right into counting it.
$this->quick_update_terms_count( $object_id, $tt_ids, $taxonomy, $transition_type );
} elseif ( in_array( get_post_status( $post ), $this->counted_statuses, true ) ) {
// If this is a post, we only count it if it's in a counted status.
// If the status changed, that will be caught by
// `LTCU_Plugin::transition_post_status()`. Also note that we used
// `get_post_status()` above because that checks the parent status
// if the status is inherit.
$this->quick_update_terms_count( $object_id, $tt_ids, $taxonomy, $transition_type );
} else {
clean_term_cache( $tt_ids, $taxonomy, false );
}
}
/**
* When a post transitions, increment or decrement term counts as
* appropriate.
*
* @param string $new_status New post status.
* @param string $old_status Old post status.
* @param object $post {
* Post being transitioned. This not always a \WP_Post.
*
* @type int $ID Post ID.
* @type string $post_type Post type.
* }
*/
public function transition_post_status( $new_status, $old_status, $post ) {
foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) {
$tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array(
'fields' => 'tt_ids',
) );
if ( ! empty( $tt_ids ) && ! is_wp_error( $tt_ids ) ) {
$this->quick_update_terms_count(
$post->ID,
$tt_ids,
$taxonomy,
$this->transition_type( $new_status, $old_status )
);
}
}
// For non-attachments, let's check if there are any attachment children
// with inherited post status -- if so those will need to be re-counted.
if ( 'attachment' !== $post->post_type ) {
$attachments = new WP_Query( array(
'post_type' => 'attachment',
'post_parent' => $post->ID,
'post_status' => 'inherit',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'posts_per_page' => -1,
'fields' => 'ids',
'orderby' => 'ID',
'order' => 'ASC',
) );
if ( $attachments->have_posts() ) {
foreach ( $attachments->posts as $attachment_id ) {
$this->transition_post_status( $new_status, $old_status, (object) array(
'ID' => $attachment_id,
'post_type' => 'attachment',
) );
}
}
}
}
/**
* Update term counts using a very light SQL query.
*
* @param int $object_id Object ID with the term relationship.
* @param array $tt_ids Term taxonomy IDs.
* @param string $taxonomy Taxonomy slug.
* @param string $transition_type 'increment' or 'decrement'.
*/
public function quick_update_terms_count( $object_id, $tt_ids, $taxonomy, $transition_type ) {
global $wpdb;
if ( ! $transition_type ) {
return false;
}
$tax_obj = get_taxonomy( $taxonomy );
if ( $tax_obj ) {
$tt_ids = array_filter( array_map( 'intval', (array) $tt_ids ) );
// Respect if a taxonomy has a callback override.
if ( ! empty( $tax_obj->update_count_callback ) ) {
call_user_func( $tax_obj->update_count_callback, $tt_ids, $tax_obj );
} elseif ( ! empty( $tt_ids ) ) {
if ( ! isset( $this->counted_terms[ $object_id ][ $taxonomy ][ $transition_type ] ) ) {
$this->counted_terms[ $object_id ][ $taxonomy ][ $transition_type ] = array();
}
// Ensure that these terms haven't already been counted.
$tt_ids = array_diff( $tt_ids, $this->counted_terms[ $object_id ][ $taxonomy ][ $transition_type ] );
if ( empty( $tt_ids ) ) {
// No term to process. So return.
return;
}
$this->counted_terms[ $object_id ][ $taxonomy ][ $transition_type ] = array_merge(
$this->counted_terms[ $object_id ][ $taxonomy ][ $transition_type ],
$tt_ids
);
$tt_ids = array_map( 'absint', $tt_ids );
$tt_ids_string = '(' . implode( ',', $tt_ids ) . ')';
if ( 'increment' === $transition_type ) {
// Incrementing.
$update_query = "UPDATE {$wpdb->term_taxonomy} AS tt SET tt.count = tt.count + 1 WHERE tt.term_taxonomy_id IN $tt_ids_string";
} else {
// Decrementing.
$update_query = "UPDATE {$wpdb->term_taxonomy} AS tt SET tt.count = tt.count - 1 WHERE tt.term_taxonomy_id IN $tt_ids_string AND tt.count > 0";
}
foreach ( $tt_ids as $tt_id ) {
/** This action is documented in wp-includes/taxonomy.php */
do_action( 'edit_term_taxonomy', $tt_id, $taxonomy );
}
$wpdb->query( $update_query ); // WPCS: unprepared SQL ok.
foreach ( $tt_ids as $tt_id ) {
/** This action is documented in wp-includes/taxonomy.php */
do_action( 'edited_term_taxonomy', $tt_id, $taxonomy );
}
} // End if().
clean_term_cache( $tt_ids, $taxonomy, false );
} // End if().
}
/**
* Determine if a term count should be incremented or decremented.
*
* @param string $new New post status.
* @param string $old Old post status.
* @return string|bool 'increment', 'decrement', or false.
*/
public function transition_type( $new, $old ) {
if ( ! is_array( $this->counted_statuses ) || ! $this->counted_statuses ) {
return false;
}
$new_is_counted = in_array( $new, $this->counted_statuses, true );
$old_is_counted = in_array( $old, $this->counted_statuses, true );
if ( $new_is_counted && ! $old_is_counted ) {
return 'increment';
} elseif ( $old_is_counted && ! $new_is_counted ) {
return 'decrement';
} else {
return false;
}
}
/**
* Force-recount posts for a term. Do this only when the update originates from the edit term screen.
*
* @param int $term_id the term id.
* @param int $tt_id the term taxonomy id.
* @param string $taxonomy the taxonomy.
*
* @return bool false if the screen check fails, true otherwise
*/
public function maybe_recount_posts_for_term( $term_id, $tt_id, $taxonomy ) {
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : '';
if ( ! ( $screen instanceof WP_Screen ) ) {
return false;
}
if ( "edit-$taxonomy" === $screen->id ) {
wp_update_term_count_now( array( $tt_id ), $taxonomy );
}
return true;
}
}
// Only run this plugin if we're not running a cron task or wp-cli task.
if (
! ( defined( 'DOING_CRON' ) && DOING_CRON )
&& ! ( defined( 'WP_CLI' ) && WP_CLI )
&& ! ( defined( 'WP_IMPORTING' ) && WP_IMPORTING )
) {
add_action( 'init', array( 'LTCU_Plugin', 'instance' ) );
}