diff --git a/core/modules/responsive_image/lib/Drupal/responsive_image/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php b/core/modules/responsive_image/lib/Drupal/responsive_image/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php index 8f935c0..5955f1b 100644 --- a/core/modules/responsive_image/lib/Drupal/responsive_image/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php +++ b/core/modules/responsive_image/lib/Drupal/responsive_image/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php @@ -114,18 +114,9 @@ public function settingsSummary() { */ public function viewElements(FieldItemListInterface $items) { $elements = array(); - // Check if the formatter involves a link. - if ($this->getSetting('image_link') == 'content') { - $uri = $items->getEntity()->urlInfo(); - // @todo Remove when theme_responsive_image_formatter() has support for route name. - $uri['path'] = $items->getEntity()->getSystemPath(); - } - elseif ($this->getSetting('image_link') == 'file') { - $link_file = TRUE; - } - $breakpoint_styles = array(); - $fallback_image_style = ''; + $image_styles = array_keys(image_style_options(FALSE)); + $fallback_image_style = reset($image_styles); $responsive_image_mapping = entity_load('responsive_image_mapping', $this->getSetting('responsive_image_mapping')); if ($responsive_image_mapping) { @@ -163,22 +154,52 @@ public function viewElements(FieldItemListInterface $items) { } foreach ($items as $delta => $item) { - if (isset($link_file)) { - $uri = array( - 'path' => file_create_url($item->entity->getFileUri()), - 'options' => array(), - ); + $item = $item->getValue(TRUE); + if (!isset($item['uri']) && isset($item['entity'])) { + $item['uri'] = $item['entity']->getFileUri(); + } + if (isset($item['title']) && drupal_strlen($item['title']) == 0) { + unset($item['title']); } - $elements[$delta] = array( - '#theme' => 'responsive_image_formatter', + $picture = array( + '#theme' => 'picture', + '#style_name' => $fallback_image_style, + '#uri' => $item['uri'], + '#width' => $item['width'], + '#height' => $item['height'], + '#breakpoints' => $breakpoint_styles, + '#alt' => isset($item['alt']) || array_key_exists('alt', $item) ? $item['alt'] : NULL, + '#title' => isset($item['title']) ? $item['title'] : NULL, + '#attributes' => isset($item['attributes']) ? $item['attributes'] : NULL, '#attached' => array('library' => array( 'core/picturefill', )), - '#item' => $item, - '#image_style' => $fallback_image_style, - '#breakpoints' => $breakpoint_styles, - '#path' => isset($uri) ? $uri : '', ); + + // Check if the formatter involves a link. + if ($this->getSetting('image_link') == 'content') { + $uri = $items->getEntity()->urlInfo(); + } + elseif ($this->getSetting('image_link') == 'file') { + $uri = array( + 'path' => file_create_url($item['uri']), + 'options' => array(), + ); + } + if (isset($uri)) { + $elements[$delta] = array( + '#type' => 'link', + '#href' => $uri['path'], + '#title' => $picture, + '#options' => array( + 'html' => TRUE, + 'attributes' => isset($uri['attributes']) ? $uri['attributes'] : array(), + ), + ); + } + else { + $elements[$delta] = $picture; + } } return $elements; diff --git a/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageFieldDisplayTest.php index c643774..f31768e 100644 --- a/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageFieldDisplayTest.php +++ b/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageFieldDisplayTest.php @@ -199,14 +199,13 @@ public function _testResponsiveImageFieldFormatters($scheme) { ); $default_output = ''; $this->drupalGet('node/' . $nid); - $this->assertRaw($default_output, 'Image style thumbnail formatter displaying correctly on full node view.'); + $this->assertRaw($default_output, 'Fallback image style (large) printed correctly on full node view.'); if ($scheme == 'private') { // Log out and try to access the file. $this->drupalLogout(); $this->drupalGet($large_style->buildUrl($image_uri)); - $this->assertResponse('403', 'Access denied to image style thumbnail as anonymous user.'); + $this->assertResponse('403', 'Access denied to image style (large) as anonymous user.'); } } - } diff --git a/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageThemeFunctionsTest.php b/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageThemeFunctionsTest.php new file mode 100644 index 0000000..494e6f4 --- /dev/null +++ b/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageThemeFunctionsTest.php @@ -0,0 +1,115 @@ + 'Picture theme functions', + 'description' => 'Tests the picture theme functions.', + 'group' => 'Picture', + ); + } + + /** + * Tests usage of the theme_picture() function. + */ + function testPictureTheme() { + // Create an image. + $files = $this->drupalGetTestFiles('image'); + $file = reset($files); + $original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME); + + // Create a style. + $style = entity_create('image_style', array('name' => 'test', 'label' => 'Test')); + $style->save(); + $url = $style->buildUrl($original_uri); + + // Test using theme_picture() with a NULL value for the alt option. + $path = $this->randomName(); + $element = array( + '#theme' => 'picture', + '#uri' => $original_uri, + '#alt' => NULL, + '#style_name' => 'test', + ); + $rendered_element = render($element); + $expected_result = ''; + $this->assertTrue(strpos($rendered_element, '') !== FALSE, 'picture element in theme_picture() correctly renders with a NULL value for the alt option.'); + + // Test using theme_picture() without passing a value for the alt option. + unset($element['#alt']); + $rendered_element = render($element); + $expected_result = ''; + $this->assertTrue(strpos($rendered_element, $expected_result) !== FALSE, 'img element in theme_picture() correctly renders the alt option.'); + $this->assertTrue(strpos($rendered_element, '') !== FALSE, 'picture element in theme_picture() correctly renders the alt option.'); + } + + /** + * Tests usage of the theme_picture_formatter() function. + */ + function testPictureFormatterTheme() { + // Create an image. + $files = $this->drupalGetTestFiles('image'); + $file = reset($files); + $original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME); + + // Create a style. + $style = entity_create('image_style', array('name' => 'test', 'label' => 'Test')); + $style->save(); + $url = $style->buildUrl($original_uri); + + // Test using theme_picture_formatter() without breakpoints and with a NULL value for the alt option. + $path = $this->randomName(); + $element = array( + '#theme' => 'picture_formatter', + '#item' => array( + 'uri' => $original_uri, + 'alt' => NULL, + ), + '#image_style' => 'test', + '#path' => array( + 'path' => $path, + ), + ); + $rendered_element = render($element); + $expected_result = ''; + $this->assertEqual($expected_result, $rendered_element, 'theme_picture_formatter() correctly renders without breakpoints and with a NULL value for the alt option.'); + + // Test using theme_picture_formatter() without an image title, alt text, or + // link options. + unset($element['#item']['alt']); + $rendered_element = render($element); + $expected_result = ''; + $this->assertEqual($expected_result, $rendered_element, 'theme_picture_formatter() correctly renders without title, alt, or path options.'); + + // Link the image to a fragment on the page, and not a full URL. + $fragment = $this->randomName(); + $element['#path']['path'] = ''; + $element['#path']['options'] = array( + 'external' => TRUE, + 'fragment' => $fragment, + ); + $rendered_element = render($element); + $expected_result = ''; + $this->assertEqual($expected_result, $rendered_element, 'theme_picture_formatter() correctly renders a link fragment.'); + } +} diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module index 5113afc..df1caea 100644 --- a/core/modules/responsive_image/responsive_image.module +++ b/core/modules/responsive_image/responsive_image.module @@ -117,96 +117,30 @@ function responsive_image_theme() { '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, - ), + 'template' => 'picture', ), ); } /** - * 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. + * Prepares variables for picture templates. + * Returns HTML for a picture. + * Default template: picture.html.twig. * * @param $variables * An associative array containing: - * - uri: Either the path of the image file (relative to base_path()) or a + * - path: 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). + * - breakpoints: An array containing breakpoints. * - alt: The alternative text for text-based browsers. * - title: The title text is displayed when the image is hovered in some * popular browsers. - * - breakpoints: An array containing breakpoints. - * - * @ingroup themeable + * - attributes: Attributes for the picture element. + * - style_name: The name of the style to be used to alter the original image. */ -function theme_responsive_image($variables) { +function template_preprocess_picture(&$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/ @@ -219,118 +153,94 @@ function theme_responsive_image($variables) { unset($variables['height']); } - $sources = array(); - $output = array(); + $variables['sources'] = array(); - // Fallback image, output as source with media query. - $sources[] = array( - 'src' => entity_load('image_style', $variables['style_name'])->buildUrl($variables['uri']), - 'dimensions' => responsive_image_get_image_dimensions($variables), - ); + // Prepare picture tag attributes. + $attributes = array(); + if (isset($variables['alt']) || array_key_exists('alt', $variables)) { + $attributes['alt'] = $variables['alt']; + } + if (isset($variables['title'])) { + $attributes['title'] = $variables['title']; + } + $variables['attributes'] = new Attribute($attributes); - // 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; - } + if (!empty($variables['breakpoints'])) { + // Fallback image, output as source with media query. + $src_attributes = picture_get_image_dimensions($variables); + $src_attributes['src'] = entity_load('image_style', $variables['style_name'])->buildUrl($variables['uri']); + $variables['sources'][] = array( + 'attributes' => new Attribute($src_attributes), + ); - // Only one image, use src. - if (count($new_sources) == 1) { - $sources[] = array( - 'src' => entity_load('image_style', $new_sources[0]['style_name'])->buildUrl($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[] = entity_load('image_style', $new_source['style_name'])->buildUrl($new_source['uri']) . ' ' . $new_source['#multiplier']; + // 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; } - $sources[] = array( - 'srcset' => implode(', ', $srcset), - 'dimensions' => responsive_image_get_image_dimensions($new_sources[0]), - 'media' => $breakpoint->mediaQuery, - ); - } - } - } - if (!empty($sources)) { - $output[] = ''; + // Only one image, use src. @todo + if (count($new_sources) == 1) { + // Setup source attributes. + $src_attributes = array( + 'media' => $breakpoint->mediaQuery, + 'src' => entity_load('image_style', $new_sources[0]['style_name'])->buildUrl($new_sources[0]['uri']), + ); + } + else { + // Multiple images, use srcset. + $srcset = array(); + foreach ($new_sources as $new_source) { + $srcset[] = entity_load('image_style', $new_source['style_name'])->buildUrl($new_source['uri']) . ' ' . $new_source['#multiplier']; + } - // 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']; + // Setup source attributes. + $src_attributes = array( + 'media' => $breakpoint->mediaQuery, + 'srcset' => implode(', ', $srcset), + ); + + // Add src if we have one. + if (!empty($variables['src'])) { + $src_attributes['src'] = $variables['src']; + unset($variables['src']); + } + } + $src_attributes += picture_get_image_dimensions($new_sources[0]); + $variables['sources'][] = array( + 'attributes' => new Attribute($src_attributes), + ); } - $output[] = drupal_render($responsive_image_source); } - - // Output the fallback image. - $image_style = array( + // Prepare noscript fallback image. + $variables['fallback'] = array( '#theme' => 'image_style', - '#style_name' => $variables['style_name'], + '#uri' => $variables['uri'], '#width' => $variables['width'], '#height' => $variables['height'], + '#style_name' => $variables['style_name'], + '#alt' => isset($variables['alt']) || array_key_exists('alt', $variables) ? $variables['alt'] : NULL, + '#title' => isset($variables['title']) ? $variables['title'] : NULL, ); - foreach (array('uri', 'alt', 'title', 'attributes') as $key) { - if (isset($variables[$key])) { - $image_style["#$key"] = $variables[$key]; - } - } - $output[] = ' '; - $output[] = ''; - return 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[] = ''; - $output[] = ''; - } - elseif (!isset($variables['src'])) { - $output[] = ''; - $output[] = ''; - } } else { - $output[] = ''; - $output[] = ''; + // No matching picture mapping or style name. + $variables['fallback'] = array( + '#theme' => 'image', + '#uri' => $variables['uri'], + '#width' => $variables['width'], + '#height' => $variables['height'], + '#alt' => isset($variables['alt']) || array_key_exists('alt', $variables) ? $variables['alt'] : NULL, + '#title' => isset($variables['title']) ? $variables['title'] : NULL, + ); } - return implode("\n", $output); + } /** @@ -353,7 +263,9 @@ function responsive_image_get_image_dimensions($variables) { 'height' => $variables['height'], ); - entity_load('image_style', $variables['style_name'])->transformDimensions($dimensions); + if ($image_style = entity_load('image_style', $variables['style_name'])) { + $image_style->transformDimensions($dimensions); + } return $dimensions; } diff --git a/core/modules/responsive_image/templates/picture.html.twig b/core/modules/responsive_image/templates/picture.html.twig new file mode 100644 index 0000000..bbd8ef9 --- /dev/null +++ b/core/modules/responsive_image/templates/picture.html.twig @@ -0,0 +1,26 @@ +{# +/** + * @file + * Default theme implementation to display a picture tag. + * + * Available variables: + * - attributes: HTML attributes for the picture tag. + * - sources: A set of source data. + * - source.attributes: HTML attributes for each source tag. + * - fallback: Fallback image tag for non-JS browsers. + * + * @see template_preprocess_picture() + * + * @ingroup themeable + */ +#} +{% if sources %} + + {% for source in sources %} + + {% endfor %} + + +{% else %} + {{- fallback -}} +{% endif %}