diff core/modules/media/src/OEmbed/ResourceFetcher.php @ 17:129ea1e6d783

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:21:36 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/modules/media/src/OEmbed/ResourceFetcher.php	Thu Feb 28 13:21:36 2019 +0000
@@ -0,0 +1,233 @@
+<?php
+
+namespace Drupal\media\OEmbed;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\UseCacheBackendTrait;
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Exception\RequestException;
+
+/**
+ * Fetches and caches oEmbed resources.
+ */
+class ResourceFetcher implements ResourceFetcherInterface {
+
+  use UseCacheBackendTrait;
+
+  /**
+   * The HTTP client.
+   *
+   * @var \GuzzleHttp\Client
+   */
+  protected $httpClient;
+
+  /**
+   * The oEmbed provider repository service.
+   *
+   * @var \Drupal\media\OEmbed\ProviderRepositoryInterface
+   */
+  protected $providers;
+
+  /**
+   * Constructs a ResourceFetcher object.
+   *
+   * @param \GuzzleHttp\ClientInterface $http_client
+   *   The HTTP client.
+   * @param \Drupal\media\OEmbed\ProviderRepositoryInterface $providers
+   *   The oEmbed provider repository service.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   (optional) The cache backend.
+   */
+  public function __construct(ClientInterface $http_client, ProviderRepositoryInterface $providers, CacheBackendInterface $cache_backend = NULL) {
+    $this->httpClient = $http_client;
+    $this->providers = $providers;
+    $this->cacheBackend = $cache_backend;
+    $this->useCaches = isset($cache_backend);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchResource($url) {
+    $cache_id = "media:oembed_resource:$url";
+
+    $cached = $this->cacheGet($cache_id);
+    if ($cached) {
+      return $this->createResource($cached->data, $url);
+    }
+
+    try {
+      $response = $this->httpClient->get($url);
+    }
+    catch (RequestException $e) {
+      throw new ResourceException('Could not retrieve the oEmbed resource.', $url, [], $e);
+    }
+
+    list($format) = $response->getHeader('Content-Type');
+    $content = (string) $response->getBody();
+
+    if (strstr($format, 'text/xml') || strstr($format, 'application/xml')) {
+      $data = $this->parseResourceXml($content, $url);
+    }
+    elseif (strstr($format, 'text/javascript') || strstr($format, 'application/json')) {
+      $data = Json::decode($content);
+    }
+    // If the response is neither XML nor JSON, we are in bat country.
+    else {
+      throw new ResourceException('The fetched resource did not have a valid Content-Type header.', $url);
+    }
+
+    $this->cacheSet($cache_id, $data);
+
+    return $this->createResource($data, $url);
+  }
+
+  /**
+   * Creates a Resource object from raw resource data.
+   *
+   * @param array $data
+   *   The resource data returned by the provider.
+   * @param string $url
+   *   The URL of the resource.
+   *
+   * @return \Drupal\media\OEmbed\Resource
+   *   A value object representing the resource.
+   *
+   * @throws \Drupal\media\OEmbed\ResourceException
+   *   If the resource cannot be created.
+   */
+  protected function createResource(array $data, $url) {
+    $data += [
+      'title' => NULL,
+      'author_name' => NULL,
+      'author_url' => NULL,
+      'provider_name' => NULL,
+      'cache_age' => NULL,
+      'thumbnail_url' => NULL,
+      'thumbnail_width' => NULL,
+      'thumbnail_height' => NULL,
+      'width' => NULL,
+      'height' => NULL,
+      'url' => NULL,
+      'html' => NULL,
+      'version' => NULL,
+    ];
+
+    if ($data['version'] !== '1.0') {
+      throw new ResourceException("Resource version must be '1.0'", $url, $data);
+    }
+
+    // Prepare the arguments to pass to the factory method.
+    $provider = $data['provider_name'] ? $this->providers->get($data['provider_name']) : NULL;
+
+    // The Resource object will validate the data we create it with and throw an
+    // exception if anything looks wrong. For better debugging, catch those
+    // exceptions and wrap them in a more specific and useful exception.
+    try {
+      switch ($data['type']) {
+        case Resource::TYPE_LINK:
+          return Resource::link(
+            $data['url'],
+            $provider,
+            $data['title'],
+            $data['author_name'],
+            $data['author_url'],
+            $data['cache_age'],
+            $data['thumbnail_url'],
+            $data['thumbnail_width'],
+            $data['thumbnail_height']
+          );
+
+        case Resource::TYPE_PHOTO:
+          return Resource::photo(
+            $data['url'],
+            $data['width'],
+            $data['height'],
+            $provider,
+            $data['title'],
+            $data['author_name'],
+            $data['author_url'],
+            $data['cache_age'],
+            $data['thumbnail_url'],
+            $data['thumbnail_width'],
+            $data['thumbnail_height']
+          );
+
+        case Resource::TYPE_RICH:
+          return Resource::rich(
+            $data['html'],
+            $data['width'],
+            $data['height'],
+            $provider,
+            $data['title'],
+            $data['author_name'],
+            $data['author_url'],
+            $data['cache_age'],
+            $data['thumbnail_url'],
+            $data['thumbnail_width'],
+            $data['thumbnail_height']
+          );
+        case Resource::TYPE_VIDEO:
+          return Resource::video(
+            $data['html'],
+            $data['width'],
+            $data['height'],
+            $provider,
+            $data['title'],
+            $data['author_name'],
+            $data['author_url'],
+            $data['cache_age'],
+            $data['thumbnail_url'],
+            $data['thumbnail_width'],
+            $data['thumbnail_height']
+          );
+
+        default:
+          throw new ResourceException('Unknown resource type: ' . $data['type'], $url, $data);
+      }
+    }
+    catch (\InvalidArgumentException $e) {
+      throw new ResourceException($e->getMessage(), $url, $data, $e);
+    }
+  }
+
+  /**
+   * Parses XML resource data.
+   *
+   * @param string $data
+   *   The raw XML for the resource.
+   * @param string $url
+   *   The resource URL.
+   *
+   * @return array
+   *   The parsed resource data.
+   *
+   * @throws \Drupal\media\OEmbed\ResourceException
+   *   If the resource data could not be parsed.
+   */
+  protected function parseResourceXml($data, $url) {
+    // Enable userspace error handling.
+    $was_using_internal_errors = libxml_use_internal_errors(TRUE);
+    libxml_clear_errors();
+
+    $content = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
+    // Restore the previous error handling behavior.
+    libxml_use_internal_errors($was_using_internal_errors);
+
+    $error = libxml_get_last_error();
+    if ($error) {
+      libxml_clear_errors();
+      throw new ResourceException($error->message, $url);
+    }
+    elseif ($content === FALSE) {
+      throw new ResourceException('The fetched resource could not be parsed.', $url);
+    }
+
+    // Convert XML to JSON so that the parsed resource has a consistent array
+    // structure, regardless of any XML attributes or quirks of the XML parser.
+    $data = Json::encode($content);
+    return Json::decode($data);
+  }
+
+}