7382b1f29ee0e55f201a2f5f7cde68a96699f277
[yaffs-website] / web / modules / contrib / blazy / src / Form / BlazyAdminBase.php
1 <?php
2
3 namespace Drupal\blazy\Form;
4
5 use Drupal\Core\Url;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\Render\Element;
8 use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
9 use Drupal\Core\Config\TypedConfigManagerInterface;
10 use Drupal\Core\StringTranslation\StringTranslationTrait;
11 use Drupal\Component\Utility\Html;
12 use Symfony\Component\DependencyInjection\ContainerInterface;
13 use Drupal\blazy\BlazyManagerInterface;
14
15 /**
16  * A base for blazy admin integration to have re-usable methods in one place.
17  *
18  * @see \Drupal\gridstack\Form\GridStackAdmin
19  * @see \Drupal\mason\Form\MasonAdmin
20  * @see \Drupal\slick\Form\SlickAdmin
21  * @see \Drupal\blazy\Form\BlazyAdminFormatterBase
22  */
23 abstract class BlazyAdminBase implements BlazyAdminInterface {
24
25   use StringTranslationTrait;
26
27   /**
28    * A state that represents the responsive image style is disabled.
29    */
30   const STATE_RESPONSIVE_IMAGE_STYLE_DISABLED = 0;
31
32   /**
33    * A state that represents the media switch lightbox is enabled.
34    */
35   const STATE_LIGHTBOX_ENABLED = 1;
36
37   /**
38    * A state that represents the media switch iframe is enabled.
39    */
40   const STATE_IFRAME_ENABLED = 2;
41
42   /**
43    * A state that represents the thumbnail style is enabled.
44    */
45   const STATE_THUMBNAIL_STYLE_ENABLED = 3;
46
47   /**
48    * A state that represents the custom lightbox caption is enabled.
49    */
50   const STATE_LIGHTBOX_CUSTOM = 4;
51
52   /**
53    * A state that represents the image rendered switch is enabled.
54    */
55   const STATE_IMAGE_RENDERED_ENABLED = 5;
56
57   /**
58    * The entity type manager service.
59    *
60    * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
61    */
62   protected $entityDisplayRepository;
63
64   /**
65    * The typed config manager service.
66    *
67    * @var \Drupal\Core\Config\TypedConfigManagerInterface
68    */
69   protected $typedConfig;
70
71   /**
72    * The blazy manager service.
73    *
74    * @var \Drupal\blazy\BlazyManagerInterface
75    */
76   protected $blazyManager;
77
78   /**
79    * Constructs a BlazyAdminBase object.
80    *
81    * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
82    *   The entity display repository.
83    * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
84    *   The typed config service.
85    * @param \Drupal\slick\BlazyManagerInterface $blazy_manager
86    *   The blazy manager service.
87    */
88   public function __construct(EntityDisplayRepositoryInterface $entity_display_repository, TypedConfigManagerInterface $typed_config, BlazyManagerInterface $blazy_manager) {
89     $this->entityDisplayRepository = $entity_display_repository;
90     $this->typedConfig             = $typed_config;
91     $this->blazyManager            = $blazy_manager;
92   }
93
94   /**
95    * {@inheritdoc}
96    */
97   public static function create(ContainerInterface $container) {
98     return new static($container->get('entity_display.repository'), $container->get('config.typed'), $container->get('blazy.manager'));
99   }
100
101   /**
102    * Returns the entity display repository.
103    */
104   public function getEntityDisplayRepository() {
105     return $this->entityDisplayRepository;
106   }
107
108   /**
109    * Returns the typed config.
110    */
111   public function getTypedConfig() {
112     return $this->typedConfig;
113   }
114
115   /**
116    * Returns the blazy manager.
117    */
118   public function blazyManager() {
119     return $this->blazyManager;
120   }
121
122   /**
123    * Returns shared form elements across field formatter and Views.
124    */
125   public function openingForm(array &$form, $definition = []) {
126     if (!isset($definition['namespace'])) {
127       return;
128     }
129
130     $this->blazyManager->getModuleHandler()->alter('blazy_form_element_definition', $definition);
131
132     // Display style: column, plain static grid, slick grid, slick carousel.
133     // https://drafts.csswg.org/css-multicol
134     if (!empty($definition['style'])) {
135       $form['style'] = [
136         '#type'          => 'select',
137         '#title'         => $this->t('Display style'),
138         '#description'   => $this->t('Either <strong>CSS3 Columns</strong> (experimental pure CSS Masonry) or <strong>Grid Foundation</strong> requires <strong>Grid</strong>. Difference: <strong>Columns</strong> is best with irregular image sizes (scale width, empty height), affects the natural order of grid items. <strong>Grid</strong> with regular cropped ones. Unless required, leave empty to use default formatter, or style.'),
139         '#enforced'      => TRUE,
140         '#empty_option'  => '- None -',
141         '#options'       => [
142           'column' => $this->t('CSS3 Columns'),
143           'grid'   => $this->t('Grid Foundation'),
144         ],
145         '#weight'             => -112,
146         '#wrapper_attributes' => ['class' => ['form-item--style', 'form-item--tooltip-bottom']],
147       ];
148     }
149
150     if (!empty($definition['skins'])) {
151       $form['skin'] = [
152         '#type'        => 'select',
153         '#title'       => $this->t('Skin'),
154         '#options'     => $definition['skins'],
155         '#enforced'    => TRUE,
156         '#description' => $this->t('Skins allow various layouts with just CSS. Some options below depend on a skin. Leave empty to DIY. Or use the provided hook_info() and implement the skin interface to register ones.'),
157         '#weight'      => -107,
158       ];
159     }
160
161     if (!empty($definition['background'])) {
162       $form['background'] = [
163         '#type'        => 'checkbox',
164         '#title'       => $this->t('Use CSS background'),
165         '#description' => $this->t('Check this to turn the image into CSS background. This opens up the goodness of CSS, such as background cover, fixed attachment, etc. <br /><strong>Important!</strong> Requires a consistent Aspect ratio, otherwise collapsed containers. Unless a min-height is added manually to <strong>.media--background</strong> selector. Not compatible with Responsive image.'),
166         '#weight'      => -98,
167       ];
168     }
169
170     if (!empty($definition['layouts'])) {
171       $form['layout'] = [
172         '#type'        => 'select',
173         '#title'       => $this->t('Layout'),
174         '#options'     => $definition['layouts'],
175         '#description' => $this->t('Requires a skin. The builtin layouts affects the entire items uniformly. Leave empty to DIY.'),
176         '#weight'      => 2,
177       ];
178     }
179
180     if (!empty($definition['captions'])) {
181       $form['caption'] = [
182         '#type'        => 'checkboxes',
183         '#title'       => $this->t('Caption fields'),
184         '#options'     => $definition['captions'],
185         '#description' => $this->t('Enable any of the following fields as captions. These fields are treated and wrapped as captions.'),
186         '#weight'      => 80,
187         '#attributes'  => ['class' => ['form-wrapper--caption']],
188       ];
189     }
190
191     if (!empty($definition['target_type']) && !empty($definition['view_mode'])) {
192       $form['view_mode'] = $this->baseForm($definition)['view_mode'];
193     }
194
195     $weight = -99;
196     foreach (Element::children($form) as $key) {
197       if (!isset($form[$key]['#weight'])) {
198         $form[$key]['#weight'] = ++$weight;
199       }
200     }
201   }
202
203   /**
204    * Defines re-usable breakpoints form.
205    *
206    * @see https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-srcset
207    * @see http://ericportis.com/posts/2014/srcset-sizes/
208    * @see http://www.sitepoint.com/how-to-build-responsive-images-with-srcset/
209    */
210   public function breakpointsForm(array &$form, $definition = []) {
211     $settings = isset($definition['settings']) ? $definition['settings'] : [];
212     $title    = $this->t('Leave Breakpoints empty to disable multi-serving images. <small>If provided, Blazy lazyload applies. Ignored if core Responsive image is provided.<br /> If only two is needed, simply leave the rest empty. At any rate, the last should target the largest monitor. <br />It uses <strong>max-width</strong>, not <strong>min-width</strong>.</small>');
213
214     $form['sizes'] = [
215       '#type'               => 'textfield',
216       '#title'              => $this->t('Sizes'),
217       '#description'        => $this->t('E.g.: (min-width: 1290px) 1290px, 100vw. Use sizes to implement different size image (different height, width) on different screen sizes along with the <strong>w (width)</strong> descriptor below. Ignored by Responsive image.'),
218       '#weight'             => 114,
219       '#attributes'         => ['class' => ['form-text--sizes', 'js-expandable']],
220       '#wrapper_attributes' => ['class' => ['form-item--sizes']],
221       '#prefix'             => '<h2 class="form__title form__title--breakpoints">' . $title . '</h2>',
222     ];
223
224     $form['breakpoints'] = [
225       '#type'       => 'table',
226       '#tree'       => TRUE,
227       '#header'     => [
228         $this->t('Breakpoint'),
229         $this->t('Image style'),
230         $this->t('Max-width/Descriptor'),
231       ],
232       '#attributes' => ['class' => ['form-wrapper--table', 'form-wrapper--table-breakpoints']],
233       '#weight'     => 115,
234       '#enforced'   => TRUE,
235     ];
236
237     // Unlike D7, D8 form states seem to not recognize individual field form.
238     $vanilla = ':input[name$="[vanilla]"]';
239     if (isset($definition['field_name'])) {
240       $vanilla = ':input[name="fields[' . $definition['field_name'] . '][settings_edit_form][settings][vanilla]"]';
241     }
242
243     if (!empty($definition['_views'])) {
244       $vanilla = ':input[name="options[settings][vanilla]"]';
245     }
246
247     $breakpoints = $this->breakpointElements($definition);
248     foreach ($breakpoints as $breakpoint => $elements) {
249       foreach ($elements as $key => $element) {
250         $form['breakpoints'][$breakpoint][$key] = $element;
251
252         if (isset($definition['vanilla'])) {
253           $form['breakpoints'][$breakpoint][$key]['#states']['enabled'][$vanilla] = ['checked' => FALSE];
254         }
255         $value = isset($settings['breakpoints'][$breakpoint][$key]) ? $settings['breakpoints'][$breakpoint][$key] : '';
256         $form['breakpoints'][$breakpoint][$key]['#default_value'] = $value;
257       }
258     }
259   }
260
261   /**
262    * Defines re-usable breakpoints form.
263    */
264   public function breakpointElements($definition = []) {
265     if (!isset($definition['breakpoints'])) {
266       return [];
267     }
268
269     foreach ($definition['breakpoints'] as $breakpoint) {
270       $form[$breakpoint]['breakpoint'] = [
271         '#type'               => 'item',
272         '#markup'             => $breakpoint,
273         '#weight'             => 1,
274         '#wrapper_attributes' => ['class' => ['form-item--right']],
275       ];
276
277       $form[$breakpoint]['image_style'] = [
278         '#type'               => 'select',
279         '#title'              => $this->t('Image style'),
280         '#title_display'      => 'invisible',
281         '#options'            => image_style_options(FALSE),
282         '#empty_option'       => $this->t('- None -'),
283         '#weight'             => 2,
284         '#wrapper_attributes' => ['class' => ['form-item--left']],
285       ];
286
287       $form[$breakpoint]['width'] = [
288         '#type'               => 'textfield',
289         '#title'              => $this->t('Width'),
290         '#title_display'      => 'invisible',
291         '#description'        => $this->t('See <strong>XS</strong> for detailed info.'),
292         '#maz_length'         => 32,
293         '#size'               => 6,
294         '#weight'             => 3,
295         '#attributes'         => ['class' => ['form-text--width', 'js-expandable']],
296         '#wrapper_attributes' => ['class' => ['form-item--width']],
297       ];
298
299       if ($breakpoint == 'xs') {
300         $form[$breakpoint]['width']['#description'] = $this->t('E.g.: <strong>640</strong>, or <strong>2x</strong>, or for <strong>small devices</strong> may be combined into <strong>640w 2x</strong> where <strong>x (pixel density)</strong> descriptor is used to define the device-pixel ratio, and <strong>w (width)</strong> descriptor is the width of image source and works in tandem with <strong>sizes</strong> attributes. Use <strong>w (width)</strong> if any issue/ unsure. Default to <strong>w</strong> if no descriptor provided for backward compatibility.');
301       }
302     }
303
304     return $form;
305   }
306
307   /**
308    * Returns re-usable grid elements across field formatter and Views.
309    */
310   public function gridForm(array &$form, $definition = []) {
311     $range = range(1, 12);
312     $grid_options = array_combine($range, $range);
313
314     $header = $this->t('Group individual items as block grid<small>Depends on the <strong>Display style</strong>.</small>');
315     $form['grid_header'] = [
316       '#type'   => 'item',
317       '#markup' => '<h3 class="form__title form__title--grid">' . $header . '</h3>',
318     ];
319
320     $form['grid'] = [
321       '#type'        => 'select',
322       '#title'       => $this->t('Grid large'),
323       '#options'     => $grid_options,
324       '#description' => $this->t('Select <strong>- None -</strong> first if trouble with changing form states. The amount of block grid columns for large monitors 64.063em+. <br /><strong>Requires</strong>:<ol><li>Visible items,</li><li>Skin Grid for starter,</li><li>A reasonable amount of contents.</li></ol>Leave empty to DIY, or to not build grids.'),
325       '#enforced'    => TRUE,
326     ];
327
328     $form['grid_medium'] = [
329       '#type'        => 'select',
330       '#title'       => $this->t('Grid medium'),
331       '#options'     => $grid_options,
332       '#description' => $this->t('The amount of block grid columns for medium devices 40.063em - 64em.'),
333     ];
334
335     $form['grid_small'] = [
336       '#type'        => 'select',
337       '#title'       => $this->t('Grid small'),
338       '#options'     => $grid_options,
339       '#description' => $this->t('The amount of block grid columns for small devices 0 - 40em. Specific to <strong>CSS3 Columns</strong>, only 1 - 2 column is respected due to small real estate at smallest device.'),
340     ];
341
342     $form['visible_items'] = [
343       '#type'        => 'select',
344       '#title'       => $this->t('Visible items'),
345       '#options'     => array_combine(range(1, 32), range(1, 32)),
346       '#description' => $this->t('How many items per display at a time.'),
347     ];
348
349     $form['preserve_keys'] = [
350       '#type'        => 'checkbox',
351       '#title'       => $this->t('Preserve keys'),
352       '#description' => $this->t('If checked, keys will be preserved. Default is FALSE which will reindex the grid chunk numerically.'),
353     ];
354
355     $grids = [
356       'grid_header',
357       'grid_medium',
358       'grid_small',
359       'visible_items',
360       'preserve_keys',
361     ];
362
363     foreach ($grids as $key) {
364       $form[$key]['#enforced'] = TRUE;
365       $form[$key]['#states'] = [
366         'visible' => [
367           'select[name$="[grid]"]' => ['!value' => ''],
368         ],
369       ];
370     }
371   }
372
373   /**
374    * Returns shared ending form elements across field formatter and Views.
375    */
376   public function closingForm(array &$form, $definition = []) {
377     if (isset($definition['current_view_mode'])) {
378       $form['current_view_mode'] = [
379         '#type'          => 'hidden',
380         '#default_value' => isset($definition['current_view_mode']) ? $definition['current_view_mode'] : '_custom',
381         '#weight'        => 120,
382       ];
383     }
384
385     $this->finalizeForm($form, $definition);
386   }
387
388   /**
389    * Returns simple form elements common for Views field, EB widget, formatters.
390    */
391   public function baseForm($definition = []) {
392     $settings      = isset($definition['settings']) ? $definition['settings'] : [];
393     $lightboxes    = $this->blazyManager->getLightboxes();
394     $image_styles  = image_style_options(FALSE);
395     $is_responsive = function_exists('responsive_image_get_image_dimensions') && !empty($definition['responsive_image']);
396
397     $form = [];
398     if (empty($definition['no_image_style'])) {
399       $form['image_style'] = [
400         '#type'        => 'select',
401         '#title'       => $this->t('Image style'),
402         '#options'     => $image_styles,
403         '#description' => $this->t('The content image style. This will be treated as the fallback image, which is normally smaller, if Breakpoints are provided. Otherwise this is the only image displayed.'),
404         '#weight'      => -100,
405       ];
406     }
407
408     if (isset($settings['media_switch'])) {
409       $form['media_switch'] = [
410         '#type'         => 'select',
411         '#title'        => $this->t('Media switcher'),
412         '#options'      => [
413           'content' => $this->t('Image linked to content'),
414         ],
415         '#empty_option' => $this->t('- None -'),
416         '#description'  => $this->t('May depend on the enabled supported or supportive modules: colorbox, photobox etc. Be sure to add Thumbnail style if using Photobox. Try selecting "<strong>- None -</strong>" first before changing if trouble with this complex form states.'),
417         '#weight'       => -99,
418       ];
419
420       // Optional lightbox integration.
421       if (!empty($lightboxes)) {
422         foreach ($lightboxes as $lightbox) {
423           $form['media_switch']['#options'][$lightbox] = $this->t('Image to @lightbox', ['@lightbox' => $lightbox]);
424         }
425
426         // Re-use the same image style for both lightboxes.
427         $form['box_style'] = [
428           '#type'    => 'select',
429           '#title'   => $this->t('Lightbox image style'),
430           '#options' => $image_styles,
431           '#states'  => $this->getState(static::STATE_LIGHTBOX_ENABLED, $definition),
432           '#weight'  => -99,
433         ];
434
435         if (!empty($definition['multimedia'])) {
436           $form['box_media_style'] = [
437             '#type'        => 'select',
438             '#title'       => $this->t('Lightbox video style'),
439             '#options'     => $image_styles,
440             '#description' => $this->t('Allows different lightbox video dimensions. Or can be used to have a swipable video if Blazy PhotoSwipe installed.'),
441             '#states'      => $this->getState(static::STATE_LIGHTBOX_ENABLED, $definition),
442             '#weight'      => -99,
443           ];
444         }
445       }
446
447       // Adds common supported entities for media integration.
448       if (!empty($definition['multimedia'])) {
449         $form['media_switch']['#options']['media'] = $this->t('Image to iFrame');
450       }
451
452       // http://en.wikipedia.org/wiki/List_of_common_resolutions
453       $ratio = ['1:1', '3:2', '4:3', '8:5', '16:9', 'fluid', 'enforced'];
454       if (empty($definition['no_ratio'])) {
455         $form['ratio'] = [
456           '#type'         => 'select',
457           '#title'        => $this->t('Aspect ratio'),
458           '#options'      => array_combine($ratio, $ratio),
459           '#empty_option' => $this->t('- None -'),
460           '#description'  => $this->t('Aspect ratio to get consistently responsive images and iframes. And to fix layout reflow and excessive height issues. <a href="@dimensions"   target="_blank">Image styles and video dimensions</a> must <a href="@follow" target="_blank">follow the aspect ratio</a>. If not, images will be distorted. Choose <strong>fluid</strong> if unsure. Choose <strong>enforced</strong> if you can stick to one aspect ratio and want multi-serving, or Responsive images. <a href="@link" target="_blank">Learn more</a>, or leave empty to DIY, or when working with multi-image-style plugin like GridStack. <br /><strong>Note!</strong> Only compatible with Blazy multi-serving images, but not Responsive image.', [
461             '@dimensions'  => '//size43.com/jqueryVideoTool.html',
462             '@follow'      => '//en.wikipedia.org/wiki/Aspect_ratio_%28image%29',
463             '@link'        => '//www.smashingmagazine.com/2014/02/27/making-embedded-content-work-in-responsive-design/',
464           ]),
465           '#weight'        => -96,
466         ];
467
468         if ($is_responsive) {
469           $form['ratio']['#states'] = $this->getState(static::STATE_RESPONSIVE_IMAGE_STYLE_DISABLED, $definition);
470         }
471       }
472     }
473
474     if (!empty($definition['target_type']) && !empty($definition['view_mode'])) {
475       $form['view_mode'] = [
476         '#type'        => 'select',
477         '#options'     => $this->getViewModeOptions($definition['target_type']),
478         '#title'       => $this->t('View mode'),
479         '#description' => $this->t('Required to grab the fields, or to have custom entity display as fallback display. If it has fields, be sure the selected "View mode" is enabled, and the enabled fields here are not hidden there. Manage view modes on the <a href=":view_modes">View modes page</a>.', [':view_modes' => Url::fromRoute('entity.entity_view_mode.collection')->toString()]),
480         '#weight'      => -96,
481         '#enforced'    => TRUE,
482       ];
483     }
484
485     if (!empty($definition['thumbnail_style'])) {
486       $form['thumbnail_style'] = [
487         '#type'        => 'select',
488         '#title'       => $this->t('Thumbnail style'),
489         '#options'     => image_style_options(TRUE),
490         '#description' => $this->t('Usages: Photobox/PhotoSwipe thumbnail, or custom work with thumbnails. Leave empty to not use thumbnails.'),
491         '#weight'      => -100,
492       ];
493     }
494
495     $this->blazyManager->getModuleHandler()->alter('blazy_base_form_element', $form, $definition);
496
497     return $form;
498   }
499
500   /**
501    * Returns re-usable media switch form elements.
502    */
503   public function mediaSwitchForm(array &$form, $definition = []) {
504     $settings   = isset($definition['settings']) ? $definition['settings'] : [];
505     $lightboxes = $this->blazyManager->getLightboxes();
506     $is_token   = function_exists('token_theme');
507
508     if (empty($definition['media_switch_form'])) {
509       return;
510     }
511
512     if (isset($settings['media_switch'])) {
513       $form['media_switch'] = $this->baseForm($definition)['media_switch'];
514       $form['media_switch']['#prefix'] = '<h3 class="form__title form__title--media-switch">' . $this->t('Media switcher') . '</h3>';
515
516       if (empty($definition['no_ratio'])) {
517         $form['ratio'] = $this->baseForm($definition)['ratio'];
518       }
519     }
520
521     if (!empty($definition['multimedia']) && empty($definition['no_iframe_lazy'])) {
522       $form['iframe_lazy'] = [
523         '#type'        => 'checkbox',
524         '#title'       => $this->t('Lazy iframe'),
525         '#description' => $this->t('Check to make the video/audio iframes truly lazyloaded, and speed up loading time. Depends on JS enabled at client side. <a href=":more" target="_blank">Read more</a> to <a href=":url" target="_blank">decide</a>.', [':more' => '//goo.gl/FQLFQ6', ':url' => '//goo.gl/f78pMl']),
526         '#weight'      => -96,
527         '#states'      => $this->getState(static::STATE_IFRAME_ENABLED, $definition),
528       ];
529     }
530
531     // Optional lightbox integration.
532     if (!empty($lightboxes) && isset($settings['media_switch'])) {
533       $form['box_style'] = $this->baseForm($definition)['box_style'];
534
535       if (!empty($definition['multimedia'])) {
536         $form['box_media_style'] = $this->baseForm($definition)['box_media_style'];
537       }
538
539       $box_captions = [
540         'auto'         => $this->t('Automatic'),
541         'alt'          => $this->t('Alt text'),
542         'title'        => $this->t('Title text'),
543         'alt_title'    => $this->t('Alt and Title'),
544         'title_alt'    => $this->t('Title and Alt'),
545         'entity_title' => $this->t('Content title'),
546         'custom'       => $this->t('Custom'),
547       ];
548
549       if (!empty($definition['box_captions'])) {
550         $form['box_caption'] = [
551           '#type'        => 'select',
552           '#title'       => $this->t('Lightbox caption'),
553           '#options'     => $box_captions,
554           '#weight'      => -99,
555           '#states'      => $this->getState(static::STATE_LIGHTBOX_ENABLED, $definition),
556           '#description' => $this->t('Automatic will search for Alt text first, then Title text. Try selecting <strong>- None -</strong> first when changing if trouble with form states.'),
557         ];
558
559         $form['box_caption_custom'] = [
560           '#title'       => $this->t('Lightbox custom caption'),
561           '#type'        => 'textfield',
562           '#weight'      => -99,
563           '#states'      => $this->getState(static::STATE_LIGHTBOX_CUSTOM, $definition),
564           '#description' => $this->t('Multi-value rich text field will be mapped to each image by its delta.'),
565         ];
566
567         if ($is_token) {
568           $types = isset($definition['entity_type']) ? [$definition['entity_type']] : [];
569           $types = isset($definition['target_type']) ? array_merge($types, [$definition['target_type']]) : $types;
570           $form['box_caption_custom']['#field_suffix'] = [
571             '#theme'       => 'token_tree_link',
572             '#text'        => $this->t('Tokens'),
573             '#token_types' => $types,
574           ];
575         }
576         else {
577           $form['box_caption_custom']['#description'] .= ' ' . $this->t('Install Token module to browse available tokens.');
578         }
579       }
580     }
581
582     $this->blazyManager->getModuleHandler()->alter('blazy_media_switch_form_element', $form, $definition);
583   }
584
585   /**
586    * Returns re-usable logic, styling and assets across fields and Views.
587    */
588   public function finalizeForm(array &$form, $definition = []) {
589     $namespace = isset($definition['namespace']) ? $definition['namespace'] : 'slick';
590     $settings = isset($definition['settings']) ? $definition['settings'] : [];
591     $vanilla = isset($definition['vanilla']) ? ' form--vanilla' : '';
592     $captions = empty($definition['captions']) ? 0 : count($definition['captions']);
593     $wide = $captions > 2 ? ' form--wide form--caption-' . $captions : ' form--caption-' . $captions;
594     $fallback = $namespace == 'slick' ? 'form--slick' : 'form--' . $namespace . ' form--slick';
595     $classes = isset($definition['form_opening_classes'])
596       ? $definition['form_opening_classes']
597       : $fallback . ' form--half has-tooltip' . $wide . $vanilla;
598
599     if (!empty($definition['field_type'])) {
600       $classes .= ' form--' . str_replace('_', '-', $definition['field_type']);
601     }
602
603     $form['opening'] = [
604       '#markup' => '<div class="' . $classes . '">',
605       '#weight' => -120,
606     ];
607
608     $form['closing'] = [
609       '#markup' => '</div>',
610       '#weight' => 120,
611     ];
612
613     $admin_css = isset($definition['admin_css']) ? $definition['admin_css'] : '';
614     $admin_css = $admin_css ?: $this->blazyManager->configLoad('admin_css', 'blazy.settings');
615
616     // @todo: Check if needed: 'button', 'container', 'submit'.
617     $excludes = ['details', 'fieldset', 'hidden', 'markup', 'item', 'table'];
618     $selects  = ['cache', 'optionset', 'view_mode'];
619
620     foreach (Element::children($form) as $key) {
621       if (isset($form[$key]['#type']) && !in_array($form[$key]['#type'], $excludes)) {
622         if (!isset($form[$key]['#default_value']) && isset($settings[$key])) {
623           $value = is_array($settings[$key]) ? array_values((array) $settings[$key]) : $settings[$key];
624           $form[$key]['#default_value'] = $value;
625         }
626         if (!isset($form[$key]['#attributes']) && isset($form[$key]['#description'])) {
627           $form[$key]['#attributes'] = ['class' => ['is-tooltip']];
628         }
629
630         if ($admin_css) {
631           if ($form[$key]['#type'] == 'checkbox' && $form[$key]['#type'] != 'checkboxes') {
632             $form[$key]['#field_suffix'] = '&nbsp;';
633             $form[$key]['#title_display'] = 'before';
634           }
635           elseif ($form[$key]['#type'] == 'checkboxes' && !empty($form[$key]['#options'])) {
636             foreach ($form[$key]['#options'] as $i => $option) {
637               $form[$key][$i]['#field_suffix'] = '&nbsp;';
638               $form[$key][$i]['#title_display'] = 'before';
639             }
640           }
641         }
642
643         if ($form[$key]['#type'] == 'select' && !in_array($key, $selects)) {
644           if (!isset($form[$key]['#empty_option']) && !isset($form[$key]['#required'])) {
645             $form[$key]['#empty_option'] = $this->t('- None -');
646           }
647         }
648
649         if (!isset($form[$key]['#enforced']) && isset($definition['vanilla']) && isset($form[$key]['#type'])) {
650           $states['visible'][':input[name*="[vanilla]"]'] = ['checked' => FALSE];
651           if (isset($form[$key]['#states'])) {
652             $form[$key]['#states']['visible'][':input[name*="[vanilla]"]'] = ['checked' => FALSE];
653           }
654           else {
655             $form[$key]['#states'] = $states;
656           }
657         }
658       }
659
660       $form[$key]['#wrapper_attributes']['class'][] = 'form-item--' . str_replace('_', '-', $key);
661
662       if (isset($form[$key]['#access']) && $form[$key]['#access'] == FALSE) {
663         unset($form[$key]['#default_value']);
664       }
665     }
666
667     if ($admin_css) {
668       $form['closing']['#attached']['library'][] = 'blazy/admin';
669     }
670
671     $this->blazyManager->getModuleHandler()->alter('blazy_complete_form_element', $form, $definition);
672   }
673
674   /**
675    * Returns time in interval for select options.
676    */
677   public function getCacheOptions() {
678     $period = [
679       0,
680       60,
681       180,
682       300,
683       600,
684       900,
685       1800,
686       2700,
687       3600,
688       10800,
689       21600,
690       32400,
691       43200,
692       86400,
693     ];
694     $period = array_map([\Drupal::service('date.formatter'), 'formatInterval'], array_combine($period, $period));
695     $period[0] = '<' . $this->t('No caching') . '>';
696     return $period + [Cache::PERMANENT => $this->t('Permanent')];
697   }
698
699   /**
700    * Returns available optionsets for select options.
701    */
702   public function getOptionsetOptions($entity_type = '') {
703     $optionsets = [];
704     if (empty($entity_type)) {
705       return $optionsets;
706     }
707
708     $entities = $this->blazyManager->entityLoadMultiple($entity_type);
709     foreach ((array) $entities as $entity) {
710       $optionsets[$entity->id()] = Html::escape($entity->label());
711     }
712     asort($optionsets);
713     return $optionsets;
714   }
715
716   /**
717    * Returns available view modes for select options.
718    */
719   public function getViewModeOptions($target_type) {
720     return $this->entityDisplayRepository->getViewModeOptions($target_type);
721   }
722
723   /**
724    * Get one of the pre-defined states used in this form.
725    *
726    * Thanks to SAM152 at colorbox.module for the little sweet idea.
727    *
728    * @param string $state
729    *   The state to get that matches one of the state class constants.
730    *
731    * @return array
732    *   A corresponding form API state.
733    */
734   protected function getState($state, $definition = []) {
735     $lightboxes = [];
736     foreach ($this->blazyManager->getLightboxes() as $key => $lightbox) {
737       $lightboxes[$key]['value'] = $lightbox;
738     }
739
740     $states = [
741       static::STATE_RESPONSIVE_IMAGE_STYLE_DISABLED => [
742         'visible' => [
743           'select[name$="[responsive_image_style]"]' => ['value' => ''],
744         ],
745       ],
746       static::STATE_LIGHTBOX_ENABLED => [
747         'visible' => [
748           'select[name*="[media_switch]"]' => $lightboxes,
749         ],
750       ],
751       static::STATE_LIGHTBOX_CUSTOM => [
752         'visible' => [
753           'select[name$="[box_caption]"]' => ['value' => 'custom'],
754           'select[name*="[media_switch]"]' => $lightboxes,
755         ],
756       ],
757       static::STATE_IFRAME_ENABLED => [
758         'visible' => [
759           'select[name*="[media_switch]"]' => ['value' => 'media'],
760         ],
761       ],
762       static::STATE_THUMBNAIL_STYLE_ENABLED => [
763         'visible' => [
764           'select[name$="[thumbnail_style]"]' => ['!value' => ''],
765         ],
766       ],
767       static::STATE_IMAGE_RENDERED_ENABLED => [
768         'visible' => [
769           'select[name$="[media_switch]"]' => ['!value' => 'rendered'],
770         ],
771       ],
772     ];
773     return $states[$state];
774   }
775
776 }