register()) { // $url always resolves to the main file's URL regardless of which WP core hook fired; $file doesn't, so use it to detect an original-image request. $key = $item['key']; if (!empty($item['original_source_path']) && !empty($item['original_key']) && basename((string) $file) === basename($item['original_source_path'])) { $key = $item['original_key']; } return self::PROTOCOL . '://' . $this->bucket_name . '/' . $key; } // No disk-restore fallback here on purpose — that's what a dedicated integration is for. return $url; } private function register() { $bucket_name = Utils::get_credentials('bucketConfig')['bucket_name'] ?? null; $service = Utils::get_credentials('service'); if ($this->registered && $this->bucket_name === $bucket_name) { return true; } // Bucket-keyed failure memoization avoids re-attempting/re-logging on every call of a persistently broken setup. if ($this->failed_bucket === $bucket_name) { return false; } if (empty($bucket_name)) { return false; } $client = Service::instance()->get_active_client(); if (empty($client)) { $this->failed_bucket = $bucket_name; return false; } try { // Unregister explicitly first — the two vendor register() methods aren't consistently idempotent. if (in_array(self::PROTOCOL, stream_get_wrappers(), true)) { stream_wrapper_unregister(self::PROTOCOL); } // Branches on the credential value, not instanceof, to avoid force-loading the GCS SDK on non-GCS sites. if ($service === 'gcloud') { ReadOnlyGcsStreamWrapper::register($client, self::PROTOCOL); } else { ReadOnlyS3StreamWrapper::register($client, self::PROTOCOL); } } catch (\Throwable $e) { // Must never propagate — this runs inside a filter WP core fires broadly. Logger::instance()->add_log('stream_wrapper_register_failed', 0, 'media_library', ['message' => $e->getMessage()]); $this->registered = false; $this->failed_bucket = $bucket_name; return false; } // Default context ensures unaware callers get seekable reads (needed by e.g. getimagesize()). stream_context_set_default([self::PROTOCOL => ['seekable' => true]]); $this->registered = true; $this->bucket_name = $bucket_name; $this->failed_bucket = null; return true; } public static function instance() { if (is_null(self::$instance)) { self::$instance = new self(); } return self::$instance; } }