Version 1
[yaffs-website] / web / modules / contrib / slick / src / SlickManager.php
1 <?php
2
3 namespace Drupal\slick;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Utility\NestedArray;
7 use Drupal\Core\Cache\Cache;
8 use Drupal\slick\Entity\Slick;
9 use Drupal\blazy\BlazyManagerBase;
10 use Drupal\blazy\BlazyManagerInterface;
11
12 /**
13  * Implements BlazyManagerInterface, SlickManagerInterface.
14  */
15 class SlickManager extends BlazyManagerBase implements BlazyManagerInterface, SlickManagerInterface {
16
17   /**
18    * The supported skins.
19    *
20    * @var array
21    */
22   private static $skins = [
23     'browser',
24     'overlay',
25     'main',
26     'thumbnail',
27     'arrows',
28     'dots',
29     'widget',
30   ];
31
32   /**
33    * Returns the supported skins.
34    */
35   public static function getConstantSkins() {
36     return self::$skins;
37   }
38
39   /**
40    * Returns slick skins registered via hook_slick_skins_info(), or defaults.
41    *
42    * @see \Drupal\blazy\BlazyManagerBase::buildSkins()
43    */
44   public function getSkins() {
45     $skins = &drupal_static(__METHOD__, NULL);
46     if (!isset($skins)) {
47       $methods = ['skins', 'arrows', 'dots'];
48       $skins = $this->buildSkins('slick', '\Drupal\slick\SlickSkin', $methods);
49     }
50     return $skins;
51   }
52
53   /**
54    * Returns available slick skins by group.
55    */
56   public function getSkinsByGroup($group = '', $option = FALSE) {
57     $skins         = $groups = $ungroups = [];
58     $nav_skins     = in_array($group, ['arrows', 'dots']);
59     $defined_skins = $nav_skins ? $this->getSkins()[$group] : $this->getSkins()['skins'];
60
61     foreach ($defined_skins as $skin => $properties) {
62       $item = $option ? Html::escape($properties['name']) : $properties;
63       if (!empty($group)) {
64         if (isset($properties['group'])) {
65           if ($properties['group'] != $group) {
66             continue;
67           }
68           $groups[$skin] = $item;
69         }
70         elseif (!$nav_skins) {
71           $ungroups[$skin] = $item;
72         }
73       }
74       $skins[$skin] = $item;
75     }
76
77     return $group ? array_merge($ungroups, $groups) : $skins;
78   }
79
80   /**
81    * Implements hook_library_info_build().
82    */
83   public function libraryInfoBuild() {
84     $libraries['slick.css'] = [
85       'dependencies' => ['slick/slick'],
86       'css' => [
87         'theme' => ['/libraries/slick/slick/slick-theme.css' => []],
88       ],
89     ];
90
91     foreach (self::getConstantSkins() as $group) {
92       if ($skins = $this->getSkinsByGroup($group)) {
93         foreach ($skins as $key => $skin) {
94           $provider = isset($skin['provider']) ? $skin['provider'] : 'slick';
95           $id = $provider . '.' . $group . '.' . $key;
96
97           foreach (['css', 'js', 'dependencies'] as $property) {
98             if (isset($skin[$property]) && is_array($skin[$property])) {
99               $libraries[$id][$property] = $skin[$property];
100             }
101           }
102         }
103       }
104     }
105
106     return $libraries;
107   }
108
109   /**
110    * {@inheritdoc}
111    */
112   public function attach($attach = []) {
113     $attach += [
114       'slick_css'  => $this->configLoad('slick_css', 'slick.settings'),
115       'module_css' => $this->configLoad('module_css', 'slick.settings'),
116     ];
117
118     $load = parent::attach($attach);
119
120     if (!empty($attach['lazy'])) {
121       $load['library'][] = 'blazy/loading';
122     }
123
124     // @todo: Only load slick if not static grid.
125     if (is_file('libraries/easing/jquery.easing.min.js')) {
126       $load['library'][] = 'slick/slick.easing';
127     }
128
129     $load['library'][] = 'slick/slick.load';
130
131     $components = ['colorbox', 'mousewheel'];
132     foreach ($components as $component) {
133       if (!empty($attach[$component])) {
134         $load['library'][] = 'slick/slick.' . $component;
135       }
136     }
137
138     if (!empty($attach['skin'])) {
139       $this->attachSkin($load, $attach);
140     }
141
142     // Attach default JS settings to allow responsive displays have a lookup,
143     // excluding wasted/trouble options, e.g.: PHP string vs JS object.
144     $excludes = explode(' ', 'mobileFirst appendArrows appendDots asNavFor prevArrow nextArrow respondTo');
145     $excludes = array_combine($excludes, $excludes);
146     $load['drupalSettings']['slick'] = array_diff_key(Slick::defaultSettings(), $excludes);
147
148     $this->moduleHandler->alter('slick_attach_load_info', $load, $attach);
149     return $load;
150   }
151
152   /**
153    * Provides skins only if required.
154    */
155   public function attachSkin(array &$load, $attach = []) {
156     if ($attach['slick_css']) {
157       $load['library'][] = 'slick/slick.css';
158     }
159
160     if ($attach['module_css']) {
161       $load['library'][] = 'slick/slick.theme';
162     }
163
164     if (!empty($attach['thumbnail_effect'])) {
165       $load['library'][] = 'slick/slick.thumbnail.' . $attach['thumbnail_effect'];
166     }
167
168     if (!empty($attach['down_arrow'])) {
169       $load['library'][] = 'slick/slick.arrow.down';
170     }
171
172     foreach (self::getConstantSkins() as $group) {
173       $skin = $group == 'main' ? $attach['skin'] : (isset($attach['skin_' . $group]) ? $attach['skin_' . $group] : '');
174       if (!empty($skin)) {
175         $skins = $this->getSkinsByGroup($group);
176         $provider = isset($skins[$skin]['provider']) ? $skins[$skin]['provider'] : 'slick';
177         $load['library'][] = 'slick/' . $provider . '.' . $group . '.' . $skin;
178       }
179     }
180   }
181
182   /**
183    * {@inheritdoc}
184    */
185   public static function slick(array $build = []) {
186     foreach (['items', 'options', 'optionset', 'settings'] as $key) {
187       $build[$key] = isset($build[$key]) ? $build[$key] : [];
188     }
189
190     if (empty($build['items'])) {
191       return [];
192     }
193
194     $slick = [
195       '#theme'      => 'slick',
196       '#items'      => [],
197       '#build'      => $build,
198       '#pre_render' => [static::class . '::preRenderSlick'],
199     ];
200
201     $settings = $build['settings'];
202     if (isset($settings['cache'])) {
203       $suffixes[]        = count($build['items']);
204       $suffixes[]        = count(array_filter($settings));
205       $suffixes[]        = $settings['cache'];
206       $cache['contexts'] = ['languages'];
207       $cache['max-age']  = $settings['cache'];
208       $cache['keys']     = isset($settings['cache_metadata']['keys']) ? $settings['cache_metadata']['keys'] : [$settings['id']];
209       $cache['keys'][]   = $settings['display'];
210       $cache['tags']     = Cache::buildTags('slick:' . $settings['id'], $suffixes, '.');
211
212       if (!empty($settings['cache_tags'])) {
213         $cache['tags'] = array_merge($cache['tags'], $settings['cache_tags']);
214       }
215
216       $slick['#cache'] = $cache;
217     }
218
219     return $slick;
220   }
221
222   /**
223    * Builds the Slick instance as a structured array ready for ::renderer().
224    */
225   public static function preRenderSlick(array $element) {
226     $build = $element['#build'];
227     unset($element['#build']);
228
229     $settings = &$build['settings'];
230     if (empty($build['items'])) {
231       return [];
232     }
233
234     // Adds helper class if thumbnail on dots hover provided.
235     $dots_class = [];
236     if (!empty($settings['thumbnail_style']) && !empty($settings['thumbnail_effect'])) {
237       $dots_class[] = 'slick-dots--thumbnail-' . $settings['thumbnail_effect'];
238     }
239
240     // Adds dots skin modifier class if provided.
241     if (!empty($settings['skin_dots'])) {
242       $dots_class[] = Html::cleanCssIdentifier('slick-dots--' . $settings['skin_dots']);
243     }
244
245     if ($dots_class) {
246       $dots_class[] = $build['optionset']->getSetting('dotsClass') ?: 'slick-dots';
247       $js['dotsClass'] = implode(" ", $dots_class);
248     }
249
250     // Overrides common options to re-use an optionset.
251     if ($settings['display'] == 'main') {
252       if (!empty($settings['override'])) {
253         foreach ($settings['overridables'] as $key => $override) {
254           $js[$key] = empty($override) ? FALSE : TRUE;
255         }
256       }
257
258       // Build the Slick grid if provided.
259       if (!empty($settings['grid']) && !empty($settings['visible_items'])) {
260         $build['items'] = self::buildGrid($build['items'], $settings);
261       }
262     }
263
264     $build['options'] = isset($js) ? array_merge($build['options'], $js) : $build['options'];
265     foreach (['items', 'options', 'optionset', 'settings'] as $key) {
266       $element["#$key"] = $build[$key];
267     }
268
269     return $element;
270   }
271
272   /**
273    * Returns items as a grid display.
274    */
275   public static function buildGrid(array $items = [], array &$settings = []) {
276     $grids = [];
277
278     // Enforces unslick with less items.
279     if (empty($settings['unslick']) && !empty($settings['count'])) {
280       $settings['unslick'] = $settings['count'] < $settings['visible_items'];
281     }
282
283     // Display all items if unslick is enforced for plain grid to lightbox.
284     // Or when the total is less than visible_items.
285     if (!empty($settings['unslick'])) {
286       $settings['display']      = 'main';
287       $settings['current_item'] = 'grid';
288       $settings['count']        = 2;
289
290       $slide['slide'] = [
291         '#theme'    => 'slick_grid',
292         '#items'    => $items,
293         '#delta'    => 0,
294         '#settings' => $settings,
295       ];
296       $slide['settings'] = $settings;
297       $grids[0] = $slide;
298     }
299     else {
300       // Otherwise do chunks to have a grid carousel, and also update count.
301       $preserve_keys     = !empty($settings['preserve_keys']);
302       $grid_items        = array_chunk($items, $settings['visible_items'], $preserve_keys);
303       $settings['count'] = count($grid_items);
304
305       foreach ($grid_items as $delta => $grid_item) {
306         $slide = [];
307         $slide['slide'] = [
308           '#theme'    => 'slick_grid',
309           '#items'    => $grid_item,
310           '#delta'    => $delta,
311           '#settings' => $settings,
312         ];
313         $slide['settings'] = $settings;
314         $grids[] = $slide;
315         unset($slide);
316       }
317     }
318     return $grids;
319   }
320
321   /**
322    * {@inheritdoc}
323    */
324   public function build(array $build = []) {
325     foreach (['items', 'options', 'optionset', 'settings'] as $key) {
326       $build[$key] = isset($build[$key]) ? $build[$key] : [];
327     }
328
329     return empty($build['items']) ? [] : [
330       '#theme'      => 'slick_wrapper',
331       '#items'      => [],
332       '#build'      => $build,
333       '#pre_render' => [[$this, 'preRenderSlickWrapper']],
334     ];
335   }
336
337   /**
338    * {@inheritdoc}
339    */
340   public function preRenderSlickWrapper($element) {
341     $build = $element['#build'];
342     unset($element['#build']);
343
344     if (empty($build['items'])) {
345       return [];
346     }
347
348     // One slick_theme() to serve multiple displays: main, overlay, thumbnail.
349     $defaults = Slick::htmlSettings();
350     $settings = $build['settings'] ? array_merge($defaults, $build['settings']) : $defaults;
351     $id       = isset($settings['id']) ? $settings['id'] : '';
352     $id       = Slick::getHtmlId('slick', $id);
353     $thumb_id = $id . '-thumbnail';
354     $options  = $build['options'];
355     $switch   = isset($settings['media_switch']) ? $settings['media_switch'] : '';
356
357     // Supports programmatic options defined within skin definitions to allow
358     // addition of options with other libraries integrated with Slick without
359     // modifying Optionset like Zoom, Reflection, Slicebox, etc.
360     if (!empty($settings['skin'])) {
361       $skins = $this->getSkinsByGroup('main');
362       if (isset($skins[$settings['skin']]['options'])) {
363         $options = array_merge($options, $skins[$settings['skin']]['options']);
364       }
365     }
366
367     // Additional settings.
368     $build['optionset'] = $build['optionset'] ?: Slick::load($settings['optionset']);
369
370     // Ensures deleted optionset while being used doesn't screw up.
371     if (empty($build['optionset'])) {
372       $build['optionset'] = Slick::load('default');
373     }
374
375     $settings['count']    = empty($settings['count']) ? count($build['items']) : $settings['count'];
376     $settings['id']       = $id;
377     $settings['nav']      = isset($settings['nav']) ? $settings['nav'] : (!empty($settings['optionset_thumbnail']) && isset($build['items'][1]));
378     $settings['navpos']   = !empty($settings['nav']) && !empty($settings['thumbnail_position']);
379     $settings['vertical'] = $build['optionset']->getSetting('vertical');
380     $mousewheel           = $build['optionset']->getSetting('mouseWheel');
381
382     if ($settings['nav']) {
383       $options['asNavFor']     = "#{$thumb_id}-slider";
384       $optionset_thumbnail     = Slick::load($settings['optionset_thumbnail']);
385       $mousewheel              = $optionset_thumbnail->getSetting('mouseWheel');
386       $settings['vertical_tn'] = $optionset_thumbnail->getSetting('vertical');
387     }
388
389     // Attach libraries.
390     if ($switch && $switch != 'content') {
391       $settings[$switch] = $switch;
392     }
393
394     $settings['mousewheel'] = $mousewheel;
395     $settings['down_arrow'] = $build['optionset']->getSetting('downArrow');
396     $settings['lazy']       = empty($settings['lazy']) ? $build['optionset']->getSetting('lazyLoad') : $settings['lazy'];
397     $settings['blazy']      = empty($settings['blazy']) ? $settings['lazy'] == 'blazy' : $settings['blazy'];
398     $attachments            = $this->attach($settings);
399     $build['options']       = $options;
400     $build['settings']      = $settings;
401
402     // Build the Slick wrapper elements.
403     $element['#settings'] = $settings;
404     $element['#attached'] = empty($build['attached']) ? $attachments : NestedArray::mergeDeep($build['attached'], $attachments);
405
406     // Build the main Slick.
407     $slick[0] = self::slick($build);
408
409     // Build the thumbnail Slick.
410     if (isset($build['thumb'])) {
411       foreach (['items', 'options', 'settings'] as $key) {
412         $build[$key] = isset($build['thumb'][$key]) ? $build['thumb'][$key] : [];
413       }
414
415       $settings                     = array_merge($settings, $build['settings']);
416       $settings['optionset']        = $settings['optionset_thumbnail'];
417       $settings['skin']             = isset($settings['skin_thumbnail']) ? $settings['skin_thumbnail'] : '';
418       $settings['display']          = 'thumbnail';
419       $build['optionset']           = $optionset_thumbnail;
420       $build['settings']            = $settings;
421       $build['options']['asNavFor'] = "#{$id}-slider";
422
423       unset($build['thumb']);
424       $slick[1] = self::slick($build);
425     }
426
427     // Reverse slicks if thumbnail position is provided to get CSS float work.
428     if ($settings['navpos']) {
429       $slick = array_reverse($slick);
430     }
431
432     // Collect the slick instances.
433     $element['#items'] = $slick;
434     return $element;
435   }
436
437 }