diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index 50536dc1a5..0c718757b7 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -2163,4 +2163,22 @@ class BBCode return $ret; } + + /** + * Perform a custom function on a text after having escaped blocks enclosed in the provided tag list. + * + * @param string $text + * @param array $tagList A list of tag names, e.g ['noparse', 'nobb', 'pre'] + * @param callable $callback + * @return string + * @throws Exception + *@see Strings::performWithEscapedBlocks + * + */ + public static function performWithEscapedTags(string $text, array $tagList, callable $callback) + { + $tagList = array_map('preg_quote', $tagList); + + return Strings::performWithEscapedBlocks($text, '#\[(?:' . implode('|', $tagList) . ').*?\[/(?:' . implode('|', $tagList) . ')]#ism', $callback); + } } diff --git a/src/Util/Strings.php b/src/Util/Strings.php index 04d676ef57..35e7ebe151 100644 --- a/src/Util/Strings.php +++ b/src/Util/Strings.php @@ -472,4 +472,52 @@ class Strings return mb_substr($string, 0, $start) . $replacement . mb_substr($string, $start + $length, $string_length - $start - $length); } + + /** + * Perform a custom function on a text after having escaped blocks matched by the provided regular expressions. + * Only full matches are used, capturing group are ignored. + * + * To change the provided text, the callback function needs to return it and this function will return the modified + * version as well after having restored the escaped blocks. + * + * @param string $text + * @param string $regex + * @param callable $callback + * @return string + * @throws \Exception + */ + public static function performWithEscapedBlocks(string $text, string $regex, callable $callback) + { + // Enables nested use + $executionId = random_int(PHP_INT_MAX / 10, PHP_INT_MAX); + + $blocks = []; + + $text = preg_replace_callback($regex, + function ($matches) use ($executionId, &$blocks) { + $return = '«block-' . $executionId . '-' . count($blocks) . '»'; + + $blocks[] = $matches[0]; + + return $return; + }, + $text + ); + + $text = $callback($text) ?? ''; + + // Restore code blocks + $text = preg_replace_callback('/«block-' . $executionId . '-([0-9]+)»/iU', + function ($matches) use ($blocks) { + $return = $matches[0]; + if (isset($blocks[intval($matches[1])])) { + $return = $blocks[$matches[1]]; + } + return $return; + }, + $text + ); + + return $text; + } } diff --git a/tests/src/Util/StringsTest.php b/tests/src/Util/StringsTest.php index 66fb2f0f53..611d72fe7c 100644 --- a/tests/src/Util/StringsTest.php +++ b/tests/src/Util/StringsTest.php @@ -194,4 +194,30 @@ class StringsTest extends TestCase ) ); } + + public function testPerformWithEscapedBlocks() + { + $originalText = '[noparse][/noparse][nobb]nobb[/nobb][noparse]noparse[/noparse]'; + + $text = Strings::performWithEscapedBlocks($originalText, '#[(?:noparse|nobb)].*?\[/(?:noparse|nobb)]#is', function ($text) { + return $text; + }); + + $this->assertEquals($originalText, $text); + } + + public function testPerformWithEscapedBlocksNested() + { + $originalText = '[noparse][/noparse][nobb]nobb[/nobb][noparse]noparse[/noparse]'; + + $text = Strings::performWithEscapedBlocks($originalText, '#[nobb].*?\[/nobb]#is', function ($text) { + $text = Strings::performWithEscapedBlocks($text, '#[noparse].*?\[/noparse]#is', function ($text) { + return $text; + }); + + return $text; + }); + + $this->assertEquals($originalText, $text); + } }