3 namespace Drupal\media\OEmbed;
5 use Drupal\Component\Serialization\Json;
6 use Drupal\Core\Cache\CacheBackendInterface;
7 use Drupal\Core\Cache\UseCacheBackendTrait;
8 use GuzzleHttp\ClientInterface;
9 use GuzzleHttp\Exception\RequestException;
12 * Fetches and caches oEmbed resources.
14 class ResourceFetcher implements ResourceFetcherInterface {
16 use UseCacheBackendTrait;
21 * @var \GuzzleHttp\Client
23 protected $httpClient;
26 * The oEmbed provider repository service.
28 * @var \Drupal\media\OEmbed\ProviderRepositoryInterface
33 * Constructs a ResourceFetcher object.
35 * @param \GuzzleHttp\ClientInterface $http_client
37 * @param \Drupal\media\OEmbed\ProviderRepositoryInterface $providers
38 * The oEmbed provider repository service.
39 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
40 * (optional) The cache backend.
42 public function __construct(ClientInterface $http_client, ProviderRepositoryInterface $providers, CacheBackendInterface $cache_backend = NULL) {
43 $this->httpClient = $http_client;
44 $this->providers = $providers;
45 $this->cacheBackend = $cache_backend;
46 $this->useCaches = isset($cache_backend);
52 public function fetchResource($url) {
53 $cache_id = "media:oembed_resource:$url";
55 $cached = $this->cacheGet($cache_id);
57 return $this->createResource($cached->data, $url);
61 $response = $this->httpClient->get($url);
63 catch (RequestException $e) {
64 throw new ResourceException('Could not retrieve the oEmbed resource.', $url, [], $e);
67 list($format) = $response->getHeader('Content-Type');
68 $content = (string) $response->getBody();
70 if (strstr($format, 'text/xml') || strstr($format, 'application/xml')) {
71 $data = $this->parseResourceXml($content, $url);
73 elseif (strstr($format, 'text/javascript') || strstr($format, 'application/json')) {
74 $data = Json::decode($content);
76 // If the response is neither XML nor JSON, we are in bat country.
78 throw new ResourceException('The fetched resource did not have a valid Content-Type header.', $url);
81 $this->cacheSet($cache_id, $data);
83 return $this->createResource($data, $url);
87 * Creates a Resource object from raw resource data.
90 * The resource data returned by the provider.
92 * The URL of the resource.
94 * @return \Drupal\media\OEmbed\Resource
95 * A value object representing the resource.
97 * @throws \Drupal\media\OEmbed\ResourceException
98 * If the resource cannot be created.
100 protected function createResource(array $data, $url) {
103 'author_name' => NULL,
104 'author_url' => NULL,
105 'provider_name' => NULL,
107 'thumbnail_url' => NULL,
108 'thumbnail_width' => NULL,
109 'thumbnail_height' => NULL,
117 if ($data['version'] !== '1.0') {
118 throw new ResourceException("Resource version must be '1.0'", $url, $data);
121 // Prepare the arguments to pass to the factory method.
122 $provider = $data['provider_name'] ? $this->providers->get($data['provider_name']) : NULL;
124 // The Resource object will validate the data we create it with and throw an
125 // exception if anything looks wrong. For better debugging, catch those
126 // exceptions and wrap them in a more specific and useful exception.
128 switch ($data['type']) {
129 case Resource::TYPE_LINK:
130 return Resource::link(
134 $data['author_name'],
137 $data['thumbnail_url'],
138 $data['thumbnail_width'],
139 $data['thumbnail_height']
142 case Resource::TYPE_PHOTO:
143 return Resource::photo(
149 $data['author_name'],
152 $data['thumbnail_url'],
153 $data['thumbnail_width'],
154 $data['thumbnail_height']
157 case Resource::TYPE_RICH:
158 return Resource::rich(
164 $data['author_name'],
167 $data['thumbnail_url'],
168 $data['thumbnail_width'],
169 $data['thumbnail_height']
171 case Resource::TYPE_VIDEO:
172 return Resource::video(
178 $data['author_name'],
181 $data['thumbnail_url'],
182 $data['thumbnail_width'],
183 $data['thumbnail_height']
187 throw new ResourceException('Unknown resource type: ' . $data['type'], $url, $data);
190 catch (\InvalidArgumentException $e) {
191 throw new ResourceException($e->getMessage(), $url, $data, $e);
196 * Parses XML resource data.
198 * @param string $data
199 * The raw XML for the resource.
204 * The parsed resource data.
206 * @throws \Drupal\media\OEmbed\ResourceException
207 * If the resource data could not be parsed.
209 protected function parseResourceXml($data, $url) {
210 // Enable userspace error handling.
211 $was_using_internal_errors = libxml_use_internal_errors(TRUE);
212 libxml_clear_errors();
214 $content = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
215 // Restore the previous error handling behavior.
216 libxml_use_internal_errors($was_using_internal_errors);
218 $error = libxml_get_last_error();
220 libxml_clear_errors();
221 throw new ResourceException($error->message, $url);
223 elseif ($content === FALSE) {
224 throw new ResourceException('The fetched resource could not be parsed.', $url);
227 // Convert XML to JSON so that the parsed resource has a consistent array
228 // structure, regardless of any XML attributes or quirks of the XML parser.
229 $data = Json::encode($content);
230 return Json::decode($data);