3 define('RESP_IMG_CLASS', 'resp-img-picture');
4 define('RESP_IMG_SEPARATOR', '__');
5 define('RESP_IMG_STYLE_PREFIX', 'resp_img__');
8 * Implements hook_permission().
10 function resp_img_permission() {
12 'administer responsive images and styles' => array(
13 'title' => t('Administer Responsive Images and Styles'),
14 'description' => t('Administer responsive images and styles'),
20 * Implements hook_menu().
22 function resp_img_menu() {
25 // @todo: link to all breakpoints and a list of all groups
26 // cf theme settings page
27 $items['admin/config/media/resp_img'] = array(
28 'title' => 'Responsive images and styles',
29 'description' => 'Manage Responsive Images and Styles',
30 'page callback' => 'drupal_get_form',
31 'page arguments' => array('resp_img_admin_breakpoints'),
32 'access arguments' => array('administer responsive images and styles'),
33 'file' => 'resp_img.admin.inc',
36 $items['admin/config/media/resp_img/create_style'] = array(
37 'title' => 'Add responsive style',
38 'description' => 'Add a responsive image style',
39 'page callback' => 'drupal_get_form',
40 'page arguments' => array('resp_img_add_style_form'),
41 'access arguments' => array('administer responsive images and styles'),
42 'file' => 'resp_img.admin.inc',
43 'type' => MENU_LOCAL_TASK
,
47 $items['admin/config/media/resp_img/groups'] = array(
49 'type' => MENU_DEFAULT_LOCAL_TASK
,
53 $items['admin/config/media/resp_img/groups/global'] = array(
54 'title' => 'Map breakpoints and image styles',
55 'type' => MENU_DEFAULT_LOCAL_TASK
,
58 $items['admin/config/media/resp_img/groups/import'] = array(
59 'title' => 'Import mappings',
60 'page arguments' => array('resp_img_admin_import_form'),
61 'type' => MENU_LOCAL_TASK
,
62 'access arguments' => array('administer responsive images and styles'),
63 'file' => 'resp_img.admin.inc',
67 $breakpoint_groups = breakpoints_breakpoint_group_load_all();
68 foreach ($breakpoint_groups as
$breakpoint_group_name => $breakpoint_group) {
69 if (!empty($breakpoint_group->machine_name
)) {
70 $items['admin/config/media/resp_img/groups/' .
$breakpoint_group->machine_name
] = array(
71 'title' => $breakpoint_group->name
,
72 'page arguments' => array('resp_img_admin_breakpoints', $breakpoint_group->machine_name
),
73 'type' => MENU_LOCAL_TASK
,
74 'access arguments' => array('administer responsive images and styles'),
75 'file' => 'resp_img.admin.inc',
78 $items['admin/config/media/resp_img/groups/' .
$breakpoint_group->machine_name .
'/export'] = array(
79 'title' => 'Export ' .
check_plain($breakpoint_group->name
) .
' mappings',
80 'page callback' => 'drupal_get_form',
81 'page arguments' => array('resp_img_admin_export_form', 'mappings.' .
$breakpoint_group->machine_name
),
82 'type' => MENU_LOCAL_ACTION
,
83 'access arguments' => array('administer responsive images and styles', 'mappings.' .
$breakpoint_group->machine_name
),
84 'access callback' => 'resp_img_mappings_export_access',
85 'file' => 'resp_img.admin.inc',
97 function resp_img_mappings_export_access($perm, $mapping_name) {
98 return resp_img_mapping_load($mapping_name) && user_access($perm);
104 function resp_img_mapping_load($name = NULL
) {
105 ctools_include('export');
107 $mappings = ctools_export_load_object('resp_img_mapping', 'names', array($name));
108 $mapping = isset($mappings[$name]) ?
$mappings[$name] : FALSE
;
111 return ctools_export_load_object('resp_img_mapping');
118 function resp_img_mapping_save(&$mapping) {
119 ctools_include('export');
120 $update = isset($mapping->id
) ?
array('id') : array();
121 $style = image_style_load(RESP_IMG_STYLE_PREFIX .
$mapping->breakpoint_group
);
123 image_style_flush($style);
125 return drupal_write_record('resp_img_mapping', $mapping, $update);
131 function resp_img_mapping_validate($mapping) {
132 if (!is_object($mapping)) {
135 foreach (array('machine_name', 'breakpoint_group', 'mapping') as
$property) {
136 if (!property_exists($mapping, $property)) {
144 * Add javascript for older browser support
146 function resp_img_add_js() {
147 static
$added = FALSE
;
150 drupal_add_js(drupal_get_path('module', 'resp_img') .
'/picturefill/matchmedia.js', array('type' => 'file', 'weight' => -10, 'group' => JS_DEFAULT
));
151 drupal_add_js(drupal_get_path('module', 'resp_img') .
'/picturefill/picturefill.js', array('type' => 'file', 'weight' => -10, 'group' => JS_DEFAULT
));
152 drupal_add_js(drupal_get_path('module', 'resp_img') .
'/resp_img.js', array('type' => 'file', 'weight' => -10, 'group' => JS_DEFAULT
));
157 * Implements hook_theme().
159 function resp_img_theme() {
162 'variables' => array(
163 'style_name' => NULL
,
169 'attributes' => array(),
170 'breakpoints' => array(),
173 'picture_formatter' => array(
174 'variables' => array(
177 'image_style' => NULL
,
178 'breakpoints' => array(),
181 'colorbox_picture_formatter' => array(
182 'variables' => array(
186 'display_settings' => array(),
187 'breakpoints' => array(),
190 'colorbox_picturefield' => array(
191 'variables' => array(
196 'breakpoints' => array(),
203 * Implements hook_entity_info_alter().
205 function resp_img_entity_info_alter(&$info) {
206 if (isset($info['file'])) {
207 foreach (breakpoints_breakpoint_group_load_all() as
$group) {
208 $info['file']['view modes']['media_responsive_' .
$group->machine_name
] = array(
209 'label' => t('@group (Responsive)', array('@group' => $group->name
)),
210 'custom settings' => TRUE
,
217 * Implements hook_ctools_plugin_api().
219 * Lets CTools know which plugin APIs are implemented by resp_img module.
221 function resp_img_ctools_plugin_api($owner, $api) {
222 static
$api_versions = array(
223 'file_entity' => array(
224 'file_default_displays' => 1,
227 if (isset($api_versions[$owner][$api])) {
228 return array('version' => $api_versions[$owner][$api]);
233 * Implements hook_file_default_displays().
235 function resp_img_file_default_displays() {
236 $default_image_styles = array();
237 foreach (breakpoints_breakpoint_group_load_all() as
$group) {
238 $default_image_styles['media_responsive_' .
$group->machine_name
] = RESP_IMG_STYLE_PREFIX .
$group->machine_name
;
240 $default_displays = array();
242 foreach ($default_image_styles as
$view_mode => $image_style) {
243 $display_name = 'image__' .
$view_mode .
'__file_image';
244 $default_displays[$display_name] = (object) array(
246 'name' => $display_name,
249 'settings' => array('image_style' => $image_style),
253 return $default_displays;
257 * Implements hook_preprocess_field().
259 function resp_img_field_attach_view_alter(&$output, $context) {
260 $view = module_exists('views') ?
views_get_current_view() : FALSE
;
261 foreach (element_children($output) as
$field_name) {
262 $element = &$output[$field_name];
264 if (isset($element['#formatter'])) {
265 if ($element['#formatter'] == 'image') {
266 $vars['image_style'] = 'image_style';
267 $vars['#formatter'] = 'picture';
268 $vars['#theme'] = 'picture_formatter';
270 elseif ($element['#formatter'] == 'colorbox') {
271 $vars['image_style'] = 'colorbox_node_style';
272 $vars['#formatter'] = 'picture';
273 $vars['#theme'] = 'colorbox_picture_formatter';
275 elseif ($element['#formatter'] == 'slideshow') {
276 $vars['image_style'] = 'slideshow_image_style';
277 $vars['#formatter'] = 'slideshow';
278 $vars['#theme'] = 'field_slideshow';
282 $instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
283 if ($view && isset($view->query
->pager
->display
->handler
->handlers
['field'][$element['#field_name']])) {
284 $settings = $view->query
->pager
->display
->handler
->handlers
['field'][$element['#field_name']]->options
['settings'];
286 elseif (isset($instance['display'][$context['view_mode']]['settings'][$vars['image_style']]) && !empty($instance['display'][$context['view_mode']]['settings'][$vars['image_style']])) {
287 $settings = $instance['display'][$context['view_mode']]['settings'];
290 $settings = $instance['display']['default']['settings'];
292 if (isset($settings[$vars['image_style']]) && strpos($settings[$vars['image_style']], RESP_IMG_STYLE_PREFIX
) !== FALSE
) {
293 $group_name = drupal_substr($settings[$vars['image_style']], drupal_strlen(RESP_IMG_STYLE_PREFIX
));
294 $breakpoint_styles = _resp_img_get_breakpoint_styles($group_name);
295 if (!empty($breakpoint_styles)) {
296 // Change the formatter so it uses ours.
297 $element['#formatter'] = $vars['#formatter'];
299 // Change the formatter on all items as well.
300 $num_fields = ($element['#formatter'] != 'slideshow') ?
count($element['#items']) : 1;
301 for ($delta = 0; $delta < $num_fields; $delta++) {
302 $element[$delta]['#theme'] = $vars['#theme'];
303 // Change the image style to the first in use.
304 $reset = reset($breakpoint_styles['mapping']);
305 $element[$delta]['#image_style'] = reset($reset);
306 $element[$delta]['#breakpoints'] = $breakpoint_styles;
310 watchdog('Responsive images', 'You have to map at least 1 style for ' .
check_plain($group_name) .
'.');
318 function theme_picture_formatter($variables) {
319 if (!isset($variables['breakpoints']) || empty($variables['breakpoints'])) {
320 return theme('image_formatter', $variables);
323 $item = $variables['item'];
325 // Do not output an empty 'title' attribute.
326 if (isset($item['title']) && drupal_strlen($item['title']) == 0) {
327 unset($item['title']);
330 $item['style_name'] = $variables['image_style'];
331 $item['breakpoints'] = $variables['breakpoints'];
333 if (!isset($item['path']) && isset($variables['uri'])) {
334 $item['path'] = $variables['uri'];
336 $output = theme('picture', $item);
338 if (isset($variables['path']['path'])) {
339 $path = $variables['path']['path'];
340 $options = isset($variables['path']['options']) ?
$variables['path']['options'] : array();
341 $options['html'] = TRUE
;
342 $output = l($output, $path, $options);
348 * Theme a picture element.
350 function theme_picture($variables) {
352 // Add classes to ease styling
353 if (!isset($variables['attributes'])) {
354 $variables['attributes'] = array();
356 if (!isset($variables['attributes']['class'])) {
357 $variables['attributes']['class'] = array();
359 $variables['attributes']['class'][] = RESP_IMG_CLASS
;
361 // Make sure that width and height are proper values
362 // If they exists we'll output them
363 // @see http://www.w3.org/community/respimg/2012/06/18/florians-compromise/
364 if (isset($variables['width']) && empty($variables['width'])) {
365 unset($variables['width']);
366 unset($variables['height']);
368 elseif (isset($variables['height']) && empty($variables['height'])) {
369 unset($variables['width']);
370 unset($variables['height']);
373 // Use path or uri untill D7 uses one in all places.
374 if (!isset($variables['path']) || empty($variables['path'])) {
375 $variables['path'] = ($variables['uri']);
377 if (!isset($variables['uri']) || empty($variables['uri'])) {
378 $variables['uri'] = ($variables['path']);
385 if (isset($variables['image_style']) && !empty($variables['image_style'])) {
386 $img = theme('image_style', $variables);
387 $img = str_replace('<img', '', $img);
388 $img = str_replace('/>', '', $img);
394 // All breakpoints and multipliers.
395 if (isset($variables['breakpoints']['mapping'])) {
396 foreach ($variables['breakpoints']['mapping'] as
$breakpoint_name => $multipliers) {
397 $breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint_name);
399 $new_images = array();
400 foreach ($multipliers as
$multiplier => $image_style) {
401 $new_image = $variables;
402 // Add classes to ease styling
403 $new_image['attributes']['class'][] = RESP_IMG_CLASS .
'-' .
drupal_clean_css_identifier($breakpoint->name
);
404 $new_image['style_name'] = $image_style;
405 $new_image['#multiplier'] = $multiplier;
406 $new_images[] = $new_image;
408 $img = theme('image_style', $new_images[0]);
409 $img = str_replace('<img', '', $img);
410 $img = str_replace('/>', '', $img);
412 // Only one image, output as src.
413 if (count($new_images) == 1) {
416 'media' => $breakpoint->breakpoint
,
420 // Mutliple images, output as srcset.
422 foreach ($new_images as
$new_image) {
423 $srcset[] = image_style_url($new_image['style_name'], $new_image['uri']) .
' ' .
$new_image['#multiplier'];
425 $img = preg_replace('/src="[^"]*"/', '', $img);
427 'srcset' => implode(', ', $srcset),
428 'media' => $breakpoint->breakpoint
,
436 if (!empty($images)) {
437 $output[] = '<picture class="' . RESP_IMG_CLASS .
'" alt="' .
check_plain($variables['alt']) .
'" title="' .
check_plain($variables['title']) .
'">';
439 // add variants to the output
440 foreach ($images as
$image) {
441 if (isset($image['media']) && !empty($image['media'])) {
442 if (!isset($image['srcset'])) {
443 $output[] = '<!-- <source media="' .
$image['media'] .
'" ' .
$image['image'] .
' /> -->';
444 $output[] = '<source media="' .
$image['media'] .
'" ' .
$image['image'] .
' />';
447 $output[] = '<!-- <source media="' .
$image['media'] .
'" srcset="' .
$image['srcset'] .
'" ' .
$image['image'] .
' /> -->';
448 $output[] = '<source media="' .
$image['media'] .
'" srcset="' .
$image['srcset'] .
'" ' .
$image['image'] .
' />';
452 $output[] = '<!-- <source ' .
$image['image'] .
' /> -->';
453 $output[] = '<source ' .
$image['image'] .
' />';
457 // output the default image as fallback
458 // $output .= '<img src="' . image_style_url($variables['style_name'], $variables['uri']) . '" alt="' . check_plain($variables['alt']) . '" />';
459 $output[] = '<noscript><img ' .
$images[0]['image'] .
'/></noscript>';
460 $output[] = '</picture>';
461 return implode("\n", $output);
466 * colorbox_picture_formatter similar to colorbox_image_formatter.
468 * only differences are:
469 * 'breakpoints' => $variables['breakpoints'],
472 function theme_colorbox_picture_formatter($variables) {
473 $item = $variables['item'];
474 $node = $variables['node'];
475 $field = $variables['field'];
476 $settings = $variables['display_settings'];
479 'path' => $item['uri'],
480 'alt' => $item['alt'],
481 'title' => $item['title'],
482 'style_name' => $settings['colorbox_node_style'],
483 'breakpoints' => $variables['breakpoints'],
486 if (isset($item['width']) && isset($item['height'])) {
487 $image['width'] = $item['width'];
488 $image['height'] = $item['height'];
491 switch ($settings['colorbox_caption']) {
493 // If the title is empty use alt or the node title in that order.
494 if (!empty($image['title'])) {
495 $caption = $image['title'];
497 elseif (!empty($image['alt'])) {
498 $caption = $image['alt'];
500 elseif (!empty($node->title
)) {
501 $caption = $node->title
;
508 $caption = $image['title'];
511 $caption = $image['alt'];
514 $caption = $node->title
;
517 $caption = token_replace($settings['colorbox_caption_custom'], array('node' => $node));
523 // Shorten the caption for the example styles or when caption shortening is active.
524 $colorbox_style = variable_get('colorbox_style', 'default');
525 $trim_length = variable_get('colorbox_caption_trim_length', 75);
526 if (((strpos($colorbox_style, 'colorbox/example') !== FALSE
) || variable_get('colorbox_caption_trim', 0)) && (drupal_strlen($caption) > $trim_length)) {
527 $caption = drupal_substr($caption, 0, $trim_length - 5) .
'...';
530 // Build the gallery id.
531 $nid = !empty($node->nid
) ?
$node->nid
: 'nid';
532 switch ($settings['colorbox_gallery']) {
534 $gallery_id = 'gallery-' .
$nid;
537 $gallery_id = 'gallery-all';
540 $gallery_id = 'gallery-' .
$nid .
'-' .
$field['field_name'];
543 $gallery_id = 'gallery-' .
$field['field_name'];
546 $gallery_id = $settings['colorbox_gallery_custom'];
552 if ($style_name = $settings['colorbox_image_style']) {
553 $path = image_style_url($style_name, $image['path']);
556 $path = file_create_url($image['path']);
559 return theme('colorbox_picturefield', array('image' => $image, 'path' => $path, 'title' => $caption, 'gid' => $gallery_id));
563 * theme_colorbox_picturefield similar to theme_colorbox_imagefield.
565 * only difference is: $image = theme('picture', $variables['image']);.
567 function theme_colorbox_picturefield($variables) {
568 $class = array('colorbox');
570 if ($variables['image']['style_name'] == 'hide') {
572 $class[] = 'js-hide';
574 elseif (!empty($variables['image']['style_name'])) {
575 $image = theme('picture', $variables['image']);
578 $image = theme('image', $variables['image']);
583 'attributes' => array(
584 'title' => $variables['title'],
585 'class' => implode(' ', $class),
586 'rel' => $variables['gid'],
590 return l($image, $variables['path'], $options);
594 * Implements hook_field_formatter_info_alter().
596 function resp_img_field_formatter_info_alter(&$info) {
597 foreach ($info as
$formatter_key => &$formatter) {
598 if ($formatter_key == 'image') {
599 if (!isset($formatter['settings']) || !is_array($formatter['settings'])) {
600 $formatter['settings'] = array();
607 * Implements hook_image_default_styles().
609 function resp_img_image_default_styles() {
611 // Provide fake responsive image styles for users to select.
612 $breakpoint_groups = breakpoints_breakpoint_group_load_all();
613 if ($breakpoint_groups && !empty($breakpoint_groups)) {
614 foreach (array_keys($breakpoint_groups) as
$machine_name) {
615 $styles[RESP_IMG_STYLE_PREFIX .
$machine_name] = array(
616 'effects' => array(),
624 * Implements hook_image_styles_alter().
626 function resp_img_image_styles_alter(&$styles) {
627 $breakpoint_groups = breakpoints_breakpoint_group_load_all();
628 if ($breakpoint_groups && !empty($breakpoint_groups)) {
629 foreach ($breakpoint_groups as
$machine_name => $breakpoint_group) {
630 $mapping = _resp_img_get_breakpoint_styles($machine_name);
631 $first_style = FALSE
;
632 if (!empty($mapping)) {
633 $first_style = reset(reset($mapping['mapping']));
636 $first_style = isset($styles[$first_style]) ?
$styles[$first_style] : FALSE
;
639 $new_effects = array();
640 foreach ($first_style['effects'] as
$effect) {
641 $new_effect = $effect;
642 unset($new_effect['isid']);
643 unset($new_effect['ieid']);
644 $new_effects[] = $new_effect;
646 $styles[RESP_IMG_STYLE_PREFIX .
$machine_name]['effects'] = $new_effects;
653 * Implements MODULE_preprocess_HOOK().
655 function resp_img_preprocess_image_style_list(&$variables) {
656 $variables['styles'] = array_filter($variables['styles'], '_resp_img_filter_styles');
660 * array_filter callback.
662 function _resp_img_filter_styles($var) {
663 return strpos(is_array($var) ?
$var['name'] : $var, RESP_IMG_STYLE_PREFIX
) !== 0;
667 * Implements hook_field_formatter_settings_summary_alter().
669 function resp_img_field_formatter_settings_summary_alter(&$summary, $context) {
670 if (isset($context['field']['type']) && $context['field']['type'] === 'image') {
671 $settings = $context['instance']['display'][$context['view_mode']]['settings'];
672 if (isset($settings['image_style']) && strpos($settings['image_style'], RESP_IMG_STYLE_PREFIX
) !== FALSE
) {
673 $group_name = drupal_substr($settings['image_style'], drupal_strlen(RESP_IMG_STYLE_PREFIX
));
674 $summary = 'Responsive mode activated using ' .
$group_name;
676 if (isset($settings['colorbox_node_style']) && strpos($settings['colorbox_node_style'], RESP_IMG_STYLE_PREFIX
) !== FALSE
) {
677 $group_name = drupal_substr($settings['colorbox_node_style'], drupal_strlen(RESP_IMG_STYLE_PREFIX
));
678 $summary .
= '<br />Responsive mode activated using ' .
$group_name;
683 function resp_img_entity_view_alter(&$build, $type) {
684 foreach (element_children($build) as
$child) {
685 if (isset($build[$child]['#field_name'])) {
686 $build[$child]['#post_render'][] = 'resp_img_post_render';
692 * #post_render callback.
694 function resp_img_post_render($content, $element) {
695 return _resp_img_replace_picture($content);
698 function _resp_img_replace_picture($content) {
700 preg_match_all('/<img[^>]*>/i', $content, $result);
701 $orig_imgs = $imgs = $result[0];
702 $vars['image_style'] = 'image_style';
703 $vars['#formatter'] = 'picture';
704 $vars['#theme'] = 'picture_formatter';
705 foreach ($imgs as
&$img) {
706 if (strpos($img, RESP_IMG_STYLE_PREFIX
) !== FALSE
) {
707 $xml = simplexml_load_string('<image>' .
html_entity_decode($img, ENT_QUOTES
, "utf-8") .
'</image>');
708 if (isset($xml->img
[0]) && is_object($xml->img
[0])) {
709 $attributes = array();
710 foreach ($xml->img
[0]->attributes() as
$a => $b) {
711 $attributes[$a] = (string)$b;
715 if (isset($attributes['src']) && !empty($attributes['src'])) {
716 $group_name = drupal_substr($attributes['src'], strpos($attributes['src'], RESP_IMG_STYLE_PREFIX
));
717 $group_name = str_replace(RESP_IMG_STYLE_PREFIX
, '', drupal_substr($group_name, 0, strpos($group_name, '/')));
718 $breakpoint_styles = _resp_img_get_breakpoint_styles($group_name);
720 // @todo: Ugly! Find a better way?
721 $src = drupal_substr($attributes['src'], strpos($attributes['src'], RESP_IMG_STYLE_PREFIX .
$group_name) + drupal_strlen(RESP_IMG_STYLE_PREFIX .
$group_name) + 1);
722 $src = preg_replace('/\//', '://', $src, 1);
723 unset($attributes['src']);
726 'breakpoints' => $breakpoint_styles,
728 if (isset($attributes['width'])) {
729 $variables['width'] = $attributes['width'];
730 unset($attributes['width']);
732 if (isset($attributes['height'])) {
733 $variables['height'] = $attributes['height'];
734 unset($attributes['height']);
736 $variables['attributes'] = $attributes;
737 if (isset($variables['attributes']['class'])) {
738 $variables['attributes']['class'] = explode(' ', $variables['attributes']['class']);
740 $variables['style_name'] = RESP_IMG_STYLE_PREFIX .
$group_name;
741 $img = theme('picture', $variables);
745 $content = str_replace($orig_imgs, $imgs, $content);
749 function _resp_img_get_breakpoint_styles($group_name) {
750 $active_breakpoints = &drupal_static('resp_img_breakpoints', FALSE
);
751 if (!$active_breakpoints) {
752 $active_breakpoints = breakpoints_breakpoint_load_all_active();
754 $breakpoint_styles = array();
755 if ($mappings = resp_img_mapping_load('mappings.' .
$group_name)) {
756 $group = breakpoints_breakpoint_group_load($group_name);
757 $breakpoints = array_intersect_key(drupal_map_assoc($group->breakpoints
), $active_breakpoints);
758 $mappings->mapping
= array_intersect_key($mappings->mapping
, $breakpoints);
759 foreach ($mappings->mapping as
$breakpoint_name => $multipliers) {
760 $settings = breakpoints_settings();
761 $existing_multipliers = drupal_map_assoc($settings->multipliers
);
762 $multipliers = array_intersect_key($multipliers, array_intersect_key($existing_multipliers, array_filter($active_breakpoints[$breakpoint_name]->multipliers
)));
763 if (!empty($multipliers) && is_array($multipliers)) {
764 foreach ($multipliers as
$multiplier => $image_style) {
765 if (!empty($image_style)) {
766 if (!isset($breakpoint_styles['mapping'][$breakpoint_name])) {
767 $breakpoint_styles['mapping'][$breakpoint_name] = array();
769 $breakpoint_styles['mapping'][$breakpoint_name][$multiplier] = $image_style;
775 return $breakpoint_styles;