1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
use Automattic\Jetpack\Sync\Actions; |
4
|
|
|
use Automattic\Jetpack\Sync\Modules; |
5
|
|
|
use Automattic\Jetpack\Sync\Queue; |
6
|
|
|
use Automattic\Jetpack\Sync\Queue_Buffer; |
7
|
|
|
use Automattic\Jetpack\Sync\Replicastore; |
8
|
|
|
use Automattic\Jetpack\Sync\Sender; |
9
|
|
|
use Automattic\Jetpack\Sync\Settings; |
10
|
|
|
|
11
|
|
|
// POST /sites/%s/sync |
12
|
|
|
class Jetpack_JSON_API_Sync_Endpoint extends Jetpack_JSON_API_Endpoint { |
13
|
|
|
protected $needed_capabilities = 'manage_options'; |
14
|
|
|
|
15
|
|
|
protected function validate_call( $_blog_id, $capability, $check_manage_active = true ) { |
16
|
|
|
return parent::validate_call( $_blog_id, $capability, false ); |
17
|
|
|
} |
18
|
|
|
|
19
|
|
|
protected function result() { |
20
|
|
|
$args = $this->input(); |
21
|
|
|
$modules = null; |
22
|
|
|
|
23
|
|
|
// convert list of modules in comma-delimited format into an array |
24
|
|
|
// of "$modulename => true" |
25
|
|
View Code Duplication |
if ( isset( $args['modules'] ) && ! empty( $args['modules'] ) ) { |
26
|
|
|
$modules = array_map( '__return_true', array_flip( array_map( 'trim', explode( ',', $args['modules'] ) ) ) ); |
27
|
|
|
} |
28
|
|
|
|
29
|
|
View Code Duplication |
foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) { |
30
|
|
|
if ( 'users' === $module_name && isset( $args[ $module_name ] ) && 'initial' === $args[ $module_name ] ) { |
31
|
|
|
$modules[ 'users' ] = 'initial'; |
32
|
|
|
} elseif ( isset( $args[ $module_name ] ) ) { |
33
|
|
|
$ids = explode( ',', $args[ $module_name ] ); |
34
|
|
|
if ( count( $ids ) > 0 ) { |
35
|
|
|
$modules[ $module_name ] = $ids; |
36
|
|
|
} |
37
|
|
|
} |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
if ( empty( $modules ) ) { |
41
|
|
|
$modules = null; |
42
|
|
|
} |
43
|
|
|
return array( 'scheduled' => Actions::do_full_sync( $modules ) ); |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
protected function validate_queue( $query ) { |
47
|
|
|
if ( ! isset( $query ) ) { |
48
|
|
|
return new WP_Error( 'invalid_queue', 'Queue name is required', 400 ); |
|
|
|
|
49
|
|
|
} |
50
|
|
|
|
51
|
|
View Code Duplication |
if ( ! in_array( $query, array( 'sync', 'full_sync', 'immediate' ) ) ) { |
52
|
|
|
return new WP_Error( 'invalid_queue', 'Queue name should be sync, full_sync or immediate', 400 ); |
|
|
|
|
53
|
|
|
} |
54
|
|
|
return $query; |
55
|
|
|
} |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
// GET /sites/%s/sync/status |
59
|
|
|
class Jetpack_JSON_API_Sync_Status_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { |
60
|
|
|
protected function result() { |
61
|
|
|
$args = $this->query_args(); |
62
|
|
|
$fields = isset( $args['fields'] ) ? $args['fields'] : array(); |
63
|
|
|
return Actions::get_sync_status( $fields ); |
64
|
|
|
} |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
// GET /sites/%s/data-check |
68
|
|
|
class Jetpack_JSON_API_Sync_Check_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { |
69
|
|
|
protected function result() { |
70
|
|
|
$store = new Replicastore(); |
71
|
|
|
return $store->checksum_all(); |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
// GET /sites/%s/data-histogram |
76
|
|
|
class Jetpack_JSON_API_Sync_Histogram_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { |
77
|
|
|
protected function result() { |
78
|
|
|
$args = $this->query_args(); |
79
|
|
|
|
80
|
|
|
if ( isset( $args['columns'] ) ) { |
81
|
|
|
$columns = array_map( 'trim', explode( ',', $args['columns'] ) ); |
82
|
|
|
} else { |
83
|
|
|
$columns = null; // go with defaults |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
$store = new Replicastore(); |
87
|
|
|
|
88
|
|
|
if ( ! isset( $args['strip_non_ascii'] ) ) { |
89
|
|
|
$args['strip_non_ascii'] = true; |
90
|
|
|
} |
91
|
|
|
$histogram = $store->checksum_histogram( $args['object_type'], $args['buckets'], $args['start_id'], $args['end_id'], $columns, $args['strip_non_ascii'], $args['shared_salt'] ); |
92
|
|
|
|
93
|
|
|
return array( 'histogram' => $histogram, 'type' => $store->get_checksum_type() ); |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
// POST /sites/%s/sync/settings |
98
|
|
|
class Jetpack_JSON_API_Sync_Modify_Settings_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { |
99
|
|
|
protected function result() { |
100
|
|
|
$args = $this->input(); |
101
|
|
|
|
102
|
|
|
$sync_settings = Settings::get_settings(); |
103
|
|
|
|
104
|
|
|
foreach ( $args as $key => $value ) { |
105
|
|
|
if ( $value !== false ) { |
106
|
|
|
if ( is_numeric( $value ) ) { |
107
|
|
|
$value = (int) $value; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
// special case for sending empty arrays - a string with value 'empty' |
111
|
|
|
if ( $value === 'empty' ) { |
112
|
|
|
$value = array(); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
$sync_settings[ $key ] = $value; |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
Settings::update_settings( $sync_settings ); |
120
|
|
|
|
121
|
|
|
// re-fetch so we see what's really being stored |
122
|
|
|
return Settings::get_settings(); |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
// GET /sites/%s/sync/settings |
127
|
|
|
class Jetpack_JSON_API_Sync_Get_Settings_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { |
128
|
|
|
protected function result() { |
129
|
|
|
|
130
|
|
|
return Settings::get_settings(); |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
// GET /sites/%s/sync/object |
135
|
|
|
class Jetpack_JSON_API_Sync_Object extends Jetpack_JSON_API_Sync_Endpoint { |
136
|
|
|
protected function result() { |
137
|
|
|
$args = $this->query_args(); |
138
|
|
|
|
139
|
|
|
$module_name = $args['module_name']; |
140
|
|
|
|
141
|
|
|
if ( ! $sync_module = Modules::get_module( $module_name ) ) { |
142
|
|
|
return new WP_Error( 'invalid_module', 'You specified an invalid sync module' ); |
|
|
|
|
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
$object_type = $args['object_type']; |
146
|
|
|
$object_ids = $args['object_ids']; |
147
|
|
|
|
148
|
|
|
$codec = Sender::get_instance()->get_codec(); |
149
|
|
|
|
150
|
|
|
Settings::set_is_syncing( true ); |
151
|
|
|
$objects = $codec->encode( $sync_module->get_objects_by_id( $object_type, $object_ids ) ); |
152
|
|
|
Settings::set_is_syncing( false ); |
153
|
|
|
|
154
|
|
|
return array( |
155
|
|
|
'objects' => $objects, |
156
|
|
|
'codec' => $codec->name(), |
157
|
|
|
); |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
class Jetpack_JSON_API_Sync_Now_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { |
162
|
|
|
protected function result() { |
163
|
|
|
$args = $this->input(); |
164
|
|
|
$queue_name = $this->validate_queue( $args['queue'] ); |
165
|
|
|
|
166
|
|
|
if ( is_wp_error( $queue_name ) ){ |
167
|
|
|
return $queue_name; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
$sender = Sender::get_instance(); |
171
|
|
|
$response = $sender->do_sync_for_queue( new Queue( $args['queue'] ) ); |
|
|
|
|
172
|
|
|
|
173
|
|
|
return array( |
174
|
|
|
'response' => $response |
175
|
|
|
); |
176
|
|
|
} |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
class Jetpack_JSON_API_Sync_Checkout_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { |
180
|
|
|
protected function result() { |
181
|
|
|
$args = $this->input(); |
182
|
|
|
$queue_name = $this->validate_queue( $args['queue'] ); |
183
|
|
|
|
184
|
|
|
if ( is_wp_error( $queue_name ) ) { |
185
|
|
|
return $queue_name; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
if ( $args['number_of_items'] < 1 || $args['number_of_items'] > 100 ) { |
189
|
|
|
return new WP_Error( 'invalid_number_of_items', 'Number of items needs to be an integer that is larger than 0 and less then 100', 400 ); |
|
|
|
|
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
$number_of_items = absint( $args['number_of_items'] ); |
193
|
|
|
|
194
|
|
|
if ( 'immediate' === $queue_name ) { |
195
|
|
|
return $this->immediate_full_sync_pull( $number_of_items ); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
return $this->queue_pull( $queue_name, $number_of_items, $args ); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
function queue_pull( $queue_name, $number_of_items, $args ){ |
202
|
|
|
$queue = new Queue( $queue_name ); |
203
|
|
|
|
204
|
|
|
if ( 0 === $queue->size() ) { |
205
|
|
|
return new WP_Error( 'queue_size', 'The queue is empty and there is nothing to send', 400 ); |
|
|
|
|
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
$sender = Sender::get_instance(); |
209
|
|
|
|
210
|
|
|
// try to give ourselves as much time as possible. |
211
|
|
|
set_time_limit( 0 ); |
212
|
|
|
|
213
|
|
|
if ( $args['pop'] ) { |
214
|
|
|
$buffer = new Queue_Buffer( 'pop', $queue->pop( $number_of_items ) ); |
|
|
|
|
215
|
|
|
} else { |
216
|
|
|
// let's delete the checkin state. |
217
|
|
|
if ( $args['force'] ) { |
218
|
|
|
$queue->unlock(); |
219
|
|
|
} |
220
|
|
|
$buffer = $this->get_buffer( $queue, $number_of_items ); |
221
|
|
|
} |
222
|
|
|
// Check that the $buffer is not checkout out already. |
223
|
|
|
if ( is_wp_error( $buffer ) ) { |
224
|
|
|
return new WP_Error( 'buffer_open', "We couldn't get the buffer it is currently checked out", 400 ); |
|
|
|
|
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
if ( ! is_object( $buffer ) ) { |
228
|
|
|
return new WP_Error( 'buffer_non-object', 'Buffer is not an object', 400 ); |
|
|
|
|
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
Settings::set_is_syncing( true ); |
232
|
|
|
list( $items_to_send, $skipped_items_ids ) = $sender->get_items_to_send( $buffer, $args['encode'] ); |
233
|
|
|
Settings::set_is_syncing( false ); |
234
|
|
|
|
235
|
|
|
return array( |
236
|
|
|
'buffer_id' => $buffer->id, |
237
|
|
|
'items' => $items_to_send, |
238
|
|
|
'skipped_items' => $skipped_items_ids, |
239
|
|
|
'codec' => $args['encode'] ? $sender->get_codec()->name() : null, |
240
|
|
|
'sent_timestamp' => time(), |
241
|
|
|
); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
public $items = []; |
245
|
|
|
|
246
|
|
|
public function jetpack_sync_send_data_listener() { |
247
|
|
|
foreach ( func_get_args()[0] as $key => $item ) { |
248
|
|
|
$this->items[ $key ] = $item; |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
public function immediate_full_sync_pull( $number_of_items ) { |
253
|
|
|
// try to give ourselves as much time as possible. |
254
|
|
|
set_time_limit( 0 ); |
255
|
|
|
|
256
|
|
|
$original_send_data_cb = array( 'Automattic\Jetpack\Sync\Actions', 'send_data' ); |
257
|
|
|
$temp_send_data_cb = array( $this, 'jetpack_sync_send_data_listener' ); |
258
|
|
|
|
259
|
|
|
Sender::get_instance()->set_enqueue_wait_time( 0 ); |
260
|
|
|
remove_filter( 'jetpack_sync_send_data', $original_send_data_cb ); |
261
|
|
|
add_filter( 'jetpack_sync_send_data', $temp_send_data_cb, 10, 6 ); |
262
|
|
|
Sender::get_instance()->do_full_sync(); |
263
|
|
|
remove_filter( 'jetpack_sync_send_data', $temp_send_data_cb ); |
264
|
|
|
add_filter( 'jetpack_sync_send_data', $original_send_data_cb, 10, 6 ); |
265
|
|
|
|
266
|
|
|
return array( |
267
|
|
|
'items' => $this->items, |
268
|
|
|
'codec' => Sender::get_instance()->get_codec()->name(), |
269
|
|
|
'sent_timestamp' => time(), |
270
|
|
|
'status' => Actions::get_sync_status(), |
271
|
|
|
); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
protected function get_buffer( $queue, $number_of_items ) { |
275
|
|
|
$start = time(); |
276
|
|
|
$max_duration = 5; // this will try to get the buffer |
277
|
|
|
|
278
|
|
|
$buffer = $queue->checkout( $number_of_items ); |
279
|
|
|
$duration = time() - $start; |
280
|
|
|
|
281
|
|
|
while( is_wp_error( $buffer ) && $duration < $max_duration ) { |
282
|
|
|
sleep( 2 ); |
283
|
|
|
$duration = time() - $start; |
284
|
|
|
$buffer = $queue->checkout( $number_of_items ); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
if ( $buffer === false ) { |
288
|
|
|
return new WP_Error( 'queue_size', 'The queue is empty and there is nothing to send', 400 ); |
|
|
|
|
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
return $buffer; |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
class Jetpack_JSON_API_Sync_Close_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { |
296
|
|
|
protected function result() { |
297
|
|
|
$request_body = $this->input(); |
298
|
|
|
$queue_name = $this->validate_queue( $request_body['queue'] ); |
299
|
|
|
|
300
|
|
|
if ( is_wp_error( $queue_name ) ) { |
301
|
|
|
return $queue_name; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
if ( ! isset( $request_body['buffer_id'] ) ) { |
305
|
|
|
return new WP_Error( 'missing_buffer_id', 'Please provide a buffer id', 400 ); |
|
|
|
|
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
if ( ! isset( $request_body['item_ids'] ) || ! is_array( $request_body['item_ids'] ) ) { |
309
|
|
|
return new WP_Error( 'missing_item_ids', 'Please provide a list of item ids in the item_ids argument', 400 ); |
|
|
|
|
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
//Limit to A-Z,a-z,0-9,_,- |
313
|
|
|
$request_body ['buffer_id'] = preg_replace( '/[^A-Za-z0-9]/', '', $request_body['buffer_id'] ); |
314
|
|
|
$request_body['item_ids'] = array_filter( array_map( array( 'Jetpack_JSON_API_Sync_Close_Endpoint', 'sanitize_item_ids' ), $request_body['item_ids'] ) ); |
315
|
|
|
|
316
|
|
|
$queue = new Queue( $queue_name ); |
317
|
|
|
|
318
|
|
|
$items = $queue->peek_by_id( $request_body['item_ids'] ); |
319
|
|
|
|
320
|
|
|
/** This action is documented in packages/sync/src/modules/Full_Sync.php */ |
321
|
|
|
$full_sync_module = Modules::get_module( 'full-sync' ); |
322
|
|
|
|
323
|
|
|
$full_sync_module->update_sent_progress_action( $items ); |
324
|
|
|
|
325
|
|
|
$buffer = new Queue_Buffer( $request_body['buffer_id'], $request_body['item_ids'] ); |
326
|
|
|
$response = $queue->close( $buffer, $request_body['item_ids'] ); |
|
|
|
|
327
|
|
|
|
328
|
|
|
if ( is_wp_error( $response ) ) { |
329
|
|
|
return $response; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
return array( |
333
|
|
|
'success' => $response, |
334
|
|
|
'status' => Actions::get_sync_status(), |
335
|
|
|
); |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
protected static function sanitize_item_ids( $item ) { |
339
|
|
|
// lets not delete any options that don't start with jpsq_sync- |
340
|
|
|
if ( substr( $item, 0, 5 ) !== 'jpsq_' ) { |
341
|
|
|
return null; |
342
|
|
|
} |
343
|
|
|
//Limit to A-Z,a-z,0-9,_,-,. |
344
|
|
|
return preg_replace( '/[^A-Za-z0-9-_.]/', '', $item ); |
345
|
|
|
} |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
class Jetpack_JSON_API_Sync_Unlock_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { |
349
|
|
|
protected function result() { |
350
|
|
|
$args = $this->input(); |
351
|
|
|
|
352
|
|
|
if ( ! isset( $args['queue'] ) ) { |
353
|
|
|
return new WP_Error( 'invalid_queue', 'Queue name is required', 400 ); |
|
|
|
|
354
|
|
|
} |
355
|
|
|
|
356
|
|
View Code Duplication |
if ( ! in_array( $args['queue'], array( 'sync', 'full_sync' ) ) ) { |
357
|
|
|
return new WP_Error( 'invalid_queue', 'Queue name should be sync or full_sync', 400 ); |
|
|
|
|
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
$queue = new Queue( $args['queue'] ); |
361
|
|
|
|
362
|
|
|
// False means that there was no lock to delete. |
363
|
|
|
$response = $queue->unlock(); |
364
|
|
|
return array( |
365
|
|
|
'success' => $response |
366
|
|
|
); |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
class Jetpack_JSON_API_Sync_Object_Id_Range extends Jetpack_JSON_API_Sync_Endpoint { |
371
|
|
|
protected function result() { |
372
|
|
|
$args = $this->query_args(); |
373
|
|
|
|
374
|
|
|
$module_name = $args['sync_module']; |
375
|
|
|
$batch_size = $args['batch_size']; |
376
|
|
|
|
377
|
|
|
if ( ! $this->is_valid_sync_module( $module_name ) ) { |
378
|
|
|
return new WP_Error( 'invalid_module', 'This sync module cannot be used to calculate a range.', 400 ); |
|
|
|
|
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
$module = Modules::get_module( $module_name ); |
382
|
|
|
|
383
|
|
|
return array( |
384
|
|
|
'ranges' => $module->get_min_max_object_ids_for_batches( $batch_size ), |
385
|
|
|
); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
protected function is_valid_sync_module( $module_name ) { |
389
|
|
|
return in_array( |
390
|
|
|
$module_name, |
391
|
|
|
array( |
392
|
|
|
'comments', |
393
|
|
|
'posts', |
394
|
|
|
'terms', |
395
|
|
|
'term_relationships', |
396
|
|
|
'users', |
397
|
|
|
), |
398
|
|
|
true |
399
|
|
|
); |
400
|
|
|
} |
401
|
|
|
} |
402
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.