Version 1
[yaffs-website] / web / modules / contrib / blazy / src / Dejavu / BlazyStylePluginBase.php
1 <?php
2
3 namespace Drupal\blazy\Dejavu;
4
5 use Drupal\Component\Utility\Xss;
6 use Drupal\Component\Utility\Html;
7 use Drupal\Component\Utility\Unicode;
8 use Symfony\Component\DependencyInjection\ContainerInterface;
9 use Drupal\views\Plugin\views\style\StylePluginBase;
10 use Drupal\blazy\BlazyManagerInterface;
11
12 /**
13  * A base for blazy views integration to have re-usable methods in one place.
14  *
15  * @see \Drupal\mason\Plugin\views\style\MasonViews
16  * @see \Drupal\gridstack\Plugin\views\style\GridStackViews
17  * @see \Drupal\slick_views\Plugin\views\style\SlickViews
18  */
19 abstract class BlazyStylePluginBase extends StylePluginBase {
20
21   /**
22    * {@inheritdoc}
23    */
24   protected $usesRowPlugin = TRUE;
25
26   /**
27    * {@inheritdoc}
28    */
29   protected $usesGrouping = FALSE;
30
31   /**
32    * The blazy manager service.
33    *
34    * @var \Drupal\blazy\BlazyManagerInterface
35    */
36   protected $blazyManager;
37
38   /**
39    * Constructs a GridStackManager object.
40    */
41   public function __construct(array $configuration, $plugin_id, $plugin_definition, BlazyManagerInterface $blazy_manager) {
42     parent::__construct($configuration, $plugin_id, $plugin_definition);
43     $this->blazyManager = $blazy_manager;
44   }
45
46   /**
47    * {@inheritdoc}
48    */
49   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
50     return new static($configuration, $plugin_id, $plugin_definition, $container->get('blazy.manager'));
51   }
52
53   /**
54    * Returns the blazy manager.
55    */
56   public function blazyManager() {
57     return $this->blazyManager;
58   }
59
60   /**
61    * Returns available fields for select options.
62    */
63   public function getDefinedFieldOptions($definitions = []) {
64     $field_names = $this->displayHandler->getFieldLabels();
65     $definition = [];
66
67     // Formatter based fields.
68     $options = [];
69     foreach ($this->displayHandler->getOption('fields') as $field => $handler) {
70       // This is formatter based type, not actual field type.
71       if (isset($handler['type'])) {
72         switch ($handler['type']) {
73           // @todo recheck other reasonable image-related formatters.
74           case 'blazy':
75           case 'image':
76           case 'media':
77           case 'media_thumbnail':
78           case 'intense':
79           case 'responsive_image':
80           case 'video_embed_field_thumbnail':
81           case 'video_embed_field_colorbox':
82           case 'youtube_thumbnail':
83             $options['images'][$field] = $field_names[$field];
84             $options['overlays'][$field] = $field_names[$field];
85             $options['thumbnails'][$field] = $field_names[$field];
86             break;
87
88           case 'list_key':
89             $options['layouts'][$field] = $field_names[$field];
90             break;
91
92           case 'entity_reference_label':
93           case 'text':
94           case 'string':
95           case 'link':
96             $options['links'][$field] = $field_names[$field];
97             $options['titles'][$field] = $field_names[$field];
98             if ($handler['type'] != 'link') {
99               $options['thumb_captions'][$field] = $field_names[$field];
100             }
101             break;
102         }
103
104         $classes = ['list_key', 'entity_reference_label', 'text', 'string'];
105         if (in_array($handler['type'], $classes)) {
106           $options['classes'][$field] = $field_names[$field];
107         }
108
109         $slicks   = strpos($handler['type'], 'slick') !== FALSE;
110         $overlays = [
111           'entity_reference_entity_view',
112           'video_embed_field_video',
113           'youtube_video',
114         ];
115         if ($slicks || in_array($handler['type'], $overlays)) {
116           $options['overlays'][$field] = $field_names[$field];
117         }
118
119         // Allows advanced formatters/video as the main image replacement.
120         // They are not reasonable for thumbnails, but main images.
121         // Note: Certain Responsive image has no ID at Views, possibly a bug.
122         $images = [
123           'colorbox',
124           'photobox',
125           'video_embed_field_video',
126           'youtube_video',
127         ];
128         if (in_array($handler['type'], $images)) {
129           $options['images'][$field] = $field_names[$field];
130         }
131       }
132
133       // Content: title is not really a field, unless title.module installed.
134       if (isset($handler['field'])) {
135         if ($handler['field'] == 'title') {
136           $options['classes'][$field] = $field_names[$field];
137           $options['titles'][$field] = $field_names[$field];
138           $options['thumb_captions'][$field] = $field_names[$field];
139         }
140
141         if ($handler['field'] == 'view_node') {
142           $options['links'][$field] = $field_names[$field];
143         }
144
145         $blazies = strpos($handler['field'], 'blazy_') !== FALSE;
146         if ($blazies) {
147           $options['images'][$field] = $field_names[$field];
148           $options['overlays'][$field] = $field_names[$field];
149           $options['thumbnails'][$field] = $field_names[$field];
150         }
151       }
152
153       // Captions can be anything to get custom works going.
154       $options['captions'][$field] = $field_names[$field];
155     }
156
157     $definition['plugin_id'] = $this->getPluginId();
158     $definition['settings'] = $this->options;
159     $definition['current_view_mode'] = $this->view->current_display;
160
161     // Provides the requested fields.
162     foreach ($definitions as $key) {
163       $definition[$key] = isset($options[$key]) ? $options[$key] : [];
164     }
165
166     return $definition;
167   }
168
169   /**
170    * Returns an individual row/element content.
171    */
172   public function buildElement(array &$element, $row, $index) {
173     $settings = &$element['settings'];
174     $item_id  = empty($settings['item_id']) ? 'box' : $settings['item_id'];
175
176     // Add main image fields if so configured.
177     if (!empty($settings['image'])) {
178       // Supports individual grid/box image style either inline IMG, or CSS.
179       $image             = $this->getImageRenderable($settings, $row, $index);
180       $element['item']   = $this->getImageItem($image);
181       $element[$item_id] = empty($image['rendered']) ? [] : $image['rendered'];
182     }
183
184     // Add caption fields if so configured.
185     $element['caption'] = $this->getCaption($index, $settings);
186
187     // Add layout field, may be a list field, or builtin layout options.
188     if (!empty($settings['layout'])) {
189       $this->getLayout($settings, $index);
190     }
191   }
192
193   /**
194    * Returns the modified renderable image_formatter to support lazyload.
195    */
196   public function getImageRenderable(array &$settings, $row, $index) {
197     $image = $this->isImageRenderable($row, $index, $settings['image']);
198
199     /* @var Drupal\image\Plugin\Field\FieldType\ImageItem $item */
200     if (empty($image['raw'])) {
201       return $image;
202     }
203
204     // If the image has #item property, lazyload may work, otherwise skip.
205     // This hustle is to lazyload tons of images -- grids, large galleries,
206     // gridstack, mason, with multimedia/ lightboxes for free.
207     if ($item = $this->getImageItem($image)) {
208       // Supports multiple image styles within a single view such as GridStack,
209       // else fallbacks to the defined image style if available.
210       if (empty($settings['image_style'])) {
211         $image_style = isset($image['rendered']['#image_style']) ? $image['rendered']['#image_style'] : '';
212         $settings['image_style'] = empty($settings['image_style']) ? $image_style : $settings['image_style'];
213       }
214
215       // Converts image formatter for blazy to reduce complexity with CSS
216       // background option, and other options, and still lazyload it.
217       $theme = isset($image['rendered']['#theme']) ? $image['rendered']['#theme'] : '';
218       if (in_array($theme, ['blazy', 'image_formatter'])) {
219         $settings['cache_tags'] = isset($image['rendered']['#cache']['tags']) ? $image['rendered']['#cache']['tags'] : [];
220
221         if ($theme == 'blazy') {
222           // Pass Blazy field formatter settings into Views style plugin.
223           // This allows richer contents such as multimedia/ lightbox for free.
224           // Yet, ensures the Views style plugin wins over Blazy formatter,
225           // such as with GridStack which may have its own breakpoints.
226           $item_settings = array_filter($image['rendered']['#build']['settings']);
227           $settings = array_filter($settings);
228           $settings = array_merge($item_settings, $settings);
229         }
230         elseif ($theme == 'image_formatter') {
231           // Deals with "link to content/image" by formatters.
232           $settings['content_url'] = isset($image['rendered']['#url']) ? $image['rendered']['#url'] : '';
233           if (empty($settings['media_switch']) && !empty($settings['content_url'])) {
234             $settings['media_switch'] = 'content';
235           }
236         }
237
238         // Rebuilds the image for the brand new richer Blazy.
239         // With the working Views cache, nothing to worry much.
240         $build = ['item' => $item, 'settings' => $settings];
241         $image['rendered'] = $this->blazyManager->getImage($build);
242       }
243     }
244
245     return $image;
246   }
247
248   /**
249    * Checks if we can work with this formatter, otherwise no go if flattened.
250    */
251   public function isImageRenderable($row, $index, $field_image = '') {
252     if (!empty($field_image) && $image = $this->getFieldRenderable($row, $index, $field_image)) {
253       if ($item = $this->getImageItem($image)) {
254         return $image;
255       }
256
257       // Dump Video embed thumbnail/video/colorbox as is.
258       if (isset($image['rendered'])) {
259         return $image;
260       }
261     }
262     return [];
263   }
264
265   /**
266    * Get the image item to work with out of this formatter.
267    *
268    * All this mess is because Views may render/flatten images earlier.
269    */
270   public function getImageItem($image) {
271     // Image formatter.
272     $item = empty($image['rendered']['#item']) ? [] : $image['rendered']['#item'];
273
274     // Blazy formatter.
275     if (isset($image['rendered']['#build'])) {
276       $item = $image['rendered']['#build']['item'];
277     }
278
279     // Don't know other reasonable formatters to work with.
280     if (!is_object($item)) {
281       return [];
282     }
283     return $item;
284   }
285
286   /**
287    * Returns the rendered caption fields.
288    */
289   public function getCaption($index, $settings = []) {
290     $items = [];
291     $keys  = array_keys($this->view->field);
292     if (!empty($settings['caption'])) {
293       $caption_items = [];
294       foreach ($settings['caption'] as $key => $caption) {
295         $caption_rendered = $this->getField($index, $caption);
296         if (empty($caption_rendered)) {
297           continue;
298         }
299
300         if (in_array($caption, array_values($keys))) {
301           $caption_items[$key]['#markup'] = $caption_rendered;
302         }
303       }
304       $items['data'] = $caption_items;
305     }
306
307     $items['link']  = empty($settings['link']) ? [] : $this->getFieldRendered($index, $settings['link']);
308     $items['title'] = empty($settings['title']) ? [] : $this->getFieldRendered($index, $settings['title'], TRUE);
309
310     if (!empty($settings['overlay'])) {
311       $items['overlay'] = $this->getFieldRendered($index, $settings['overlay']);
312     }
313
314     return $items;
315   }
316
317   /**
318    * Returns the rendered layout fields.
319    */
320   public function getLayout(array &$settings, $index) {
321     if (strpos($settings['layout'], 'field_') !== FALSE) {
322       $settings['layout'] = strip_tags($this->getField($index, $settings['layout']));
323     }
324   }
325
326   /**
327    * Returns the rendered field, either string or array.
328    */
329   public function getFieldRendered($index, $field_name = '', $restricted = FALSE) {
330     if (!empty($field_name) && $output = $this->getField($index, $field_name)) {
331       return is_array($output) ? $output : ['#markup' => ($restricted ? Xss::filterAdmin($output) : $output)];
332     }
333     return [];
334   }
335
336   /**
337    * Returns the renderable array of field containing rendered and raw data.
338    */
339   public function getFieldRenderable($row, $index, $field_name = '', $multiple = FALSE) {
340     if (empty($field_name)) {
341       return FALSE;
342     }
343
344     // Be sure to not check "Use field template" under "Style settings" to have
345     // renderable array to work with, otherwise flattened string!
346     $result = isset($this->view->field[$field_name]) ? $this->view->field[$field_name]->getItems($row) : [];
347     return empty($result) ? [] : ($multiple ? $result : $result[0]);
348   }
349
350   /**
351    * Returns the string values for the expected Title, ET label, List, Term.
352    *
353    * @todo re-check this, or if any consistent way to retrieve string values.
354    */
355   public function getFieldString($row, $field_name, $index) {
356     $values   = [];
357     $renderer = $this->blazyManager->getRenderer();
358
359     // Content title/List/Text, either as link or plain text.
360     if ($value = $this->getFieldValue($index, $field_name)) {
361       $value = is_array($value) ? array_filter($value) : $value;
362
363       // Entity reference label.
364       if (empty($value) && $markup = $this->getField($index, $field_name)) {
365         $value = is_object($markup) ? trim(strip_tags($markup->__toString())) : $value;
366       }
367
368       // Tags has comma separated value, although can be changed, just too much.
369       if (strpos($value, ',') !== FALSE) {
370         $tags = explode(',', $value);
371         $rendered_tags = [];
372         foreach ($tags as $tag) {
373           $rendered_tags[] = Html::cleanCssIdentifier(Unicode::strtolower(trim($tag)));
374         }
375         $values[$index] = implode(' ', $rendered_tags);
376       }
377       else {
378         $value = is_string($value) ? $value : (isset($value[0]['value']) && !empty($value[0]['value']) ? $value[0]['value'] : '');
379         $values[$index] = empty($value) ? '' : Html::cleanCssIdentifier(Unicode::strtolower($value));
380       }
381     }
382
383     // Term reference/ET, either as link or plain text.
384     if (empty($values)) {
385       if ($renderable = $this->getFieldRenderable($row, $field_name, TRUE)) {
386         $value = [];
387         foreach ($renderable as $key => $render) {
388           $class = isset($render['rendered']['#title']) ? $render['rendered']['#title'] : $renderer->render($render['rendered']);
389           $class = trim(strip_tags($class));
390           $value[$key] = Html::cleanCssIdentifier(Unicode::strtolower($class));
391         }
392         $values[$index] = empty($value) ? '' : implode(' ', $value);
393       }
394     }
395     return $values;
396   }
397
398 }