summaryrefslogtreecommitdiffstats
path: root/core/modules/responsive_image/responsive_image.module
blob: 63f25e655d2fc0ca7d7e4a4f336d351dd0007d97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
<?php

/**
 * @file
 * Responsive image display formatter for image fields.
 */

use Drupal\breakpoint\Entity\Breakpoint;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Routing\RouteMatchInterface;
use \Drupal\Core\Template\Attribute;

/**
 * The machine name for the empty image breakpoint image style option.
 */
const RESPONSIVE_IMAGE_EMPTY_IMAGE = '_empty image_';

/**
 * Implements hook_help().
 */
function responsive_image_help($route_name, RouteMatchInterface $route_match) {
  $output = '';
  switch ($route_name) {
    case 'help.page.responsive_image':
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Responsive Image module provides an image formatter and breakpoint mappings to output responsive images using the HTML5 picture tag. For more information, see the <a href="!responsive_image">online documentation for the Responsive Image module</a>.', array( '!responsive_image' => 'https://drupal.org/documentation/modules/responsive_image')) . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Defining responsive image mappings') . '</dt>';
      $output .= '<dd>' . t('By creating responsive image mappings you define the image styles that are being used to output images at certain breakpoints. On the <a href="!responsive_image_mapping">Responsive image mappings</a> page, click <em>Add responsive image mapping</em> to create a new mapping. First chose a label and a breakpoint group and click Save. After that you can choose the image styles that will be used for each breakpoint. Image styles can be defined on the <a href="!image_styles">Image styles page</a> that is provided by the <a href="!image_help">Image module</a>. Breakpoints are defined in the configuration files of the theme. See the <a href="!breakpoint_help">help page of the Breakpoint module</a> for more information.', array('!responsive_image_mapping' => \Drupal::url('responsive_image.mapping_page'), '!image_styles' => \Drupal::url('image.style_list'),'!image_help' => \Drupal::url('help.page', array('name' => 'image')), '!breakpoint_help' => \Drupal::url('help.page', array('name' => 'breakpoint')))) . '</dd>';
      $output .= '<dt>' . t('Using responsive image mappings in Image fields') . '</dt>';
      $output .= '<dd>' . t('After defining responsive image mappings, you can use them in the display settings for your Image fields, so that the site displays responsive images using the HTML5 picture tag. Open the Manage display page for the entity type (content type, taxonomy vocabulary, etc.) that the Image field is attached to. Choose the format <em>Responsive image</em>, click the Edit icon, and select one of the responsive image mappings that you have created. For general information on how to manage fields and their display see the <a href="!field_ui">help page of the Field UI module</a>. For information about entities see the <a href="!entity_help">help page of the Entity module</a>.', array('!field_ui' => \Drupal::url('help.page', array('name' => 'field_ui')),'!entity_help' => \Drupal::url('help.page', array('name' => 'entity')))) . '</dd>';
      $output .= '</dl>';
      break;

    case 'responsive_image.mapping_page':
      $output .= '<p>' . t('A responsive image mapping associates an image style with each breakpoint defined by your theme.') . '</p>';
      break;

  }
  return $output;
}

/**
 * Implements hook_permission().
 */
