summaryrefslogtreecommitdiff
blob: 17a183dced24b74ae9128471d6412a941539769c (plain)
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
<?php
/**
 * Term relationships sync module.
 *
 * @package automattic/jetpack-sync
 */

namespace Automattic\Jetpack\Sync\Modules;

use Automattic\Jetpack\Sync\Listener;
use Automattic\Jetpack\Sync\Settings;

/**
 * Class to handle sync for term relationships.
 */
class Term_Relationships extends Module {

	/**
	 * Max terms to return in one single query
	 *
	 * @access public
	 *
	 * @const int
	 */
	const QUERY_LIMIT = 1000;

	/**
	 * Max value for a signed INT in MySQL - https://dev.mysql.com/doc/refman/8.0/en/integer-types.html
	 *
	 * @access public
	 *
	 * @const int
	 */
	const MAX_INT = 2147483647;

	/**
	 * Sync module name.
	 *
	 * @access public
	 *
	 * @return string
	 */
	public function name() {
		return 'term_relationships';
	}

	/**
	 * The id field in the database.
	 *
	 * @access public
	 *
	 * @return string
	 */
	public function id_field() {
		return 'object_id';
	}

	/**
	 * The table in the database.
	 *
	 * @access public
	 *
	 * @return string
	 */
	public function table_name() {
		return 'term_relationships';
	}

	/**
	 * Initialize term relationships action listeners for full sync.
	 *
	 * @access public
	 *
	 * @param callable $callable Action handler callable.
	 */
	public function init_full_sync_listeners( $callable ) {
		add_action( 'jetpack_full_sync_term_relationships', $callable, 10, 2 );
	}

	/**
	 * Initialize the module in the sender.
	 *
	 * @access public
	 */
	public function init_before_send() {
		// Full sync.
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_term_relationships', array( $this, 'expand_term_relationships' ) );
	}

	/**
	 * Enqueue the term relationships actions for full sync.
	 *
	 * @access public
	 *
	 * @param array  $config Full sync configuration for this sync module.
	 * @param int    $max_items_to_enqueue Maximum number of items to enqueue.
	 * @param object $last_object_enqueued Last object enqueued.
	 *
	 * @return array Number of actions enqueued, and next module state.
	 * @todo This method has similarities with Automattic\Jetpack\Sync\Modules\Module::enqueue_all_ids_as_action. Refactor to keep DRY.
	 * @see Automattic\Jetpack\Sync\Modules\Module::enqueue_all_ids_as_action
	 */
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $last_object_enqueued ) {
		global $wpdb;
		$term_relationships_full_sync_item_size = Settings::get_setting( 'term_relationships_full_sync_item_size' );
		$limit                                  = min( $max_items_to_enqueue * $term_relationships_full_sync_item_size, self::QUERY_LIMIT );
		$items_enqueued_count                   = 0;
		$last_object_enqueued                   = $last_object_enqueued ? $last_object_enqueued : array(
			'object_id'        => self::MAX_INT,
			'term_taxonomy_id' => self::MAX_INT,
		);

		while ( $limit > 0 ) {
			/*
			 * SELECT object_id, term_taxonomy_id
			 *  FROM $wpdb->term_relationships
			 *  WHERE ( object_id = 11 AND term_taxonomy_id < 14 ) OR ( object_id < 11 )
			 *  ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT 1000
			 */
			$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 );
			// Request term relationships in groups of N for efficiency.
			$objects_count = count( $objects );
			if ( ! count( $objects ) ) {
				return array( $items_enqueued_count, true );
			}
			$items                 = array_chunk( $objects, $term_relationships_full_sync_item_size );
			$last_object_enqueued  = $this->bulk_enqueue_full_sync_term_relationships( $items, $last_object_enqueued );
			$items_enqueued_count += count( $items );
			$limit                 = min( $limit - $objects_count, self::QUERY_LIMIT );
		}

		// We need to do this extra check in case $max_items_to_enqueue * $term_relationships_full_sync_item_size == relationships objects left.
		$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 ) );
		if ( intval( $count ) === 0 ) {
			return array( $items_enqueued_count, true );
		}

		return array( $items_enqueued_count, $last_object_enqueued );
	}

	/**
	 * Return the initial last sent object.
	 *
	 * @return string|array initial status.
	 */
	public function get_initial_last_sent() {
		return array(
			'object_id'        => self::MAX_INT,
			'term_taxonomy_id' => self::MAX_INT,
		);
	}

	/**
	 * Given the Module Full Sync Configuration and Status return the next chunk of items to send.
	 *
	 * @param array $config This module Full Sync configuration.
	 * @param array $status This module Full Sync status.
	 * @param int   $chunk_size Chunk size.
	 *
	 * @return array|object|null
	 */
	public function get_next_chunk( $config, $status, $chunk_size ) {
		global $wpdb;

		return $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",
				$status['last_sent']['object_id'],
				$status['last_sent']['term_taxonomy_id'],
				$status['last_sent']['object_id'],
				$chunk_size
			),
			ARRAY_A
		);
	}

	/**
	 *
	 * Enqueue all $items within `jetpack_full_sync_term_relationships` actions.
	 *
	 * @param array $items Groups of objects to sync.
	 * @param array $previous_interval_end Last item enqueued.
	 *
	 * @return array Last enqueued object.
	 */
	public function bulk_enqueue_full_sync_term_relationships( $items, $previous_interval_end ) {
		$listener                         = Listener::get_instance();
		$items_with_previous_interval_end = $this->get_chunks_with_preceding_end( $items, $previous_interval_end );
		$listener->bulk_enqueue_full_sync_actions( 'jetpack_full_sync_term_relationships', $items_with_previous_interval_end );
		$last_item = end( $items );
		return end( $last_item );
	}

	/**
	 * Retrieve an estimated number of actions that will be enqueued.
	 *
	 * @access public
	 *
	 * @param array $config Full sync configuration for this sync module.
	 * @return int Number of items yet to be enqueued.
	 */
	public function estimate_full_sync_actions( $config ) {
		global $wpdb;

		$query = "SELECT COUNT(*) FROM $wpdb->term_relationships";

		// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
		$count = $wpdb->get_var( $query );

		return (int) ceil( $count / Settings::get_setting( 'term_relationships_full_sync_item_size' ) );
	}

	/**
	 * Retrieve the actions that will be sent for this module during a full sync.
	 *
	 * @access public
	 *
	 * @return array Full sync actions of this module.
	 */
	public function get_full_sync_actions() {
		return array( 'jetpack_full_sync_term_relationships' );
	}

	/**
	 * Expand the term relationships within a hook before they are serialized and sent to the server.
	 *
	 * @access public
	 *
	 * @param array $args The hook parameters.
	 * @return array $args The expanded hook parameters.
	 */
	public function expand_term_relationships( $args ) {
		list( $term_relationships, $previous_end ) = $args;

		return array(
			'term_relationships' => $term_relationships,
			'previous_end'       => $previous_end,
		);
	}
}