assets_url = WPMCS_ASSETS_URL; $this->version = WPMCS_VERSION; $this->token = WPMCS_TOKEN; // Initialize setup $this->init(); } public function init() { /* * WP_Customize_Control * /wp-includes/class-wp-customize_control.php */ add_filter('attachment_url_to_postid', [$this, 'customizer_background_image'], 10, 2); /* * Legacy filter * 'wpmcs_get_attached_file_copy_back_to_local' */ add_filter('wpmcs_get_attached_file', [$this, 'legacy_copy_back_to_local'], 10, 4); /* * Regenerate Thumbnails (before v3) * https://wordpress.org/plugins/regenerate-thumbnails/ */ add_filter('wpmcs_get_attached_file', [$this, 'regenerate_thumbnails_download_file'], 10, 4); /** * Regenerate Thumbnails v3+ and other REST-API using plugins that need a local file. */ add_filter('rest_dispatch_request', [$this, 'rest_dispatch_request_copy_back_to_local'], 10, 4); /* * WP-CLI Compatibility */ if (defined('WP_CLI') && class_exists('WP_CLI')) { WP_CLI::add_hook('before_invoke:media regenerate', [$this, 'enable_copy_back_media']); } } /** * Filters the REST dispatch request to determine whether route needs compatibility actions. * * @param bool $dispatch_result Dispatch result, will be used if not empty. * @param WP_REST_Request $request Request used to generate the response. * @param string $route Route matched for the request. * @param array $handler Route handler used for the request. * * @return bool */ public function rest_dispatch_request_copy_back_to_local($dispatch_result, $request, $route, $handler) { $routes = [ '/regenerate-thumbnails/v\d+/regenerate/', ]; $routes = apply_filters('wpmcs_rest_api_enable_get_attached_file_copy_back_to_local', $routes); $routes = is_array($routes) ? $routes : (array)$routes; if (!empty($routes)) { foreach ($routes as $match_route) { if (preg_match('@' . $match_route . '@i', $route)) { $this->enable_copy_back_media(); break; } } } return $dispatch_result; } /** * Enable copying back attachments from provider * and waiting for their metadata to be regenerated * before re-offloading. * * @handles WP_CLI:before_invoke:media regenerate */ public function enable_copy_back_media() { add_filter('wpmcs_get_attached_file_copy_back_to_local', '__return_true'); } /** * Allow the Regenerate Thumbnails plugin to copy the bucket file back to the local * server when the file is missing on the server via get_attached_file. * * @param string $url * @param string $file * @param int $attachment_id * * @return string */ public function regenerate_thumbnails_download_file($url, $file, $attachment_id, $item) { return $this->copy_image_to_server_on_action('regeneratethumbnail', true, $url, $file, $attachment_id); } /** * Allow any process to trigger the copy back to local with * the filter 'wpmcs_get_attached_file_copy_back_to_local' * * @param string $url * @param string $file * @param int $attachment_id * * @return string */ public function legacy_copy_back_to_local($url, $file, $attachment_id, $wpmcs_item) { $copy_back_to_local = apply_filters('wpmcs_get_attached_file_copy_back_to_local', false, $file, $attachment_id, $wpmcs_item); if (false === $copy_back_to_local) { // Not copying back file return $url; } if (($file = $this->copy_provider_file_to_server($attachment_id, $file))) { // Return the file if successfully downloaded from S3 return $file; } // Return S3 URL as a fallback return $url; } /** * Show the correct background image in the customizer * * @param int|null $post_id * @param string $url * * @return int|null */ public function customizer_background_image($post_id, $url) { if (!is_null($post_id)) { return $post_id; } // There seems to be a bug in the WP Customizer whereby sometimes it puts the attachment ID on the URL. if (is_numeric($url)) { $item = Item::get($url); // If we found an offloaded Media Library item for that ID, job's a good'n'. if (!Utils::is_empty($item)) { $post_id = $url; } } else { $path = Utils::get_attachment_source_path($url); if (!Utils::is_empty($path)) { $items = Item::instance()->get_items_by_source_paths([$path]); if (!Utils::is_empty($items)) { // If we found an offloaded Media Library item for that path, job's a good'n'. $post_id = $items[0]['source_id']; } } } // Must return null if not found. return empty($post_id) ? null : $post_id; } /** * Check the current request is a specific one based on action and * optional context * * @param string $action_key * @param bool $ajax * @param null|string|array $context_key * * @return bool */ public function maybe_process_on_action($action_key, $ajax, $context_key = null) { if ($ajax !== Utils::is_ajax()) { return false; } $var_type = 'GET'; if (isset($_GET['action'])) { $action = Utils::filter_input('action'); } elseif (isset($_POST['action'])) { $var_type = 'POST'; $action = Utils::filter_input('action', INPUT_POST); } else { return false; } $context_check = true; if (!is_null($context_key)) { $global = constant('INPUT_' . $var_type); $context = Utils::filter_input('context', $global); if (is_array($context_key)) { $context_check = in_array($context, $context_key); } else { $context_check = ($context_key === $context); } } return ($action_key === sanitize_key($action) && $context_check); } /** * Generic method for copying back an S3 file to the server on a specific AJAX action * * @return string */ public function copy_image_to_server_on_action($action_key, $ajax, $url, $file, $attachment_id) { if (false === $this->maybe_process_on_action($action_key, $ajax)) { return $url; } if (($file = $this->copy_provider_file_to_server($attachment_id, $file))) { // Return the file if successfully downloaded from S3 return $file; } return $url; } /** * Download a file from bucket if the file does not exist locally and places it where * the attachment's file should be. * * @return string|bool File if downloaded, false on failure */ public function copy_provider_file_to_server($attachment_id, $file) { // Download files if (!Item::instance()->moveToServerBySourcePath($attachment_id, $file)) { return false; } return $file; } /** * Get the singleton instance of the Compatibility class. * * @return Compatibility */ public static function instance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } }