3 namespace Drupal\file_mdm\Plugin\FileMetadata;
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\Cache\CacheBackendInterface;
7 use Drupal\Core\Config\ConfigFactoryInterface;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\Core\Plugin\PluginBase;
10 use Drupal\file_mdm\FileMetadataException;
11 use Drupal\file_mdm\FileMetadataInterface;
12 use Drupal\file_mdm\Plugin\FileMetadataPluginInterface;
13 use Symfony\Component\DependencyInjection\ContainerInterface;
16 * Abstract implementation of a base File Metadata plugin.
18 abstract class FileMetadataPluginBase extends PluginBase implements FileMetadataPluginInterface {
23 * @var \Drupal\Core\Cache\CacheBackendInterface
30 * @var \Drupal\Core\Config\ConfigFactoryInterface
32 protected $configFactory;
35 * The URI of the file.
42 * The local filesystem path to the file.
44 * This is used to allow accessing local copies of files stored remotely, to
45 * minimise remote calls and allow functions that cannot access remote stream
46 * wrappers to operate locally.
50 protected $localTempPath;
53 * The hash used to reference the URI.
60 * The metadata of the file.
64 protected $metadata = NULL;
67 * The metadata loading status.
71 protected $isMetadataLoaded = FileMetadataInterface::NOT_LOADED;
74 * Track if metadata has been changed from version on file.
78 protected $hasMetadataChangedFromFileVersion = FALSE;
81 * Track if file metadata on cache needs update.
85 protected $hasMetadataChangedFromCacheVersion = FALSE;
88 * Constructs a FileMetadataPluginBase plugin.
90 * @param array $configuration
91 * A configuration array containing information about the plugin instance.
92 * @param string $plugin_id
93 * The plugin_id for the plugin instance.
94 * @param array $plugin_definition
95 * The plugin implementation definition.
96 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_service
98 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
101 public function __construct(array $configuration, $plugin_id, array $plugin_definition, CacheBackendInterface $cache_service, ConfigFactoryInterface $config_factory) {
102 parent::__construct($configuration, $plugin_id, $plugin_definition);
103 $this->cache = $cache_service;
104 $this->configFactory = $config_factory;
110 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
115 $container->get('cache.file_mdm'),
116 $container->get('config.factory')
123 public static function defaultConfiguration() {
129 'expiration' => 172800,
130 'disallowed_paths' => [],
137 * Gets the configuration object for this plugin.
139 * @param bool $editable
140 * If TRUE returns the editable configuration object.
142 * @return \Drupal\Core\Config\ImmutableConfig|\Drupal\Core\Config\Config
143 * The ImmutableConfig of the Config object for this plugin.
145 protected function getConfigObject($editable = FALSE) {
146 $plugin_definition = $this->getPluginDefinition();
147 $config_name = $plugin_definition['provider'] . '.file_metadata_plugin.' . $plugin_definition['id'];
148 return $editable ? $this->configFactory->getEditable($config_name) : $this->configFactory->get($config_name);
154 public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
155 $form['override'] = [
156 '#type' => 'checkbox',
157 '#title' => $this->t('Override main caching settings'),
158 '#default_value' => $this->configuration['cache']['override'],
160 $form['cache_details'] = [
161 '#type' => 'details',
163 '#collapsible' => FALSE,
164 '#title' => $this->t('Metadata caching'),
168 ':input[name="' . $this->getPluginId() . '[override]"]' => ['checked' => TRUE],
172 $form['cache_details']['settings'] = [
173 '#type' => 'file_mdm_caching',
174 '#default_value' => $this->configuration['cache']['settings'],
183 public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
189 public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
190 // @codingStandardsIgnoreStart
191 $this->configuration['cache']['override'] = (bool) $form_state->getValue([$this->getPluginId(), 'override']);
192 $this->configuration['cache']['settings'] = $form_state->getValue([$this->getPluginId(), 'cache_details', 'settings']);
193 // @codingStandardsIgnoreEnd
195 $config = $this->getConfigObject(TRUE);
196 $config->set('configuration', $this->configuration);
197 if ($config->getOriginal('configuration') != $config->get('configuration')) {
205 public function setUri($uri) {
207 throw new FileMetadataException('Missing $uri argument', $this->getPluginId(), __FUNCTION__);
216 public function getUri() {
223 public function setLocalTempPath($temp_path) {
224 $this->localTempPath = $temp_path;
231 public function getLocalTempPath() {
232 return $this->localTempPath;
238 public function setHash($hash) {
240 throw new FileMetadataException('Missing $hash argument', $this->getPluginId(), __FUNCTION__);
249 public function isMetadataLoaded() {
250 return $this->isMetadataLoaded;
256 public function loadMetadata($metadata) {
257 $this->metadata = $metadata;
258 $this->hasMetadataChangedFromFileVersion = TRUE;
259 $this->hasMetadataChangedFromCacheVersion = TRUE;
260 $this->deleteCachedMetadata();
261 if ($this->metadata === NULL) {
262 $this->isMetadataLoaded = FileMetadataInterface::NOT_LOADED;
265 $this->isMetadataLoaded = FileMetadataInterface::LOADED_BY_CODE;
266 $this->saveMetadataToCache();
268 return (bool) $this->metadata;
274 public function loadMetadataFromFile() {
275 if (!file_exists($this->getLocalTempPath())) {
276 // File does not exists.
277 throw new FileMetadataException("File at '{$this->getLocalTempPath()}' does not exist", $this->getPluginId(), __FUNCTION__);
279 $this->hasMetadataChangedFromFileVersion = FALSE;
280 if (($this->metadata = $this->doGetMetadataFromFile()) === NULL) {
281 $this->isMetadataLoaded = FileMetadataInterface::NOT_LOADED;
282 $this->deleteCachedMetadata();
285 $this->isMetadataLoaded = FileMetadataInterface::LOADED_FROM_FILE;
286 $this->saveMetadataToCache();
288 return (bool) $this->metadata;
292 * Gets file metadata from the file at URI/local path.
295 * The metadata retrieved from the file.
297 * @throws \Drupal\file_mdm\FileMetadataException
298 * In case there were significant errors reading from file.
300 abstract protected function doGetMetadataFromFile();
305 public function loadMetadataFromCache() {
306 $plugin_id = $this->getPluginId();
307 $this->hasMetadataChangedFromFileVersion = FALSE;
308 $this->hasMetadataChangedFromCacheVersion = FALSE;
309 if ($this->isUriFileMetadataCacheable() !== FALSE && ($cache = $this->cache->get("hash:{$plugin_id}:{$this->hash}"))) {
310 $this->metadata = $cache->data;
311 $this->isMetadataLoaded = FileMetadataInterface::LOADED_FROM_CACHE;
314 $this->metadata = NULL;
315 $this->isMetadataLoaded = FileMetadataInterface::NOT_LOADED;
317 return (bool) $this->metadata;
321 * Checks if file metadata should be cached.
324 * The caching settings array retrieved from configuration if file metadata
325 * is cacheable, FALSE otherwise.
327 protected function isUriFileMetadataCacheable() {
328 // Check plugin settings first, if they override general settings.
329 if ($this->configuration['cache']['override']) {
330 $settings = $this->configuration['cache']['settings'];
331 if (!$settings['enabled']) {
336 // Use general settings if they are not overridden by plugin.
337 if (!isset($settings)) {
338 $settings = $this->configFactory->get('file_mdm.settings')->get('metadata_cache');
339 if (!$settings['enabled']) {
344 // URIs without valid scheme, and temporary:// URIs are not cached.
345 if (!file_valid_uri($this->getUri()) || file_uri_scheme($this->getUri()) === 'temporary') {
349 // URIs falling into disallowed paths are not cached.
350 foreach ($settings['disallowed_paths'] as $pattern) {
351 $p = "#^" . strtr(preg_quote($pattern, '#'), ['\*' => '.*', '\?' => '.']) . "$#i";
352 if (preg_match($p, $this->getUri())) {
363 public function getMetadata($key = NULL) {
364 if (!$this->getUri()) {
365 throw new FileMetadataException("No URI specified", $this->getPluginId(), __FUNCTION__);
368 throw new FileMetadataException("No hash specified", $this->getPluginId(), __FUNCTION__);
370 if ($this->metadata === NULL) {
371 // Metadata has not been loaded yet. Try loading it from cache first.
372 $this->loadMetadataFromCache();
374 if ($this->metadata === NULL && $this->isMetadataLoaded !== FileMetadataInterface::LOADED_FROM_FILE) {
375 // Metadata has not been loaded yet. Try loading it from file if URI is
376 // defined and a read attempt was not made yet.
377 $this->loadMetadataFromFile();
379 return $this->doGetMetadata($key);
383 * Gets a metadata element.
385 * @param mixed|null $key
386 * A key to determine the metadata element to be returned. If NULL, the
387 * entire metadata will be returned.
390 * The value of the element specified by $key. If $key is NULL, the entire
391 * metadata. If no metadata is available, return NULL.
393 abstract protected function doGetMetadata($key = NULL);
398 public function setMetadata($key, $value) {
400 throw new FileMetadataException("No metadata key specified for file at '{$this->getUri()}'", $this->getPluginId(), __FUNCTION__);
402 if (!$this->metadata && !$this->getMetadata()) {
403 throw new FileMetadataException("No metadata loaded for file at '{$this->getUri()}'", $this->getPluginId(), __FUNCTION__);
405 if ($this->doSetMetadata($key, $value)) {
406 $this->hasMetadataChangedFromFileVersion = TRUE;
407 if ($this->isMetadataLoaded === FileMetadataInterface::LOADED_FROM_CACHE) {
408 $this->hasMetadataChangedFromCacheVersion = TRUE;
416 * Sets a metadata element.
419 * A key to determine the metadata element to be changed.
420 * @param mixed $value
421 * The value to change the metadata element to.
424 * TRUE if metadata was changed successfully, FALSE otherwise.
426 abstract protected function doSetMetadata($key, $value);
431 public function removeMetadata($key) {
433 throw new FileMetadataException("No metadata key specified for file at '{$this->getUri()}'", $this->getPluginId(), __FUNCTION__);
435 if (!$this->metadata && !$this->getMetadata()) {
436 throw new FileMetadataException("No metadata loaded for file at '{$this->getUri()}'", $this->getPluginId(), __FUNCTION__);
438 if ($this->doRemoveMetadata($key)) {
439 $this->hasMetadataChangedFromFileVersion = TRUE;
440 if ($this->isMetadataLoaded === FileMetadataInterface::LOADED_FROM_CACHE) {
441 $this->hasMetadataChangedFromCacheVersion = TRUE;
449 * Removes a metadata element.
452 * A key to determine the metadata element to be removed.
455 * TRUE if metadata was removed successfully, FALSE otherwise.
457 abstract protected function doRemoveMetadata($key);
462 public function isSaveToFileSupported() {
469 public function saveMetadataToFile() {
470 if (!$this->isSaveToFileSupported()) {
471 throw new FileMetadataException('Write metadata to file is not supported', $this->getPluginId(), __FUNCTION__);
473 if ($this->metadata === NULL) {
476 if ($this->hasMetadataChangedFromFileVersion) {
477 // Clears cache so that next time metadata will be fetched from file.
478 $this->deleteCachedMetadata();
479 return $this->doSaveMetadataToFile();
485 * Saves metadata to file at URI.
488 * TRUE if metadata was saved successfully, FALSE otherwise.
490 protected function doSaveMetadataToFile() {
497 public function saveMetadataToCache(array $tags = []) {
498 if ($this->metadata === NULL) {
501 if (($cache_settings = $this->isUriFileMetadataCacheable()) === FALSE) {
504 if ($this->isMetadataLoaded !== FileMetadataInterface::LOADED_FROM_CACHE || ($this->isMetadataLoaded === FileMetadataInterface::LOADED_FROM_CACHE && $this->hasMetadataChangedFromCacheVersion)) {
505 $tags = Cache::mergeTags($tags, $this->getConfigObject()->getCacheTags());
506 $tags = Cache::mergeTags($tags, $this->configFactory->get('file_mdm.settings')->getCacheTags());
507 $expire = $cache_settings['expiration'] === -1 ? Cache::PERMANENT : time() + $cache_settings['expiration'];
508 $this->cache->set("hash:{$this->getPluginId()}:{$this->hash}", $this->getMetadataToCache(), $expire, $tags);
509 $this->hasMetadataChangedFromCacheVersion = FALSE;
516 * Gets metadata to save to cache.
519 * The metadata to be cached.
521 protected function getMetadataToCache() {
522 return $this->metadata;
528 public function deleteCachedMetadata() {
529 if ($this->isUriFileMetadataCacheable() === FALSE) {
532 $plugin_id = $this->getPluginId();
533 $this->cache->delete("hash:{$plugin_id}:{$this->hash}");
534 $this->hasMetadataChangedFromCacheVersion = FALSE;