Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
project
drupal
Commits
500c0820
Commit
500c0820
authored
Jun 29, 2013
by
alexpott
Browse files
Issue
#2014895
by Wim Leers, jessebeach: Image captions & alignment.
parent
6e4b4135
Changes
8
Hide whitespace changes
Inline
Side-by-side
core/modules/filter/css/filter.caption.css
0 → 100644
View file @
500c0820
/**
* @file
* Caption filter: default styling for displaying image captions.
*/
/**
* Essentials, based on http://stackoverflow.com/a/13363408.
*/
.caption
{
display
:
table
;
}
.caption
>
*
{
display
:
block
;
max-width
:
100%
;
}
.caption
>
figcaption
{
display
:
table-caption
;
caption-side
:
bottom
;
max-width
:
none
;
}
/**
* Caption alignment.
*/
.caption-left
{
float
:
left
;
/* LTR */
margin-left
:
0
;
/* LTR */
}
[
dir
=
rtl
]
.caption-left
{
float
:
right
;
margin-left
:
auto
;
margin-right
:
0
;
}
.caption-right
{
float
:
right
;
/* LTR */
margin-right
:
0
;
/* LTR */
}
[
dir
=
rtl
]
.caption-right
{
float
:
left
;
margin-left
:
0
;
margin-right
:
auto
;
}
.caption-center
{
margin-left
:
auto
;
margin-right
:
auto
;
text-align
:
center
;
}
core/modules/filter/filter.module
View file @
500c0820
...
...
@@ -89,6 +89,15 @@ function filter_theme() {
'filter_html_image_secure_image'
=>
array
(
'variables'
=>
array
(
'image'
=>
NULL
),
),
'filter_caption'
=>
array
(
'variables'
=>
array
(
'node'
=>
NULL
,
'tag'
=>
NULL
,
'caption'
=>
NULL
,
'align'
=>
NULL
,
),
'template'
=>
'filter-caption'
,
)
);
}
...
...
@@ -1576,6 +1585,13 @@ function theme_filter_html_image_secure_image(&$variables) {
* @} End of "defgroup standard_filters".
*/
/**
* Implements hook_page_build().
*/
function
filter_page_build
(
&
$page
)
{
$page
[
'#attached'
][
'library'
][]
=
array
(
'filter'
,
'caption'
);
}
/**
* Implements hook_library_info().
*/
...
...
@@ -1625,6 +1641,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
;
}
core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php
0 → 100644
View file @
500c0820
<?php
/**
* @file
* Contains \Drupal\filter\Plugin\Filter\FilterCaption.
*/
namespace
Drupal\filter\Plugin\Filter
;
use
Drupal\Component\Utility\String
;
use
Drupal\Component\Utility\Unicode
;
use
Drupal\Component\Utility\Xss
;
use
Drupal\Core\Annotation\Translation
;
use
Drupal\filter\Annotation\Filter
;
use
Drupal\filter\Plugin\FilterBase
;
/**
* Provides a filter to display image captions and align images.
*
* @Filter(
* id = "filter_caption",
* module = "filter",
* title = @Translation("Display image captions and align images"),
* description = @Translation("Uses data-caption and data-align attributes on <img> tags to caption and align images."),
* type = FILTER_TYPE_TRANSFORM_REVERSIBLE
* )
*/
class
FilterCaption
extends
FilterBase
{
/**
* {@inheritdoc}
*/
public
function
process
(
$text
,
$langcode
,
$cache
,
$cache_id
)
{
$search
=
array
();
$replace
=
array
();
if
(
stristr
(
$text
,
'data-caption'
)
!==
FALSE
||
stristr
(
$text
,
'data-align'
)
!==
FALSE
)
{
$dom
=
filter_dom_load
(
$text
);
$xpath
=
new
\
DOMXPath
(
$dom
);
foreach
(
$xpath
->
query
(
'//*[@data-caption or @data-align]'
)
as
$node
)
{
$caption
=
NULL
;
$align
=
NULL
;
// Retrieve, then remove the data-caption and data-align attributes.
if
(
$node
->
hasAttribute
(
'data-caption'
))
{
$caption
=
String
::
checkPlain
(
$node
->
getAttribute
(
'data-caption'
));
$node
->
removeAttribute
(
'data-caption'
);
// Sanitize caption: decode HTML encoding, limit allowed HTML tags.
$caption
=
String
::
decodeEntities
(
$caption
);
$caption
=
Xss
::
filter
(
$caption
);
// The caption must be non-empty.
if
(
Unicode
::
strlen
(
$caption
)
===
0
)
{
$caption
=
NULL
;
}
}
if
(
$node
->
hasAttribute
(
'data-align'
))
{
$align
=
$node
->
getAttribute
(
'data-align'
);
$node
->
removeAttribute
(
'data-align'
);
// Only allow 3 values: 'left', 'center' and 'right'.
if
(
!
in_array
(
$align
,
array
(
'left'
,
'center'
,
'right'
)))
{
$align
=
NULL
;
}
}
// If neither attribute has a value after validation, then don't
// transform the HTML.
if
(
$caption
===
NULL
&&
$align
===
NULL
)
{
continue
;
}
// Given the updated node, caption and alignment: re-render it with a
// caption.
$altered_html
=
theme
(
'filter_caption'
,
array
(
'node'
=>
$node
->
C14N
(),
'tag'
=>
$node
->
tagName
,
'caption'
=>
$caption
,
'align'
=>
$align
,
));
// Load the altered HTML into a new DOMDocument and retrieve the element.
$updated_node
=
filter_dom_load
(
$altered_html
)
->
getElementsByTagName
(
'body'
)
->
item
(
0
)
->
childNodes
->
item
(
0
);
// Import the updated node from the new DOMDocument into the original
// one, importing also the child nodes of the updated node.
$updated_node
=
$dom
->
importNode
(
$updated_node
,
TRUE
);
// Finally, replace the original image node with the new image node!
$node
->
parentNode
->
replaceChild
(
$updated_node
,
$node
);
}
return
filter_dom_serialize
(
$dom
);
}
return
$text
;
}
/**
* {@inheritdoc}
*/
public
function
tips
(
$long
=
FALSE
)
{
if
(
$long
)
{
return
t
(
'
<p>You can add image captions and align images left, right or centered. Examples:</p>
<ul>
<li>Caption an image: <code><img src="" data-caption="This is a caption" /></code></li>
<li>Align an image: <code><img src="" data-align="center" /></code></li>
<li>Caption & align an image: <code><img src="" data-caption="Alpaca" data-align="right" /></code></li>
</ul>'
);
}
else
{
return
t
(
'You can caption (data-caption="Text") and align images (data-align="center"), but also video, blockquotes, and so on.'
);
}
}
}
core/modules/filter/lib/Drupal/filter/Tests/FilterUnitTest.php
View file @
500c0820
...
...
@@ -9,6 +9,8 @@
use
Drupal\simpletest\DrupalUnitTestBase
;
use
stdClass
;
use
Drupal\filter\FilterBag
;
use
Drupal\filter\Plugin\Filter\FilterCaption
;
/**
* Unit tests for core filters.
...
...
@@ -33,15 +35,121 @@ public static function getInfo() {
protected
function
setUp
()
{
parent
::
setUp
();
config_install_default_config
(
'module'
,
'system'
);
$manager
=
$this
->
container
->
get
(
'plugin.manager.filter'
);
$bag
=
new
FilterBag
(
$manager
,
array
());
$this
->
filters
=
$bag
->
getAll
();
}
/**
* Tests the caption filter.
*/
function
testCaptionFilter
()
{
$filter
=
$this
->
filters
[
'filter_caption'
];
$test
=
function
(
$input
)
use
(
$filter
)
{
return
$filter
->
process
(
$input
,
'und'
,
FALSE
,
''
);
};
// No data-caption nor data-align attributes.
$input
=
'<img src="llama.jpg" />'
;
$expected
=
$input
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Only data-caption attribute.
$input
=
'<img src="llama.jpg" data-caption="Loquacious llama!" />'
;
$expected
=
'<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Empty data-caption attribute.
$input
=
'<img src="llama.jpg" data-caption="" />'
;
$expected
=
'<img src="llama.jpg" />'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// HTML entities in the caption.
$input
=
'<img src="llama.jpg" data-caption="“Loquacious llama!”" />'
;
$expected
=
'<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>“Loquacious llama!”</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// HTML encoded as HTML entities in data-caption attribute.
$input
=
'<img src="llama.jpg" data-caption="<em>Loquacious llama!</em>" />'
;
$expected
=
'<figure class="caption caption-img"><img src="llama.jpg" /><figcaption><em>Loquacious llama!</em></figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// HTML (not encoded as HTML entities) in data-caption attribute, which is
// not allowed by the HTML spec, but may happen when people manually write
// HTML, so we explicitly support it.
$input
=
'<img src="llama.jpg" data-caption="<em>Loquacious llama!</em>" />'
;
$expected
=
'<figure class="caption caption-img"><img src="llama.jpg" /><figcaption><em>Loquacious llama!</em></figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Security test: attempt an XSS.
$input
=
'<img src="llama.jpg" data-caption="<script>alert(\'Loquacious llama!\')</script>" />'
;
$expected
=
'<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>alert(\'Loquacious llama!\')</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Only data-align attribute: all 3 allowed values.
$input
=
'<img src="llama.jpg" data-align="left" />'
;
$expected
=
'<figure class="caption caption-img caption-left"><img src="llama.jpg" /></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
$input
=
'<img src="llama.jpg" data-align="center" />'
;
$expected
=
'<figure class="caption caption-img caption-center"><img src="llama.jpg" /></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
$input
=
'<img src="llama.jpg" data-align="right" />'
;
$expected
=
'<figure class="caption caption-img caption-right"><img src="llama.jpg" /></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Only data-align attribute: a disallowed value.
$input
=
'<img src="llama.jpg" data-align="left foobar" />'
;
$expected
=
'<img src="llama.jpg" />'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Empty data-align attribute.
$input
=
'<img src="llama.jpg" data-align="" />'
;
$expected
=
'<img src="llama.jpg" />'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Both data-caption and data-align attributes.
$input
=
'<img src="llama.jpg" data-caption="Loquacious llama!" data-align="right" />'
;
$expected
=
'<figure class="caption caption-img caption-right"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Both data-caption and data-align attributes, but a disallowed data-align
// attribute value.
$input
=
'<img src="llama.jpg" data-caption="Loquacious llama!" data-align="left foobar" />'
;
$expected
=
'<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Ensure the filter also works with uncommon yet valid attribute quoting.
$input
=
'<img src=llama.jpg data-caption=\'Loquacious llama!\' data-align=right />'
;
$expected
=
'<figure class="caption caption-img caption-right"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Security test: attempt to inject an additional class.
$input
=
'<img src="llama.jpg" data-caption="Loquacious llama!" data-align="center another-class-here" />'
;
$expected
=
'<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Security test: attempt an XSS.
$input
=
'<img src="llama.jpg" data-caption="Loquacious llama!" data-align="center \'onclick=\'alert(foo);" />'
;
$expected
=
'<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
// Finally, ensure that this also works on any other tag.
$input
=
'<video src="llama.jpg" data-caption="Loquacious llama!" />'
;
$expected
=
'<figure class="caption caption-video"><video src="llama.jpg"></video><figcaption>Loquacious llama!</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
$input
=
'<foobar data-caption="Loquacious llama!">baz</foobar>'
;
$expected
=
'<figure class="caption caption-foobar"><foobar>baz</foobar><figcaption>Loquacious llama!</figcaption></figure>'
;
$this
->
assertIdentical
(
$expected
,
$test
(
$input
));
}
/**
* Tests the line break filter.
*/
function
testLineBreakFilter
()
{
// Setup dummy filter object.
$filter
=
new
stdClass
();
$filter
->
callback
=
'_filter_autop'
;
// Get FilterAutoP object.
$filter
=
$this
->
filters
[
'filter_autop'
];
// Since the line break filter naturally needs plenty of newlines in test
// strings and expectations, we're using "\n" instead of regular newlines
...
...
@@ -128,13 +236,15 @@ function testLineBreakFilter() {
* or better a whitelist approach should be used for that too.
*/
function
testHtmlFilter
()
{
// Setup dummy filter object.
$filter
=
new
stdClass
();
$filter
->
settings
=
array
(
'allowed_html'
=>
'<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>'
,
'filter_html_help'
=>
1
,
'filter_html_nofollow'
=>
0
,
);
// Get FilterHtml object.
$filter
=
$this
->
filters
[
'filter_html'
];
$filter
->
setPluginConfiguration
(
array
(
'settings'
=>
array
(
'allowed_html'
=>
'<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>'
,
'filter_html_help'
=>
1
,
'filter_html_nofollow'
=>
0
,
)
));
// HTML filter is not able to secure some tags, these should never be
// allowed.
...
...
@@ -173,13 +283,15 @@ function testHtmlFilter() {
* Tests the spam deterrent.
*/
function
testNoFollowFilter
()
{
// Setup dummy filter object.
$filter
=
new
stdClass
();
$filter
->
settings
=
array
(
'allowed_html'
=>
'<a>'
,
'filter_html_help'
=>
1
,
'filter_html_nofollow'
=>
1
,
);
// Get FilterHtml object.
$filter
=
$this
->
filters
[
'filter_html'
];
$filter
->
setPluginConfiguration
(
array
(
'settings'
=>
array
(
'allowed_html'
=>
'<a>'
,
'filter_html_help'
=>
1
,
'filter_html_nofollow'
=>
1
,
)
));
// Test if the rel="nofollow" attribute is added, even if we try to prevent
// it.
...
...
@@ -206,9 +318,8 @@ function testNoFollowFilter() {
* check_plain() is not tested here.
*/
function
testHtmlEscapeFilter
()
{
// Setup dummy filter object.
$filter
=
new
stdClass
();
$filter
->
callback
=
'_filter_html_escape'
;
// Get FilterHtmlEscape object.
$filter
=
$this
->
filters
[
'filter_html_escape'
];
$tests
=
array
(
" One. <!--
\"
comment
\"
--> Two'.
\n
<p>Three.</p>
\n
"
=>
array
(
...
...
@@ -224,12 +335,14 @@ function testHtmlEscapeFilter() {
* Tests the URL filter.
*/
function
testUrlFilter
()
{
// Setup dummy filter object.
$filter
=
new
stdClass
();
$filter
->
callback
=
'_filter_url'
;
$filter
->
settings
=
array
(
'filter_url_length'
=>
496
,
);
// Get FilterUrl object.
$filter
=
$this
->
filters
[
'filter_url'
];
$filter
->
setPluginConfiguration
(
array
(
'settings'
=>
array
(
'filter_url_length'
=>
496
,
)
));
// @todo Possible categories:
// - absolute, mail, partial
// - characters/encoding, surrounding markup, security
...
...
@@ -516,7 +629,11 @@ function testUrlFilter() {
$this
->
assertFilteredString
(
$filter
,
$tests
);
// URL trimming.
$filter
->
settings
[
'filter_url_length'
]
=
20
;
$filter
->
setPluginConfiguration
(
array
(
'settings'
=>
array
(
'filter_url_length'
=>
20
,
)
));
$tests
=
array
(
'www.trimmed.com/d/ff.ext?a=1&b=2#a1'
=>
array
(
'<a href="http://www.trimmed.com/d/ff.ext?a=1&b=2#a1">www.trimmed.com/d/ff...</a>'
=>
TRUE
,
...
...
@@ -528,7 +645,7 @@ function testUrlFilter() {
/**
* Asserts multiple filter output expectations for multiple input strings.
*
* @param $filter
* @param
FilterInterface
$filter
* A input filter object.
* @param $tests
* An associative array, whereas each key is an arbitrary input string and
...
...
@@ -548,8 +665,7 @@ function testUrlFilter() {
*/
function
assertFilteredString
(
$filter
,
$tests
)
{
foreach
(
$tests
as
$source
=>
$tasks
)
{
$function
=
$filter
->
callback
;
$result
=
$function
(
$source
,
$filter
);
$result
=
$filter
->
process
(
$source
,
$filter
,
FALSE
,
''
);
foreach
(
$tasks
as
$value
=>
$is_expected
)
{
// Not using assertIdentical, since combination with strpos() is hard to grok.
if
(
$is_expected
)
{
...
...
@@ -593,11 +709,13 @@ function assertFilteredString($filter, $tests) {
* - Mix of absolute and partial URLs, and e-mail addresses in one content.
*/
function
testUrlFilterContent
()
{
// Setup dummy filter object.
$filter
=
new
stdClass
();
$filter
->
settings
=
array
(
'filter_url_length'
=>
496
,
);
// Get FilterUrl object.
$filter
=
$this
->
filters
[
'filter_url'
];
$filter
->
setPluginConfiguration
(
array
(
'settings'
=>
array
(
'filter_url_length'
=>
496
,
)
));
$path
=
drupal_get_path
(
'module'
,
'filter'
)
.
'/tests'
;
$input
=
file_get_contents
(
$path
.
'/filter.url-input.txt'
);
...
...
core/modules/filter/templates/filter-caption.html.twig
0 → 100644
View file @
500c0820
{#
/**
* Returns HTML for a captioned image, audio, video or other tag.
*
* Available variables
* - string node: The complete HTML tag whose contents are being captioned.
* - string tag: The name of the HTML tag whose contents are being captioned.
* - string|NULL caption: (optional) The caption text, or NULL.
* - string|NULL align: (optional) The alignment: 'left', 'center', 'right' or
* NULL.
*/
#}
<figure
class=
"caption caption-
{{
tag
}}
{%
-
if
align
%}
caption-
{{
align
}}
{%
-
endif
%}
"
>
{{
node
}}
{%
if
caption
%}
<figcaption>
{{
caption
}}
</figcaption>
{%
endif
%}
</figure>
core/profiles/standard/config/filter.format.basic_html.yml
View file @
500c0820
...
...
@@ -14,6 +14,11 @@ filters:
allowed_html
:
'
<a>
<em>
<strong>
<cite>
<blockquote>
<code>
<ul>
<ol>
<li>
<dl>
<dt>
<dd>
<h4>
<h5>
<h6>
<p>
<span>
<img>'
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'
...
...
core/profiles/standard/config/filter.format.full_html.yml
View file @
500c0820
...
...
@@ -6,6 +6,11 @@ roles:
-
administrator
cache
:
'
1'
filters
:
filter_caption
:
module
:
filter
status
:
'
1'
weight
:
'
9'
settings
:
{
}
filter_htmlcorrector
:
module
:
filter
status
:
'
1'
...
...
core/themes/bartik/css/style.css
View file @
500c0820
...
...
@@ -1611,6 +1611,37 @@ ol.search-results {
padding-left
:
0
;
}
/* -------------- Captions -------------- */
.caption
>
*
{
background
:
#F3F3F3
;
padding
:
0.5ex
;
border
:
1px
solid
#CCC
;
}
.caption
>
figcaption
{
border
:
1px
solid
#CCC
;
border-top
:
none
;
padding-top
:
0.5ex
;
font-size
:
small
;
text-align
:
center
;
}
/* Override Bartik's default blockquote and pre styles when captioned. */
.caption-pre
>
pre
,
.caption-blockquote
>
blockquote
{
margin
:
0
;
}
.caption-blockquote
>
figcaption
::before
{
content
:
"— "
;
}
.caption-blockquote
>
figcaption
{
text-align
:
left
;
}
[
dir
=
rtl
]
.caption-blockquote
>
figcaption
{
text-align
:
right
;
}
/* -------------- Shortcut Links -------------- */
.shortcut-wrapper
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment