PluginProbe
Media Cloud Sync / trunk
Media Cloud Sync vtrunk
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
← All changes | includes/api.php +211 -37 1.3.10trunk View file →
@@ -30,13 +30,14 @@
30 30 */
31 31
32 32 public function register_routes() {
33 33 $this->add_route( '/verifyCredentials', 'verifyCredentials', 'POST' );
34 - $this->add_route( '/permissions/checkExist', 'verifyBucketExist', 'POST' );
35 - $this->add_route( '/permissions/addNewBucket', 'createBucket', 'POST' );
36 - $this->add_route( '/permissions/write', 'verifyObjectWrite', 'POST' );
34 + $this->add_route( '/bucket/checkExist', 'verifyBucketExist', 'POST' );
35 + $this->add_route( '/bucket/addNew', 'createBucket', 'POST' );
36 + $this->add_route( '/permissions/status', 'verifyStatus', 'POST' );
37 + $this->add_route( '/permissions/write', 'verifyWrite', 'POST' );
37 38 $this->add_route( '/permissions/delete', 'verifyObjectDelete', 'POST' );
38 - $this->add_route( '/permissions/read', 'verifyObjectRead', 'POST' );
39 + $this->add_route( '/permissions/read', 'verifyRead', 'POST' );
39 40
40 41 $this->add_route( '/security/get_security', 'getBucketSecuritySettings', 'POST' );
41 42 $this->add_route( '/security/block_public_access', 'changePublicAccess', 'POST' );
42 43 $this->add_route( '/security/object_ownership_enforce', 'changeObjectOwnership', 'POST' );
@@ -44,8 +45,9 @@
44 45 $this->add_route( '/getSettings', 'getSettings' );
45 46 $this->add_route( '/saveConfig', 'saveConfig', 'POST' );
46 47 $this->add_route( '/refreshCounts', 'refreshCounts', 'POST' );
47 48 $this->add_route( '/getErrorLogs', 'getErrorLogs', 'POST' );
49 + $this->add_route( '/clearErrorLogs', 'clearErrorLogs', 'POST' );
48 50
49 51 // Upgrade Database Routes
50 52 $this->add_route( '/upgrade/status', 'getUpgradeStatus' );
51 53 $this->add_route( '/upgrade/start', 'startUpgrade', 'POST' );
@@ -55,8 +57,10 @@
55 57 $this->add_route( '/sync/start', 'startSync', 'POST' );
56 58 $this->add_route( '/sync/pause', 'pauseSync', 'POST' );
57 59 $this->add_route( '/sync/resume', 'resumeSync', 'POST' );
58 60 $this->add_route( '/sync/stop', 'stopSync', 'POST' );
61 + $this->add_route( '/sync/tick', 'tickSync', 'POST' );
62 + $this->add_route( '/sync/setMethod', 'setSyncMethod', 'POST' );
59 63
60 64 $this->add_route( '/sync/retrySingle', 'retrySingle', 'POST' );
61 65 }
62 66
@@ -80,17 +84,31 @@
80 84 public function createBucket( $data ) {
81 85 return new WP_REST_Response( Service::instance()->createBucket($data->get_params()), 200 );
82 86 }
83 87
84 -
85 88 /**
86 - * Verify the Bucket Write Permission
89 + * Saved connection status checks (storage, CDN).
90 + * @since 1.3.11
87 91 */
88 - public function verifyObjectWrite( $data ) {
89 - return new WP_REST_Response( Service::instance()->verifyObjectWritePermission($data->get_params()), 200 );
92 + public function verifyStatus( $data ) {
93 + $params = $data->get_params();
94 + $check = isset( $params['check'] ) ? sanitize_text_field( $params['check'] ) : 'storage';
95 + return new WP_REST_Response( Service::instance()->verifyStatus( $check ), 200 );
90 96 }
91 97
92 98 /**
99 + * Verify bucket write permission (configure wizard) or saved storage status (legacy route).
100 + * @since 1.3.11
101 + */
102 + public function verifyWrite( $data ) {
103 + $params = $data->get_params();
104 + if ( !empty( $params['service'] ) ) {
105 + return new WP_REST_Response( Service::instance()->verifyObjectWritePermission( $params ), 200 );
106 + }
107 + return new WP_REST_Response( Service::instance()->verifyStatus( 'storage' ), 200 );
108 + }
109 +
110 + /**
93 111 * Verify the Bucket delete Permission
94 112 */
95 113 public function verifyObjectDelete( $data ) {
96 114 return new WP_REST_Response( Service::instance()->verifyObjectDeletePermission($data->get_params()), 200 );
@@ -96,12 +114,13 @@
96 114 return new WP_REST_Response( Service::instance()->verifyObjectDeletePermission($data->get_params()), 200 );
97 115 }
98 116
99 117 /**
100 - * Verify the Bucket Read Permission
118 + * Verify delivery read access (legacy route).
119 + * @since 1.3.11
101 120 */
102 - public function verifyObjectRead( $data ) {
103 - return new WP_REST_Response( Service::instance()->verifyObjectReadPermission(), 200 );
121 + public function verifyRead( $data ) {
122 + return new WP_REST_Response( Service::instance()->verifyStatus( 'cdn' ), 200 );
104 123 }
105 124
106 125 /**
107 126 * Get the Security Settings
@@ -133,10 +152,12 @@
133 152 // Fetch and update media counts for make sure they are up to date
134 153 Counter::fetch_and_update();
135 154
136 155 $data = [
137 - 'credentials' => Utils::get_credentials( '', [], true ),
138 - 'common' => [
156 + 'credentials' => Utils::get_credentials( '', [], true ),
157 + 'serviceEnabled' => Utils::is_service_enabled(),
158 + 'serviceError' => Utils::get_service_configuration_error(),
159 + 'common' => [
139 160 'version' => defined('WPMCS_PRO_VERSION') ? WPMCS_PRO_VERSION : WPMCS_VERSION,
140 161 'counts' => [
141 162 'all' => Counter::get_count(),
142 163 'categorized' => $this->getSortedMediaCounts()
@@ -145,8 +166,12 @@
145 166 'settings' => Utils::get_settings(),
146 167 ],
147 168 'sync' => Sync::instance()->get_status(),
148 169 'license' => defined('WPMCS_PRO_VERSION') ? Utils::get_safe_license_data() : [],
170 + 'file_manager' => defined('WPMCS_PRO_VERSION') ? [
171 + 'delete_enabled' => (bool) Utils::get_option('delete_enabled', false, Schema::getConstant('FILE_MANAGER_SETTINGS_KEY')),
172 + 'root_folder' => Utils::get_option('root_folder', '', Schema::getConstant('FILE_MANAGER_SETTINGS_KEY')),
173 + ] : [],
149 174 ];
150 175
151 176 return new WP_REST_Response( $data, 200 );
152 177 }
@@ -167,8 +192,9 @@
167 192 $service = isset($saveData['service']) ? $saveData['service'] : false;
168 193 $serviceLabel = isset($saveData['serviceLabel']) ? $saveData['serviceLabel'] : '';
169 194 $cdn = isset($saveData['cdn']) ? $saveData['cdn'] : [];
170 195 $config = isset($saveData['config']) ? $saveData['config'] : false;
196 + $configSource = isset($saveData['configSource']) ? $saveData['configSource'] : 'database';
171 197 $bucketConfig = isset($saveData['bucketConfig']) ? $saveData['bucketConfig'] : [];
172 198 $security = isset($saveData['security']) ? $saveData['security'] : [];
173 199 $settings = isset($saveData['settings']) ? $saveData['settings'] : [];
174 200
@@ -182,13 +208,16 @@
182 208 $updated = Utils::update_option('credentials', $existing, Schema::getConstant('GLOBAL_SETTINGS_KEY'));
183 209 $updatedSettings = Utils::update_option('settings', $settings, Schema::getConstant('GLOBAL_SETTINGS_KEY'));
184 210
185 211 if(!$updated) $serviceOk = false;
212 + if(!$updatedSettings) $settingsOk = false;
186 213 }
187 214
188 215
189 216 if($action === 'all' || $action === 'service'){
190 - if ( $service === false || empty($config) || empty($bucketConfig)) return new WP_REST_Response( $result, 200 );
217 + // In 'config' source mode the credentials live in the WPMCS_CONFIG constant,
218 + // so an empty config payload is expected and must not block the save.
219 + if ( $service === false || ($configSource !== 'config' && empty($config)) || empty($bucketConfig)) return new WP_REST_Response( $result, 200 );
191 220
192 221 $updated = Utils::update_option(
193 222 'credentials',
194 223 [
@@ -194,9 +223,10 @@
194 223 [
195 224 'service' => $service,
196 225 'serviceLabel' => $serviceLabel,
197 226 'cdn' => $cdn,
198 - 'config' => $config,
227 + 'config' => $configSource === 'config' ? [] : $config,
228 + 'configSource' => $configSource,
199 229 'bucketConfig' => $bucketConfig,
200 230 'security' => $security
201 231 ],
202 232 Schema::getConstant('GLOBAL_SETTINGS_KEY')
@@ -208,28 +238,32 @@
208 238 'status' => false,
209 239 'message' => '',
210 240 'lastChecked' => null
211 241 ]);
242 + Utils::set_status('storageCredentials', [
243 + 'status' => false,
244 + 'message' => '',
245 + 'lastChecked' => null
246 + ]);
212 247 }
213 248 }
214 249
215 250 if(($action === 'all' || $action === 'settings')) {
216 251 $updatedSettings = Utils::update_option('settings', $settings, Schema::getConstant('GLOBAL_SETTINGS_KEY'));
217 -
218 - // Clear content meta cache if settings updated
219 - Utils::clear_all_content_meta();
220 252 if(!$updatedSettings) $settingsOk = false;
221 253 }
222 254
223 - // If action is all, service or settings, clear caches and update counts
224 - if($action === 'all' || $action === 'service' || $action === 'settings') {
225 - //clear object cache
255 + // Clear plugin caches and refresh counts after any configuration save.
256 + if(in_array($action, ['all', 'service', 'settings', 'cdn'], true)) {
257 + if($action === 'all' || $action === 'service' || $action === 'settings') {
258 + Integration::instance()->clear_all_meta(false);
259 + }
260 +
261 + if($action === 'all' || $action === 'settings') {
262 + Utils::clear_all_content_meta(false, false);
263 + }
264 +
226 265 Cache::flush_object_cache();
227 - // clear all meta of attachments
228 - Integration::instance()->clear_all_meta();
229 - // clear all content meta
230 - Utils::clear_all_content_meta();
231 - // update counts
232 266 Counter::instance()->fetch_and_update();
233 267 }
234 268
235 269
@@ -309,8 +343,32 @@
309 343 }
310 344
311 345
312 346 /**
347 + * Clear all error logs for a sync type (e.g. stale entries from earlier failed attempts).
348 + * @since 1.3.13
349 + */
350 + public function clearErrorLogs( $request ) {
351 + $type = isset($request->get_params()['type']) ? $request->get_params()['type'] : '';
352 +
353 + if (empty($type) || !Sync::instance()->get_class_by_action($type)) {
354 + return new WP_REST_Response( [
355 + 'success' => false,
356 + 'message' => esc_html__('Invalid action', 'media-cloud-sync')
357 + ], 200 );
358 + }
359 +
360 + Logger::instance()->remove_logs_by_type($type);
361 +
362 + $result = [
363 + 'success' => true,
364 + 'message' => esc_html__('Error logs cleared', 'media-cloud-sync')
365 + ];
366 + return new WP_REST_Response( $result, 200 );
367 + }
368 +
369 +
370 + /**
313 371 * Get Status
314 372 * @since 1.2.13
315 373 */
316 374 public function getStatus( $request ) {
@@ -341,9 +399,9 @@
341 399 * @since 1.2.13
342 400 */
343 401 public function startSync( $data ) {
344 402 $action = isset($data->get_params()['action']) ? $data->get_params()['action'] : '';
345 - if(empty($action)) {
403 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
346 404 $result = [
347 405 'success' => false,
348 406 'message' => esc_html__('Invalid action', 'media-cloud-sync')
349 407 ];
@@ -348,10 +406,23 @@
348 406 'message' => esc_html__('Invalid action', 'media-cloud-sync')
349 407 ];
350 408 return new WP_REST_Response( $result, 200 );
351 409 }
410 + if(!Utils::is_service_enabled()) {
411 + $result = [
412 + 'success' => false,
413 + 'message' => Utils::get_service_configuration_error() ?: esc_html__('Storage is not configured correctly.', 'media-cloud-sync')
414 + ];
415 + return new WP_REST_Response( $result, 200 );
416 + }
352 417 $status = Sync::instance()->start($action);
353 418
419 + // Sync::start() passes a job's own precondition-failure array straight through
420 + // (e.g. no destination configured) instead of masking it as a started status.
421 + if (is_array($status) && isset($status['success']) && $status['success'] === false) {
422 + return new WP_REST_Response( $status, 200 );
423 + }
424 +
354 425 $result = [
355 426 'success' => true,
356 427 'status' => $status,
357 428 'message' => esc_html__('Sync started successfully', 'media-cloud-sync')
@@ -365,9 +436,9 @@
365 436 * @since 1.2.13
366 437 */
367 438 public function pauseSync( $data ) {
368 439 $action = isset($data->get_params()['action']) ? $data->get_params()['action'] : '';
369 - if(empty($action)) {
440 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
370 441 $result = [
371 442 'success' => false,
372 443 'message' => esc_html__('Invalid action', 'media-cloud-sync')
373 444 ];
@@ -384,14 +455,46 @@
384 455 }
385 456
386 457
387 458 /**
459 + * Save the sync method ('ajax'|'cron'|'mixed') preference for a sync type.
460 + * @since 1.3.13
461 + */
462 + public function setSyncMethod( $data ) {
463 + $params = $data->get_params();
464 + $action = isset($params['action']) ? sanitize_text_field($params['action']) : '';
465 + $method = isset($params['sync_method']) ? sanitize_text_field($params['sync_method']) : '';
466 +
467 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
468 + return new WP_REST_Response( [
469 + 'success' => false,
470 + 'message' => esc_html__('Invalid action', 'media-cloud-sync')
471 + ], 200 );
472 + }
473 +
474 + $saved = Sync::instance()->set_sync_method($action, $method);
475 + if(!$saved) {
476 + return new WP_REST_Response( [
477 + 'success' => false,
478 + 'message' => esc_html__('Invalid sync method', 'media-cloud-sync')
479 + ], 200 );
480 + }
481 +
482 + $result = [
483 + 'success' => true,
484 + 'sync_method' => $method,
485 + 'status' => Sync::instance()->get_class_by_action($action)->get_status(),
486 + ];
487 + return new WP_REST_Response( $result, 200 );
488 + }
489 +
490 + /**
388 491 * Resume Sync
389 492 * @since 1.2.13
390 493 */
391 494 public function resumeSync( $data ) {
392 495 $action = isset($data->get_params()['action']) ? $data->get_params()['action'] : '';
393 - if(empty($action)) {
496 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
394 497 $result = [
395 498 'success' => false,
396 499 'message' => esc_html__('Invalid action', 'media-cloud-sync')
397 500 ];
@@ -413,9 +516,9 @@
413 516 * @since 1.2.13
414 517 */
415 518 public function stopSync( $data ) {
416 519 $action = isset($data->get_params()['action']) ? $data->get_params()['action'] : '';
417 - if(empty($action)) {
520 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
418 521 $result = [
419 522 'success' => false,
420 523 'message' => esc_html__('Invalid action', 'media-cloud-sync')
421 524 ];
@@ -436,8 +539,67 @@
436 539 }
437 540
438 541
439 542 /**
543 + * Tick Sync — process a small batch synchronously, driving Ajax/Mixed sync mode.
544 + * @since 1.3.13
545 + */
546 + public function tickSync( $data ) {
547 + $params = $data->get_params();
548 + $action = isset($params['action']) ? $params['action'] : '';
549 +
550 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
551 + $result = [
552 + 'success' => false,
553 + 'message' => esc_html__('Invalid action', 'media-cloud-sync')
554 + ];
555 + return new WP_REST_Response( $result, 200 );
556 + }
557 +
558 + // Defense in depth — the frontend should never call this in free/unlicensed mode, but
559 + // the endpoint must not trust that, and must not silently no-op: an unlicensed call
560 + // needs a clear rejection so the frontend's tick loop stops instead of retrying forever.
561 + if ( ! Utils::is_pro_licensed() ) {
562 + $result = [
563 + 'success' => false,
564 + 'message' => esc_html__('Ajax sync requires an active Pro license', 'media-cloud-sync')
565 + ];
566 + return new WP_REST_Response( $result, 200 );
567 + }
568 +
569 + // Clamp regardless of what the frontend sends — the endpoint itself must enforce the
570 + // ceiling, since a batch of slow uploads processed sequentially in one request risks
571 + // exceeding reverse-proxy/webserver timeouts that process_iterations() can't see.
572 + $batch = isset($params['batch']) ? (int) $params['batch'] : 5;
573 + $batch = min(10, max(1, $batch));
574 +
575 + $status = Sync::instance()->tick($action, $batch);
576 +
577 + $result = [
578 + 'success' => true,
579 + 'status' => $status,
580 + 'message' => esc_html__('Sync ticked successfully', 'media-cloud-sync')
581 + ];
582 +
583 + // Deliberately omitted on every tick (this endpoint may be hit ~once/second while a
584 + // sync runs, and Counter::get_count() is a real query) — but the Dashboard's own
585 + // Statistics widget reads a *different* redux slice (common.counts) than the Sync
586 + // screen's progress (sync[action]), only refreshed via the slower ~2-3s useSyncPolling
587 + // cadence. That's an acceptable few-seconds lag while a sync is actively running, but
588 + // right when it finishes it becomes a visibly wrong, stuck-looking number on the
589 + // Dashboard until the next poll happens to catch up. Only pay for the query at that
590 + // one transition, not on every tick.
591 + if ( isset( $status['status'] ) && $status['status'] !== 'running' ) {
592 + $result['counts'] = [
593 + 'all' => Counter::get_count(),
594 + 'categorized' => $this->getSortedMediaCounts()
595 + ];
596 + }
597 + return new WP_REST_Response( $result, 200 );
598 + }
599 +
600 +
601 + /**
440 602 * Retry a Media Sync Error
441 603 */
442 604 public function retrySingle( $data ) {
443 605 $params = $data->get_params();
@@ -453,11 +615,23 @@
453 615 return new WP_REST_Response( $result, 200 );
454 616 }
455 617 // Retry Sync
456 618 $handler = Sync::instance()->get_class_by_action($type);
457 - if($handler) {
458 - $handler->retry_single($id, $source_type);
619 + if(!$handler) {
620 + $result = [
621 + 'success' => false,
622 + 'message' => esc_html__('Invalid action', 'media-cloud-sync')
623 + ];
624 + return new WP_REST_Response( $result, 200 );
459 625 }
626 + if(!method_exists($handler, 'retry_single')) {
627 + $result = [
628 + 'success' => false,
629 + 'message' => esc_html__('Retry is not available for this sync type.', 'media-cloud-sync')
630 + ];
631 + return new WP_REST_Response( $result, 200 );
632 + }
633 + $handler->retry_single($id, $source_type);
460 634
461 635 $status = Utils::is_empty(Logger::instance()->get_log($type, $id, $source_type)) ? true : false;
462 636
463 637 $result = [
@@ -482,9 +656,9 @@
482 656 $prefixes = array_keys($source_labels);
483 657 foreach($detailed_counts as $source_type => $counts) {
484 658 $found_prefix = false;
485 659 foreach($prefixes as $prefix) {
486 - $found_prefix = strpos($source_type, $prefix) === 0 ? $prefix : false;;
660 + $found_prefix = strpos($source_type, $prefix) === 0 ? $prefix : false;
487 661 if($found_prefix !== false) break;
488 662 }
489 663
490 664 if($found_prefix !== false) {
@@ -489,13 +663,13 @@
489 663
490 664 if($found_prefix !== false) {
491 665 $sorted_counts[$found_prefix] = [
492 666 'label' => $source_labels[$found_prefix],
493 - 'uploaded' => isset($sorted_counts[$found_prefix]['data']['uploaded'])
494 - ? $sorted_counts[$found_prefix]['data']['uploaded'] + $counts['uploaded']
667 + 'uploaded' => isset( $sorted_counts[ $found_prefix ]['uploaded'] )
668 + ? $sorted_counts[ $found_prefix ]['uploaded'] + $counts['uploaded']
495 669 : $counts['uploaded'],
496 - 'total' => isset($sorted_counts[$found_prefix]['data']['total'])
497 - ? $sorted_counts[$found_prefix]['data']['total'] + $counts['total']
670 + 'total' => isset( $sorted_counts[ $found_prefix ]['total'] )
671 + ? $sorted_counts[ $found_prefix ]['total'] + $counts['total']
498 672 : $counts['total']
499 673 ];
500 674 }
501 675 }