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 +556 -96 1.0.2trunk View file →
@@ -30,13 +30,39 @@
30 30 */
31 31
32 32 public function register_routes() {
33 33 $this->add_route( '/verifyCredentials', 'verifyCredentials', 'POST' );
34 - $this->add_route( '/verifyBucket', 'verifyBucket', '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' );
38 + $this->add_route( '/permissions/delete', 'verifyObjectDelete', 'POST' );
39 + $this->add_route( '/permissions/read', 'verifyRead', 'POST' );
40 +
41 + $this->add_route( '/security/get_security', 'getBucketSecuritySettings', 'POST' );
42 + $this->add_route( '/security/block_public_access', 'changePublicAccess', 'POST' );
43 + $this->add_route( '/security/object_ownership_enforce', 'changeObjectOwnership', 'POST' );
44 +
45 + $this->add_route( '/getSettings', 'getSettings' );
35 46 $this->add_route( '/saveConfig', 'saveConfig', 'POST' );
36 - $this->add_route( '/createBucket', 'createBucket', 'POST' );
37 - $this->add_route( '/getSettings', 'getSettings' );
38 - $this->add_route( '/uploadCredentials', 'uploadCredentials', 'POST' );
47 + $this->add_route( '/refreshCounts', 'refreshCounts', 'POST' );
48 + $this->add_route( '/getErrorLogs', 'getErrorLogs', 'POST' );
49 + $this->add_route( '/clearErrorLogs', 'clearErrorLogs', 'POST' );
50 +
51 + // Upgrade Database Routes
52 + $this->add_route( '/upgrade/status', 'getUpgradeStatus' );
53 + $this->add_route( '/upgrade/start', 'startUpgrade', 'POST' );
54 +
55 + // Sync Routes
56 + $this->add_route( '/sync/status', 'getStatus' );
57 + $this->add_route( '/sync/start', 'startSync', 'POST' );
58 + $this->add_route( '/sync/pause', 'pauseSync', 'POST' );
59 + $this->add_route( '/sync/resume', 'resumeSync', 'POST' );
60 + $this->add_route( '/sync/stop', 'stopSync', 'POST' );
61 + $this->add_route( '/sync/tick', 'tickSync', 'POST' );
62 + $this->add_route( '/sync/setMethod', 'setSyncMethod', 'POST' );
63 +
64 + $this->add_route( '/sync/retrySingle', 'retrySingle', 'POST' );
39 65 }
40 66
41 67 /**
42 68 * Verify the Service Credentials
@@ -41,18 +67,16 @@
41 67 /**
42 68 * Verify the Service Credentials
43 69 */
44 70 public function verifyCredentials( $data ) {
45 - global $wpmcsService;
46 - return new WP_REST_Response( $wpmcsService->verifyCredentials($data->get_params()), 200 );
71 + return new WP_REST_Response( Service::instance()->verifyCredentials($data->get_params()), 200 );
47 72 }
48 73
49 74 /**
50 - * Verify the Bucket Credentials
75 + * Verify the Bucket exist
51 76 */
52 - public function verifyBucket( $data ) {
53 - global $wpmcsService;
54 - return new WP_REST_Response( $wpmcsService->verifyBucket($data->get_params()), 200 );
77 + public function verifyBucketExist( $data ) {
78 + return new WP_REST_Response( Service::instance()->verifyBucketExist($data->get_params()), 200 );
55 79 }
56 80
57 81 /**
58 82 * Create the bucket
@@ -57,23 +81,97 @@
57 81 /**
58 82 * Create the bucket
59 83 */
60 84 public function createBucket( $data ) {
61 - global $wpmcsService;
62 - return new WP_REST_Response( $wpmcsService->createBucket($data->get_params()), 200 );
85 + return new WP_REST_Response( Service::instance()->createBucket($data->get_params()), 200 );
63 86 }
64 87
65 88 /**
89 + * Saved connection status checks (storage, CDN).
90 + * @since 1.3.11
91 + */
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 );
96 + }
97 +
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 + /**
111 + * Verify the Bucket delete Permission
112 + */
113 + public function verifyObjectDelete( $data ) {
114 + return new WP_REST_Response( Service::instance()->verifyObjectDeletePermission($data->get_params()), 200 );
115 + }
116 +
117 + /**
118 + * Verify delivery read access (legacy route).
119 + * @since 1.3.11
120 + */
121 + public function verifyRead( $data ) {
122 + return new WP_REST_Response( Service::instance()->verifyStatus( 'cdn' ), 200 );
123 + }
124 +
125 + /**
126 + * Get the Security Settings
127 + */
128 + public function getBucketSecuritySettings( $data ) {
129 + return new WP_REST_Response( Service::instance()->getBucketSecuritySettings($data->get_params()), 200 );
130 + }
131 +
132 +
133 + /**
134 + * Change the Public Access
135 + */
136 + public function changePublicAccess( $data ) {
137 + return new WP_REST_Response( Service::instance()->changePublicAccess($data->get_params()), 200 );
138 + }
139 +
140 +
141 + /**
142 + * Change the Object Ownership
143 + */
144 + public function changeObjectOwnership( $data ) {
145 + return new WP_REST_Response( Service::instance()->changeObjectOwnership($data->get_params()), 200 );
146 + }
147 +
148 + /**
66 149 * Get Settings
67 150 */
68 151 public function getSettings( ) {
152 + // Fetch and update media counts for make sure they are up to date
153 + Counter::fetch_and_update();
154 +
69 155 $data = [
70 - 'credentials' => Utils::get_credentials(),
71 - 'common' => [
72 - 'version' => WPMCS_VERSION,
73 - 'uploaded' => Counter::get( 'uploaded' ),
156 + 'credentials' => Utils::get_credentials( '', [], true ),
157 + 'serviceEnabled' => Utils::is_service_enabled(),
158 + 'serviceError' => Utils::get_service_configuration_error(),
159 + 'common' => [
160 + 'version' => defined('WPMCS_PRO_VERSION') ? WPMCS_PRO_VERSION : WPMCS_VERSION,
161 + 'counts' => [
162 + 'all' => Counter::get_count(),
163 + 'categorized' => $this->getSortedMediaCounts()
164 + ],
165 + 'status' => Utils::get_status('', false),
74 166 'settings' => Utils::get_settings(),
75 - ]
167 + ],
168 + 'sync' => Sync::instance()->get_status(),
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 + ] : [],
76 174 ];
77 175
78 176 return new WP_REST_Response( $data, 200 );
79 177 }
@@ -91,27 +189,63 @@
91 189 $action = isset($saveData['action']) ? $saveData['action'] : false;
92 190 if($action === false) return new WP_REST_Response( $result, 200 );
93 191
94 192 $service = isset($saveData['service']) ? $saveData['service'] : false;
193 + $serviceLabel = isset($saveData['serviceLabel']) ? $saveData['serviceLabel'] : '';
194 + $cdn = isset($saveData['cdn']) ? $saveData['cdn'] : [];
95 195 $config = isset($saveData['config']) ? $saveData['config'] : false;
196 + $configSource = isset($saveData['configSource']) ? $saveData['configSource'] : 'database';
96 197 $bucketConfig = isset($saveData['bucketConfig']) ? $saveData['bucketConfig'] : [];
198 + $security = isset($saveData['security']) ? $saveData['security'] : [];
97 199 $settings = isset($saveData['settings']) ? $saveData['settings'] : [];
98 200
99 201 $serviceOk = true;
100 202 $settingsOk = true;
203 +
204 + if( $action === 'cdn' ){
205 + $existing = Utils::get_credentials();
206 + $existing['cdn'] = $cdn;
207 +
208 + $updated = Utils::update_option('credentials', $existing, Schema::getConstant('GLOBAL_SETTINGS_KEY'));
209 + $updatedSettings = Utils::update_option('settings', $settings, Schema::getConstant('GLOBAL_SETTINGS_KEY'));
210 +
211 + if(!$updated) $serviceOk = false;
212 + if(!$updatedSettings) $settingsOk = false;
213 + }
214 +
215 +
101 216 if($action === 'all' || $action === 'service'){
102 - 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 );
103 220
104 221 $updated = Utils::update_option(
105 222 'credentials',
106 223 [
107 224 'service' => $service,
108 - 'config' => $config,
109 - 'bucketConfig' => $bucketConfig
225 + 'serviceLabel' => $serviceLabel,
226 + 'cdn' => $cdn,
227 + 'config' => $configSource === 'config' ? [] : $config,
228 + 'configSource' => $configSource,
229 + 'bucketConfig' => $bucketConfig,
230 + 'security' => $security
110 231 ],
111 232 Schema::getConstant('GLOBAL_SETTINGS_KEY')
112 233 );
113 234 if(!$updated) $serviceOk = false;
235 +
236 + if($serviceOk) {
237 + Utils::set_status('cdnRead', [
238 + 'status' => false,
239 + 'message' => '',
240 + 'lastChecked' => null
241 + ]);
242 + Utils::set_status('storageCredentials', [
243 + 'status' => false,
244 + 'message' => '',
245 + 'lastChecked' => null
246 + ]);
247 + }
114 248 }
115 249
116 250 if(($action === 'all' || $action === 'settings')) {
117 251 $updatedSettings = Utils::update_option('settings', $settings, Schema::getConstant('GLOBAL_SETTINGS_KEY'));
@@ -117,9 +251,23 @@
117 251 $updatedSettings = Utils::update_option('settings', $settings, Schema::getConstant('GLOBAL_SETTINGS_KEY'));
118 252 if(!$updatedSettings) $settingsOk = false;
119 253 }
120 254
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 + }
121 260
261 + if($action === 'all' || $action === 'settings') {
262 + Utils::clear_all_content_meta(false, false);
263 + }
264 +
265 + Cache::flush_object_cache();
266 + Counter::instance()->fetch_and_update();
267 + }
268 +
269 +
122 270 if($serviceOk && $settingsOk) {
123 271 $result = [
124 272 'success' => true,
125 273 'message' => esc_html__('Configuration saved successfully', 'media-cloud-sync')
@@ -130,98 +278,410 @@
130 278 }
131 279
132 280
133 281 /**
134 - * Upload Credentials
135 - * @since 1.0.0
136 - */
137 - public function uploadCredentials($request) {
138 - if (isset($_FILES['file']) && !empty($_FILES['file'])) {
139 - $config_file = $_FILES['file'];
140 - if (isset($config_file['type']) && $config_file['type'] == 'application/json') {
141 - if (file_exists($config_file["tmp_name"])) {
282 + * Refresh the Media Counts
283 + *
284 + */
285 + public function refreshCounts( ) {
286 + Cache::flush_object_cache();
287 + Integration::instance()->clear_all_meta();
288 + Counter::instance()->fetch_and_update();
142 289
143 - add_filter( 'upload_dir', [ $this, 'change_file_upload_dir' ]);
144 - add_filter( 'mime_types', [ $this, 'add_custom_mime_type_json' ]);
290 + $result = [
291 + 'success' => true,
292 + 'counts' => [
293 + 'all' => Counter::get_count(),
294 + 'categorized' => $this->getSortedMediaCounts()
295 + ],
296 + 'message' => esc_html__('Media counts refreshed successfully', 'media-cloud-sync')
297 + ];
298 + return new WP_REST_Response( $result, 200 );
299 + }
145 300
146 - if ( ! function_exists( 'wp_handle_upload' ) ) {
147 - require_once( ABSPATH . 'wp-admin/includes/file.php' );
148 - }
149 301
150 - $upload_overrides = [
151 - 'test_form' => false,
152 - 'test_type' => true,
153 - 'mimes' => [ 'json'=>'application/json' ]
154 - ];
302 + /**
303 + * Start Upgrade
304 + * @since 1.3.6
305 + */
306 + public function startUpgrade( $request ) {
307 + $result = Upgrade::instance()->start_upgrade();
308 + return new WP_REST_Response( $result, 200 );
309 + }
155 310
156 - $movefile = wp_handle_upload( $config_file, $upload_overrides );
157 311
158 - remove_filter( 'upload_dir', [ $this, 'change_file_upload_dir' ]);
159 - remove_filter( 'mime_types', [ $this, 'add_custom_mime_type_json' ]);
312 + /**
313 + * Get Upgrade Status
314 + * @since 1.3.6
315 + */ public function getUpgradeStatus( $request ) {
316 + $result = Upgrade::instance()->get_progress();
317 + return new WP_REST_Response( $result, 200 );
318 + }
160 319
161 - if ($movefile && !isset($movefile['error'])) {
162 - $result = [
163 - 'success' => true,
164 - 'file_path' => $movefile['file'],
165 - 'file_name' => basename($movefile['file']),
166 - ];
167 - return new WP_REST_Response($result, 200);
168 - } else {
169 - $result = [
170 - 'success' => false,
171 - 'message' => $movefile['error'],
172 - ];
173 - return new WP_REST_Response($result, 200);
174 - }
175 - }
176 - } else {
177 - $result = [
178 - 'success' => false,
179 - 'message' => esc_html__('Invalid file format', 'media-cloud-sync'),
180 - ];
181 - return new WP_REST_Response($result, 200);
182 - }
183 - } else {
184 - $result = [
185 - 'success' => false,
186 - 'message' => esc_html__('Insufficient data. Please try again', 'media-cloud-sync'),
320 +
321 + /**
322 + * Get Media Error Logs
323 + * @since 1.2.13
324 + */
325 + public function getErrorLogs( $request ) {
326 + $params = $request->get_params();
327 + $type = isset($params['type']) ? $params['type'] : 'all';
328 + $page = isset($params['page']) ? $params['page'] : null;
329 + $per_page = isset($params['per_page']) ? $params['per_page'] : null;
330 +
331 + if($type !== 'all') {
332 + $logs = Logger::instance()->get_logs_by_type($type, $page, $per_page);
333 + } else {
334 + $logs = Logger::instance()->get_all_logs($page, $per_page);
335 + }
336 +
337 + $result = [
338 + 'success' => true,
339 + 'logs' => $logs,
340 + 'message' => esc_html__('Error logs fetched successfully', 'media-cloud-sync')
341 + ];
342 + return new WP_REST_Response( $result, 200 );
343 + }
344 +
345 +
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 + /**
371 + * Get Status
372 + * @since 1.2.13
373 + */
374 + public function getStatus( $request ) {
375 + // Fetch and update media counts for make sure they are up to date
376 + $action = isset($request->get_params()['action']) ? $request->get_params()['action'] : 'all';
377 + $status = Sync::instance()->get_status();
378 +
379 + if($action !== 'all') {
380 + $status = isset($status[$action]) ? $status[$action] : [];
381 + }
382 +
383 + $result = [
384 + 'success' => true,
385 + 'status' => $status,
386 + 'counts' => [
387 + 'all' => Counter::get_count(),
388 + 'categorized' => $this->getSortedMediaCounts()
389 + ],
390 + 'message' => esc_html__('Status fetched successfully', 'media-cloud-sync')
391 + ];
392 +
393 + return new WP_REST_Response( $result, 200 );
394 + }
395 +
396 +
397 + /**
398 + * Start Sync
399 + * @since 1.2.13
400 + */
401 + public function startSync( $data ) {
402 + $action = isset($data->get_params()['action']) ? $data->get_params()['action'] : '';
403 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
404 + $result = [
405 + 'success' => false,
406 + 'message' => esc_html__('Invalid action', 'media-cloud-sync')
187 407 ];
188 - return new WP_REST_Response($result, 200);
189 - }
190 - }
408 + return new WP_REST_Response( $result, 200 );
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 + }
417 + $status = Sync::instance()->start($action);
191 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 +
425 + $result = [
426 + 'success' => true,
427 + 'status' => $status,
428 + 'message' => esc_html__('Sync started successfully', 'media-cloud-sync')
429 + ];
430 + return new WP_REST_Response( $result, 200 );
431 + }
432 +
433 +
192 434 /**
193 - * Change File Upload Directory
194 - * @since 1.0.0
195 - */
196 - public function change_file_upload_dir($upload) {
197 - $wpmcs_path = '/' . Schema::getConstant('UPLOADS');
198 - $path = $upload['basedir'] . $wpmcs_path;
199 - $url = $upload['baseurl'] . $wpmcs_path;
435 + * Pause Sync
436 + * @since 1.2.13
437 + */
438 + public function pauseSync( $data ) {
439 + $action = isset($data->get_params()['action']) ? $data->get_params()['action'] : '';
440 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
441 + $result = [
442 + 'success' => false,
443 + 'message' => esc_html__('Invalid action', 'media-cloud-sync')
444 + ];
445 + return new WP_REST_Response( $result, 200 );
446 + }
447 + $status = Sync::instance()->pause($action);
200 448
201 - if (!is_dir($path)) {
202 - do_action( $this->token.'_create_plugin_dir' );
203 - }
449 + $result = [
450 + 'success' => true,
451 + 'status' => $status,
452 + 'message' => esc_html__('Sync paused successfully', 'media-cloud-sync')
453 + ];
454 + return new WP_REST_Response( $result, 200 );
455 + }
204 456
205 - $upload['subdir'] = $wpmcs_path;
206 - $upload['path'] = $upload['basedir'] . $wpmcs_path;
207 - $upload['url'] = $upload['baseurl'] . $wpmcs_path;
208 457
209 - return $upload;
210 - }
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']) : '';
211 466
212 - /**
213 - * Add Custom Mime Type JSON
214 - * @since 1.0.0
215 - */
216 - public function add_custom_mime_type_json($mimes) {
217 - $mimes['json'] = 'application/json';
218 - // Return the array back to the function with our added mime type.
219 - return $mimes;
220 - }
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 + }
221 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 + }
222 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 +
223 490 /**
491 + * Resume Sync
492 + * @since 1.2.13
493 + */
494 + public function resumeSync( $data ) {
495 + $action = isset($data->get_params()['action']) ? $data->get_params()['action'] : '';
496 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
497 + $result = [
498 + 'success' => false,
499 + 'message' => esc_html__('Invalid action', 'media-cloud-sync')
500 + ];
501 + return new WP_REST_Response( $result, 200 );
502 + }
503 + $status = Sync::instance()->resume($action);
504 +
505 + $result = [
506 + 'success' => true,
507 + 'status' => $status,
508 + 'message' => esc_html__('Sync resumed successfully', 'media-cloud-sync')
509 + ];
510 + return new WP_REST_Response( $result, 200 );
511 + }
512 +
513 +
514 + /**
515 + * Stop Sync
516 + * @since 1.2.13
517 + */
518 + public function stopSync( $data ) {
519 + $action = isset($data->get_params()['action']) ? $data->get_params()['action'] : '';
520 + if ( ! Sync::instance()->get_class_by_action( $action ) ) {
521 + $result = [
522 + 'success' => false,
523 + 'message' => esc_html__('Invalid action', 'media-cloud-sync')
524 + ];
525 + return new WP_REST_Response( $result, 200 );
526 + }
527 + $status = Sync::instance()->stop($action);
528 +
529 + $result = [
530 + 'success' => true,
531 + 'status' => $status,
532 + 'counts' => [
533 + 'all' => Counter::get_count(),
534 + 'categorized' => $this->getSortedMediaCounts()
535 + ],
536 + 'message' => esc_html__('Sync stopped successfully', 'media-cloud-sync')
537 + ];
538 + return new WP_REST_Response( $result, 200 );
539 + }
540 +
541 +
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 + /**
602 + * Retry a Media Sync Error
603 + */
604 + public function retrySingle( $data ) {
605 + $params = $data->get_params();
606 + $id = isset($params['id']) ? $params['id'] : '';
607 + $source_type = isset($params['source_type']) ? $params['source_type'] : 'media_library';
608 + $type = isset($params['type']) ? $params['type'] : '';
609 + $status = false;
610 + if(empty($id) || empty($source_type) || empty($type)) {
611 + $result = [
612 + 'success' => false,
613 + 'message' => esc_html__('Invalid parameters', 'media-cloud-sync')
614 + ];
615 + return new WP_REST_Response( $result, 200 );
616 + }
617 + // Retry Sync
618 + $handler = Sync::instance()->get_class_by_action($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 );
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);
634 +
635 + $status = Utils::is_empty(Logger::instance()->get_log($type, $id, $source_type)) ? true : false;
636 +
637 + $result = [
638 + 'success' => $status,
639 + 'message' => $status ? esc_html__('Media synced successfully', 'media-cloud-sync') : esc_html__('Media sync failed', 'media-cloud-sync')
640 + ];
641 + return new WP_REST_Response( $result, 200 );
642 + }
643 +
644 +
645 + /**
646 + * Get Sorted Media Counts
647 + * Sorts media counts by source type
648 + * @since 1.2.13
649 + */
650 + private function getSortedMediaCounts() {
651 + $sorted_counts = [];
652 + $detailed_counts = Counter::get_count( 'all', false );
653 + if(!empty($detailed_counts)) {
654 + $source_labels = Integration::$source_labels;
655 + if(!empty($source_labels)) {
656 + $prefixes = array_keys($source_labels);
657 + foreach($detailed_counts as $source_type => $counts) {
658 + $found_prefix = false;
659 + foreach($prefixes as $prefix) {
660 + $found_prefix = strpos($source_type, $prefix) === 0 ? $prefix : false;
661 + if($found_prefix !== false) break;
662 + }
663 +
664 + if($found_prefix !== false) {
665 + $sorted_counts[$found_prefix] = [
666 + 'label' => $source_labels[$found_prefix],
667 + 'uploaded' => isset( $sorted_counts[ $found_prefix ]['uploaded'] )
668 + ? $sorted_counts[ $found_prefix ]['uploaded'] + $counts['uploaded']
669 + : $counts['uploaded'],
670 + 'total' => isset( $sorted_counts[ $found_prefix ]['total'] )
671 + ? $sorted_counts[ $found_prefix ]['total'] + $counts['total']
672 + : $counts['total']
673 + ];
674 + }
675 + }
676 + }
677 + }
678 +
679 + return $sorted_counts;
680 + }
681 +
682 +
683 + /**
224 684 * Helper function to create Adding Route
225 685 * @since 1.0.0
226 686 */
227 687 private function add_route( $slug, $callBack, $method = 'GET' ) {
@@ -238,12 +698,12 @@
238 698 /**
239 699 * Permission Callback
240 700 **/
241 701 public function getPermission() {
242 - if ( current_user_can( 'administrator' ) ) {
702 + if ( current_user_can( 'manage_options' ) ) {
243 703 return true;
244 704 } else {
245 - return true;
705 + return false;
246 706 }
247 707 }
248 708
249 709 /**