3 namespace Drupal\slick;
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;
13 * Implements BlazyManagerInterface, SlickManagerInterface.
15 class SlickManager extends BlazyManagerBase implements BlazyManagerInterface, SlickManagerInterface {
18 * The supported skins.
22 private static $skins = [
33 * Static cache for the skin definition.
37 protected $skinDefinition;
40 * Returns the supported skins.
42 public static function getConstantSkins() {
47 * Returns slick skins registered via hook_slick_skins_info(), or defaults.
49 * @see \Drupal\blazy\BlazyManagerBase::buildSkins()
51 public function getSkins() {
52 if (!isset($this->skinDefinition)) {
53 $methods = ['skins', 'arrows', 'dots'];
54 $this->skinDefinition = $this->buildSkins('slick', '\Drupal\slick\SlickSkin', $methods);
57 return $this->skinDefinition;
61 * Returns available slick skins by group.
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'];
68 foreach ($defined_skins as $skin => $properties) {
69 $item = $option ? Html::escape($properties['name']) : $properties;
71 if (isset($properties['group'])) {
72 if ($properties['group'] != $group) {
75 $groups[$skin] = $item;
77 elseif (!$nav_skins) {
78 $ungroups[$skin] = $item;
81 $skins[$skin] = $item;
84 return $group ? array_merge($ungroups, $groups) : $skins;
88 * Implements hook_library_info_build().
90 public function libraryInfoBuild() {
91 $libraries['slick.css'] = [
92 'dependencies' => ['slick/slick'],
94 'theme' => ['/libraries/slick/slick/slick-theme.css' => []],
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;
104 foreach (['css', 'js', 'dependencies'] as $property) {
105 if (isset($skin[$property]) && is_array($skin[$property])) {
106 $libraries[$id][$property] = $skin[$property];
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');
123 $load = parent::attach($attach);
125 if (!empty($attach['lazy'])) {
126 $load['library'][] = 'blazy/loading';
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';
134 $load['library'][] = 'slick/slick.load';
136 $components = ['colorbox', 'mousewheel'];
137 foreach ($components as $component) {
138 if (!empty($attach[$component])) {
139 $load['library'][] = 'slick/slick.' . $component;
143 if (!empty($attach['skin'])) {
144 $this->attachSkin($load, $attach);
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);
153 $this->moduleHandler->alter('slick_attach_load_info', $load, $attach);
158 * Provides skins only if required.
160 public function attachSkin(array &$load, $attach = []) {
161 if ($attach['slick_css']) {
162 $load['library'][] = 'slick/slick.css';
165 if ($attach['module_css']) {
166 $load['library'][] = 'slick/slick.theme';
169 if (!empty($attach['thumbnail_effect'])) {
170 $load['library'][] = 'slick/slick.thumbnail.' . $attach['thumbnail_effect'];
173 if (!empty($attach['down_arrow'])) {
174 $load['library'][] = 'slick/slick.arrow.down';
177 foreach (self::getConstantSkins() as $group) {
178 $skin = $group == 'main' ? $attach['skin'] : (isset($attach['skin_' . $group]) ? $attach['skin_' . $group] : '');
180 $skins = $this->getSkinsByGroup($group);
181 $provider = isset($skins[$skin]['provider']) ? $skins[$skin]['provider'] : 'slick';
182 $load['library'][] = 'slick/' . $provider . '.' . $group . '.' . $skin;
190 public static function slick(array $build = []) {
191 foreach (['items', 'options', 'optionset', 'settings'] as $key) {
192 $build[$key] = isset($build[$key]) ? $build[$key] : [];
195 if (empty($build['items'])) {
203 '#pre_render' => [static::class . '::preRenderSlick'],
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, '.');
217 if (!empty($settings['cache_tags'])) {
218 $cache['tags'] = Cache::mergeTags($cache['tags'], $settings['cache_tags']);
221 $slick['#cache'] = $cache;
228 * Builds the Slick instance as a structured array ready for ::renderer().
230 public static function preRenderSlick(array $element) {
231 $build = $element['#build'];
232 unset($element['#build']);
234 $settings = &$build['settings'];
235 if (empty($build['items'])) {
239 // Adds helper class if thumbnail on dots hover provided.
241 if (!empty($settings['thumbnail_style']) && !empty($settings['thumbnail_effect'])) {
242 $dots_class[] = 'slick-dots--thumbnail-' . $settings['thumbnail_effect'];
245 // Adds dots skin modifier class if provided.
246 if (!empty($settings['skin_dots'])) {
247 $dots_class[] = Html::cleanCssIdentifier('slick-dots--' . $settings['skin_dots']);
250 if ($dots_class && !empty($build['optionset'])) {
251 $dots_class[] = $build['optionset']->getSetting('dotsClass') ?: 'slick-dots';
252 $js['dotsClass'] = implode(" ", $dots_class);
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;
263 // Build the Slick grid if provided.
264 if (!empty($settings['grid']) && !empty($settings['visible_items'])) {
265 $build['items'] = self::buildGrid($build['items'], $settings);
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];
278 * Returns items as a grid display.
280 public static function buildGrid(array $items = [], array &$settings = []) {
283 // Enforces unslick with less items.
284 if (empty($settings['unslick']) && !empty($settings['count'])) {
285 $settings['unslick'] = $settings['count'] < $settings['visible_items'];
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;
296 '#theme' => 'slick_grid',
299 '#settings' => $settings,
301 $slide['settings'] = $settings;
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);
310 foreach ($grid_items as $delta => $grid_item) {
313 '#theme' => 'slick_grid',
314 '#items' => $grid_item,
316 '#settings' => $settings,
318 $slide['settings'] = $settings;
329 public function build(array $build = []) {
330 foreach (['items', 'options', 'optionset', 'settings'] as $key) {
331 $build[$key] = isset($build[$key]) ? $build[$key] : [];
334 return empty($build['items']) ? [] : [
335 '#theme' => 'slick_wrapper',
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.
349 public function preRenderSlickWrapper($element) {
350 $build = $element['#build'];
351 unset($element['#build']);
353 if (empty($build['items'])) {
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'] : '';
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']);
376 // Additional settings.
377 $build['optionset'] = $build['optionset'] ?: Slick::load($settings['optionset']);
379 // Ensures deleted optionset while being used doesn't screw up.
380 if (empty($build['optionset'])) {
381 $build['optionset'] = Slick::load('default');
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');
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');
399 if ($switch && $switch != 'content') {
400 $settings[$switch] = $switch;
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;
411 // Build the Slick wrapper elements.
412 $element['#settings'] = $settings;
413 $element['#attached'] = empty($build['attached']) ? $attachments : NestedArray::mergeDeep($build['attached'], $attachments);
415 // Build the main Slick.
416 $slick[0] = self::slick($build);
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] : [];
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";
432 unset($build['thumb']);
433 $slick[1] = self::slick($build);
436 // Reverse slicks if thumbnail position is provided to get CSS float work.
437 if ($settings['navpos']) {
438 $slick = array_reverse($slick);
441 // Collect the slick instances.
442 $element['#items'] = $slick;