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