annotate core/modules/media/src/OEmbed/UrlResolver.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\Utility\Html;
Chris@17 6 use Drupal\Component\Utility\UrlHelper;
Chris@17 7 use Drupal\Core\Cache\CacheBackendInterface;
Chris@17 8 use Drupal\Core\Cache\UseCacheBackendTrait;
Chris@17 9 use Drupal\Core\Extension\ModuleHandlerInterface;
Chris@17 10 use GuzzleHttp\ClientInterface;
Chris@17 11 use GuzzleHttp\Exception\RequestException;
Chris@17 12
Chris@17 13 /**
Chris@17 14 * Converts oEmbed media URLs into endpoint-specific resource URLs.
Chris@17 15 */
Chris@17 16 class UrlResolver implements UrlResolverInterface {
Chris@17 17
Chris@17 18 use UseCacheBackendTrait;
Chris@17 19
Chris@17 20 /**
Chris@17 21 * The HTTP client.
Chris@17 22 *
Chris@17 23 * @var \GuzzleHttp\Client
Chris@17 24 */
Chris@17 25 protected $httpClient;
Chris@17 26
Chris@17 27 /**
Chris@17 28 * The OEmbed provider repository service.
Chris@17 29 *
Chris@17 30 * @var \Drupal\media\OEmbed\ProviderRepositoryInterface
Chris@17 31 */
Chris@17 32 protected $providers;
Chris@17 33
Chris@17 34 /**
Chris@17 35 * The OEmbed resource fetcher service.
Chris@17 36 *
Chris@17 37 * @var \Drupal\media\OEmbed\ResourceFetcherInterface
Chris@17 38 */
Chris@17 39 protected $resourceFetcher;
Chris@17 40
Chris@17 41 /**
Chris@17 42 * The module handler service.
Chris@17 43 *
Chris@17 44 * @var \Drupal\Core\Extension\ModuleHandlerInterface
Chris@17 45 */
Chris@17 46 protected $moduleHandler;
Chris@17 47
Chris@17 48 /**
Chris@17 49 * Static cache of discovered oEmbed resource URLs, keyed by canonical URL.
Chris@17 50 *
Chris@17 51 * A discovered resource URL is the actual endpoint URL for a specific media
Chris@17 52 * object, fetched from its canonical URL.
Chris@17 53 *
Chris@17 54 * @var string[]
Chris@17 55 */
Chris@17 56 protected $urlCache = [];
Chris@17 57
Chris@17 58 /**
Chris@17 59 * Constructs a UrlResolver object.
Chris@17 60 *
Chris@17 61 * @param \Drupal\media\OEmbed\ProviderRepositoryInterface $providers
Chris@17 62 * The oEmbed provider repository service.
Chris@17 63 * @param \Drupal\media\OEmbed\ResourceFetcherInterface $resource_fetcher
Chris@17 64 * The OEmbed resource fetcher service.
Chris@17 65 * @param \GuzzleHttp\ClientInterface $http_client
Chris@17 66 * The HTTP client.
Chris@17 67 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
Chris@17 68 * The module handler service.
Chris@17 69 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
Chris@17 70 * (optional) The cache backend.
Chris@17 71 */
Chris@17 72 public function __construct(ProviderRepositoryInterface $providers, ResourceFetcherInterface $resource_fetcher, ClientInterface $http_client, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend = NULL) {
Chris@17 73 $this->providers = $providers;
Chris@17 74 $this->resourceFetcher = $resource_fetcher;
Chris@17 75 $this->httpClient = $http_client;
Chris@17 76 $this->moduleHandler = $module_handler;
Chris@17 77 $this->cacheBackend = $cache_backend;
Chris@17 78 $this->useCaches = isset($cache_backend);
Chris@17 79 }
Chris@17 80
Chris@17 81 /**
Chris@17 82 * Runs oEmbed discovery and returns the endpoint URL if successful.
Chris@17 83 *
Chris@17 84 * @param string $url
Chris@17 85 * The resource's URL.
Chris@17 86 *
Chris@17 87 * @return string|bool
Chris@17 88 * URL of the oEmbed endpoint, or FALSE if the discovery was unsuccessful.
Chris@17 89 *
Chris@17 90 * @throws \Drupal\media\OEmbed\ResourceException
Chris@17 91 * If the resource cannot be retrieved.
Chris@17 92 */
Chris@17 93 protected function discoverResourceUrl($url) {
Chris@17 94 try {
Chris@17 95 $response = $this->httpClient->get($url);
Chris@17 96 }
Chris@17 97 catch (RequestException $e) {
Chris@17 98 throw new ResourceException('Could not fetch oEmbed resource.', $url, [], $e);
Chris@17 99 }
Chris@17 100
Chris@17 101 $document = Html::load((string) $response->getBody());
Chris@17 102 $xpath = new \DOMXpath($document);
Chris@17 103
Chris@17 104 return $this->findUrl($xpath, 'json') ?: $this->findUrl($xpath, 'xml');
Chris@17 105 }
Chris@17 106
Chris@17 107 /**
Chris@17 108 * Tries to find the oEmbed URL in a DOM.
Chris@17 109 *
Chris@17 110 * @param \DOMXPath $xpath
Chris@17 111 * Page HTML as DOMXPath.
Chris@17 112 * @param string $format
Chris@17 113 * Format of oEmbed resource. Possible values are 'json' and 'xml'.
Chris@17 114 *
Chris@17 115 * @return bool|string
Chris@17 116 * A URL to an oEmbed resource or FALSE if not found.
Chris@17 117 */
Chris@17 118 protected function findUrl(\DOMXPath $xpath, $format) {
Chris@17 119 $result = $xpath->query("//link[@type='application/$format+oembed']");
Chris@17 120 return $result->length ? $result->item(0)->getAttribute('href') : FALSE;
Chris@17 121 }
Chris@17 122
Chris@17 123 /**
Chris@17 124 * {@inheritdoc}
Chris@17 125 */
Chris@17 126 public function getProviderByUrl($url) {
Chris@17 127 // Check the URL against every scheme of every endpoint of every provider
Chris@17 128 // until we find a match.
Chris@17 129 foreach ($this->providers->getAll() as $provider_name => $provider_info) {
Chris@17 130 foreach ($provider_info->getEndpoints() as $endpoint) {
Chris@17 131 if ($endpoint->matchUrl($url)) {
Chris@17 132 return $provider_info;
Chris@17 133 }
Chris@17 134 }
Chris@17 135 }
Chris@17 136
Chris@17 137 $resource_url = $this->discoverResourceUrl($url);
Chris@17 138 if ($resource_url) {
Chris@17 139 return $this->resourceFetcher->fetchResource($resource_url)->getProvider();
Chris@17 140 }
Chris@17 141
Chris@17 142 throw new ResourceException('No matching provider found.', $url);
Chris@17 143 }
Chris@17 144
Chris@17 145 /**
Chris@17 146 * {@inheritdoc}
Chris@17 147 */
Chris@17 148 public function getResourceUrl($url, $max_width = NULL, $max_height = NULL) {
Chris@17 149 // Try to get the resource URL from the static cache.
Chris@17 150 if (isset($this->urlCache[$url])) {
Chris@17 151 return $this->urlCache[$url];
Chris@17 152 }
Chris@17 153
Chris@17 154 // Try to get the resource URL from the persistent cache.
Chris@17 155 $cache_id = "media:oembed_resource_url:$url:$max_width:$max_height";
Chris@17 156
Chris@17 157 $cached = $this->cacheGet($cache_id);
Chris@17 158 if ($cached) {
Chris@17 159 $this->urlCache[$url] = $cached->data;
Chris@17 160 return $this->urlCache[$url];
Chris@17 161 }
Chris@17 162
Chris@17 163 $provider = $this->getProviderByUrl($url);
Chris@17 164 $endpoints = $provider->getEndpoints();
Chris@17 165 $endpoint = reset($endpoints);
Chris@17 166 $resource_url = $endpoint->buildResourceUrl($url);
Chris@17 167
Chris@17 168 $parsed_url = UrlHelper::parse($resource_url);
Chris@17 169 if ($max_width) {
Chris@17 170 $parsed_url['query']['maxwidth'] = $max_width;
Chris@17 171 }
Chris@17 172 if ($max_height) {
Chris@17 173 $parsed_url['query']['maxheight'] = $max_height;
Chris@17 174 }
Chris@17 175 // Let other modules alter the resource URL, because some oEmbed providers
Chris@17 176 // provide extra parameters in the query string. For example, Instagram also
Chris@17 177 // supports the 'omitscript' parameter.
Chris@17 178 $this->moduleHandler->alter('oembed_resource_url', $parsed_url, $provider);
Chris@17 179 $resource_url = $parsed_url['path'] . '?' . UrlHelper::buildQuery($parsed_url['query']);
Chris@17 180
Chris@17 181 $this->urlCache[$url] = $resource_url;
Chris@17 182 $this->cacheSet($cache_id, $resource_url);
Chris@17 183
Chris@17 184 return $resource_url;
Chris@17 185 }
Chris@17 186
Chris@17 187 }