Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
3.03% covered (danger)
3.03%
2 / 66
7.69% covered (danger)
7.69%
1 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Term_Relationships
1.56% covered (danger)
1.56%
1 / 64
7.69% covered (danger)
7.69%
1 / 13
363.34
0.00% covered (danger)
0.00%
0 / 1
 name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 id_field
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 table_name
n/a
0 / 0
n/a
0 / 0
1
 table
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 init_full_sync_listeners
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 init_before_send
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_full_sync_actions
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 get_initial_last_sent
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 get_next_chunk
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
 get_last_item
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 bulk_enqueue_full_sync_term_relationships
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 estimate_full_sync_actions
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 get_full_sync_actions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 expand_term_relationships
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Term relationships sync module.
4 *
5 * @package automattic/jetpack-sync
6 */
7
8namespace Automattic\Jetpack\Sync\Modules;
9
10use Automattic\Jetpack\Sync\Listener;
11use Automattic\Jetpack\Sync\Settings;
12
13if ( ! defined( 'ABSPATH' ) ) {
14    exit( 0 );
15}
16
17/**
18 * Class to handle sync for term relationships.
19 */
20class Term_Relationships extends Module {
21
22    /**
23     * Max terms to return in one single query
24     *
25     * @access public
26     *
27     * @const int
28     */
29    const QUERY_LIMIT = 1000;
30
31    /**
32     * Max value for a signed INT in MySQL - https://dev.mysql.com/doc/refman/8.0/en/integer-types.html
33     *
34     * @access public
35     *
36     * @const int
37     */
38    const MAX_INT = 2147483647;
39
40    /**
41     * Sync module name.
42     *
43     * @access public
44     *
45     * @return string
46     */
47    public function name() {
48        return 'term_relationships';
49    }
50
51    /**
52     * The id field in the database.
53     *
54     * @access public
55     *
56     * @return string
57     */
58    public function id_field() {
59        return 'object_id';
60    }
61
62    /**
63     * The table name.
64     *
65     * @access public
66     *
67     * @return string
68     * @deprecated since 3.11.0 Use table() instead.
69     */
70    public function table_name() {
71        _deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Term_Relationships->table' );
72        return 'term_relationships';
73    }
74
75    /**
76     * The table in the database with the prefix.
77     *
78     * @access public
79     *
80     * @return string|bool
81     */
82    public function table() {
83        global $wpdb;
84        return $wpdb->term_relationships;
85    }
86
87    /**
88     * Initialize term relationships action listeners for full sync.
89     *
90     * @access public
91     *
92     * @param callable $callable Action handler callable.
93     */
94    public function init_full_sync_listeners( $callable ) {
95        add_action( 'jetpack_full_sync_term_relationships', $callable, 10, 2 );
96    }
97
98    /**
99     * Initialize the module in the sender.
100     *
101     * @access public
102     */
103    public function init_before_send() {
104        // Full sync.
105        add_filter( 'jetpack_sync_before_send_jetpack_full_sync_term_relationships', array( $this, 'expand_term_relationships' ) );
106    }
107
108    /**
109     * Enqueue the term relationships actions for full sync.
110     *
111     * @access public
112     *
113     * @param array  $config Full sync configuration for this sync module.
114     * @param int    $max_items_to_enqueue Maximum number of items to enqueue.
115     * @param object $last_object_enqueued Last object enqueued.
116     *
117     * @return array Number of actions enqueued, and next module state.
118     * @todo This method has similarities with Automattic\Jetpack\Sync\Modules\Module::enqueue_all_ids_as_action. Refactor to keep DRY.
119     * @see Automattic\Jetpack\Sync\Modules\Module::enqueue_all_ids_as_action
120     */
121    public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $last_object_enqueued ) {
122        global $wpdb;
123        $term_relationships_full_sync_item_size = Settings::get_setting( 'term_relationships_full_sync_item_size' );
124        $limit                                  = min( $max_items_to_enqueue * $term_relationships_full_sync_item_size, self::QUERY_LIMIT );
125        $items_enqueued_count                   = 0;
126        $last_object_enqueued                   = $last_object_enqueued ? $last_object_enqueued : array(
127            'object_id'        => self::MAX_INT,
128            'term_taxonomy_id' => self::MAX_INT,
129        );
130
131        while ( $limit > 0 ) {
132            /*
133             * SELECT object_id, term_taxonomy_id
134             *  FROM $wpdb->term_relationships
135             *  WHERE ( object_id = 11 AND term_taxonomy_id < 14 ) OR ( object_id < 11 )
136             *  ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT 1000
137             */
138            $objects = $wpdb->get_results( $wpdb->prepare( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships WHERE ( object_id = %d AND term_taxonomy_id < %d ) OR ( object_id < %d ) ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT %d", $last_object_enqueued['object_id'], $last_object_enqueued['term_taxonomy_id'], $last_object_enqueued['object_id'], $limit ), ARRAY_A );
139            // Request term relationships in groups of N for efficiency.
140            if ( ! is_countable( $objects ) || count( $objects ) === 0 ) {
141                return array( $items_enqueued_count, true );
142            }
143            $objects_count         = count( $objects );
144            $items                 = array_chunk( $objects, $term_relationships_full_sync_item_size );
145            $last_object_enqueued  = $this->bulk_enqueue_full_sync_term_relationships( $items, $last_object_enqueued );
146            $items_enqueued_count += count( $items );
147            $limit                 = min( $limit - $objects_count, self::QUERY_LIMIT );
148        }
149
150        // We need to do this extra check in case $max_items_to_enqueue * $term_relationships_full_sync_item_size == relationships objects left.
151        $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE ( object_id = %d AND term_taxonomy_id < %d ) OR ( object_id < %d ) ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT %d", $last_object_enqueued['object_id'], $last_object_enqueued['term_taxonomy_id'], $last_object_enqueued['object_id'], 1 ) );
152        if ( 0 === (int) $count ) {
153            return array( $items_enqueued_count, true );
154        }
155
156        return array( $items_enqueued_count, $last_object_enqueued );
157    }
158
159    /**
160     * Return the initial last sent object.
161     *
162     * @return string|array initial status.
163     */
164    public function get_initial_last_sent() {
165        return array(
166            'object_id'        => self::MAX_INT,
167            'term_taxonomy_id' => self::MAX_INT,
168        );
169    }
170
171    /**
172     * Given the Module Full Sync Configuration and Status return the next chunk of items to send.
173     *
174     * @param array $config This module Full Sync configuration.
175     * @param array $status This module Full Sync status.
176     * @param int   $chunk_size Chunk size.
177     *
178     * @return array|object|null
179     */
180    public function get_next_chunk( $config, $status, $chunk_size ) {
181        global $wpdb;
182
183        return $wpdb->get_results(
184            $wpdb->prepare(
185                "SELECT tr.object_id, tr.term_taxonomy_id                 
186                FROM $wpdb->term_relationships tr INNER JOIN $wpdb->term_taxonomy tt 
187                ON tr.term_taxonomy_id=tt.term_taxonomy_id
188                WHERE " .
189                Settings::get_whitelisted_taxonomies_sql() // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
190                . ' AND ( ( tr.object_id = %d AND tr.term_taxonomy_id < %d ) OR ( tr.object_id < %d ) )
191                ORDER BY tr.object_id DESC, tr.term_taxonomy_id 
192                DESC LIMIT %d',
193                $status['last_sent']['object_id'],
194                $status['last_sent']['term_taxonomy_id'],
195                $status['last_sent']['object_id'],
196                $chunk_size
197            ),
198            ARRAY_A
199        );
200    }
201
202    /**
203     * Return last_item to send for Module Full Sync Configuration.
204     *
205     * @param array $config This module Full Sync configuration.
206     *
207     * @return array|object|null
208     */
209    public function get_last_item( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
210        global $wpdb;
211        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
212        return $wpdb->get_results(
213            "SELECT object_id, term_taxonomy_id 
214            FROM $wpdb->term_relationships 
215            ORDER BY object_id , term_taxonomy_id
216            LIMIT 1",
217            ARRAY_A
218        );
219    }
220
221    /**
222     *
223     * Enqueue all $items within `jetpack_full_sync_term_relationships` actions.
224     *
225     * @param array $items Groups of objects to sync.
226     * @param array $previous_interval_end Last item enqueued.
227     *
228     * @return array Last enqueued object.
229     */
230    public function bulk_enqueue_full_sync_term_relationships( $items, $previous_interval_end ) {
231        $listener                         = Listener::get_instance();
232        $items_with_previous_interval_end = $this->get_chunks_with_preceding_end( $items, $previous_interval_end );
233        $listener->bulk_enqueue_full_sync_actions( 'jetpack_full_sync_term_relationships', $items_with_previous_interval_end );
234        $last_item = end( $items );
235        return end( $last_item );
236    }
237
238    /**
239     * Retrieve an estimated number of actions that will be enqueued.
240     *
241     * @access public
242     *
243     * @param array $config Full sync configuration for this sync module.
244     * @return int Number of items yet to be enqueued.
245     */
246    public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
247        global $wpdb;
248
249        $query = "SELECT COUNT(*) FROM $wpdb->term_relationships";
250
251        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
252        $count = (int) $wpdb->get_var( $query );
253
254        return (int) ceil( $count / Settings::get_setting( 'term_relationships_full_sync_item_size' ) );
255    }
256
257    /**
258     * Retrieve the actions that will be sent for this module during a full sync.
259     *
260     * @access public
261     *
262     * @return array Full sync actions of this module.
263     */
264    public function get_full_sync_actions() {
265        return array( 'jetpack_full_sync_term_relationships' );
266    }
267
268    /**
269     * Expand the term relationships within a hook before they are serialized and sent to the server.
270     *
271     * @access public
272     *
273     * @param array $args The hook parameters.
274     * @return array $args The expanded hook parameters.
275     */
276    public function expand_term_relationships( $args ) {
277        list( $term_relationships, $previous_end ) = $args;
278
279        return array(
280            'term_relationships' => $term_relationships,
281            'previous_end'       => $previous_end,
282        );
283    }
284}