diff --git a/parsers/ShortcodeParser.php b/parsers/ShortcodeParser.php
index 6cd0f737b..37996e50a 100644
--- a/parsers/ShortcodeParser.php
+++ b/parsers/ShortcodeParser.php
@@ -103,11 +103,47 @@ class ShortcodeParser extends Object {
$this->shortcodes = array();
}
+ /**
+ * Call a shortcode and return its replacement text
+ * Returns false if the shortcode isn't registered
+ */
public function callShortcode($tag, $attributes, $content, $extra = array()) {
- if (!isset($this->shortcodes[$tag])) return false;
+ if (!$tag || !isset($this->shortcodes[$tag])) return false;
return call_user_func($this->shortcodes[$tag], $attributes, $content, $this, $tag, $extra);
}
+ /**
+ * Return the text to insert in place of a shoprtcode.
+ * Behaviour in the case of missing shortcodes depends on the setting of ShortcodeParser::$error_behavior.
+ * @param $tag A map containing the the following keys:
+ * - 'open': The name of the tag
+ * - 'attrs': Attributes of the tag
+ * - 'content': Content of the tag
+ * @param $extra Extra-meta data
+ * @param $isHTMLAllowed A boolean indicating whether it's okay to insert HTML tags into the result
+ */
+ function getShortcodeReplacementText($tag, $extra = array(), $isHTMLAllowed = true) {
+ $content = $this->callShortcode($tag['open'], $tag['attrs'], $tag['content'], $extra);
+
+ // Missing tag
+ if ($content === false) {
+ if(ShortcodeParser::$error_behavior == ShortcodeParser::ERROR) {
+ user_error('Unknown shortcode tag '.$tag['open'], E_USER_ERRROR);
+ }
+ else if (self::$error_behavior == self::WARN && $isHTMLAllowed) {
+ $content = ''.$tag['text'].'';
+ }
+ else if(ShortcodeParser::$error_behavior == ShortcodeParser::STRIP) {
+ return '';
+ }
+ else {
+ return $tag['text'];
+ }
+ }
+
+ return $content;
+ }
+
// --------------------------------------------------------------------------------------------------------------
protected function removeNode($node) {
@@ -207,6 +243,7 @@ class ShortcodeParser extends Object {
protected function extractTags($content) {
$tags = array();
+ // Step 1: perform basic regex scan of individual tags
if(preg_match_all(static::tagrx(), $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
foreach($matches as $match) {
// Ignore any elements
@@ -236,8 +273,9 @@ class ShortcodeParser extends Object {
'escaped' => !empty($match['oesc'][0]) || !empty($match['cesc1'][0]) || !empty($match['cesc2'][0])
);
}
- }
+ }
+ // Step 2: cluster open/close tag pairs into single entries
$i = count($tags);
while($i--) {
if(!empty($tags[$i]['close'])) {
@@ -337,24 +375,11 @@ class ShortcodeParser extends Object {
if($tags) {
$node->nodeValue = $this->replaceTagsWithText($node->nodeValue, $tags,
- function($idx, $tag) use ($parser, $extra){
- $content = $parser->callShortcode($tag['open'], $tag['attrs'], $tag['content'], $extra);
-
- if ($content === false) {
- if(ShortcodeParser::$error_behavior == ShortcodeParser::ERROR) {
- user_error('Unknown shortcode tag '.$tag['open'], E_USER_ERRROR);
- }
- else if(ShortcodeParser::$error_behavior == ShortcodeParser::STRIP) {
- return '';
- }
- else {
- return $tag['text'];
- }
+ function($idx, $tag) use ($parser, $extra) {
+ return $parser->getShortcodeReplacementText($tag, $extra, false);
}
-
- return $content;
- });
- }
+ );
+ }
}
}
@@ -365,17 +390,17 @@ class ShortcodeParser extends Object {
*/
protected function replaceElementTagsWithMarkers($content) {
$tags = $this->extractTags($content);
-
+
if($tags) {
$markerClass = self::$marker_class;
$content = $this->replaceTagsWithText($content, $tags, function($idx, $tag) use ($markerClass) {
return '';
});
- }
+ }
return array($content, $tags);
- }
+ }
protected function findParentsForMarkers($nodes) {
$parents = array();
@@ -477,23 +502,7 @@ class ShortcodeParser extends Object {
* @param array $tag
*/
protected function replaceMarkerWithContent($node, $tag) {
- $content = false;
- if($tag['open']) $content = $this->callShortcode($tag['open'], $tag['attrs'], $tag['content']);
-
- if ($content === false) {
- if(self::$error_behavior == self::ERROR) {
- user_error('Unknown shortcode tag '.$tag['open'], E_USER_ERRROR);
- }
- if (self::$error_behavior == self::WARN) {
- $content = ''.$tag['text'].'';
- }
- else if (self::$error_behavior == self::LEAVE) {
- $content = $tag['text'];
- }
- else {
- // self::$error_behavior == self::STRIP - NOP
- }
- }
+ $content = $this->getShortcodeReplacementText($tag);
if ($content) {
$parsed = Injector::inst()->create('HTMLValue', $content);
@@ -567,8 +576,21 @@ class ShortcodeParser extends Object {
$this->replaceMarkerWithContent($shortcode, $tag);
}
- return $htmlvalue->getContent();
+ $content = $htmlvalue->getContent();
+
+ // Clean up any marker classes left over, for example, those injected into ',
+ $this->parser->parse('')
+ );
+ }
+
+ public function testNumericShortcodes() {
+ $this->assertEqualsIgnoringWhitespace(
+ '[2]',
+ $this->parser->parse('[2]')
+ );
+ $this->assertEqualsIgnoringWhitespace(
+ '',
+ $this->parser->parse('')
+ );
+
+ $this->parser->register('2', function($attributes, $content, $this, $tag, $extra) {
+ return 'this is 2';
+ });
+
+ $this->assertEqualsIgnoringWhitespace(
+ 'this is 2',
+ $this->parser->parse('[2]')
+ );
+ $this->assertEqualsIgnoringWhitespace(
+ '',
+ $this->parser->parse('')
+ );
+ }
+
public function testExtraContext() {
$this->parser->parse('Test');