aboutsummaryrefslogtreecommitdiffstats
path: root/tests/text_formatter
diff options
context:
space:
mode:
Diffstat (limited to 'tests/text_formatter')
-rw-r--r--tests/text_formatter/s9e/default_formatting_test.php268
-rw-r--r--tests/text_formatter/s9e/factory_test.php241
-rw-r--r--tests/text_formatter/s9e/fixtures/default_formatting.xml466
-rw-r--r--tests/text_formatter/s9e/fixtures/default_lang.xml20
-rw-r--r--tests/text_formatter/s9e/fixtures/factory.xml115
-rw-r--r--tests/text_formatter/s9e/fixtures/inttext_token.xml27
-rw-r--r--tests/text_formatter/s9e/fixtures/local_url.xml28
-rw-r--r--tests/text_formatter/s9e/fixtures/preserve_comments.xml28
-rw-r--r--tests/text_formatter/s9e/fixtures/smilies_special_chars.xml23
-rw-r--r--tests/text_formatter/s9e/fixtures/style_inheritance.xml66
-rw-r--r--tests/text_formatter/s9e/fixtures/styles.xml36
-rw-r--r--tests/text_formatter/s9e/fixtures/styles/bar/template/bbcode.html40
-rw-r--r--tests/text_formatter/s9e/fixtures/styles/barplus/template/bbcode.html40
-rw-r--r--tests/text_formatter/s9e/fixtures/styles/foo/template/bbcode.html40
-rw-r--r--tests/text_formatter/s9e/fixtures/unsafe_bbcode.xml28
-rw-r--r--tests/text_formatter/s9e/parser_test.php260
-rw-r--r--tests/text_formatter/s9e/renderer_test.php483
-rw-r--r--tests/text_formatter/s9e/utils_test.php270
18 files changed, 2479 insertions, 0 deletions
diff --git a/tests/text_formatter/s9e/default_formatting_test.php b/tests/text_formatter/s9e/default_formatting_test.php
new file mode 100644
index 0000000000..c67976301f
--- /dev/null
+++ b/tests/text_formatter/s9e/default_formatting_test.php
@@ -0,0 +1,268 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+require_once __DIR__ . '/../../../phpBB/includes/functions.php';
+require_once __DIR__ . '/../../../phpBB/includes/functions_content.php';
+
+class phpbb_textformatter_s9e_default_formatting_test extends phpbb_test_case
+{
+ public function test_bbcode_code_lang_is_saved()
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $parser = $container->get('text_formatter.parser');
+
+ $original = '[code]...[/code][code=php]...[/code]';
+ $expected = '<r><CODE><s>[code]</s>...<e>[/code]</e></CODE><CODE lang="php"><s>[code=php]</s>...<e>[/code]</e></CODE></r>';
+
+ $this->assertXmlStringEqualsXmlString($expected, $parser->parse($original));
+ }
+
+ /**
+ * @dataProvider get_default_formatting_tests
+ */
+ public function test_default_formatting($original, $expected)
+ {
+ $fixture = __DIR__ . '/fixtures/default_formatting.xml';
+ $container = $this->get_test_case_helpers()->set_s9e_services(null, $fixture);
+
+ $parser = $container->get('text_formatter.parser');
+ $renderer = $container->get('text_formatter.renderer');
+
+ $parsed_text = $parser->parse($original);
+
+ $this->assertSame($expected, $renderer->render($parsed_text));
+ }
+
+ public function get_default_formatting_tests()
+ {
+ return array(
+ array(
+ '[b]bold[/b]',
+ '<span style="font-weight: bold">bold</span>'
+ ),
+ array(
+ '[u]underlined[/u]',
+ '<span style="text-decoration: underline">underlined</span>'
+ ),
+ array(
+ '[i]italic[/i]',
+ '<span style="font-style: italic">italic</span>'
+ ),
+ array(
+ '[color=#FF0000]colored[/color]',
+ '<span style="color: #FF0000">colored</span>'
+ ),
+ array(
+ '[color=red]colored[/color]',
+ '<span style="color: red">colored</span>'
+ ),
+ array(
+ '[size=75]smaller[/size]',
+ '<span style="font-size: 75%; line-height: normal">smaller</span>'
+ ),
+ array(
+ '[quote]quoted[/quote]',
+ '<blockquote class="uncited"><div>quoted</div></blockquote>'
+ ),
+ array(
+ '[quote="username"]quoted[/quote]',
+ '<blockquote><div><cite>username wrote:</cite>quoted</div></blockquote>'
+ ),
+ array(
+ '[code]unparsed code[/code]',
+ '<div class="codebox"><p>CODE: <a href="#" onclick="selectCode(this); return false;">Select all</a></p><pre><code>unparsed code</code></pre></div>'
+ ),
+ array(
+ '[list]no item[/list]',
+ '<ul>no item</ul>'
+ ),
+ array(
+ '[*]unparsed',
+ '[*]unparsed'
+ ),
+ array(
+ '[list][*]item[/list]',
+ '<ul><li>item</li></ul>'
+ ),
+ array(
+ '[list][*]item[/*][/list]',
+ '<ul><li>item</li></ul>'
+ ),
+ array(
+ '[list=1][*]item[/list]',
+ '<ol style="list-style-type: decimal"><li>item</li></ol>'
+ ),
+ array(
+ '[list=a][*]item[/list]',
+ '<ol style="list-style-type: lower-alpha"><li>item</li></ol>'
+ ),
+ array(
+ '[list=i][*]item[/list]',
+ '<ol style="list-style-type: lower-roman"><li>item</li></ol>'
+ ),
+ array(
+ '[list=I][*]item[/list]',
+ '<ol style="list-style-type: upper-roman"><li>item</li></ol>'
+ ),
+ array(
+ '[list=disc][*]item[/list]',
+ '<ul style="list-style-type: disc"><li>item</li></ul>'
+ ),
+ array(
+ '[list=circle][*]item[/list]',
+ '<ul style="list-style-type: circle"><li>item</li></ul>'
+ ),
+ array(
+ '[list=square][*]item[/list]',
+ '<ul style="list-style-type: square"><li>item</li></ul>'
+ ),
+ array(
+ '[img]https://area51.phpbb.com/images/area51.png[/img]',
+ '<img src="https://area51.phpbb.com/images/area51.png" alt="Image">'
+ ),
+ array(
+ '[url]https://area51.phpbb.com/[/url]',
+ '<a href="https://area51.phpbb.com/" class="postlink">https://area51.phpbb.com/</a>'
+ ),
+ array(
+ '[url=https://area51.phpbb.com/]Area51[/url]',
+ '<a href="https://area51.phpbb.com/" class="postlink">Area51</a>'
+ ),
+ array(
+ '[email]bbcode-test@phpbb.com[/email]',
+ '<a href="mailto:bbcode-test@phpbb.com">bbcode-test@phpbb.com</a>'
+ ),
+ array(
+ '[email=bbcode-test@phpbb.com]Email[/email]',
+ '<a href="mailto:bbcode-test@phpbb.com">Email</a>'
+ ),
+ array(
+ '[attachment=0]filename[/attachment]',
+ '<div class="inline-attachment"><!-- ia0 -->filename<!-- ia0 --></div>'
+ ),
+ array(
+ // PHPBB3-1401 - correct: parsed
+ '[quote="[test]test"]test [ test[/quote]',
+ '<blockquote><div><cite>[test]test wrote:</cite>test [ test</div></blockquote>'
+ ),
+ array(
+ // PHPBB3-6117 - correct: parsed
+ '[quote]test[/quote] test ] and [ test [quote]test[/quote]',
+ '<blockquote class="uncited"><div>test</div></blockquote> test ] and [ test <blockquote class="uncited"><div>test</div></blockquote>'
+ ),
+ array(
+ // PHPBB3-6200 - correct: parsed
+ '[quote="["]test[/quote]',
+ '<blockquote><div><cite>[ wrote:</cite>test</div></blockquote>'
+ ),
+ array(
+ // PHPBB3-9364 - quoted: "test[/[/b]quote] test" / non-quoted: "[/quote] test" - also failed if layout distorted
+ '[quote]test[/[/b]quote] test [/quote][/quote] test',
+ '<blockquote class="uncited"><div>test[/[/b]quote] test </div></blockquote>[/quote] test'
+ ),
+ array(
+ // PHPBB3-8096 - first quote tag parsed, second quote tag unparsed
+ '[quote="a"]a[/quote][quote="a]a[/quote]',
+ '<blockquote><div><cite>a wrote:</cite>a</div></blockquote>[quote="a]a[/quote]'
+ ),
+ array(
+ // Allow textual bbcodes in textual bbcodes
+ '[b]bold [i]bold + italic[/i][/b]',
+ '<span style="font-weight: bold">bold <span style="font-style: italic">bold + italic</span></span>'
+ ),
+ array(
+ // Allow textual bbcodes in url with description
+ '[url=https://area51.phpbb.com/]Area51 [i]italic[/i][/url]',
+ '<a href="https://area51.phpbb.com/" class="postlink">Area51 <span style="font-style: italic">italic</span></a>'
+ ),
+ array(
+ // Allow url with description in textual bbcodes
+ '[i]italic [url=https://area51.phpbb.com/]Area51[/url][/i]',
+ '<span style="font-style: italic">italic <a href="https://area51.phpbb.com/" class="postlink">Area51</a></span>'
+ ),
+ array(
+ // Do not parse textual bbcodes in code
+ '[code]unparsed code [b]bold [i]bold + italic[/i][/b][/code]',
+ '<div class="codebox"><p>CODE: <a href="#" onclick="selectCode(this); return false;">Select all</a></p><pre><code>unparsed code [b]bold [i]bold + italic[/i][/b]</code></pre></div>'
+ ),
+ array(
+ // Do not parse quote bbcodes in code
+ '[code]unparsed code [quote="username"]quoted[/quote][/code]',
+ '<div class="codebox"><p>CODE: <a href="#" onclick="selectCode(this); return false;">Select all</a></p><pre><code>unparsed code [quote="username"]quoted[/quote]</code></pre></div>'
+ ),
+ array(
+ // Textual bbcode nesting into textual bbcode
+ '[b]bold [i]bold + italic[/b] italic[/i]',
+ '<span style="font-weight: bold">bold <span style="font-style: italic">bold + italic</span></span><span style="font-style: italic"> italic</span>'
+ ),
+ array(
+ "[code]\tline1\n line2[/code]",
+ '<div class="codebox"><p>CODE: <a href="#" onclick="selectCode(this); return false;">Select all</a></p><pre><code>' . "\tline1\n line2</code></pre></div>"
+ ),
+ array(
+ "[code]\n\tline1\n line2[/code]",
+ '<div class="codebox"><p>CODE: <a href="#" onclick="selectCode(this); return false;">Select all</a></p><pre><code>' . "\tline1\n line2</code></pre></div>"
+ ),
+ array(
+ '... http://example.org ...',
+ '... <a href="http://example.org" class="postlink">http://example.org</a> ...'
+ ),
+ array(
+ '... www.example.org ...',
+ '... <a href="http://www.example.org" class="postlink">www.example.org</a> ...'
+ ),
+ array(
+ '[quote="[url=http://example.org]xxx[/url]"]...[/quote]',
+ '<blockquote><div><cite><a href="http://example.org" class="postlink">xxx</a> wrote:</cite>...</div></blockquote>'
+ ),
+ array(
+ '[quote="[url]http://example.org[/url]"]...[/quote]',
+ '<blockquote><div><cite><a href="http://example.org" class="postlink">http://example.org</a> wrote:</cite>...</div></blockquote>'
+ ),
+ array(
+ '[quote=http://example.org]...[/quote]',
+ '<blockquote><div><cite><a href="http://example.org" class="postlink">http://example.org</a> wrote:</cite>...</div></blockquote>'
+ ),
+ array(
+ "[quote]\nThis is a long quote that is definitely going to exceed 80 characters\n[/quote]\n\nFollowed by a reply",
+ "<blockquote class=\"uncited\"><div>\nThis is a long quote that is definitely going to exceed 80 characters\n</div></blockquote>\n\nFollowed by a reply"
+ ),
+ array(
+ '[quote=Username post_id=123]...[/quote]',
+ '<blockquote><div><cite>Username wrote: <a href="phpBB/viewtopic.php?p=123#p123" data-post-id="123" onclick="if(document.getElementById(hash.substr(1)))href=hash">↑</a></cite>...</div></blockquote>'
+ ),
+ array(
+ // Users are not allowed to submit their own URL for the post
+ '[quote="Username" post_url="http://fake.example.org"]...[/quote]',
+ '<blockquote><div><cite>Username wrote:</cite>...</div></blockquote>'
+ ),
+ array(
+ '[quote=Username time=58705871]...[/quote]',
+ '<blockquote><div><cite>Username wrote:<div class="responsive-hide">1971-11-11 11:11:11</div></cite>...</div></blockquote>'
+ ),
+ array(
+ '[quote=Username user_id=123]...[/quote]',
+ '<blockquote><div><cite><a href="phpBB/memberlist.php?mode=viewprofile&amp;u=123">Username</a> wrote:</cite>...</div></blockquote>'
+ ),
+ array(
+ // Users are not allowed to submit their own URL for the profile
+ '[quote=Username profile_url=http://fake.example.org]...[/quote]',
+ '<blockquote><div><cite>Username wrote:</cite>...</div></blockquote>'
+ ),
+ array(
+ // From phpbb_textformatter_s9e_utils_test::test_generate_quote()
+ '[quote=\'[quote="foo"]\']...[/quote]',
+ '<blockquote><div><cite>[quote="foo"] wrote:</cite>...</div></blockquote>'
+ ),
+ );
+ }
+}
diff --git a/tests/text_formatter/s9e/factory_test.php b/tests/text_formatter/s9e/factory_test.php
new file mode 100644
index 0000000000..8382097544
--- /dev/null
+++ b/tests/text_formatter/s9e/factory_test.php
@@ -0,0 +1,241 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+require_once __DIR__ . '/../../../phpBB/includes/functions.php';
+require_once __DIR__ . '/../../../phpBB/includes/functions_content.php';
+require_once __DIR__ . '/../../test_framework/phpbb_database_test_case.php';
+
+class phpbb_textformatter_s9e_factory_test extends phpbb_database_test_case
+{
+ public function setUp()
+ {
+ $this->cache = new phpbb_mock_cache;
+ $this->dispatcher = new phpbb_mock_event_dispatcher;
+ parent::setUp();
+ }
+
+ public function getDataSet()
+ {
+ return $this->createXMLDataSet(__DIR__ . '/fixtures/factory.xml');
+ }
+
+ public function get_cache_dir()
+ {
+ return __DIR__ . '/../../tmp/';
+ }
+
+ public function get_factory()
+ {
+ global $phpbb_root_path;
+ $this->cache = new phpbb_mock_cache;
+ $dal = new \phpbb\textformatter\data_access(
+ $this->new_dbal(),
+ 'phpbb_bbcodes',
+ 'phpbb_smilies',
+ 'phpbb_styles',
+ 'phpbb_words',
+ $phpbb_root_path . 'styles/'
+ );
+ $factory = new \phpbb\textformatter\s9e\factory(
+ $dal,
+ $this->cache,
+ $this->dispatcher,
+ $this->get_cache_dir(),
+ '_foo_parser',
+ '_foo_renderer'
+ );
+
+ return $factory;
+ }
+
+ public function test_get_configurator()
+ {
+ $configurator = $this->get_factory()->get_configurator();
+
+ $this->assertInstanceOf('s9e\\TextFormatter\\Configurator', $configurator);
+
+ $this->assertTrue(isset($configurator->plugins['Autoemail']));
+ $this->assertTrue(isset($configurator->plugins['Autolink']));
+
+ $this->assertTrue(isset($configurator->BBCodes['B']));
+ $this->assertTrue(isset($configurator->BBCodes['CODE']));
+ $this->assertTrue(isset($configurator->BBCodes['COLOR']));
+ $this->assertTrue(isset($configurator->BBCodes['EMAIL']));
+ $this->assertTrue(isset($configurator->BBCodes['FLASH']));
+ $this->assertTrue(isset($configurator->BBCodes['I']));
+ $this->assertTrue(isset($configurator->BBCodes['IMG']));
+ $this->assertTrue(isset($configurator->BBCodes['LIST']));
+ $this->assertTrue(isset($configurator->BBCodes['*']));
+ $this->assertTrue(isset($configurator->BBCodes['QUOTE']));
+ $this->assertTrue(isset($configurator->BBCodes['SIZE']));
+ $this->assertTrue(isset($configurator->BBCodes['U']));
+ $this->assertTrue(isset($configurator->BBCodes['URL']));
+
+ // This custom BBCode should be set
+ $this->assertTrue(isset($configurator->BBCodes['CUSTOM']));
+
+ $this->assertTrue(isset($configurator->Emoticons[':D']));
+ }
+
+ public function test_regenerate()
+ {
+ extract($this->get_factory()->regenerate());
+
+ $this->assertInstanceOf('s9e\\TextFormatter\\Parser', $parser);
+ $this->assertInstanceOf('s9e\\TextFormatter\\Renderer', $renderer);
+
+ $renderer_data = $this->cache->get('_foo_renderer');
+ $this->assertEquals($parser, $this->cache->get('_foo_parser'), 'The parser was not cached');
+ $this->assertEquals(get_class($renderer), $renderer_data['class']);
+ $this->assertInstanceOf('s9e\\TextFormatter\\Plugins\\Censor\\Helper', $renderer_data['censor']);
+
+ $file = $this->get_cache_dir() . get_class($renderer) . '.php';
+ $this->assertFileExists($file);
+ unlink($file);
+ }
+
+ public function test_tidy()
+ {
+ $factory = $this->get_factory();
+
+ // Create a fake "old" cache file
+ $old_file = $this->get_cache_dir() . 's9e_foo.php';
+ touch($old_file);
+
+ // Create a current renderer
+ extract($factory->regenerate());
+ $new_file = $this->get_cache_dir() . get_class($renderer) . '.php';
+
+ // Tidy the cache
+ $factory->tidy();
+
+ $this->assertFileExists($new_file, 'The current renderer has been deleted');
+ $this->assertFileNotExists($old_file, 'The old renderer has not been deleted');
+
+ unlink($new_file);
+ }
+
+ public function test_local_url()
+ {
+ global $config, $user, $request;
+ $config = array(
+ 'force_server_vars' => true,
+ 'server_protocol' => 'http://',
+ 'server_name' => 'path',
+ 'server_port' => 80,
+ 'script_path' => '/to',
+ 'cookie_secure' => false
+ );
+ $user = new phpbb_mock_user;
+ $request = new phpbb_mock_request;
+
+ $fixture = __DIR__ . '/fixtures/local_url.xml';
+ $renderer = $this->get_test_case_helpers()->set_s9e_services(null, $fixture)->get('text_formatter.renderer');
+
+ $this->assertSame(
+ '<a href="http://path/to/foo">http://path/to/foo</a>',
+ $renderer->render('<r><LOCAL content="foo"><s>[local]</s>foo<e>[/local]</e></LOCAL></r>')
+ );
+ }
+
+ public function test_smilies_special_chars()
+ {
+ // Use a smiley that contains every special chars in every field
+ $fixture = __DIR__ . '/fixtures/smilies_special_chars.xml';
+ $renderer = $this->get_test_case_helpers()->set_s9e_services(null, $fixture)->get('text_formatter.renderer');
+
+ $this->assertSame(
+ '<img class="smilies" src="phpBB/images/smilies/%22%27%3C&amp;%3E.png" alt="&quot;\'&lt;&amp;&gt;" title="&quot;\'&lt;&amp;&gt;">',
+ $renderer->render('<r><E>"\'&lt;&amp;&gt;</E></r>')
+ );
+ }
+
+ /**
+ * @testdox {INTTEXT} is supported in custom BBCodes
+ */
+ public function test_inttext_token()
+ {
+ $fixture = __DIR__ . '/fixtures/inttext_token.xml';
+ $container = $this->get_test_case_helpers()->set_s9e_services(null, $fixture);
+ $parser = $container->get('text_formatter.parser');
+ $renderer = $container->get('text_formatter.renderer');
+
+ $original = '[spoiler=ɎɆS]text[/spoiler]';
+ $expected = '<div class="spoiler"><div class="title">ɎɆS</div><div class="content">text</div></div>';
+ $this->assertSame($expected, $renderer->render($parser->parse($original)));
+
+ $original = '[spoiler=N:O:P:E]text[/spoiler]';
+ $expected = $original;
+ $this->assertSame($expected, $renderer->render($parser->parse($original)));
+ }
+
+ /**
+ * @testdox Preserves comments in custom BBCodes
+ */
+ public function test_preserve_comments()
+ {
+ $fixture = __DIR__ . '/fixtures/preserve_comments.xml';
+ $container = $this->get_test_case_helpers()->set_s9e_services(null, $fixture);
+ $parser = $container->get('text_formatter.parser');
+ $renderer = $container->get('text_formatter.renderer');
+
+ $original = '[X]';
+ $expected = '<!-- comment -->';
+ $this->assertSame($expected, $renderer->render($parser->parse($original)));
+ }
+
+ /**
+ * @testdox Accepts unsafe custom BBCodes
+ */
+ public function test_unsafe_bbcode()
+ {
+ $fixture = __DIR__ . '/fixtures/unsafe_bbcode.xml';
+ $container = $this->get_test_case_helpers()->set_s9e_services(null, $fixture);
+ $parser = $container->get('text_formatter.parser');
+ $renderer = $container->get('text_formatter.renderer');
+
+ $original = '[xss=javascript:alert(1)]text[/xss]';
+ $expected = '<a href="javascript:alert(1)">text</a>';
+ $this->assertSame($expected, $renderer->render($parser->parse($original)));
+ }
+
+ /**
+ * @testdox get_configurator() triggers events before and after configuration
+ */
+ public function test_configure_events()
+ {
+ $this->dispatcher = $this->getMock('phpbb\\event\\dispatcher_interface');
+ $this->dispatcher
+ ->expects($this->at(0))
+ ->method('trigger_event')
+ ->with(
+ 'core.text_formatter_s9e_configure_before',
+ $this->callback(array($this, 'configure_event_callback'))
+ )
+ ->will($this->returnArgument(1));
+ $this->dispatcher
+ ->expects($this->at(1))
+ ->method('trigger_event')
+ ->with(
+ 'core.text_formatter_s9e_configure_after',
+ $this->callback(array($this, 'configure_event_callback'))
+ )
+ ->will($this->returnArgument(1));
+
+ $this->get_factory()->get_configurator();
+ }
+
+ public function configure_event_callback($vars)
+ {
+ return isset($vars['configurator']) && $vars['configurator'] instanceof \s9e\TextFormatter\Configurator;
+ }
+}
diff --git a/tests/text_formatter/s9e/fixtures/default_formatting.xml b/tests/text_formatter/s9e/fixtures/default_formatting.xml
new file mode 100644
index 0000000000..2b7236fb30
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/default_formatting.xml
@@ -0,0 +1,466 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_smilies">
+ <column>smiley_id</column>
+ <column>code</column>
+ <column>emotion</column>
+ <column>smiley_url</column>
+ <column>smiley_width</column>
+ <column>smiley_height</column>
+ <column>smiley_order</column>
+ <column>display_on_posting</column>
+ <row>
+ <value>1</value>
+ <value>:D</value>
+ <value>Very Happy</value>
+ <value>icon_e_biggrin.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>1</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>2</value>
+ <value>:-D</value>
+ <value>Very Happy</value>
+ <value>icon_e_biggrin.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>2</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>3</value>
+ <value>:grin:</value>
+ <value>Very Happy</value>
+ <value>icon_e_biggrin.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>3</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>4</value>
+ <value>:)</value>
+ <value>Smile</value>
+ <value>icon_e_smile.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>4</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>5</value>
+ <value>:-)</value>
+ <value>Smile</value>
+ <value>icon_e_smile.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>5</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>6</value>
+ <value>:smile:</value>
+ <value>Smile</value>
+ <value>icon_e_smile.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>6</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>7</value>
+ <value>;)</value>
+ <value>Wink</value>
+ <value>icon_e_wink.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>7</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>8</value>
+ <value>;-)</value>
+ <value>Wink</value>
+ <value>icon_e_wink.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>8</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>9</value>
+ <value>:wink:</value>
+ <value>Wink</value>
+ <value>icon_e_wink.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>9</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>10</value>
+ <value>:(</value>
+ <value>Sad</value>
+ <value>icon_e_sad.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>10</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>11</value>
+ <value>:-(</value>
+ <value>Sad</value>
+ <value>icon_e_sad.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>11</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>12</value>
+ <value>:sad:</value>
+ <value>Sad</value>
+ <value>icon_e_sad.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>12</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>13</value>
+ <value>:o</value>
+ <value>Surprised</value>
+ <value>icon_e_surprised.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>13</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>14</value>
+ <value>:-o</value>
+ <value>Surprised</value>
+ <value>icon_e_surprised.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>14</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>15</value>
+ <value>:eek:</value>
+ <value>Surprised</value>
+ <value>icon_e_surprised.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>15</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>16</value>
+ <value>:shock:</value>
+ <value>Shocked</value>
+ <value>icon_eek.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>16</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>17</value>
+ <value>:?</value>
+ <value>Confused</value>
+ <value>icon_e_confused.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>17</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>18</value>
+ <value>:-?</value>
+ <value>Confused</value>
+ <value>icon_e_confused.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>18</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>19</value>
+ <value>:???:</value>
+ <value>Confused</value>
+ <value>icon_e_confused.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>19</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>20</value>
+ <value>8-)</value>
+ <value>Cool</value>
+ <value>icon_cool.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>20</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>21</value>
+ <value>:cool:</value>
+ <value>Cool</value>
+ <value>icon_cool.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>21</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>22</value>
+ <value>:lol:</value>
+ <value>Laughing</value>
+ <value>icon_lol.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>22</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>23</value>
+ <value>:x</value>
+ <value>Mad</value>
+ <value>icon_mad.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>23</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>24</value>
+ <value>:-x</value>
+ <value>Mad</value>
+ <value>icon_mad.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>24</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>25</value>
+ <value>:mad:</value>
+ <value>Mad</value>
+ <value>icon_mad.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>25</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>26</value>
+ <value>:P</value>
+ <value>Razz</value>
+ <value>icon_razz.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>26</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>27</value>
+ <value>:-P</value>
+ <value>Razz</value>
+ <value>icon_razz.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>27</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>28</value>
+ <value>:razz:</value>
+ <value>Razz</value>
+ <value>icon_razz.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>28</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>29</value>
+ <value>:oops:</value>
+ <value>Embarrassed</value>
+ <value>icon_redface.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>29</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>30</value>
+ <value>:cry:</value>
+ <value>Crying or Very Sad</value>
+ <value>icon_cry.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>30</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>31</value>
+ <value>:evil:</value>
+ <value>Evil or Very Mad</value>
+ <value>icon_evil.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>31</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>32</value>
+ <value>:twisted:</value>
+ <value>Twisted Evil</value>
+ <value>icon_twisted.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>32</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>33</value>
+ <value>:roll:</value>
+ <value>Rolling Eyes</value>
+ <value>icon_rolleyes.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>33</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>34</value>
+ <value>:!:</value>
+ <value>Exclamation</value>
+ <value>icon_exclaim.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>34</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>35</value>
+ <value>:?:</value>
+ <value>Question</value>
+ <value>icon_question.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>35</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>36</value>
+ <value>:idea:</value>
+ <value>Idea</value>
+ <value>icon_idea.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>36</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>37</value>
+ <value>:arrow:</value>
+ <value>Arrow</value>
+ <value>icon_arrow.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>37</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>38</value>
+ <value>:|</value>
+ <value>Neutral</value>
+ <value>icon_neutral.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>38</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>39</value>
+ <value>:-|</value>
+ <value>Neutral</value>
+ <value>icon_neutral.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>39</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>40</value>
+ <value>:mrgreen:</value>
+ <value>Mr. Green</value>
+ <value>icon_mrgreen.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>40</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>41</value>
+ <value>:geek:</value>
+ <value>Geek</value>
+ <value>icon_e_geek.gif</value>
+ <value>17</value>
+ <value>17</value>
+ <value>41</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>42</value>
+ <value>:ugeek:</value>
+ <value>Uber Geek</value>
+ <value>icon_e_ugeek.gif</value>
+ <value>17</value>
+ <value>18</value>
+ <value>42</value>
+ <value>1</value>
+ </row>
+ </table>
+
+ <table name="phpbb_styles">
+ <column>style_id</column>
+ <column>style_name</column>
+ <column>style_copyright</column>
+ <column>style_active</column>
+ <column>style_path</column>
+ <column>bbcode_bitfield</column>
+ <column>style_parent_id</column>
+ <column>style_parent_tree</column>
+ <row>
+ <value>1</value>
+ <value>prosilver</value>
+ <value>&amp;copy; phpBB Group</value>
+ <value>1</value>
+ <value>prosilver</value>
+ <value>kNg=</value>
+ <value>0</value>
+ <value></value>
+ </row>
+ </table>
+
+ <table name="phpbb_words">
+ <column>word_id</column>
+ <column>word</column>
+ <column>replacement</column>
+
+ <row>
+ <value>1</value>
+ <value>apple</value>
+ <value>banana</value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/fixtures/default_lang.xml b/tests/text_formatter/s9e/fixtures/default_lang.xml
new file mode 100644
index 0000000000..2cfde4aab2
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/default_lang.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_bbcodes">
+ <column>bbcode_id</column>
+ <column>bbcode_tag</column>
+ <column>bbcode_helpline</column>
+ <column>display_on_posting</column>
+ <column>bbcode_match</column>
+ <column>bbcode_tpl</column>
+
+ <row>
+ <value>13</value>
+ <value>foo</value>
+ <value></value>
+ <value>1</value>
+ <value>[foo]{TEXT}[/foo]</value>
+ <value>{L_FOO_BAR}</value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/fixtures/factory.xml b/tests/text_formatter/s9e/fixtures/factory.xml
new file mode 100644
index 0000000000..9ae52e9747
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/factory.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_bbcodes">
+ <column>bbcode_id</column>
+ <column>bbcode_tag</column>
+ <column>bbcode_helpline</column>
+ <column>display_on_posting</column>
+ <column>bbcode_match</column>
+ <column>bbcode_tpl</column>
+ <column>first_pass_match</column>
+ <column>first_pass_replace</column>
+ <column>second_pass_match</column>
+ <column>second_pass_replace</column>
+
+ <row>
+ <value>13</value>
+ <value>custom</value>
+ <value></value>
+ <value>1</value>
+ <value>[custom]{TEXT}[/custom]</value>
+ <value>&lt;span style=&quot;color:red&quot;&gt;{TEXT}&lt;/span&gt;</value>
+ <value>!\[custom\](.*?)\[/custom\]!ies</value>
+ <value>'[custom:$uid]'.str_replace(array(&quot;\r\n&quot;, '\&quot;', '\'', '(', ')'), array(&quot;\n&quot;, '&quot;', '&amp;#39;', '&amp;#40;', '&amp;#41;'), trim('${1}')).'[/custom:$uid]'</value>
+ <value>!\[custom:$uid\](.*?)\[/custom:$uid\]!s</value>
+ <value>&lt;span style=&quot;color:red&quot;&gt;${1}&lt;/span&gt;</value>
+ </row>
+ <row>
+ <value>14</value>
+ <value>unsafe</value>
+ <value></value>
+ <value>1</value>
+ <value>[unsafe]{TEXT}[/unsafe]</value>
+ <value>&lt;script&gt;{TEXT}&lt;/script&gt;</value>
+ <value>!\[unsafe\](.*?)\[/unsafe\]!ies</value>
+ <value>'[unsafe:$uid]'.str_replace(array(&quot;\r\n&quot;, '\&quot;', '\'', '(', ')'), array(&quot;\n&quot;, '&quot;', '&amp;#39;', '&amp;#40;', '&amp;#41;'), trim('${1}')).'[/unsafe:$uid]'</value>
+ <value>!\[unsafe:$uid\](.*?)\[/unsafe:$uid\]!s</value>
+ <value>&lt;script&gt;${1}&lt;/script&gt;</value>
+ </row>
+ </table>
+
+ <table name="phpbb_smilies">
+ <column>smiley_id</column>
+ <column>code</column>
+ <column>emotion</column>
+ <column>smiley_url</column>
+ <column>smiley_width</column>
+ <column>smiley_height</column>
+ <column>smiley_order</column>
+ <column>display_on_posting</column>
+ <row>
+ <value>1</value>
+ <value>:D</value>
+ <value>Very Happy</value>
+ <value>icon_e_biggrin.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>2</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>4</value>
+ <value>:)</value>
+ <value>Smile</value>
+ <value>icon_e_smile.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>4</value>
+ <value>1</value>
+ </row>
+ <row>
+ <value>10</value>
+ <value>:(</value>
+ <value>Sad</value>
+ <value>icon_e_sad.gif</value>
+ <value>15</value>
+ <value>17</value>
+ <value>10</value>
+ <value>1</value>
+ </row>
+ </table>
+
+ <table name="phpbb_styles">
+ <column>style_id</column>
+ <column>style_name</column>
+ <column>style_copyright</column>
+ <column>style_active</column>
+ <column>style_path</column>
+ <column>bbcode_bitfield</column>
+ <column>style_parent_id</column>
+ <column>style_parent_tree</column>
+
+ <row>
+ <value>1</value>
+ <value>prosilver</value>
+ <value>&amp;copy; phpBB Group</value>
+ <value>1</value>
+ <value>prosilver</value>
+ <value>kNg=</value>
+ <value>0</value>
+ <value></value>
+ </row>
+ </table>
+
+ <table name="phpbb_words">
+ <column>word_id</column>
+ <column>word</column>
+ <column>replacement</column>
+
+ <row>
+ <value>1</value>
+ <value>apple</value>
+ <value>banana</value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/fixtures/inttext_token.xml b/tests/text_formatter/s9e/fixtures/inttext_token.xml
new file mode 100644
index 0000000000..30b971f315
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/inttext_token.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_bbcodes">
+ <column>bbcode_id</column>
+ <column>bbcode_tag</column>
+ <column>bbcode_helpline</column>
+ <column>display_on_posting</column>
+ <column>bbcode_match</column>
+ <column>bbcode_tpl</column>
+ <column>first_pass_match</column>
+ <column>first_pass_replace</column>
+ <column>second_pass_match</column>
+ <column>second_pass_replace</column>
+
+ <row>
+ <value>13</value>
+ <value>spoiler=</value>
+ <value></value>
+ <value>1</value>
+ <value>[spoiler={INTTEXT}]{TEXT}[/spoiler]</value>
+ <value><![CDATA[<div class="spoiler"><div class="title">{INTTEXT}</div><div class="content">{TEXT}</div></div>]]></value>
+ <value><![CDATA[!\[spoiler\=([\p{L}\p{N}\-+,_. ]+)\](.*?)\[/spoiler\]!iues]]></value>
+ <value><![CDATA['[spoiler=${1}:$uid]'.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', '&#39;', '&#40;', '&#41;'), trim('${2}')).'[/spoiler:$uid]']]></value>
+ <value><![CDATA[!\[spoiler\=([\p{L}\p{N}\-+,_. ]+):$uid\](.*?)\[/spoiler:$uid\]!su]]></value><value><![CDATA[<div class="spoiler"><div class="title">${1}</div><div class="content">${2}</div></div>]]></value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/fixtures/local_url.xml b/tests/text_formatter/s9e/fixtures/local_url.xml
new file mode 100644
index 0000000000..9db2bf4710
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/local_url.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_bbcodes">
+ <column>bbcode_id</column>
+ <column>bbcode_tag</column>
+ <column>bbcode_helpline</column>
+ <column>display_on_posting</column>
+ <column>bbcode_match</column>
+ <column>bbcode_tpl</column>
+ <column>first_pass_match</column>
+ <column>first_pass_replace</column>
+ <column>second_pass_match</column>
+ <column>second_pass_replace</column>
+
+ <row>
+ <value>13</value>
+ <value>local</value>
+ <value></value>
+ <value>1</value>
+ <value>[local]{LOCAL_URL}[/local]</value>
+ <value><![CDATA[<a href="{LOCAL_URL}">{LOCAL_URL}</a>]]></value>
+ <value><![CDATA[!\[local\]((?:[a-z0-9\-._~\!$&'()*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[a-z0-9\-._~\!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~\!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:#(?:[a-z0-9\-._~\!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?)\[/local\]!ie]]></value>
+ <value><![CDATA['[local:$uid]'.$this->bbcode_specialchars('${1}').'[/local:$uid]']]></value>
+ <value><![CDATA[!\[local:$uid\](?i)((?:[a-z0-9\-._~\!$&'()*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[a-z0-9\-._~\!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~\!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:#(?:[a-z0-9\-._~\!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?)(?-i)\[/local:$uid\]!s]]></value>
+ <value><![CDATA[<a href="http://path/to/phpBB/${1}">http://path/to/phpBB/${1}</a>]]></value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/fixtures/preserve_comments.xml b/tests/text_formatter/s9e/fixtures/preserve_comments.xml
new file mode 100644
index 0000000000..f81d366aad
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/preserve_comments.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_bbcodes">
+ <column>bbcode_id</column>
+ <column>bbcode_tag</column>
+ <column>bbcode_helpline</column>
+ <column>display_on_posting</column>
+ <column>bbcode_match</column>
+ <column>bbcode_tpl</column>
+ <column>first_pass_match</column>
+ <column>first_pass_replace</column>
+ <column>second_pass_match</column>
+ <column>second_pass_replace</column>
+
+ <row>
+ <value>13</value>
+ <value>X</value>
+ <value></value>
+ <value>1</value>
+ <value>[X][/X]</value>
+ <value><![CDATA[<!-- comment -->]]></value>
+ <value><![CDATA[!\[x\]\[/x\]!i]]></value>
+ <value><![CDATA[[x:$uid][/x:$uid]]]></value>
+ <value><![CDATA[[x:$uid][/x:$uid]]]></value>
+ <value></value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/fixtures/smilies_special_chars.xml b/tests/text_formatter/s9e/fixtures/smilies_special_chars.xml
new file mode 100644
index 0000000000..d3a7cfa4f7
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/smilies_special_chars.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_smilies">
+ <column>smiley_id</column>
+ <column>code</column>
+ <column>emotion</column>
+ <column>smiley_url</column>
+ <column>smiley_width</column>
+ <column>smiley_height</column>
+ <column>smiley_order</column>
+ <column>display_on_posting</column>
+ <row>
+ <value>1</value>
+ <value>"'&lt;&amp;&gt;</value>
+ <value>"'&lt;&amp;&gt;</value>
+ <value>"'&lt;&amp;&gt;.png</value>
+ <value>15</value>
+ <value>17</value>
+ <value>2</value>
+ <value>1</value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/fixtures/style_inheritance.xml b/tests/text_formatter/s9e/fixtures/style_inheritance.xml
new file mode 100644
index 0000000000..a692d0ef2d
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/style_inheritance.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_styles">
+ <column>style_id</column>
+ <column>style_name</column>
+ <column>style_copyright</column>
+ <column>style_active</column>
+ <column>style_path</column>
+ <column>bbcode_bitfield</column>
+ <column>style_parent_id</column>
+ <column>style_parent_tree</column>
+
+ <row>
+ <value>1</value>
+ <value>foo</value>
+ <value></value>
+ <value>1</value>
+ <value>foo</value>
+ <!-- Bitfield for "b" only -->
+ <value>QA==</value>
+ <value>0</value>
+ <value></value>
+ </row>
+ <row>
+ <value>2</value>
+ <value>fooplus</value>
+ <value></value>
+ <value>1</value>
+ <value>fooplus</value>
+ <value>QA==</value>
+ <value>1</value>
+ <value></value>
+ </row>
+ <row>
+ <value>3</value>
+ <value>fooplusplus</value>
+ <value></value>
+ <value>1</value>
+ <value>fooplusplus</value>
+ <value>QA==</value>
+ <value>2</value>
+ <value></value>
+ </row>
+ <row>
+ <value>4</value>
+ <value>bar</value>
+ <value></value>
+ <value>1</value>
+ <value>bar</value>
+ <!-- Bitfield for "b" only -->
+ <value>QA==</value>
+ <value>0</value>
+ <value></value>
+ </row>
+ <row>
+ <value>5</value>
+ <value>barplus</value>
+ <value></value>
+ <value>1</value>
+ <value>barplus</value>
+ <value>QA==</value>
+ <value>4</value>
+ <value></value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/fixtures/styles.xml b/tests/text_formatter/s9e/fixtures/styles.xml
new file mode 100644
index 0000000000..8004412aea
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/styles.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_styles">
+ <column>style_id</column>
+ <column>style_name</column>
+ <column>style_copyright</column>
+ <column>style_active</column>
+ <column>style_path</column>
+ <column>bbcode_bitfield</column>
+ <column>style_parent_id</column>
+ <column>style_parent_tree</column>
+
+ <row>
+ <value>1</value>
+ <value>foo</value>
+ <value></value>
+ <value>1</value>
+ <value>foo</value>
+ <!-- Bitfield for "b" only -->
+ <value>QA==</value>
+ <value>0</value>
+ <value></value>
+ </row>
+ <row>
+ <value>2</value>
+ <value>bar</value>
+ <value></value>
+ <value>1</value>
+ <value>bar</value>
+ <!-- Bitfield for "b" only -->
+ <value>QA==</value>
+ <value>0</value>
+ <value></value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/fixtures/styles/bar/template/bbcode.html b/tests/text_formatter/s9e/fixtures/styles/bar/template/bbcode.html
new file mode 100644
index 0000000000..a17446581a
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/styles/bar/template/bbcode.html
@@ -0,0 +1,40 @@
+<!-- BEGIN ulist_open --><ul style="list-style-type: {LIST_TYPE}"><!-- END ulist_open -->
+<!-- BEGIN ulist_open_default --><ul><!-- END ulist_open_default -->
+<!-- BEGIN ulist_close --></ul><!-- END ulist_close -->
+
+<!-- BEGIN olist_open --><ol style="list-style-type: {LIST_TYPE}"><!-- END olist_open -->
+<!-- BEGIN olist_close --></ol><!-- END olist_close -->
+
+<!-- BEGIN listitem --><li><!-- END listitem -->
+<!-- BEGIN listitem_close --></li><!-- END listitem_close -->
+
+<!-- BEGIN quote_username_open --><blockquote><div><cite>{USERNAME} {L_WROTE}{L_COLON}</cite><!-- END quote_username_open -->
+<!-- BEGIN quote_open --><blockquote class="uncited"><div><!-- END quote_open -->
+<!-- BEGIN quote_close --></div></blockquote><!-- END quote_close -->
+
+<!-- BEGIN code_open --><div class="codebox"><p>{L_CODE}{L_COLON} <a href="#" onclick="selectCode(this); return false;">{L_SELECT_ALL_CODE}</a></p><code><!-- END code_open -->
+<!-- BEGIN code_close --></code></div><!-- END code_close -->
+
+<!-- BEGIN inline_attachment_open --><div class="inline-attachment"><!-- END inline_attachment_open -->
+<!-- BEGIN inline_attachment_close --></div><!-- END inline_attachment_close -->
+
+<!-- BEGIN b_open --><b><!-- END b_open -->
+<!-- BEGIN b_close --></b><!-- END b_close -->
+
+<!-- BEGIN u_open --><span style="text-decoration: underline"><!-- END u_open -->
+<!-- BEGIN u_close --></span><!-- END u_close -->
+
+<!-- BEGIN i_open --><em><!-- END i_open -->
+<!-- BEGIN i_close --></em><!-- END i_close -->
+
+<!-- BEGIN color --><span style="color: {COLOR}">{TEXT}</span><!-- END color -->
+
+<!-- BEGIN size --><span style="font-size: {SIZE}%; line-height: 116%;">{TEXT}</span><!-- END size -->
+
+<!-- BEGIN img --><img src="{URL}" alt="{L_IMAGE}" /><!-- END img -->
+
+<!-- BEGIN url --><a href="{URL}" class="postlink">{DESCRIPTION}</a><!-- END url -->
+
+<!-- BEGIN email --><a href="mailto:{EMAIL}">{DESCRIPTION}</a><!-- END email -->
+
+<!-- BEGIN flash --><object classid="clsid:D27CDB6E-AE6D-11CF-96B8-444553540000" codebase="http://active.macromedia.com/flash2/cabs/swflash.cab#version=5,0,0,0" width="{WIDTH}" height="{HEIGHT}"><param name="movie" value="{URL}" /><param name="play" value="false" /><param name="loop" value="false" /><param name="quality" value="high" /><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><embed src="{URL}" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" width="{WIDTH}" height="{HEIGHT}" play="false" loop="false" quality="high" allowscriptaccess="never" allownetworking="internal"></embed></object><!-- END flash -->
diff --git a/tests/text_formatter/s9e/fixtures/styles/barplus/template/bbcode.html b/tests/text_formatter/s9e/fixtures/styles/barplus/template/bbcode.html
new file mode 100644
index 0000000000..cd2f0acae3
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/styles/barplus/template/bbcode.html
@@ -0,0 +1,40 @@
+<!-- BEGIN ulist_open --><ul style="list-style-type: {LIST_TYPE}"><!-- END ulist_open -->
+<!-- BEGIN ulist_open_default --><ul><!-- END ulist_open_default -->
+<!-- BEGIN ulist_close --></ul><!-- END ulist_close -->
+
+<!-- BEGIN olist_open --><ol style="list-style-type: {LIST_TYPE}"><!-- END olist_open -->
+<!-- BEGIN olist_close --></ol><!-- END olist_close -->
+
+<!-- BEGIN listitem --><li><!-- END listitem -->
+<!-- BEGIN listitem_close --></li><!-- END listitem_close -->
+
+<!-- BEGIN quote_username_open --><blockquote><div><cite>{USERNAME} {L_WROTE}{L_COLON}</cite><!-- END quote_username_open -->
+<!-- BEGIN quote_open --><blockquote class="uncited"><div><!-- END quote_open -->
+<!-- BEGIN quote_close --></div></blockquote><!-- END quote_close -->
+
+<!-- BEGIN code_open --><div class="codebox"><p>{L_CODE}{L_COLON} <a href="#" onclick="selectCode(this); return false;">{L_SELECT_ALL_CODE}</a></p><code><!-- END code_open -->
+<!-- BEGIN code_close --></code></div><!-- END code_close -->
+
+<!-- BEGIN inline_attachment_open --><div class="inline-attachment"><!-- END inline_attachment_open -->
+<!-- BEGIN inline_attachment_close --></div><!-- END inline_attachment_close -->
+
+<!-- BEGIN b_open --><b class="barplus"><!-- END b_open -->
+<!-- BEGIN b_close --></b><!-- END b_close -->
+
+<!-- BEGIN u_open --><span style="text-decoration: underline"><!-- END u_open -->
+<!-- BEGIN u_close --></span><!-- END u_close -->
+
+<!-- BEGIN i_open --><em><!-- END i_open -->
+<!-- BEGIN i_close --></em><!-- END i_close -->
+
+<!-- BEGIN color --><span style="color: {COLOR}">{TEXT}</span><!-- END color -->
+
+<!-- BEGIN size --><span style="font-size: {SIZE}%; line-height: 116%;">{TEXT}</span><!-- END size -->
+
+<!-- BEGIN img --><img src="{URL}" alt="{L_IMAGE}" /><!-- END img -->
+
+<!-- BEGIN url --><a href="{URL}" class="postlink">{DESCRIPTION}</a><!-- END url -->
+
+<!-- BEGIN email --><a href="mailto:{EMAIL}">{DESCRIPTION}</a><!-- END email -->
+
+<!-- BEGIN flash --><object classid="clsid:D27CDB6E-AE6D-11CF-96B8-444553540000" codebase="http://active.macromedia.com/flash2/cabs/swflash.cab#version=5,0,0,0" width="{WIDTH}" height="{HEIGHT}"><param name="movie" value="{URL}" /><param name="play" value="false" /><param name="loop" value="false" /><param name="quality" value="high" /><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><embed src="{URL}" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" width="{WIDTH}" height="{HEIGHT}" play="false" loop="false" quality="high" allowscriptaccess="never" allownetworking="internal"></embed></object><!-- END flash -->
diff --git a/tests/text_formatter/s9e/fixtures/styles/foo/template/bbcode.html b/tests/text_formatter/s9e/fixtures/styles/foo/template/bbcode.html
new file mode 100644
index 0000000000..909c09df5a
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/styles/foo/template/bbcode.html
@@ -0,0 +1,40 @@
+<!-- BEGIN ulist_open --><ul style="list-style-type: {LIST_TYPE}"><!-- END ulist_open -->
+<!-- BEGIN ulist_open_default --><ul><!-- END ulist_open_default -->
+<!-- BEGIN ulist_close --></ul><!-- END ulist_close -->
+
+<!-- BEGIN olist_open --><ol style="list-style-type: {LIST_TYPE}"><!-- END olist_open -->
+<!-- BEGIN olist_close --></ol><!-- END olist_close -->
+
+<!-- BEGIN listitem --><li><!-- END listitem -->
+<!-- BEGIN listitem_close --></li><!-- END listitem_close -->
+
+<!-- BEGIN quote_username_open --><blockquote><div><cite>{USERNAME} {L_WROTE}{L_COLON}</cite><!-- END quote_username_open -->
+<!-- BEGIN quote_open --><blockquote class="uncited"><div><!-- END quote_open -->
+<!-- BEGIN quote_close --></div></blockquote><!-- END quote_close -->
+
+<!-- BEGIN code_open --><div class="codebox"><p>{L_CODE}{L_COLON} <a href="#" onclick="selectCode(this); return false;">{L_SELECT_ALL_CODE}</a></p><code><!-- END code_open -->
+<!-- BEGIN code_close --></code></div><!-- END code_close -->
+
+<!-- BEGIN inline_attachment_open --><div class="inline-attachment"><!-- END inline_attachment_open -->
+<!-- BEGIN inline_attachment_close --></div><!-- END inline_attachment_close -->
+
+<!-- BEGIN b_open --><strong><!-- END b_open -->
+<!-- BEGIN b_close --></strong><!-- END b_close -->
+
+<!-- BEGIN u_open --><span style="text-decoration: underline"><!-- END u_open -->
+<!-- BEGIN u_close --></span><!-- END u_close -->
+
+<!-- BEGIN i_open --><em><!-- END i_open -->
+<!-- BEGIN i_close --></em><!-- END i_close -->
+
+<!-- BEGIN color --><span style="color: {COLOR}">{TEXT}</span><!-- END color -->
+
+<!-- BEGIN size --><span style="font-size: {SIZE}%; line-height: 116%;">{TEXT}</span><!-- END size -->
+
+<!-- BEGIN img --><img src="{URL}" alt="{L_IMAGE}" /><!-- END img -->
+
+<!-- BEGIN url --><a href="{URL}" class="postlink">{DESCRIPTION}</a><!-- END url -->
+
+<!-- BEGIN email --><a href="mailto:{EMAIL}">{DESCRIPTION}</a><!-- END email -->
+
+<!-- BEGIN flash --><object classid="clsid:D27CDB6E-AE6D-11CF-96B8-444553540000" codebase="http://active.macromedia.com/flash2/cabs/swflash.cab#version=5,0,0,0" width="{WIDTH}" height="{HEIGHT}"><param name="movie" value="{URL}" /><param name="play" value="false" /><param name="loop" value="false" /><param name="quality" value="high" /><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><embed src="{URL}" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" width="{WIDTH}" height="{HEIGHT}" play="false" loop="false" quality="high" allowscriptaccess="never" allownetworking="internal"></embed></object><!-- END flash -->
diff --git a/tests/text_formatter/s9e/fixtures/unsafe_bbcode.xml b/tests/text_formatter/s9e/fixtures/unsafe_bbcode.xml
new file mode 100644
index 0000000000..55a2e689b6
--- /dev/null
+++ b/tests/text_formatter/s9e/fixtures/unsafe_bbcode.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<dataset>
+ <table name="phpbb_bbcodes">
+ <column>bbcode_id</column>
+ <column>bbcode_tag</column>
+ <column>bbcode_helpline</column>
+ <column>display_on_posting</column>
+ <column>bbcode_match</column>
+ <column>bbcode_tpl</column>
+ <column>first_pass_match</column>
+ <column>first_pass_replace</column>
+ <column>second_pass_match</column>
+ <column>second_pass_replace</column>
+
+ <row>
+ <value>13</value>
+ <value>xss=</value>
+ <value></value>
+ <value>1</value>
+ <value>[xss={TEXT1}]{TEXT2}[/xss]</value>
+ <value><![CDATA[<a href="{TEXT1}">{TEXT2}</a>]]></value>
+ <value><![CDATA[!\[xss\=(.*?)\](.*?)\[/xss\]!ies]]></value>
+ <value><![CDATA['[xss='.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', '&#39;', '&#40;', '&#41;'), trim('${1}')).':$uid]'.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', '&#39;', '&#40;', '&#41;'), trim('${2}')).'[/xss:$uid]']]></value>
+ <value><![CDATA[!\[xss\=(.*?):$uid\](.*?)\[/xss:$uid\]!s]]></value>
+ <value><![CDATA[<a href="${1}">${2}</a>]]></value>
+ </row>
+ </table>
+</dataset>
diff --git a/tests/text_formatter/s9e/parser_test.php b/tests/text_formatter/s9e/parser_test.php
new file mode 100644
index 0000000000..3b72e713e1
--- /dev/null
+++ b/tests/text_formatter/s9e/parser_test.php
@@ -0,0 +1,260 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+require_once __DIR__ . '/../../../phpBB/includes/functions.php';
+require_once __DIR__ . '/../../../phpBB/includes/functions_content.php';
+
+class phpbb_textformatter_s9e_parser_test extends phpbb_test_case
+{
+ public function test_load_from_cache()
+ {
+ $mock = $this->getMockBuilder('s9e\\TextFormatter\\Parser')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $cache = $this->getMock('phpbb_mock_cache');
+ $cache->expects($this->once())
+ ->method('get')
+ ->with('_foo_parser')
+ ->will($this->returnValue($mock));
+
+ $factory = $this->getMockBuilder('phpbb\\textformatter\\s9e\\factory')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $factory->expects($this->never())->method('regenerate');
+
+ $parser = new \phpbb\textformatter\s9e\parser(
+ $cache,
+ '_foo_parser',
+ $factory,
+ new phpbb_mock_event_dispatcher
+ );
+ }
+
+ public function test_use_from_cache()
+ {
+ $mock = $this->getMockBuilder('s9e\\TextFormatter\\Parser')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $mock->expects($this->once())
+ ->method('parse')
+ ->with('test')
+ ->will($this->returnValue('<t>test</t>'));
+
+ $cache = new phpbb_mock_cache;
+ $cache->put('_foo_parser', $mock);
+
+ $factory = $this->getMockBuilder('phpbb\\textformatter\\s9e\\factory')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $factory->expects($this->never())->method('regenerate');
+
+ $parser = new \phpbb\textformatter\s9e\parser(
+ $cache,
+ '_foo_parser',
+ $factory,
+ new phpbb_mock_event_dispatcher
+ );
+
+ $this->assertSame('<t>test</t>', $parser->parse('test'));
+ }
+
+ public function test_regenerate_on_cache_miss()
+ {
+ $mock = $this->getMockBuilder('s9e\\TextFormatter\\Parser')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $mock->expects($this->once())
+ ->method('parse')
+ ->with('test')
+ ->will($this->returnValue('<t>test</t>'));
+
+ $factory = $this->getMockBuilder('phpbb\\textformatter\\s9e\\factory')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $factory->expects($this->once())
+ ->method('regenerate')
+ ->will($this->returnValue(array('parser' => $mock)));
+
+ $parser = new \phpbb\textformatter\s9e\parser(
+ new phpbb_mock_cache,
+ '_foo_parser',
+ $factory,
+ new phpbb_mock_event_dispatcher
+ );
+
+ $this->assertSame('<t>test</t>', $parser->parse('test'));
+ }
+
+ /**
+ * @dataProvider get_options_tests()
+ */
+ public function test_options($adapter_method, $adapter_arg, $concrete_method, $concrete_arg)
+ {
+ $mock = $this->getMockBuilder('s9e\\TextFormatter\\Parser')
+ ->setMethods(array($concrete_method))
+ ->disableOriginalConstructor()
+ ->getMock();
+ foreach ((array) $concrete_arg as $i => $concrete_arg)
+ {
+ $mock->expects($this->at($i))
+ ->method($concrete_method)
+ ->with($concrete_arg);
+ }
+
+ $cache = new phpbb_mock_cache;
+ $cache->put('_foo_parser', $mock);
+
+ $factory = $this->getMockBuilder('phpbb\\textformatter\\s9e\\factory')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $parser = new \phpbb\textformatter\s9e\parser(
+ $cache,
+ '_foo_parser',
+ $factory,
+ new phpbb_mock_event_dispatcher
+ );
+
+ call_user_func_array(array($parser, $adapter_method), (array) $adapter_arg);
+ }
+
+ public function get_options_tests()
+ {
+ return array(
+ array(
+ 'disable_bbcode', 'url',
+ 'disableTag', 'URL'
+ ),
+ array(
+ 'disable_bbcodes', null,
+ 'disablePlugin', 'BBCodes'
+ ),
+ array(
+ 'disable_magic_url', null,
+ 'disablePlugin', array('Autoemail', 'Autolink')
+ ),
+ array(
+ 'disable_smilies', null,
+ 'disablePlugin', 'Emoticons'
+ ),
+ array(
+ 'enable_bbcode', 'url',
+ 'enableTag', 'URL'
+ ),
+ array(
+ 'enable_bbcodes', null,
+ 'enablePlugin', 'BBCodes'
+ ),
+ array(
+ 'enable_magic_url', null,
+ 'enablePlugin', array('Autoemail', 'Autolink')
+ ),
+ array(
+ 'enable_smilies', null,
+ 'enablePlugin', 'Emoticons'
+ )
+ );
+ }
+
+ /**
+ * @testdox The constructor triggers a core.text_formatter_s9e_parser_setup event
+ */
+ public function test_setup_event()
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $dispatcher = $this->getMock('phpbb\\event\\dispatcher_interface');
+ $dispatcher
+ ->expects($this->once())
+ ->method('trigger_event')
+ ->with(
+ 'core.text_formatter_s9e_parser_setup',
+ $this->callback(array($this, 'setup_event_callback'))
+ )
+ ->will($this->returnArgument(1));
+
+ new \phpbb\textformatter\s9e\parser(
+ $container->get('cache.driver'),
+ '_foo_parser',
+ $container->get('text_formatter.s9e.factory'),
+ $dispatcher
+ );
+ }
+
+ public function setup_event_callback($vars)
+ {
+ return isset($vars['parser'])
+ && $vars['parser'] instanceof \phpbb\textformatter\s9e\parser;
+ }
+
+ /**
+ * @testdox parse() triggers a core.text_formatter_s9e_parse_before and core.text_formatter_s9e_parse_after events
+ */
+ public function test_parse_event()
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $dispatcher = $this->getMock('phpbb\\event\\dispatcher_interface');
+ $dispatcher
+ ->expects($this->any())
+ ->method('trigger_event')
+ ->will($this->returnArgument(1));
+ $dispatcher
+ ->expects($this->at(1))
+ ->method('trigger_event')
+ ->with(
+ 'core.text_formatter_s9e_parse_before',
+ $this->callback(array($this, 'parse_before_event_callback'))
+ )
+ ->will($this->returnArgument(1));
+ $dispatcher
+ ->expects($this->at(2))
+ ->method('trigger_event')
+ ->with(
+ 'core.text_formatter_s9e_parse_after',
+ $this->callback(array($this, 'parse_after_event_callback'))
+ )
+ ->will($this->returnArgument(1));
+
+ $parser = new \phpbb\textformatter\s9e\parser(
+ $container->get('cache.driver'),
+ '_foo_parser',
+ $container->get('text_formatter.s9e.factory'),
+ $dispatcher
+ );
+ $parser->parse('...');
+ }
+
+ public function parse_before_event_callback($vars)
+ {
+ return isset($vars['parser'])
+ && $vars['parser'] instanceof \phpbb\textformatter\s9e\parser
+ && isset($vars['text'])
+ && $vars['text'] === '...';
+ }
+
+ public function parse_after_event_callback($vars)
+ {
+ return isset($vars['parser'])
+ && $vars['parser'] instanceof \phpbb\textformatter\s9e\parser
+ && isset($vars['xml'])
+ && $vars['xml'] === '<t>...</t>';
+ }
+
+ public function test_get_parser()
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $parser = $container->get('text_formatter.parser');
+ $this->assertInstanceOf('s9e\\TextFormatter\\Parser', $parser->get_parser());
+ }
+}
diff --git a/tests/text_formatter/s9e/renderer_test.php b/tests/text_formatter/s9e/renderer_test.php
new file mode 100644
index 0000000000..3c0bbb96c7
--- /dev/null
+++ b/tests/text_formatter/s9e/renderer_test.php
@@ -0,0 +1,483 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+require_once __DIR__ . '/../../../phpBB/includes/functions.php';
+require_once __DIR__ . '/../../../phpBB/includes/functions_content.php';
+
+class phpbb_textformatter_s9e_renderer_test extends phpbb_test_case
+{
+ public function get_cache_dir()
+ {
+ return __DIR__ . '/../../tmp/';
+ }
+
+ public function test_load_from_cache()
+ {
+ // Save a fake renderer class in the cache dir
+ file_put_contents(
+ $this->get_cache_dir() . 'renderer_foo.php',
+ '<?php class renderer_foo { public function setParameter() {} }'
+ );
+
+ $cache = $this->getMock('phpbb_mock_cache');
+ $cache->expects($this->once())
+ ->method('get')
+ ->with('_foo_renderer')
+ ->will($this->returnValue(array('class' => 'renderer_foo')));
+
+ $factory = $this->getMockBuilder('phpbb\\textformatter\\s9e\\factory')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $factory->expects($this->never())->method('regenerate');
+
+ $renderer = new \phpbb\textformatter\s9e\renderer(
+ $cache,
+ $this->get_cache_dir(),
+ '_foo_renderer',
+ $factory,
+ new phpbb_mock_event_dispatcher
+ );
+ }
+
+ public function test_regenerate_on_cache_miss()
+ {
+ $mock = $this->getMockForAbstractClass('s9e\\TextFormatter\\Renderer');
+
+ $cache = $this->getMock('phpbb_mock_cache');
+ $cache->expects($this->once())
+ ->method('get')
+ ->with('_foo_renderer')
+ ->will($this->returnValue(false));
+
+ $factory = $this->getMockBuilder('phpbb\\textformatter\\s9e\\factory')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $factory->expects($this->once())
+ ->method('regenerate')
+ ->will($this->returnValue(array('parser' => $mock)));
+
+ $renderer = new \phpbb\textformatter\s9e\renderer(
+ $cache,
+ $this->get_cache_dir(),
+ '_foo_renderer',
+ $factory,
+ new phpbb_mock_event_dispatcher
+ );
+ }
+
+ /**
+ * @dataProvider get_options_cases
+ */
+ public function test_options($original, $expected, $calls)
+ {
+ $container = new phpbb_mock_container_builder;
+ $this->get_test_case_helpers()->set_s9e_services($container);
+
+ $renderer = $container->get('text_formatter.renderer');
+
+ foreach ($calls as $method => $arg)
+ {
+ $renderer->$method($arg);
+ }
+
+ $this->assertSame($expected, $renderer->render($original));
+ }
+
+ public function get_options_cases()
+ {
+ return array(
+ array(
+ '<t>apple</t>',
+ 'banana',
+ array('set_viewcensors' => true)
+ ),
+ array(
+ '<t>apple</t>',
+ 'apple',
+ array('set_viewcensors' => false)
+ ),
+ array(
+ '<r><FLASH height="456" url="http://example.org/foo.swf" width="123"><s>[flash=123,456]</s><URL url="http://example.org/foo.swf">http://example.org/foo.swf</URL><e>[/flash]</e></FLASH></r>',
+ '<object classid="clsid:D27CDB6E-AE6D-11CF-96B8-444553540000" codebase="http://active.macromedia.com/flash2/cabs/swflash.cab#version=5,0,0,0" width="123" height="456"><param name="movie" value="http://example.org/foo.swf"><param name="play" value="false"><param name="loop" value="false"><param name="quality" value="high"><param name="allowScriptAccess" value="never"><param name="allowNetworking" value="internal"><embed src="http://example.org/foo.swf" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" width="123" height="456" play="false" loop="false" quality="high" allowscriptaccess="never" allownetworking="internal"></object>',
+ array('set_viewflash' => true)
+ ),
+ array(
+ '<r><IMG src="http://example.org/foo.png"><s>[img]</s>http://example.org/foo.png<e>[/img]</e></IMG></r>',
+ '<img src="http://example.org/foo.png" alt="Image">',
+ array('set_viewimg' => true)
+ ),
+ array(
+ '<r><E>:)</E></r>',
+ '<img class="smilies" src="phpBB/images/smilies/icon_e_smile.gif" alt=":)" title="Smile">',
+ array('set_viewsmilies' => true)
+ ),
+ array(
+ '<r><E>:)</E></r>',
+ ':)',
+ array('set_viewsmilies' => false)
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider get_default_options_cases
+ */
+ public function test_default_options($original, $expected, $setup = null)
+ {
+ $container = new phpbb_mock_container_builder;
+
+ if (isset($setup))
+ {
+ $setup($container, $this);
+ }
+
+ $this->get_test_case_helpers()->set_s9e_services($container);
+
+ $this->assertSame($expected, $container->get('text_formatter.renderer')->render($original));
+ }
+
+ public function get_default_options_cases()
+ {
+ return array(
+ array(
+ '<t>apple</t>',
+ 'banana'
+ ),
+ array(
+ '<t>apple</t>',
+ 'banana',
+ function ($phpbb_container)
+ {
+ global $phpbb_root_path, $phpEx;
+
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $lang = new \phpbb\language\language($lang_loader);
+ $user = new \phpbb\user($lang, '\phpbb\datetime');
+ $user->optionset('viewcensors', false);
+
+ $phpbb_container->set('user', $user);
+ }
+ ),
+ array(
+ '<t>apple</t>',
+ 'banana',
+ function ($phpbb_container)
+ {
+ global $phpbb_root_path, $phpEx;
+
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $lang = new \phpbb\language\language($lang_loader);
+ $user = new \phpbb\user($lang, '\phpbb\datetime');
+ $user->optionset('viewcensors', false);
+
+ $config = new \phpbb\config\config(array('allow_nocensors' => true));
+
+ $phpbb_container->set('user', $user);
+ $phpbb_container->set('config', $config);
+ }
+ ),
+ array(
+ '<t>apple</t>',
+ 'apple',
+ function ($phpbb_container, $test)
+ {
+ global $phpbb_root_path, $phpEx;
+
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $lang = new \phpbb\language\language($lang_loader);
+ $user = new \phpbb\user($lang, '\phpbb\datetime');
+ $user->optionset('viewcensors', false);
+
+ $config = new \phpbb\config\config(array('allow_nocensors' => true));
+
+ $auth = $test->getMock('phpbb\\auth\\auth');
+ $auth->expects($test->any())
+ ->method('acl_get')
+ ->with('u_chgcensors')
+ ->will($test->returnValue(true));
+
+ $phpbb_container->set('user', $user);
+ $phpbb_container->set('config', $config);
+ $phpbb_container->set('auth', $auth);
+ }
+ ),
+ array(
+ '<r><FLASH url="http://localhost/foo.swf" width="123" height="456"><s>[flash=123,456]</s>http://localhost/foo.swf<e>[/flash]</e></FLASH></r>',
+ '<object classid="clsid:D27CDB6E-AE6D-11CF-96B8-444553540000" codebase="http://active.macromedia.com/flash2/cabs/swflash.cab#version=5,0,0,0" width="123" height="456"><param name="movie" value="http://localhost/foo.swf"><param name="play" value="false"><param name="loop" value="false"><param name="quality" value="high"><param name="allowScriptAccess" value="never"><param name="allowNetworking" value="internal"><embed src="http://localhost/foo.swf" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" width="123" height="456" play="false" loop="false" quality="high" allowscriptaccess="never" allownetworking="internal"></object>'
+ ),
+ array(
+ '<r><FLASH url="http://localhost/foo.swf" width="123" height="456"><s>[flash=123,456]</s>http://localhost/foo.swf<e>[/flash]</e></FLASH></r>',
+ 'http://localhost/foo.swf',
+ function ($phpbb_container)
+ {
+ global $phpbb_root_path, $phpEx;
+
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $lang = new \phpbb\language\language($lang_loader);
+ $user = new \phpbb\user($lang, '\phpbb\datetime');
+ $user->optionset('viewflash', false);
+
+ $phpbb_container->set('user', $user);
+ }
+ ),
+ array(
+ '<r><IMG src="http://localhost/mrgreen.gif"><s>[img]</s><URL url="http://localhost/mrgreen.gif">http://localhost/mrgreen.gif</URL><e>[/img]</e></IMG></r>',
+ '<img src="http://localhost/mrgreen.gif" alt="Image">'
+ ),
+ array(
+ '<r><IMG src="http://localhost/mrgreen.gif"><s>[img]</s><URL url="http://localhost/mrgreen.gif">http://localhost/mrgreen.gif</URL><e>[/img]</e></IMG></r>',
+ '<a href="http://localhost/mrgreen.gif" class="postlink">http://localhost/mrgreen.gif</a>',
+ function ($phpbb_container)
+ {
+ global $phpbb_root_path, $phpEx;
+
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $lang = new \phpbb\language\language($lang_loader);
+ $user = new \phpbb\user($lang, '\phpbb\datetime');
+ $user->optionset('viewimg', false);
+
+ $phpbb_container->set('user', $user);
+ }
+ ),
+ array(
+ '<r><E>:)</E></r>',
+ '<img class="smilies" src="phpBB/images/smilies/icon_e_smile.gif" alt=":)" title="Smile">'
+ ),
+ array(
+ '<r><E>:)</E></r>',
+ ':)',
+ function ($phpbb_container)
+ {
+ global $phpbb_root_path, $phpEx;
+
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $lang = new \phpbb\language\language($lang_loader);
+ $user = new \phpbb\user($lang, '\phpbb\datetime');
+ $user->optionset('smilies', false);
+
+ $phpbb_container->set('user', $user);
+ }
+ ),
+ );
+ }
+
+ public function test_default_lang()
+ {
+ global $phpbb_container;
+ $this->get_test_case_helpers()->set_s9e_services($phpbb_container, __DIR__ . '/fixtures/default_lang.xml');
+
+ $renderer = $phpbb_container->get('text_formatter.renderer');
+
+ $this->assertSame('FOO_BAR', $renderer->render('<r><FOO/></r>'));
+ }
+
+ /**
+ * @dataProvider get_option_names
+ */
+ public function test_get_option($option_name)
+ {
+ global $phpbb_container;
+ $this->get_test_case_helpers()->set_s9e_services();
+
+ $renderer = $phpbb_container->get('text_formatter.renderer');
+
+ $renderer->{'set_' . $option_name}(false);
+ $this->assertFalse($renderer->{'get_' . $option_name}());
+ $renderer->{'set_' . $option_name}(true);
+ $this->assertTrue($renderer->{'get_' . $option_name}());
+ }
+
+ public function get_option_names()
+ {
+ return array(
+ array('viewcensors'),
+ array('viewflash'),
+ array('viewimg'),
+ array('viewsmilies')
+ );
+ }
+
+ public function test_styles()
+ {
+ global $phpbb_container;
+
+ $tests = array(
+ 1 => '<strong>bold</strong>',
+ 2 => '<b>bold</b>'
+ );
+
+ global $phpbb_root_path, $phpEx;
+
+ foreach ($tests as $style_id => $expected)
+ {
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $lang = new \phpbb\language\language($lang_loader);
+ $user = new \phpbb\user($lang, '\phpbb\datetime');
+ $user->style = array('style_id' => $style_id);
+
+ $phpbb_container = new phpbb_mock_container_builder;
+ $phpbb_container->set('user', $user);
+
+ $this->get_test_case_helpers()->set_s9e_services($phpbb_container, __DIR__ . '/fixtures/styles.xml', __DIR__ . '/fixtures/styles/');
+
+ $renderer = $phpbb_container->get('text_formatter.renderer');
+ $this->assertSame(
+ $expected,
+ $renderer->render('<r><B><s>[b]</s>bold<e>[/b]</e></B></r>')
+ );
+ }
+ }
+
+ public function test_style_inheritance1()
+ {
+ global $phpbb_container, $phpbb_root_path, $phpEx;
+
+ // Style 3 inherits from 2 which inherits from 1. Only style 1 has a bbcode.html
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $lang = new \phpbb\language\language($lang_loader);
+ $user = new \phpbb\user($lang, '\phpbb\datetime');
+ $user->style = array('style_id' => 3);
+
+ $phpbb_container = new phpbb_mock_container_builder;
+ $phpbb_container->set('user', $user);
+
+ $this->get_test_case_helpers()->set_s9e_services($phpbb_container, __DIR__ . '/fixtures/style_inheritance.xml', __DIR__ . '/fixtures/styles/');
+
+ $renderer = $phpbb_container->get('text_formatter.renderer');
+ $this->assertSame(
+ '<strong>bold</strong>',
+ $renderer->render('<r><B><s>[b]</s>bold<e>[/b]</e></B></r>')
+ );
+ }
+
+ public function test_style_inheritance2()
+ {
+ global $phpbb_container, $phpbb_root_path, $phpEx;
+
+ // Style 5 inherits from 4, but both have a bbcode.html
+ $tests = array(
+ 4 => '<b>bold</b>',
+ 5 => '<b class="barplus">bold</b>'
+ );
+
+ foreach ($tests as $style_id => $expected)
+ {
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $lang = new \phpbb\language\language($lang_loader);
+ $user = new \phpbb\user($lang, '\phpbb\datetime');
+ $user->style = array('style_id' => $style_id);
+
+ $phpbb_container = new phpbb_mock_container_builder;
+ $phpbb_container->set('user', $user);
+
+ $this->get_test_case_helpers()->set_s9e_services($phpbb_container, __DIR__ . '/fixtures/style_inheritance.xml', __DIR__ . '/fixtures/styles/');
+
+ $renderer = $phpbb_container->get('text_formatter.renderer');
+ $this->assertSame(
+ $expected,
+ $renderer->render('<r><B><s>[b]</s>bold<e>[/b]</e></B></r>')
+ );
+ }
+ }
+
+ /**
+ * @testdox The constructor triggers a core.text_formatter_s9e_renderer_setup event
+ */
+ public function test_setup_event()
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $dispatcher = $this->getMock('phpbb\\event\\dispatcher_interface');
+ $dispatcher
+ ->expects($this->once())
+ ->method('trigger_event')
+ ->with(
+ 'core.text_formatter_s9e_renderer_setup',
+ $this->callback(array($this, 'setup_event_callback'))
+ )
+ ->will($this->returnArgument(1));
+
+ new \phpbb\textformatter\s9e\renderer(
+ $container->get('cache.driver'),
+ $container->getParameter('cache.dir'),
+ '_foo_renderer',
+ $container->get('text_formatter.s9e.factory'),
+ $dispatcher
+ );
+ }
+
+ public function setup_event_callback($vars)
+ {
+ return isset($vars['renderer'])
+ && $vars['renderer'] instanceof \phpbb\textformatter\s9e\renderer;
+ }
+
+ /**
+ * @testdox render() triggers a core.text_formatter_s9e_render_before and core.text_formatter_s9e_render_after events
+ */
+ public function test_render_event()
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $dispatcher = $this->getMock('phpbb\\event\\dispatcher_interface');
+ $dispatcher
+ ->expects($this->any())
+ ->method('trigger_event')
+ ->will($this->returnArgument(1));
+ $dispatcher
+ ->expects($this->at(1))
+ ->method('trigger_event')
+ ->with(
+ 'core.text_formatter_s9e_render_before',
+ $this->callback(array($this, 'render_before_event_callback'))
+ )
+ ->will($this->returnArgument(1));
+ $dispatcher
+ ->expects($this->at(2))
+ ->method('trigger_event')
+ ->with(
+ 'core.text_formatter_s9e_render_after',
+ $this->callback(array($this, 'render_after_event_callback'))
+ )
+ ->will($this->returnArgument(1));
+
+ $renderer = new \phpbb\textformatter\s9e\renderer(
+ $container->get('cache.driver'),
+ $container->getParameter('cache.dir'),
+ '_foo_renderer',
+ $container->get('text_formatter.s9e.factory'),
+ $dispatcher
+ );
+ $renderer->render('<t>...</t>');
+ }
+
+ public function render_before_event_callback($vars)
+ {
+ return isset($vars['renderer'])
+ && $vars['renderer'] instanceof \phpbb\textformatter\s9e\renderer
+ && isset($vars['xml'])
+ && $vars['xml'] === '<t>...</t>';
+ }
+
+ public function render_after_event_callback($vars)
+ {
+ return isset($vars['html'])
+ && $vars['html'] === '...'
+ && isset($vars['renderer'])
+ && $vars['renderer'] instanceof \phpbb\textformatter\s9e\renderer;
+ }
+
+ public function test_get_renderer()
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $renderer = $container->get('text_formatter.renderer');
+ $this->assertInstanceOf('s9e\\TextFormatter\\Renderer', $renderer->get_renderer());
+ }
+}
diff --git a/tests/text_formatter/s9e/utils_test.php b/tests/text_formatter/s9e/utils_test.php
new file mode 100644
index 0000000000..1c03783792
--- /dev/null
+++ b/tests/text_formatter/s9e/utils_test.php
@@ -0,0 +1,270 @@
+<?php
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+require_once __DIR__ . '/../../../phpBB/includes/functions.php';
+require_once __DIR__ . '/../../../phpBB/includes/functions_content.php';
+
+class phpbb_textformatter_s9e_utils_test extends phpbb_test_case
+{
+ /**
+ * @dataProvider get_unparse_tests
+ */
+ public function test_unparse($original, $expected)
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $utils = $container->get('text_formatter.utils');
+
+ $this->assertSame($expected, $utils->unparse($original));
+ }
+
+ public function get_unparse_tests()
+ {
+ return array(
+ array(
+ '<t>Plain text</t>',
+ 'Plain text'
+ ),
+ array(
+ "<t>Multi<br/>\nline</t>",
+ "Multi\nline"
+ ),
+ array(
+ '<r><B><s>[b]</s>bold<e>[/b]</e></B></r>',
+ '[b]bold[/b]'
+ )
+ );
+ }
+
+ /**
+ * @dataProvider get_clean_formatting_tests
+ */
+ public function test_clean_formatting($original, $expected)
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $utils = $container->get('text_formatter.utils');
+
+ $this->assertSame($expected, $utils->clean_formatting($original));
+ }
+
+ public function get_clean_formatting_tests()
+ {
+ return array(
+ array(
+ '<t>Plain text</t>',
+ 'Plain text'
+ ),
+ array(
+ "<t>Multi<br/>\nline</t>",
+ "Multi\nline"
+ ),
+ array(
+ '<r><B><s>[b]</s>bold<e>[/b]</e></B></r>',
+ ' bold '
+ )
+ );
+ }
+
+ /**
+ * @dataProvider get_outermost_quote_authors_tests
+ */
+ public function test_get_outermost_quote_authors($original, $expected)
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $utils = $container->get('text_formatter.utils');
+ $parser = $container->get('text_formatter.parser');
+
+ $this->assertSame($expected, $utils->get_outermost_quote_authors($parser->parse($original)));
+ }
+
+ public function get_outermost_quote_authors_tests()
+ {
+ return array(
+ array(
+ 'No quotes here',
+ array()
+ ),
+ array(
+ '[quote="foo"]..[/quote] [quote]..[/quote]',
+ array('foo')
+ ),
+ array(
+ '[quote=foo]..[/quote] [quote]..[/quote]',
+ array('foo')
+ ),
+ array(
+ '[quote=foo]..[/quote] [quote=bar]..[/quote]',
+ array('foo', 'bar')
+ ),
+ array(
+ '[quote=foo].[quote=baz]..[/quote].[/quote] [quote=bar]..[/quote]',
+ array('foo', 'bar')
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider get_generate_quote_tests
+ */
+ public function test_generate_quote($text, $params, $expected)
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $utils = $container->get('text_formatter.utils');
+
+ $this->assertSame($expected, $utils->generate_quote($text, $params));
+ }
+
+ public function get_generate_quote_tests()
+ {
+ return array(
+ array(
+ '...',
+ array(),
+ '[quote]...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => 'Brian Kibler'),
+ '[quote="Brian Kibler"]...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => 'Brian "Brian Kibler" Kibler of Brian Kibler Gaming'),
+ '[quote=\'Brian "Brian Kibler" Kibler of Brian Kibler Gaming\']...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => "Brian Kibler Gaming's Brian Kibler"),
+ '[quote="Brian Kibler Gaming\'s Brian Kibler"]...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => "\\\"'"),
+ '[quote="\\\\\\"\'"]...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => 'Lots of doubles """ one single \' one backslash \\'),
+ '[quote=\'Lots of doubles """ one single \\\' one backslash \\\\\']...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => "Lots of singles ''' one double \" one backslash \\"),
+ '[quote="Lots of singles \'\'\' one double \\" one backslash \\\\"]...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => 'Defaults to doublequotes """\'\'\''),
+ '[quote="Defaults to doublequotes \\"\\"\\"\'\'\'"]...[/quote]',
+ ),
+ array(
+ '...',
+ array(
+ 'author' => 'user',
+ 'post_id' => 123,
+ 'url' => 'http://example.org'
+ ),
+ '[quote=user post_id=123 url=http://example.org]...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => ' '),
+ '[quote=" "]...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => 'foo bar'),
+ '[quote="foo bar"]...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => '\\'),
+ '[quote="\\\\"]...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => '[quote="foo"]'),
+ '[quote=\'[quote="foo"]\']...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => '""'),
+ '[quote=\'""\']...[/quote]',
+ ),
+ array(
+ '...',
+ array('author' => "''"),
+ '[quote="\'\'"]...[/quote]',
+ ),
+ array(
+ 'This is a long quote that is definitely going to exceed 80 characters',
+ array(),
+ "[quote]\nThis is a long quote that is definitely going to exceed 80 characters\n[/quote]",
+ ),
+ array(
+ ' This is a short quote on its own line ',
+ array(),
+ '[quote]This is a short quote on its own line[/quote]',
+ ),
+ array(
+ "This is a short quote\non two lines",
+ array(),
+ "[quote]\nThis is a short quote\non two lines\n[/quote]",
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider get_remove_bbcode_tests
+ */
+ public function test_remove_bbcode($original, $name, $depth, $expected)
+ {
+ $container = $this->get_test_case_helpers()->set_s9e_services();
+ $parser = $container->get('text_formatter.parser');
+ $utils = $container->get('text_formatter.utils');
+
+ $parsed = $parser->parse($original);
+ $actual = $utils->unparse($utils->remove_bbcode($parsed, $name, $depth));
+
+ $this->assertSame($expected, $actual);
+ }
+
+ public function get_remove_bbcode_tests()
+ {
+ return array(
+ array(
+ 'Plain text',
+ 'b',
+ 1,
+ 'Plain text'
+ ),
+ array(
+ '[quote="u0"][quote="u1"][quote="u2"]q2[/quote]q1[/quote]q0[/quote][b]bold[/b]',
+ 'quote',
+ 0,
+ '[b]bold[/b]',
+ ),
+ array(
+ '[quote="u0"][quote="u1"][quote="u2"]q2[/quote]q1[/quote]q0[/quote][b]bold[/b]',
+ 'quote',
+ 1,
+ '[quote="u0"]q0[/quote][b]bold[/b]',
+ ),
+ array(
+ '[quote="u0"][quote="u1"][quote="u2"]q2[/quote]q1[/quote]q0[/quote][b]bold[/b]',
+ 'quote',
+ 2,
+ '[quote="u0"][quote="u1"]q1[/quote]q0[/quote][b]bold[/b]',
+ ),
+ );
+ }
+}