diff options
-rw-r--r-- | phpBB/config/default/container/services_text_formatter.yml | 8 | ||||
-rw-r--r-- | phpBB/includes/ucp/ucp_pm_compose.php | 11 | ||||
-rw-r--r-- | phpBB/phpbb/textformatter/s9e/factory.php | 28 | ||||
-rw-r--r-- | phpBB/phpbb/textformatter/s9e/quote_helper.php | 81 | ||||
-rw-r--r-- | phpBB/phpbb/textformatter/s9e/renderer.php | 19 | ||||
-rw-r--r-- | phpBB/phpbb/textformatter/s9e/utils.php | 1 | ||||
-rw-r--r-- | phpBB/posting.php | 7 | ||||
-rw-r--r-- | phpBB/styles/prosilver/template/bbcode.html | 33 | ||||
-rw-r--r-- | tests/functional/posting_test.php | 17 | ||||
-rw-r--r-- | tests/functional/private_messages_test.php | 20 | ||||
-rw-r--r-- | tests/test_framework/phpbb_test_case_helpers.php | 32 | ||||
-rw-r--r-- | tests/text_formatter/s9e/default_formatting_test.php | 22 |
12 files changed, 248 insertions, 31 deletions
diff --git a/phpBB/config/default/container/services_text_formatter.yml b/phpBB/config/default/container/services_text_formatter.yml index 20436f0f64..ae698af9e4 100644 --- a/phpBB/config/default/container/services_text_formatter.yml +++ b/phpBB/config/default/container/services_text_formatter.yml @@ -44,6 +44,13 @@ services: - @text_formatter.s9e.factory - @dispatcher + text_formatter.s9e.quote_helper: + class: phpbb\textformatter\s9e\quote_helper + arguments: + - @user + - %core.root_path% + - %core.php_ext% + text_formatter.s9e.renderer: class: phpbb\textformatter\s9e\renderer arguments: @@ -53,6 +60,7 @@ services: - @text_formatter.s9e.factory - @dispatcher calls: + - [configure_quote_helper, [@text_formatter.s9e.quote_helper]] - [configure_smilies_path, [@config, @path_helper]] - [configure_user, [@user, @config, @auth]] diff --git a/phpBB/includes/ucp/ucp_pm_compose.php b/phpBB/includes/ucp/ucp_pm_compose.php index 0989539a0f..c00363bee9 100644 --- a/phpBB/includes/ucp/ucp_pm_compose.php +++ b/phpBB/includes/ucp/ucp_pm_compose.php @@ -941,9 +941,18 @@ function compose_pm($id, $mode, $action, $user_folders = array()) { $message_link = ''; } + $quote_attributes = array( + 'author' => $quote_username, + 'time' => $post['message_time'], + 'user_id' => $post['author_id'], + ); + if ($action === 'quotepost') + { + $quote_attributes['post_id'] = $post['msg_id']; + } $quote_text = $phpbb_container->get('text_formatter.utils')->generate_quote( censor_text($message_parser->message), - array('author' => $quote_username) + $quote_attributes ); $message_parser->message = $message_link . $quote_text . "\n\n"; } diff --git a/phpBB/phpbb/textformatter/s9e/factory.php b/phpBB/phpbb/textformatter/s9e/factory.php index e07a1b52ca..cd4e593fb1 100644 --- a/phpBB/phpbb/textformatter/s9e/factory.php +++ b/phpBB/phpbb/textformatter/s9e/factory.php @@ -77,7 +77,12 @@ class factory implements \phpbb\textformatter\cache_interface 'quote' => "[QUOTE author={TEXT1;optional} + post_id={UINT;optional} + post_url={URL;optional;postFilter=#false} + profile_url={URL;optional;postFilter=#false} + time={UINT;optional} url={URL;optional} + user_id={UINT;optional} author={PARSE=/^\\[url=(?'url'.*?)](?'author'.*)\\[\\/url]$/i} author={PARSE=/^\\[url](?'author'(?'url'.*?))\\[\\/url]$/i} author={PARSE=/(?'url'https?:\\/\\/[^[\\]]+)/i} @@ -471,24 +476,11 @@ class factory implements \phpbb\textformatter\cache_interface $templates['li'] = $fragments['listitem'] . '<xsl:apply-templates/>' . $fragments['listitem_close']; - $fragments['quote_username_open'] = str_replace( - '{USERNAME}', - '<xsl:choose> - <xsl:when test="@url">' . str_replace('{DESCRIPTION}', '{USERNAME}', $fragments['url']) . '</xsl:when> - <xsl:otherwise>{USERNAME}</xsl:otherwise> - </xsl:choose>', - $fragments['quote_username_open'] - ); - - $templates['quote'] = - '<xsl:choose> - <xsl:when test="@author"> - ' . $fragments['quote_username_open'] . '<xsl:apply-templates/>' . $fragments['quote_close'] . ' - </xsl:when> - <xsl:otherwise> - ' . $fragments['quote_open'] . '<xsl:apply-templates/>' . $fragments['quote_close'] . ' - </xsl:otherwise> - </xsl:choose>'; + // Replace the regular quote template with the extended quote template if available + if (isset($fragments['quote_extended'])) + { + $templates['quote'] = $fragments['quote_extended']; + } // The [attachment] BBCode uses the inline_attachment template to output a comment that // is post-processed by parse_attachments() diff --git a/phpBB/phpbb/textformatter/s9e/quote_helper.php b/phpBB/phpbb/textformatter/s9e/quote_helper.php new file mode 100644 index 0000000000..24109ac8cc --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/quote_helper.php @@ -0,0 +1,81 @@ +<?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. +* +*/ + +namespace phpbb\textformatter\s9e; + +class quote_helper +{ + /** + * @var string Base URL for a post link, uses {POST_ID} as placeholder + */ + protected $post_url; + + /** + * @var string Base URL for a profile link, uses {USER_ID} as placeholder + */ + protected $profile_url; + + /** + * @var \phpbb\user + */ + protected $user; + + /** + * Constructor + * + * @param \phpbb\user $user + * @param string $root_path + * @param string $php_ext + */ + public function __construct(\phpbb\user $user, $root_path, $php_ext) + { + $this->post_url = append_sid($root_path . 'viewtopic.' . $php_ext, 'p={POST_ID}#p{POST_ID}'); + $this->profile_url = append_sid($root_path . 'memberlist.' . $php_ext, 'mode=viewprofile&u={USER_ID}'); + $this->user = $user; + } + + /** + * Inject dynamic metadata into QUOTE tags in given XML + * + * @param string $xml Original XML + * @return string Modified XML + */ + public function inject_metadata($xml) + { + $post_url = $this->post_url; + $profile_url = $this->profile_url; + $user = $this->user; + + return \s9e\TextFormatter\Utils::replaceAttributes( + $xml, + 'QUOTE', + function ($attributes) use ($post_url, $profile_url, $user) + { + if (isset($attributes['post_id'])) + { + $attributes['post_url'] = str_replace('{POST_ID}', $attributes['post_id'], $post_url); + } + if (isset($attributes['time'])) + { + $attributes['date'] = $user->format_date($attributes['time']); + } + if (isset($attributes['user_id'])) + { + $attributes['profile_url'] = str_replace('{USER_ID}', $attributes['user_id'], $profile_url); + } + + return $attributes; + } + ); + } +} diff --git a/phpBB/phpbb/textformatter/s9e/renderer.php b/phpBB/phpbb/textformatter/s9e/renderer.php index 51bc44f339..2206605ba2 100644 --- a/phpBB/phpbb/textformatter/s9e/renderer.php +++ b/phpBB/phpbb/textformatter/s9e/renderer.php @@ -29,6 +29,11 @@ class renderer implements \phpbb\textformatter\renderer_interface protected $dispatcher; /** + * @var quote_helper + */ + protected $quote_helper; + + /** * @var \s9e\TextFormatter\Renderer */ protected $renderer; @@ -113,6 +118,16 @@ class renderer implements \phpbb\textformatter\renderer_interface } /** + * Configure the quote_helper object used to display extended information in quotes + * + * @param quote_helper $quote_helper + */ + public function configure_quote_helper(quote_helper $quote_helper) + { + $this->quote_helper = $quote_helper; + } + + /** * Automatically set the smilies path based on config * * @param \phpbb\config\config $config @@ -214,6 +229,10 @@ class renderer implements \phpbb\textformatter\renderer_interface */ public function render($xml) { + if (isset($this->quote_helper)) + { + $xml = $this->quote_helper->inject_metadata($xml); + } $renderer = $this; /** diff --git a/phpBB/phpbb/textformatter/s9e/utils.php b/phpBB/phpbb/textformatter/s9e/utils.php index 803c71a5a2..df1966fa32 100644 --- a/phpBB/phpbb/textformatter/s9e/utils.php +++ b/phpBB/phpbb/textformatter/s9e/utils.php @@ -64,6 +64,7 @@ class utils implements \phpbb\textformatter\utils_interface $quote .= '=' . $this->enquote($attributes['author']); unset($attributes['author']); } + ksort($attributes); foreach ($attributes as $name => $value) { $quote .= ' ' . $name . '=' . $this->enquote($value); diff --git a/phpBB/posting.php b/phpBB/posting.php index 2f9beefcf9..327004b1bf 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -1605,7 +1605,12 @@ if ($generate_quote) { $message_parser->message = $phpbb_container->get('text_formatter.utils')->generate_quote( censor_text($message_parser->message), - array('author' => $post_data['quote_username']) + array( + 'author' => $post_data['quote_username'], + 'post_id' => $post_data['post_id'], + 'time' => $post_data['post_time'], + 'user_id' => $post_data['poster_id'], + ) ); $message_parser->message .= "\n\n"; } diff --git a/phpBB/styles/prosilver/template/bbcode.html b/phpBB/styles/prosilver/template/bbcode.html index af8e6ae4b0..6adbdb6aba 100644 --- a/phpBB/styles/prosilver/template/bbcode.html +++ b/phpBB/styles/prosilver/template/bbcode.html @@ -11,6 +11,39 @@ <!-- 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 quote_extended --> +<blockquote> + <xsl:if test="not(@author)"> + <xsl:attribute name="class">uncited</xsl:attribute> + </xsl:if> + <div> + <xsl:if test="@author"> + <cite> + <xsl:if test="@date"><xsl:value-of select="@date"/> </xsl:if> + <xsl:choose> + <xsl:when test="@url"> + <a href="{@url}" class="postlink"><xsl:value-of select="@author"/></a> + </xsl:when> + <xsl:when test="@profile_url"> + <a href="{@profile_url}"><xsl:value-of select="@author"/></a> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@author"/> + </xsl:otherwise> + </xsl:choose> + <xsl:text> </xsl:text> + <xsl:value-of select="$L_WROTE"/> + <xsl:value-of select="$L_COLON"/> + <xsl:if test="@post_url"> + <xsl:text> </xsl:text> + <a href="{@post_url}" data-post-id="{@post_id}" onclick="if(document.getElementById(hash.substr(1)))href=hash">↑</a> + </xsl:if> + </cite> + </xsl:if> + <xsl:apply-templates/> + </div> +</blockquote> +<!-- END quote_extended --> <!-- BEGIN code_open --><div class="codebox"><p>{L_CODE}{L_COLON} <a href="#" onclick="selectCode(this); return false;">{L_SELECT_ALL_CODE}</a></p><pre><code><!-- END code_open --> <!-- BEGIN code_close --></code></pre></div><!-- END code_close --> diff --git a/tests/functional/posting_test.php b/tests/functional/posting_test.php index ccfeb10deb..d9e6cc5ab3 100644 --- a/tests/functional/posting_test.php +++ b/tests/functional/posting_test.php @@ -75,7 +75,7 @@ class phpbb_functional_posting_test extends phpbb_functional_test_case public function test_quote() { $text = 'Test post </textarea>"\' &&amp;'; - $expected = '[quote="admin"]' . $text . '[/quote]'; + $expected = '([quote="admin"[^]]*\\]' . preg_quote($text) . '\\[/quote\\])'; $this->login(); $topic = $this->create_topic(2, 'Test Topic 1', 'Test topic'); @@ -83,7 +83,7 @@ class phpbb_functional_posting_test extends phpbb_functional_test_case $crawler = self::request('GET', "posting.php?mode=quote&f=2&t={$post['topic_id']}&p={$post['post_id']}&sid={$this->sid}"); - $this->assertContains($expected, $crawler->filter('textarea#message')->text()); + $this->assertRegexp($expected, $crawler->filter('textarea#message')->text()); } /** @@ -93,10 +93,10 @@ class phpbb_functional_posting_test extends phpbb_functional_test_case { $text = '0[quote]1[quote]2[/quote]1[/quote]0'; $expected = array( - 0 => '[quote="admin"]0[quote]1[quote]2[/quote]1[/quote]0[/quote]', - 1 => '[quote="admin"]00[/quote]', - 2 => '[quote="admin"]0[quote]11[/quote]0[/quote]', - 3 => '[quote="admin"]0[quote]1[quote]2[/quote]1[/quote]0[/quote]', + 0 => '0[quote]1[quote]2[/quote]1[/quote]0', + 1 => '00', + 2 => '0[quote]11[/quote]0', + 3 => '0[quote]1[quote]2[/quote]1[/quote]0', ); $this->login(); @@ -109,7 +109,10 @@ class phpbb_functional_posting_test extends phpbb_functional_test_case { $this->set_quote_depth($quote_depth); $crawler = self::request('GET', $quote_url); - $this->assertContains($expected_text, $crawler->filter('textarea#message')->text()); + $this->assertRegexp( + '(\\[quote="admin"[^]]*\\]' . preg_quote($expected_text) . '\\[/quote\\])', + $crawler->filter('textarea#message')->text() + ); } } diff --git a/tests/functional/private_messages_test.php b/tests/functional/private_messages_test.php index be584c20c1..9bfb5bc7ad 100644 --- a/tests/functional/private_messages_test.php +++ b/tests/functional/private_messages_test.php @@ -69,16 +69,30 @@ class phpbb_functional_private_messages_test extends phpbb_functional_test_case public function test_quote_post() { - $text = 'Test post'; - $expected = '[quote="admin"]' . $text . '[/quote]'; + $text = 'Test post'; $this->login(); $topic = $this->create_topic(2, 'Test Topic 1', 'Test topic'); $post = $this->create_post(2, $topic['topic_id'], 'Re: Test Topic 1', $text); + $expected = '(\\[quote="admin" post_id="' . $post['post_id'] . '" time="\\d+" user_id="2"\\]' . $text . '\\[/quote\\])'; + $crawler = self::request('GET', 'ucp.php?i=pm&mode=compose&action=quotepost&p=' . $post['post_id'] . '&sid=' . $this->sid); - $this->assertContains($expected, $crawler->filter('textarea#message')->text()); + $this->assertRegexp($expected, $crawler->filter('textarea#message')->text()); + } + + public function test_quote_pm() + { + $text = 'This is a test private message sent by the testing framework.'; + $expected = '(\\[quote="admin" time="\\d+" user_id="2"\\]' . $text . '\\[/quote\\])'; + + $this->login(); + $message_id = $this->create_private_message('Test', $text, array(2)); + + $crawler = self::request('GET', 'ucp.php?i=pm&mode=compose&action=quote&p=' . $message_id . '&sid=' . $this->sid); + + $this->assertRegexp($expected, $crawler->filter('textarea#message')->text()); } public function test_quote_forward() diff --git a/tests/test_framework/phpbb_test_case_helpers.php b/tests/test_framework/phpbb_test_case_helpers.php index cf530cc5be..62a56ed693 100644 --- a/tests/test_framework/phpbb_test_case_helpers.php +++ b/tests/test_framework/phpbb_test_case_helpers.php @@ -476,11 +476,21 @@ class phpbb_test_case_helpers { $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 = $this->test_case->getMockBuilder('\phpbb\user') + ->setConstructorArgs(array($lang, '\phpbb\datetime')) + ->setMethods(array('format_date')) + ->getMock(); + $user->expects($this->test_case->any()) + ->method('format_date') + ->will($this->test_case->returnCallback(__CLASS__ . '::format_date')); + + $user->date_format = 'Y-m-d H:i:s'; $user->optionset('viewcensors', true); $user->optionset('viewflash', true); $user->optionset('viewimg', true); $user->optionset('viewsmilies', true); + $user->timezone = new \DateTimeZone('UTC'); $container->set('user', $user); } $user->add_lang('common'); @@ -490,6 +500,14 @@ class phpbb_test_case_helpers $user->style = array('style_id' => 1); } + // Create and register a quote_helper + $quote_helper = new \phpbb\textformatter\s9e\quote_helper( + $container->get('user'), + $phpbb_root_path, + $phpEx + ); + $container->set('text_formatter.s9e.quote_helper', $quote_helper); + // Create and register the text_formatter.s9e.parser service and its alias $parser = new \phpbb\textformatter\s9e\parser( $cache, @@ -515,6 +533,7 @@ class phpbb_test_case_helpers $auth = ($container->has('auth')) ? $container->get('auth') : new \phpbb\auth\auth; // Calls configured in services.yml + $renderer->configure_quote_helper($quote_helper); $renderer->configure_smilies_path($config, $container->get('path_helper')); $renderer->configure_user($user, $config, $auth); @@ -528,4 +547,15 @@ class phpbb_test_case_helpers return $container; } + + /** + * Mocked replacement for \phpbb\user::format_date() + * + * @param integer $gmepoch unix timestamp + * @return string + */ + static public function format_date($gmepoch) + { + return gmdate('Y-m-d H:i:s', $gmepoch); + } } diff --git a/tests/text_formatter/s9e/default_formatting_test.php b/tests/text_formatter/s9e/default_formatting_test.php index 67921d5b1e..3f8e375ad1 100644 --- a/tests/text_formatter/s9e/default_formatting_test.php +++ b/tests/text_formatter/s9e/default_formatting_test.php @@ -225,6 +225,28 @@ class phpbb_textformatter_s9e_default_formatting_test extends phpbb_test_case "[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>1971-11-11 11:11:11 Username wrote:</cite>...</div></blockquote>' + ), + array( + '[quote="Username" user_id="123"]...[/quote]', + '<blockquote><div><cite><a href="phpBB/memberlist.php?mode=viewprofile&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>' + ), ); } } |