Mercurial > hg > cmmr2012-drupal-site
comparison core/modules/image/src/Entity/ImageStyle.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\image\Entity; | |
4 | |
5 use Drupal\Core\Cache\Cache; | |
6 use Drupal\Core\Config\Entity\ConfigEntityBase; | |
7 use Drupal\Core\Entity\EntityStorageInterface; | |
8 use Drupal\Core\Entity\EntityWithPluginCollectionInterface; | |
9 use Drupal\Core\Routing\RequestHelper; | |
10 use Drupal\Core\Site\Settings; | |
11 use Drupal\Core\Url; | |
12 use Drupal\image\ImageEffectPluginCollection; | |
13 use Drupal\image\ImageEffectInterface; | |
14 use Drupal\image\ImageStyleInterface; | |
15 use Drupal\Component\Utility\Crypt; | |
16 use Drupal\Component\Utility\UrlHelper; | |
17 use Drupal\Component\Utility\Unicode; | |
18 use Drupal\Core\StreamWrapper\StreamWrapperInterface; | |
19 use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; | |
20 use Drupal\Core\Entity\Entity\EntityViewDisplay; | |
21 | |
22 /** | |
23 * Defines an image style configuration entity. | |
24 * | |
25 * @ConfigEntityType( | |
26 * id = "image_style", | |
27 * label = @Translation("Image style"), | |
28 * handlers = { | |
29 * "form" = { | |
30 * "add" = "Drupal\image\Form\ImageStyleAddForm", | |
31 * "edit" = "Drupal\image\Form\ImageStyleEditForm", | |
32 * "delete" = "Drupal\image\Form\ImageStyleDeleteForm", | |
33 * "flush" = "Drupal\image\Form\ImageStyleFlushForm" | |
34 * }, | |
35 * "list_builder" = "Drupal\image\ImageStyleListBuilder", | |
36 * "storage" = "Drupal\image\ImageStyleStorage", | |
37 * }, | |
38 * admin_permission = "administer image styles", | |
39 * config_prefix = "style", | |
40 * entity_keys = { | |
41 * "id" = "name", | |
42 * "label" = "label" | |
43 * }, | |
44 * links = { | |
45 * "flush-form" = "/admin/config/media/image-styles/manage/{image_style}/flush", | |
46 * "edit-form" = "/admin/config/media/image-styles/manage/{image_style}", | |
47 * "delete-form" = "/admin/config/media/image-styles/manage/{image_style}/delete", | |
48 * "collection" = "/admin/config/media/image-styles", | |
49 * }, | |
50 * config_export = { | |
51 * "name", | |
52 * "label", | |
53 * "effects", | |
54 * } | |
55 * ) | |
56 */ | |
57 class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, EntityWithPluginCollectionInterface { | |
58 | |
59 /** | |
60 * The name of the image style. | |
61 * | |
62 * @var string | |
63 */ | |
64 protected $name; | |
65 | |
66 /** | |
67 * The image style label. | |
68 * | |
69 * @var string | |
70 */ | |
71 protected $label; | |
72 | |
73 /** | |
74 * The array of image effects for this image style. | |
75 * | |
76 * @var array | |
77 */ | |
78 protected $effects = []; | |
79 | |
80 /** | |
81 * Holds the collection of image effects that are used by this image style. | |
82 * | |
83 * @var \Drupal\image\ImageEffectPluginCollection | |
84 */ | |
85 protected $effectsCollection; | |
86 | |
87 /** | |
88 * {@inheritdoc} | |
89 */ | |
90 public function id() { | |
91 return $this->name; | |
92 } | |
93 | |
94 /** | |
95 * {@inheritdoc} | |
96 */ | |
97 public function postSave(EntityStorageInterface $storage, $update = TRUE) { | |
98 parent::postSave($storage, $update); | |
99 | |
100 if ($update) { | |
101 if (!empty($this->original) && $this->id() !== $this->original->id()) { | |
102 // The old image style name needs flushing after a rename. | |
103 $this->original->flush(); | |
104 // Update field settings if necessary. | |
105 if (!$this->isSyncing()) { | |
106 static::replaceImageStyle($this); | |
107 } | |
108 } | |
109 else { | |
110 // Flush image style when updating without changing the name. | |
111 $this->flush(); | |
112 } | |
113 } | |
114 } | |
115 | |
116 /** | |
117 * {@inheritdoc} | |
118 */ | |
119 public static function postDelete(EntityStorageInterface $storage, array $entities) { | |
120 parent::postDelete($storage, $entities); | |
121 | |
122 /** @var \Drupal\image\ImageStyleInterface[] $entities */ | |
123 foreach ($entities as $style) { | |
124 // Flush cached media for the deleted style. | |
125 $style->flush(); | |
126 // Clear the replacement ID, if one has been previously stored. | |
127 /** @var \Drupal\image\ImageStyleStorageInterface $storage */ | |
128 $storage->clearReplacementId($style->id()); | |
129 } | |
130 } | |
131 | |
132 /** | |
133 * Update field settings if the image style name is changed. | |
134 * | |
135 * @param \Drupal\image\ImageStyleInterface $style | |
136 * The image style. | |
137 */ | |
138 protected static function replaceImageStyle(ImageStyleInterface $style) { | |
139 if ($style->id() != $style->getOriginalId()) { | |
140 // Loop through all entity displays looking for formatters / widgets using | |
141 // the image style. | |
142 foreach (EntityViewDisplay::loadMultiple() as $display) { | |
143 foreach ($display->getComponents() as $name => $options) { | |
144 if (isset($options['type']) && $options['type'] == 'image' && $options['settings']['image_style'] == $style->getOriginalId()) { | |
145 $options['settings']['image_style'] = $style->id(); | |
146 $display->setComponent($name, $options) | |
147 ->save(); | |
148 } | |
149 } | |
150 } | |
151 foreach (EntityViewDisplay::loadMultiple() as $display) { | |
152 foreach ($display->getComponents() as $name => $options) { | |
153 if (isset($options['type']) && $options['type'] == 'image_image' && $options['settings']['preview_image_style'] == $style->getOriginalId()) { | |
154 $options['settings']['preview_image_style'] = $style->id(); | |
155 $display->setComponent($name, $options) | |
156 ->save(); | |
157 } | |
158 } | |
159 } | |
160 } | |
161 } | |
162 | |
163 /** | |
164 * {@inheritdoc} | |
165 */ | |
166 public function buildUri($uri) { | |
167 $source_scheme = $scheme = $this->fileUriScheme($uri); | |
168 $default_scheme = $this->fileDefaultScheme(); | |
169 | |
170 if ($source_scheme) { | |
171 $path = $this->fileUriTarget($uri); | |
172 // The scheme of derivative image files only needs to be computed for | |
173 // source files not stored in the default scheme. | |
174 if ($source_scheme != $default_scheme) { | |
175 $class = $this->getStreamWrapperManager()->getClass($source_scheme); | |
176 $is_writable = $class::getType() & StreamWrapperInterface::WRITE; | |
177 | |
178 // Compute the derivative URI scheme. Derivatives created from writable | |
179 // source stream wrappers will inherit the scheme. Derivatives created | |
180 // from read-only stream wrappers will fall-back to the default scheme. | |
181 $scheme = $is_writable ? $source_scheme : $default_scheme; | |
182 } | |
183 } | |
184 else { | |
185 $path = $uri; | |
186 $source_scheme = $scheme = $default_scheme; | |
187 } | |
188 return "$scheme://styles/{$this->id()}/$source_scheme/{$this->addExtension($path)}"; | |
189 } | |
190 | |
191 /** | |
192 * {@inheritdoc} | |
193 */ | |
194 public function buildUrl($path, $clean_urls = NULL) { | |
195 $uri = $this->buildUri($path); | |
196 // The token query is added even if the | |
197 // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so | |
198 // that the emitted links remain valid if it is changed back to the default | |
199 // FALSE. However, sites which need to prevent the token query from being | |
200 // emitted at all can additionally set the | |
201 // 'image.settings:suppress_itok_output' configuration to TRUE to achieve | |
202 // that (if both are set, the security token will neither be emitted in the | |
203 // image derivative URL nor checked for in | |
204 // \Drupal\image\ImageStyleInterface::deliver()). | |
205 $token_query = []; | |
206 if (!\Drupal::config('image.settings')->get('suppress_itok_output')) { | |
207 // The passed $path variable can be either a relative path or a full URI. | |
208 $original_uri = file_uri_scheme($path) ? file_stream_wrapper_uri_normalize($path) : file_build_uri($path); | |
209 $token_query = [IMAGE_DERIVATIVE_TOKEN => $this->getPathToken($original_uri)]; | |
210 } | |
211 | |
212 if ($clean_urls === NULL) { | |
213 // Assume clean URLs unless the request tells us otherwise. | |
214 $clean_urls = TRUE; | |
215 try { | |
216 $request = \Drupal::request(); | |
217 $clean_urls = RequestHelper::isCleanUrl($request); | |
218 } | |
219 catch (ServiceNotFoundException $e) { | |
220 } | |
221 } | |
222 | |
223 // If not using clean URLs, the image derivative callback is only available | |
224 // with the script path. If the file does not exist, use Url::fromUri() to | |
225 // ensure that it is included. Once the file exists it's fine to fall back | |
226 // to the actual file path, this avoids bootstrapping PHP once the files are | |
227 // built. | |
228 if ($clean_urls === FALSE && file_uri_scheme($uri) == 'public' && !file_exists($uri)) { | |
229 $directory_path = $this->getStreamWrapperManager()->getViaUri($uri)->getDirectoryPath(); | |
230 return Url::fromUri('base:' . $directory_path . '/' . file_uri_target($uri), ['absolute' => TRUE, 'query' => $token_query])->toString(); | |
231 } | |
232 | |
233 $file_url = file_create_url($uri); | |
234 // Append the query string with the token, if necessary. | |
235 if ($token_query) { | |
236 $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($token_query); | |
237 } | |
238 | |
239 return $file_url; | |
240 } | |
241 | |
242 /** | |
243 * {@inheritdoc} | |
244 */ | |
245 public function flush($path = NULL) { | |
246 // A specific image path has been provided. Flush only that derivative. | |
247 if (isset($path)) { | |
248 $derivative_uri = $this->buildUri($path); | |
249 if (file_exists($derivative_uri)) { | |
250 file_unmanaged_delete($derivative_uri); | |
251 } | |
252 return $this; | |
253 } | |
254 | |
255 // Delete the style directory in each registered wrapper. | |
256 $wrappers = $this->getStreamWrapperManager()->getWrappers(StreamWrapperInterface::WRITE_VISIBLE); | |
257 foreach ($wrappers as $wrapper => $wrapper_data) { | |
258 if (file_exists($directory = $wrapper . '://styles/' . $this->id())) { | |
259 file_unmanaged_delete_recursive($directory); | |
260 } | |
261 } | |
262 | |
263 // Let other modules update as necessary on flush. | |
264 $module_handler = \Drupal::moduleHandler(); | |
265 $module_handler->invokeAll('image_style_flush', [$this]); | |
266 | |
267 // Clear caches so that formatters may be added for this style. | |
268 drupal_theme_rebuild(); | |
269 | |
270 Cache::invalidateTags($this->getCacheTagsToInvalidate()); | |
271 | |
272 return $this; | |
273 } | |
274 | |
275 /** | |
276 * {@inheritdoc} | |
277 */ | |
278 public function createDerivative($original_uri, $derivative_uri) { | |
279 // If the source file doesn't exist, return FALSE without creating folders. | |
280 $image = $this->getImageFactory()->get($original_uri); | |
281 if (!$image->isValid()) { | |
282 return FALSE; | |
283 } | |
284 | |
285 // Get the folder for the final location of this style. | |
286 $directory = drupal_dirname($derivative_uri); | |
287 | |
288 // Build the destination folder tree if it doesn't already exist. | |
289 if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { | |
290 \Drupal::logger('image')->error('Failed to create style directory: %directory', ['%directory' => $directory]); | |
291 return FALSE; | |
292 } | |
293 | |
294 foreach ($this->getEffects() as $effect) { | |
295 $effect->applyEffect($image); | |
296 } | |
297 | |
298 if (!$image->save($derivative_uri)) { | |
299 if (file_exists($derivative_uri)) { | |
300 \Drupal::logger('image')->error('Cached image file %destination already exists. There may be an issue with your rewrite configuration.', ['%destination' => $derivative_uri]); | |
301 } | |
302 return FALSE; | |
303 } | |
304 | |
305 return TRUE; | |
306 } | |
307 | |
308 /** | |
309 * {@inheritdoc} | |
310 */ | |
311 public function transformDimensions(array &$dimensions, $uri) { | |
312 foreach ($this->getEffects() as $effect) { | |
313 $effect->transformDimensions($dimensions, $uri); | |
314 } | |
315 } | |
316 | |
317 /** | |
318 * {@inheritdoc} | |
319 */ | |
320 public function getDerivativeExtension($extension) { | |
321 foreach ($this->getEffects() as $effect) { | |
322 $extension = $effect->getDerivativeExtension($extension); | |
323 } | |
324 return $extension; | |
325 } | |
326 | |
327 /** | |
328 * {@inheritdoc} | |
329 */ | |
330 public function getPathToken($uri) { | |
331 // Return the first 8 characters. | |
332 return substr(Crypt::hmacBase64($this->id() . ':' . $this->addExtension($uri), $this->getPrivateKey() . $this->getHashSalt()), 0, 8); | |
333 } | |
334 | |
335 /** | |
336 * {@inheritdoc} | |
337 */ | |
338 public function deleteImageEffect(ImageEffectInterface $effect) { | |
339 $this->getEffects()->removeInstanceId($effect->getUuid()); | |
340 $this->save(); | |
341 return $this; | |
342 } | |
343 | |
344 /** | |
345 * {@inheritdoc} | |
346 */ | |
347 public function supportsUri($uri) { | |
348 // Only support the URI if its extension is supported by the current image | |
349 // toolkit. | |
350 return in_array( | |
351 Unicode::strtolower(pathinfo($uri, PATHINFO_EXTENSION)), | |
352 $this->getImageFactory()->getSupportedExtensions() | |
353 ); | |
354 } | |
355 | |
356 /** | |
357 * {@inheritdoc} | |
358 */ | |
359 public function getEffect($effect) { | |
360 return $this->getEffects()->get($effect); | |
361 } | |
362 | |
363 /** | |
364 * {@inheritdoc} | |
365 */ | |
366 public function getEffects() { | |
367 if (!$this->effectsCollection) { | |
368 $this->effectsCollection = new ImageEffectPluginCollection($this->getImageEffectPluginManager(), $this->effects); | |
369 $this->effectsCollection->sort(); | |
370 } | |
371 return $this->effectsCollection; | |
372 } | |
373 | |
374 /** | |
375 * {@inheritdoc} | |
376 */ | |
377 public function getPluginCollections() { | |
378 return ['effects' => $this->getEffects()]; | |
379 } | |
380 | |
381 /** | |
382 * {@inheritdoc} | |
383 */ | |
384 public function addImageEffect(array $configuration) { | |
385 $configuration['uuid'] = $this->uuidGenerator()->generate(); | |
386 $this->getEffects()->addInstanceId($configuration['uuid'], $configuration); | |
387 return $configuration['uuid']; | |
388 } | |
389 | |
390 /** | |
391 * {@inheritdoc} | |
392 */ | |
393 public function getReplacementID() { | |
394 /** @var \Drupal\image\ImageStyleStorageInterface $storage */ | |
395 $storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId()); | |
396 return $storage->getReplacementId($this->id()); | |
397 } | |
398 | |
399 /** | |
400 * {@inheritdoc} | |
401 */ | |
402 public function getName() { | |
403 return $this->get('name'); | |
404 } | |
405 | |
406 /** | |
407 * {@inheritdoc} | |
408 */ | |
409 public function setName($name) { | |
410 $this->set('name', $name); | |
411 return $this; | |
412 } | |
413 | |
414 /** | |
415 * Returns the image effect plugin manager. | |
416 * | |
417 * @return \Drupal\Component\Plugin\PluginManagerInterface | |
418 * The image effect plugin manager. | |
419 */ | |
420 protected function getImageEffectPluginManager() { | |
421 return \Drupal::service('plugin.manager.image.effect'); | |
422 } | |
423 | |
424 /** | |
425 * Returns the image factory. | |
426 * | |
427 * @return \Drupal\Core\Image\ImageFactory | |
428 * The image factory. | |
429 */ | |
430 protected function getImageFactory() { | |
431 return \Drupal::service('image.factory'); | |
432 } | |
433 | |
434 /** | |
435 * Gets the Drupal private key. | |
436 * | |
437 * @return string | |
438 * The Drupal private key. | |
439 */ | |
440 protected function getPrivateKey() { | |
441 return \Drupal::service('private_key')->get(); | |
442 } | |
443 | |
444 /** | |
445 * Gets a salt useful for hardening against SQL injection. | |
446 * | |
447 * @return string | |
448 * A salt based on information in settings.php, not in the database. | |
449 * | |
450 * @throws \RuntimeException | |
451 */ | |
452 protected function getHashSalt() { | |
453 return Settings::getHashSalt(); | |
454 } | |
455 | |
456 /** | |
457 * Adds an extension to a path. | |
458 * | |
459 * If this image style changes the extension of the derivative, this method | |
460 * adds the new extension to the given path. This way we avoid filename | |
461 * clashes while still allowing us to find the source image. | |
462 * | |
463 * @param string $path | |
464 * The path to add the extension to. | |
465 * | |
466 * @return string | |
467 * The given path if this image style doesn't change its extension, or the | |
468 * path with the added extension if it does. | |
469 */ | |
470 protected function addExtension($path) { | |
471 $original_extension = pathinfo($path, PATHINFO_EXTENSION); | |
472 $extension = $this->getDerivativeExtension($original_extension); | |
473 if ($original_extension !== $extension) { | |
474 $path .= '.' . $extension; | |
475 } | |
476 return $path; | |
477 } | |
478 | |
479 /** | |
480 * Provides a wrapper for file_uri_scheme() to allow unit testing. | |
481 * | |
482 * Returns the scheme of a URI (e.g. a stream). | |
483 * | |
484 * @param string $uri | |
485 * A stream, referenced as "scheme://target" or "data:target". | |
486 * | |
487 * @see file_uri_target() | |
488 * | |
489 * @todo: Remove when https://www.drupal.org/node/2050759 is in. | |
490 * | |
491 * @return string | |
492 * A string containing the name of the scheme, or FALSE if none. For | |
493 * example, the URI "public://example.txt" would return "public". | |
494 */ | |
495 protected function fileUriScheme($uri) { | |
496 return file_uri_scheme($uri); | |
497 } | |
498 | |
499 /** | |
500 * Provides a wrapper for file_uri_target() to allow unit testing. | |
501 * | |
502 * Returns the part of a URI after the schema. | |
503 * | |
504 * @param string $uri | |
505 * A stream, referenced as "scheme://target" or "data:target". | |
506 * | |
507 * @see file_uri_scheme() | |
508 * | |
509 * @todo: Convert file_uri_target() into a proper injectable service. | |
510 * | |
511 * @return string|bool | |
512 * A string containing the target (path), or FALSE if none. | |
513 * For example, the URI "public://sample/test.txt" would return | |
514 * "sample/test.txt". | |
515 */ | |
516 protected function fileUriTarget($uri) { | |
517 return file_uri_target($uri); | |
518 } | |
519 | |
520 /** | |
521 * Provides a wrapper for file_default_scheme() to allow unit testing. | |
522 * | |
523 * Gets the default file stream implementation. | |
524 * | |
525 * @todo: Convert file_default_scheme() into a proper injectable service. | |
526 * | |
527 * @return string | |
528 * 'public', 'private' or any other file scheme defined as the default. | |
529 */ | |
530 protected function fileDefaultScheme() { | |
531 return file_default_scheme(); | |
532 } | |
533 | |
534 /** | |
535 * Gets the stream wrapper manager service. | |
536 * | |
537 * @return \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface | |
538 * The stream wrapper manager service | |
539 * | |
540 * @todo Properly inject this service in Drupal 9.0.x. | |
541 */ | |
542 protected function getStreamWrapperManager() { | |
543 return \Drupal::service('stream_wrapper_manager'); | |
544 } | |
545 | |
546 } |