Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / file_mdm / file_mdm_exif / src / Plugin / FileMetadata / Exif.php
1 <?php
2
3 namespace Drupal\file_mdm_exif\Plugin\FileMetadata;
4
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\file_mdm\Plugin\FileMetadata\FileMetadataPluginBase;
8 use Drupal\file_mdm_exif\ExifTagMapperInterface;
9 use Symfony\Component\DependencyInjection\ContainerInterface;
10 use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
11 use lsolesen\pel\PelEntry;
12 use lsolesen\pel\PelExif;
13 use lsolesen\pel\PelIfd;
14 use lsolesen\pel\PelJpeg;
15 use lsolesen\pel\PelTiff;
16
17 /**
18  * FileMetadata plugin for EXIF.
19  *
20  * @FileMetadata(
21  *   id = "exif",
22  *   title = @Translation("EXIF"),
23  *   help = @Translation("File metadata plugin for EXIF image information, using the PHP Exif Library (PEL)."),
24  * )
25  */
26 class Exif extends FileMetadataPluginBase {
27
28   /**
29    * The MIME type guessing service.
30    *
31    * @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface
32    */
33   protected $mimeTypeGuesser;
34
35   /**
36    * The EXIF tag mapping service.
37    *
38    * @var \Drupal\file_mdm_exif\ExifTagMapperInterface
39    */
40   protected $tagMapper;
41
42   /**
43    * The PEL file object being processed.
44    *
45    * @var \lsolesen\pel\PelJpeg|\lsolesen\pel\PelTiff
46    */
47   protected $pelFile;
48
49   /**
50    * Constructs an Exif file metadata plugin.
51    *
52    * @param array $configuration
53    *   A configuration array containing information about the plugin instance.
54    * @param string $plugin_id
55    *   The plugin_id for the plugin instance.
56    * @param array $plugin_definition
57    *   The plugin implementation definition.
58    * @param \Drupal\Core\Cache\CacheBackendInterface $cache_service
59    *   The cache service.
60    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
61    *   The config factory.
62    * @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $mime_type_guesser
63    *   The MIME type mapping service.
64    * @param \Drupal\file_mdm_exif\ExifTagMapperInterface $tag_mapper
65    *   The EXIF tag mapping service.
66    */
67   public function __construct(array $configuration, $plugin_id, array $plugin_definition, CacheBackendInterface $cache_service, ConfigFactoryInterface $config_factory, MimeTypeGuesserInterface $mime_type_guesser, ExifTagMapperInterface $tag_mapper) {
68     parent::__construct($configuration, $plugin_id, $plugin_definition, $cache_service, $config_factory);
69     $this->mimeTypeGuesser = $mime_type_guesser;
70     $this->tagMapper = $tag_mapper;
71   }
72
73   /**
74    * {@inheritdoc}
75    */
76   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
77     return new static(
78       $configuration,
79       $plugin_id,
80       $plugin_definition,
81       $container->get('cache.file_mdm'),
82       $container->get('config.factory'),
83       $container->get('file.mime_type.guesser'),
84       $container->get('file_mdm_exif.tag_mapper')
85     );
86   }
87
88   /**
89    * {@inheritdoc}
90    */
91   public function getSupportedKeys($options = NULL) {
92     return $this->tagMapper->getSupportedKeys($options);
93   }
94
95   /**
96    * Returns the PEL file object for the image file.
97    *
98    * @return \lsolesen\pel\PelJpeg|\lsolesen\pel\PelTiff
99    *   A PEL file object.
100    */
101   protected function getFile() {
102     if ($this->pelFile !== NULL) {
103       return $this->pelFile;
104     }
105     else {
106       switch ($this->mimeTypeGuesser->guess($this->getUri())) {
107         case 'image/jpeg':
108           $this->pelFile = new PelJpeg($this->getLocalTempPath());
109           return $this->pelFile !== NULL ? $this->pelFile : FALSE;
110
111         case 'image/tiff':
112           $this->pelFile = new PelTiff($this->getLocalTempPath());
113           return $this->pelFile !== NULL ? $this->pelFile : FALSE;
114
115         default:
116           return FALSE;
117
118       }
119     }
120   }
121
122   /**
123    * {@inheritdoc}
124    */
125   protected function doGetMetadataFromFile() {
126     // Get the file as a PelJpeg or PelTiff object.
127     $file = $this->getFile();
128     if (!$file) {
129       return [];
130     }
131
132     // Get the TIFF section if existing, or return if not.
133     if ($file instanceof PelJpeg) {
134       $exif = $file->getExif();
135       if ($exif === NULL) {
136         return [];
137       }
138       $tiff = $exif->getTiff();
139       if ($tiff === NULL) {
140         return [];
141       }
142     }
143     elseif ($file instanceof PelTiff) {
144       $tiff = $file;
145     }
146
147     // Scans metadata for entries of supported tags.
148     $metadata = [];
149     $keys = $this->tagMapper->getSupportedKeys();
150     foreach ($keys as $key) {
151       $ifd_tag = $this->tagMapper->resolveKeyToIfdAndTag($key);
152       if ($entry = $this->getEntry($tiff, $ifd_tag['ifd'], $ifd_tag['tag'])) {
153         $metadata[$ifd_tag['ifd']][$ifd_tag['tag']] = $entry;
154       }
155     }
156     return $metadata;
157   }
158
159   /**
160    * Returns a PelEntry.
161    *
162    * @param \lsolesen\pel\PelTiff $tiff
163    *   A PelTiff object.
164    * @param int $ifd_tag
165    *   The IFD EXIF integer identifier.
166    * @param int $key_tag
167    *   The TAG EXIF integer identifier.
168    *
169    * @return \lsolesen\pel\PelEntry
170    *   The PelEntry for the specified IFD and TAG.
171    */
172   protected function getEntry(PelTiff $tiff, $ifd_tag, $key_tag) {
173     $ifd = $tiff->getIfd();
174     switch ($ifd_tag) {
175       case PelIfd::IFD0:
176         return $ifd->getEntry($key_tag);
177
178       case PelIfd::IFD1:
179         $ifd1 = $ifd->getNextIfd();
180         if (!$ifd1) {
181           return NULL;
182         }
183         return $ifd1->getEntry($key_tag);
184
185       case PelIfd::EXIF:
186         $exif = $ifd->getSubIfd(PelIfd::EXIF);
187         if (!$exif) {
188           return NULL;
189         }
190         return $exif->getEntry($key_tag);
191
192       case PelIfd::INTEROPERABILITY:
193         $exif = $ifd->getSubIfd(PelIfd::EXIF);
194         if (!$exif) {
195           return NULL;
196         }
197         $interop = $exif->getSubIfd(PelIfd::INTEROPERABILITY);
198         if (!$interop) {
199           return NULL;
200         }
201         return $interop->getEntry($key_tag);
202
203       case PelIfd::GPS:
204         $gps = $ifd->getSubIfd(PelIfd::GPS);
205         if (!$gps) {
206           return NULL;
207         }
208         return $gps->getEntry($key_tag);
209
210     }
211   }
212
213   /**
214    * {@inheritdoc}
215    */
216   public function isSaveToFileSupported() {
217     return TRUE;
218   }
219
220   /**
221    * {@inheritdoc}
222    */
223   protected function doSaveMetadataToFile() {
224     // Get the file as a PelJpeg or PelTiff object.
225     $file = $this->getFile();
226     if (!$file) {
227       return FALSE;
228     }
229
230     // Get the TIFF section if existing, or create one if not.
231     if ($file instanceof PelJpeg) {
232       $exif = $file->getExif();
233       if ($exif === NULL) {
234         // If EXIF section is missing we simply create a new APP1 section
235         // (a PelExif object) and add it to the PelJpeg object.
236         $exif = new PelExif();
237         $file->setExif($exif);
238       }
239       $tiff = $exif->getTiff();
240       if ($tiff === NULL) {
241         // Same for TIFF section.
242         $tiff = new PelTiff();
243         $exif->setTiff($tiff);
244       }
245     }
246     elseif ($file instanceof PelTiff) {
247       $tiff = $file;
248     }
249
250     // Get IFD0 if existing, or create it if not.
251     $ifd0 = $tiff->getIfd();
252     if ($ifd0 === NULL) {
253       // No IFD in the TIFF data, we just create and insert an empty PelIfd
254       // object.
255       $ifd0 = new PelIfd(PelIfd::IFD0);
256       $tiff->setIfd($ifd0);
257     }
258
259     // Loops through in-memory metadata and update tag entries accordingly.
260     foreach ($this->metadata as $ifd_id => $entries) {
261       switch ($ifd_id) {
262         case PelIfd::IFD0:
263           $this->setIfdEntries($ifd0, $entries);
264           break;
265
266         case PelIfd::IFD1:
267           $ifd1 = $ifd0->getNextIfd();
268           if ($ifd1 === NULL) {
269             $ifd1 = new PelIfd(PelIfd::IFD1);
270             $ifd0->setNextIfd($ifd1);
271           }
272           $this->setIfdEntries($ifd1, $entries);
273           break;
274
275         case PelIfd::EXIF:
276           $exif = $ifd0->getSubIfd(PelIfd::EXIF);
277           if ($exif === NULL) {
278             $exif = new PelIfd(PelIfd::EXIF);
279             $ifd0->addSubIfd($exif);
280           }
281           $this->setIfdEntries($exif, $entries);
282           break;
283
284         case PelIfd::INTEROPERABILITY:
285           $exif = $ifd0->getSubIfd(PelIfd::EXIF);
286           if ($exif === NULL) {
287             $exif = new PelIfd(PelIfd::EXIF);
288             $ifd0->addSubIfd($exif);
289           }
290           $interop = $exif->getSubIfd(PelIfd::INTEROPERABILITY);
291           if ($interop === NULL) {
292             $interop = new PelIfd(PelIfd::INTEROPERABILITY);
293             $exif->addSubIfd($interop);
294           }
295           $this->setIfdEntries($interop, $entries);
296           break;
297
298         case PelIfd::GPS:
299           $gps = $ifd0->getSubIfd(PelIfd::GPS);
300           if ($gps === NULL) {
301             $gps = new PelIfd(PelIfd::GPS);
302             $ifd0->addSubIfd($gps);
303           }
304           $this->setIfdEntries($gps, $entries);
305           break;
306
307       }
308     }
309
310     return $file->saveFile($this->getLocalTempPath()) === FALSE ? FALSE : TRUE;
311   }
312
313   /**
314    * Adds or changes entries for an IFD.
315    *
316    * @param lsolesen\pel\PelIfd $ifd
317    *   A PelIfd object.
318    * @param lsolesen\pel\PelEntry[] $entries
319    *   An array of PelEntry objects.
320    *
321    * @return bool
322    *   TRUE if entries were added/changed successfully, FALSE otherwise.
323    */
324   protected function setIfdEntries(PelIfd $ifd, array $entries) {
325     foreach ($entries as $tag => $input_entry) {
326       if ($c = $ifd->getEntry($tag)) {
327         if ($input_entry === 'deleted') {
328           unset($ifd[$tag]);
329         }
330         else {
331           $c->setValue($input_entry->getValue());
332         }
333       }
334       else {
335         if ($input_entry !== 'deleted') {
336           $ifd->addEntry($input_entry);
337         }
338       }
339     }
340     return TRUE;
341   }
342
343   /**
344    * {@inheritdoc}
345    */
346   protected function doGetMetadata($key = NULL) {
347     if (!$this->metadata) {
348       return NULL;
349     }
350     if (!$key) {
351       return $this->metadata;
352     }
353     else {
354       $ifd_tag = $this->tagMapper->resolveKeyToIfdAndTag($key);
355       if (!isset($this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']]) || $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']] === 'deleted') {
356         return NULL;
357       }
358       $entry = $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']];
359       return [
360         'value' => $entry->getValue(),
361         'text' => $entry->getText(),
362       ];
363     }
364   }
365
366   /**
367    * {@inheritdoc}
368    */
369   protected function doSetMetadata($key, $value) {
370     $ifd_tag = $this->tagMapper->resolveKeyToIfdAndTag($key);
371     if ($value instanceof PelEntry) {
372       $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']] = $value;
373       return TRUE;
374     }
375     elseif (isset($this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']])) {
376       $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']]->setValue($value);
377       return TRUE;
378     }
379     return FALSE;
380   }
381
382   /**
383    * {@inheritdoc}
384    */
385   protected function doRemoveMetadata($key) {
386     if (!$this->metadata || !$key) {
387       return FALSE;
388     }
389     else {
390       $ifd_tag = $this->tagMapper->resolveKeyToIfdAndTag($key);
391       if (isset($this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']])) {
392         $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']] = 'deleted';
393         return TRUE;
394       }
395       return FALSE;
396     }
397   }
398
399 }