2030e84673726c1503d0f9ba8019449040d4b62e
[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     $this->blazyManager->getModuleHandler()->alter('blazy_form_element_definition', $definition);
127
128     // Display style: column, plain static grid, slick grid, slick carousel.
129     // https://drafts.csswg.org/css-multicol
130     if (!empty($definition['style'])) {
131       $form['style'] = [
132         '#type'          => 'select',
133         '#title'         => $this->t('Display style'),
134         '#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.'),
135         '#enforced'      => TRUE,
136         '#empty_option'  => '- None -',
137         '#options'       => [
138           'column' => $this->t('CSS3 Columns'),
139           'grid'   => $this->t('Grid Foundation'),
140         ],
141         '#weight'             => -112,
142         '#wrapper_attributes' => ['class' => ['form-item--style', 'form-item--tooltip-bottom']],
143       ];
144     }
145
146     if (!empty($definition['skins'])) {
147       $form['skin'] = [
148         '#type'        => 'select',
149         '#title'       => $this->t('Skin'),
150         '#options'     => $definition['skins'],
151         '#enforced'    => TRUE,
152         '#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.'),
153         '#weight'      => -107,
154       ];
155     }
156
157     if (!empty($definition['background'])) {
158       $form['background'] = [
159         '#type'        => 'checkbox',
160         '#title'       => $this->t('Use CSS background'),
161         '#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.'),
162         '#weight'      => -98,
163       ];
164     }
165
166     if (!empty($definition['layouts'])) {
167       $form['layout'] = [
168         '#type'        => 'select',
169         '#title'       => $this->t('Layout'),
170         '#options'     => $definition['layouts'],
171         '#description' => $this->t('Requires a skin. The builtin layouts affects the entire items uniformly. Leave empty to DIY.'),
172         '#weight'      => 2,
173       ];
174     }
175
176     if (!empty($definition['captions'])) {
177       $form['caption'] = [
178         '#type'        => 'checkboxes',
179         '#title'       => $this->t('Caption fields'),
180         '#options'     => $definition['captions'],
181         '#description' => $this->t('Enable any of the following fields as captions. These fields are treated and wrapped as captions.'),
182         '#weight'      => 80,
183         '#attributes'  => ['class' => ['form-wrapper--caption']],
184       ];
185     }
186
187     if (!empty($definition['target_type']) && !empty($definition['view_mode'])) {
188       $form['view_mode'] = $this->baseForm($definition)['view_mode'];
189     }
190
191     $weight = -99;
192     foreach (Element::children($form) as $key) {
193       if (!isset($form[$key]['#weight'])) {
194         $form[$key]['#weight'] = ++$weight;
195       }
196     }
197   }
198
199   /**
200    * Defines re-usable breakpoints form.
201    *
202    * @see https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-srcset
203    * @see http://ericportis.com/posts/2014/srcset-sizes/
204    * @see http://www.sitepoint.com/how-to-build-responsive-images-with-srcset/
205    */
206   public function breakpointsForm(array &$form, $definition = []) {
207     $settings = isset($definition['settings']) ? $definition['settings'] : [];
208     $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>');
209
210     $form['sizes'] = [
211       '#type'               => 'textfield',
212       '#title'              => $this->t('Sizes'),
213       '#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.'),
214       '#weight'             => 114,
215       '#attributes'         => ['class' => ['form-text--sizes', 'js-expandable']],
216       '#wrapper_attributes' => ['class' => ['form-item--sizes']],
217       '#prefix'             => '<h2 class="form__title form__title--breakpoints">' . $title . '</h2>',
218     ];
219
220     $form['breakpoints'] = [
221       '#type'       => 'table',
222       '#tree'       => TRUE,
223       '#header'     => [
224         $this->t('Breakpoint'),
225         $this->t('Image style'),
226         $this->t('Max-width/Descriptor'),
227       ],
228       '#attributes' => ['class' => ['form-wrapper--table', 'form-wrapper--table-breakpoints']],
229       '#weight'     => 115,
230       '#enforced'   => TRUE,
231     ];
232
233     // Unlike D7, D8 form states seem to not recognize individual field form.
234     $vanilla = ':input[name$="[vanilla]"]';
235     if (isset($definition['field_name'])) {
236       $vanilla = ':input[name="fields[' . $definition['field_name'] . '][settings_edit_form][settings][vanilla]"]';
237     }
238
239     if (!empty($definition['_views'])) {
240       $vanilla = ':input[name="options[settings][vanilla]"]';
241     }
242
243     $breakpoints = $this->breakpointElements($definition);
244     foreach ($breakpoints as $breakpoint => $elements) {
245       foreach ($elements as $key => $element) {
246         $form['breakpoints'][$breakpoint][$key] = $element;
247
248         if (isset($definition['vanilla'])) {
249           $form['breakpoints'][$breakpoint][$key]['#states']['enabled'][$vanilla] = ['checked' => FALSE];
250         }
251         $value = isset($settings['breakpoints'][$breakpoint][$key]) ? $settings['breakpoints'][$breakpoint][$key] : '';
252         $form['breakpoints'][$breakpoint][$key]['#default_value'] = $value;
253       }
254     }
255   }
256
257   /**
258    * Defines re-usable breakpoints form.
259    */
260   public function breakpointElements($definition = []) {
261     foreach ($definition['breakpoints'] as $breakpoint) {
262       $form[$breakpoint]['breakpoint'] = [
263         '#type'               => 'item',
264         '#markup'             => $breakpoint,
265         '#weight'             => 1,
266         '#wrapper_attributes' => ['class' => ['form-item--right']],
267       ];
268
269       $form[$breakpoint]['image_style'] = [
270         '#type'               => 'select',
271         '#title'              => $this->t('Image style'),
272         '#title_display'      => 'invisible',
273         '#options'            => function_exists('image_style_options') ? image_style_options(FALSE) : [],
274         '#empty_option'       => $this->t('- None -'),
275         '#weight'             => 2,
276         '#wrapper_attributes' => ['class' => ['form-item--left']],
277       ];
278
279       $form[$breakpoint]['width'] = [
280         '#type'               => 'textfield',
281         '#title'              => $this->t('Width'),
282         '#title_display'      => 'invisible',
283         '#description'        => $this->t('See <strong>XS</strong> for detailed info.'),
284         '#maz_length'         => 32,
285         '#size'               => 6,
286         '#weight'             => 3,
287         '#attributes'         => ['class' => ['form-text--width', 'js-expandable']],
288         '#wrapper_attributes' => ['class' => ['form-item--width']],
289       ];
290
291       if ($breakpoint == 'xs') {
292         $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.');
293       }
294     }
295
296     return $form;
297   }
298
299   /**
300    * Returns re-usable grid elements across field formatter and Views.
301    */
302   public function gridForm(array &$form, $definition = []) {
303     $range = range(1, 12);
304     $grid_options = array_combine($range, $range);
305
306     $header = $this->t('Group individual items as block grid<small>Depends on the <strong>Display style</strong>.</small>');
307     $form['grid_header'] = [
308       '#type'   => 'item',
309       '#markup' => '<h3 class="form__title form__title--grid">' . $header . '</h3>',
310     ];
311
312     $form['grid'] = [
313       '#type'        => 'select',
314       '#title'       => $this->t('Grid large'),
315       '#options'     => $grid_options,
316       '#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.'),
317       '#enforced'    => TRUE,
318     ];
319
320     $form['grid_medium'] = [
321       '#type'        => 'select',
322       '#title'       => $this->t('Grid medium'),
323       '#options'     => $grid_options,
324       '#description' => $this->t('The amount of block grid columns for medium devices 40.063em - 64em.'),
325     ];
326
327     $form['grid_small'] = [
328       '#type'        => 'select',
329       '#title'       => $this->t('Grid small'),
330       '#options'     => $grid_options,
331       '#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.'),
332     ];
333
334     $form['visible_items'] = [
335       '#type'        => 'select',
336       '#title'       => $this->t('Visible items'),
337       '#options'     => array_combine(range(1, 32), range(1, 32)),
338       '#description' => $this->t('How many items per display at a time.'),
339     ];
340
341     $form['preserve_keys'] = [
342       '#type'        => 'checkbox',
343       '#title'       => $this->t('Preserve keys'),
344       '#description' => $this->t('If checked, keys will be preserved. Default is FALSE which will reindex the grid chunk numerically.'),
345     ];
346
347     $grids = [
348       'grid_header',
349       'grid_medium',
350       'grid_small',
351       'visible_items',
352       'preserve_keys',
353     ];
354
355     foreach ($grids as $key) {
356       $form[$key]['#enforced'] = TRUE;
357       $form[$key]['#states'] = [
358         'visible' => [
359           'select[name$="[grid]"]' => ['!value' => ''],
360         ],
361       ];
362     }
363   }
364
365   /**
366    * Returns shared ending form elements across field formatter and Views.
367    */
368   public function closingForm(array &$form, $definition = []) {
369     if (isset($definition['current_view_mode'])) {
370       $form['current_view_mode'] = [
371         '#type'          => 'hidden',
372         '#default_value' => isset($definition['current_view_mode']) ? $definition['current_view_mode'] : '_custom',
373         '#weight'        => 120,
374       ];
375     }
376
377     $this->finalizeForm($form, $definition);
378   }
379
380   /**
381    * Returns simple form elements common for Views field, EB widget, formatters.
382    */
383   public function baseForm($definition = []) {
384     $settings      = isset($definition['settings']) ? $definition['settings'] : [];
385     $lightboxes    = $this->blazyManager->getLightboxes();
386     $image_styles  = function_exists('image_style_options') ? image_style_options(FALSE) : [];
387     $is_responsive = function_exists('responsive_image_get_image_dimensions') && !empty($definition['responsive_image']);
388
389     $form = [];
390     if (empty($definition['no_image_style'])) {
391       $form['image_style'] = [
392         '#type'        => 'select',
393         '#title'       => $this->t('Image style'),
394         '#options'     => $image_styles,
395         '#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.'),
396         '#weight'      => -100,
397       ];
398     }
399
400     if (isset($settings['media_switch'])) {
401       $form['media_switch'] = [
402         '#type'         => 'select',
403         '#title'        => $this->t('Media switcher'),
404         '#options'      => [
405           'content' => $this->t('Image linked to content'),
406         ],
407         '#empty_option' => $this->t('- None -'),
408         '#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.'),
409         '#weight'       => -99,
410       ];
411
412       // Optional lightbox integration.
413       if (!empty($lightboxes)) {
414         foreach ($lightboxes as $lightbox) {
415           $form['media_switch']['#options'][$lightbox] = $this->t('Image to @lightbox', ['@lightbox' => $lightbox]);
416         }
417
418         // Re-use the same image style for both lightboxes.
419         $form['box_style'] = [
420           '#type'    => 'select',
421           '#title'   => $this->t('Lightbox image style'),
422           '#options' => $image_styles,
423           '#states'  => $this->getState(static::STATE_LIGHTBOX_ENABLED, $definition),
424           '#weight'  => -99,
425         ];
426
427         if (!empty($definition['multimedia'])) {
428           $form['box_media_style'] = [
429             '#type'        => 'select',
430             '#title'       => $this->t('Lightbox video style'),
431             '#options'     => $image_styles,
432             '#description' => $this->t('Allows different lightbox video dimensions. Or can be used to have a swipable video if Blazy PhotoSwipe installed.'),
433             '#states'      => $this->getState(static::STATE_LIGHTBOX_ENABLED, $definition),
434             '#weight'      => -99,
435           ];
436         }
437       }
438
439       // Adds common supported entities for media integration.
440       if (!empty($definition['multimedia'])) {
441         $form['media_switch']['#options']['media'] = $this->t('Image to iFrame');
442       }
443
444       // http://en.wikipedia.org/wiki/List_of_common_resolutions
445       $ratio = ['1:1', '3:2', '4:3', '8:5', '16:9', 'fluid', 'enforced'];
446       if (empty($definition['no_ratio'])) {
447         $form['ratio'] = [
448           '#type'         => 'select',
449           '#title'        => $this->t('Aspect ratio'),
450           '#options'      => array_combine($ratio, $ratio),
451           '#empty_option' => $this->t('- None -'),
452           '#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.', [
453             '@dimensions'  => '//size43.com/jqueryVideoTool.html',
454             '@follow'      => '//en.wikipedia.org/wiki/Aspect_ratio_%28image%29',
455             '@link'        => '//www.smashingmagazine.com/2014/02/27/making-embedded-content-work-in-responsive-design/',
456           ]),
457           '#weight'        => -96,
458         ];
459
460         if ($is_responsive) {
461           $form['ratio']['#states'] = $this->getState(static::STATE_RESPONSIVE_IMAGE_STYLE_DISABLED, $definition);
462         }
463       }
464     }
465
466     if (!empty($definition['target_type']) && !empty($definition['view_mode'])) {
467       $form['view_mode'] = [
468         '#type'        => 'select',
469         '#options'     => $this->getViewModeOptions($definition['target_type']),
470         '#title'       => $this->t('View mode'),
471         '#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.'),
472         '#weight'      => -96,
473         '#enforced'    => TRUE,
474       ];
475
476       if ($this->blazyManager()->getModuleHandler()->moduleExists('field_ui')) {
477         $form['view_mode']['#description'] .= $this->t('Manage view modes on the <a href=":view_modes">View modes page</a>.', [':view_modes' => Url::fromRoute('entity.entity_view_mode.collection')->toString()]);
478       }
479     }
480
481     if (!empty($definition['thumbnail_style'])) {
482       $form['thumbnail_style'] = [
483         '#type'        => 'select',
484         '#title'       => $this->t('Thumbnail style'),
485         '#options'     => function_exists('image_style_options') ? image_style_options(TRUE) : [],
486         '#description' => $this->t('Usages: Photobox/PhotoSwipe thumbnail, or custom work with thumbnails. Leave empty to not use thumbnails.'),
487         '#weight'      => -100,
488       ];
489     }
490
491     $this->blazyManager->getModuleHandler()->alter('blazy_base_form_element', $form, $definition);
492
493     return $form;
494   }
495
496   /**
497    * Returns re-usable media switch form elements.
498    */
499   public function mediaSwitchForm(array &$form, $definition = []) {
500     $settings   = isset($definition['settings']) ? $definition['settings'] : [];
501     $lightboxes = $this->blazyManager->getLightboxes();
502     $is_token   = function_exists('token_theme');
503
504     if (isset($settings['media_switch'])) {
505       $form['media_switch'] = $this->baseForm($definition)['media_switch'];
506       $form['media_switch']['#prefix'] = '<h3 class="form__title form__title--media-switch">' . $this->t('Media switcher') . '</h3>';
507
508       if (empty($definition['no_ratio'])) {
509         $form['ratio'] = $this->baseForm($definition)['ratio'];
510       }
511     }
512
513     if (!empty($definition['multimedia']) && empty($definition['no_iframe_lazy'])) {
514       $form['iframe_lazy'] = [
515         '#type'        => 'checkbox',
516         '#title'       => $this->t('Lazy iframe'),
517         '#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']),
518         '#weight'      => -96,
519         '#states'      => $this->getState(static::STATE_IFRAME_ENABLED, $definition),
520       ];
521     }
522
523     // Optional lightbox integration.
524     if (!empty($lightboxes) && isset($settings['media_switch'])) {
525       $form['box_style'] = $this->baseForm($definition)['box_style'];
526
527       if (!empty($definition['multimedia'])) {
528         $form['box_media_style'] = $this->baseForm($definition)['box_media_style'];
529       }
530
531       $box_captions = [
532         'auto'         => $this->t('Automatic'),
533         'alt'          => $this->t('Alt text'),
534         'title'        => $this->t('Title text'),
535         'alt_title'    => $this->t('Alt and Title'),
536         'title_alt'    => $this->t('Title and Alt'),
537         'entity_title' => $this->t('Content title'),
538         'custom'       => $this->t('Custom'),
539       ];
540
541       if (!empty($definition['box_captions'])) {
542         $form['box_caption'] = [
543           '#type'        => 'select',
544           '#title'       => $this->t('Lightbox caption'),
545           '#options'     => $box_captions,
546           '#weight'      => -99,
547           '#states'      => $this->getState(static::STATE_LIGHTBOX_ENABLED, $definition),
548           '#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.'),
549         ];
550
551         $form['box_caption_custom'] = [
552           '#title'       => $this->t('Lightbox custom caption'),
553           '#type'        => 'textfield',
554           '#weight'      => -99,
555           '#states'      => $this->getState(static::STATE_LIGHTBOX_CUSTOM, $definition),
556           '#description' => $this->t('Multi-value rich text field will be mapped to each image by its delta.'),
557         ];
558
559         if ($is_token) {
560           $types = isset($definition['entity_type']) ? [$definition['entity_type']] : [];
561           $types = isset($definition['target_type']) ? array_merge($types, [$definition['target_type']]) : $types;
562           $form['box_caption_custom']['#field_suffix'] = [
563             '#theme'       => 'token_tree_link',
564             '#text'        => $this->t('Tokens'),
565             '#token_types' => $types,
566           ];
567         }
568       }
569     }
570
571     $this->blazyManager->getModuleHandler()->alter('blazy_media_switch_form_element', $form, $definition);
572   }
573
574   /**
575    * Returns re-usable logic, styling and assets across fields and Views.
576    */
577   public function finalizeForm(array &$form, $definition = []) {
578     $namespace = isset($definition['namespace']) ? $definition['namespace'] : 'slick';
579     $settings = isset($definition['settings']) ? $definition['settings'] : [];
580     $vanilla = isset($definition['vanilla']) ? ' form--vanilla' : '';
581     $captions = empty($definition['captions']) ? 0 : count($definition['captions']);
582     $wide = $captions > 2 ? ' form--wide form--caption-' . $captions : ' form--caption-' . $captions;
583     $fallback = $namespace == 'slick' ? 'form--slick' : 'form--' . $namespace . ' form--slick';
584     $classes = isset($definition['form_opening_classes'])
585       ? $definition['form_opening_classes']
586       : $fallback . ' form--half has-tooltip' . $wide . $vanilla;
587
588     if (!empty($definition['field_type'])) {
589       $classes .= ' form--' . str_replace('_', '-', $definition['field_type']);
590     }
591
592     $form['opening'] = [
593       '#markup' => '<div class="' . $classes . '">',
594       '#weight' => -120,
595     ];
596
597     $form['closing'] = [
598       '#markup' => '</div>',
599       '#weight' => 120,
600     ];
601
602     $admin_css = isset($definition['admin_css']) ? $definition['admin_css'] : '';
603     $admin_css = $admin_css ?: $this->blazyManager->configLoad('admin_css', 'blazy.settings');
604
605     // @todo: Check if needed: 'button', 'container', 'submit'.
606     $excludes = ['details', 'fieldset', 'hidden', 'markup', 'item', 'table'];
607     $selects  = ['cache', 'optionset', 'view_mode'];
608
609     foreach (Element::children($form) as $key) {
610       if (isset($form[$key]['#type']) && !in_array($form[$key]['#type'], $excludes)) {
611         if (!isset($form[$key]['#default_value']) && isset($settings[$key])) {
612           $value = is_array($settings[$key]) ? array_values((array) $settings[$key]) : $settings[$key];
613           $form[$key]['#default_value'] = $value;
614         }
615         if (!isset($form[$key]['#attributes']) && isset($form[$key]['#description'])) {
616           $form[$key]['#attributes'] = ['class' => ['is-tooltip']];
617         }
618
619         if ($admin_css) {
620           if ($form[$key]['#type'] == 'checkbox' && $form[$key]['#type'] != 'checkboxes') {
621             $form[$key]['#field_suffix'] = '&nbsp;';
622             $form[$key]['#title_display'] = 'before';
623           }
624           elseif ($form[$key]['#type'] == 'checkboxes' && !empty($form[$key]['#options'])) {
625             $form[$key]['#attributes']['class'][] = 'form-wrapper--checkboxes';
626             $form[$key]['#attributes']['class'][] = 'form-wrapper--' . str_replace('_', '-', $key);
627             $count = count($form[$key]['#options']);
628             $form[$key]['#attributes']['class'][] = 'form-wrapper--count-' . ($count > 3 ? 'max' : $count);
629
630             foreach ($form[$key]['#options'] as $i => $option) {
631               $form[$key][$i]['#field_suffix'] = '&nbsp;';
632               $form[$key][$i]['#title_display'] = 'before';
633             }
634           }
635         }
636
637         if ($form[$key]['#type'] == 'select' && !in_array($key, $selects)) {
638           if (!isset($form[$key]['#empty_option']) && !isset($form[$key]['#required'])) {
639             $form[$key]['#empty_option'] = $this->t('- None -');
640           }
641         }
642
643         if (!isset($form[$key]['#enforced']) && isset($definition['vanilla']) && isset($form[$key]['#type'])) {
644           $states['visible'][':input[name*="[vanilla]"]'] = ['checked' => FALSE];
645           if (isset($form[$key]['#states'])) {
646             $form[$key]['#states']['visible'][':input[name*="[vanilla]"]'] = ['checked' => FALSE];
647           }
648           else {
649             $form[$key]['#states'] = $states;
650           }
651         }
652       }
653
654       $form[$key]['#wrapper_attributes']['class'][] = 'form-item--' . str_replace('_', '-', $key);
655
656       if (isset($form[$key]['#access']) && $form[$key]['#access'] == FALSE) {
657         unset($form[$key]['#default_value']);
658       }
659     }
660
661     if ($admin_css) {
662       $form['closing']['#attached']['library'][] = 'blazy/admin';
663     }
664
665     $this->blazyManager->getModuleHandler()->alter('blazy_complete_form_element', $form, $definition);
666   }
667
668   /**
669    * Returns time in interval for select options.
670    */
671   public function getCacheOptions() {
672     $period = [
673       0,
674       60,
675       180,
676       300,
677       600,
678       900,
679       1800,
680       2700,
681       3600,
682       10800,
683       21600,
684       32400,
685       43200,
686       86400,
687     ];
688     $period = array_map([\Drupal::service('date.formatter'), 'formatInterval'], array_combine($period, $period));
689     $period[0] = '<' . $this->t('No caching') . '>';
690     return $period + [Cache::PERMANENT => $this->t('Permanent')];
691   }
692
693   /**
694    * Returns available optionsets for select options.
695    */
696   public function getOptionsetOptions($entity_type = '') {
697     $optionsets = [];
698     if (empty($entity_type)) {
699       return $optionsets;
700     }
701
702     $entities = $this->blazyManager->entityLoadMultiple($entity_type);
703     foreach ((array) $entities as $entity) {
704       $optionsets[$entity->id()] = Html::escape($entity->label());
705     }
706     asort($optionsets);
707     return $optionsets;
708   }
709
710   /**
711    * Returns available view modes for select options.
712    */
713   public function getViewModeOptions($target_type) {
714     return $this->entityDisplayRepository->getViewModeOptions($target_type);
715   }
716
717   /**
718    * Get one of the pre-defined states used in this form.
719    *
720    * Thanks to SAM152 at colorbox.module for the little sweet idea.
721    *
722    * @param string $state
723    *   The state to get that matches one of the state class constants.
724    *
725    * @return array
726    *   A corresponding form API state.
727    */
728   protected function getState($state, $definition = []) {
729     $lightboxes = [];
730
731     foreach ($this->blazyManager->getLightboxes() as $key => $lightbox) {
732       $lightboxes[$key]['value'] = $lightbox;
733     }
734
735     $states = [
736       static::STATE_RESPONSIVE_IMAGE_STYLE_DISABLED => [
737         'visible' => [
738           'select[name$="[responsive_image_style]"]' => ['value' => ''],
739         ],
740       ],
741       static::STATE_LIGHTBOX_ENABLED => [
742         'visible' => [
743           'select[name*="[media_switch]"]' => $lightboxes,
744         ],
745       ],
746       static::STATE_LIGHTBOX_CUSTOM => [
747         'visible' => [
748           'select[name$="[box_caption]"]' => ['value' => 'custom'],
749           'select[name*="[media_switch]"]' => $lightboxes,
750         ],
751       ],
752       static::STATE_IFRAME_ENABLED => [
753         'visible' => [
754           'select[name*="[media_switch]"]' => ['value' => 'media'],
755         ],
756       ],
757       static::STATE_THUMBNAIL_STYLE_ENABLED => [
758         'visible' => [
759           'select[name$="[thumbnail_style]"]' => ['!value' => ''],
760         ],
761       ],
762       static::STATE_IMAGE_RENDERED_ENABLED => [
763         'visible' => [
764           'select[name$="[media_switch]"]' => ['!value' => 'rendered'],
765         ],
766       ],
767     ];
768     return $states[$state];
769   }
770
771 }