function responsive_image_permission() {
  return array(
    'administer responsive images' => array(
      'title' => t('Administer responsive images'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function responsive_image_menu() {
  $items = array();

  $items['admin/config/media/responsive-image-mapping'] = array(
    'title' => 'Responsive image mappings',
    'description' => 'Manage responsive image mappings',
    'weight' => 10,
    'route_name' => 'responsive_image.mapping_page',
  );
  $items['admin/config/media/responsive-image-mapping/%responsive_image_mapping'] = array(
    'title' => 'Edit responsive image mapping',
    'route_name' => 'entity.responsive_image_mapping.edit_form',
  );
  $items['admin/config/media/responsive-image-mapping/%responsive_image_mapping/duplicate'] = array(
    'title' => 'Duplicate responsive image mapping',
    'route_name' => 'entity.responsive_image_mapping.duplicate_form',
  );

  return $items;
}

/**
 * Implements hook_theme().
 */
function responsive_image_theme() {
  return array(
    'responsive_image' => array(
      'variables' => array(
        'style_name' => NULL,
        'uri' => NULL,
        'width' => NULL,
        'height' => NULL,
        'alt' => '',
        'title' => NULL,
        'attributes' => array(),
        'breakpoints' => array(),
      ),
    ),
    'responsive_image_formatter' => array(
      'variables' => array(
        'item' => NULL,
        'path' => NULL,
        'image_style' => NULL,
        'breakpoints' => array(),
      ),
    ),
    'responsive_image_source' => array(
      'variables' => array(
        'src' => NULL,
        'srcset' => NULL,
        'dimensions' => NULL,
        'media' => NULL,
      ),
    ),
  );
}

/**
 * Returns HTML for a responsive image field formatter.
 *
 * @param array $variables
 *   An associative array containing:
 *   - item: An ImageItem object.
 *   - image_style: An optional image style.
 *   - path: An optional array containing the link 'path' and link 'options'.
 *   - breakpoints: An array containing breakpoints.
 *
 * @ingroup themeable
 */
function theme_responsive_image_formatter($variables) {
  $item = $variables['item'];
  if (!isset($variables['breakpoints']) || empty($variables['breakpoints'])) {
    $image_formatter = array(
      '#theme' => 'image_formatter',
      '#item' => $item,
      '#image_style' => $variables['image_style'],
      '#path' => $variables['path'],
    );
    return drupal_render($image_formatter);
  }

  $responsive_image = array(
    '#theme' => 'responsive_image',
    '#width' => $item->width,
    '#height' => $item->height,
    '#style_name' => $variables['image_style'],
    '#breakpoints' => $variables['breakpoints'],
  );
  if (isset($item->uri)) {
    $responsive_image['#uri'] = $item->uri;
  }
  elseif ($entity = $item->entity) {
    $responsive_image['#uri'] = $entity->getFileUri();
    $responsive_image['#entity'] = $entity;
  }
  $responsive_image['#alt'] = $item->alt;
  if (drupal_strlen($item->title) != 0) {
    $responsive_image['#title'] = $item->title;
  }
  // @todo Add support for route names.
  if (isset($variables['path']['path'])) {
    $path = $variables['path']['path'];
    $options = isset($variables['path']['options']) ? $variables['path']['options'] : array();
    $options['html'] = TRUE;
    return l($responsive_image, $path, $options);
  }

  return drupal_render($responsive_image);
}

/**
 * Returns HTML for a responsive image.
 *
 * @param $variables
 *   An associative array containing:
 *   - uri: Either the path of the image file (relative to base_path()) or a
 *     full URL.
 *   - width: The width of the image (if known).
 *   - height: The height of the image (if known).
 *   - alt: The alternative text for text-based browsers.
 *   - title: The title text is displayed when the image is hovered in some
 *     popular browsers.
 *   - style_name: The name of the style to be used as a fallback image.
 *   - breakpoints: An array containing breakpoints.
 *
 * @ingroup themeable
 */
function theme_responsive_image($variables) {
  // Make sure that width and height are proper values
  // If they exists we'll output them
  // @see http://www.w3.org/community/respimg/2012/06/18/florians-compromise/
  if (isset($variables['width']) && empty($variables['width'])) {
    unset($variables['width']);
    unset($variables['height']);
  }
  elseif (isset($variables['height']) && empty($variables['height'])) {
    unset($variables['width']);
    unset($variables['height']);
  }

  $sources = array();

  // Fallback image, output as source with media query.
  $sources[] = array(
    'src' => _responsive_image_image_style_url($variables['style_name'], $variables['uri']),
    'dimensions' => responsive_image_get_image_dimensions($variables),
  );

  // All breakpoints and multipliers.
  foreach ($variables['breakpoints'] as $breakpoint_name => $multipliers) {
    $breakpoint = Breakpoint::load($breakpoint_name);
    if ($breakpoint) {
      $new_sources = array();
      foreach ($multipliers as $multiplier => $image_style) {
        $new_source = $variables;
        $new_source['style_name'] = $image_style;
        $new_source['#multiplier'] = $multiplier;
        $new_sources[] = $new_source;
      }

      // Only one image, use src.
      if (count($new_sources) == 1) {
        $sources[] = array(
          'src' => _responsive_image_image_style_url($new_sources[0]['style_name'], $new_sources[0]['uri']),
          'dimensions' => responsive_image_get_image_dimensions($new_sources[0]),
          'media' => $breakpoint->mediaQuery,
        );
      }
      else {
        // Multiple images, use srcset.
        $srcset = array();
        foreach ($new_sources as $new_source) {
          $srcset[] = _responsive_image_image_style_url($new_source['style_name'], $new_source['uri']) . ' ' . $new_source['#multiplier'];
        }
        $sources[] = array(
          'srcset' => implode(', ', $srcset),
          'dimensions' => responsive_image_get_image_dimensions($new_sources[0]),
          'media' => $breakpoint->mediaQuery,
        );
      }
    }
  }

  if (!empty($sources)) {
    $output = array();
    $output[] = '<picture>';

    // Add source tags to the output.
    foreach ($sources as $source) {
      $responsive_image_source = array(
        '#theme' => 'responsive_image_source',
        '#src' => $source['src'],
        '#dimensions' => $source['dimensions'],
      );
      if (isset($source['media'])) {
        $responsive_image_source['#media'] = $source['media'];
      }
      if (isset($source['srcset'])) {
        $responsive_image_source['#srcset'] = $source['srcset'];
      }
      $output[] = drupal_render($responsive_image_source);
    }

    $output[] = '</picture>';
    return SafeMarkup::set(implode("\n", $output));
  }
}

/**
 * Returns HTML for a source tag.
 *
 * @param type $variables
 *   An associative array containing:
 *   - media: The media query to use.
 *   - srcset: The srcset containing the the path of the image file or a full
 *     URL and optionally multipliers.
 *   - src: Either the path of the image file (relative to base_path()) or a
 *     full URL.
 *   - dimensions: The width and height of the image (if known).
 *
 * @ingroup themeable
 */
function theme_responsive_image_source($variables) {
  $output = array();
  if (isset($variables['media']) && !empty($variables['media'])) {
    if (!isset($variables['srcset'])) {
      $output[] = '<!-- <source media="' . $variables['media'] . '" src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
      $output[] = '<source media="' . $variables['media'] . '" src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . '/>';
    }
    elseif (!isset($variables['src'])) {
      $output[] = '<!-- <source media="' . $variables['media'] . '" srcset="' . $variables['srcset'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
      $output[] = '<source media="' . $variables['media'] . '" srcset="' . $variables['srcset'] . '" ' . new Attribute($variables['dimensions']) . ' />';
    }
  }
  else {
    $output[] = '<!-- <source src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
    $output[] = '<source src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . '/>';
  }
  return implode("\n", $output);
}

/**
 * Determines the dimensions of an image.
 *
 * @param $variables
 *   An associative array containing:
 *   - style_name: The name of the style to be used to alter the original image.
 *   - width: The width of the source image (if known).
 *   - height: The height of the source image (if known).
 *
 * @return array
 *   Dimensions to be modified - an array with components width and height, in
 *   pixels.
 */
function responsive_image_get_image_dimensions($variables) {
  // Determine the dimensions of the styled image.
  $dimensions = array(
    'width' => $variables['width'],
    'height' => $variables['height'],
  );

  if ($variables['style_name'] == RESPONSIVE_IMAGE_EMPTY_IMAGE) {
    $dimensions = array(
      'width' => 1,
      'height' => 1,
    );
  }
  else {
    entity_load('image_style', $variables['style_name'])->transformDimensions($dimensions);
  }

  return $dimensions;
}

/**
 * Wrapper around image_style_url() so we can return an empty image.
 */
function _responsive_image_image_style_url($style_name, $path) {
  if ($style_name == RESPONSIVE_IMAGE_EMPTY_IMAGE) {
    // The smallest data URI for a 1px square transparent GIF image.
    return 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
  }
  return entity_load('image_style', $style_name)->buildUrl($path);
}