Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / file_mdm / src / Plugin / FileMetadata / FileMetadataPluginBase.php
1 <?php
2
3 namespace Drupal\file_mdm\Plugin\FileMetadata;
4
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;
14
15 /**
16  * Abstract implementation of a base File Metadata plugin.
17  */
18 abstract class FileMetadataPluginBase extends PluginBase implements FileMetadataPluginInterface {
19
20   /**
21    * The cache service.
22    *
23    * @var \Drupal\Core\Cache\CacheBackendInterface
24    */
25   protected $cache;
26
27   /**
28    * The config factory.
29    *
30    * @var \Drupal\Core\Config\ConfigFactoryInterface
31    */
32   protected $configFactory;
33
34   /**
35    * The URI of the file.
36    *
37    * @var string
38    */
39   protected $uri;
40
41   /**
42    * The local filesystem path to the file.
43    *
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.
47    *
48    * @var string
49    */
50   protected $localTempPath;
51
52   /**
53    * The hash used to reference the URI.
54    *
55    * @var string
56    */
57   protected $hash;
58
59   /**
60    * The metadata of the file.
61    *
62    * @var mixed
63    */
64   protected $metadata = NULL;
65
66   /**
67    * The metadata loading status.
68    *
69    * @var int
70    */
71   protected $isMetadataLoaded = FileMetadataInterface::NOT_LOADED;
72
73   /**
74    * Track if metadata has been changed from version on file.
75    *
76    * @var bool
77    */
78   protected $hasMetadataChangedFromFileVersion = FALSE;
79
80   /**
81    * Track if file metadata on cache needs update.
82    *
83    * @var bool
84    */
85   protected $hasMetadataChangedFromCacheVersion = FALSE;
86
87   /**
88    * Constructs a FileMetadataPluginBase plugin.
89    *
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
97    *   The cache service.
98    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
99    *   The config factory.
100    */
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;
105   }
106
107   /**
108    * {@inheritdoc}
109    */
110   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
111     return new static(
112       $configuration,
113       $plugin_id,
114       $plugin_definition,
115       $container->get('cache.file_mdm'),
116       $container->get('config.factory')
117     );
118   }
119
120   /**
121    * {@inheritdoc}
122    */
123   public static function defaultConfiguration() {
124     return [
125       'cache' => [
126         'override' => FALSE,
127         'settings' => [
128           'enabled' => TRUE,
129           'expiration' => 172800,
130           'disallowed_paths' => [],
131         ],
132       ],
133     ];
134   }
135
136   /**
137    * Gets the configuration object for this plugin.
138    *
139    * @param bool $editable
140    *   If TRUE returns the editable configuration object.
141    *
142    * @return \Drupal\Core\Config\ImmutableConfig|\Drupal\Core\Config\Config
143    *   The ImmutableConfig of the Config object for this plugin.
144    */
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);
149   }
150
151   /**
152    * {@inheritdoc}
153    */
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'],
159     ];
160     $form['cache_details'] = [
161       '#type' => 'details',
162       '#open' => TRUE,
163       '#collapsible' => FALSE,
164       '#title' => $this->t('Metadata caching'),
165       '#tree' => TRUE,
166       '#states' => [
167         'visible' => [
168           ':input[name="' . $this->getPluginId() . '[override]"]' => ['checked' => TRUE],
169         ],
170       ],
171     ];
172     $form['cache_details']['settings'] = [
173       '#type' => 'file_mdm_caching',
174       '#default_value' => $this->configuration['cache']['settings'],
175     ];
176
177     return $form;
178   }
179
180   /**
181    * {@inheritdoc}
182    */
183   public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
184   }
185
186   /**
187    * {@inheritdoc}
188    */
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
194
195     $config = $this->getConfigObject(TRUE);
196     $config->set('configuration', $this->configuration);
197     if ($config->getOriginal('configuration') != $config->get('configuration')) {
198       $config->save();
199     }
200   }
201
202   /**
203    * {@inheritdoc}
204    */
205   public function setUri($uri) {
206     if (!$uri) {
207       throw new FileMetadataException('Missing $uri argument', $this->getPluginId(), __FUNCTION__);
208     }
209     $this->uri = $uri;
210     return $this;
211   }
212
213   /**
214    * {@inheritdoc}
215    */
216   public function getUri() {
217     return $this->uri;
218   }
219
220   /**
221    * {@inheritdoc}
222    */
223   public function setLocalTempPath($temp_path) {
224     $this->localTempPath = $temp_path;
225     return $this;
226   }
227
228   /**
229    * {@inheritdoc}
230    */
231   public function getLocalTempPath() {
232     return $this->localTempPath;
233   }
234
235   /**
236    * {@inheritdoc}
237    */
238   public function setHash($hash) {
239     if (!$hash) {
240       throw new FileMetadataException('Missing $hash argument', $this->getPluginId(), __FUNCTION__);
241     }
242     $this->hash = $hash;
243     return $this;
244   }
245
246   /**
247    * {@inheritdoc}
248    */
249   public function isMetadataLoaded() {
250     return $this->isMetadataLoaded;
251   }
252
253   /**
254    * {@inheritdoc}
255    */
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;
263     }
264     else {
265       $this->isMetadataLoaded = FileMetadataInterface::LOADED_BY_CODE;
266       $this->saveMetadataToCache();
267     }
268     return (bool) $this->metadata;
269   }
270
271   /**
272    * {@inheritdoc}
273    */
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__);
278     }
279     $this->hasMetadataChangedFromFileVersion = FALSE;
280     if (($this->metadata = $this->doGetMetadataFromFile()) === NULL) {
281       $this->isMetadataLoaded = FileMetadataInterface::NOT_LOADED;
282       $this->deleteCachedMetadata();
283     }
284     else {
285       $this->isMetadataLoaded = FileMetadataInterface::LOADED_FROM_FILE;
286       $this->saveMetadataToCache();
287     }
288     return (bool) $this->metadata;
289   }
290
291   /**
292    * Gets file metadata from the file at URI/local path.
293    *
294    * @return mixed
295    *   The metadata retrieved from the file.
296    *
297    * @throws \Drupal\file_mdm\FileMetadataException
298    *   In case there were significant errors reading from file.
299    */
300   abstract protected function doGetMetadataFromFile();
301
302   /**
303    * {@inheritdoc}
304    */
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;
312     }
313     else {
314       $this->metadata = NULL;
315       $this->isMetadataLoaded = FileMetadataInterface::NOT_LOADED;
316     }
317     return (bool) $this->metadata;
318   }
319
320   /**
321    * Checks if file metadata should be cached.
322    *
323    * @return array|bool
324    *   The caching settings array retrieved from configuration if file metadata
325    *   is cacheable, FALSE otherwise.
326    */
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']) {
332         return FALSE;
333       }
334     }
335
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']) {
340         return FALSE;
341       }
342     }
343
344     // URIs without valid scheme, and temporary:// URIs are not cached.
345     if (!file_valid_uri($this->getUri()) || file_uri_scheme($this->getUri()) === 'temporary') {
346       return FALSE;
347     }
348
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())) {
353         return FALSE;
354       }
355     }
356
357     return $settings;
358   }
359
360   /**
361    * {@inheritdoc}
362    */
363   public function getMetadata($key = NULL) {
364     if (!$this->getUri()) {
365       throw new FileMetadataException("No URI specified", $this->getPluginId(), __FUNCTION__);
366     }
367     if (!$this->hash) {
368       throw new FileMetadataException("No hash specified", $this->getPluginId(), __FUNCTION__);
369     }
370     if ($this->metadata === NULL) {
371       // Metadata has not been loaded yet. Try loading it from cache first.
372       $this->loadMetadataFromCache();
373     }
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();
378     }
379     return $this->doGetMetadata($key);
380   }
381
382   /**
383    * Gets a metadata element.
384    *
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.
388    *
389    * @return mixed|null
390    *   The value of the element specified by $key. If $key is NULL, the entire
391    *   metadata. If no metadata is available, return NULL.
392    */
393   abstract protected function doGetMetadata($key = NULL);
394
395   /**
396    * {@inheritdoc}
397    */
398   public function setMetadata($key, $value) {
399     if ($key === NULL) {
400       throw new FileMetadataException("No metadata key specified for file at '{$this->getUri()}'", $this->getPluginId(), __FUNCTION__);
401     }
402     if (!$this->metadata && !$this->getMetadata()) {
403       throw new FileMetadataException("No metadata loaded for file at '{$this->getUri()}'", $this->getPluginId(), __FUNCTION__);
404     }
405     if ($this->doSetMetadata($key, $value)) {
406       $this->hasMetadataChangedFromFileVersion = TRUE;
407       if ($this->isMetadataLoaded === FileMetadataInterface::LOADED_FROM_CACHE) {
408         $this->hasMetadataChangedFromCacheVersion = TRUE;
409       }
410       return TRUE;
411     }
412     return FALSE;
413   }
414
415   /**
416    * Sets a metadata element.
417    *
418    * @param mixed $key
419    *   A key to determine the metadata element to be changed.
420    * @param mixed $value
421    *   The value to change the metadata element to.
422    *
423    * @return bool
424    *   TRUE if metadata was changed successfully, FALSE otherwise.
425    */
426   abstract protected function doSetMetadata($key, $value);
427
428   /**
429    * {@inheritdoc}
430    */
431   public function removeMetadata($key) {
432     if ($key === NULL) {
433       throw new FileMetadataException("No metadata key specified for file at '{$this->getUri()}'", $this->getPluginId(), __FUNCTION__);
434     }
435     if (!$this->metadata && !$this->getMetadata()) {
436       throw new FileMetadataException("No metadata loaded for file at '{$this->getUri()}'", $this->getPluginId(), __FUNCTION__);
437     }
438     if ($this->doRemoveMetadata($key)) {
439       $this->hasMetadataChangedFromFileVersion = TRUE;
440       if ($this->isMetadataLoaded === FileMetadataInterface::LOADED_FROM_CACHE) {
441         $this->hasMetadataChangedFromCacheVersion = TRUE;
442       }
443       return TRUE;
444     }
445     return FALSE;
446   }
447
448   /**
449    * Removes a metadata element.
450    *
451    * @param mixed $key
452    *   A key to determine the metadata element to be removed.
453    *
454    * @return bool
455    *   TRUE if metadata was removed successfully, FALSE otherwise.
456    */
457   abstract protected function doRemoveMetadata($key);
458
459   /**
460    * {@inheritdoc}
461    */
462   public function isSaveToFileSupported() {
463     return FALSE;
464   }
465
466   /**
467    * {@inheritdoc}
468    */
469   public function saveMetadataToFile() {
470     if (!$this->isSaveToFileSupported()) {
471       throw new FileMetadataException('Write metadata to file is not supported', $this->getPluginId(), __FUNCTION__);
472     }
473     if ($this->metadata === NULL) {
474       return FALSE;
475     }
476     if ($this->hasMetadataChangedFromFileVersion) {
477       // Clears cache so that next time metadata will be fetched from file.
478       $this->deleteCachedMetadata();
479       return $this->doSaveMetadataToFile();
480     }
481     return FALSE;
482   }
483
484   /**
485    * Saves metadata to file at URI.
486    *
487    * @return bool
488    *   TRUE if metadata was saved successfully, FALSE otherwise.
489    */
490   protected function doSaveMetadataToFile() {
491     return FALSE;
492   }
493
494   /**
495    * {@inheritdoc}
496    */
497   public function saveMetadataToCache(array $tags = []) {
498     if ($this->metadata === NULL) {
499       return FALSE;
500     }
501     if (($cache_settings = $this->isUriFileMetadataCacheable()) === FALSE) {
502       return FALSE;
503     }
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;
510       return TRUE;
511     }
512     return FALSE;
513   }
514
515   /**
516    * Gets metadata to save to cache.
517    *
518    * @return mixed
519    *   The metadata to be cached.
520    */
521   protected function getMetadataToCache() {
522     return $this->metadata;
523   }
524
525   /**
526    * {@inheritdoc}
527    */
528   public function deleteCachedMetadata() {
529     if ($this->isUriFileMetadataCacheable() === FALSE) {
530       return FALSE;
531     }
532     $plugin_id = $this->getPluginId();
533     $this->cache->delete("hash:{$plugin_id}:{$this->hash}");
534     $this->hasMetadataChangedFromCacheVersion = FALSE;
535     return TRUE;
536   }
537
538 }