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/compatbility/compatibility.php +261 -25 1.2.12trunk View file →
@@ -9,10 +9,23 @@
9 9 private static $instance = null;
10 10 private $assets_url;
11 11 private $version;
12 12 private $token;
13 + /**
14 + * Whether to wait for the attachment metadata to be regenerated
15 + * before re-offloading the file.
16 + *
17 + * @var bool
18 + */
19 + public $wait_for_generate_attachment_metadata = false;
13 20
14 21 /**
22 + * Files to remove that were restored
23 + * @var array
24 + */
25 + private $restored_files = array();
26 +
27 + /**
15 28 * Compatibility constructor.
16 29 * @since 1.0.0
17 30 */
18 31 private function __construct() {
@@ -21,12 +34,26 @@
21 34 $this->token = WPMCS_TOKEN;
22 35
23 36 // Initialize setup
24 37 $this->init();
38 +
25 39 }
26 40
27 41 public function init() {
42 + if(!Utils::is_service_enabled()) {
43 + return;
44 + }
45 +
28 46 /*
47 + * Image Editor Handler
48 + * /wp-admin/includes/image-edit.php
49 + */
50 + add_filter( 'wpmcs_get_attached_file_noop', array( $this, 'image_editor_download_file' ), 10, 4 );
51 + add_filter( 'wpmcs_get_attached_file', array( $this, 'image_editor_download_file' ), 10, 4 );
52 + add_filter( 'wpmcs_get_attached_file', array( $this, 'customizer_crop_download_file' ), 10, 4 );
53 + add_filter( 'wpmcs_pre_update_item_additional_files_to_remove_from_server', [ $this, 'customizer_crop_remove_restored_files' ], 10, 5 );
54 +
55 + /*
29 56 * WP_Customize_Control
30 57 * /wp-includes/class-wp-customize_control.php
31 58 */
32 59 add_filter('attachment_url_to_postid', [$this, 'customizer_background_image'], 10, 2);
@@ -32,11 +59,11 @@
32 59 add_filter('attachment_url_to_postid', [$this, 'customizer_background_image'], 10, 2);
33 60
34 61 /*
35 62 * Legacy filter
36 - * 'wpmcs_get_attached_file_copy_back_to_local'
63 + * 'wpmcs_get_attached_file_copy_back_to_server'
37 64 */
38 - add_filter('wpmcs_get_attached_file', [$this, 'legacy_copy_back_to_local'], 10, 4);
65 + add_filter('wpmcs_get_attached_file', [$this, 'legacy_copy_back_to_server'], 10, 4);
39 66
40 67 /*
41 68 * Regenerate Thumbnails (before v3)
42 69 * https://wordpress.org/plugins/regenerate-thumbnails/
@@ -43,21 +70,180 @@
43 70 */
44 71 add_filter('wpmcs_get_attached_file', [$this, 'regenerate_thumbnails_download_file'], 10, 4);
45 72
46 73 /**
47 - * Regenerate Thumbnails v3+ and other REST-API using plugins that need a local file.
74 + * Regenerate Thumbnails v3+ and other REST-API using plugins that need a server file.
48 75 */
49 - add_filter('rest_dispatch_request', [$this, 'rest_dispatch_request_copy_back_to_local'], 10, 4);
76 + add_filter('rest_dispatch_request', [$this, 'rest_dispatch_request_copy_back_to_server'], 10, 4);
77 + add_filter('rest_request_after_callbacks', [$this, 'rest_request_after_callbacks_remove_from_server'], 10, 3);
78 + add_filter('wpmcs_wait_for_generate_attachment_metadata', array( $this, 'wait_for_generate_attachment_metadata' ) );
50 79
51 80 /*
52 81 * WP-CLI Compatibility
53 82 */
54 83 if (defined('WP_CLI') && class_exists('WP_CLI')) {
55 - WP_CLI::add_hook('before_invoke:media regenerate', [$this, 'enable_copy_back_media']);
84 + WP_CLI::add_hook('before_invoke:media regenerate', [$this, 'enable_copy_back_and_wait_for_generate_metadata']);
56 85 }
57 86 }
58 87
88 +
59 89 /**
90 + * Allow the WordPress Customizer to crop images that have been copied to bucket
91 + * but removed from the local server, by copying them back temporarily.
92 + *
93 + * @param string $url
94 + * @param string $file
95 + * @param int $attachment_id
96 + * @param array $item
97 + *
98 + * @return string
99 + */
100 + public function customizer_crop_download_file( $url, $file, $attachment_id, $item ) {
101 + if ( false === $this->is_customizer_crop_action() ) {
102 + return $url;
103 + }
104 +
105 + // Check if file was restored already and return the URL
106 + // Avoid removed files being copied back multiple times
107 + if(in_array($file, $this->restored_files)) {
108 + return $url;
109 + }
110 +
111 + if ( ( $file = $this->copy_provider_file_to_server( $attachment_id, $file ) ) ) {
112 + // Return the file if successfully downloaded from bucket.
113 + return $file;
114 + }
115 +
116 + return $url;
117 + }
118 +
119 + /**
120 + * Generic check for Customizer crop actions
121 + *
122 + * @return bool
123 + */
124 + public function is_customizer_crop_action() {
125 + $header_crop = $this->maybe_process_on_action( 'custom-header-crop', true );
126 +
127 + $context = array( 'site-icon', 'custom_logo' );
128 + $image_crop = $this->maybe_process_on_action( 'crop-image', true, $context );
129 +
130 + if ( ! $header_crop && ! $image_crop ) {
131 + // Not doing a Customizer action
132 + return false;
133 + }
134 +
135 + return true;
136 + }
137 +
138 + /**
139 + * Additional filter to remove any restored files from the server
140 + * during a Customizer crop action.
141 + * @param array $files_to_remove
142 + * @param int $source_id
143 + * @param array $data
144 + * @param string $source_type
145 + * @return array
146 + */
147 + public function customizer_crop_remove_restored_files($files_to_remove, $source_id, $new_item, $old_item=[], $source_type = 'media_library') {
148 + $upload_dir = wp_get_upload_dir();
149 +
150 + if (false === $this->is_customizer_crop_action()) {
151 + return $files_to_remove;
152 + }
153 +
154 + if (isset($old_item['source_path']) && $old_item['source_path'] !== $new_item['source_path']) {
155 + // The file has changed, so we need to remove the old file from the server
156 + $files_to_remove[] = trailingslashit( $upload_dir['basedir'] ) . $old_item['source_path'];
157 + }
158 +
159 + if( isset($old_item['original_source_path']) && !empty($old_item['original_source_path']) ) {
160 + $files_to_remove[] = trailingslashit( $upload_dir['basedir'] ) . $old_item['original_source_path'];
161 + }
162 +
163 + return array_merge($files_to_remove, $this->restored_files);
164 + }
165 +
166 +
167 + /**
168 + * Allow the WordPress Image Editor to edit files that have been copied to provider
169 + * but removed from the local server, by copying them back temporarily
170 + *
171 + * @param string $url
172 + * @param string $file
173 + * @param int $attachment_id
174 + * @param array $item
175 + *
176 + * @return string
177 + */
178 + public function image_editor_download_file($url, $file, $attachment_id, $item) {
179 + // If this filter expects a file path, $url is a URL or a file path
180 + if (!Utils::is_ajax()) {
181 + return $url;
182 + }
183 +
184 + $action = Utils::filter_input('action', INPUT_GET) ?: Utils::filter_input('action', INPUT_POST);
185 + $do = Utils::filter_input('do', INPUT_POST);
186 +
187 + // Avoid multiple rewrites when restoring/saving.
188 + if ($action === 'image-editor' && in_array($do, ['restore', 'save'], true)) {
189 + return $file; // Return the file if image editor is doing a restore or save
190 + }
191 +
192 + // Copy back only once during a save triggered by the image editor.
193 + if ($do === 'save' && in_array($action, ['image-editor', 'imgedit-preview'], true)) {
194 + foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 15) as $caller) {
195 + if (!empty($caller['function']) && $caller['function'] === '_load_image_to_edit_path') {
196 + $provider_file = $this->copy_provider_file_to_server($attachment_id, $file);
197 + return $provider_file ?: $url;
198 + }
199 + }
200 + }
201 +
202 + return $url;
203 +}
204 +
205 +
206 +
207 +
208 + /**
209 + * Called after REST API callback, and removes server files from the server for routes that need it.
210 + *
211 + * @param WP_HTTP_Response $response The response object.
212 + * @param WP_REST_Server $handler The response handler.
213 + * @param WP_REST_Request $request The current request.
214 + *
215 + * @return WP_HTTP_Response The filtered response object.
216 + */
217 + public function rest_request_after_callbacks_remove_from_server($response, $handler, $request) {
218 + $routes = [
219 + '/regenerate-thumbnails/v\d+/regenerate/',
220 + ];
221 +
222 + // Get the current REST route
223 + $route = $request->get_route();
224 +
225 + // Apply filter so devs can modify routes
226 + $routes = apply_filters('wpmcs_rest_api_enable_get_attached_file_remove_from_server', $routes);
227 + $routes = is_array($routes) ? $routes : (array) $routes;
228 +
229 + if (!empty($routes)) {
230 + foreach ($routes as $match_route) {
231 + if (preg_match('@' . $match_route . '@i', $route)) {
232 + if ($request->get_param('id')) {
233 + $attachment_id = absint($request->get_param('id'));
234 + // Remove the file from the server
235 + Item::instance()->may_be_delete_server_files_by_id($attachment_id, 'media_library', true, true);
236 + }
237 + break;
238 + }
239 + }
240 + }
241 +
242 + return $response;
243 + }
244 +
245 + /**
60 246 * Filters the REST dispatch request to determine whether route needs compatibility actions.
61 247 *
62 248 * @param bool $dispatch_result Dispatch result, will be used if not empty.
63 249 * @param WP_REST_Request $request Request used to generate the response.
@@ -65,20 +251,20 @@
65 251 * @param array $handler Route handler used for the request.
66 252 *
67 253 * @return bool
68 254 */
69 - public function rest_dispatch_request_copy_back_to_local($dispatch_result, $request, $route, $handler) {
255 + public function rest_dispatch_request_copy_back_to_server($dispatch_result, $request, $route, $handler) {
70 256 $routes = [
71 257 '/regenerate-thumbnails/v\d+/regenerate/',
72 258 ];
73 259
74 - $routes = apply_filters('wpmcs_rest_api_enable_get_attached_file_copy_back_to_local', $routes);
260 + $routes = apply_filters('wpmcs_rest_api_enable_get_attached_file_copy_back_to_server', $routes);
75 261 $routes = is_array($routes) ? $routes : (array)$routes;
76 262
77 263 if (!empty($routes)) {
78 264 foreach ($routes as $match_route) {
79 265 if (preg_match('@' . $match_route . '@i', $route)) {
80 - $this->enable_copy_back_media();
266 + $this->enable_copy_back_and_wait_for_generate_metadata();
81 267 break;
82 268 }
83 269 }
84 270 }
@@ -92,14 +278,59 @@
92 278 * before re-offloading.
93 279 *
94 280 * @handles WP_CLI:before_invoke:media regenerate
95 281 */
96 - public function enable_copy_back_media() {
97 - add_filter('wpmcs_get_attached_file_copy_back_to_local', '__return_true');
282 + public function enable_copy_back_and_wait_for_generate_metadata() {
283 + add_filter('wpmcs_get_attached_file_copy_back_to_server', '__return_true');
284 + $this->enable_get_attached_file_copy_back_to_server();
285 + $this->wait_for_generate_attachment_metadata = true;
98 286 }
99 287
100 288 /**
101 - * Allow the Regenerate Thumbnails plugin to copy the bucket file back to the local
289 + * Enables copying missing local files back to the server when `get_attached_file` filter is called.
290 + */
291 + public function enable_get_attached_file_copy_back_to_server() {
292 + add_filter('wpmcs_get_attached_file_copy_back_to_server', '__return_true');
293 +
294 + add_filter( 'wp_generate_attachment_metadata', array( $this, 'wp_generate_attachment_metadata' ) );
295 + }
296 +
297 + /**
298 + * Handler for wp_generate_attachment_metadata. Updates class
299 + * member variable when the filter has fired.
300 + *
301 + * @handles wp_generate_attachment_metadata
302 + *
303 + * @param mixed $metadata
304 + *
305 + * @return mixed
306 + */
307 + public function wp_generate_attachment_metadata( $metadata ) {
308 + $this->wait_for_generate_attachment_metadata = false;
309 +
310 + return $metadata;
311 + }
312 +
313 + /**
314 + * Are we waiting for the wp_generate_attachment_metadata filter and
315 + * if so, has it run yet?
316 + *
317 + * @handles wpmcs_wait_for_generate_attachment_metadata
318 + *
319 + * @param bool $wait
320 + *
321 + * @return bool
322 + */
323 + public function wait_for_generate_attachment_metadata( $wait ) {
324 + if ( $this->wait_for_generate_attachment_metadata ) {
325 + return true;
326 + }
327 +
328 + return $wait;
329 + }
330 +
331 + /**
332 + * Allow the Regenerate Thumbnails plugin to copy the bucket file back to the server
102 333 * server when the file is missing on the server via get_attached_file.
103 334 *
104 335 * @param string $url
105 336 * @param string $file
@@ -111,10 +342,10 @@
111 342 return $this->copy_image_to_server_on_action('regeneratethumbnail', true, $url, $file, $attachment_id);
112 343 }
113 344
114 345 /**
115 - * Allow any process to trigger the copy back to local with
116 - * the filter 'wpmcs_get_attached_file_copy_back_to_local'
346 + * Allow any process to trigger the copy back to server with
347 + * the filter 'wpmcs_get_attached_file_copy_back_to_server'
117 348 *
118 349 * @param string $url
119 350 * @param string $file
120 351 * @param int $attachment_id
@@ -120,11 +351,11 @@
120 351 * @param int $attachment_id
121 352 *
122 353 * @return string
123 354 */
124 - public function legacy_copy_back_to_local($url, $file, $attachment_id, $wpmcs_item) {
125 - $copy_back_to_local = apply_filters('wpmcs_get_attached_file_copy_back_to_local', false, $file, $attachment_id, $wpmcs_item);
126 - if (false === $copy_back_to_local) {
355 + public function legacy_copy_back_to_server($url, $file, $attachment_id, $wpmcs_item) {
356 + $copy_back_to_server = apply_filters('wpmcs_get_attached_file_copy_back_to_server', false, $file, $attachment_id, $wpmcs_item);
357 + if (false === $copy_back_to_server) {
127 358 // Not copying back file
128 359 return $url;
129 360 }
130 361
@@ -145,15 +376,15 @@
145 376 *
146 377 * @return int|null
147 378 */
148 379 public function customizer_background_image($post_id, $url) {
149 - if (!is_null($post_id)) {
380 + if (!empty($post_id)) {
150 381 return $post_id;
151 382 }
152 383
153 384 // There seems to be a bug in the WP Customizer whereby sometimes it puts the attachment ID on the URL.
154 385 if (is_numeric($url)) {
155 - $item = Item::get($url);
386 + $item = Item::instance()->get($url);
156 387
157 388 // If we found an offloaded Media Library item for that ID, job's a good'n'.
158 389 if (!Utils::is_empty($item)) {
159 390 $post_id = $url;
@@ -160,12 +391,12 @@
160 391 }
161 392 } else {
162 393 $path = Utils::get_attachment_source_path($url);
163 394 if (!Utils::is_empty($path)) {
164 - $items = Item::instance()->get_items_by_source_paths([$path]);
165 - if (!Utils::is_empty($items)) {
395 + $item = Item::instance()->get_items_by_paths( $path, true, true );
396 + if (!Utils::is_empty($item)) {
166 397 // If we found an offloaded Media Library item for that path, job's a good'n'.
167 - $post_id = $items[0]['source_id'];
398 + $post_id = $item['source_id'];
168 399 }
169 400 }
170 401 }
171 402
@@ -232,19 +463,21 @@
232 463 return $url;
233 464 }
234 465
235 466 /**
236 - * Download a file from bucket if the file does not exist locally and places it where
467 + * Download a file from bucket if the file does not exist serverly and places it where
237 468 * the attachment's file should be.
238 469 *
239 470 * @return string|bool File if downloaded, false on failure
240 471 */
241 - public function copy_provider_file_to_server($attachment_id, $file) {
472 + private function copy_provider_file_to_server($attachment_id, $file) {
242 473 // Download files
243 - if (!Item::instance()->moveToServerBySourcePath($attachment_id, $file)) {
474 + if (!Item::instance()->moveToServerBySourcePath($attachment_id, $file, 'media_library')) {
244 475 return false;
245 - }
476 + }
246 477
478 + $this->restored_files[] = $file;
479 +
247 480 return $file;
248 481 }
249 482
250 483 /**
@@ -257,5 +490,8 @@
257 490 self::$instance = new self();
258 491 }
259 492 return self::$instance;
260 493 }
494 +
495 + private function __clone() {}
496 + public function __wakeup() { throw new \Exception('Cannot unserialize singleton'); }
261 497 }