3 namespace Drupal\blazy\Dejavu;
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;
13 * A base for blazy views integration to have re-usable methods in one place.
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
19 abstract class BlazyStylePluginBase extends StylePluginBase {
24 protected $usesRowPlugin = TRUE;
29 protected $usesGrouping = FALSE;
32 * The blazy manager service.
34 * @var \Drupal\blazy\BlazyManagerInterface
36 protected $blazyManager;
39 * Constructs a GridStackManager object.
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;
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'));
54 * Returns the blazy manager.
56 public function blazyManager() {
57 return $this->blazyManager;
61 * Returns available fields for select options.
63 public function getDefinedFieldOptions($definitions = []) {
64 $field_names = $this->displayHandler->getFieldLabels();
67 // Formatter based fields.
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.
77 case 'media_thumbnail':
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];
89 $options['layouts'][$field] = $field_names[$field];
92 case 'entity_reference_label':
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];
104 $classes = ['list_key', 'entity_reference_label', 'text', 'string'];
105 if (in_array($handler['type'], $classes)) {
106 $options['classes'][$field] = $field_names[$field];
109 $slicks = strpos($handler['type'], 'slick') !== FALSE;
111 'entity_reference_entity_view',
112 'video_embed_field_video',
115 if ($slicks || in_array($handler['type'], $overlays)) {
116 $options['overlays'][$field] = $field_names[$field];
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.
125 'video_embed_field_video',
128 if (in_array($handler['type'], $images)) {
129 $options['images'][$field] = $field_names[$field];
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];
141 if ($handler['field'] == 'view_node') {
142 $options['links'][$field] = $field_names[$field];
145 $blazies = strpos($handler['field'], 'blazy_') !== FALSE;
147 $options['images'][$field] = $field_names[$field];
148 $options['overlays'][$field] = $field_names[$field];
149 $options['thumbnails'][$field] = $field_names[$field];
153 // Captions can be anything to get custom works going.
154 $options['captions'][$field] = $field_names[$field];
157 $definition['plugin_id'] = $this->getPluginId();
158 $definition['settings'] = $this->options;
159 $definition['current_view_mode'] = $this->view->current_display;
161 // Provides the requested fields.
162 foreach ($definitions as $key) {
163 $definition[$key] = isset($options[$key]) ? $options[$key] : [];
170 * Returns an individual row/element content.
172 public function buildElement(array &$element, $row, $index) {
173 $settings = &$element['settings'];
174 $item_id = empty($settings['item_id']) ? 'box' : $settings['item_id'];
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'];
184 // Add caption fields if so configured.
185 $element['caption'] = $this->getCaption($index, $settings);
187 // Add layout field, may be a list field, or builtin layout options.
188 if (!empty($settings['layout'])) {
189 $this->getLayout($settings, $index);
194 * Returns the modified renderable image_formatter to support lazyload.
196 public function getImageRenderable(array &$settings, $row, $index) {
197 $image = $this->isImageRenderable($row, $index, $settings['image']);
199 /* @var Drupal\image\Plugin\Field\FieldType\ImageItem $item */
200 if (empty($image['raw'])) {
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'];
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'] : [];
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);
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';
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);
249 * Checks if we can work with this formatter, otherwise no go if flattened.
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)) {
257 // Dump Video embed thumbnail/video/colorbox as is.
258 if (isset($image['rendered'])) {
266 * Get the image item to work with out of this formatter.
268 * All this mess is because Views may render/flatten images earlier.
270 public function getImageItem($image) {
272 $item = empty($image['rendered']['#item']) ? [] : $image['rendered']['#item'];
275 if (isset($image['rendered']['#build'])) {
276 $item = $image['rendered']['#build']['item'];
279 // Don't know other reasonable formatters to work with.
280 if (!is_object($item)) {
287 * Returns the rendered caption fields.
289 public function getCaption($index, $settings = []) {
291 $keys = array_keys($this->view->field);
292 if (!empty($settings['caption'])) {
294 foreach ($settings['caption'] as $key => $caption) {
295 $caption_rendered = $this->getField($index, $caption);
296 if (empty($caption_rendered)) {
300 if (in_array($caption, array_values($keys))) {
301 $caption_items[$key]['#markup'] = $caption_rendered;
304 $items['data'] = $caption_items;
307 $items['link'] = empty($settings['link']) ? [] : $this->getFieldRendered($index, $settings['link']);
308 $items['title'] = empty($settings['title']) ? [] : $this->getFieldRendered($index, $settings['title'], TRUE);
310 if (!empty($settings['overlay'])) {
311 $items['overlay'] = $this->getFieldRendered($index, $settings['overlay']);
318 * Returns the rendered layout fields.
320 public function getLayout(array &$settings, $index) {
321 if (strpos($settings['layout'], 'field_') !== FALSE) {
322 $settings['layout'] = strip_tags($this->getField($index, $settings['layout']));
327 * Returns the rendered field, either string or array.
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)];
337 * Returns the renderable array of field containing rendered and raw data.
339 public function getFieldRenderable($row, $index, $field_name = '', $multiple = FALSE) {
340 if (empty($field_name)) {
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]);
351 * Returns the string values for the expected Title, ET label, List, Term.
353 * @todo re-check this, or if any consistent way to retrieve string values.
355 public function getFieldString($row, $field_name, $index) {
357 $renderer = $this->blazyManager->getRenderer();
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;
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;
368 // Tags has comma separated value, although can be changed, just too much.
369 if (strpos($value, ',') !== FALSE) {
370 $tags = explode(',', $value);
372 foreach ($tags as $tag) {
373 $rendered_tags[] = Html::cleanCssIdentifier(Unicode::strtolower(trim($tag)));
375 $values[$index] = implode(' ', $rendered_tags);
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));
383 // Term reference/ET, either as link or plain text.
384 if (empty($values)) {
385 if ($renderable = $this->getFieldRenderable($row, $field_name, TRUE)) {
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));
392 $values[$index] = empty($value) ? '' : implode(' ', $value);