diff --git a/core/modules/picture/lib/Drupal/picture/Plugin/Field/FieldFormatter/PictureFormatter.php b/core/modules/picture/lib/Drupal/picture/Plugin/Field/FieldFormatter/PictureFormatter.php
index eb1864d..3b86835 100644
--- a/core/modules/picture/lib/Drupal/picture/Plugin/Field/FieldFormatter/PictureFormatter.php
+++ b/core/modules/picture/lib/Drupal/picture/Plugin/Field/FieldFormatter/PictureFormatter.php
@@ -115,17 +115,10 @@ 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_picture_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);
$picture_mapping = entity_load('picture_mapping', $this->getSetting('picture_mapping'));
if ($picture_mapping) {
@@ -163,24 +156,54 @@ 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();
}
- $elements[$delta] = array(
- '#theme' => 'picture_formatter',
+ if (isset($item['title']) && drupal_strlen($item['title']) == 0) {
+ unset($item['title']);
+ }
+ $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(
array('picture', '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 = $entity->uri();
+ }
+ 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;
}
-}
+}
\ No newline at end of file
diff --git a/core/modules/picture/lib/Drupal/picture/Tests/PictureFieldDisplayTest.php b/core/modules/picture/lib/Drupal/picture/Tests/PictureFieldDisplayTest.php
index 7c11a6f..388e46a 100644
--- a/core/modules/picture/lib/Drupal/picture/Tests/PictureFieldDisplayTest.php
+++ b/core/modules/picture/lib/Drupal/picture/Tests/PictureFieldDisplayTest.php
@@ -199,14 +199,13 @@ public function _testPictureFieldFormatters($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/picture/lib/Drupal/picture/Tests/PictureThemeFunctionsTest.php b/core/modules/picture/lib/Drupal/picture/Tests/PictureThemeFunctionsTest.php
new file mode 100644
index 0000000..494e6f4
--- /dev/null
+++ b/core/modules/picture/lib/Drupal/picture/Tests/PictureThemeFunctionsTest.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/picture/picture.module b/core/modules/picture/picture.module
index ae676c9..331c6d0 100644
--- a/core/modules/picture/picture.module
+++ b/core/modules/picture/picture.module
@@ -118,94 +118,31 @@ function picture_theme() {
'attributes' => array(),
'breakpoints' => array(),
),
- ),
- 'picture_formatter' => array(
- 'variables' => array(
- 'item' => NULL,
- 'path' => NULL,
- 'image_style' => NULL,
- 'breakpoints' => array(),
- ),
- ),
- 'picture_source' => array(
- 'variables' => array(
- 'src' => NULL,
- 'srcset' => NULL,
- 'dimensions' => NULL,
- 'media' => NULL,
- ),
+ 'template' => 'picture',
),
);
}
/**
- * Returns HTML for a picture 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_picture_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);
- }
-
- $picture = array(
- '#theme' => 'picture',
- '#width' => $item->width,
- '#height' => $item->height,
- '#style_name' => $variables['image_style'],
- '#breakpoints' => $variables['breakpoints'],
- );
- if (isset($item->uri)) {
- $picture['#uri'] = $item->uri;
- }
- elseif ($entity = $item->entity) {
- $picture['#uri'] = $entity->getFileUri();
- $picture['#entity'] = $entity;
- }
- $picture['#alt'] = $item->alt;
- if (drupal_strlen($item->title) != 0) {
- $picture['#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($picture, $path, $options);
- }
-
- return drupal_render($picture);
-}
-
-/**
+ * 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).
- * - alt: The alternative text for text-based browsers.
* - breakpoints: An array containing breakpoints.
- *
- * @ingroup themeable
+ * - alt: The alternative text for text-based browsers.
+ * - title: The title text is displayed when the image is hovered in some
+ * popular browsers.
+ * - attributes: Attributes for the picture element.
+ * - style_name: The name of the style to be used to alter the original image.
*/
-function theme_picture($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/
@@ -218,124 +155,94 @@ function theme_picture($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' => picture_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' => picture_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' => picture_get_image_dimensions($new_sources[0]),
- 'media' => $breakpoint->mediaQuery,
- );
- }
- }
- }
- if (!empty($sources)) {
- $attributes = array();
- foreach (array('alt', 'title') as $key) {
- if (isset($variables[$key])) {
- $attributes[$key] = $variables[$key];
- }
- }
- $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) {
- $picture_source = array(
- '#theme' => 'picture_source',
- '#src' => $source['src'],
- '#dimensions' => $source['dimensions'],
- );
- if (isset($source['media'])) {
- $picture_source['#media'] = $source['media'];
- }
- if (isset($source['srcset'])) {
- $picture_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($picture_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_picture_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);
+
}
/**
@@ -358,7 +265,9 @@ function picture_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;
-}
+}
\ No newline at end of file
diff --git a/core/modules/picture/templates/picture.html.twig b/core/modules/picture/templates/picture.html.twig
new file mode 100644
index 0000000..bbd8ef9
--- /dev/null
+++ b/core/modules/picture/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 %}