core/modules/filter/css/filter.caption.css | 42 ++++++ core/modules/filter/filter.module | 55 +++++++- .../Drupal/filter/Plugin/Filter/FilterCaption.php | 137 ++++++++++++++++++++ .../standard/config/filter.format.basic_html.yml | 5 + .../standard/config/filter.format.full_html.yml | 5 + 5 files changed, 240 insertions(+), 4 deletions(-) diff --git a/core/modules/filter/css/filter.caption.css b/core/modules/filter/css/filter.caption.css new file mode 100644 index 0000000..c66d4b1 --- /dev/null +++ b/core/modules/filter/css/filter.caption.css @@ -0,0 +1,42 @@ +/** + * @file + * Caption filter: default styling for displaying image captions. + */ + +div.caption-inner { + border: 1px solid #CCC; + padding: 4px; + background: #F3F3F3; + font-size: 0.857em; /* assuming you have a base font size of 14px, this is 12px */ + text-align: center; +} + +div.caption p { + margin: .25em 0; +} + +div.caption img, +div.caption object { + margin-bottom: 5px; + display: block; +} + +/** aligned captions **/ +div.caption-left { + float: left; + margin: 10px 10px 10px 0; +} + +div.caption-right { + float: right; + margin: 10px 0 10px 10px; +} + +div.caption-center { + display: block; + text-align: center; +} + +div.caption-center .caption-inner { + display: inline-block; +} diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index cf0eaef..6c77b1c 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -89,6 +89,14 @@ function filter_theme() { 'filter_html_image_secure_image' => array( 'variables' => array('image' => NULL), ), + 'filter_caption' => array( + 'variables' => array( + 'image' => NULL, + 'caption' => NULL, + 'align' => NULL, + 'width' => NULL, + ) + ) ); } @@ -1425,21 +1433,53 @@ function theme_filter_html_image_secure_image(&$variables) { } /** + * Returns HTML for a captioned image. + * + * @param $variables + * An associative array containing: + * - image: The complete image tag whose image is being captioned. + * - caption: The caption text, or NULL. + * - align: The alignment: 'left', 'center', 'right' or NULL. + * - width: The width of the image. + * + * @ingroup themeable + */ +function theme_filter_caption($variables) { + $image = $variables['image']; + $caption = $variables['caption']; + $align = $variables['align']; + $width = $variables['width']; + return '
' . + '
'. + $image . + '
' . $caption . '
' . + '
'; +} + +/** * @} End of "defgroup standard_filters". */ /** + * Implements hook_page_build(). + */ +function filter_page_build(&$page) { + $page['#attached']['library'][] = array('filter', 'caption'); +} + +/** * Implements hook_library_info(). */ function filter_library_info() { + $path = drupal_get_path('module', 'filter'); $libraries['drupal.filter.admin'] = array( 'title' => 'Filter', 'version' => VERSION, 'js' => array( - drupal_get_path('module', 'filter') . '/filter.admin.js' => array(), + $path . '/filter.admin.js' => array(), ), 'css' => array( - drupal_get_path('module', 'filter') . '/css/filter.admin.css' + $path . '/css/filter.admin.css' ), 'dependencies' => array( array('system', 'jquery'), @@ -1452,10 +1492,10 @@ function filter_library_info() { 'title' => 'Filter', 'version' => VERSION, 'js' => array( - drupal_get_path('module', 'filter') . '/filter.js' => array(), + $path . '/filter.js' => array(), ), 'css' => array( - drupal_get_path('module', 'filter') . '/css/filter.admin.css' + $path . '/css/filter.admin.css' ), 'dependencies' => array( array('system', 'jquery'), @@ -1463,6 +1503,13 @@ function filter_library_info() { array('system', 'jquery.once'), ), ); + $libraries['caption'] = array( + 'title' => 'Captions for images and alignments', + 'version' => VERSION, + 'css' => array( + $path . '/css/filter.caption.css', + ), + ); return $libraries; } diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php new file mode 100644 index 0000000..1815155 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php @@ -0,0 +1,137 @@ +]*\s+)(?:data-caption|data-align)\s*.*(?:\s*>|\s*/>|\s*>))#i"; + if (preg_match_all($pattern, $text, $matches)) { + foreach ($matches[0] as $image_html) { + $dom = filter_dom_load($image_html); + $image_node = $dom->getElementsByTagName('img')->item(0); + $caption = NULL; + $align = NULL; + + // Retrieve, then remove the data-caption and data-align attributes. + if ($image_node->hasAttribute('data-caption')) { + $caption = $image_node->getAttribute('data-caption'); + $image_node->removeAttribute('data-caption'); + } + if ($image_node->hasAttribute('data-align')) { + $align = $image_node->getAttribute('data-align'); + $image_node->removeAttribute('data-align'); + } + + // Given the updated image node, caption, alignment and width: re-render + // the image with a caption. + $altered_image_html = theme('filter_caption', array( + 'image' => $image_node->C14N(), + 'caption' => $caption, + 'align' => $align, + 'width' => $this->getImageWidth($image_node), + )); + + // Load the new HTML into a new DOMDocument. + $dom2 = filter_dom_load($altered_image_html); + + // Locate the snippet of HTML we're interested in. + $dom2_image_node = $dom2->getElementsByTagName('body')->item(0) + ->childNodes->item(0); + // Import the new "image" node from the second DOMDocument into the main + // one, importing also the child nodes of the new "image" node. + $new_image_node = $dom->importNode($dom2_image_node, TRUE); + // Finally, replace the original image node with the new image node! + $image_node->parentNode->replaceChild($new_image_node, $image_node); + + $search[] = $image_html; + $replace[] = filter_dom_serialize($dom); + } + } + } + + return str_replace($search, $replace, $text); + } + + /** + * Determines the width of the image tag that is being captioned. + * + * @param DOMNode $image_node + * A DOMNode for an image tag from a DOMDocument. @see filter_dom_load(). + * + * @return string + * The CSS declaration for the image's width, typically a number with "px" + * appended to it; possibly "auto" or even a percentage. + */ + protected function getImageWidth(DOMNode $image_node) { + // Retrieve the width attribute, or calculate the width directly from + // the image. + if ($image_node->hasAttribute('width')) { + $width = $image_node->getAttribute('width'); + } + else { + if ($image_node->hasAttribute('src')) { + list($width) = getimagesize($image_node->getAttribute('src')); + } + else { + // We cannot determine the width so just set it to the default CSS value. + $width = 'auto'; + } + } + + // We need to append the 'px' to any numeric widths. + if (is_numeric($width)) { + $width = $width . 'px'; + } + + return $width; + } + + /** + * {@inheritdoc} + */ + public function tips($long = FALSE) { + if ($long) { + return t(' +

You can add image captions and align images left, right or centered. Examples:

+ '); + } + else { + return t('You can caption images (data-caption="Text") and align images (data-align="center").'); + } + } +} diff --git a/core/profiles/standard/config/filter.format.basic_html.yml b/core/profiles/standard/config/filter.format.basic_html.yml index 5810786..69fc520 100644 --- a/core/profiles/standard/config/filter.format.basic_html.yml +++ b/core/profiles/standard/config/filter.format.basic_html.yml @@ -14,6 +14,11 @@ filters: allowed_html: '
    1. ' filter_html_help: '0' filter_html_nofollow: '0' + filter_caption: + module: filter + status: '1' + weight: '8' + settings: { } filter_html_image_secure: module: filter status: '1' diff --git a/core/profiles/standard/config/filter.format.full_html.yml b/core/profiles/standard/config/filter.format.full_html.yml index 204a342..0265845 100644 --- a/core/profiles/standard/config/filter.format.full_html.yml +++ b/core/profiles/standard/config/filter.format.full_html.yml @@ -6,6 +6,11 @@ roles: - administrator cache: '1' filters: + filter_caption: + module: filter + status: '1' + weight: '9' + settings: { } filter_htmlcorrector: module: filter status: '1'