Pull merge.
[yaffs-website] / web / core / modules / media / src / OEmbed / ResourceFetcher.php
1 <?php
2
3 namespace Drupal\media\OEmbed;
4
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;
10
11 /**
12  * Fetches and caches oEmbed resources.
13  */
14 class ResourceFetcher implements ResourceFetcherInterface {
15
16   use UseCacheBackendTrait;
17
18   /**
19    * The HTTP client.
20    *
21    * @var \GuzzleHttp\Client
22    */
23   protected $httpClient;
24
25   /**
26    * The oEmbed provider repository service.
27    *
28    * @var \Drupal\media\OEmbed\ProviderRepositoryInterface
29    */
30   protected $providers;
31
32   /**
33    * Constructs a ResourceFetcher object.
34    *
35    * @param \GuzzleHttp\ClientInterface $http_client
36    *   The 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.
41    */
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);
47   }
48
49   /**
50    * {@inheritdoc}
51    */
52   public function fetchResource($url) {
53     $cache_id = "media:oembed_resource:$url";
54
55     $cached = $this->cacheGet($cache_id);
56     if ($cached) {
57       return $this->createResource($cached->data, $url);
58     }
59
60     try {
61       $response = $this->httpClient->get($url);
62     }
63     catch (RequestException $e) {
64       throw new ResourceException('Could not retrieve the oEmbed resource.', $url, [], $e);
65     }
66
67     list($format) = $response->getHeader('Content-Type');
68     $content = (string) $response->getBody();
69
70     if (strstr($format, 'text/xml') || strstr($format, 'application/xml')) {
71       $data = $this->parseResourceXml($content, $url);
72     }
73     elseif (strstr($format, 'text/javascript') || strstr($format, 'application/json')) {
74       $data = Json::decode($content);
75     }
76     // If the response is neither XML nor JSON, we are in bat country.
77     else {
78       throw new ResourceException('The fetched resource did not have a valid Content-Type header.', $url);
79     }
80
81     $this->cacheSet($cache_id, $data);
82
83     return $this->createResource($data, $url);
84   }
85
86   /**
87    * Creates a Resource object from raw resource data.
88    *
89    * @param array $data
90    *   The resource data returned by the provider.
91    * @param string $url
92    *   The URL of the resource.
93    *
94    * @return \Drupal\media\OEmbed\Resource
95    *   A value object representing the resource.
96    *
97    * @throws \Drupal\media\OEmbed\ResourceException
98    *   If the resource cannot be created.
99    */
100   protected function createResource(array $data, $url) {
101     $data += [
102       'title' => NULL,
103       'author_name' => NULL,
104       'author_url' => NULL,
105       'provider_name' => NULL,
106       'cache_age' => NULL,
107       'thumbnail_url' => NULL,
108       'thumbnail_width' => NULL,
109       'thumbnail_height' => NULL,
110       'width' => NULL,
111       'height' => NULL,
112       'url' => NULL,
113       'html' => NULL,
114       'version' => NULL,
115     ];
116
117     if ($data['version'] !== '1.0') {
118       throw new ResourceException("Resource version must be '1.0'", $url, $data);
119     }
120
121     // Prepare the arguments to pass to the factory method.
122     $provider = $data['provider_name'] ? $this->providers->get($data['provider_name']) : NULL;
123
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.
127     try {
128       switch ($data['type']) {
129         case Resource::TYPE_LINK:
130           return Resource::link(
131             $data['url'],
132             $provider,
133             $data['title'],
134             $data['author_name'],
135             $data['author_url'],
136             $data['cache_age'],
137             $data['thumbnail_url'],
138             $data['thumbnail_width'],
139             $data['thumbnail_height']
140           );
141
142         case Resource::TYPE_PHOTO:
143           return Resource::photo(
144             $data['url'],
145             $data['width'],
146             $data['height'],
147             $provider,
148             $data['title'],
149             $data['author_name'],
150             $data['author_url'],
151             $data['cache_age'],
152             $data['thumbnail_url'],
153             $data['thumbnail_width'],
154             $data['thumbnail_height']
155           );
156
157         case Resource::TYPE_RICH:
158           return Resource::rich(
159             $data['html'],
160             $data['width'],
161             $data['height'],
162             $provider,
163             $data['title'],
164             $data['author_name'],
165             $data['author_url'],
166             $data['cache_age'],
167             $data['thumbnail_url'],
168             $data['thumbnail_width'],
169             $data['thumbnail_height']
170           );
171         case Resource::TYPE_VIDEO:
172           return Resource::video(
173             $data['html'],
174             $data['width'],
175             $data['height'],
176             $provider,
177             $data['title'],
178             $data['author_name'],
179             $data['author_url'],
180             $data['cache_age'],
181             $data['thumbnail_url'],
182             $data['thumbnail_width'],
183             $data['thumbnail_height']
184           );
185
186         default:
187           throw new ResourceException('Unknown resource type: ' . $data['type'], $url, $data);
188       }
189     }
190     catch (\InvalidArgumentException $e) {
191       throw new ResourceException($e->getMessage(), $url, $data, $e);
192     }
193   }
194
195   /**
196    * Parses XML resource data.
197    *
198    * @param string $data
199    *   The raw XML for the resource.
200    * @param string $url
201    *   The resource URL.
202    *
203    * @return array
204    *   The parsed resource data.
205    *
206    * @throws \Drupal\media\OEmbed\ResourceException
207    *   If the resource data could not be parsed.
208    */
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();
213
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);
217
218     $error = libxml_get_last_error();
219     if ($error) {
220       libxml_clear_errors();
221       throw new ResourceException($error->message, $url);
222     }
223     elseif ($content === FALSE) {
224       throw new ResourceException('The fetched resource could not be parsed.', $url);
225     }
226
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);
231   }
232
233 }