annotate core/modules/media/src/OEmbed/ResourceFetcher.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
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 }