PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-sync / src / modules / class-terms.php

class-terms.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at jetpack_vendor/automattic/jetpack-sync/src/modules/class-terms.php

492 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Terms sync module.
4 *
5 * @package automattic/jetpack-sync
6 */
7
8 namespace Automattic\Jetpack\Sync\Modules;
9
10 use Automattic\Jetpack\Sync\Settings;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit( 0 );
14 }
15
16 /**
17 * Class to handle sync for terms.
18 */
19 class Terms extends Module {
20
21 /**
22 * Sync module name.
23 *
24 * @access public
25 *
26 * @return string
27 */
28 public function name() {
29 return 'terms';
30 }
31
32 /**
33 * The id field in the database.
34 *
35 * @access public
36 *
37 * @return string
38 */
39 public function id_field() {
40 return 'term_taxonomy_id';
41 }
42
43 /**
44 * The table name.
45 *
46 * @access public
47 *
48 * @return string
49 * @deprecated since 3.11.0 Use table() instead.
50 */
51 public function table_name() {
52 _deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Terms->table' );
53 return 'term_taxonomy';
54 }
55
56 /**
57 * The table in the database with the prefix.
58 *
59 * @access public
60 *
61 * @return string|bool
62 */
63 public function table() {
64 global $wpdb;
65 return $wpdb->term_taxonomy;
66 }
67
68 /**
69 * Allows WordPress.com servers to retrieve term-related objects via the sync API.
70 *
71 * @param string $object_type The type of object. Accepts: 'term', 'term_taxonomy', 'term_relationships'.
72 * @param int $id The id of the object.
73 *
74 * @return false|object A term or term_taxonomy object, depending on object type.
75 */
76 public function get_object_by_id( $object_type, $id ) {
77 $id = (int) $id;
78
79 if ( empty( $id ) ) {
80 return false;
81 }
82
83 $objects = $this->get_objects_by_id( $object_type, array( $id ) );
84
85 return $objects[ $id ] ?? false;
86 }
87
88 /**
89 * Retrieve a set of objects by their IDs.
90 *
91 * @access public
92 *
93 * @param string $object_type Object type. Accepts: 'term', 'term_taxonomy', 'term_relationships'.
94 * @param array $ids Object IDs.
95 *
96 * @return array Array of objects.
97 */
98 public function get_objects_by_id( $object_type, $ids ) {
99 global $wpdb;
100
101 $objects = array();
102
103 if ( ! is_array( $ids ) || empty( $ids ) || empty( $object_type ) ) {
104 return $objects;
105 }
106
107 // Sanitize.
108 $ids = array_map( 'intval', $ids );
109 $ids_str = implode( ',', $ids );
110
111 $where_sql = Settings::get_whitelisted_taxonomies_sql();
112
113 switch ( $object_type ) {
114 case 'term':
115 $query = "SELECT * FROM $wpdb->terms t INNER JOIN $wpdb->term_taxonomy tt ON t.term_id=tt.term_id WHERE t.term_id IN ( $ids_str ) AND ";
116 $callback = 'expand_raw_terms';
117 break;
118 case 'term_taxonomy':
119 $query = "SELECT * FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ( $ids_str ) AND ";
120 $callback = 'expand_raw_term_taxonomies';
121 break;
122 case 'term_relationships':
123 $query = "SELECT * FROM $wpdb->term_relationships tr INNER JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id=tt.term_taxonomy_id WHERE object_id IN ( $ids_str ) AND ";
124 $callback = 'expand_raw_term_relationships';
125 break;
126 default:
127 return array();
128 }
129
130 $query .= $where_sql;
131
132 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Already sanitized above.
133 $results = $wpdb->get_results( $query );
134
135 if ( ! is_array( $results ) ) {
136 return array();
137 }
138
139 $objects = $this->$callback( $results );
140
141 return $objects ?? array();
142 }
143
144 /**
145 * Initialize terms action listeners.
146 *
147 * @access public
148 *
149 * @param callable $callable Action handler callable.
150 */
151 public function init_listeners( $callable ) {
152 add_action( 'created_term', array( $this, 'save_term_handler' ), 10, 3 );
153 add_action( 'edited_term', array( $this, 'save_term_handler' ), 10, 3 );
154 add_action( 'jetpack_sync_save_term', $callable );
155 add_action( 'jetpack_sync_add_term', $callable );
156 add_action( 'delete_term', $callable, 10, 4 );
157 add_action( 'set_object_terms', $callable, 10, 6 );
158 add_action( 'deleted_term_relationships', $callable, 10, 2 );
159 add_filter( 'jetpack_sync_before_enqueue_set_object_terms', array( $this, 'filter_set_object_terms_no_update' ) );
160 add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_term', array( $this, 'filter_blacklisted_taxonomies' ) );
161 add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_term', array( $this, 'filter_blacklisted_taxonomies' ) );
162 }
163
164 /**
165 * Initialize terms action listeners for full sync.
166 *
167 * @access public
168 *
169 * @param callable $callable Action handler callable.
170 */
171 public function init_full_sync_listeners( $callable ) {
172 add_action( 'jetpack_full_sync_terms', $callable, 10, 2 );
173 }
174
175 /**
176 * Initialize the module in the sender.
177 *
178 * @access public
179 */
180 public function init_before_send() {
181 // Full sync.
182 add_filter( 'jetpack_sync_before_send_jetpack_full_sync_terms', array( $this, 'expand_term_taxonomy_id' ) );
183 }
184
185 /**
186 * Enqueue the terms actions for full sync.
187 *
188 * @access public
189 *
190 * @param array $config Full sync configuration for this sync module.
191 * @param int $max_items_to_enqueue Maximum number of items to enqueue.
192 * @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
193 * @return array Number of actions enqueued, and next module state.
194 */
195 public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
196 global $wpdb;
197 return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_terms', $wpdb->term_taxonomy, 'term_taxonomy_id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
198 }
199
200 /**
201 * Retrieve the WHERE SQL clause based on the module config.
202 *
203 * @access public
204 *
205 * @param array $config Full sync configuration for this sync module.
206 * @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
207 */
208 public function get_where_sql( $config ) {
209 $where_sql = Settings::get_blacklisted_taxonomies_sql();
210
211 if ( is_array( $config ) && ! empty( $config ) ) {
212 $where_sql .= ' AND term_taxonomy_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
213 }
214
215 return $where_sql;
216 }
217
218 /**
219 * Retrieve an estimated number of actions that will be enqueued.
220 *
221 * @access public
222 *
223 * @param array $config Full sync configuration for this sync module.
224 * @return int Number of items yet to be enqueued.
225 */
226 public function estimate_full_sync_actions( $config ) {
227 global $wpdb;
228
229 $query = "SELECT count(*) FROM $wpdb->term_taxonomy";
230
231 $where_sql = $this->get_where_sql( $config );
232 if ( $where_sql ) {
233 $query .= ' WHERE ' . $where_sql;
234 }
235
236 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
237 $count = (int) $wpdb->get_var( $query );
238
239 return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
240 }
241
242 /**
243 * Retrieve the actions that will be sent for this module during a full sync.
244 *
245 * @access public
246 *
247 * @return array Full sync actions of this module.
248 */
249 public function get_full_sync_actions() {
250 return array( 'jetpack_full_sync_terms' );
251 }
252
253 /**
254 * Handler for creating and updating terms.
255 *
256 * @access public
257 *
258 * @param int $term_id Term ID.
259 * @param int $tt_id Term taxonomy ID.
260 * @param string $taxonomy Taxonomy slug.
261 */
262 public function save_term_handler( $term_id, $tt_id, $taxonomy ) {
263 if ( class_exists( '\\WP_Term' ) ) {
264 $term_object = \WP_Term::get_instance( $term_id, $taxonomy );
265 } else {
266 $term_object = get_term_by( 'id', $term_id, $taxonomy );
267 }
268
269 $current_filter = current_filter();
270
271 if ( 'created_term' === $current_filter ) {
272 /**
273 * Fires when the client needs to add a new term
274 *
275 * @since 1.6.3
276 * @since-jetpack 5.0.0
277 *
278 * @param object the Term object
279 */
280 do_action( 'jetpack_sync_add_term', $term_object );
281 return;
282 }
283
284 /**
285 * Fires when the client needs to update a term
286 *
287 * @since 1.6.3
288 * @since-jetpack 4.2.0
289 *
290 * @param object the Term object
291 */
292 do_action( 'jetpack_sync_save_term', $term_object );
293 }
294
295 /**
296 * Filter blacklisted taxonomies.
297 *
298 * @access public
299 *
300 * @param array $args Hook args.
301 * @return array|boolean False if not whitelisted, the original hook args otherwise.
302 */
303 public function filter_blacklisted_taxonomies( $args ) {
304 $term = $args[0] ?? null;
305
306 if ( ! $term instanceof \WP_Term ) {
307 return false;
308 }
309
310 if ( in_array( $term->taxonomy, Settings::get_setting( 'taxonomies_blacklist' ), true ) ) {
311 return false;
312 }
313
314 return $args;
315 }
316
317 /**
318 * Filter out set_object_terms actions with blacklisted taxonomies or where the terms have not changed.
319 *
320 * @param array $args Hook args.
321 * @return array|boolean False if blacklisted taxonomy or no change in terms, the original hook args otherwise.
322 */
323 public function filter_set_object_terms_no_update( $args ) {
324 // Check if the taxonomy is blacklisted. $args[3] is the taxonomy.
325 if ( isset( $args[3] ) && in_array( $args[3], Settings::get_setting( 'taxonomies_blacklist' ), true ) ) {
326 return false;
327 }
328 // There is potential for other plugins to modify args, therefore lets validate # of and types.
329 // $args[2] is $tt_ids, $args[5] is $old_tt_ids see wp-includes/taxonomy.php L2740.
330 if ( 6 === count( $args ) && is_array( $args[2] ) && is_array( $args[5] ) ) {
331 if ( empty( array_diff( $args[2], $args[5] ) ) && empty( array_diff( $args[5], $args[2] ) ) ) {
332 return false;
333 }
334 }
335 return $args;
336 }
337
338 /**
339 * Expand the term taxonomy IDs to terms within a hook before they are serialized and sent to the server.
340 *
341 * @access public
342 *
343 * @param array $args The hook parameters.
344 * @return array $args The expanded hook parameters.
345 */
346 public function expand_term_taxonomy_id( $args ) {
347 list( $term_taxonomy_ids, $previous_end ) = $args;
348
349 return array(
350 'terms' => get_terms(
351 array(
352 'hide_empty' => false,
353 'orderby' => 'term_taxonomy_id',
354 'order' => 'DESC',
355 'taxonomy' => array(),
356 'term_taxonomy_id' => $term_taxonomy_ids,
357 )
358 ),
359 'previous_end' => $previous_end,
360 );
361 }
362
363 /**
364 * Gets a term object based on a given row from the term_relationships database table.
365 *
366 * @access public
367 *
368 * @deprecated since 4.8.1
369 *
370 * @param object $relationship A row object from the term_relationships table.
371 * @return object|bool A term object, or false if term taxonomy doesn't exist.
372 */
373 public function expand_terms_for_relationship( $relationship ) {
374 _deprecated_function( __METHOD__, '4.8.1' );
375
376 return get_term_by( 'term_taxonomy_id', $relationship->term_taxonomy_id );
377 }
378
379 /**
380 * Prepare raw terms and return them in a standard format.
381 *
382 * @param array $terms An array of raw term objects.
383 * @return array An array of term objects.
384 */
385 private function expand_raw_terms( array $terms ) {
386 $objects = array();
387 $columns = array(
388 'term_id' => 'int',
389 'name' => 'string',
390 'slug' => 'string',
391 'taxonomy' => 'string',
392 'description' => 'string',
393 'term_group' => 'int',
394 'term_taxonomy_id' => 'int',
395 'parent' => 'int',
396 'count' => 'int',
397 );
398
399 foreach ( $terms as $term ) {
400 if ( ! array_key_exists( $term->term_id, $objects ) ) {
401 $t_array = array();
402
403 foreach ( $columns as $field => $type ) {
404 $value = $term->$field ?? '';
405 $t_array[ $field ] = 'int' === $type ? (int) $value : $value;
406 }
407 // This will allow us to know on WPCOM that the term name is the raw one, coming from the DB. Useful with backwards compatibility in mind.
408 $t_array['raw_name'] = $t_array['name'];
409
410 $objects[ $term->term_id ] = (object) $t_array;
411 }
412 }
413
414 return $objects;
415 }
416
417 /**
418 * Prepare raw term taxonomies and return them in a standard format.
419 *
420 * @param array $term_taxonomies An array of raw term_taxonomy objects.
421 * @return array An array of term_taxonomy objects.
422 */
423 private function expand_raw_term_taxonomies( array $term_taxonomies ) {
424 $objects = array();
425 $columns = array(
426 'term_id' => 'int',
427 'taxonomy' => 'string',
428 'description' => 'string',
429 'term_taxonomy_id' => 'int',
430 'parent' => 'int',
431 'count' => 'int',
432 );
433
434 foreach ( $term_taxonomies as $tt ) {
435 if ( ! array_key_exists( $tt->term_taxonomy_id, $objects ) ) {
436 $t_array = array();
437
438 foreach ( $columns as $field => $type ) {
439 $value = $tt->$field ?? '';
440 $t_array[ $field ] = 'int' === $type ? (int) $value : $value;
441 }
442
443 $objects[ $tt->term_taxonomy_id ] = (object) $t_array;
444 }
445 }
446
447 return $objects;
448 }
449
450 /**
451 * Prepare raw term taxonomies and return them in a standard format.
452 *
453 * @param array $term_relationships An array of raw term_taxonomy objects.
454 * @return array An array of term_taxonomy objects or false.
455 */
456 private function expand_raw_term_relationships( array $term_relationships ) {
457 $objects = array();
458 $columns = array(
459 'object_id' => 'int',
460 'term_id' => 'int',
461 'taxonomy' => 'string',
462 'term_taxonomy_id' => 'int',
463 'term_order' => 'int',
464 'parent' => 'int',
465 'count' => 'int',
466 );
467
468 foreach ( $term_relationships as $tt ) {
469 $object_id = (int) $tt->object_id;
470
471 $relationship = array();
472 foreach ( $columns as $field => $type ) {
473 $value = $tt->$field ?? '';
474 $relationship[ $field ] = 'int' === $type ? (int) $value : $value;
475 }
476
477 $relationship = (object) $relationship;
478
479 if ( ! array_key_exists( $object_id, $objects ) ) {
480 $objects[ $object_id ] = (object) array(
481 'object_id' => $object_id,
482 'relationships' => array( $relationship ),
483 );
484 } else {
485 $objects[ $object_id ]->relationships[] = $relationship;
486 }
487 }
488
489 return $objects;
490 }
491 }
492