Chris@17
|
1 <?php
|
Chris@17
|
2
|
Chris@17
|
3 namespace Drupal\media\OEmbed;
|
Chris@17
|
4
|
Chris@17
|
5 use Drupal\Component\Serialization\Json;
|
Chris@17
|
6 use Drupal\Core\Cache\CacheBackendInterface;
|
Chris@17
|
7 use Drupal\Core\Cache\UseCacheBackendTrait;
|
Chris@17
|
8 use GuzzleHttp\ClientInterface;
|
Chris@17
|
9 use GuzzleHttp\Exception\RequestException;
|
Chris@17
|
10
|
Chris@17
|
11 /**
|
Chris@17
|
12 * Fetches and caches oEmbed resources.
|
Chris@17
|
13 */
|
Chris@17
|
14 class ResourceFetcher implements ResourceFetcherInterface {
|
Chris@17
|
15
|
Chris@17
|
16 use UseCacheBackendTrait;
|
Chris@17
|
17
|
Chris@17
|
18 /**
|
Chris@17
|
19 * The HTTP client.
|
Chris@17
|
20 *
|
Chris@17
|
21 * @var \GuzzleHttp\Client
|
Chris@17
|
22 */
|
Chris@17
|
23 protected $httpClient;
|
Chris@17
|
24
|
Chris@17
|
25 /**
|
Chris@17
|
26 * The oEmbed provider repository service.
|
Chris@17
|
27 *
|
Chris@17
|
28 * @var \Drupal\media\OEmbed\ProviderRepositoryInterface
|
Chris@17
|
29 */
|
Chris@17
|
30 protected $providers;
|
Chris@17
|
31
|
Chris@17
|
32 /**
|
Chris@17
|
33 * Constructs a ResourceFetcher object.
|
Chris@17
|
34 *
|
Chris@17
|
35 * @param \GuzzleHttp\ClientInterface $http_client
|
Chris@17
|
36 * The HTTP client.
|
Chris@17
|
37 * @param \Drupal\media\OEmbed\ProviderRepositoryInterface $providers
|
Chris@17
|
38 * The oEmbed provider repository service.
|
Chris@17
|
39 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
Chris@17
|
40 * (optional) The cache backend.
|
Chris@17
|
41 */
|
Chris@17
|
42 public function __construct(ClientInterface $http_client, ProviderRepositoryInterface $providers, CacheBackendInterface $cache_backend = NULL) {
|
Chris@17
|
43 $this->httpClient = $http_client;
|
Chris@17
|
44 $this->providers = $providers;
|
Chris@17
|
45 $this->cacheBackend = $cache_backend;
|
Chris@17
|
46 $this->useCaches = isset($cache_backend);
|
Chris@17
|
47 }
|
Chris@17
|
48
|
Chris@17
|
49 /**
|
Chris@17
|
50 * {@inheritdoc}
|
Chris@17
|
51 */
|
Chris@17
|
52 public function fetchResource($url) {
|
Chris@17
|
53 $cache_id = "media:oembed_resource:$url";
|
Chris@17
|
54
|
Chris@17
|
55 $cached = $this->cacheGet($cache_id);
|
Chris@17
|
56 if ($cached) {
|
Chris@17
|
57 return $this->createResource($cached->data, $url);
|
Chris@17
|
58 }
|
Chris@17
|
59
|
Chris@17
|
60 try {
|
Chris@17
|
61 $response = $this->httpClient->get($url);
|
Chris@17
|
62 }
|
Chris@17
|
63 catch (RequestException $e) {
|
Chris@17
|
64 throw new ResourceException('Could not retrieve the oEmbed resource.', $url, [], $e);
|
Chris@17
|
65 }
|
Chris@17
|
66
|
Chris@17
|
67 list($format) = $response->getHeader('Content-Type');
|
Chris@17
|
68 $content = (string) $response->getBody();
|
Chris@17
|
69
|
Chris@17
|
70 if (strstr($format, 'text/xml') || strstr($format, 'application/xml')) {
|
Chris@17
|
71 $data = $this->parseResourceXml($content, $url);
|
Chris@17
|
72 }
|
Chris@17
|
73 elseif (strstr($format, 'text/javascript') || strstr($format, 'application/json')) {
|
Chris@17
|
74 $data = Json::decode($content);
|
Chris@17
|
75 }
|
Chris@17
|
76 // If the response is neither XML nor JSON, we are in bat country.
|
Chris@17
|
77 else {
|
Chris@17
|
78 throw new ResourceException('The fetched resource did not have a valid Content-Type header.', $url);
|
Chris@17
|
79 }
|
Chris@17
|
80
|
Chris@17
|
81 $this->cacheSet($cache_id, $data);
|
Chris@17
|
82
|
Chris@17
|
83 return $this->createResource($data, $url);
|
Chris@17
|
84 }
|
Chris@17
|
85
|
Chris@17
|
86 /**
|
Chris@17
|
87 * Creates a Resource object from raw resource data.
|
Chris@17
|
88 *
|
Chris@17
|
89 * @param array $data
|
Chris@17
|
90 * The resource data returned by the provider.
|
Chris@17
|
91 * @param string $url
|
Chris@17
|
92 * The URL of the resource.
|
Chris@17
|
93 *
|
Chris@17
|
94 * @return \Drupal\media\OEmbed\Resource
|
Chris@17
|
95 * A value object representing the resource.
|
Chris@17
|
96 *
|
Chris@17
|
97 * @throws \Drupal\media\OEmbed\ResourceException
|
Chris@17
|
98 * If the resource cannot be created.
|
Chris@17
|
99 */
|
Chris@17
|
100 protected function createResource(array $data, $url) {
|
Chris@17
|
101 $data += [
|
Chris@17
|
102 'title' => NULL,
|
Chris@17
|
103 'author_name' => NULL,
|
Chris@17
|
104 'author_url' => NULL,
|
Chris@17
|
105 'provider_name' => NULL,
|
Chris@17
|
106 'cache_age' => NULL,
|
Chris@17
|
107 'thumbnail_url' => NULL,
|
Chris@17
|
108 'thumbnail_width' => NULL,
|
Chris@17
|
109 'thumbnail_height' => NULL,
|
Chris@17
|
110 'width' => NULL,
|
Chris@17
|
111 'height' => NULL,
|
Chris@17
|
112 'url' => NULL,
|
Chris@17
|
113 'html' => NULL,
|
Chris@17
|
114 'version' => NULL,
|
Chris@17
|
115 ];
|
Chris@17
|
116
|
Chris@17
|
117 if ($data['version'] !== '1.0') {
|
Chris@17
|
118 throw new ResourceException("Resource version must be '1.0'", $url, $data);
|
Chris@17
|
119 }
|
Chris@17
|
120
|
Chris@17
|
121 // Prepare the arguments to pass to the factory method.
|
Chris@17
|
122 $provider = $data['provider_name'] ? $this->providers->get($data['provider_name']) : NULL;
|
Chris@17
|
123
|
Chris@17
|
124 // The Resource object will validate the data we create it with and throw an
|
Chris@17
|
125 // exception if anything looks wrong. For better debugging, catch those
|
Chris@17
|
126 // exceptions and wrap them in a more specific and useful exception.
|
Chris@17
|
127 try {
|
Chris@17
|
128 switch ($data['type']) {
|
Chris@17
|
129 case Resource::TYPE_LINK:
|
Chris@17
|
130 return Resource::link(
|
Chris@17
|
131 $data['url'],
|
Chris@17
|
132 $provider,
|
Chris@17
|
133 $data['title'],
|
Chris@17
|
134 $data['author_name'],
|
Chris@17
|
135 $data['author_url'],
|
Chris@17
|
136 $data['cache_age'],
|
Chris@17
|
137 $data['thumbnail_url'],
|
Chris@17
|
138 $data['thumbnail_width'],
|
Chris@17
|
139 $data['thumbnail_height']
|
Chris@17
|
140 );
|
Chris@17
|
141
|
Chris@17
|
142 case Resource::TYPE_PHOTO:
|
Chris@17
|
143 return Resource::photo(
|
Chris@17
|
144 $data['url'],
|
Chris@17
|
145 $data['width'],
|
Chris@17
|
146 $data['height'],
|
Chris@17
|
147 $provider,
|
Chris@17
|
148 $data['title'],
|
Chris@17
|
149 $data['author_name'],
|
Chris@17
|
150 $data['author_url'],
|
Chris@17
|
151 $data['cache_age'],
|
Chris@17
|
152 $data['thumbnail_url'],
|
Chris@17
|
153 $data['thumbnail_width'],
|
Chris@17
|
154 $data['thumbnail_height']
|
Chris@17
|
155 );
|
Chris@17
|
156
|
Chris@17
|
157 case Resource::TYPE_RICH:
|
Chris@17
|
158 return Resource::rich(
|
Chris@17
|
159 $data['html'],
|
Chris@17
|
160 $data['width'],
|
Chris@17
|
161 $data['height'],
|
Chris@17
|
162 $provider,
|
Chris@17
|
163 $data['title'],
|
Chris@17
|
164 $data['author_name'],
|
Chris@17
|
165 $data['author_url'],
|
Chris@17
|
166 $data['cache_age'],
|
Chris@17
|
167 $data['thumbnail_url'],
|
Chris@17
|
168 $data['thumbnail_width'],
|
Chris@17
|
169 $data['thumbnail_height']
|
Chris@17
|
170 );
|
Chris@17
|
171 case Resource::TYPE_VIDEO:
|
Chris@17
|
172 return Resource::video(
|
Chris@17
|
173 $data['html'],
|
Chris@17
|
174 $data['width'],
|
Chris@17
|
175 $data['height'],
|
Chris@17
|
176 $provider,
|
Chris@17
|
177 $data['title'],
|
Chris@17
|
178 $data['author_name'],
|
Chris@17
|
179 $data['author_url'],
|
Chris@17
|
180 $data['cache_age'],
|
Chris@17
|
181 $data['thumbnail_url'],
|
Chris@17
|
182 $data['thumbnail_width'],
|
Chris@17
|
183 $data['thumbnail_height']
|
Chris@17
|
184 );
|
Chris@17
|
185
|
Chris@17
|
186 default:
|
Chris@17
|
187 throw new ResourceException('Unknown resource type: ' . $data['type'], $url, $data);
|
Chris@17
|
188 }
|
Chris@17
|
189 }
|
Chris@17
|
190 catch (\InvalidArgumentException $e) {
|
Chris@17
|
191 throw new ResourceException($e->getMessage(), $url, $data, $e);
|
Chris@17
|
192 }
|
Chris@17
|
193 }
|
Chris@17
|
194
|
Chris@17
|
195 /**
|
Chris@17
|
196 * Parses XML resource data.
|
Chris@17
|
197 *
|
Chris@17
|
198 * @param string $data
|
Chris@17
|
199 * The raw XML for the resource.
|
Chris@17
|
200 * @param string $url
|
Chris@17
|
201 * The resource URL.
|
Chris@17
|
202 *
|
Chris@17
|
203 * @return array
|
Chris@17
|
204 * The parsed resource data.
|
Chris@17
|
205 *
|
Chris@17
|
206 * @throws \Drupal\media\OEmbed\ResourceException
|
Chris@17
|
207 * If the resource data could not be parsed.
|
Chris@17
|
208 */
|
Chris@17
|
209 protected function parseResourceXml($data, $url) {
|
Chris@17
|
210 // Enable userspace error handling.
|
Chris@17
|
211 $was_using_internal_errors = libxml_use_internal_errors(TRUE);
|
Chris@17
|
212 libxml_clear_errors();
|
Chris@17
|
213
|
Chris@17
|
214 $content = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
|
Chris@17
|
215 // Restore the previous error handling behavior.
|
Chris@17
|
216 libxml_use_internal_errors($was_using_internal_errors);
|
Chris@17
|
217
|
Chris@17
|
218 $error = libxml_get_last_error();
|
Chris@17
|
219 if ($error) {
|
Chris@17
|
220 libxml_clear_errors();
|
Chris@17
|
221 throw new ResourceException($error->message, $url);
|
Chris@17
|
222 }
|
Chris@17
|
223 elseif ($content === FALSE) {
|
Chris@17
|
224 throw new ResourceException('The fetched resource could not be parsed.', $url);
|
Chris@17
|
225 }
|
Chris@17
|
226
|
Chris@17
|
227 // Convert XML to JSON so that the parsed resource has a consistent array
|
Chris@17
|
228 // structure, regardless of any XML attributes or quirks of the XML parser.
|
Chris@17
|
229 $data = Json::encode($content);
|
Chris@17
|
230 return Json::decode($data);
|
Chris@17
|
231 }
|
Chris@17
|
232
|
Chris@17
|
233 }
|