diff options
124 files changed, 9459 insertions, 1901 deletions
diff --git a/build/sami-all.conf.php b/build/sami-all.conf.php index 68350fee8f..fb1a269206 100644 --- a/build/sami-all.conf.php +++ b/build/sami-all.conf.php @@ -18,13 +18,13 @@ $config['versions'] = Sami\Version\GitVersionCollection::create(__DIR__ . '/../' This would be nice, but currently causes various problems that need debugging. ->addFromTags('release-3.0.*') - ->add('develop-olympus', '3.0-next (olympus)') + ->add('3.0.x', '3.0-next (olympus)') ->addFromTags('release-3.1.*') - ->add('develop-ascraeus', '3.1-next (ascraeus)') - ->add('develop') + ->add('3.1.x', '3.1-next (ascraeus)') + ->add('master') */ - ->add('develop-olympus') - ->add('develop-ascraeus') + ->add('3.0.x') + ->add('3.1.x') ; return new Sami\Sami($iterator, $config); diff --git a/phpBB/composer.json b/phpBB/composer.json index 64d47af172..a11bcad391 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -28,6 +28,7 @@ "php": ">=5.3.9", "lusitanian/oauth": "0.2.*", "patchwork/utf8": "1.1.*", + "s9e/text-formatter": "dev-release/php5.3", "symfony/config": "2.7.*@dev", "symfony/console": "2.7.*@dev", "symfony/dependency-injection": "2.7.*@dev", diff --git a/phpBB/composer.lock b/phpBB/composer.lock index b6957aa667..8a634a1ffd 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "2038bc8bd0fea66b22774ca7bca11a79", + "hash": "25df57c9c90534dcc86d31175b248719", "packages": [ { "name": "lusitanian/oauth", @@ -145,7 +145,7 @@ "Psr\\Log\\": "" } }, - "notification-url": "https://packagist.org/downloads/", + "notification-url": "http://packagist.org/downloads/", "license": [ "MIT" ], @@ -164,6 +164,64 @@ "time": "2012-12-21 11:40:51" }, { + "name": "s9e/text-formatter", + "version": "dev-release/php5.3", + "source": { + "type": "git", + "url": "https://github.com/s9e/TextFormatter.git", + "reference": "4e0d311a3c56d0db4a7789e31457053be8148283" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/4e0d311a3c56d0db4a7789e31457053be8148283", + "reference": "4e0d311a3c56d0db4a7789e31457053be8148283", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "lib-pcre": ">=7.2", + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "Allows international URLs to be accepted by the URL filter", + "ext-json": "Enables the generation of a JavaScript parser", + "ext-mbstring": "Enables some optimizations in the PHP renderer", + "ext-tokenizer": "Enables optimizations in the PHP renderer", + "ext-xsl": "Enables the XSLT renderer", + "ext-zlib": "Enables gzip compression when scraping content via the MediaEmbed plugin" + }, + "type": "library", + "autoload": { + "psr-4": { + "s9e\\TextFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Multi-purpose text formatting and markup library. Plugins offer support for BBCodes, Markdown, emoticons, HTML, embedding media (YouTube, etc...), enhanced typography and more.", + "keywords": [ + "bbcode", + "bbcodes", + "blog", + "censor", + "embed", + "emoji", + "emoticons", + "engine", + "forum", + "html", + "markdown", + "markup", + "media", + "parser", + "shortcodes" + ], + "time": "2015-03-30 22:52:16" + }, + { "name": "symfony/config", "version": "2.7.x-dev", "target-dir": "Symfony/Component/Config", @@ -960,16 +1018,14 @@ "Twig_": "lib/" } }, - "notification-url": "https://packagist.org/downloads/", + "notification-url": "http://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" + "email": "fabien@symfony.com" }, { "name": "Armin Ronacher", @@ -1376,8 +1432,7 @@ "authors": [ { "name": "Michiel Rook", - "email": "mrook@php.net", - "role": "Lead" + "email": "mrook@php.net" }, { "name": "Phing Community", @@ -2530,6 +2585,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { + "s9e/text-formatter": 20, "symfony/config": 20, "symfony/console": 20, "symfony/dependency-injection": 20, diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index db07183832..93e8ddb122 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -13,6 +13,7 @@ imports: - { resource: services_notification.yml } - { resource: services_password.yml } - { resource: services_profilefield.yml } + - { resource: services_text_formatter.yml } - { resource: services_twig.yml } - { resource: services_user.yml } diff --git a/phpBB/config/default/container/services_db.yml b/phpBB/config/default/container/services_db.yml index 6afc5a60a9..ae2707b9a5 100644 --- a/phpBB/config/default/container/services_db.yml +++ b/phpBB/config/default/container/services_db.yml @@ -19,6 +19,67 @@ services: arguments: - @dbal.conn.driver +# ----- DB Extractor ----- + dbal.extractor.factory: + class: phpbb\db\extractor\factory + arguments: + - @dbal.conn.driver + - @service_container + + dbal.extractor: + class: phpbb\db\extractor\extractor_interface + factory: ["@dbal.extractor.factory", get] + +# ----- DB Extractors for different drivers ----- +# Scope MUST be prototype for all the handlers to work correctly. + dbal.extractor.extractors.mssql_extractor: + class: phpbb\db\extractor\mssql_extractor + scope: prototype + arguments: + - %core.root_path% + - @request + - @dbal.conn.driver + + dbal.extractor.extractors.mysql_extractor: + class: phpbb\db\extractor\mysql_extractor + scope: prototype + arguments: + - %core.root_path% + - @request + - @dbal.conn.driver + + dbal.extractor.extractors.oracle_extractor: + class: phpbb\db\extractor\oracle_extractor + scope: prototype + arguments: + - %core.root_path% + - @request + - @dbal.conn.driver + + dbal.extractor.extractors.postgres_extractor: + class: phpbb\db\extractor\postgres_extractor + scope: prototype + arguments: + - %core.root_path% + - @request + - @dbal.conn.driver + + dbal.extractor.extractors.sqlite3_extractor: + class: phpbb\db\extractor\sqlite3_extractor + scope: prototype + arguments: + - %core.root_path% + - @request + - @dbal.conn.driver + + dbal.extractor.extractors.sqlite_extractor: + class: phpbb\db\extractor\sqlite_extractor + scope: prototype + arguments: + - %core.root_path% + - @request + - @dbal.conn.driver + # ----- Migrator ----- migrator: class: phpbb\db\migrator diff --git a/phpBB/config/default/container/services_text_formatter.yml b/phpBB/config/default/container/services_text_formatter.yml new file mode 100644 index 0000000000..972be31b31 --- /dev/null +++ b/phpBB/config/default/container/services_text_formatter.yml @@ -0,0 +1,61 @@ +parameters: + text_formatter.cache.dir: %core.root_path%cache/%core.environment%/ + text_formatter.cache.parser.key: _text_formatter_parser + text_formatter.cache.renderer.key: _text_formatter_renderer + +services: + text_formatter.cache: + alias: text_formatter.s9e.factory + + text_formatter.data_access: + class: phpbb\textformatter\data_access + arguments: + - @dbal.conn + - %tables.bbcodes% + - %tables.smilies% + - %tables.styles% + - %tables.words% + - %core.root_path%styles/ + + text_formatter.parser: + alias: text_formatter.s9e.parser + + text_formatter.renderer: + alias: text_formatter.s9e.renderer + + text_formatter.utils: + alias: text_formatter.s9e.utils + + text_formatter.s9e.factory: + class: phpbb\textformatter\s9e\factory + arguments: + - @text_formatter.data_access + - @cache.driver + - @dispatcher + - %text_formatter.cache.dir% + - %text_formatter.cache.parser.key% + - %text_formatter.cache.renderer.key% + + text_formatter.s9e.parser: + class: phpbb\textformatter\s9e\parser + arguments: + - @cache.driver + - %text_formatter.cache.parser.key% + - @user + - @text_formatter.s9e.factory + - @dispatcher + + text_formatter.s9e.renderer: + class: phpbb\textformatter\s9e\renderer + arguments: + - @cache.driver + - %text_formatter.cache.dir% + - %text_formatter.cache.renderer.key% + - @text_formatter.s9e.factory + - @dispatcher + calls: + - [configure_smilies_path, [@config, @path_helper]] + - [configure_user, [@user, @config, @auth]] + + text_formatter.s9e.utils: + class: phpbb\textformatter\s9e\utils diff --git a/phpBB/config/default/container/tables.yml b/phpBB/config/default/container/tables.yml index 2fe2a33be8..00067d5abe 100644 --- a/phpBB/config/default/container/tables.yml +++ b/phpBB/config/default/container/tables.yml @@ -1,6 +1,7 @@ parameters: tables.auth_provider_oauth_token_storage: %core.table_prefix%oauth_tokens tables.auth_provider_oauth_account_assoc: %core.table_prefix%oauth_accounts + tables.bbcodes: %core.table_prefix%bbcodes tables.captcha_qa_questions: %core.table_prefix%captcha_questions tables.captcha_qa_answers: %core.table_prefix%captcha_answers tables.captcha_qa_confirm: %core.table_prefix%qa_confirm @@ -18,6 +19,9 @@ parameters: tables.profile_fields_options_language: %core.table_prefix%profile_fields_lang tables.profile_fields_language: %core.table_prefix%profile_lang tables.posts: %core.table_prefix%posts + tables.smilies: %core.table_prefix%smilies + tables.styles: %core.table_prefix%styles tables.topics: %core.table_prefix%topics tables.user_notifications: %core.table_prefix%user_notifications tables.users: %core.table_prefix%users + tables.words: %core.table_prefix%words diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 9710014b56..27275d4cfc 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -49,7 +49,8 @@ <ol> <li><a href="#changelog">Changelog</a> <ul> - <li><a href="#v312">Changes since 3.1.3-RC1</a></li> + <li><a href="#v313">Changes since 3.1.3</a></li> + <li><a href="#v313rc1">Changes since 3.1.3-RC1</a></li> <li><a href="#v312">Changes since 3.1.2</a></li> <li><a href="#v311">Changes since 3.1.1</a></li> <li><a href="#v310">Changes since 3.1.0</a></li> @@ -109,6 +110,120 @@ <div class="content"> + <a name="v313"></a><h3>Changes since 3.1.3</h3> + + <h4>Bug</h4> + <ul> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-8050">PHPBB3-8050</a>] - Avatar & Long PM recipients list break out of template</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-8250">PHPBB3-8250</a>] - Forum selections in MCP queue not working</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-8494">PHPBB3-8494</a>] - Cannot install two boards on the same postgresql database</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11424">PHPBB3-11424</a>] - Quick-Mod Tools race condition results in NO_MODE</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12368">PHPBB3-12368</a>] - Updating database fails in upgrade from 3.0 when trying twice without purging the cache</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13348">PHPBB3-13348</a>] - sql_freeresult() should be called in feed base class</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13414">PHPBB3-13414</a>] - download/file.php 304 Not Modified bug</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13433">PHPBB3-13433</a>] - A dot in email address leads to unwanted extraneous dot</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13463">PHPBB3-13463</a>] - Mark read icon displays on wrong side in RTL</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13469">PHPBB3-13469</a>] - Soft delete fails with error message</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13472">PHPBB3-13472</a>] - Notification for admin activation of user doesn't get pruned after the user is deleted</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13477">PHPBB3-13477</a>] - File caching of extensions' version check file doesn't work</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13493">PHPBB3-13493</a>] - $helper->route gives wrong path for guests with trailing slashes and mod_rewrite disabled</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13522">PHPBB3-13522</a>] - Q&A Captcha ACP, admins can add blank answers</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13538">PHPBB3-13538</a>] - Add tests for pagination in nested loops</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13542">PHPBB3-13542</a>] - Add $error to core UCP event for better validating of new UCP options</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13550">PHPBB3-13550</a>] - Invalid JSON response returned when plupload dir is not writable</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13551">PHPBB3-13551</a>] - Authentication method- LDAP- entered value 'ldap base dn' does not display</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13555">PHPBB3-13555</a>] - Poll options preview rendered incorrectly by <br /> collision</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13563">PHPBB3-13563</a>] - No Private Message button shown in memberlist for subsilver2</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13568">PHPBB3-13568</a>] - Imagick path validated as relative path although ACP asks for absolute path</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13569">PHPBB3-13569</a>] - Use sql_freeresult for $result assignments and remove unneeded $result assignments</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13570">PHPBB3-13570</a>] - Mysqli extension supports persistent connection since PHP 5.3.0</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13577">PHPBB3-13577</a>] - Skip tests requiring fileinfo if fileinfo is not enabled</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13586">PHPBB3-13586</a>] - Allow '0' as username with Jabber notifications</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13587">PHPBB3-13587</a>] - SQL syntax errors in get_prune_users()</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13588">PHPBB3-13588</a>] - Information message for disabled fsockopen() is not displayed correctly</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13590">PHPBB3-13590</a>] - Remember me login keys should be centered</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13597">PHPBB3-13597</a>] - Modify variable-variable syntax to be compatible with PHP7</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13612">PHPBB3-13612</a>] - Functional test of extension fails if ext requires page refresh</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13615">PHPBB3-13615</a>] - Avatar Gallery shows categories but no images in subsilver2</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13617">PHPBB3-13617</a>] - Bot session continuation with invalid f= query paramter causes SQL error</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13618">PHPBB3-13618</a>] - Small grammatical typo in the English FAQ regarding COPPA</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13631">PHPBB3-13631</a>] - Fix variable name in core.phpbb_content_visibility_get_global_visibility_before event</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13639">PHPBB3-13639</a>] - Unused class icon-search-advanced references nonexistent image</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13644">PHPBB3-13644</a>] - Type hint dispatcher_interface instead of dispatcher</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13649">PHPBB3-13649</a>] - Subforum tooltip always displays "no unread posts" on viewforum.php</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13655">PHPBB3-13655</a>] - $phpbb_dispatcher undefined in phpbb_mcp_sorting()</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13657">PHPBB3-13657</a>] - Start testing against PHP7</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13666">PHPBB3-13666</a>] - data-clicked attribute is not always removed on ajax form submissions</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13667">PHPBB3-13667</a>] - Big buttons are incorrectly aligned in Chrome on Windows</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13670">PHPBB3-13670</a>] - Fix fatal function name must be a string in functional tests</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13698">PHPBB3-13698</a>] - Incorrect password message shows unparsed "Board Administrator"-link</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13702">PHPBB3-13702</a>] - Page is zoomed in by default on iOS devices in landscape mode</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13703">PHPBB3-13703</a>] - Uploaded avatars are not loading correctly when passing through the events</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13719">PHPBB3-13719</a>] - Remove superfluous $search_options in acp_search.php </li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13721">PHPBB3-13721</a>] - URL Rewriting doesn't work on IIS7</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13723">PHPBB3-13723</a>] - Update docs/AUTHORS for 3.1.4-RC1</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13726">PHPBB3-13726</a>] - Responsive breadcrumbs JavaScript incorrectly calculates width of hidden items</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13727">PHPBB3-13727</a>] - Responsive breadcrumbs JavaScript doesn't reset wrap- classes when resizing</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13732">PHPBB3-13732</a>] - Update composer for PHP7 compatibility</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13736">PHPBB3-13736</a>] - Replace colons with colon lang keys in Contact us page</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13738">PHPBB3-13738</a>] - Sami still refers to develop-* branches</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13741">PHPBB3-13741</a>] - Remove outdated comments in CSS files</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13742">PHPBB3-13742</a>] - Local avatar driver is not generating correct urls on index</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13743">PHPBB3-13743</a>] - Missing global vars $phpbb_root_path and $phpEx in message_parser.php</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13747">PHPBB3-13747</a>] - Fix test_validate_path_linux method</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13749">PHPBB3-13749</a>] - Add missing slash to base uri in helper route tests</li> + </ul> + <h4>Improvement</h4> + <ul> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13313">PHPBB3-13313</a>] - Add a core php event to the mass email form</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13467">PHPBB3-13467</a>] - Add a CONTRIBUTING file to the project on Github</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13510">PHPBB3-13510</a>] - Add template event before/after the pagination on viewtopic</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13512">PHPBB3-13512</a>] - Add template events to viewtopic_body.html before/after the post details</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13518">PHPBB3-13518</a>] - Add core event to markread() in functions.php</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13532">PHPBB3-13532</a>] - Add core event to get_unread_topics() in functions.php</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13533">PHPBB3-13533</a>] - Add template events to the header of search_results.html</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13535">PHPBB3-13535</a>] - Add ucp_profile.php core event to allow modifying account settings on editing</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13536">PHPBB3-13536</a>] - Add UCP/ACP core events to allow modifying user profile data on editing</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13537">PHPBB3-13537</a>] - Add core events on mcp_queue for approval and disapproval</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13540">PHPBB3-13540</a>] - Add events to the topic review while posting and moderating posts</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13578">PHPBB3-13578</a>] - Add ucp_register.php core event</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13591">PHPBB3-13591</a>] - Add functions.php core event to the function obtain_users_online_string()</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13595">PHPBB3-13595</a>] - Remove unused instances of the bbcode class</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13596">PHPBB3-13596</a>] - Add display_forums() core event to allow modifying forums list data</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13600">PHPBB3-13600</a>] - Add core event to allow extensions to create a custom help page</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13602">PHPBB3-13602</a>] - Add template event overall_header_navbar_before</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13628">PHPBB3-13628</a>] - Add template events into ucp profile html files</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13635">PHPBB3-13635</a>] - Add sql_ary to UCP profile event</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13637">PHPBB3-13637</a>] - Add php event for modifying the data when composing a PM</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13643">PHPBB3-13643</a>] - kernel_terminate_subscriber should have a very low priority</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13650">PHPBB3-13650</a>] - New core event for UCP profile mode</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13658">PHPBB3-13658</a>] - [Event] - Before and after deletion of topics</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13675">PHPBB3-13675</a>] - Add validate to acp_profile event and add template events</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13679">PHPBB3-13679</a>] - Add template event overall_header_searchbox_before</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13701">PHPBB3-13701</a>] - New posting_pm_layout.html template events to wrap "include posting_pm_header.html"</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13710">PHPBB3-13710</a>] - Add template events around smilies display</li> + </ul> + <h4>New Feature</h4> + <ul> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13336">PHPBB3-13336</a>] - New core events for user activation</li> + </ul> + <h4>Sub-task</h4> + <ul> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13142">PHPBB3-13142</a>] - [Event] - Before query to list unapproved and deleted posts</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13592">PHPBB3-13592</a>] - Add core event to allow changing get_visibility_sql's result</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13621">PHPBB3-13621</a>] - Fix event phpbb_content_visibility_get_forums_visibility_before to get where_sql working as specified</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13625">PHPBB3-13625</a>] - Add more variables to core.viewforum_get_topic_data</li> + </ul> + <h4>Task</h4> + <ul> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-9457">PHPBB3-9457</a>] - [Accessibility] - Add WAI-ARIA landmarks to the Prosilver template files</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-12599">PHPBB3-12599</a>] - Update documentation styling</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13572">PHPBB3-13572</a>] - Upgrade composer to 1.0.0-alpha9</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13626">PHPBB3-13626</a>] - Add branch aliases</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13634">PHPBB3-13634</a>] - Update README to show new branch names</li> + <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-13640">PHPBB3-13640</a>] - Rearrange order of color css rules</li> + </ul> + <a name="v313rc1"></a><h3>Changes since 3.1.3-RC1</h3> <h4>Bug</h4> diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index 5c4f561a3c..a91c9a8ae8 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -776,6 +776,13 @@ overall_header_head_append * Since: 3.1.0-a1 * Purpose: Add asset calls directly before the `</head>` tag +overall_header_navbar_before +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the navigation bar + overall_header_navigation_append === * Locations: @@ -826,6 +833,13 @@ overall_header_stylesheets_after * Purpose: Add asset calls after stylesheets within the `</head>` tag. Note that INCLUDECSS will not work with this event. +posting_editor_bbcode_status_after +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.4-RC1 +* Purpose: Add content after bbcode status + posting_editor_buttons_after === * Locations: @@ -868,6 +882,20 @@ posting_editor_options_prepend * Since: 3.1.0-a1 * Purpose: Add posting options on the posting screen +posting_editor_smilies_after +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.4-RC1 +* Purpose: Add content after smilies + +posting_editor_smilies_before +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the smilies + posting_editor_subject_after === * Locations: @@ -896,6 +924,27 @@ posting_pm_header_find_username_before * Since: 3.1.0-RC4 * Purpose: Add content before the find username link on composing pm +posting_pm_layout_include_pm_header_after +=== +* Locations: + + styles/prosilver/template/posting_pm_layout.html +* Since: 3.1.4-RC1 +* Purpose: Add content after the include of posting_pm_header.html + +posting_pm_layout_include_pm_header_before +=== +* Locations: + + styles/prosilver/template/posting_pm_layout.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the include of posting_pm_header.html + +posting_poll_body_options_after +=== +* Locations: + + styles/prosilver/template/posting_poll_body.html +* Since: 3.1.4-RC1 +* Purpose: Add content after the poll options on creating a poll + quickreply_editor_panel_after === * Locations: diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index a5cd48c444..d451b4d899 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -25,7 +25,7 @@ class acp_bbcodes function main($id, $mode) { - global $db, $user, $auth, $template, $cache, $request, $phpbb_dispatcher; + global $db, $user, $auth, $template, $cache, $request, $phpbb_dispatcher, $phpbb_container; global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx, $phpbb_log; $user->add_lang('acp/posting'); @@ -269,6 +269,7 @@ class acp_bbcodes $db->sql_query('INSERT INTO ' . BBCODES_TABLE . $db->sql_build_array('INSERT', $sql_ary)); $cache->destroy('sql', BBCODES_TABLE); + $phpbb_container->get('text_formatter.cache')->invalidate(); $lang = 'BBCODE_ADDED'; $log_action = 'LOG_BBCODE_ADD'; @@ -280,6 +281,7 @@ class acp_bbcodes WHERE bbcode_id = ' . $bbcode_id; $db->sql_query($sql); $cache->destroy('sql', BBCODES_TABLE); + $phpbb_container->get('text_formatter.cache')->invalidate(); $lang = 'BBCODE_EDITED'; $log_action = 'LOG_BBCODE_EDIT'; @@ -319,6 +321,7 @@ class acp_bbcodes { $db->sql_query('DELETE FROM ' . BBCODES_TABLE . " WHERE bbcode_id = $bbcode_id"); $cache->destroy('sql', BBCODES_TABLE); + $phpbb_container->get('text_formatter.cache')->invalidate(); $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_BBCODE_DELETE', false, array($row['bbcode_tag'])); if ($request->is_ajax()) diff --git a/phpBB/includes/acp/acp_database.php b/phpBB/includes/acp/acp_database.php index 25cddaa5d4..984301a38f 100644 --- a/phpBB/includes/acp/acp_database.php +++ b/phpBB/includes/acp/acp_database.php @@ -90,36 +90,9 @@ class acp_database $time = time(); $filename = 'backup_' . $time . '_' . unique_id(); - switch ($db->get_sql_layer()) - { - case 'mysqli': - case 'mysql4': - case 'mysql': - $extractor = new mysql_extractor($format, $filename, $time, $download, $store); - break; - - case 'sqlite': - $extractor = new sqlite_extractor($format, $filename, $time, $download, $store); - break; - case 'sqlite3': - $extractor = new sqlite3_extractor($format, $filename, $time, $download, $store); - break; - - case 'postgres': - $extractor = new postgres_extractor($format, $filename, $time, $download, $store); - break; - - case 'oracle': - $extractor = new oracle_extractor($format, $filename, $time, $download, $store); - break; - - case 'mssql': - case 'mssql_odbc': - case 'mssqlnative': - $extractor = new mssql_extractor($format, $filename, $time, $download, $store); - break; - } + $extractor = $phpbb_container->get('dbal.extractor'); + $extractor->init_extractor($format, $filename, $time, $download, $store); $extractor->write_start($table_prefix); @@ -461,1637 +434,6 @@ class acp_database } } -class base_extractor -{ - var $fh; - var $fp; - var $write; - var $close; - var $store; - var $download; - var $time; - var $format; - var $run_comp = false; - - function base_extractor($format, $filename, $time, $download = false, $store = false) - { - global $request; - - $this->download = $download; - $this->store = $store; - $this->time = $time; - $this->format = $format; - - switch ($format) - { - case 'text': - $ext = '.sql'; - $open = 'fopen'; - $this->write = 'fwrite'; - $this->close = 'fclose'; - $mimetype = 'text/x-sql'; - break; - case 'bzip2': - $ext = '.sql.bz2'; - $open = 'bzopen'; - $this->write = 'bzwrite'; - $this->close = 'bzclose'; - $mimetype = 'application/x-bzip2'; - break; - case 'gzip': - $ext = '.sql.gz'; - $open = 'gzopen'; - $this->write = 'gzwrite'; - $this->close = 'gzclose'; - $mimetype = 'application/x-gzip'; - break; - } - - if ($download == true) - { - $name = $filename . $ext; - header('Cache-Control: private, no-cache'); - header("Content-Type: $mimetype; name=\"$name\""); - header("Content-disposition: attachment; filename=$name"); - - switch ($format) - { - case 'bzip2': - ob_start(); - break; - - case 'gzip': - if (strpos($request->header('Accept-Encoding'), 'gzip') !== false && strpos(strtolower($request->header('User-Agent')), 'msie') === false) - { - ob_start('ob_gzhandler'); - } - else - { - $this->run_comp = true; - } - break; - } - } - - if ($store == true) - { - global $phpbb_root_path; - $file = $phpbb_root_path . 'store/' . $filename . $ext; - - $this->fp = $open($file, 'w'); - - if (!$this->fp) - { - trigger_error('FILE_WRITE_FAIL', E_USER_ERROR); - } - } - } - - function write_end() - { - static $close; - - if ($this->store) - { - if ($close === null) - { - $close = $this->close; - } - $close($this->fp); - } - - // bzip2 must be written all the way at the end - if ($this->download && $this->format === 'bzip2') - { - $c = ob_get_clean(); - echo bzcompress($c); - } - } - - function flush($data) - { - static $write; - if ($this->store === true) - { - if ($write === null) - { - $write = $this->write; - } - $write($this->fp, $data); - } - - if ($this->download === true) - { - if ($this->format === 'bzip2' || $this->format === 'text' || ($this->format === 'gzip' && !$this->run_comp)) - { - echo $data; - } - - // we can write the gzip data as soon as we get it - if ($this->format === 'gzip') - { - if ($this->run_comp) - { - echo gzencode($data); - } - else - { - ob_flush(); - flush(); - } - } - } - } -} - -class mysql_extractor extends base_extractor -{ - function write_start($table_prefix) - { - $sql_data = "#\n"; - $sql_data .= "# phpBB Backup Script\n"; - $sql_data .= "# Dump of tables for $table_prefix\n"; - $sql_data .= "# DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; - $sql_data .= "#\n"; - $this->flush($sql_data); - } - - function write_table($table_name) - { - global $db; - static $new_extract; - - if ($new_extract === null) - { - if ($db->get_sql_layer() === 'mysqli' || version_compare($db->sql_server_info(true), '3.23.20', '>=')) - { - $new_extract = true; - } - else - { - $new_extract = false; - } - } - - if ($new_extract) - { - $this->new_write_table($table_name); - } - else - { - $this->old_write_table($table_name); - } - } - - function write_data($table_name) - { - global $db; - if ($db->get_sql_layer() === 'mysqli') - { - $this->write_data_mysqli($table_name); - } - else - { - $this->write_data_mysql($table_name); - } - } - - function write_data_mysqli($table_name) - { - global $db; - $sql = "SELECT * - FROM $table_name"; - $result = mysqli_query($db->get_db_connect_id(), $sql, MYSQLI_USE_RESULT); - if ($result != false) - { - $fields_cnt = mysqli_num_fields($result); - - // Get field information - $field = mysqli_fetch_fields($result); - $field_set = array(); - - for ($j = 0; $j < $fields_cnt; $j++) - { - $field_set[] = $field[$j]->name; - } - - $search = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); - $replace = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); - $fields = implode(', ', $field_set); - $sql_data = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; - $first_set = true; - $query_len = 0; - $max_len = get_usable_memory(); - - while ($row = mysqli_fetch_row($result)) - { - $values = array(); - if ($first_set) - { - $query = $sql_data . '('; - } - else - { - $query .= ',('; - } - - for ($j = 0; $j < $fields_cnt; $j++) - { - if (!isset($row[$j]) || is_null($row[$j])) - { - $values[$j] = 'NULL'; - } - else if (($field[$j]->flags & 32768) && !($field[$j]->flags & 1024)) - { - $values[$j] = $row[$j]; - } - else - { - $values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; - } - } - $query .= implode(', ', $values) . ')'; - - $query_len += strlen($query); - if ($query_len > $max_len) - { - $this->flush($query . ";\n\n"); - $query = ''; - $query_len = 0; - $first_set = true; - } - else - { - $first_set = false; - } - } - mysqli_free_result($result); - - // check to make sure we have nothing left to flush - if (!$first_set && $query) - { - $this->flush($query . ";\n\n"); - } - } - } - - function write_data_mysql($table_name) - { - global $db; - $sql = "SELECT * - FROM $table_name"; - $result = mysql_unbuffered_query($sql, $db->get_db_connect_id()); - - if ($result != false) - { - $fields_cnt = mysql_num_fields($result); - - // Get field information - $field = array(); - for ($i = 0; $i < $fields_cnt; $i++) - { - $field[] = mysql_fetch_field($result, $i); - } - $field_set = array(); - - for ($j = 0; $j < $fields_cnt; $j++) - { - $field_set[] = $field[$j]->name; - } - - $search = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); - $replace = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); - $fields = implode(', ', $field_set); - $sql_data = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; - $first_set = true; - $query_len = 0; - $max_len = get_usable_memory(); - - while ($row = mysql_fetch_row($result)) - { - $values = array(); - if ($first_set) - { - $query = $sql_data . '('; - } - else - { - $query .= ',('; - } - - for ($j = 0; $j < $fields_cnt; $j++) - { - if (!isset($row[$j]) || is_null($row[$j])) - { - $values[$j] = 'NULL'; - } - else if ($field[$j]->numeric && ($field[$j]->type !== 'timestamp')) - { - $values[$j] = $row[$j]; - } - else - { - $values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; - } - } - $query .= implode(', ', $values) . ')'; - - $query_len += strlen($query); - if ($query_len > $max_len) - { - $this->flush($query . ";\n\n"); - $query = ''; - $query_len = 0; - $first_set = true; - } - else - { - $first_set = false; - } - } - mysql_free_result($result); - - // check to make sure we have nothing left to flush - if (!$first_set && $query) - { - $this->flush($query . ";\n\n"); - } - } - } - - function new_write_table($table_name) - { - global $db; - - $sql = 'SHOW CREATE TABLE ' . $table_name; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - - $sql_data = '# Table: ' . $table_name . "\n"; - $sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; - $this->flush($sql_data . $row['Create Table'] . ";\n\n"); - - $db->sql_freeresult($result); - } - - function old_write_table($table_name) - { - global $db; - - $sql_data = '# Table: ' . $table_name . "\n"; - $sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; - $sql_data .= "CREATE TABLE $table_name(\n"; - $rows = array(); - - $sql = "SHOW FIELDS - FROM $table_name"; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $line = ' ' . $row['Field'] . ' ' . $row['Type']; - - if (!is_null($row['Default'])) - { - $line .= " DEFAULT '{$row['Default']}'"; - } - - if ($row['Null'] != 'YES') - { - $line .= ' NOT NULL'; - } - - if ($row['Extra'] != '') - { - $line .= ' ' . $row['Extra']; - } - - $rows[] = $line; - } - $db->sql_freeresult($result); - - $sql = "SHOW KEYS - FROM $table_name"; - - $result = $db->sql_query($sql); - - $index = array(); - while ($row = $db->sql_fetchrow($result)) - { - $kname = $row['Key_name']; - - if ($kname != 'PRIMARY') - { - if ($row['Non_unique'] == 0) - { - $kname = "UNIQUE|$kname"; - } - } - - if ($row['Sub_part']) - { - $row['Column_name'] .= '(' . $row['Sub_part'] . ')'; - } - $index[$kname][] = $row['Column_name']; - } - $db->sql_freeresult($result); - - foreach ($index as $key => $columns) - { - $line = ' '; - - if ($key == 'PRIMARY') - { - $line .= 'PRIMARY KEY (' . implode(', ', $columns) . ')'; - } - else if (strpos($key, 'UNIQUE') === 0) - { - $line .= 'UNIQUE ' . substr($key, 7) . ' (' . implode(', ', $columns) . ')'; - } - else if (strpos($key, 'FULLTEXT') === 0) - { - $line .= 'FULLTEXT ' . substr($key, 9) . ' (' . implode(', ', $columns) . ')'; - } - else - { - $line .= "KEY $key (" . implode(', ', $columns) . ')'; - } - - $rows[] = $line; - } - - $sql_data .= implode(",\n", $rows); - $sql_data .= "\n);\n\n"; - - $this->flush($sql_data); - } -} - -class sqlite_extractor extends base_extractor -{ - function write_start($prefix) - { - $sql_data = "--\n"; - $sql_data .= "-- phpBB Backup Script\n"; - $sql_data .= "-- Dump of tables for $prefix\n"; - $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; - $sql_data .= "--\n"; - $sql_data .= "BEGIN TRANSACTION;\n"; - $this->flush($sql_data); - } - - function write_table($table_name) - { - global $db; - $sql_data = '-- Table: ' . $table_name . "\n"; - $sql_data .= "DROP TABLE $table_name;\n"; - - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '" . $db->sql_escape($table_name) . "' - ORDER BY type DESC, name;"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - // Create Table - $sql_data .= $row['sql'] . ";\n"; - - $result = $db->sql_query("PRAGMA index_list('" . $db->sql_escape($table_name) . "');"); - - $ar = array(); - while ($row = $db->sql_fetchrow($result)) - { - $ar[] = $row; - } - $db->sql_freeresult($result); - - foreach ($ar as $value) - { - if (strpos($value['name'], 'autoindex') !== false) - { - continue; - } - - $result = $db->sql_query("PRAGMA index_info('" . $db->sql_escape($value['name']) . "');"); - - $fields = array(); - while ($row = $db->sql_fetchrow($result)) - { - $fields[] = $row['name']; - } - $db->sql_freeresult($result); - - $sql_data .= 'CREATE ' . ($value['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $value['name'] . ' on ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; - } - - $this->flush($sql_data . "\n"); - } - - function write_data($table_name) - { - global $db; - - $col_types = sqlite_fetch_column_types($db->get_db_connect_id(), $table_name); - - $sql = "SELECT * - FROM $table_name"; - $result = sqlite_unbuffered_query($db->get_db_connect_id(), $sql); - $rows = sqlite_fetch_all($result, SQLITE_ASSOC); - $sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; - foreach ($rows as $row) - { - foreach ($row as $column_name => $column_data) - { - if (is_null($column_data)) - { - $row[$column_name] = 'NULL'; - } - else if ($column_data == '') - { - $row[$column_name] = "''"; - } - else if (strpos($col_types[$column_name], 'text') !== false || strpos($col_types[$column_name], 'char') !== false || strpos($col_types[$column_name], 'blob') !== false) - { - $row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); - } - } - $this->flush($sql_insert . implode(', ', $row) . ");\n"); - } - } - - function write_end() - { - $this->flush("COMMIT;\n"); - parent::write_end(); - } -} - -class sqlite3_extractor extends base_extractor -{ - function write_start($prefix) - { - $sql_data = "--\n"; - $sql_data .= "-- phpBB Backup Script\n"; - $sql_data .= "-- Dump of tables for $prefix\n"; - $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; - $sql_data .= "--\n"; - $sql_data .= "BEGIN TRANSACTION;\n"; - $this->flush($sql_data); - } - - function write_table($table_name) - { - global $db; - $sql_data = '-- Table: ' . $table_name . "\n"; - $sql_data .= "DROP TABLE $table_name;\n"; - - $sql = "SELECT sql - FROM sqlite_master - WHERE type = 'table' - AND name = '" . $db->sql_escape($table_name) . "' - ORDER BY name ASC;"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - // Create Table - $sql_data .= $row['sql'] . ";\n"; - - $result = $db->sql_query("PRAGMA index_list('" . $db->sql_escape($table_name) . "');"); - - while ($row = $db->sql_fetchrow($result)) - { - if (strpos($row['name'], 'autoindex') !== false) - { - continue; - } - - $result2 = $db->sql_query("PRAGMA index_info('" . $db->sql_escape($row['name']) . "');"); - - $fields = array(); - while ($row2 = $db->sql_fetchrow($result2)) - { - $fields[] = $row2['name']; - } - $db->sql_freeresult($result2); - - $sql_data .= 'CREATE ' . ($row['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $row['name'] . ' ON ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; - } - $db->sql_freeresult($result); - - $this->flush($sql_data . "\n"); - } - - function write_data($table_name) - { - global $db; - - $result = $db->sql_query("PRAGMA table_info('" . $db->sql_escape($table_name) . "');"); - - $col_types = array(); - while ($row = $db->sql_fetchrow($result)) - { - $col_types[$row['name']] = $row['type']; - } - $db->sql_freeresult($result); - - $sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; - - $sql = "SELECT * - FROM $table_name"; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - foreach ($row as $column_name => $column_data) - { - if (is_null($column_data)) - { - $row[$column_name] = 'NULL'; - } - else if ($column_data === '') - { - $row[$column_name] = "''"; - } - else if (stripos($col_types[$column_name], 'text') !== false || stripos($col_types[$column_name], 'char') !== false || stripos($col_types[$column_name], 'blob') !== false) - { - $row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); - } - } - $this->flush($sql_insert . implode(', ', $row) . ");\n"); - } - } - - function write_end() - { - $this->flush("COMMIT;\n"); - parent::write_end(); - } -} - -class postgres_extractor extends base_extractor -{ - function write_start($prefix) - { - $sql_data = "--\n"; - $sql_data .= "-- phpBB Backup Script\n"; - $sql_data .= "-- Dump of tables for $prefix\n"; - $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; - $sql_data .= "--\n"; - $sql_data .= "BEGIN TRANSACTION;\n"; - $this->flush($sql_data); - } - - function write_table($table_name) - { - global $db; - static $domains_created = array(); - - $sql = "SELECT a.domain_name, a.data_type, a.character_maximum_length, a.domain_default - FROM INFORMATION_SCHEMA.domains a, INFORMATION_SCHEMA.column_domain_usage b - WHERE a.domain_name = b.domain_name - AND b.table_name = '{$table_name}'"; - $result = $db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) - { - if (empty($domains_created[$row['domain_name']])) - { - $domains_created[$row['domain_name']] = true; - //$sql_data = "DROP DOMAIN {$row['domain_name']};\n"; - $sql_data = "CREATE DOMAIN {$row['domain_name']} as {$row['data_type']}"; - if (!empty($row['character_maximum_length'])) - { - $sql_data .= '(' . $row['character_maximum_length'] . ')'; - } - $sql_data .= ' NOT NULL'; - if (!empty($row['domain_default'])) - { - $sql_data .= ' DEFAULT ' . $row['domain_default']; - } - $this->flush($sql_data . ";\n"); - } - } - - $sql_data = '-- Table: ' . $table_name . "\n"; - $sql_data .= "DROP TABLE $table_name;\n"; - // PGSQL does not "tightly" bind sequences and tables, we must guess... - $sql = "SELECT relname - FROM pg_class - WHERE relkind = 'S' - AND relname = '{$table_name}_seq'"; - $result = $db->sql_query($sql); - // We don't even care about storing the results. We already know the answer if we get rows back. - if ($db->sql_fetchrow($result)) - { - $sql_data .= "DROP SEQUENCE {$table_name}_seq;\n"; - $sql_data .= "CREATE SEQUENCE {$table_name}_seq;\n"; - } - $db->sql_freeresult($result); - - $field_query = "SELECT a.attnum, a.attname as field, t.typname as type, a.attlen as length, a.atttypmod as lengthvar, a.attnotnull as notnull - FROM pg_class c, pg_attribute a, pg_type t - WHERE c.relname = '" . $db->sql_escape($table_name) . "' - AND a.attnum > 0 - AND a.attrelid = c.oid - AND a.atttypid = t.oid - ORDER BY a.attnum"; - $result = $db->sql_query($field_query); - - $sql_data .= "CREATE TABLE $table_name(\n"; - $lines = array(); - while ($row = $db->sql_fetchrow($result)) - { - // Get the data from the table - $sql_get_default = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault - FROM pg_attrdef d, pg_class c - WHERE (c.relname = '" . $db->sql_escape($table_name) . "') - AND (c.oid = d.adrelid) - AND d.adnum = " . $row['attnum']; - $def_res = $db->sql_query($sql_get_default); - $def_row = $db->sql_fetchrow($def_res); - $db->sql_freeresult($def_res); - - if (empty($def_row)) - { - unset($row['rowdefault']); - } - else - { - $row['rowdefault'] = $def_row['rowdefault']; - } - - if ($row['type'] == 'bpchar') - { - // Internally stored as bpchar, but isn't accepted in a CREATE TABLE statement. - $row['type'] = 'char'; - } - - $line = ' ' . $row['field'] . ' ' . $row['type']; - - if (strpos($row['type'], 'char') !== false) - { - if ($row['lengthvar'] > 0) - { - $line .= '(' . ($row['lengthvar'] - 4) . ')'; - } - } - - if (strpos($row['type'], 'numeric') !== false) - { - $line .= '('; - $line .= sprintf("%s,%s", (($row['lengthvar'] >> 16) & 0xffff), (($row['lengthvar'] - 4) & 0xffff)); - $line .= ')'; - } - - if (isset($row['rowdefault'])) - { - $line .= ' DEFAULT ' . $row['rowdefault']; - } - - if ($row['notnull'] == 't') - { - $line .= ' NOT NULL'; - } - - $lines[] = $line; - } - $db->sql_freeresult($result); - - // Get the listing of primary keys. - $sql_pri_keys = "SELECT ic.relname as index_name, bc.relname as tab_name, ta.attname as column_name, i.indisunique as unique_key, i.indisprimary as primary_key - FROM pg_class bc, pg_class ic, pg_index i, pg_attribute ta, pg_attribute ia - WHERE (bc.oid = i.indrelid) - AND (ic.oid = i.indexrelid) - AND (ia.attrelid = i.indexrelid) - AND (ta.attrelid = bc.oid) - AND (bc.relname = '" . $db->sql_escape($table_name) . "') - AND (ta.attrelid = i.indrelid) - AND (ta.attnum = i.indkey[ia.attnum-1]) - ORDER BY index_name, tab_name, column_name"; - - $result = $db->sql_query($sql_pri_keys); - - $index_create = $index_rows = $primary_key = array(); - - // We do this in two steps. It makes placing the comma easier - while ($row = $db->sql_fetchrow($result)) - { - if ($row['primary_key'] == 't') - { - $primary_key[] = $row['column_name']; - $primary_key_name = $row['index_name']; - } - else - { - // We have to store this all this info because it is possible to have a multi-column key... - // we can loop through it again and build the statement - $index_rows[$row['index_name']]['table'] = $table_name; - $index_rows[$row['index_name']]['unique'] = ($row['unique_key'] == 't') ? true : false; - $index_rows[$row['index_name']]['column_names'][] = $row['column_name']; - } - } - $db->sql_freeresult($result); - - if (!empty($index_rows)) - { - foreach ($index_rows as $idx_name => $props) - { - $index_create[] = 'CREATE ' . ($props['unique'] ? 'UNIQUE ' : '') . "INDEX $idx_name ON $table_name (" . implode(', ', $props['column_names']) . ");"; - } - } - - if (!empty($primary_key)) - { - $lines[] = " CONSTRAINT $primary_key_name PRIMARY KEY (" . implode(', ', $primary_key) . ")"; - } - - // Generate constraint clauses for CHECK constraints - $sql_checks = "SELECT conname as index_name, consrc - FROM pg_constraint, pg_class bc - WHERE conrelid = bc.oid - AND bc.relname = '" . $db->sql_escape($table_name) . "' - AND NOT EXISTS ( - SELECT * - FROM pg_constraint as c, pg_inherits as i - WHERE i.inhrelid = pg_constraint.conrelid - AND c.conname = pg_constraint.conname - AND c.consrc = pg_constraint.consrc - AND c.conrelid = i.inhparent - )"; - $result = $db->sql_query($sql_checks); - - // Add the constraints to the sql file. - while ($row = $db->sql_fetchrow($result)) - { - if (!is_null($row['consrc'])) - { - $lines[] = ' CONSTRAINT ' . $row['index_name'] . ' CHECK ' . $row['consrc']; - } - } - $db->sql_freeresult($result); - - $sql_data .= implode(", \n", $lines); - $sql_data .= "\n);\n"; - - if (!empty($index_create)) - { - $sql_data .= implode("\n", $index_create) . "\n\n"; - } - $this->flush($sql_data); - } - - function write_data($table_name) - { - global $db; - // Grab all of the data from current table. - $sql = "SELECT * - FROM $table_name"; - $result = $db->sql_query($sql); - - $i_num_fields = pg_num_fields($result); - $seq = ''; - - for ($i = 0; $i < $i_num_fields; $i++) - { - $ary_type[] = pg_field_type($result, $i); - $ary_name[] = pg_field_name($result, $i); - - $sql = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault - FROM pg_attrdef d, pg_class c - WHERE (c.relname = '{$table_name}') - AND (c.oid = d.adrelid) - AND d.adnum = " . strval($i + 1); - $result2 = $db->sql_query($sql); - if ($row = $db->sql_fetchrow($result2)) - { - // Determine if we must reset the sequences - if (strpos($row['rowdefault'], "nextval('") === 0) - { - $seq .= "SELECT SETVAL('{$table_name}_seq',(select case when max({$ary_name[$i]})>0 then max({$ary_name[$i]})+1 else 1 end FROM {$table_name}));\n"; - } - } - } - - $this->flush("COPY $table_name (" . implode(', ', $ary_name) . ') FROM stdin;' . "\n"); - while ($row = $db->sql_fetchrow($result)) - { - $schema_vals = array(); - - // Build the SQL statement to recreate the data. - for ($i = 0; $i < $i_num_fields; $i++) - { - $str_val = $row[$ary_name[$i]]; - - if (preg_match('#char|text|bool|bytea#i', $ary_type[$i])) - { - $str_val = str_replace(array("\n", "\t", "\r", "\b", "\f", "\v"), array('\n', '\t', '\r', '\b', '\f', '\v'), addslashes($str_val)); - $str_empty = ''; - } - else - { - $str_empty = '\N'; - } - - if (empty($str_val) && $str_val !== '0') - { - $str_val = $str_empty; - } - - $schema_vals[] = $str_val; - } - - // Take the ordered fields and their associated data and build it - // into a valid sql statement to recreate that field in the data. - $this->flush(implode("\t", $schema_vals) . "\n"); - } - $db->sql_freeresult($result); - $this->flush("\\.\n"); - - // Write out the sequence statements - $this->flush($seq); - } - - function write_end() - { - $this->flush("COMMIT;\n"); - parent::write_end(); - } -} - -class mssql_extractor extends base_extractor -{ - function write_end() - { - $this->flush("COMMIT\nGO\n"); - parent::write_end(); - } - - function write_start($prefix) - { - $sql_data = "--\n"; - $sql_data .= "-- phpBB Backup Script\n"; - $sql_data .= "-- Dump of tables for $prefix\n"; - $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; - $sql_data .= "--\n"; - $sql_data .= "BEGIN TRANSACTION\n"; - $sql_data .= "GO\n"; - $this->flush($sql_data); - } - - function write_table($table_name) - { - global $db; - $sql_data = '-- Table: ' . $table_name . "\n"; - $sql_data .= "IF OBJECT_ID(N'$table_name', N'U') IS NOT NULL\n"; - $sql_data .= "DROP TABLE $table_name;\n"; - $sql_data .= "GO\n"; - $sql_data .= "\nCREATE TABLE [$table_name] (\n"; - $rows = array(); - - $text_flag = false; - - $sql = "SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as IS_IDENTITY - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_NAME = '$table_name'"; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $line = "\t[{$row['COLUMN_NAME']}] [{$row['DATA_TYPE']}]"; - - if ($row['DATA_TYPE'] == 'text') - { - $text_flag = true; - } - - if ($row['IS_IDENTITY']) - { - $line .= ' IDENTITY (1 , 1)'; - } - - if ($row['CHARACTER_MAXIMUM_LENGTH'] && $row['DATA_TYPE'] !== 'text') - { - $line .= ' (' . $row['CHARACTER_MAXIMUM_LENGTH'] . ')'; - } - - if ($row['IS_NULLABLE'] == 'YES') - { - $line .= ' NULL'; - } - else - { - $line .= ' NOT NULL'; - } - - if ($row['COLUMN_DEFAULT']) - { - $line .= ' DEFAULT ' . $row['COLUMN_DEFAULT']; - } - - $rows[] = $line; - } - $db->sql_freeresult($result); - - $sql_data .= implode(",\n", $rows); - $sql_data .= "\n) ON [PRIMARY]"; - - if ($text_flag) - { - $sql_data .= " TEXTIMAGE_ON [PRIMARY]"; - } - - $sql_data .= "\nGO\n\n"; - $rows = array(); - - $sql = "SELECT CONSTRAINT_NAME, COLUMN_NAME - FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE - WHERE TABLE_NAME = '$table_name'"; - $result = $db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) - { - if (!sizeof($rows)) - { - $sql_data .= "ALTER TABLE [$table_name] WITH NOCHECK ADD\n"; - $sql_data .= "\tCONSTRAINT [{$row['CONSTRAINT_NAME']}] PRIMARY KEY CLUSTERED \n\t(\n"; - } - $rows[] = "\t\t[{$row['COLUMN_NAME']}]"; - } - if (sizeof($rows)) - { - $sql_data .= implode(",\n", $rows); - $sql_data .= "\n\t) ON [PRIMARY] \nGO\n"; - } - $db->sql_freeresult($result); - - $index = array(); - $sql = "EXEC sp_statistics '$table_name'"; - $result = $db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) - { - if ($row['TYPE'] == 3) - { - $index[$row['INDEX_NAME']][] = '[' . $row['COLUMN_NAME'] . ']'; - } - } - $db->sql_freeresult($result); - - foreach ($index as $index_name => $column_name) - { - $index[$index_name] = implode(', ', $column_name); - } - - foreach ($index as $index_name => $columns) - { - $sql_data .= "\nCREATE INDEX [$index_name] ON [$table_name]($columns) ON [PRIMARY]\nGO\n"; - } - $this->flush($sql_data); - } - - function write_data($table_name) - { - global $db; - - if ($db->get_sql_layer() === 'mssql') - { - $this->write_data_mssql($table_name); - } - else if($db->get_sql_layer() === 'mssqlnative') - { - $this->write_data_mssqlnative($table_name); - } - else - { - $this->write_data_odbc($table_name); - } - } - - function write_data_mssql($table_name) - { - global $db; - $ary_type = $ary_name = array(); - $ident_set = false; - $sql_data = ''; - - // Grab all of the data from current table. - $sql = "SELECT * - FROM $table_name"; - $result = $db->sql_query($sql); - - $retrieved_data = mssql_num_rows($result); - - $i_num_fields = mssql_num_fields($result); - - for ($i = 0; $i < $i_num_fields; $i++) - { - $ary_type[$i] = mssql_field_type($result, $i); - $ary_name[$i] = mssql_field_name($result, $i); - } - - if ($retrieved_data) - { - $sql = "SELECT 1 as has_identity - FROM INFORMATION_SCHEMA.COLUMNS - WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; - $result2 = $db->sql_query($sql); - $row2 = $db->sql_fetchrow($result2); - if (!empty($row2['has_identity'])) - { - $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; - $ident_set = true; - } - $db->sql_freeresult($result2); - } - - while ($row = $db->sql_fetchrow($result)) - { - $schema_vals = $schema_fields = array(); - - // Build the SQL statement to recreate the data. - for ($i = 0; $i < $i_num_fields; $i++) - { - $str_val = $row[$ary_name[$i]]; - - if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) - { - $str_quote = ''; - $str_empty = "''"; - $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); - } - else if (preg_match('#date|timestamp#i', $ary_type[$i])) - { - if (empty($str_val)) - { - $str_quote = ''; - } - else - { - $str_quote = "'"; - } - } - else - { - $str_quote = ''; - $str_empty = 'NULL'; - } - - if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) - { - $str_val = $str_empty; - } - - $schema_vals[$i] = $str_quote . $str_val . $str_quote; - $schema_fields[$i] = $ary_name[$i]; - } - - // Take the ordered fields and their associated data and build it - // into a valid sql statement to recreate that field in the data. - $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; - - $this->flush($sql_data); - $sql_data = ''; - } - $db->sql_freeresult($result); - - if ($retrieved_data && $ident_set) - { - $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; - } - $this->flush($sql_data); - } - - function write_data_mssqlnative($table_name) - { - global $db; - $ary_type = $ary_name = array(); - $ident_set = false; - $sql_data = ''; - - // Grab all of the data from current table. - $sql = "SELECT * FROM $table_name"; - $db->mssqlnative_set_query_options(array('Scrollable' => SQLSRV_CURSOR_STATIC)); - $result = $db->sql_query($sql); - - $retrieved_data = $db->mssqlnative_num_rows($result); - - if (!$retrieved_data) - { - $db->sql_freeresult($result); - return; - } - - $sql = "SELECT COLUMN_NAME, DATA_TYPE - FROM INFORMATION_SCHEMA.COLUMNS - WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_NAME = '" . $db->sql_escape($table_name) . "'"; - $result_fields = $db->sql_query($sql); - - $i_num_fields = 0; - while ($row = $db->sql_fetchrow($result_fields)) - { - $ary_type[$i_num_fields] = $row['DATA_TYPE']; - $ary_name[$i_num_fields] = $row['COLUMN_NAME']; - $i_num_fields++; - } - $db->sql_freeresult($result_fields); - - $sql = "SELECT 1 as has_identity - FROM INFORMATION_SCHEMA.COLUMNS - WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; - $result2 = $db->sql_query($sql); - $row2 = $db->sql_fetchrow($result2); - - if (!empty($row2['has_identity'])) - { - $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; - $ident_set = true; - } - $db->sql_freeresult($result2); - - while ($row = $db->sql_fetchrow($result)) - { - $schema_vals = $schema_fields = array(); - - // Build the SQL statement to recreate the data. - for ($i = 0; $i < $i_num_fields; $i++) - { - $str_val = $row[$ary_name[$i]]; - - // defaults to type number - better quote just to be safe, so check for is_int too - if (is_int($ary_type[$i]) || preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) - { - $str_quote = ''; - $str_empty = "''"; - $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); - } - else if (preg_match('#date|timestamp#i', $ary_type[$i])) - { - if (empty($str_val)) - { - $str_quote = ''; - } - else - { - $str_quote = "'"; - } - } - else - { - $str_quote = ''; - $str_empty = 'NULL'; - } - - if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) - { - $str_val = $str_empty; - } - - $schema_vals[$i] = $str_quote . $str_val . $str_quote; - $schema_fields[$i] = $ary_name[$i]; - } - - // Take the ordered fields and their associated data and build it - // into a valid sql statement to recreate that field in the data. - $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; - - $this->flush($sql_data); - $sql_data = ''; - } - $db->sql_freeresult($result); - - if ($ident_set) - { - $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; - } - $this->flush($sql_data); - } - - function write_data_odbc($table_name) - { - global $db; - $ary_type = $ary_name = array(); - $ident_set = false; - $sql_data = ''; - - // Grab all of the data from current table. - $sql = "SELECT * - FROM $table_name"; - $result = $db->sql_query($sql); - - $retrieved_data = odbc_num_rows($result); - - if ($retrieved_data) - { - $sql = "SELECT 1 as has_identity - FROM INFORMATION_SCHEMA.COLUMNS - WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; - $result2 = $db->sql_query($sql); - $row2 = $db->sql_fetchrow($result2); - if (!empty($row2['has_identity'])) - { - $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; - $ident_set = true; - } - $db->sql_freeresult($result2); - } - - $i_num_fields = odbc_num_fields($result); - - for ($i = 0; $i < $i_num_fields; $i++) - { - $ary_type[$i] = odbc_field_type($result, $i + 1); - $ary_name[$i] = odbc_field_name($result, $i + 1); - } - - while ($row = $db->sql_fetchrow($result)) - { - $schema_vals = $schema_fields = array(); - - // Build the SQL statement to recreate the data. - for ($i = 0; $i < $i_num_fields; $i++) - { - $str_val = $row[$ary_name[$i]]; - - if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) - { - $str_quote = ''; - $str_empty = "''"; - $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); - } - else if (preg_match('#date|timestamp#i', $ary_type[$i])) - { - if (empty($str_val)) - { - $str_quote = ''; - } - else - { - $str_quote = "'"; - } - } - else - { - $str_quote = ''; - $str_empty = 'NULL'; - } - - if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) - { - $str_val = $str_empty; - } - - $schema_vals[$i] = $str_quote . $str_val . $str_quote; - $schema_fields[$i] = $ary_name[$i]; - } - - // Take the ordered fields and their associated data and build it - // into a valid sql statement to recreate that field in the data. - $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; - - $this->flush($sql_data); - - $sql_data = ''; - - } - $db->sql_freeresult($result); - - if ($retrieved_data && $ident_set) - { - $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; - } - $this->flush($sql_data); - } - -} - -class oracle_extractor extends base_extractor -{ - function write_table($table_name) - { - global $db, $request; - - $sql_data = '-- Table: ' . $table_name . "\n"; - $sql_data .= "DROP TABLE $table_name\n/\n"; - $sql_data .= "\nCREATE TABLE $table_name (\n"; - - $sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_PRECISION, DATA_LENGTH, NULLABLE, DATA_DEFAULT - FROM ALL_TAB_COLS - WHERE table_name = '{$table_name}'"; - $result = $db->sql_query($sql); - - $rows = array(); - while ($row = $db->sql_fetchrow($result)) - { - $line = ' "' . $row['column_name'] . '" ' . $row['data_type']; - - if ($row['data_type'] !== 'CLOB') - { - if ($row['data_type'] !== 'VARCHAR2' && $row['data_type'] !== 'CHAR') - { - $line .= '(' . $row['data_precision'] . ')'; - } - else - { - $line .= '(' . $row['data_length'] . ')'; - } - } - - if (!empty($row['data_default'])) - { - $line .= ' DEFAULT ' . $row['data_default']; - } - - if ($row['nullable'] == 'N') - { - $line .= ' NOT NULL'; - } - $rows[] = $line; - } - $db->sql_freeresult($result); - - $sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME - FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B - WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME - AND B.CONSTRAINT_TYPE = 'P' - AND A.TABLE_NAME = '{$table_name}'"; - $result = $db->sql_query($sql); - - $primary_key = array(); - $contraint_name = ''; - while ($row = $db->sql_fetchrow($result)) - { - $constraint_name = '"' . $row['constraint_name'] . '"'; - $primary_key[] = '"' . $row['column_name'] . '"'; - } - $db->sql_freeresult($result); - - if (sizeof($primary_key)) - { - $rows[] = " CONSTRAINT {$constraint_name} PRIMARY KEY (" . implode(', ', $primary_key) . ')'; - } - - $sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME - FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B - WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME - AND B.CONSTRAINT_TYPE = 'U' - AND A.TABLE_NAME = '{$table_name}'"; - $result = $db->sql_query($sql); - - $unique = array(); - $contraint_name = ''; - while ($row = $db->sql_fetchrow($result)) - { - $constraint_name = '"' . $row['constraint_name'] . '"'; - $unique[] = '"' . $row['column_name'] . '"'; - } - $db->sql_freeresult($result); - - if (sizeof($unique)) - { - $rows[] = " CONSTRAINT {$constraint_name} UNIQUE (" . implode(', ', $unique) . ')'; - } - - $sql_data .= implode(",\n", $rows); - $sql_data .= "\n)\n/\n"; - - $sql = "SELECT A.REFERENCED_NAME, C.* - FROM USER_DEPENDENCIES A, USER_TRIGGERS B, USER_SEQUENCES C - WHERE A.REFERENCED_TYPE = 'SEQUENCE' - AND A.NAME = B.TRIGGER_NAME - AND B.TABLE_NAME = '{$table_name}' - AND C.SEQUENCE_NAME = A.REFERENCED_NAME"; - $result = $db->sql_query($sql); - - $type = $request->variable('type', ''); - - while ($row = $db->sql_fetchrow($result)) - { - $sql_data .= "\nDROP SEQUENCE \"{$row['referenced_name']}\"\n/\n"; - $sql_data .= "\nCREATE SEQUENCE \"{$row['referenced_name']}\""; - - if ($type == 'full') - { - $sql_data .= ' START WITH ' . $row['last_number']; - } - - $sql_data .= "\n/\n"; - } - $db->sql_freeresult($result); - - $sql = "SELECT DESCRIPTION, WHEN_CLAUSE, TRIGGER_BODY - FROM USER_TRIGGERS - WHERE TABLE_NAME = '{$table_name}'"; - $result = $db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) - { - $sql_data .= "\nCREATE OR REPLACE TRIGGER {$row['description']}WHEN ({$row['when_clause']})\n{$row['trigger_body']}\n/\n"; - } - $db->sql_freeresult($result); - - $sql = "SELECT A.INDEX_NAME, B.COLUMN_NAME - FROM USER_INDEXES A, USER_IND_COLUMNS B - WHERE A.UNIQUENESS = 'NONUNIQUE' - AND A.INDEX_NAME = B.INDEX_NAME - AND B.TABLE_NAME = '{$table_name}'"; - $result = $db->sql_query($sql); - - $index = array(); - - while ($row = $db->sql_fetchrow($result)) - { - $index[$row['index_name']][] = $row['column_name']; - } - - foreach ($index as $index_name => $column_names) - { - $sql_data .= "\nCREATE INDEX $index_name ON $table_name(" . implode(', ', $column_names) . ")\n/\n"; - } - $db->sql_freeresult($result); - $this->flush($sql_data); - } - - function write_data($table_name) - { - global $db; - $ary_type = $ary_name = array(); - - // Grab all of the data from current table. - $sql = "SELECT * - FROM $table_name"; - $result = $db->sql_query($sql); - - $i_num_fields = ocinumcols($result); - - for ($i = 0; $i < $i_num_fields; $i++) - { - $ary_type[$i] = ocicolumntype($result, $i + 1); - $ary_name[$i] = ocicolumnname($result, $i + 1); - } - - $sql_data = ''; - - while ($row = $db->sql_fetchrow($result)) - { - $schema_vals = $schema_fields = array(); - - // Build the SQL statement to recreate the data. - for ($i = 0; $i < $i_num_fields; $i++) - { - // Oracle uses uppercase - we use lowercase - $str_val = $row[strtolower($ary_name[$i])]; - - if (preg_match('#char|text|bool|raw|clob#i', $ary_type[$i])) - { - $str_quote = ''; - $str_empty = "''"; - $str_val = sanitize_data_oracle($str_val); - } - else if (preg_match('#date|timestamp#i', $ary_type[$i])) - { - if (empty($str_val)) - { - $str_quote = ''; - } - else - { - $str_quote = "'"; - } - } - else - { - $str_quote = ''; - $str_empty = 'NULL'; - } - - if (empty($str_val) && $str_val !== '0') - { - $str_val = $str_empty; - } - - $schema_vals[$i] = $str_quote . $str_val . $str_quote; - $schema_fields[$i] = '"' . $ary_name[$i] . '"'; - } - - // Take the ordered fields and their associated data and build it - // into a valid sql statement to recreate that field in the data. - $sql_data = "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ")\n/\n"; - - $this->flush($sql_data); - } - $db->sql_freeresult($result); - } - - function write_start($prefix) - { - $sql_data = "--\n"; - $sql_data .= "-- phpBB Backup Script\n"; - $sql_data .= "-- Dump of tables for $prefix\n"; - $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; - $sql_data .= "--\n"; - $this->flush($sql_data); - } -} - // get how much space we allow for a chunk of data, very similar to phpMyAdmin's way of doing things ;-) (hey, we only do this for MySQL anyway :P) function get_usable_memory() { diff --git a/phpBB/includes/acp/acp_icons.php b/phpBB/includes/acp/acp_icons.php index fdf366097a..5d1756de45 100644 --- a/phpBB/includes/acp/acp_icons.php +++ b/phpBB/includes/acp/acp_icons.php @@ -28,7 +28,7 @@ class acp_icons function main($id, $mode) { - global $db, $user, $auth, $template, $cache; + global $db, $user, $auth, $template, $cache, $phpbb_container; global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; global $request, $phpbb_container; @@ -486,6 +486,7 @@ class acp_icons $cache->destroy('_icons'); $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); $level = ($icons_updated) ? E_USER_NOTICE : E_USER_WARNING; $errormsgs = ''; @@ -661,6 +662,7 @@ class acp_icons $cache->destroy('_icons'); $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); trigger_error($user->lang[$lang . '_IMPORT_SUCCESS'] . adm_back_link($this->u_action)); } @@ -783,6 +785,7 @@ class acp_icons $cache->destroy('_icons'); $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); if ($request->is_ajax()) { @@ -848,6 +851,7 @@ class acp_icons $cache->destroy('_icons'); $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); if ($request->is_ajax()) { diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index 21c6edfccf..8680b7786a 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -352,6 +352,11 @@ class acp_main $config->increment('assets_version', 1); $cache->purge(); + // Remove old renderers from the text_formatter service. Since this + // operation is performed after the cache is purged, there is not "current" + // renderer and in effect all renderers will be purged + $phpbb_container->get('text_formatter.cache')->tidy(); + // Clear permissions $auth->acl_clear_prefetch(); phpbb_cache_moderators($db, $cache, $auth); diff --git a/phpBB/includes/acp/acp_styles.php b/phpBB/includes/acp/acp_styles.php index 45f224f8b1..b652fd6587 100644 --- a/phpBB/includes/acp/acp_styles.php +++ b/phpBB/includes/acp/acp_styles.php @@ -53,6 +53,9 @@ class acp_styles /** @var \phpbb\auth\auth */ protected $auth; + /** @var \phpbb\textformatter\cache_interface */ + protected $text_formatter_cache; + /** @var string */ protected $phpbb_root_path; @@ -61,7 +64,7 @@ class acp_styles public function main($id, $mode) { - global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config; + global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config, $phpbb_container; $this->db = $db; $this->user = $user; @@ -69,6 +72,7 @@ class acp_styles $this->request = $request; $this->cache = $cache; $this->auth = $auth; + $this->text_formatter_cache = $phpbb_container->get('text_formatter.cache'); $this->config = $config; $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $phpEx; @@ -216,6 +220,12 @@ class acp_styles } } + // Invalidate the text formatter's cache for the new styles to take effect + if (!empty($installed_names)) + { + $this->text_formatter_cache->invalidate(); + } + // Show message if (!count($messages)) { diff --git a/phpBB/includes/acp/acp_words.php b/phpBB/includes/acp/acp_words.php index d28aa8e60b..ea8d47a109 100644 --- a/phpBB/includes/acp/acp_words.php +++ b/phpBB/includes/acp/acp_words.php @@ -28,7 +28,7 @@ class acp_words function main($id, $mode) { - global $db, $user, $auth, $template, $cache, $phpbb_log, $request; + global $db, $user, $auth, $template, $cache, $phpbb_log, $request, $phpbb_container; global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; $user->add_lang('acp/posting'); @@ -115,6 +115,7 @@ class acp_words } $cache->destroy('_word_censors'); + $phpbb_container->get('text_formatter.cache')->invalidate(); $log_action = ($word_id) ? 'LOG_WORD_EDIT' : 'LOG_WORD_ADD'; @@ -148,6 +149,7 @@ class acp_words $db->sql_query($sql); $cache->destroy('_word_censors'); + $phpbb_container->get('text_formatter.cache')->invalidate(); $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_WORD_DELETE', false, array($deleted_word)); diff --git a/phpBB/includes/functions_content.php b/phpBB/includes/functions_content.php index bdbc8a92fa..60511d89a4 100644 --- a/phpBB/includes/functions_content.php +++ b/phpBB/includes/functions_content.php @@ -389,46 +389,68 @@ function phpbb_clean_search_string($search_string) /** * Decode text whereby text is coming from the db and expected to be pre-parsed content * We are placing this outside of the message parser because we are often in need of it... +* +* NOTE: special chars are kept encoded +* +* @param string &$message Original message, passed by reference +* @param string $bbcode_uid BBCode UID +* @return null */ function decode_message(&$message, $bbcode_uid = '') { - global $config; + global $phpbb_container; - if ($bbcode_uid) + if (preg_match('#^<[rt][ >]#', $message)) { - $match = array('<br />', "[/*:m:$bbcode_uid]", ":u:$bbcode_uid", ":o:$bbcode_uid", ":$bbcode_uid"); - $replace = array("\n", '', '', '', ''); + $message = htmlspecialchars($phpbb_container->get('text_formatter.utils')->unparse($message), ENT_COMPAT); } else { - $match = array('<br />'); - $replace = array("\n"); - } + if ($bbcode_uid) + { + $match = array('<br />', "[/*:m:$bbcode_uid]", ":u:$bbcode_uid", ":o:$bbcode_uid", ":$bbcode_uid"); + $replace = array("\n", '', '', '', ''); + } + else + { + $match = array('<br />'); + $replace = array("\n"); + } - $message = str_replace($match, $replace, $message); + $message = str_replace($match, $replace, $message); - $match = get_preg_expression('bbcode_htm'); - $replace = array('\1', '\1', '\2', '\1', '', ''); + $match = get_preg_expression('bbcode_htm'); + $replace = array('\1', '\1', '\2', '\1', '', ''); - $message = preg_replace($match, $replace, $message); + $message = preg_replace($match, $replace, $message); + } } /** -* Strips all bbcode from a text and returns the plain content +* Strips all bbcode from a text in place */ function strip_bbcode(&$text, $uid = '') { - if (!$uid) + global $phpbb_container; + + if (preg_match('#^<[rt][ >]#', $text)) { - $uid = '[0-9a-z]{5,}'; + $text = $phpbb_container->get('text_formatter.utils')->clean_formatting($text); } + else + { + if (!$uid) + { + $uid = '[0-9a-z]{5,}'; + } - $text = preg_replace("#\[\/?[a-z0-9\*\+\-]+(?:=(?:".*"|[^\]]*))?(?::[a-z])?(\:$uid)\]#", ' ', $text); + $text = preg_replace("#\[\/?[a-z0-9\*\+\-]+(?:=(?:".*"|[^\]]*))?(?::[a-z])?(\:$uid)\]#", ' ', $text); - $match = get_preg_expression('bbcode_htm'); - $replace = array('\1', '\1', '\2', '\1', '', ''); + $match = get_preg_expression('bbcode_htm'); + $replace = array('\1', '\1', '\2', '\1', '', ''); - $text = preg_replace($match, $replace, $text); + $text = preg_replace($match, $replace, $text); + } } /** @@ -438,7 +460,7 @@ function strip_bbcode(&$text, $uid = '') function generate_text_for_display($text, $uid, $bitfield, $flags, $censor_text = true) { static $bbcode; - global $phpbb_dispatcher; + global $phpbb_dispatcher, $phpbb_container; if ($text === '') { @@ -459,34 +481,56 @@ function generate_text_for_display($text, $uid, $bitfield, $flags, $censor_text $vars = array('text', 'uid', 'bitfield', 'flags', 'censor_text'); extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_before', compact($vars))); - if ($censor_text) + if (preg_match('#^<[rt][ >]#', $text)) { - $text = censor_text($text); - } + $renderer = $phpbb_container->get('text_formatter.renderer'); - // Parse bbcode if bbcode uid stored and bbcode enabled - if ($uid && ($flags & OPTION_FLAG_BBCODE)) - { - if (!class_exists('bbcode')) + // Temporarily switch off viewcensors if applicable + $old_censor = $renderer->get_viewcensors(); + if ($old_censor !== $censor_text) { - global $phpbb_root_path, $phpEx; - include($phpbb_root_path . 'includes/bbcode.' . $phpEx); + $renderer->set_viewcensors($censor_text); } - if (empty($bbcode)) + $text = $renderer->render($text); + + // Restore the previous value + if ($old_censor !== $censor_text) { - $bbcode = new bbcode($bitfield); + $renderer->set_viewcensors($old_censor); } - else + } + else + { + if ($censor_text) { - $bbcode->bbcode($bitfield); + $text = censor_text($text); } - $bbcode->bbcode_second_pass($text, $uid); - } + // Parse bbcode if bbcode uid stored and bbcode enabled + if ($uid && ($flags & OPTION_FLAG_BBCODE)) + { + if (!class_exists('bbcode')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/bbcode.' . $phpEx); + } + + if (empty($bbcode)) + { + $bbcode = new bbcode($bitfield); + } + else + { + $bbcode->bbcode($bitfield); + } - $text = bbcode_nl2br($text); - $text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES)); + $bbcode->bbcode_second_pass($text, $uid); + } + + $text = bbcode_nl2br($text); + $text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES)); + } /** * Use this event to modify the text after it is parsed @@ -550,11 +594,6 @@ function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bb $uid = $bitfield = ''; $flags = (($allow_bbcode) ? OPTION_FLAG_BBCODE : 0) + (($allow_smilies) ? OPTION_FLAG_SMILIES : 0) + (($allow_urls) ? OPTION_FLAG_LINKS : 0); - if ($text === '') - { - return; - } - if (!class_exists('parse_message')) { include($phpbb_root_path . 'includes/message_parser.' . $phpEx); diff --git a/phpBB/includes/mcp/mcp_main.php b/phpBB/includes/mcp/mcp_main.php index e3fbbc0418..e8ab0167f5 100644 --- a/phpBB/includes/mcp/mcp_main.php +++ b/phpBB/includes/mcp/mcp_main.php @@ -226,6 +226,31 @@ class mcp_main break; default: + if ($quickmod) + { + switch ($action) + { + case 'lock': + case 'unlock': + case 'make_announce': + case 'make_sticky': + case 'make_global': + case 'make_normal': + case 'make_onindex': + case 'move': + case 'fork': + case 'delete_topic': + trigger_error('TOPIC_NOT_EXIST'); + break; + + case 'lock_post': + case 'unlock_post': + case 'delete_post': + trigger_error('POST_NOT_EXIST'); + break; + } + } + trigger_error('NO_MODE', E_USER_ERROR); break; } diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php index ccb953adbe..8353ae6843 100644 --- a/phpBB/includes/message_parser.php +++ b/phpBB/includes/message_parser.php @@ -21,6 +21,19 @@ if (!defined('IN_PHPBB')) if (!class_exists('bbcode')) { + // The following lines are for extensions which include message_parser.php + // while $phpbb_root_path and $phpEx are out of the script scope + // which may lead to the 'Undefined variable' and 'failed to open stream' errors + if (!isset($phpbb_root_path)) + { + global $phpbb_root_path; + } + + if (!isset($phpEx)) + { + global $phpEx; + } + include($phpbb_root_path . 'includes/bbcode.' . $phpEx); } @@ -1103,7 +1116,7 @@ class parse_message extends bbcode_firstpass */ function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post') { - global $config, $db, $user, $phpbb_dispatcher; + global $config, $db, $user, $phpbb_dispatcher, $phpbb_container; $this->mode = $mode; @@ -1132,12 +1145,6 @@ class parse_message extends bbcode_firstpass $this->decode_message(); } - // Do some general 'cleanup' first before processing message, - // e.g. remove excessive newlines(?), smilies(?) - $match = array('#(script|about|applet|activex|chrome):#i'); - $replace = array("\\1:"); - $this->message = preg_replace($match, $replace, trim($this->message)); - // Store message length... $message_length = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)); @@ -1210,47 +1217,29 @@ class parse_message extends bbcode_firstpass return (!$update_this_message) ? $return_message : $this->warn_msg; } - // Prepare BBcode (just prepares some tags for better parsing) - if ($allow_bbcode && strpos($this->message, '[') !== false) - { - $this->bbcode_init(); - $disallow = array('img', 'flash', 'quote', 'url'); - foreach ($disallow as $bool) - { - if (!${'allow_' . $bool . '_bbcode'}) - { - $this->bbcodes[$bool]['disabled'] = true; - } - } + // Get the parser + $parser = $phpbb_container->get('text_formatter.parser'); - $this->prepare_bbcodes(); - } + // Set the parser's options + ($allow_bbcode) ? $parser->enable_bbcodes() : $parser->disable_bbcodes(); + ($allow_magic_url) ? $parser->enable_magic_url() : $parser->disable_magic_url(); + ($allow_smilies) ? $parser->enable_smilies() : $parser->disable_smilies(); + ($allow_img_bbcode) ? $parser->enable_bbcode('img') : $parser->disable_bbcode('img'); + ($allow_flash_bbcode) ? $parser->enable_bbcode('flash') : $parser->disable_bbcode('flash'); + ($allow_quote_bbcode) ? $parser->enable_bbcode('quote') : $parser->disable_bbcode('quote'); + ($allow_url_bbcode) ? $parser->enable_bbcode('url') : $parser->disable_bbcode('url'); - // Parse smilies - if ($allow_smilies) - { - $this->smilies($config['max_' . $mode . '_smilies']); - } + // Set some config values + $parser->set_vars(array( + 'max_font_size' => $config['max_' . $this->mode . '_font_size'], + 'max_img_height' => $config['max_' . $this->mode . '_img_height'], + 'max_img_width' => $config['max_' . $this->mode . '_img_width'], + 'max_smilies' => $config['max_' . $this->mode . '_smilies'], + 'max_urls' => $config['max_' . $this->mode . '_urls'] + )); - $num_urls = 0; - - // Parse BBCode - if ($allow_bbcode && strpos($this->message, '[') !== false) - { - $this->parse_bbcode(); - $num_urls += $this->parsed_items['url']; - } - - // Parse URL's - if ($allow_magic_url) - { - $this->magic_url(generate_board_url()); - - if ($config['max_' . $mode . '_urls']) - { - $num_urls += preg_match_all('#\<!-- ([lmwe]) --\>.*?\<!-- \1 --\>#', $this->message, $matches); - } - } + // Parse this message + $this->message = $parser->parse(htmlspecialchars_decode($this->message, ENT_QUOTES)); // Check for out-of-bounds characters that are currently // not supported by utf8_bin in MySQL @@ -1269,10 +1258,12 @@ class parse_message extends bbcode_firstpass return (!$update_this_message) ? $return_message : $this->warn_msg; } - // Check number of links - if ($config['max_' . $mode . '_urls'] && $num_urls > $config['max_' . $mode . '_urls']) + // Check for errors + $errors = $parser->get_errors(); + if ($errors) { - $this->warn_msg[] = sprintf($user->lang['TOO_MANY_URLS'], $config['max_' . $mode . '_urls']); + $this->warn_msg = array_merge($this->warn_msg, $errors); + return (!$update_this_message) ? $return_message : $this->warn_msg; } @@ -1292,7 +1283,7 @@ class parse_message extends bbcode_firstpass */ function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true) { - global $phpbb_dispatcher; + global $phpbb_container, $phpbb_dispatcher; // If false, then the parsed message get returned but internal message not processed. if (!$update_this_message) @@ -1301,26 +1292,25 @@ class parse_message extends bbcode_firstpass $return_message = &$this->message; } - if ($this->message_status == 'plain') + // NOTE: message_status is unreliable for detecting unparsed text because some callers + // change $this->message without resetting $this->message_status to 'plain' so we + // inspect the message instead + //if ($this->message_status == 'plain') + if (!preg_match('/^<[rt][ >]/', $this->message)) { // Force updating message - of course. $this->parse($allow_bbcode, $allow_magic_url, $allow_smilies, $this->allow_img_bbcode, $this->allow_flash_bbcode, $this->allow_quote_bbcode, $this->allow_url_bbcode, true); } - // Replace naughty words such as farty pants - $this->message = censor_text($this->message); - - // Parse BBcode - if ($allow_bbcode) + // There's a bug when previewing a topic with no poll, because the empty title of the poll + // gets parsed but $this->message still ends up empty. This fixes it, until a proper fix is + // devised + if ($this->message === '') { - $this->bbcode_cache_init(); - - // We are giving those parameters to be able to use the bbcode class on its own - $this->bbcode_second_pass($this->message, $this->bbcode_uid); + $this->message = $phpbb_container->get('text_formatter.parser')->parse($this->message); } - $this->message = bbcode_nl2br($this->message); - $this->message = smiley_text($this->message, !$allow_smilies); + $this->message = $phpbb_container->get('text_formatter.renderer')->render($this->message); $text = $this->message; $uid = $this->bbcode_uid; @@ -1784,24 +1774,22 @@ class parse_message extends bbcode_firstpass $poll_max_options = $poll['poll_max_options']; - // Parse Poll Option text ;) + // Parse Poll Option text $tmp_message = $this->message; - $this->message = $poll['poll_option_text']; - $bbcode_bitfield = $this->bbcode_bitfield; - $poll['poll_option_text'] = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false, 'poll'); + $poll['poll_options'] = explode("\n", trim($poll['poll_option_text'])); + $poll['poll_options_size'] = sizeof($poll['poll_options']); - $bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield)); - $this->message = $tmp_message; + foreach ($poll['poll_options'] as &$poll_option) + { + $this->message = $poll_option; + $poll_option = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false, 'poll'); + } + unset($poll_option); + $poll['poll_option_text'] = implode("\n", $poll['poll_options']); // Parse Poll Title - $tmp_message = $this->message; $this->message = $poll['poll_title']; - $this->bbcode_bitfield = $bbcode_bitfield; - - $poll['poll_options'] = explode("\n", trim($poll['poll_option_text'])); - $poll['poll_options_size'] = sizeof($poll['poll_options']); - if (!$poll['poll_title'] && $poll['poll_options_size']) { $this->warn_msg[] = $user->lang['NO_POLL_TITLE']; @@ -1819,10 +1807,6 @@ class parse_message extends bbcode_firstpass } } - $this->bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield)); - $this->message = $tmp_message; - unset($tmp_message); - if (sizeof($poll['poll_options']) == 1) { $this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS']; @@ -1837,6 +1821,8 @@ class parse_message extends bbcode_firstpass } $poll['poll_max_options'] = ($poll['poll_max_options'] < 1) ? 1 : (($poll['poll_max_options'] > $config['max_poll_options']) ? $config['max_poll_options'] : $poll['poll_max_options']); + + $this->message = $tmp_message; } /** diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php index 5d2398528b..553d873b25 100644 --- a/phpBB/install/convertors/convert_phpbb20.php +++ b/phpBB/install/convertors/convert_phpbb20.php @@ -38,7 +38,7 @@ $dbms = $phpbb_config_php_file->convert_30_dbms_to_31($dbms); $convertor_data = array( 'forum_name' => 'phpBB 2.0.x', 'version' => '1.0.3', - 'phpbb_version' => '3.1.3', + 'phpbb_version' => '3.1.4', 'author' => '<a href="https://www.phpbb.com/">phpBB Limited</a>', 'dbms' => $dbms, 'dbhost' => $dbhost, diff --git a/phpBB/install/install_install.php b/phpBB/install/install_install.php index 8f39328adb..3c37a028cb 100644 --- a/phpBB/install/install_install.php +++ b/phpBB/install/install_install.php @@ -1196,12 +1196,10 @@ class install_install extends module foreach ($sql_query as $sql) { - //$sql = trim(str_replace('|', ';', $sql)); - if (!$db->sql_query($sql)) - { - $error = $db->sql_error(); - $this->p_master->db_error($error['message'], $sql, __LINE__, __FILE__); - } + // Ignore errors when the functions or types already exist + // to allow installing phpBB twice in the same database with + // a different prefix + $db->sql_query($sql); } unset($sql_query); } diff --git a/phpBB/language/en/acp/extensions.php b/phpBB/language/en/acp/extensions.php index 28cdc8829d..bacb33c70a 100644 --- a/phpBB/language/en/acp/extensions.php +++ b/phpBB/language/en/acp/extensions.php @@ -75,7 +75,7 @@ $lang = array_merge($lang, array( <li>Upload the new files</li> <li>Enable the extension</li> </ol>', - 'EXTENSION_REMOVE_HEADLINE' => 'Completly removing an extension from your board', + 'EXTENSION_REMOVE_HEADLINE' => 'Completely removing an extension from your board', 'EXTENSION_REMOVE_EXPLAIN' => '<ol> <li>Disable the extension</li> <li>Delete the extension’s data</li> diff --git a/phpBB/phpbb/avatar/driver/local.php b/phpBB/phpbb/avatar/driver/local.php index 8888686b2d..36087f8ba0 100644 --- a/phpBB/phpbb/avatar/driver/local.php +++ b/phpBB/phpbb/avatar/driver/local.php @@ -23,8 +23,10 @@ class local extends \phpbb\avatar\driver\driver */ public function get_data($row) { + $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $this->path_helper->get_web_root_path(); + return array( - 'src' => $this->path_helper->get_web_root_path() . $this->config['avatar_gallery_path'] . '/' . $row['avatar'], + 'src' => $root_path . $this->config['avatar_gallery_path'] . '/' . $row['avatar'], 'width' => $row['avatar_width'], 'height' => $row['avatar_height'], ); diff --git a/phpBB/phpbb/avatar/driver/upload.php b/phpBB/phpbb/avatar/driver/upload.php index 2ad84c087d..4fdaee9561 100644 --- a/phpBB/phpbb/avatar/driver/upload.php +++ b/phpBB/phpbb/avatar/driver/upload.php @@ -55,8 +55,10 @@ class upload extends \phpbb\avatar\driver\driver */ public function get_data($row, $ignore_config = false) { + $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $this->path_helper->get_web_root_path(); + return array( - 'src' => $this->path_helper->get_web_root_path() . 'download/file.' . $this->php_ext . '?avatar=' . $row['avatar'], + 'src' => $root_path . 'download/file.' . $this->php_ext . '?avatar=' . $row['avatar'], 'width' => $row['avatar_width'], 'height' => $row['avatar_height'], ); diff --git a/phpBB/phpbb/captcha/plugins/qa.php b/phpBB/phpbb/captcha/plugins/qa.php index cf03f92e96..4df8a86432 100644 --- a/phpBB/phpbb/captcha/plugins/qa.php +++ b/phpBB/phpbb/captcha/plugins/qa.php @@ -125,7 +125,7 @@ class qa */ public function is_available() { - global $config, $db, $phpbb_root_path, $phpEx, $user; + global $config, $db, $user; // load language file for pretty display in the ACP dropdown $user->add_lang('captcha_qa'); @@ -263,7 +263,7 @@ class qa */ function garbage_collect($type = 0) { - global $db, $config; + global $db; $sql = 'SELECT c.confirm_id FROM ' . $this->table_qa_confirm . ' c @@ -309,7 +309,6 @@ class qa global $phpbb_container; $db_tool = $phpbb_container->get('dbal.tools'); - $schemas = array( $this->table_captcha_questions => array ( 'COLUMNS' => array( @@ -364,7 +363,7 @@ class qa */ function validate() { - global $config, $db, $user; + global $user; $error = ''; @@ -412,7 +411,7 @@ class qa if (!sizeof($this->question_ids)) { - return false; + return; } $this->confirm_id = md5(unique_id($user->ip)); $this->question = (int) array_rand($this->question_ids); @@ -438,7 +437,7 @@ class qa if (!sizeof($this->question_ids)) { - return false; + return; } $this->question = (int) array_rand($this->question_ids); @@ -611,8 +610,7 @@ class qa */ function acp_page($id, &$module) { - global $db, $user, $auth, $template, $phpbb_log, $request; - global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; + global $config, $request, $phpbb_log, $template, $user; $user->add_lang('acp/board'); $user->add_lang('captcha_qa'); @@ -674,11 +672,7 @@ class qa else { // okay, show the editor - $error = false; - $input_question = $request->variable('question_text', '', true); - $input_answers = $request->variable('answers', '', true); - $input_lang = $request->variable('lang_iso', '', true); - $input_strict = $request->variable('strict', false); + $question_input = $this->acp_get_question_input(); $langs = $this->get_languages(); foreach ($langs as $lang => $entry) @@ -697,13 +691,11 @@ class qa { if ($question = $this->acp_get_question_data($question_id)) { - $answers = (isset($input_answers[$lang])) ? $input_answers[$lang] : implode("\n", $question['answers']); - $template->assign_vars(array( - 'QUESTION_TEXT' => ($input_question) ? $input_question : $question['question_text'], - 'LANG_ISO' => ($input_lang) ? $input_lang : $question['lang_iso'], - 'STRICT' => (isset($_REQUEST['strict'])) ? $input_strict : $question['strict'], - 'ANSWERS' => $answers, + 'QUESTION_TEXT' => ($question_input['question_text']) ? $question_input['question_text'] : $question['question_text'], + 'LANG_ISO' => ($question_input['lang_iso']) ? $question_input['lang_iso'] : $question['lang_iso'], + 'STRICT' => (isset($_REQUEST['strict'])) ? $question_input['strict'] : $question['strict'], + 'ANSWERS' => implode("\n", $question['answers']), )); } else @@ -714,18 +706,16 @@ class qa else { $template->assign_vars(array( - 'QUESTION_TEXT' => $input_question, - 'LANG_ISO' => $input_lang, - 'STRICT' => $input_strict, - 'ANSWERS' => $input_answers, + 'QUESTION_TEXT' => $question_input['question_text'], + 'LANG_ISO' => $question_input['lang_iso'], + 'STRICT' => $question_input['strict'], + 'ANSWERS' => (is_array($question_input['answers'])) ? implode("\n", $question_input['answers']) : '', )); } if ($submit && check_form_key($form_key)) { - $data = $this->acp_get_question_input(); - - if (!$this->validate_input($data)) + if (!$this->validate_input($question_input)) { $template->assign_vars(array( 'S_ERROR' => true, @@ -735,11 +725,11 @@ class qa { if ($question_id) { - $this->acp_update_question($data, $question_id); + $this->acp_update_question($question_input, $question_id); } else { - $this->acp_add_question($data); + $this->acp_add_question($question_input); } $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); @@ -819,6 +809,8 @@ class qa return $question; } + + return false; } /** @@ -829,13 +821,21 @@ class qa global $request; $answers = $request->variable('answers', '', true); + + // Convert answers into array and filter if answers are set + if (strlen($answers)) + { + $answers = array_filter(array_map('trim', explode("\n", $answers)), function ($value) { + return $value !== ''; + }); + } + $question = array( 'question_text' => $request->variable('question_text', '', true), 'strict' => $request->variable('strict', false), 'lang_iso' => $request->variable('lang_iso', ''), - 'answers' => (strlen($answers)) ? explode("\n", $answers) : '', + 'answers' => $answers, ); - return $question; } diff --git a/phpBB/phpbb/content_visibility.php b/phpBB/phpbb/content_visibility.php index 700009da6a..0ba0489cb7 100644 --- a/phpBB/phpbb/content_visibility.php +++ b/phpBB/phpbb/content_visibility.php @@ -237,7 +237,7 @@ class content_visibility if (!sizeof($forum_ids)) { // The user can see all posts/topics in all specified forums - return $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums); + return $where_sql . $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums) . ')'; } else { @@ -248,8 +248,8 @@ class content_visibility else { // The user is just a normal user - return $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ' - AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true); + return $where_sql . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ' + AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true) . ')'; } $where_sql .= '(' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ' diff --git a/phpBB/phpbb/controller/helper.php b/phpBB/phpbb/controller/helper.php index 1e1cf5e4e8..68ccfafddf 100644 --- a/phpBB/phpbb/controller/helper.php +++ b/phpBB/phpbb/controller/helper.php @@ -145,6 +145,12 @@ class helper $base_url = $context->getBaseUrl(); + // Append page name if base URL does not contain it + if (!empty($page_name) && strpos($base_url, '/' . $page_name) === false) + { + $base_url .= '/' . $page_name; + } + // If enable_mod_rewrite is false we need to replace the current front-end by app.php, otherwise we need to remove it. $base_url = str_replace('/' . $page_name, empty($this->config['enable_mod_rewrite']) ? '/app.' . $this->php_ext : '', $base_url); diff --git a/phpBB/phpbb/db/extractor/base_extractor.php b/phpBB/phpbb/db/extractor/base_extractor.php new file mode 100644 index 0000000000..547c85f066 --- /dev/null +++ b/phpBB/phpbb/db/extractor/base_extractor.php @@ -0,0 +1,252 @@ +<?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\db\extractor; + +use phpbb\db\extractor\exception\invalid_format_exception; +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +/** + * Abstract base class for database extraction + */ +abstract class base_extractor implements extractor_interface +{ + /** + * @var string phpBB root path + */ + protected $phpbb_root_path; + + /** + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var bool + */ + protected $download; + + /** + * @var bool + */ + protected $store; + + /** + * @var int + */ + protected $time; + + /** + * @var string + */ + protected $format; + + /** + * @var resource + */ + protected $fp; + + /** + * @var string + */ + protected $write; + + /** + * @var string + */ + protected $close; + + /** + * @var bool + */ + protected $run_comp; + + /** + * @var bool + */ + protected $is_initialized; + + /** + * Constructor + * + * @param string $phpbb_root_path + * @param \phpbb\request\request_interface $request + * @param \phpbb\db\driver\driver_interface $db + */ + public function __construct($phpbb_root_path, \phpbb\request\request_interface $request, \phpbb\db\driver\driver_interface $db) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->request = $request; + $this->db = $db; + $this->fp = null; + + $this->is_initialized = false; + } + + /** + * {@inheritdoc} + */ + public function init_extractor($format, $filename, $time, $download = false, $store = false) + { + $this->download = $download; + $this->store = $store; + $this->time = $time; + $this->format = $format; + + switch ($format) + { + case 'text': + $ext = '.sql'; + $open = 'fopen'; + $this->write = 'fwrite'; + $this->close = 'fclose'; + $mimetype = 'text/x-sql'; + break; + case 'bzip2': + $ext = '.sql.bz2'; + $open = 'bzopen'; + $this->write = 'bzwrite'; + $this->close = 'bzclose'; + $mimetype = 'application/x-bzip2'; + break; + case 'gzip': + $ext = '.sql.gz'; + $open = 'gzopen'; + $this->write = 'gzwrite'; + $this->close = 'gzclose'; + $mimetype = 'application/x-gzip'; + break; + default: + throw new invalid_format_exception(); + break; + } + + if ($download === true) + { + $name = $filename . $ext; + header('Cache-Control: private, no-cache'); + header("Content-Type: $mimetype; name=\"$name\""); + header("Content-disposition: attachment; filename=$name"); + + switch ($format) + { + case 'bzip2': + ob_start(); + break; + + case 'gzip': + if (strpos($this->request->header('Accept-Encoding'), 'gzip') !== false && strpos(strtolower($this->request->header('User-Agent')), 'msie') === false) + { + ob_start('ob_gzhandler'); + } + else + { + $this->run_comp = true; + } + break; + } + } + + if ($store === true) + { + $file = $this->phpbb_root_path . 'store/' . $filename . $ext; + + $this->fp = $open($file, 'w'); + + if (!$this->fp) + { + trigger_error('FILE_WRITE_FAIL', E_USER_ERROR); + } + } + + $this->is_initialized = true; + } + + /** + * {@inheritdoc} + */ + public function write_end() + { + static $close; + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->store) + { + if ($close === null) + { + $close = $this->close; + } + $close($this->fp); + } + + // bzip2 must be written all the way at the end + if ($this->download && $this->format === 'bzip2') + { + $c = ob_get_clean(); + echo bzcompress($c); + } + } + + /** + * {@inheritdoc} + */ + public function flush($data) + { + static $write; + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->store === true) + { + if ($write === null) + { + $write = $this->write; + } + $write($this->fp, $data); + } + + if ($this->download === true) + { + if ($this->format === 'bzip2' || $this->format === 'text' || ($this->format === 'gzip' && !$this->run_comp)) + { + echo $data; + } + + // we can write the gzip data as soon as we get it + if ($this->format === 'gzip') + { + if ($this->run_comp) + { + echo gzencode($data); + } + else + { + ob_flush(); + flush(); + } + } + } + } +} diff --git a/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php b/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php new file mode 100644 index 0000000000..62eb434be1 --- /dev/null +++ b/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php @@ -0,0 +1,24 @@ +<?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\db\extractor\exception; + +use phpbb\exception\runtime_exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class extractor_not_initialized_exception extends runtime_exception +{ + +} diff --git a/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php b/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php new file mode 100644 index 0000000000..6be24cb5dc --- /dev/null +++ b/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php @@ -0,0 +1,22 @@ +<?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\db\extractor\exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class invalid_format_exception extends \InvalidArgumentException +{ + +} diff --git a/phpBB/phpbb/db/extractor/extractor_interface.php b/phpBB/phpbb/db/extractor/extractor_interface.php new file mode 100644 index 0000000000..ff45df9bb7 --- /dev/null +++ b/phpBB/phpbb/db/extractor/extractor_interface.php @@ -0,0 +1,80 @@ +<?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\db\extractor; + +/** +* Database extractor interface +*/ +interface extractor_interface +{ + /** + * Start the extraction of the database + * + * This function initialize the database extraction. It is required to call this + * function before calling any other extractor functions. + * + * @param string $format + * @param string $filename + * @param int $time + * @param bool $download + * @param bool $store + * @return null + * @throws \phpbb\db\extractor\exception\invalid_format_exception when $format is invalid + */ + public function init_extractor($format, $filename, $time, $download = false, $store = false); + + /** + * Writes header comments to the database backup + * + * @param string $table_prefix prefix of phpBB database tables + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_start($table_prefix); + + /** + * Closes file and/or dumps download data + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end(); + + /** + * Extracts database table structure + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_table($table_name); + + /** + * Extracts data from database table + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_data($table_name); + + /** + * Writes data to file/download content + * + * @param string $data + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function flush($data); +} diff --git a/phpBB/phpbb/db/extractor/factory.php b/phpBB/phpbb/db/extractor/factory.php new file mode 100644 index 0000000000..a1ffb65595 --- /dev/null +++ b/phpBB/phpbb/db/extractor/factory.php @@ -0,0 +1,79 @@ +<?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\db\extractor; + +/** +* A factory which serves the suitable extractor instance for the given dbal +*/ +class factory +{ + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * Extractor factory constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \Symfony\Component\DependencyInjection\ContainerInterface $container) + { + $this->db = $db; + $this->container = $container; + } + + /** + * DB extractor factory getter + * + * @return \phpbb\db\extractor\extractor_interface an appropriate instance of the database extractor for the used database driver + * @throws \InvalidArgumentException when the database driver is unknown + */ + public function get() + { + // Return the appropriate DB extractor + if ($this->db instanceof \phpbb\db\driver\mssql || $this->db instanceof \phpbb\db\driver\mssql_base) + { + return $this->container->get('dbal.extractor.extractors.mssql_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\mysql_base) + { + return $this->container->get('dbal.extractor.extractors.mysql_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\oracle) + { + return $this->container->get('dbal.extractor.extractors.oracle_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\postgres) + { + return $this->container->get('dbal.extractor.extractors.postgres_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\sqlite) + { + return $this->container->get('dbal.extractor.extractors.sqlite_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\sqlite3) + { + return $this->container->get('dbal.extractor.extractors.sqlite3_extractor'); + } + + throw new \InvalidArgumentException('Invalid database driver given'); + } +} diff --git a/phpBB/phpbb/db/extractor/mssql_extractor.php b/phpBB/phpbb/db/extractor/mssql_extractor.php new file mode 100644 index 0000000000..d0aa78f1f5 --- /dev/null +++ b/phpBB/phpbb/db/extractor/mssql_extractor.php @@ -0,0 +1,524 @@ +<?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\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mssql_extractor extends base_extractor +{ + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT\nGO\n"); + parent::write_end(); + } + + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION\n"; + $sql_data .= "GO\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "IF OBJECT_ID(N'$table_name', N'U') IS NOT NULL\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + $sql_data .= "GO\n"; + $sql_data .= "\nCREATE TABLE [$table_name] (\n"; + $rows = array(); + + $text_flag = false; + + $sql = "SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as IS_IDENTITY + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $line = "\t[{$row['COLUMN_NAME']}] [{$row['DATA_TYPE']}]"; + + if ($row['DATA_TYPE'] == 'text') + { + $text_flag = true; + } + + if ($row['IS_IDENTITY']) + { + $line .= ' IDENTITY (1 , 1)'; + } + + if ($row['CHARACTER_MAXIMUM_LENGTH'] && $row['DATA_TYPE'] !== 'text') + { + $line .= ' (' . $row['CHARACTER_MAXIMUM_LENGTH'] . ')'; + } + + if ($row['IS_NULLABLE'] == 'YES') + { + $line .= ' NULL'; + } + else + { + $line .= ' NOT NULL'; + } + + if ($row['COLUMN_DEFAULT']) + { + $line .= ' DEFAULT ' . $row['COLUMN_DEFAULT']; + } + + $rows[] = $line; + } + $this->db->sql_freeresult($result); + + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n) ON [PRIMARY]"; + + if ($text_flag) + { + $sql_data .= " TEXTIMAGE_ON [PRIMARY]"; + } + + $sql_data .= "\nGO\n\n"; + $rows = array(); + + $sql = "SELECT CONSTRAINT_NAME, COLUMN_NAME + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE + WHERE TABLE_NAME = '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!sizeof($rows)) + { + $sql_data .= "ALTER TABLE [$table_name] WITH NOCHECK ADD\n"; + $sql_data .= "\tCONSTRAINT [{$row['CONSTRAINT_NAME']}] PRIMARY KEY CLUSTERED \n\t(\n"; + } + $rows[] = "\t\t[{$row['COLUMN_NAME']}]"; + } + if (sizeof($rows)) + { + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n\t) ON [PRIMARY] \nGO\n"; + } + $this->db->sql_freeresult($result); + + $index = array(); + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + $index[$row['INDEX_NAME']][] = '[' . $row['COLUMN_NAME'] . ']'; + } + } + $this->db->sql_freeresult($result); + + foreach ($index as $index_name => $column_name) + { + $index[$index_name] = implode(', ', $column_name); + } + + foreach ($index as $index_name => $columns) + { + $sql_data .= "\nCREATE INDEX [$index_name] ON [$table_name]($columns) ON [PRIMARY]\nGO\n"; + } + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->db->get_sql_layer() === 'mssql') + { + $this->write_data_mssql($table_name); + } + else if($this->db->get_sql_layer() === 'mssqlnative') + { + $this->write_data_mssqlnative($table_name); + } + else + { + $this->write_data_odbc($table_name); + } + } + + /** + * Extracts data from database table (for MSSQL driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mssql($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + $ident_set = false; + $sql_data = ''; + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $retrieved_data = mssql_num_rows($result); + + $i_num_fields = mssql_num_fields($result); + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[$i] = mssql_field_type($result, $i); + $ary_name[$i] = mssql_field_name($result, $i); + } + + if ($retrieved_data) + { + $sql = "SELECT 1 as has_identity + FROM INFORMATION_SCHEMA.COLUMNS + WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; + $result2 = $this->db->sql_query($sql); + $row2 = $this->db->sql_fetchrow($result2); + if (!empty($row2['has_identity'])) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; + $ident_set = true; + } + $this->db->sql_freeresult($result2); + } + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = $ary_name[$i]; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + + $this->flush($sql_data); + $sql_data = ''; + } + $this->db->sql_freeresult($result); + + if ($retrieved_data && $ident_set) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; + } + $this->flush($sql_data); + } + + /** + * Extracts data from database table (for MSSQL Native driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mssqlnative($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + $ident_set = false; + $sql_data = ''; + + // Grab all of the data from current table. + $sql = "SELECT * FROM $table_name"; + $this->db->mssqlnative_set_query_options(array('Scrollable' => SQLSRV_CURSOR_STATIC)); + $result = $this->db->sql_query($sql); + + $retrieved_data = $this->db->mssqlnative_num_rows($result); + + if (!$retrieved_data) + { + $this->db->sql_freeresult($result); + return; + } + + $sql = "SELECT COLUMN_NAME, DATA_TYPE + FROM INFORMATION_SCHEMA.COLUMNS + WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_NAME = '" . $this->db->sql_escape($table_name) . "'"; + $result_fields = $this->db->sql_query($sql); + + $i_num_fields = 0; + while ($row = $this->db->sql_fetchrow($result_fields)) + { + $ary_type[$i_num_fields] = $row['DATA_TYPE']; + $ary_name[$i_num_fields] = $row['COLUMN_NAME']; + $i_num_fields++; + } + $this->db->sql_freeresult($result_fields); + + $sql = "SELECT 1 as has_identity + FROM INFORMATION_SCHEMA.COLUMNS + WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; + $result2 = $this->db->sql_query($sql); + $row2 = $this->db->sql_fetchrow($result2); + + if (!empty($row2['has_identity'])) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; + $ident_set = true; + } + $this->db->sql_freeresult($result2); + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + // defaults to type number - better quote just to be safe, so check for is_int too + if (is_int($ary_type[$i]) || preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = $ary_name[$i]; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + + $this->flush($sql_data); + $sql_data = ''; + } + $this->db->sql_freeresult($result); + + if ($ident_set) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; + } + $this->flush($sql_data); + } + + /** + * Extracts data from database table (for ODBC driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_odbc($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + $ident_set = false; + $sql_data = ''; + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $retrieved_data = odbc_num_rows($result); + + if ($retrieved_data) + { + $sql = "SELECT 1 as has_identity + FROM INFORMATION_SCHEMA.COLUMNS + WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; + $result2 = $this->db->sql_query($sql); + $row2 = $this->db->sql_fetchrow($result2); + if (!empty($row2['has_identity'])) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; + $ident_set = true; + } + $this->db->sql_freeresult($result2); + } + + $i_num_fields = odbc_num_fields($result); + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[$i] = odbc_field_type($result, $i + 1); + $ary_name[$i] = odbc_field_name($result, $i + 1); + } + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = $ary_name[$i]; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + + $this->flush($sql_data); + + $sql_data = ''; + + } + $this->db->sql_freeresult($result); + + if ($retrieved_data && $ident_set) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; + } + $this->flush($sql_data); + } +} diff --git a/phpBB/phpbb/db/extractor/mysql_extractor.php b/phpBB/phpbb/db/extractor/mysql_extractor.php new file mode 100644 index 0000000000..34e309c19e --- /dev/null +++ b/phpBB/phpbb/db/extractor/mysql_extractor.php @@ -0,0 +1,403 @@ +<?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\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mysql_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "#\n"; + $sql_data .= "# phpBB Backup Script\n"; + $sql_data .= "# Dump of tables for $table_prefix\n"; + $sql_data .= "# DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "#\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + static $new_extract; + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($new_extract === null) + { + if ($this->db->get_sql_layer() === 'mysqli' || version_compare($this->db->sql_server_info(true), '3.23.20', '>=')) + { + $new_extract = true; + } + else + { + $new_extract = false; + } + } + + if ($new_extract) + { + $this->new_write_table($table_name); + } + else + { + $this->old_write_table($table_name); + } + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->db->get_sql_layer() === 'mysqli') + { + $this->write_data_mysqli($table_name); + } + else + { + $this->write_data_mysql($table_name); + } + } + + /** + * Extracts data from database table (for MySQLi driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mysqli($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = "SELECT * + FROM $table_name"; + $result = mysqli_query($this->db->get_db_connect_id(), $sql, MYSQLI_USE_RESULT); + if ($result != false) + { + $fields_cnt = mysqli_num_fields($result); + + // Get field information + $field = mysqli_fetch_fields($result); + $field_set = array(); + + for ($j = 0; $j < $fields_cnt; $j++) + { + $field_set[] = $field[$j]->name; + } + + $search = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); + $replace = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); + $fields = implode(', ', $field_set); + $sql_data = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; + $first_set = true; + $query_len = 0; + $max_len = get_usable_memory(); + + while ($row = mysqli_fetch_row($result)) + { + $values = array(); + if ($first_set) + { + $query = $sql_data . '('; + } + else + { + $query .= ',('; + } + + for ($j = 0; $j < $fields_cnt; $j++) + { + if (!isset($row[$j]) || is_null($row[$j])) + { + $values[$j] = 'NULL'; + } + else if (($field[$j]->flags & 32768) && !($field[$j]->flags & 1024)) + { + $values[$j] = $row[$j]; + } + else + { + $values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; + } + } + $query .= implode(', ', $values) . ')'; + + $query_len += strlen($query); + if ($query_len > $max_len) + { + $this->flush($query . ";\n\n"); + $query = ''; + $query_len = 0; + $first_set = true; + } + else + { + $first_set = false; + } + } + mysqli_free_result($result); + + // check to make sure we have nothing left to flush + if (!$first_set && $query) + { + $this->flush($query . ";\n\n"); + } + } + } + + /** + * Extracts data from database table (for MySQL driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mysql($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = "SELECT * + FROM $table_name"; + $result = mysql_unbuffered_query($sql, $this->db->get_db_connect_id()); + + if ($result != false) + { + $fields_cnt = mysql_num_fields($result); + + // Get field information + $field = array(); + for ($i = 0; $i < $fields_cnt; $i++) + { + $field[] = mysql_fetch_field($result, $i); + } + $field_set = array(); + + for ($j = 0; $j < $fields_cnt; $j++) + { + $field_set[] = $field[$j]->name; + } + + $search = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); + $replace = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); + $fields = implode(', ', $field_set); + $sql_data = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; + $first_set = true; + $query_len = 0; + $max_len = get_usable_memory(); + + while ($row = mysql_fetch_row($result)) + { + $values = array(); + if ($first_set) + { + $query = $sql_data . '('; + } + else + { + $query .= ',('; + } + + for ($j = 0; $j < $fields_cnt; $j++) + { + if (!isset($row[$j]) || is_null($row[$j])) + { + $values[$j] = 'NULL'; + } + else if ($field[$j]->numeric && ($field[$j]->type !== 'timestamp')) + { + $values[$j] = $row[$j]; + } + else + { + $values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; + } + } + $query .= implode(', ', $values) . ')'; + + $query_len += strlen($query); + if ($query_len > $max_len) + { + $this->flush($query . ";\n\n"); + $query = ''; + $query_len = 0; + $first_set = true; + } + else + { + $first_set = false; + } + } + mysql_free_result($result); + + // check to make sure we have nothing left to flush + if (!$first_set && $query) + { + $this->flush($query . ";\n\n"); + } + } + } + + /** + * Extracts database table structure (for MySQLi or MySQL 3.23.20+) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function new_write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = 'SHOW CREATE TABLE ' . $table_name; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + + $sql_data = '# Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; + $this->flush($sql_data . $row['Create Table'] . ";\n\n"); + + $this->db->sql_freeresult($result); + } + + /** + * Extracts database table structure (for MySQL verisons older than 3.23.20) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function old_write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '# Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; + $sql_data .= "CREATE TABLE $table_name(\n"; + $rows = array(); + + $sql = "SHOW FIELDS + FROM $table_name"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $line = ' ' . $row['Field'] . ' ' . $row['Type']; + + if (!is_null($row['Default'])) + { + $line .= " DEFAULT '{$row['Default']}'"; + } + + if ($row['Null'] != 'YES') + { + $line .= ' NOT NULL'; + } + + if ($row['Extra'] != '') + { + $line .= ' ' . $row['Extra']; + } + + $rows[] = $line; + } + $this->db->sql_freeresult($result); + + $sql = "SHOW KEYS + FROM $table_name"; + + $result = $this->db->sql_query($sql); + + $index = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $kname = $row['Key_name']; + + if ($kname != 'PRIMARY') + { + if ($row['Non_unique'] == 0) + { + $kname = "UNIQUE|$kname"; + } + } + + if ($row['Sub_part']) + { + $row['Column_name'] .= '(' . $row['Sub_part'] . ')'; + } + $index[$kname][] = $row['Column_name']; + } + $this->db->sql_freeresult($result); + + foreach ($index as $key => $columns) + { + $line = ' '; + + if ($key == 'PRIMARY') + { + $line .= 'PRIMARY KEY (' . implode(', ', $columns) . ')'; + } + else if (strpos($key, 'UNIQUE') === 0) + { + $line .= 'UNIQUE ' . substr($key, 7) . ' (' . implode(', ', $columns) . ')'; + } + else if (strpos($key, 'FULLTEXT') === 0) + { + $line .= 'FULLTEXT ' . substr($key, 9) . ' (' . implode(', ', $columns) . ')'; + } + else + { + $line .= "KEY $key (" . implode(', ', $columns) . ')'; + } + + $rows[] = $line; + } + + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n);\n\n"; + + $this->flush($sql_data); + } +} diff --git a/phpBB/phpbb/db/extractor/oracle_extractor.php b/phpBB/phpbb/db/extractor/oracle_extractor.php new file mode 100644 index 0000000000..05f7b8ac95 --- /dev/null +++ b/phpBB/phpbb/db/extractor/oracle_extractor.php @@ -0,0 +1,265 @@ +<?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\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class oracle_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name\n/\n"; + $sql_data .= "\nCREATE TABLE $table_name (\n"; + + $sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_PRECISION, DATA_LENGTH, NULLABLE, DATA_DEFAULT + FROM ALL_TAB_COLS + WHERE table_name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $rows = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $line = ' "' . $row['column_name'] . '" ' . $row['data_type']; + + if ($row['data_type'] !== 'CLOB') + { + if ($row['data_type'] !== 'VARCHAR2' && $row['data_type'] !== 'CHAR') + { + $line .= '(' . $row['data_precision'] . ')'; + } + else + { + $line .= '(' . $row['data_length'] . ')'; + } + } + + if (!empty($row['data_default'])) + { + $line .= ' DEFAULT ' . $row['data_default']; + } + + if ($row['nullable'] == 'N') + { + $line .= ' NOT NULL'; + } + $rows[] = $line; + } + $this->db->sql_freeresult($result); + + $sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME + FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B + WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME + AND B.CONSTRAINT_TYPE = 'P' + AND A.TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $primary_key = array(); + $constraint_name = ''; + while ($row = $this->db->sql_fetchrow($result)) + { + $constraint_name = '"' . $row['constraint_name'] . '"'; + $primary_key[] = '"' . $row['column_name'] . '"'; + } + $this->db->sql_freeresult($result); + + if (sizeof($primary_key)) + { + $rows[] = " CONSTRAINT {$constraint_name} PRIMARY KEY (" . implode(', ', $primary_key) . ')'; + } + + $sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME + FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B + WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME + AND B.CONSTRAINT_TYPE = 'U' + AND A.TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $unique = array(); + $constraint_name = ''; + while ($row = $this->db->sql_fetchrow($result)) + { + $constraint_name = '"' . $row['constraint_name'] . '"'; + $unique[] = '"' . $row['column_name'] . '"'; + } + $this->db->sql_freeresult($result); + + if (sizeof($unique)) + { + $rows[] = " CONSTRAINT {$constraint_name} UNIQUE (" . implode(', ', $unique) . ')'; + } + + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n)\n/\n"; + + $sql = "SELECT A.REFERENCED_NAME, C.* + FROM USER_DEPENDENCIES A, USER_TRIGGERS B, USER_SEQUENCES C + WHERE A.REFERENCED_TYPE = 'SEQUENCE' + AND A.NAME = B.TRIGGER_NAME + AND B.TABLE_NAME = '{$table_name}' + AND C.SEQUENCE_NAME = A.REFERENCED_NAME"; + $result = $this->db->sql_query($sql); + + $type = $this->request->variable('type', ''); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_data .= "\nDROP SEQUENCE \"{$row['referenced_name']}\"\n/\n"; + $sql_data .= "\nCREATE SEQUENCE \"{$row['referenced_name']}\""; + + if ($type == 'full') + { + $sql_data .= ' START WITH ' . $row['last_number']; + } + + $sql_data .= "\n/\n"; + } + $this->db->sql_freeresult($result); + + $sql = "SELECT DESCRIPTION, WHEN_CLAUSE, TRIGGER_BODY + FROM USER_TRIGGERS + WHERE TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_data .= "\nCREATE OR REPLACE TRIGGER {$row['description']}WHEN ({$row['when_clause']})\n{$row['trigger_body']}\n/\n"; + } + $this->db->sql_freeresult($result); + + $sql = "SELECT A.INDEX_NAME, B.COLUMN_NAME + FROM USER_INDEXES A, USER_IND_COLUMNS B + WHERE A.UNIQUENESS = 'NONUNIQUE' + AND A.INDEX_NAME = B.INDEX_NAME + AND B.TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $index = array(); + + while ($row = $this->db->sql_fetchrow($result)) + { + $index[$row['index_name']][] = $row['column_name']; + } + + foreach ($index as $index_name => $column_names) + { + $sql_data .= "\nCREATE INDEX $index_name ON $table_name(" . implode(', ', $column_names) . ")\n/\n"; + } + $this->db->sql_freeresult($result); + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $i_num_fields = ocinumcols($result); + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[$i] = ocicolumntype($result, $i + 1); + $ary_name[$i] = ocicolumnname($result, $i + 1); + } + + $sql_data = ''; + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + // Oracle uses uppercase - we use lowercase + $str_val = $row[strtolower($ary_name[$i])]; + + if (preg_match('#char|text|bool|raw|clob#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_oracle($str_val); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0') + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = '"' . $ary_name[$i] . '"'; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data = "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ")\n/\n"; + + $this->flush($sql_data); + } + $this->db->sql_freeresult($result); + } + + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $this->flush($sql_data); + } +} diff --git a/phpBB/phpbb/db/extractor/postgres_extractor.php b/phpBB/phpbb/db/extractor/postgres_extractor.php new file mode 100644 index 0000000000..9eff1f568d --- /dev/null +++ b/phpBB/phpbb/db/extractor/postgres_extractor.php @@ -0,0 +1,338 @@ +<?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\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class postgres_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION;\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + static $domains_created = array(); + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = "SELECT a.domain_name, a.data_type, a.character_maximum_length, a.domain_default + FROM INFORMATION_SCHEMA.domains a, INFORMATION_SCHEMA.column_domain_usage b + WHERE a.domain_name = b.domain_name + AND b.table_name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (empty($domains_created[$row['domain_name']])) + { + $domains_created[$row['domain_name']] = true; + //$sql_data = "DROP DOMAIN {$row['domain_name']};\n"; + $sql_data = "CREATE DOMAIN {$row['domain_name']} as {$row['data_type']}"; + if (!empty($row['character_maximum_length'])) + { + $sql_data .= '(' . $row['character_maximum_length'] . ')'; + } + $sql_data .= ' NOT NULL'; + if (!empty($row['domain_default'])) + { + $sql_data .= ' DEFAULT ' . $row['domain_default']; + } + $this->flush($sql_data . ";\n"); + } + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + // PGSQL does not "tightly" bind sequences and tables, we must guess... + $sql = "SELECT relname + FROM pg_class + WHERE relkind = 'S' + AND relname = '{$table_name}_seq'"; + $result = $this->db->sql_query($sql); + // We don't even care about storing the results. We already know the answer if we get rows back. + if ($this->db->sql_fetchrow($result)) + { + $sql_data .= "DROP SEQUENCE {$table_name}_seq;\n"; + $sql_data .= "CREATE SEQUENCE {$table_name}_seq;\n"; + } + $this->db->sql_freeresult($result); + + $field_query = "SELECT a.attnum, a.attname as field, t.typname as type, a.attlen as length, a.atttypmod as lengthvar, a.attnotnull as notnull + FROM pg_class c, pg_attribute a, pg_type t + WHERE c.relname = '" . $this->db->sql_escape($table_name) . "' + AND a.attnum > 0 + AND a.attrelid = c.oid + AND a.atttypid = t.oid + ORDER BY a.attnum"; + $result = $this->db->sql_query($field_query); + + $sql_data .= "CREATE TABLE $table_name(\n"; + $lines = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + // Get the data from the table + $sql_get_default = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault + FROM pg_attrdef d, pg_class c + WHERE (c.relname = '" . $this->db->sql_escape($table_name) . "') + AND (c.oid = d.adrelid) + AND d.adnum = " . $row['attnum']; + $def_res = $this->db->sql_query($sql_get_default); + $def_row = $this->db->sql_fetchrow($def_res); + $this->db->sql_freeresult($def_res); + + if (empty($def_row)) + { + unset($row['rowdefault']); + } + else + { + $row['rowdefault'] = $def_row['rowdefault']; + } + + if ($row['type'] == 'bpchar') + { + // Internally stored as bpchar, but isn't accepted in a CREATE TABLE statement. + $row['type'] = 'char'; + } + + $line = ' ' . $row['field'] . ' ' . $row['type']; + + if (strpos($row['type'], 'char') !== false) + { + if ($row['lengthvar'] > 0) + { + $line .= '(' . ($row['lengthvar'] - 4) . ')'; + } + } + + if (strpos($row['type'], 'numeric') !== false) + { + $line .= '('; + $line .= sprintf("%s,%s", (($row['lengthvar'] >> 16) & 0xffff), (($row['lengthvar'] - 4) & 0xffff)); + $line .= ')'; + } + + if (isset($row['rowdefault'])) + { + $line .= ' DEFAULT ' . $row['rowdefault']; + } + + if ($row['notnull'] == 't') + { + $line .= ' NOT NULL'; + } + + $lines[] = $line; + } + $this->db->sql_freeresult($result); + + // Get the listing of primary keys. + $sql_pri_keys = "SELECT ic.relname as index_name, bc.relname as tab_name, ta.attname as column_name, i.indisunique as unique_key, i.indisprimary as primary_key + FROM pg_class bc, pg_class ic, pg_index i, pg_attribute ta, pg_attribute ia + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (ia.attrelid = i.indexrelid) + AND (ta.attrelid = bc.oid) + AND (bc.relname = '" . $this->db->sql_escape($table_name) . "') + AND (ta.attrelid = i.indrelid) + AND (ta.attnum = i.indkey[ia.attnum-1]) + ORDER BY index_name, tab_name, column_name"; + + $result = $this->db->sql_query($sql_pri_keys); + + $index_create = $index_rows = $primary_key = array(); + + // We do this in two steps. It makes placing the comma easier + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['primary_key'] == 't') + { + $primary_key[] = $row['column_name']; + $primary_key_name = $row['index_name']; + } + else + { + // We have to store this all this info because it is possible to have a multi-column key... + // we can loop through it again and build the statement + $index_rows[$row['index_name']]['table'] = $table_name; + $index_rows[$row['index_name']]['unique'] = ($row['unique_key'] == 't') ? true : false; + $index_rows[$row['index_name']]['column_names'][] = $row['column_name']; + } + } + $this->db->sql_freeresult($result); + + if (!empty($index_rows)) + { + foreach ($index_rows as $idx_name => $props) + { + $index_create[] = 'CREATE ' . ($props['unique'] ? 'UNIQUE ' : '') . "INDEX $idx_name ON $table_name (" . implode(', ', $props['column_names']) . ");"; + } + } + + if (!empty($primary_key)) + { + $lines[] = " CONSTRAINT $primary_key_name PRIMARY KEY (" . implode(', ', $primary_key) . ")"; + } + + // Generate constraint clauses for CHECK constraints + $sql_checks = "SELECT conname as index_name, consrc + FROM pg_constraint, pg_class bc + WHERE conrelid = bc.oid + AND bc.relname = '" . $this->db->sql_escape($table_name) . "' + AND NOT EXISTS ( + SELECT * + FROM pg_constraint as c, pg_inherits as i + WHERE i.inhrelid = pg_constraint.conrelid + AND c.conname = pg_constraint.conname + AND c.consrc = pg_constraint.consrc + AND c.conrelid = i.inhparent + )"; + $result = $this->db->sql_query($sql_checks); + + // Add the constraints to the sql file. + while ($row = $this->db->sql_fetchrow($result)) + { + if (!is_null($row['consrc'])) + { + $lines[] = ' CONSTRAINT ' . $row['index_name'] . ' CHECK ' . $row['consrc']; + } + } + $this->db->sql_freeresult($result); + + $sql_data .= implode(", \n", $lines); + $sql_data .= "\n);\n"; + + if (!empty($index_create)) + { + $sql_data .= implode("\n", $index_create) . "\n\n"; + } + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $i_num_fields = pg_num_fields($result); + $seq = ''; + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[] = pg_field_type($result, $i); + $ary_name[] = pg_field_name($result, $i); + + $sql = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault + FROM pg_attrdef d, pg_class c + WHERE (c.relname = '{$table_name}') + AND (c.oid = d.adrelid) + AND d.adnum = " . strval($i + 1); + $result2 = $this->db->sql_query($sql); + if ($row = $this->db->sql_fetchrow($result2)) + { + // Determine if we must reset the sequences + if (strpos($row['rowdefault'], "nextval('") === 0) + { + $seq .= "SELECT SETVAL('{$table_name}_seq',(select case when max({$ary_name[$i]})>0 then max({$ary_name[$i]})+1 else 1 end FROM {$table_name}));\n"; + } + } + } + + $this->flush("COPY $table_name (" . implode(', ', $ary_name) . ') FROM stdin;' . "\n"); + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + if (preg_match('#char|text|bool|bytea#i', $ary_type[$i])) + { + $str_val = str_replace(array("\n", "\t", "\r", "\b", "\f", "\v"), array('\n', '\t', '\r', '\b', '\f', '\v'), addslashes($str_val)); + $str_empty = ''; + } + else + { + $str_empty = '\N'; + } + + if (empty($str_val) && $str_val !== '0') + { + $str_val = $str_empty; + } + + $schema_vals[] = $str_val; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $this->flush(implode("\t", $schema_vals) . "\n"); + } + $this->db->sql_freeresult($result); + $this->flush("\\.\n"); + + // Write out the sequence statements + $this->flush($seq); + } + + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT;\n"); + parent::write_end(); + } +} diff --git a/phpBB/phpbb/db/extractor/sqlite3_extractor.php b/phpBB/phpbb/db/extractor/sqlite3_extractor.php new file mode 100644 index 0000000000..ce8da6a652 --- /dev/null +++ b/phpBB/phpbb/db/extractor/sqlite3_extractor.php @@ -0,0 +1,151 @@ +<?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\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class sqlite3_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION;\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '" . $this->db->sql_escape($table_name) . "' + ORDER BY name ASC;"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Create Table + $sql_data .= $row['sql'] . ";\n"; + + $result = $this->db->sql_query("PRAGMA index_list('" . $this->db->sql_escape($table_name) . "');"); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (strpos($row['name'], 'autoindex') !== false) + { + continue; + } + + $result2 = $this->db->sql_query("PRAGMA index_info('" . $this->db->sql_escape($row['name']) . "');"); + + $fields = array(); + while ($row2 = $this->db->sql_fetchrow($result2)) + { + $fields[] = $row2['name']; + } + $this->db->sql_freeresult($result2); + + $sql_data .= 'CREATE ' . ($row['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $row['name'] . ' ON ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; + } + $this->db->sql_freeresult($result); + + $this->flush($sql_data . "\n"); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $result = $this->db->sql_query("PRAGMA table_info('" . $this->db->sql_escape($table_name) . "');"); + + $col_types = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $col_types[$row['name']] = $row['type']; + } + $this->db->sql_freeresult($result); + + $sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; + + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + foreach ($row as $column_name => $column_data) + { + if (is_null($column_data)) + { + $row[$column_name] = 'NULL'; + } + else if ($column_data === '') + { + $row[$column_name] = "''"; + } + else if (stripos($col_types[$column_name], 'text') !== false || stripos($col_types[$column_name], 'char') !== false || stripos($col_types[$column_name], 'blob') !== false) + { + $row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); + } + } + $this->flush($sql_insert . implode(', ', $row) . ");\n"); + } + } + + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT;\n"); + parent::write_end(); + } +} diff --git a/phpBB/phpbb/db/extractor/sqlite_extractor.php b/phpBB/phpbb/db/extractor/sqlite_extractor.php new file mode 100644 index 0000000000..2734e23235 --- /dev/null +++ b/phpBB/phpbb/db/extractor/sqlite_extractor.php @@ -0,0 +1,149 @@ +<?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\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class sqlite_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION;\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '" . $this->db->sql_escape($table_name) . "' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Create Table + $sql_data .= $row['sql'] . ";\n"; + + $result = $this->db->sql_query("PRAGMA index_list('" . $this->db->sql_escape($table_name) . "');"); + + $ar = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $ar[] = $row; + } + $this->db->sql_freeresult($result); + + foreach ($ar as $value) + { + if (strpos($value['name'], 'autoindex') !== false) + { + continue; + } + + $result = $this->db->sql_query("PRAGMA index_info('" . $this->db->sql_escape($value['name']) . "');"); + + $fields = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $fields[] = $row['name']; + } + $this->db->sql_freeresult($result); + + $sql_data .= 'CREATE ' . ($value['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $value['name'] . ' on ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; + } + + $this->flush($sql_data . "\n"); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $col_types = sqlite_fetch_column_types($this->db->get_db_connect_id(), $table_name); + + $sql = "SELECT * + FROM $table_name"; + $result = sqlite_unbuffered_query($this->db->get_db_connect_id(), $sql); + $rows = sqlite_fetch_all($result, SQLITE_ASSOC); + $sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; + foreach ($rows as $row) + { + foreach ($row as $column_name => $column_data) + { + if (is_null($column_data)) + { + $row[$column_name] = 'NULL'; + } + else if ($column_data == '') + { + $row[$column_name] = "''"; + } + else if (strpos($col_types[$column_name], 'text') !== false || strpos($col_types[$column_name], 'char') !== false || strpos($col_types[$column_name], 'blob') !== false) + { + $row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); + } + } + $this->flush($sql_insert . implode(', ', $row) . ");\n"); + } + } + + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT;\n"); + parent::write_end(); + } +} diff --git a/phpBB/phpbb/db/migration/data/v310/avatars.php b/phpBB/phpbb/db/migration/data/v310/avatars.php index 2698adeed5..9b03a8fa94 100644 --- a/phpBB/phpbb/db/migration/data/v310/avatars.php +++ b/phpBB/phpbb/db/migration/data/v310/avatars.php @@ -17,7 +17,29 @@ class avatars extends \phpbb\db\migration\migration { public function effectively_installed() { - return isset($this->config['allow_avatar_gravatar']); + // Get current avatar type of guest user + $sql = 'SELECT user_avatar_type + FROM ' . $this->table_prefix . 'users + WHERE user_id = ' . ANONYMOUS; + $result = $this->db->sql_query($sql); + $backup_type = $this->db->sql_fetchfield('user_avatar_type'); + $this->db->sql_freeresult($result); + + // Try to set avatar type to string + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_avatar_type = 'avatar.driver.upload' + WHERE user_id = " . ANONYMOUS; + $this->db->sql_return_on_error(true); + $effectively_installed = $this->db->sql_query($sql); + $this->db->sql_return_on_error(); + + // Restore avatar type of guest user to previous state + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_avatar_type = '{$backup_type}' + WHERE user_id = " . ANONYMOUS; + $this->db->sql_query($sql); + + return $effectively_installed !== false; } static public function depends_on() diff --git a/phpBB/phpbb/db/migration/data/v31x/v314rc1.php b/phpBB/phpbb/db/migration/data/v31x/v314rc1.php new file mode 100644 index 0000000000..10cdbe3f9c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v31x/v314rc1.php @@ -0,0 +1,31 @@ +<?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\db\migration\data\v31x; + +class v314rc1 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v313', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.4-RC1')), + ); + } +} diff --git a/phpBB/phpbb/plupload/plupload.php b/phpBB/phpbb/plupload/plupload.php index fcce5b3bd8..ca78167ec0 100644 --- a/phpBB/phpbb/plupload/plupload.php +++ b/phpBB/phpbb/plupload/plupload.php @@ -267,8 +267,8 @@ class plupload { $resize = sprintf( 'resize: {width: %d, height: %d, quality: 100},', - (int) $this->config['img_max_height'], - (int) $this->config['img_max_width'] + (int) $this->config['img_max_width'], + (int) $this->config['img_max_height'] ); } diff --git a/phpBB/phpbb/textformatter/cache_interface.php b/phpBB/phpbb/textformatter/cache_interface.php new file mode 100644 index 0000000000..f6b5f195c7 --- /dev/null +++ b/phpBB/phpbb/textformatter/cache_interface.php @@ -0,0 +1,31 @@ +<?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; + +/** +* Currently only used to signal that something that could effect the rendering has changed. +* BBCodes, smilies, censored words, templates, etc... +*/ +interface cache_interface +{ + /** + * Invalidate and/or regenerate this text formatter's cache(s) + */ + public function invalidate(); + + /** + * Tidy/prune this text formatter's cache(s) + */ + public function tidy(); +} diff --git a/phpBB/phpbb/textformatter/data_access.php b/phpBB/phpbb/textformatter/data_access.php new file mode 100644 index 0000000000..8938d66935 --- /dev/null +++ b/phpBB/phpbb/textformatter/data_access.php @@ -0,0 +1,228 @@ +<?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; + +/** +* Data access layer that fetchs BBCodes, smilies and censored words from the database. +* To be extended to include insert/update/delete operations. +* +* Also used to get templates. +*/ +class data_access +{ + /** + * @var string Name of the BBCodes table + */ + protected $bbcodes_table; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var string Name of the smilies table + */ + protected $smilies_table; + + /** + * @var string Name of the styles table + */ + protected $styles_table; + + /** + * @var string Path to the styles dir + */ + protected $styles_path; + + /** + * @var string Name of the words table + */ + protected $words_table; + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param string $bbcodes_table Name of the BBCodes table + * @param string $smilies_table Name of the smilies table + * @param string $styles_table Name of the styles table + * @param string $words_table Name of the words table + * @param string $styles_path Path to the styles dir + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $bbcodes_table, $smilies_table, $styles_table, $words_table, $styles_path) + { + $this->db = $db; + + $this->bbcodes_table = $bbcodes_table; + $this->smilies_table = $smilies_table; + $this->styles_table = $styles_table; + $this->words_table = $words_table; + + $this->styles_path = $styles_path; + } + + /** + * Return the list of custom BBCodes + * + * @return array + */ + public function get_bbcodes() + { + $sql = 'SELECT bbcode_match, bbcode_tpl FROM ' . $this->bbcodes_table; + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * Return the list of smilies + * + * @return array + */ + public function get_smilies() + { + // NOTE: smilies that are displayed on the posting page are processed first because they're + // typically the most used smilies and it ends up producing a slightly more efficient + // renderer + $sql = 'SELECT code, emotion, smiley_url, smiley_width, smiley_height + FROM ' . $this->smilies_table . ' + ORDER BY display_on_posting DESC'; + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * Return the list of installed styles + * + * @return array + */ + protected function get_styles() + { + $sql = 'SELECT style_id, style_path, bbcode_bitfield FROM ' . $this->styles_table; + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * Return the bbcode.html template for every installed style + * + * @return array 2D array. style_id as keys, each element is an array with a "template" element that contains the style's bbcode.html and a "bbcodes" element that contains the name of each BBCode that is to be stylised + */ + public function get_styles_templates() + { + $templates = array(); + + $bbcode_ids = array( + 'quote' => 0, + 'b' => 1, + 'i' => 2, + 'url' => 3, + 'img' => 4, + 'size' => 5, + 'color' => 6, + 'u' => 7, + 'code' => 8, + 'list' => 9, + '*' => 9, + 'email' => 10, + 'flash' => 11, + 'attachment' => 12, + ); + + $styles = array(); + foreach ($this->get_styles() as $row) + { + $styles[$row['style_id']] = $row; + } + + foreach ($styles as $style_id => $style) + { + $bbcodes = array(); + + // Collect the name of the BBCodes whose bit is set in the style's bbcode_bitfield + $template_bitfield = new \bitfield($style['bbcode_bitfield']); + foreach ($bbcode_ids as $bbcode_name => $bit) + { + if ($template_bitfield->get($bit)) + { + $bbcodes[] = $bbcode_name; + } + } + + $filename = $this->resolve_style_filename($styles, $style); + if ($filename === false) + { + // Ignore this style, it will use the default templates + continue; + } + + $templates[$style_id] = array( + 'bbcodes' => $bbcodes, + 'template' => file_get_contents($filename), + ); + } + + return $templates; + } + + /** + * Resolve inheritance for given style and return the path to their bbcode.html file + * + * @param array $styles Associative array of [style_id => style] containing all styles + * @param array $style Style for which we resolve + * @return string|bool Path to this style's bbcode.html, or FALSE + */ + protected function resolve_style_filename(array $styles, array $style) + { + // Look for a bbcode.html in this style's dir + $filename = $this->styles_path . $style['style_path'] . '/template/bbcode.html'; + if (file_exists($filename)) + { + return $filename; + } + + // Resolve using this style's parent + $parent_id = $style['style_parent_id']; + if ($parent_id && !empty($styles[$parent_id])) + { + return $this->resolve_style_filename($styles, $styles[$parent_id]); + } + + return false; + } + + /** + * Return the list of censored words + * + * @return array + */ + public function get_censored_words() + { + $sql = 'SELECT word, replacement FROM ' . $this->words_table; + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } +} diff --git a/phpBB/phpbb/textformatter/parser_interface.php b/phpBB/phpbb/textformatter/parser_interface.php new file mode 100644 index 0000000000..3cb9f8e977 --- /dev/null +++ b/phpBB/phpbb/textformatter/parser_interface.php @@ -0,0 +1,111 @@ +<?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; + +interface parser_interface +{ + /** + * Parse given text + * + * @param string $text + * @return string + */ + public function parse($text); + + /** + * Disable a specific BBCode + * + * @param string $name BBCode name + * @return null + */ + public function disable_bbcode($name); + + /** + * Disable BBCodes in general + */ + public function disable_bbcodes(); + + /** + * Disable the censor + */ + public function disable_censor(); + + /** + * Disable magic URLs + */ + public function disable_magic_url(); + + /** + * Disable smilies + */ + public function disable_smilies(); + + /** + * Enable a specific BBCode + * + * @param string $name BBCode name + * @return null + */ + public function enable_bbcode($name); + + /** + * Enable BBCodes in general + */ + public function enable_bbcodes(); + + /** + * Enable the censor + */ + public function enable_censor(); + + /** + * Enable magic URLs + */ + public function enable_magic_url(); + + /** + * Enable smilies + */ + public function enable_smilies(); + + /** + * Get the list of errors that were generated during last parsing + * + * @return array + */ + public function get_errors(); + + /** + * Set a variable to be used by the parser + * + * - max_font_size + * - max_img_height + * - max_img_width + * - max_smilies + * - max_urls + * + * @param string $name + * @param mixed $value + * @return null + */ + public function set_var($name, $value); + + /** + * Set multiple variables to be used by the parser + * + * @param array $vars Associative array of [name => value] + * @return null + */ + public function set_vars(array $vars); +} diff --git a/phpBB/phpbb/textformatter/renderer_interface.php b/phpBB/phpbb/textformatter/renderer_interface.php new file mode 100644 index 0000000000..609b0bb642 --- /dev/null +++ b/phpBB/phpbb/textformatter/renderer_interface.php @@ -0,0 +1,92 @@ +<?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; + +interface renderer_interface +{ + /** + * Render given text + * + * @param string $text Text, as parsed by something that implements \phpbb\textformatter\parser + * @return string + */ + public function render($text); + + /** + * Set the smilies' path + * + * @return null + */ + public function set_smilies_path($path); + + /** + * Return the value of the "viewcensors" option + * + * @return bool Option's value + */ + public function get_viewcensors(); + + /** + * Return the value of the "viewflash" option + * + * @return bool Option's value + */ + public function get_viewflash(); + + /** + * Return the value of the "viewimg" option + * + * @return bool Option's value + */ + public function get_viewimg(); + + /** + * Return the value of the "viewsmilies" option + * + * @return bool Option's value + */ + public function get_viewsmilies(); + + /** + * Set the "viewcensors" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewcensors($value); + + /** + * Set the "viewflash" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewflash($value); + + /** + * Set the "viewimg" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewimg($value); + + /** + * Set the "viewsmilies" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewsmilies($value); +} diff --git a/phpBB/phpbb/textformatter/s9e/factory.php b/phpBB/phpbb/textformatter/s9e/factory.php new file mode 100644 index 0000000000..9576abe1f0 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/factory.php @@ -0,0 +1,545 @@ +<?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; + +use s9e\TextFormatter\Configurator; +use s9e\TextFormatter\Configurator\Items\AttributeFilters\RegexpFilter; +use s9e\TextFormatter\Configurator\Items\UnsafeTemplate; + +/** +* Creates s9e\TextFormatter objects +*/ +class factory implements \phpbb\textformatter\cache_interface +{ + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var string Path to the cache dir + */ + protected $cache_dir; + + /** + * @var string Cache key used for the parser + */ + protected $cache_key_parser; + + /** + * @var string Cache key used for the renderer + */ + protected $cache_key_renderer; + + /** + * @var array Custom tokens used in bbcode.html and their corresponding token from the definition + */ + protected $custom_tokens = array( + 'email' => array('{DESCRIPTION}' => '{TEXT}'), + 'flash' => array('{WIDTH}' => '{NUMBER1}', '{HEIGHT}' => '{NUMBER2}'), + 'img' => array('{URL}' => '{IMAGEURL}'), + 'list' => array('{LIST_TYPE}' => '{HASHMAP}'), + 'quote' => array('{USERNAME}' => '{TEXT1}'), + 'size' => array('{SIZE}' => '{FONTSIZE}'), + 'url' => array('{DESCRIPTION}' => '{TEXT}'), + ); + + /** + * @var \phpbb\textformatter\data_access + */ + protected $data_access; + + /** + * @var array Default BBCode definitions + */ + protected $default_definitions = array( + 'attachment' => '[ATTACHMENT index={NUMBER} filename={TEXT;useContent}]', + 'b' => '[B]{TEXT}[/B]', + 'code' => '[CODE]{TEXT}[/CODE]', + 'color' => '[COLOR={COLOR}]{TEXT}[/COLOR]', + 'email' => '[EMAIL={EMAIL;useContent}]{TEXT}[/EMAIL]', + 'flash' => '[FLASH={NUMBER1},{NUMBER2} width={NUMBER1;postFilter=#flashwidth} height={NUMBER2;postFilter=#flashheight} url={URL;useContent} /]', + 'i' => '[I]{TEXT}[/I]', + 'img' => '[IMG src={IMAGEURL;useContent}]', + 'list' => '[LIST type={HASHMAP=1:decimal,a:lower-alpha,A:upper-alpha,i:lower-roman,I:upper-roman;optional;postFilter=#simpletext}]{TEXT}[/LIST]', + 'li' => '[* $tagName=LI]{TEXT}[/*]', + 'quote' => + "[QUOTE + author={TEXT1;optional} + url={URL;optional} + author={PARSE=/^\\[url=(?'url'.*?)](?'author'.*)\\[\\/url]$/i} + author={PARSE=/^\\[url](?'author'(?'url'.*?))\\[\\/url]$/i} + author={PARSE=/(?'url'https?:\\/\\/[^[\\]]+)/i} + ]{TEXT2}[/QUOTE]", + 'size' => '[SIZE={FONTSIZE}]{TEXT}[/SIZE]', + 'u' => '[U]{TEXT}[/U]', + 'url' => '[URL={URL;useContent}]{TEXT}[/URL]', + ); + + /** + * @var array Default templates, taken from bbcode::bbcode_tpl() + */ + protected $default_templates = array( + 'b' => '<span style="font-weight: bold"><xsl:apply-templates/></span>', + 'i' => '<span style="font-style: italic"><xsl:apply-templates/></span>', + 'u' => '<span style="text-decoration: underline"><xsl:apply-templates/></span>', + 'img' => '<img src="{IMAGEURL}" alt="{L_IMAGE}"/>', + 'size' => '<span style="font-size: {FONTSIZE}%; line-height: normal"><xsl:apply-templates/></span>', + 'color' => '<span style="color: {COLOR}"><xsl:apply-templates/></span>', + 'email' => '<a href="mailto:{EMAIL}"><xsl:apply-templates/></a>', + ); + + /** + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * Constructor + * + * @param \phpbb\textformatter\data_access $data_access + * @param \phpbb\cache\driver\driver_interface $cache + * @param \phpbb\event\dispatcher_interface $dispatcher + * @param string $cache_dir Path to the cache dir + * @param string $cache_key_parser Cache key used for the parser + * @param string $cache_key_renderer Cache key used for the renderer + */ + public function __construct(\phpbb\textformatter\data_access $data_access, \phpbb\cache\driver\driver_interface $cache, \phpbb\event\dispatcher_interface $dispatcher, $cache_dir, $cache_key_parser, $cache_key_renderer) + { + $this->cache = $cache; + $this->cache_dir = $cache_dir; + $this->cache_key_parser = $cache_key_parser; + $this->cache_key_renderer = $cache_key_renderer; + $this->data_access = $data_access; + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function invalidate() + { + $this->regenerate(); + } + + /** + * {@inheritdoc} + * + * Will remove old renderers from the cache dir but won't touch the current renderer + */ + public function tidy() + { + // Get the name of current renderer + $renderer_data = $this->cache->get($this->cache_key_renderer); + $renderer_file = ($renderer_data) ? $renderer_data['class'] . '.php' : null; + + foreach (glob($this->cache_dir . 's9e_*') as $filename) + { + // Only remove the file if it's not the current renderer + if (!$renderer_file || substr($filename, -strlen($renderer_file)) !== $renderer_file) + { + unlink($filename); + } + } + } + + /** + * Generate and return a new configured instance of s9e\TextFormatter\Configurator + * + * @return Configurator + */ + public function get_configurator() + { + // Create a new Configurator + $configurator = new Configurator; + + /** + * Modify the s9e\TextFormatter configurator before the default settings are set + * + * @event core.text_formatter_s9e_configure_before + * @var \s9e\TextFormatter\Configurator configurator Configurator instance + * @since 3.2.0-a1 + */ + $vars = array('configurator'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_before', compact($vars))); + + // Convert newlines to br elements by default + $configurator->rootRules->enableAutoLineBreaks(); + + // Don't automatically ignore text in places where text is not allowed + $configurator->rulesGenerator->remove('IgnoreTextIfDisallowed'); + + // Don't remove comments and instead convert them to xsl:comment elements + $configurator->templateNormalizer->remove('RemoveComments'); + $configurator->templateNormalizer->add('TransposeComments'); + + // Set the rendering engine and configure it to save to the cache dir + $configurator->rendering->engine = 'PHP'; + $configurator->rendering->engine->cacheDir = $this->cache_dir; + $configurator->rendering->engine->defaultClassPrefix = 's9e_renderer_'; + $configurator->rendering->engine->enableQuickRenderer = true; + + // Create custom filters for BBCode tokens that are supported in phpBB but not in + // s9e\TextFormatter + $filter = new RegexpFilter('#^' . get_preg_expression('relative_url') . '$#Du'); + $configurator->attributeFilters->add('#local_url', $filter); + $configurator->attributeFilters->add('#relative_url', $filter); + + // INTTEXT regexp from acp_bbcodes + $filter = new RegexpFilter('!^([\p{L}\p{N}\-+,_. ]+)$!Du'); + $configurator->attributeFilters->add('#inttext', $filter); + + // Create custom filters for Flash restrictions, which use the same values as the image + // restrictions but have their own error message + $configurator->attributeFilters + ->add('#flashheight', __NAMESPACE__ . '\\parser::filter_flash_height') + ->addParameterByName('max_img_height') + ->addParameterByName('logger'); + + $configurator->attributeFilters + ->add('#flashwidth', __NAMESPACE__ . '\\parser::filter_flash_width') + ->addParameterByName('max_img_width') + ->addParameterByName('logger'); + + // Create a custom filter for phpBB's per-mode font size limits + $configurator->attributeFilters + ->add('#fontsize', __NAMESPACE__ . '\\parser::filter_font_size') + ->addParameterByName('max_font_size') + ->addParameterByName('logger') + ->markAsSafeInCSS(); + + // Create a custom filter for image URLs + $configurator->attributeFilters + ->add('#imageurl', __NAMESPACE__ . '\\parser::filter_img_url') + ->addParameterByName('urlConfig') + ->addParameterByName('logger') + ->addParameterByName('max_img_height') + ->addParameterByName('max_img_width') + ->markAsSafeAsURL(); + + // Add default BBCodes + foreach ($this->get_default_bbcodes($configurator) as $bbcode) + { + $configurator->BBCodes->addCustom($bbcode['usage'], $bbcode['template']); + } + + // Modify the template to disable images/flash depending on user's settings + foreach (array('FLASH', 'IMG') as $name) + { + $tag = $configurator->tags[$name]; + $tag->template = '<xsl:choose><xsl:when test="$S_VIEW' . $name . '">' . $tag->template . '</xsl:when><xsl:otherwise><xsl:apply-templates/></xsl:otherwise></xsl:choose>'; + } + + // Load custom BBCodes + foreach ($this->data_access->get_bbcodes() as $row) + { + // Insert the board's URL before {LOCAL_URL} tokens + $tpl = preg_replace_callback( + '#\\{LOCAL_URL\\d*\\}#', + function ($m) + { + return generate_board_url() . '/' . $m[0]; + }, + $row['bbcode_tpl'] + ); + + try + { + $configurator->BBCodes->addCustom($row['bbcode_match'], new UnsafeTemplate($tpl)); + } + catch (\Exception $e) + { + /** + * @todo log an error? + */ + } + } + + // Load smilies + foreach ($this->data_access->get_smilies() as $row) + { + $configurator->Emoticons->add( + $row['code'], + '<img class="smilies" src="{$T_SMILIES_PATH}/' . htmlspecialchars($row['smiley_url']) . '" alt="{.}" title="' . htmlspecialchars($row['emotion']) . '"/>' + ); + } + + if (isset($configurator->Emoticons)) + { + // Force emoticons to be rendered as text if $S_VIEWSMILIES is not set + $configurator->Emoticons->notIfCondition = 'not($S_VIEWSMILIES)'; + + // Only parse emoticons at the beginning of the text or if they're preceded by any + // one of: a new line, a space, a dot, or a right square bracket + $configurator->Emoticons->notAfter = '[^\\n .\\]]'; + } + + // Load the censored words + $censor = $this->data_access->get_censored_words(); + if (!empty($censor)) + { + // Use a namespaced tag to avoid collisions + $configurator->plugins->load('Censor', array('tagName' => 'censor:tag')); + foreach ($censor as $row) + { + // NOTE: words are stored as HTML, we need to decode them to plain text + $configurator->Censor->add(htmlspecialchars_decode($row['word']), htmlspecialchars_decode($row['replacement'])); + } + } + + // Load the magic links plugins. We do that after BBCodes so that they use the same tags + $configurator->plugins->load('Autoemail'); + $configurator->plugins->load('Autolink', array('matchWww' => true)); + + // Register some vars with a default value. Those should be set at runtime by whatever calls + // the parser + $configurator->registeredVars['max_font_size'] = 0; + $configurator->registeredVars['max_img_height'] = 0; + $configurator->registeredVars['max_img_width'] = 0; + + /** + * Modify the s9e\TextFormatter configurator after the default settings are set + * + * @event core.text_formatter_s9e_configure_after + * @var \s9e\TextFormatter\Configurator configurator Configurator instance + * @since 3.2.0-a1 + */ + $vars = array('configurator'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_after', compact($vars))); + + return $configurator; + } + + /** + * Regenerate and cache a new parser and renderer + * + * @return array Associative array with at least two elements: "parser" and "renderer" + */ + public function regenerate() + { + $configurator = $this->get_configurator(); + + // Get the censor helper and remove the Censor plugin if applicable + if (isset($configurator->Censor)) + { + $censor = $configurator->Censor->getHelper(); + unset($configurator->Censor); + unset($configurator->tags['censor:tag']); + } + + $objects = $configurator->finalize(); + $parser = $objects['parser']; + $renderer = $objects['renderer']; + + // Cache the parser as-is + $this->cache->put($this->cache_key_parser, $parser); + + // We need to cache the name of the renderer's generated class + $renderer_data = array('class' => get_class($renderer)); + if (isset($censor)) + { + $renderer_data['censor'] = $censor; + } + $this->cache->put($this->cache_key_renderer, $renderer_data); + + return array('parser' => $parser, 'renderer' => $renderer); + } + + /** + * Return the default BBCodes configuration + * + * @return array 2D array. Each element has a 'usage' key, a 'template' key, and an optional 'options' key + */ + protected function get_default_bbcodes($configurator) + { + // For each BBCode, build an associative array matching style_ids to their template + $templates = array(); + foreach ($this->data_access->get_styles_templates() as $style_id => $data) + { + foreach ($this->extract_templates($data['template']) as $bbcode_name => $template) + { + $templates[$bbcode_name][$style_id] = $template; + } + + // Add default templates wherever missing, or for BBCodes that were not specified in + // this template's bitfield. For instance, prosilver has a custom template for b but its + // bitfield does not enable it so the default template is used instead + foreach ($this->default_templates as $bbcode_name => $template) + { + if (!isset($templates[$bbcode_name][$style_id]) || !in_array($bbcode_name, $data['bbcodes'], true)) + { + $templates[$bbcode_name][$style_id] = $template; + } + } + } + + // Replace custom tokens and normalize templates + foreach ($templates as $bbcode_name => $style_templates) + { + foreach ($style_templates as $i => $template) + { + if (isset($this->custom_tokens[$bbcode_name])) + { + $template = strtr($template, $this->custom_tokens[$bbcode_name]); + } + + $templates[$bbcode_name][$i] = $configurator->templateNormalizer->normalizeTemplate($template); + } + } + + $bbcodes = array(); + foreach ($this->default_definitions as $bbcode_name => $usage) + { + $bbcodes[$bbcode_name] = array( + 'usage' => $usage, + 'template' => $this->merge_templates($templates[$bbcode_name]), + ); + } + + return $bbcodes; + } + + /** + * Extract and recompose individual BBCode templates from a style's template file + * + * @param string $template Style template (bbcode.html) + * @return array Associative array matching BBCode names to their template + */ + protected function extract_templates($template) + { + // Capture the template fragments + preg_match_all('#<!-- BEGIN (.*?) -->(.*?)<!-- END .*? -->#s', $template, $matches, PREG_SET_ORDER); + + $fragments = array(); + foreach ($matches as $match) + { + // Normalize the whitespace + $fragment = preg_replace('#>\\n\\t*<#', '><', trim($match[2])); + + $fragments[$match[1]] = $fragment; + } + + // Automatically recompose templates split between *_open and *_close + foreach ($fragments as $fragment_name => $fragment) + { + if (preg_match('#^(\\w+)_close$#', $fragment_name, $match)) + { + $bbcode_name = $match[1]; + + if (isset($fragments[$bbcode_name . '_open'])) + { + $templates[$bbcode_name] = $fragments[$bbcode_name . '_open'] . '<xsl:apply-templates/>' . $fragment; + } + } + } + + // Manually recompose and overwrite irregular templates + $templates['list'] = + '<xsl:choose> + <xsl:when test="not(@type)"> + ' . $fragments['ulist_open_default'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . ' + </xsl:when> + <xsl:when test="contains(\'upperlowerdecim\',substring(@type,1,5))"> + ' . $fragments['olist_open'] . '<xsl:apply-templates/>' . $fragments['olist_close'] . ' + </xsl:when> + <xsl:otherwise> + ' . $fragments['ulist_open'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . ' + </xsl:otherwise> + </xsl:choose>'; + + $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>'; + + // The [attachment] BBCode uses the inline_attachment template to output a comment that + // is post-processed by parse_attachments() + $templates['attachment'] = $fragments['inline_attachment_open'] . '<xsl:comment> ia<xsl:value-of select="@index"/> </xsl:comment><xsl:value-of select="@filename"/><xsl:comment> ia<xsl:value-of select="@index"/> </xsl:comment>' . $fragments['inline_attachment_close']; + + // Add fragments as templates + foreach ($fragments as $fragment_name => $fragment) + { + if (preg_match('#^\\w+$#', $fragment_name)) + { + $templates[$fragment_name] = $fragment; + } + } + + // Keep only templates that are named after an existing BBCode + $templates = array_intersect_key($templates, $this->default_definitions); + + return $templates; + } + + /** + * Merge the templates from any number of styles into one BBCode template + * + * When multiple templates are available for the same BBCode (because of multiple styles) we + * merge them into a single template that uses an xsl:choose construct that determines which + * style to use at rendering time. + * + * @param array $style_templates Associative array matching style_ids to their template + * @return string + */ + protected function merge_templates(array $style_templates) + { + // Return the template as-is if there's only one style or all styles share the same template + if (count(array_unique($style_templates)) === 1) + { + return end($style_templates); + } + + // Group identical templates together + $grouped_templates = array(); + foreach ($style_templates as $style_id => $style_template) + { + $grouped_templates[$style_template][] = '$STYLE_ID=' . $style_id; + } + + // Sort templates by frequency descending + $templates_cnt = array_map('sizeof', $grouped_templates); + array_multisort($grouped_templates, $templates_cnt); + + // Remove the most frequent template from the list; It becomes the default + reset($grouped_templates); + $default_template = key($grouped_templates); + unset($grouped_templates[$default_template]); + + // Build an xsl:choose switch + $template = '<xsl:choose>'; + foreach ($grouped_templates as $style_template => $exprs) + { + $template .= '<xsl:when test="' . implode(' or ', $exprs) . '">' . $style_template . '</xsl:when>'; + } + $template .= '<xsl:otherwise>' . $default_template . '</xsl:otherwise></xsl:choose>'; + + return $template; + } +} diff --git a/phpBB/phpbb/textformatter/s9e/parser.php b/phpBB/phpbb/textformatter/s9e/parser.php new file mode 100644 index 0000000000..77328ee4d9 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/parser.php @@ -0,0 +1,404 @@ +<?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; + +use s9e\TextFormatter\Parser\BuiltInFilters; +use s9e\TextFormatter\Parser\Logger; + +/** +* s9e\TextFormatter\Parser adapter +*/ +class parser implements \phpbb\textformatter\parser_interface +{ + /** + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * @var \s9e\TextFormatter\Parser + */ + protected $parser; + + /** + * @var \phpbb\user User object, used for translating errors + */ + protected $user; + + /** + * Constructor + * + * @param \phpbb\cache\driver_interface $cache + * @param string $key Cache key + * @param \phpbb\user $user + * @param factory $factory + * @param \phpbb\event\dispatcher_interface $dispatcher + */ + public function __construct(\phpbb\cache\driver\driver_interface $cache, $key, \phpbb\user $user, factory $factory, \phpbb\event\dispatcher_interface $dispatcher) + { + $parser = $cache->get($key); + if (!$parser) + { + $objects = $factory->regenerate(); + $parser = $objects['parser']; + } + + $this->dispatcher = $dispatcher; + $this->parser = $parser; + $this->user = $user; + $parser = $this; + + /** + * Configure the parser service + * + * Can be used to: + * - toggle features according to the user's preferences, + * - toggle BBCodes according to the user's permissions, + * - register variables or custom parsers in the s9e\TextFormatter + * - configure the s9e\TextFormatter parser + * + * @event core.text_formatter_s9e_parser_setup + * @var \phpbb\textformatter\s9e\parser parser This parser service + * @var \phpbb\user user Current user + * @since 3.2.0-a1 + */ + $vars = array('parser', 'user'); + extract($dispatcher->trigger_event('core.text_formatter_s9e_parser_setup', compact($vars))); + } + + /** + * {@inheritdoc} + */ + public function parse($text) + { + $parser = $this; + + /** + * Modify a text before it is parsed + * + * @event core.text_formatter_s9e_parse_before + * @var \phpbb\textformatter\s9e\parser parser This parser service + * @var string text The original text + * @since 3.2.0-a1 + */ + $vars = array('parser', 'text'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_before', compact($vars))); + + $xml = $this->parser->parse($text); + + /** + * Modify a parsed text in its XML form + * + * @event core.text_formatter_s9e_parse_after + * @var \phpbb\textformatter\s9e\parser parser This parser service + * @var string xml The parsed text, in XML + * @since 3.2.0-a1 + */ + $vars = array('parser', 'xml'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_after', compact($vars))); + + return $xml; + } + + /** + * {@inheritdoc} + */ + public function disable_bbcode($name) + { + $this->parser->disableTag(strtoupper($name)); + } + + /** + * {@inheritdoc} + */ + public function disable_bbcodes() + { + $this->parser->disablePlugin('BBCodes'); + } + + /** + * {@inheritdoc} + */ + public function disable_censor() + { + $this->parser->disablePlugin('Censor'); + } + + /** + * {@inheritdoc} + */ + public function disable_magic_url() + { + $this->parser->disablePlugin('Autoemail'); + $this->parser->disablePlugin('Autolink'); + } + + /** + * {@inheritdoc} + */ + public function disable_smilies() + { + $this->parser->disablePlugin('Emoticons'); + } + + /** + * {@inheritdoc} + */ + public function enable_bbcode($name) + { + $this->parser->enableTag(strtoupper($name)); + } + + /** + * {@inheritdoc} + */ + public function enable_bbcodes() + { + $this->parser->enablePlugin('BBCodes'); + } + + /** + * {@inheritdoc} + */ + public function enable_censor() + { + $this->parser->enablePlugin('Censor'); + } + + /** + * {@inheritdoc} + */ + public function enable_magic_url() + { + $this->parser->enablePlugin('Autoemail'); + $this->parser->enablePlugin('Autolink'); + } + + /** + * {@inheritdoc} + */ + public function enable_smilies() + { + $this->parser->enablePlugin('Emoticons'); + } + + /** + * {@inheritdoc} + * + * This will translate the log entries found in s9e\TextFormatter's logger into phpBB error + * messages + */ + public function get_errors() + { + $errors = array(); + + foreach ($this->parser->getLogger()->get() as $entry) + { + list($type, $msg, $context) = $entry; + + if ($msg === 'Tag limit exceeded') + { + if ($context['tagName'] === 'E') + { + $errors[] = $this->user->lang('TOO_MANY_SMILIES', $context['tagLimit']); + } + else if ($context['tagName'] === 'URL') + { + $errors[] = $this->user->lang('TOO_MANY_URLS', $context['tagLimit']); + } + } + else if ($msg === 'MAX_FONT_SIZE_EXCEEDED') + { + $errors[] = $this->user->lang($msg, $context['max_size']); + } + else if (preg_match('/^MAX_(?:FLASH|IMG)_(HEIGHT|WIDTH)_EXCEEDED$/D', $msg, $m)) + { + $errors[] = $this->user->lang($msg, $context['max_' . strtolower($m[1])]); + } + else if ($msg === 'Tag is disabled') + { + $name = strtolower($context['tag']->getName()); + $errors[] = $this->user->lang('UNAUTHORISED_BBCODE', '[' . $name . ']'); + } + else if ($msg === 'UNABLE_GET_IMAGE_SIZE') + { + $errors[] = $this->user->lang($msg); + } + } + + return array_unique($errors); + } + + /** + * Return the instance of s9e\TextFormatter\Parser used by this object + * + * @return \s9e\TextFormatter\Parser + */ + public function get_parser() + { + return $this->parser; + } + + /** + * {@inheritdoc} + */ + public function set_var($name, $value) + { + if ($name === 'max_smilies') + { + $this->parser->setTagLimit('E', $value ?: PHP_INT_MAX); + } + else if ($name === 'max_urls') + { + $this->parser->setTagLimit('URL', $value ?: PHP_INT_MAX); + } + else + { + $this->parser->registeredVars[$name] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function set_vars(array $vars) + { + foreach ($vars as $name => $value) + { + $this->set_var($name, $value); + } + } + + /** + * Filter a flash object's height + * + * @see bbcode_firstpass::bbcode_flash() + * + * @param string $height + * @param integer $max_height + * @param Logger $logger + * @return mixed Original value if valid, FALSE otherwise + */ + static public function filter_flash_height($height, $max_height, Logger $logger) + { + if ($max_height && $height > $max_height) + { + $logger->err('MAX_FLASH_HEIGHT_EXCEEDED', array('max_height' => $max_height)); + + return false; + } + + return $height; + } + + /** + * Filter a flash object's width + * + * @see bbcode_firstpass::bbcode_flash() + * + * @param string $width + * @param integer $max_width + * @param Logger $logger + * @return mixed Original value if valid, FALSE otherwise + */ + static public function filter_flash_width($width, $max_width, Logger $logger) + { + if ($max_width && $width > $max_width) + { + $logger->err('MAX_FLASH_WIDTH_EXCEEDED', array('max_width' => $max_width)); + + return false; + } + + return $width; + } + + /** + * Filter the value used in a [size] BBCode + * + * @see bbcode_firstpass::bbcode_size() + * + * @param string $size Original size + * @param integer $max_size Maximum allowed size + * @param Logger $logger + * @return mixed Original value if valid, FALSE otherwise + */ + static public function filter_font_size($size, $max_size, Logger $logger) + { + if ($max_size && $size > $max_size) + { + $logger->err('MAX_FONT_SIZE_EXCEEDED', array('max_size' => $max_size)); + + return false; + } + + if ($size < 1) + { + return false; + } + + return $size; + } + + /** + * Filter an image's URL to enforce restrictions on its dimensions + * + * @see bbcode_firstpass::bbcode_img() + * + * @param string $url Original URL + * @param array $url_config Config used by the URL filter + * @param Logger $logger + * @param integer $max_height Maximum height allowed + * @param integer $max_width Maximum width allowed + * @return string|bool Original value if valid, FALSE otherwise + */ + static public function filter_img_url($url, array $url_config, Logger $logger, $max_height, $max_width) + { + // Validate the URL + $url = BuiltInFilters::filterUrl($url, $url_config, $logger); + + if ($url === false) + { + return false; + } + + if ($max_height || $max_width) + { + $stats = @getimagesize($url); + + if ($stats === false) + { + $logger->err('UNABLE_GET_IMAGE_SIZE'); + + return false; + } + + if ($max_height && $max_height < $stats[1]) + { + $logger->err('MAX_IMG_HEIGHT_EXCEEDED', array('max_height' => $max_height)); + + return false; + } + + if ($max_width && $max_width < $stats[0]) + { + $logger->err('MAX_IMG_WIDTH_EXCEEDED', array('max_width' => $max_width)); + + return false; + } + } + + return $url; + } +} diff --git a/phpBB/phpbb/textformatter/s9e/renderer.php b/phpBB/phpbb/textformatter/s9e/renderer.php new file mode 100644 index 0000000000..8999f1d25f --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/renderer.php @@ -0,0 +1,338 @@ +<?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; + +/** +* s9e\TextFormatter\Renderer adapter +*/ +class renderer implements \phpbb\textformatter\renderer_interface +{ + /** + * @var \s9e\TextFormatter\Plugins\Censor\Helper + */ + protected $censor; + + /** + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * @var \s9e\TextFormatter\Renderer + */ + protected $renderer; + + /** + * @var bool Status of the viewcensors option + */ + protected $viewcensors = false; + + /** + * @var bool Status of the viewflash option + */ + protected $viewflash = false; + + /** + * @var bool Status of the viewimg option + */ + protected $viewimg = false; + + /** + * @var bool Status of the viewsmilies option + */ + protected $viewsmilies = false; + + /** + * Constructor + * + * @param \phpbb\cache\driver\driver_interface $cache + * @param string $cache_dir Path to the cache dir + * @param string $key Cache key + * @param factory $factory + * @param \phpbb\event\dispatcher_interface $dispatcher + */ + public function __construct(\phpbb\cache\driver\driver_interface $cache, $cache_dir, $key, factory $factory, \phpbb\event\dispatcher_interface $dispatcher) + { + $renderer_data = $cache->get($key); + if ($renderer_data) + { + $class = $renderer_data['class']; + if (!class_exists($class, false)) + { + // Try to load the renderer class from its cache file + $cache_file = $cache_dir . $class . '.php'; + + if (file_exists($cache_file)) + { + include($cache_file); + } + } + if (class_exists($class, false)) + { + $renderer = new $class; + } + if (isset($renderer_data['censor'])) + { + $censor = $renderer_data['censor']; + } + } + if (!isset($renderer)) + { + $objects = $factory->regenerate(); + $renderer = $objects['renderer']; + } + + if (isset($censor)) + { + $this->censor = $censor; + } + $this->dispatcher = $dispatcher; + $this->renderer = $renderer; + $renderer = $this; + + /** + * Configure the renderer service + * + * @event core.text_formatter_s9e_renderer_setup + * @var \phpbb\textformatter\s9e\renderer renderer This renderer service + * @since 3.2.0-a1 + */ + $vars = array('renderer'); + extract($dispatcher->trigger_event('core.text_formatter_s9e_renderer_setup', compact($vars))); + } + + /** + * Automatically set the smilies path based on config + * + * @param \phpbb\config\config $config + * @param \phpbb\path_helper $path_helper + * @return null + */ + public function configure_smilies_path(\phpbb\config\config $config, \phpbb\path_helper $path_helper) + { + /** + * @see smiley_text() + */ + $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $path_helper->get_web_root_path(); + + $this->set_smilies_path($root_path . $config['smilies_path']); + } + + /** + * Configure this renderer as per the user's settings + * + * Should set the locale as well as the viewcensor/viewflash/viewimg/viewsmilies options. + * + * @param \phpbb\user $user + * @param \phpbb\config\config $config + * @param \phpbb\auth\auth $auth + * @return null + */ + public function configure_user(\phpbb\user $user, \phpbb\config\config $config, \phpbb\auth\auth $auth) + { + $censor = $user->optionget('viewcensors') || !$config['allow_nocensors'] || !$auth->acl_get('u_chgcensors'); + + $this->set_viewcensors($censor); + $this->set_viewflash($user->optionget('viewflash')); + $this->set_viewimg($user->optionget('viewimg')); + $this->set_viewsmilies($user->optionget('viewsmilies')); + + // Set the stylesheet parameters + foreach (array_keys($this->renderer->getParameters()) as $param_name) + { + if (strpos($param_name, 'L_') === 0) + { + // L_FOO is set to $user->lang('FOO') + $this->renderer->setParameter($param_name, $user->lang(substr($param_name, 2))); + } + } + + // Set this user's style id and other parameters + $this->renderer->setParameters(array( + 'S_IS_BOT' => $user->data['is_bot'], + 'S_REGISTERED_USER' => $user->data['is_registered'], + 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS), + 'STYLE_ID' => $user->style['style_id'], + )); + } + + /** + * Return the instance of s9e\TextFormatter\Renderer used by this object + * + * @return \s9e\TextFormatter\Renderer + */ + public function get_renderer() + { + return $this->renderer; + } + + /** + * {@inheritdoc} + */ + public function get_viewcensors() + { + return $this->viewcensors; + } + + /** + * {@inheritdoc} + */ + public function get_viewflash() + { + return $this->viewflash; + } + + /** + * {@inheritdoc} + */ + public function get_viewimg() + { + return $this->viewimg; + } + + /** + * {@inheritdoc} + */ + public function get_viewsmilies() + { + return $this->viewsmilies; + } + + /** + * {@inheritdoc} + */ + public function render($xml) + { + $renderer = $this; + + /** + * Modify a parsed text before it is rendered + * + * @event core.text_formatter_s9e_render_before + * @var \phpbb\textformatter\s9e\renderer renderer This renderer service + * @var string xml The parsed text, in its XML form + * @since 3.2.0-a1 + */ + $vars = array('renderer', 'xml'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_render_before', compact($vars))); + + if (isset($this->censor) && $this->viewcensors) + { + // NOTE: censorHtml() is XML-safe + $xml = $this->censor->censorHtml($xml, true); + } + + $html = $this->renderer->render($xml); + if (stripos($html, '<code') !== false) + { + $html = $this->replace_tabs_in_code($html); + } + + /** + * Modify a rendered text + * + * @event core.text_formatter_s9e_render_after + * @var string html The rendered text's HTML + * @var \phpbb\textformatter\s9e\renderer renderer This renderer service + * @since 3.2.0-a1 + */ + $vars = array('html', 'renderer'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_render_after', compact($vars))); + + return $html; + } + + /** + * Replace tabs in code elements + * + * @see bbcode::bbcode_second_pass_code() + * + * @param string $html Original HTML + * @return string Modified HTML + */ + protected function replace_tabs_in_code($html) + { + return preg_replace_callback( + '((<code[^>]*>)(.*?)(</code>))is', + function ($captures) + { + $code = $captures[2]; + + $code = str_replace("\t", ' ', $code); + $code = str_replace(' ', ' ', $code); + $code = str_replace(' ', ' ', $code); + $code = str_replace("\n ", "\n ", $code); + + // keep space at the beginning + if (!empty($code) && $code[0] == ' ') + { + $code = ' ' . substr($code, 1); + } + + // remove newline at the beginning + if (!empty($code) && $code[0] == "\n") + { + $code = substr($code, 1); + } + + return $captures[1] . $code . $captures[3]; + }, + $html + ); + } + + /** + * {@inheritdoc} + */ + public function set_smilies_path($path) + { + $this->renderer->setParameter('T_SMILIES_PATH', $path); + } + + /** + * {@inheritdoc} + */ + public function set_viewcensors($value) + { + $this->viewcensors = $value; + $this->renderer->setParameter('S_VIEWCENSORS', $value); + } + + /** + * {@inheritdoc} + */ + public function set_viewflash($value) + { + $this->viewflash = $value; + $this->renderer->setParameter('S_VIEWFLASH', $value); + } + + /** + * {@inheritdoc} + */ + public function set_viewimg($value) + { + $this->viewimg = $value; + $this->renderer->setParameter('S_VIEWIMG', $value); + } + + /** + * {@inheritdoc} + */ + public function set_viewsmilies($value) + { + $this->viewsmilies = $value; + $this->renderer->setParameter('S_VIEWSMILIES', $value); + } +} diff --git a/phpBB/phpbb/textformatter/s9e/utils.php b/phpBB/phpbb/textformatter/s9e/utils.php new file mode 100644 index 0000000000..2018bbf519 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/utils.php @@ -0,0 +1,60 @@ +<?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; + +/** +* Text manipulation utilities +*/ +class utils implements \phpbb\textformatter\utils_interface +{ + /** + * Replace BBCodes and other formatting elements with whitespace + * + * NOTE: preserves smilies as text + * + * @param string $xml Parsed text + * @return string Plain text + */ + public function clean_formatting($xml) + { + // Insert a space before <s> and <e> then remove formatting + $xml = preg_replace('#<[es]>#', ' $0', $xml); + + return \s9e\TextFormatter\Utils::removeFormatting($xml); + } + + /** + * Remove given BBCode and its content, at given nesting depth + * + * @param string $xml Parsed text + * @param string $bbcode_name BBCode's name + * @param integer $depth Minimum nesting depth (number of parents of the same name) + * @return string Parsed text + */ + public function remove_bbcode($xml, $bbcode_name, $depth = 0) + { + return \s9e\TextFormatter\Utils::removeTag($xml, strtoupper($bbcode_name), $depth); + } + + /** + * Return a parsed text to its original form + * + * @param string $xml Parsed text + * @return string Original plain text + */ + public function unparse($xml) + { + return \s9e\TextFormatter\Unparser::unparse($xml); + } +} diff --git a/phpBB/phpbb/textformatter/utils_interface.php b/phpBB/phpbb/textformatter/utils_interface.php new file mode 100644 index 0000000000..132dc8ece4 --- /dev/null +++ b/phpBB/phpbb/textformatter/utils_interface.php @@ -0,0 +1,48 @@ +<?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; + +/** +* Used to manipulate a parsed text +*/ +interface utils_interface +{ + /** + * Replace BBCodes and other formatting elements with whitespace + * + * NOTE: preserves smilies as text + * + * @param string $text Parsed text + * @return string Plain text + */ + public function clean_formatting($text); + + /** + * Remove given BBCode and its content, at given nesting depth + * + * @param string $text Parsed text + * @param string $bbcode_name BBCode's name + * @param integer $depth Minimum nesting depth (number of parents of the same name) + * @return string Parsed text + */ + public function remove_bbcode($text, $bbcode_name, $depth = 0); + + /** + * Return a parsed text to its original form + * + * @param string $text Parsed text + * @return string Original plain text + */ + public function unparse($text); +} diff --git a/phpBB/posting.php b/phpBB/posting.php index 2e03710c1b..2d01922c80 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -331,14 +331,17 @@ switch ($mode) { $is_authed = true; } - break; + + // no break; case 'soft_delete': - if ($user->data['is_registered'] && $phpbb_content_visibility->can_soft_delete($forum_id, $post_data['poster_id'], $post_data['post_edit_locked'])) + if (!$is_authed && $user->data['is_registered'] && $phpbb_content_visibility->can_soft_delete($forum_id, $post_data['poster_id'], $post_data['post_edit_locked'])) { + // Fall back to soft_delete if we have no permissions to delete posts but to soft delete them $is_authed = true; + $mode = 'soft_delete'; } - else + else if (!$is_authed) { // Display the same error message for softdelete we use for delete $mode = 'delete'; diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index c4dbb6a035..e0c8e51d25 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -85,7 +85,7 @@ </div> </div> - + <!-- EVENT overall_header_navbar_before --> <!-- INCLUDE navbar_header.html --> </div> diff --git a/phpBB/styles/prosilver/template/posting_editor.html b/phpBB/styles/prosilver/template/posting_editor.html index e68e6a97e5..5804f95579 100644 --- a/phpBB/styles/prosilver/template/posting_editor.html +++ b/phpBB/styles/prosilver/template/posting_editor.html @@ -36,6 +36,7 @@ <!-- INCLUDE posting_buttons.html --> <div id="smiley-box"> + <!-- EVENT posting_editor_smilies_before --> <!-- IF S_SMILIES_ALLOWED and .smiley --> <strong>{L_SMILIES}</strong><br /> <!-- BEGIN smiley --> @@ -45,7 +46,7 @@ <!-- IF S_SHOW_SMILEY_LINK and S_SMILIES_ALLOWED --> <br /><a href="{U_MORE_SMILIES}" onclick="popup(this.href, 750, 350, '_phpbbsmilies'); return false;">{L_MORE_SMILIES}</a> <!-- ENDIF --> - + <!-- EVENT posting_editor_smilies_after --> <!-- IF BBCODE_STATUS --> <div class="bbcode-status"> <!-- IF .smiley --><hr /><!-- ENDIF --> @@ -58,6 +59,7 @@ {SMILIES_STATUS} </div> <!-- ENDIF --> + <!-- EVENT posting_editor_bbcode_status_after --> <!-- IF S_EDIT_DRAFT || S_DISPLAY_REVIEW --> <!-- IF S_DISPLAY_REVIEW --><hr /><!-- ENDIF --> <!-- IF S_EDIT_DRAFT --><strong><a href="{S_UCP_ACTION}">{L_BACK_TO_DRAFTS}</a></strong><!-- ENDIF --> diff --git a/phpBB/styles/prosilver/template/posting_pm_layout.html b/phpBB/styles/prosilver/template/posting_pm_layout.html index 3bdadd06ca..7f4a0ea8d7 100644 --- a/phpBB/styles/prosilver/template/posting_pm_layout.html +++ b/phpBB/styles/prosilver/template/posting_pm_layout.html @@ -19,7 +19,9 @@ <div class="panel" id="pmheader-postingbox"> <div class="inner"> + <!-- EVENT posting_pm_layout_include_pm_header_before --> <!-- INCLUDE posting_pm_header.html --> + <!-- EVENT posting_pm_layout_include_pm_header_after --> </div> </div> diff --git a/phpBB/styles/prosilver/template/posting_poll_body.html b/phpBB/styles/prosilver/template/posting_poll_body.html index a131c10533..c3eea0d21d 100644 --- a/phpBB/styles/prosilver/template/posting_poll_body.html +++ b/phpBB/styles/prosilver/template/posting_poll_body.html @@ -43,8 +43,10 @@ <dd><label for="poll_vote_change"><input type="checkbox" id="poll_vote_change" name="poll_vote_change"{VOTE_CHANGE_CHECKED} /> {L_POLL_VOTE_CHANGE_EXPLAIN}</label></dd> </dl> <!-- ENDIF --> + <!-- ENDIF --> + <!-- EVENT posting_poll_body_options_after --> - <!-- ELSEIF S_POLL_DELETE --> + <!-- IF S_POLL_DELETE --> <dl class="fields1"> <dt><label for="poll_delete">{L_POLL_DELETE}{L_COLON}</label></dt> <dd><label for="poll_delete"><input type="checkbox" name="poll_delete" id="poll_delete"<!-- IF S_POLL_DELETE_CHECKED --> checked="checked"<!-- ENDIF --> /> </label></dd> diff --git a/phpBB/styles/prosilver/theme/bidi.css b/phpBB/styles/prosilver/theme/bidi.css index d9bf9f9fa5..f3468ebcf2 100644 --- a/phpBB/styles/prosilver/theme/bidi.css +++ b/phpBB/styles/prosilver/theme/bidi.css @@ -182,8 +182,7 @@ /* Misc layout styles ---------------------------------------- */ -/* column[1-2] styles are containers for two column layouts - Also see tweaks.css */ +/* column[1-2] styles are containers for two column layouts */ .rtl .column1 { float: right; clear: right; @@ -603,9 +602,6 @@ li.breadcrumbs span:first-child > a { /** * buttons.css */ -/* Rollover buttons - Based on: http://wellstyled.com/css-nopreload-rollovers.html -----------------------------------------*/ .rtl .dropdown-select { padding-left: 24px; padding-right: 8px; @@ -983,10 +979,6 @@ li.breadcrumbs span:first-child > a { padding-left: 0; } -/** -* tweaks.css -*/ - /* Form button styles ---------------------------------------- */ diff --git a/phpBB/styles/prosilver/theme/buttons.css b/phpBB/styles/prosilver/theme/buttons.css index aecac4defd..f9a520369e 100644 --- a/phpBB/styles/prosilver/theme/buttons.css +++ b/phpBB/styles/prosilver/theme/buttons.css @@ -1,9 +1,6 @@ /* Button Styles ---------------------------------------- */ -/* Rollover buttons - Based on: http://wellstyled.com/css-nopreload-rollovers.html -----------------------------------------*/ .button { cursor: pointer; display: inline-block; diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 11e3314f17..c0cc2bb2dd 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -53,9 +53,7 @@ html { } body { - /* Text-Sizing with ems: http://www.clagnut.com/blog/348/ */ font-family: Verdana, Helvetica, Arial, sans-serif; - /*font-size: 62.5%; This sets the default font size to be equivalent to 10px */ font-size: 10px; line-height: normal; margin: 0; @@ -113,7 +111,6 @@ img { } hr { - /* Also see tweaks.css */ border: 0 solid transparent; border-top-width: 1px; height: 1px; @@ -713,8 +710,7 @@ table.info tbody th { /* Misc layout styles ---------------------------------------- */ -/* column[1-2] styles are containers for two column layouts - Also see tweaks.css */ +/* column[1-2] styles are containers for two column layouts */ .column1 { float: left; clear: left; diff --git a/phpBB/styles/prosilver/theme/content.css b/phpBB/styles/prosilver/theme/content.css index e73f8c9d54..380b285b83 100644 --- a/phpBB/styles/prosilver/theme/content.css +++ b/phpBB/styles/prosilver/theme/content.css @@ -259,7 +259,6 @@ dd.option { } .postbody h3 img { - /* Also see tweaks.css */ vertical-align: bottom; } @@ -510,7 +509,6 @@ blockquote .codebox { } .codebox code { - /* Also see tweaks.css */ overflow: auto; display: block; height: auto; @@ -693,7 +691,6 @@ fieldset.polls dd div { /* Poster profile block ----------------------------------------*/ .postprofile { - /* Also see tweaks.css */ margin: 5px 0 10px 0; min-height: 80px; border: 1px solid transparent; diff --git a/tests/captcha/qa_test.php b/tests/captcha/qa_test.php new file mode 100644 index 0000000000..4aa5e714f5 --- /dev/null +++ b/tests/captcha/qa_test.php @@ -0,0 +1,97 @@ +<?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 dirname(__FILE__) . '/../../phpBB/includes/functions.php'; + +class phpbb_captcha_qa_test extends \phpbb_database_test_case +{ + protected $request; + + /** @var \phpbb\captcha\plugins\qa */ + protected $qa; + + public function getDataSet() + { + return $this->createXMLDataSet(dirname(__FILE__) . '/../fixtures/empty.xml'); + } + + public function setUp() + { + global $db, $request, $phpbb_container; + + $db = $this->new_dbal(); + + parent::setUp(); + + $request = new \phpbb_mock_request(); + $phpbb_container = new \phpbb_mock_container_builder(); + $factory = new \phpbb\db\tools\factory(); + $phpbb_container->set('dbal.tools', $factory->get($db)); + $this->qa = new \phpbb\captcha\plugins\qa('phpbb_captcha_questions', 'phpbb_captcha_answers', 'phpbb_qa_confirm'); + } + + public function test_is_installed() + { + $this->assertFalse($this->qa->is_installed()); + + $this->qa->install(); + + $this->assertTrue($this->qa->is_installed()); + } + + public function test_set_get_name() + { + $this->assertNull($this->qa->get_service_name()); + $this->qa->set_name('foobar'); + $this->assertSame('foobar', $this->qa->get_service_name()); + } + + public function data_acp_get_question_input() + { + return array( + array("foobar\ntest\nyes", array( + 'question_text' => '', + 'strict' => false, + 'lang_iso' => '', + 'answers' => array('foobar', 'test', 'yes') + )), + array("foobar\ntest\n \nyes", array( + 'question_text' => '', + 'strict' => false, + 'lang_iso' => '', + 'answers' => array( + 0 => 'foobar', + 1 => 'test', + 3 => 'yes', + ) + )), + array('', array( + 'question_text' => '', + 'strict' => false, + 'lang_iso' => '', + 'answers' => '', + )), + ); + } + + /** + * @dataProvider data_acp_get_question_input + */ + public function test_acp_get_question_input($value, $expected) + { + global $request; + $request->overwrite('answers', $value); + + $this->assertEquals($expected, $this->qa->acp_get_question_input()); + } +} diff --git a/tests/controller/common_helper_route.php b/tests/controller/common_helper_route.php index 91b0cda68d..f1f369cce6 100644 --- a/tests/controller/common_helper_route.php +++ b/tests/controller/common_helper_route.php @@ -47,6 +47,11 @@ abstract class phpbb_controller_common_helper_route extends phpbb_test_case return '/app.php'; } + protected function get_base_uri() + { + return $this->get_uri(); + } + protected function get_script_name() { return 'app.php'; @@ -62,7 +67,7 @@ abstract class phpbb_controller_common_helper_route extends phpbb_test_case $this->request = new phpbb_mock_request(); $this->request->overwrite('SCRIPT_NAME', $this->get_uri(), \phpbb\request\request_interface::SERVER); $this->request->overwrite('SCRIPT_FILENAME', $this->get_script_name(), \phpbb\request\request_interface::SERVER); - $this->request->overwrite('REQUEST_URI', $this->get_uri(), \phpbb\request\request_interface::SERVER); + $this->request->overwrite('REQUEST_URI', $this->get_base_uri(), \phpbb\request\request_interface::SERVER); $this->request->overwrite('SERVER_NAME', 'localhost', \phpbb\request\request_interface::SERVER); $this->request->overwrite('SERVER_PORT', '80', \phpbb\request\request_interface::SERVER); diff --git a/tests/controller/helper_route_slash_test.php b/tests/controller/helper_route_slash_test.php new file mode 100644 index 0000000000..3db5ec19e5 --- /dev/null +++ b/tests/controller/helper_route_slash_test.php @@ -0,0 +1,43 @@ +<?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 dirname(__FILE__) . '/../../phpBB/includes/functions.php'; +require_once dirname(__FILE__) . '/common_helper_route.php'; + +class phpbb_controller_helper_route_slash_test extends phpbb_controller_common_helper_route +{ + protected function get_phpbb_root_path() + { + return './../'; + } + + protected function get_uri() + { + return '/phpBB3/app.php'; + } + + protected function get_base_uri() + { + return '/phpBB3/'; + } + + protected function get_script_name() + { + return 'app.php'; + } + + protected function path_to_app() + { + return 'phpBB3/'; + } +} diff --git a/tests/functional/visibility_softdelete_test.php b/tests/functional/visibility_softdelete_test.php index 794f0cde68..39efc99a35 100644 --- a/tests/functional/visibility_softdelete_test.php +++ b/tests/functional/visibility_softdelete_test.php @@ -42,6 +42,19 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'forum_perm_from' => 2, )); $crawler = self::submit($form); + + // Create second user which does not have m_delete permission + $this->add_lang('acp/permissions'); + + $second_user = $this->create_user('no m_delete moderator'); + $this->add_user_group("GLOBAL_MODERATORS", 'no m_delete moderator', true); + + // Set m_delete to never + $crawler = self::request('GET', "adm/index.php?i=acp_permissions&icat=16&mode=setting_user_global&user_id[0]=$second_user&type=m_&sid={$this->sid}"); + $form = $crawler->selectButton($this->lang('APPLY_PERMISSIONS'))->form(); + $data = array("setting[$second_user][0][m_delete]" => ACL_NEVER); + $form->setValues($data); + $crawler = self::submit($form); } public function test_create_post() @@ -98,6 +111,23 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'forum_topics_softdeleted' => 0, 'forum_last_post_id' => $this->data['posts']['Re: Soft Delete Topic #1-#2'], ), 'after replying'); + + // Test creating another reply + $post3 = $this->create_post($this->data['forums']['Soft Delete #1'], $post['topic_id'], 'Re: Soft Delete Topic #1-#3', 'This is another test post posted by the testing framework.'); + $crawler = self::request('GET', "viewtopic.php?t={$post3['topic_id']}&sid={$this->sid}"); + + $this->assertContains('Re: Soft Delete Topic #1-#3', $crawler->filter('html')->text()); + $this->data['posts']['Re: Soft Delete Topic #1-#3'] = (int) $post3['post_id']; + + $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( + 'forum_posts_approved' => 3, + 'forum_posts_unapproved' => 0, + 'forum_posts_softdeleted' => 0, + 'forum_topics_approved' => 1, + 'forum_topics_unapproved' => 0, + 'forum_topics_softdeleted' => 0, + 'forum_last_post_id' => $this->data['posts']['Re: Soft Delete Topic #1-#3'], + ), 'after replying a second time'); } public function test_softdelete_post() @@ -114,21 +144,22 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'posts' => array( 'Soft Delete Topic #1', 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3', ), )); $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( - 'forum_posts_approved' => 2, + 'forum_posts_approved' => 3, 'forum_posts_unapproved' => 0, 'forum_posts_softdeleted' => 0, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, - 'forum_last_post_id' => $this->data['posts']['Re: Soft Delete Topic #1-#2'], + 'forum_last_post_id' => $this->data['posts']['Re: Soft Delete Topic #1-#3'], ), 'before softdelete'); $this->add_lang('posting'); - $crawler = self::request('GET', "posting.php?mode=delete&f={$this->data['forums']['Soft Delete #1']}&p={$this->data['posts']['Re: Soft Delete Topic #1-#2']}&sid={$this->sid}"); + $crawler = self::request('GET', "posting.php?mode=delete&f={$this->data['forums']['Soft Delete #1']}&p={$this->data['posts']['Re: Soft Delete Topic #1-#3']}&sid={$this->sid}"); $this->assertContainsLang('DELETE_PERMANENTLY', $crawler->text()); $form = $crawler->selectButton('Yes')->form(); @@ -136,19 +167,69 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assertContainsLang('POST_DELETED', $crawler->text()); $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( - 'forum_posts_approved' => 1, + 'forum_posts_approved' => 2, 'forum_posts_unapproved' => 0, 'forum_posts_softdeleted' => 1, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, - 'forum_last_post_id' => $this->data['posts']['Soft Delete Topic #1'], + 'forum_last_post_id' => $this->data['posts']['Re: Soft Delete Topic #1-#2'], ), 'after softdelete'); $crawler = self::request('GET', "viewtopic.php?t={$this->data['topics']['Soft Delete Topic #1']}&sid={$this->sid}"); $this->assertContains($this->lang('POST_DISPLAY', '', ''), $crawler->text()); } + public function test_softdelete_post_no_m_delete() + { + $this->login('no m_delete moderator'); + $this->load_ids(array( + 'forums' => array( + 'Soft Delete #1', + 'Soft Delete #2', + ), + 'topics' => array( + 'Soft Delete Topic #1', + ), + 'posts' => array( + 'Soft Delete Topic #1', + 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3', + ), + )); + + $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( + 'forum_posts_approved' => 2, + 'forum_posts_unapproved' => 0, + 'forum_posts_softdeleted' => 1, + 'forum_topics_approved' => 1, + 'forum_topics_unapproved' => 0, + 'forum_topics_softdeleted' => 0, + 'forum_last_post_id' => $this->data['posts']['Re: Soft Delete Topic #1-#2'], + ), 'before softdelete without m_delete'); + + $this->add_lang('posting'); + $crawler = self::request('GET', "posting.php?mode=delete&f={$this->data['forums']['Soft Delete #1']}&p={$this->data['posts']['Re: Soft Delete Topic #1-#2']}&sid={$this->sid}"); + $this->assertNotContainsLang('DELETE_PERMANENTLY', $crawler->text()); + + $form = $crawler->selectButton('Yes')->form(); + $crawler = self::submit($form); + $this->assertContainsLang('POST_DELETED', $crawler->text()); + + $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( + 'forum_posts_approved' => 1, + 'forum_posts_unapproved' => 0, + 'forum_posts_softdeleted' => 2, + 'forum_topics_approved' => 1, + 'forum_topics_unapproved' => 0, + 'forum_topics_softdeleted' => 0, + 'forum_last_post_id' => $this->data['posts']['Soft Delete Topic #1'], + ), 'after softdelete without m_delete'); + + $crawler = self::request('GET', "viewtopic.php?t={$this->data['topics']['Soft Delete Topic #1']}&sid={$this->sid}"); + $this->assertContains($this->lang('POST_DISPLAY', '', ''), $crawler->text()); + } + public function test_move_softdeleted_post() { $this->login(); @@ -163,13 +244,14 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'posts' => array( 'Soft Delete Topic #1', 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3', ), )); $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, @@ -212,7 +294,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #2'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, @@ -234,6 +316,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'posts' => array( 'Soft Delete Topic #1', 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3' ), )); @@ -250,7 +333,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #2'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, @@ -283,7 +366,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #2'], array( 'forum_posts_approved' => 0, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 2, + 'forum_posts_softdeleted' => 3, 'forum_topics_approved' => 0, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 1, @@ -305,6 +388,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'posts' => array( 'Soft Delete Topic #1', 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3' ), )); @@ -321,7 +405,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #2'], array( 'forum_posts_approved' => 0, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 2, + 'forum_posts_softdeleted' => 3, 'forum_topics_approved' => 0, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 1, @@ -344,7 +428,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 0, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 2, + 'forum_posts_softdeleted' => 3, 'forum_topics_approved' => 0, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 1, @@ -376,13 +460,14 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'posts' => array( 'Soft Delete Topic #1', 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3' ), )); $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 0, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 2, + 'forum_posts_softdeleted' => 3, 'forum_topics_approved' => 0, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 1, @@ -417,7 +502,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, @@ -449,13 +534,14 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'posts' => array( 'Soft Delete Topic #1', 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3' ), )); $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, @@ -495,7 +581,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 0, + 'forum_posts_softdeleted' => 1, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, @@ -528,6 +614,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'posts' => array( 'Soft Delete Topic #1', 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3' ), )); @@ -539,7 +626,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 1, @@ -562,13 +649,14 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'posts' => array( 'Soft Delete Topic #1', 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3' ), )); $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 1, @@ -603,7 +691,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, @@ -625,13 +713,14 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ 'posts' => array( 'Soft Delete Topic #1', 'Re: Soft Delete Topic #1-#2', + 'Re: Soft Delete Topic #1-#3' ), )); $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, @@ -660,7 +749,7 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #1'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, @@ -670,11 +759,11 @@ class phpbb_functional_visibility_softdelete_test extends phpbb_functional_test_ $this->assert_forum_details($this->data['forums']['Soft Delete #2'], array( 'forum_posts_approved' => 1, 'forum_posts_unapproved' => 0, - 'forum_posts_softdeleted' => 1, + 'forum_posts_softdeleted' => 2, 'forum_topics_approved' => 1, 'forum_topics_unapproved' => 0, 'forum_topics_softdeleted' => 0, - 'forum_last_post_id' => $this->data['posts']['Soft Delete Topic #1'] + 2, + 'forum_last_post_id' => $this->data['posts']['Soft Delete Topic #1'] + 3, ), 'after forking #2'); } diff --git a/tests/functions_acp/validate_config_vars_test.php b/tests/functions_acp/validate_config_vars_test.php index 4bf6ba3984..32738e4351 100644 --- a/tests/functions_acp/validate_config_vars_test.php +++ b/tests/functions_acp/validate_config_vars_test.php @@ -200,6 +200,15 @@ class phpbb_functions_acp_validate_config_vars_test extends phpbb_test_case $config_ary, $error ); + + if ($expected === true) + { + $this->assertEmpty($error); + } + else + { + $this->assertEquals(array($expected), $error); + } } public function data_validate_path_windows() diff --git a/tests/notification/group_request_test.php b/tests/notification/group_request_test.php index 0d532882c6..6a56a38c45 100644 --- a/tests/notification/group_request_test.php +++ b/tests/notification/group_request_test.php @@ -51,6 +51,7 @@ class phpbb_notification_group_request_test extends phpbb_tests_notification_bas )); $phpbb_dispatcher = new phpbb_mock_event_dispatcher; $phpbb_log = new \phpbb\log\null(); + $this->get_test_case_helpers()->set_s9e_services(); // Now on to the actual test diff --git a/tests/plupload/plupload_test.php b/tests/plupload/plupload_test.php new file mode 100644 index 0000000000..2f47bf2b39 --- /dev/null +++ b/tests/plupload/plupload_test.php @@ -0,0 +1,53 @@ +<?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. + * + */ + +class phpbb_plupload_test extends phpbb_test_case +{ + public function generate_resize_string_data() + { + return array( + array( + 0, + 0, + '', + ), + array( + 130, + 150, + 'resize: {width: 130, height: 150, quality: 100},' + ), + ); + } + + /** + * @dataProvider generate_resize_string_data + */ + public function test_generate_resize_string($config_width, $config_height, $expected) + { + $config = new \phpbb\config\config(array( + 'img_max_width' => $config_width, + 'img_max_height' => $config_height, + 'upload_path' => 'files', + )); + $plupload = new \phpbb\plupload\plupload( + '', + $config, + new phpbb_mock_request, + new \phpbb\user('\phpbb\datetime'), + new \phpbb\php\ini, + new \phpbb\mimetype\guesser(array(new \phpbb\mimetype\extension_guesser)) + ); + + $this->assertEquals($expected, $plupload->generate_resize_string()); + } +} diff --git a/tests/test_framework/phpbb_test_case_helpers.php b/tests/test_framework/phpbb_test_case_helpers.php index dee70ad016..09fec38013 100644 --- a/tests/test_framework/phpbb_test_case_helpers.php +++ b/tests/test_framework/phpbb_test_case_helpers.php @@ -11,6 +11,8 @@ * */ +use Symfony\Component\DependencyInjection\ContainerInterface; + class phpbb_test_case_helpers { protected $expectedTriggerError = false; @@ -298,4 +300,228 @@ class phpbb_test_case_helpers } } } + + /** + * Set working instances of the text_formatter.* services + * + * If no container is passed, the global $phpbb_container will be used and/or + * created if applicable + * + * @param ContainerInterface $container Service container + * @param string $fixture Path to the XML fixture + * @param string $styles_path Path to the styles dir + * @return ContainerInterface + */ + public function set_s9e_services(ContainerInterface $container = null, $fixture = null, $styles_path = null) + { + static $first_run; + global $phpbb_container, $phpbb_dispatcher, $phpbb_root_path, $phpEx; + + $cache_dir = __DIR__ . '/../tmp/'; + + // Remove old cache files on first run + if (!isset($first_run)) + { + $first_run = 1; + + array_map('unlink', array_merge( + glob($cache_dir . 'data_s9e_*'), + glob($cache_dir . 's9e_*') + )); + } + + if (!isset($container)) + { + if (!isset($phpbb_container)) + { + $phpbb_container = new phpbb_mock_container_builder; + } + + $container = $phpbb_container; + } + + if (!isset($fixture)) + { + $fixture = __DIR__ . '/../text_formatter/s9e/fixtures/default_formatting.xml'; + } + + if (!isset($styles_path)) + { + $styles_path = $phpbb_root_path . 'styles/'; + } + + $dataset = new DOMDocument; + $dataset->load($fixture); + + $tables = array( + 'phpbb_bbcodes' => array(), + 'phpbb_smilies' => array(), + 'phpbb_styles' => array(), + 'phpbb_words' => array() + ); + foreach ($dataset->getElementsByTagName('table') as $table) + { + $name = $table->getAttribute('name'); + $columns = array(); + + foreach ($table->getElementsByTagName('column') as $column) + { + $columns[] = $column->textContent; + } + + foreach ($table->getElementsByTagName('row') as $row) + { + $values = array(); + + foreach ($row->getElementsByTagName('value') as $value) + { + $values[] = $value->textContent; + } + + $tables[$name][] = array_combine($columns, $values); + } + } + + // Set up a default style if there's none set + if (empty($tables['phpbb_styles'])) + { + $tables['phpbb_styles'][] = array( + 'style_id' => 1, + 'style_path' => 'prosilver', + 'bbcode_bitfield' => 'kNg=' + ); + } + + // Mock the DAL, make it return data from the fixture + $mb = $this->test_case->getMockBuilder('phpbb\\textformatter\\data_access'); + $mb->setMethods(array('get_bbcodes', 'get_censored_words', 'get_smilies', 'get_styles')); + $mb->setConstructorArgs(array( + $this->test_case->getMock('phpbb\\db\\driver\\driver'), + 'phpbb_bbcodes', + 'phpbb_smilies', + 'phpbb_styles', + 'phpbb_words', + $styles_path + )); + + $dal = $mb->getMock(); + $container->set('text_formatter.data_access', $dal); + + $dal->expects($this->test_case->any()) + ->method('get_bbcodes') + ->will($this->test_case->returnValue($tables['phpbb_bbcodes'])); + $dal->expects($this->test_case->any()) + ->method('get_smilies') + ->will($this->test_case->returnValue($tables['phpbb_smilies'])); + $dal->expects($this->test_case->any()) + ->method('get_styles') + ->will($this->test_case->returnValue($tables['phpbb_styles'])); + $dal->expects($this->test_case->any()) + ->method('get_censored_words') + ->will($this->test_case->returnValue($tables['phpbb_words'])); + + // Cache the parser and renderer with a key based on this method's arguments + $cache = new \phpbb\cache\driver\file($cache_dir); + $prefix = '_s9e_' . md5(serialize(func_get_args())); + $cache_key_parser = $prefix . '_parser'; + $cache_key_renderer = $prefix . '_renderer'; + $container->set('cache.driver', $cache); + $container->setParameter('cache.dir', $cache_dir); + + // Create a path_helper + if (!$container->has('path_helper')) + { + $container->set( + 'path_helper', + new \phpbb\path_helper( + new \phpbb\symfony_request( + new phpbb_mock_request() + ), + new \phpbb\filesystem(), + $this->test_case->getMock('\phpbb\request\request'), + $phpbb_root_path, + $phpEx + ) + ); + } + + // Create an event dispatcher + if ($container->has('dispatcher')) + { + $dispatcher = $container->get('dispatcher'); + } + else if (isset($phpbb_dispatcher)) + { + $dispatcher = $phpbb_dispatcher; + } + else + { + $dispatcher = new phpbb_mock_event_dispatcher; + } + + // Create and register the text_formatter.s9e.factory service + $factory = new \phpbb\textformatter\s9e\factory($dal, $cache, $dispatcher, $cache_dir, $cache_key_parser, $cache_key_renderer); + $container->set('text_formatter.s9e.factory', $factory); + + // Create a user if none was provided, and add the common lang strings + if ($container->has('user')) + { + $user = $container->get('user'); + } + else + { + $user = new \phpbb\user('\phpbb\datetime'); + $user->optionset('viewcensors', true); + $user->optionset('viewflash', true); + $user->optionset('viewimg', true); + $user->optionset('viewsmilies', true); + $container->set('user', $user); + } + $user->add_lang('common'); + + if (!isset($user->style)) + { + $user->style = array('style_id' => 1); + } + + // Create and register the text_formatter.s9e.parser service and its alias + $parser = new \phpbb\textformatter\s9e\parser( + $cache, + $cache_key_parser, + $user, + $factory, + $dispatcher + ); + + $container->set('text_formatter.parser', $parser); + $container->set('text_formatter.s9e.parser', $parser); + + // Create and register the text_formatter.s9e.renderer service and its alias + $renderer = new \phpbb\textformatter\s9e\renderer( + $cache, + $cache_dir, + $cache_key_renderer, + $factory, + $dispatcher + ); + + $config = ($container->has('config')) + ? $container->get('config') + : new \phpbb\config\config(array('smilies_path' => 'images/smilies', 'allow_nocensors' => false)); + $auth = ($container->has('auth')) ? $container->get('auth') : new \phpbb\auth\auth; + + // Calls configured in services.yml + $renderer->configure_smilies_path($config, $container->get('path_helper')); + $renderer->configure_user($user, $config, $auth); + + $container->set('text_formatter.renderer', $renderer); + $container->set('text_formatter.s9e.renderer', $renderer); + + // Create and register the text_formatter.s9e.utils service and its alias + $utils = new \phpbb\textformatter\s9e\utils; + $container->set('text_formatter.utils', $utils); + $container->set('text_formatter.s9e.utils', $utils); + + return $container; + } } 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..79232562cf --- /dev/null +++ b/tests/text_formatter/s9e/default_formatting_test.php @@ -0,0 +1,222 @@ +<?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 +{ + /** + * @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><code>unparsed code</code></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><code>unparsed code [b]bold [i]bold + italic[/i][/b]</code></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><code>unparsed code [quote="username"]quoted[/quote]</code></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><code> line1<br>' . "\n" . ' line2</code></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>' + ), + ); + } +} 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&%3E.png" alt=""\'<&>" title=""\'<&>">', + $renderer->render('<r><E>"\'<&></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>&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><span style="color:red">{TEXT}</span></value> + <value>!\[custom\](.*?)\[/custom\]!ies</value> + <value>'[custom:$uid]'.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', '&#39;', '&#40;', '&#41;'), trim('${1}')).'[/custom:$uid]'</value> + <value>!\[custom:$uid\](.*?)\[/custom:$uid\]!s</value> + <value><span style="color:red">${1}</span></value> + </row> + <row> + <value>14</value> + <value>unsafe</value> + <value></value> + <value>1</value> + <value>[unsafe]{TEXT}[/unsafe]</value> + <value><script>{TEXT}</script></value> + <value>!\[unsafe\](.*?)\[/unsafe\]!ies</value> + <value>'[unsafe:$uid]'.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', '&#39;', '&#40;', '&#41;'), trim('${1}')).'[/unsafe:$uid]'</value> + <value>!\[unsafe:$uid\](.*?)\[/unsafe:$uid\]!s</value> + <value><script>${1}</script></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>&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", '"', ''', '(', ')'), 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><*+,;=:@|]+|%[\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><((?:[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>"'<&></value> + <value>"'<&></value> + <value>"'<&>.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><\[/xss\]!ies]]></value> + <value><![CDATA['[xss='.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', ''', '(', ')'), trim('${1}')).':$uid]'.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', ''', '(', ')'), trim('${2}')).'[/xss:$uid]']]></value> + <value><\[/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..71966f9d36 --- /dev/null +++ b/tests/text_formatter/s9e/parser_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_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', + $this->getMockBuilder('phpbb\\user')->disableOriginalConstructor()->getMock(), + $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', + $this->getMockBuilder('phpbb\\user')->disableOriginalConstructor()->getMock(), + $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', + $this->getMockBuilder('phpbb\\user')->disableOriginalConstructor()->getMock(), + $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', + $this->getMockBuilder('phpbb\\user')->disableOriginalConstructor()->getMock(), + $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('user'), + $container->get('text_formatter.s9e.factory'), + $dispatcher + ); + } + + public function setup_event_callback($vars) + { + return isset($vars['parser']) + && $vars['parser'] instanceof \phpbb\textformatter\s9e\parser + && isset($vars['user']) + && $vars['user'] instanceof \phpbb\user; + } + + /** + * @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('user'), + $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..91458541d3 --- /dev/null +++ b/tests/text_formatter/s9e/renderer_test.php @@ -0,0 +1,451 @@ +<?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) + { + $user = new \phpbb\user('\\phpbb\\datetime'); + $user->optionset('viewcensors', false); + + $phpbb_container->set('user', $user); + } + ), + array( + '<t>apple</t>', + 'banana', + function ($phpbb_container) + { + $user = new \phpbb\user('\\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) + { + $user = new \phpbb\user('\\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) + { + $user = new \phpbb\user('\\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) + { + $user = new \phpbb\user('\\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) + { + $user = new \phpbb\user('\\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>' + ); + + foreach ($tests as $style_id => $expected) + { + $user = new \phpbb\user('\\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; + + // Style 3 inherits from 2 which inherits from 1. Only style 1 has a bbcode.html + $user = new \phpbb\user('\\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; + + // 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) + { + $user = new \phpbb\user('\\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..69f8682cac --- /dev/null +++ b/tests/text_formatter/s9e/utils_test.php @@ -0,0 +1,121 @@ +<?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_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]', + ), + ); + } +} diff --git a/tests/text_processing/decode_message_test.php b/tests/text_processing/decode_message_test.php new file mode 100644 index 0000000000..c9c1da52d5 --- /dev/null +++ b/tests/text_processing/decode_message_test.php @@ -0,0 +1,91 @@ +<?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 dirname(__FILE__) . '/../../phpBB/includes/functions.php'; +require_once dirname(__FILE__) . '/../../phpBB/includes/functions_content.php'; + +class phpbb_text_processing_decode_message_test extends phpbb_test_case +{ + /** + * @dataProvider get_legacy_tests + */ + public function test_legacy($original, $expected, $bbcode_uid = '') + { + $actual = $original; + decode_message($actual, $bbcode_uid); + + $this->assertSame($expected, $actual); + } + + public function get_legacy_tests() + { + return array( + array( + "&<>"'", + "&<>"'" + ), + array( + '<!-- s:) --><img src="{SMILIES_PATH}/icon_e_smile.gif" alt=":)" title="Smile" /><!-- s:) -->', + ':)' + ), + /** + * Fails as per PHPBB3-8420 + * @link http://tracker.phpbb.com/browse/PHPBB3-8420 + * + array( + '[url=http://example.com:2cpxwbdy]<!-- s:arrow: --><img src="{SMILIES_PATH}/icon_arrow.gif" alt=":arrow:" title="Arrow" /><!-- s:arrow: --> here[/url:2cpxwbdy]', + '[url=http://example.com] :arrow: here[/url]', + '2cpxwbdy' + ), + */ + ); + } + + /** + * @dataProvider get_text_formatter_tests + */ + public function test_text_formatter($original, $expected) + { + $this->get_test_case_helpers()->set_s9e_services(); + + $actual = $original; + decode_message($actual); + + $this->assertSame($expected, $actual); + } + + public function get_text_formatter_tests() + { + return array( + array( + "<t>&<>\"'", + "&<>"'" + ), + array( + '<r><E>:)</E></r>', + ':)' + ), + array( + "<t>a<br/>\nb</t>", + "a\nb" + ), + /** + * @link http://tracker.phpbb.com/browse/PHPBB3-8420 + */ + array( + '<r><URL url="http://example.com"><s>[url=http://example.com]</s> <E>:arrow:</E> here<e>[/url]</e></URL></r>', + '[url=http://example.com] :arrow: here[/url]' + ), + ); + } +} diff --git a/tests/text_processing/fixtures/empty.xml b/tests/text_processing/fixtures/empty.xml new file mode 100644 index 0000000000..d8206ad124 --- /dev/null +++ b/tests/text_processing/fixtures/empty.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<dataset> +</dataset> diff --git a/tests/text_processing/fixtures/smilies.xml b/tests/text_processing/fixtures/smilies.xml new file mode 100644 index 0000000000..25b2e60836 --- /dev/null +++ b/tests/text_processing/fixtures/smilies.xml @@ -0,0 +1,443 @@ +<?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> + <row> + <value>43</value> + <value>8)</value> + <value>8)</value> + <value>custom.gif</value> + <value>17</value> + <value>18</value> + <value>42</value> + <value>1</value> + </row> + </table> +</dataset> diff --git a/tests/text_processing/generate_text_for_display_test.php b/tests/text_processing/generate_text_for_display_test.php index 057416da33..fe83938c0b 100644 --- a/tests/text_processing/generate_text_for_display_test.php +++ b/tests/text_processing/generate_text_for_display_test.php @@ -11,10 +11,8 @@ * */ -require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php'; -require_once dirname(__FILE__) . '/../../phpBB/includes/functions_content.php'; -require_once dirname(__FILE__) . '/../mock/user.php'; -require_once dirname(__FILE__) . '/../mock/cache.php'; +require_once __DIR__ . '/../../phpBB/includes/functions.php'; +require_once __DIR__ . '/../../phpBB/includes/functions_content.php'; class phpbb_text_processing_generate_text_for_display_test extends phpbb_test_case { @@ -24,21 +22,175 @@ class phpbb_text_processing_generate_text_for_display_test extends phpbb_test_ca parent::setUp(); + $phpbb_dispatcher = new phpbb_mock_event_dispatcher; + $config = new \phpbb\config\config(array()); + set_config(null, null, null, $config); + } + + /** + * @dataProvider get_legacy_tests + */ + public function test_legacy($original, $expected, $uid = '', $bitfield = '', $flags = 0, $censor_text = true) + { + global $cache, $user; + $cache = new phpbb_mock_cache; + $user = new \phpbb\user('\\phpbb\\datetime'); + $user->optionset('viewcensors', true); + $user->optionset('viewflash', true); + $user->optionset('viewimg', true); + $user->optionset('viewsmilies', true); + + $actual = generate_text_for_display($original, $uid, $bitfield, $flags, $censor_text); + + $this->assertSame($expected, $actual); + } + + public function get_legacy_tests() + { + return array( + array( + '', + '' + ), + array( + '0', + '0' + ), + ); + } + + public function test_censor_is_restored() + { + global $phpbb_container; + + $phpbb_container = new phpbb_mock_container_builder; - $user = new phpbb_mock_user; + $user = new \phpbb\user('\\phpbb\\datetime'); $user->optionset('viewcensors', false); - $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $config = new \phpbb\config\config(array('allow_nocensors' => true)); + + $auth = $this->getMock('phpbb\\auth\\auth'); + $auth->expects($this->any()) + ->method('acl_get') + ->with('u_chgcensors') + ->will($this->returnValue(true)); + + $phpbb_container->set('user', $user); + $phpbb_container->set('config', $config); + $phpbb_container->set('auth', $auth); + + $this->get_test_case_helpers()->set_s9e_services($phpbb_container); + $renderer = $phpbb_container->get('text_formatter.renderer'); + + $original = '<r><CENSOR with="banana">apple</CENSOR></r>'; + + $renderer->set_viewcensors(false); + $this->assertSame('apple', $renderer->render($original)); + $renderer->set_viewcensors(true); + $this->assertSame('banana', $renderer->render($original)); + $this->assertSame('apple', generate_text_for_display($original, '', '', 0, false)); + $this->assertSame('banana', $renderer->render($original), 'The original setting was not restored'); + + $renderer->set_viewcensors(false); + $this->assertSame('apple', $renderer->render($original)); + $this->assertSame('banana', generate_text_for_display($original, '', '', 0, truee)); + $this->assertSame('apple', $renderer->render($original), 'The original setting was not restored'); } - public function test_empty_string() + /** + * @dataProvider get_text_formatter_tests + */ + public function test_text_formatter($original, $expected, $censor_text = true, $setup = null) { - $this->assertSame('', generate_text_for_display('', '', '', 0)); + global $phpbb_container; + + $phpbb_container = new phpbb_mock_container_builder; + + if (isset($setup)) + { + $setup($phpbb_container, $this); + } + + $this->get_test_case_helpers()->set_s9e_services($phpbb_container); + + $this->assertSame($expected, generate_text_for_display($original, '', '', 0, $censor_text)); } - public function test_zero_string() + public function get_text_formatter_tests() { - $this->assertSame('0', generate_text_for_display('0', '', '', 0)); + return array( + array( + '<t>Plain text</t>', + 'Plain text' + ), + array( + '<r>Hello <URL url="http://example.org"><s>[url=http://example.org]</s>world<e>[/url]</e></URL></r>', + 'Hello <a href="http://example.org" class="postlink">world</a>' + ), + array( + '<t>&<>"\'</t>', + '&<>"\'' + ), + array( + '<r><CENSOR with="banana">apple</CENSOR></r>', + 'banana', + true + ), + array( + '<r><CENSOR with="banana">apple</CENSOR></r>', + 'apple', + false + ), + 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', + true, + function ($phpbb_container) + { + $user = new \phpbb\user('\\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>', + true, + function ($phpbb_container) + { + $user = new \phpbb\user('\\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>', + ':)', + true, + function ($phpbb_container) + { + $user = new \phpbb\user('\\phpbb\\datetime'); + $user->optionset('smilies', false); + + $phpbb_container->set('user', $user); + } + ), + ); } } diff --git a/tests/text_processing/generate_text_for_edit_test.php b/tests/text_processing/generate_text_for_edit_test.php new file mode 100644 index 0000000000..105e8da86b --- /dev/null +++ b/tests/text_processing/generate_text_for_edit_test.php @@ -0,0 +1,92 @@ +<?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_text_processing_generate_text_for_edit_test extends phpbb_test_case +{ + /** + * @dataProvider get_legacy_tests + */ + public function test_legacy($original, $expected, $uid = '', $flags = 0) + { + global $cache, $user, $phpbb_dispatcher; + + $cache = new phpbb_mock_cache; + $phpbb_dispatcher = new phpbb_mock_event_dispatcher; + + $user = new phpbb_mock_user; + $user->optionset('viewcensors', false); + + $return = generate_text_for_edit($original, $uid, $flags); + + $this->assertSame($expected, $return['text']); + } + + public function get_legacy_tests() + { + return array( + array( + '', + '' + ), + array( + '0', + '0' + ), + array( + 'Hello [url=http://example.org:1f4coh9x]world[/url:1f4coh9x] <!-- s:) --><img src="{SMILIES_PATH}/icon_e_smile.gif" alt=":)" title="Smile" /><!-- s:) -->', + 'Hello [url=http://example.org]world[/url] :)', + '1f4coh9x', + 0 + ), + array( + "&<>"'", + "&<>"'" + ) + ); + } + + /** + * @dataProvider get_text_formatter_tests + */ + public function test_text_formatter($original, $expected) + { + global $phpbb_dispatcher; + $phpbb_dispatcher = new phpbb_mock_event_dispatcher; + $this->get_test_case_helpers()->set_s9e_services(); + + $return = generate_text_for_edit($original, '', 0); + + $this->assertSame($expected, $return['text']); + } + + public function get_text_formatter_tests() + { + return array( + array( + '<t>Plain text</t>', + 'Plain text' + ), + array( + '<r>Hello <URL url="http://example.org"><s>[url=http://example.org]</s>world<e>[/url]</e></URL> <E>:)</E></r>', + 'Hello [url=http://example.org]world[/url] :)' + ), + array( + '<t>&<>"\'</t>', + "&<>"'" + ) + ); + } +} diff --git a/tests/text_processing/generate_text_for_storage_test.php b/tests/text_processing/generate_text_for_storage_test.php new file mode 100644 index 0000000000..ffa06e4e02 --- /dev/null +++ b/tests/text_processing/generate_text_for_storage_test.php @@ -0,0 +1,73 @@ +<?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__ . '/../../phpBB/includes/utf/utf_tools.php'; + +class phpbb_text_processing_generate_text_for_storage_test extends phpbb_test_case +{ + public function setUp() + { + global $config, $phpbb_container, $phpbb_dispatcher; + + parent::setUp(); + + $config = new \phpbb\config\config(array()); + set_config(null, null, null, $config); + + $phpbb_container = new phpbb_mock_container_builder; + $phpbb_container->set('config', $config); + $this->get_test_case_helpers()->set_s9e_services($phpbb_container); + + $phpbb_dispatcher = new phpbb_mock_event_dispatcher; + } + + /** + * @dataProvider get_text_formatter_tests + */ + public function test_text_formatter($original, $expected, $allow_bbcode = true, $allow_urls = true, $allow_smilies = true, $setup = null) + { + $actual = $original; + $uid = ''; + $bitfield = ''; + $flags = 0; + + if (isset($setup)) + { + $setup(); + } + + generate_text_for_storage($actual, $uid, $bitfield, $flags, $allow_bbcode, $allow_urls, $allow_smilies); + + $this->assertSame($expected, $actual); + } + + public function get_text_formatter_tests() + { + return array( + array( + 'Hello world', + '<t>Hello world</t>' + ), + array( + 'Hello [url=http://example.org]world[/url] :)', + '<r>Hello <URL url="http://example.org"><s>[url=http://example.org]</s>world<e>[/url]</e></URL> <E>:)</E></r>' + ), + array( + '&<>"\'', + '<t>&<>"\'</t>' + ), + ); + } +} diff --git a/tests/text_processing/message_parser_test.php b/tests/text_processing/message_parser_test.php new file mode 100644 index 0000000000..691c0d5b8a --- /dev/null +++ b/tests/text_processing/message_parser_test.php @@ -0,0 +1,536 @@ +<?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/bbcode.php'; +require_once __DIR__ . '/../../phpBB/includes/functions.php'; +require_once __DIR__ . '/../../phpBB/includes/functions_content.php'; +require_once __DIR__ . '/../../phpBB/includes/message_parser.php'; +require_once __DIR__ . '/../../phpBB/includes/utf/utf_tools.php'; + +class phpbb_text_processing_message_parser_test extends phpbb_test_case +{ + public static function setUpBeforeClass() + { + parent::setUpBeforeClass(); + + // Set up an intercepting proxy for getimagesize() calls + stream_wrapper_unregister('http'); + stream_wrapper_register('http', __CLASS__ . '_proxy'); + } + + public static function tearDownAfterClass() + { + parent::tearDownAfterClass(); + stream_wrapper_restore('http'); + } + + protected function prepare_s9e_services($setup = null) + { + global $config, $phpbb_container, $user; + + $config = new \phpbb\config\config(array('max_poll_options' => 999)); + + $map = array( + array('MAX_FLASH_HEIGHT_EXCEEDED', 123, 'Your flash files may only be up to 123 pixels high.'), + array('MAX_FLASH_WIDTH_EXCEEDED', 456, 'Your flash files may only be up to 456 pixels wide.'), + array('MAX_FONT_SIZE_EXCEEDED', 120, 'You may only use fonts up to size 120.'), + array('MAX_FONT_SIZE_EXCEEDED', 200, 'You may only use fonts up to size 200.'), + array('MAX_IMG_HEIGHT_EXCEEDED', 12, 'Your images may only be up to 12 pixels high.'), + array('MAX_IMG_WIDTH_EXCEEDED', 34, 'Your images may only be up to 34 pixels wide.'), + array('TOO_MANY_SMILIES', 3, 'Your message contains too many smilies. The maximum number of smilies allowed is 3.'), + array('TOO_MANY_URLS', 2, 'Your message contains too many URLs. The maximum number of URLs allowed is 2.'), + array('UNAUTHORISED_BBCODE', '[flash]', 'You cannot use certain BBCodes: [flash].'), + array('UNAUTHORISED_BBCODE', '[img]', 'You cannot use certain BBCodes: [img].'), + array('UNAUTHORISED_BBCODE', '[quote]', 'You cannot use certain BBCodes: [quote].'), + array('UNAUTHORISED_BBCODE', '[url]', 'You cannot use certain BBCodes: [url].'), + array('UNABLE_GET_IMAGE_SIZE', 'It was not possible to determine the dimensions of the image.'), + ); + + $user = $this->getMockBuilder('phpbb\\user')->disableOriginalConstructor()->getMock(); + $user->expects($this->any()) + ->method('lang') + ->will($this->returnValueMap($map)); + + $user->lang = array( + 'NO_POLL_TITLE' => 'You have to enter a poll title.', + 'POLL_TITLE_TOO_LONG' => 'The poll title must contain fewer than 100 characters.', + 'POLL_TITLE_COMP_TOO_LONG' => 'The parsed size of your poll title is too large, consider removing BBCodes or smilies.', + 'TOO_FEW_POLL_OPTIONS' => 'You must enter at least two poll options.', + 'TOO_MANY_POLL_OPTIONS' => 'You have tried to enter too many poll options.', + 'TOO_MANY_USER_OPTIONS' => 'You cannot specify more options per user than existing poll options.', + ); + + $phpbb_container = new phpbb_mock_container_builder; + $phpbb_container->set('user', $user); + $phpbb_container->set('config', $config); + + if (isset($setup)) + { + $setup($parser, $phpbb_container, $this); + } + + $this->get_test_case_helpers()->set_s9e_services($phpbb_container); + } + + /** + * @dataProvider get_test_polls + */ + public function test_parse_poll($poll, $expected, $warn_msg = array()) + { + $this->prepare_s9e_services(); + + $message_parser = new parse_message('Me[i]s[/i]sage'); + + // Add some default values + $poll += array( + 'poll_length' => 123, + 'poll_start' => 123, + 'poll_last_vote' => 123, + 'poll_vote_change' => true, + 'enable_bbcode' => true, + 'enable_urls' => true, + 'enable_smilies' => true, + 'img_status' => true + ); + + $message_parser->parse_poll($poll); + $this->assertSame($expected, array_intersect_key($poll, $expected)); + + $this->assertSame( + '<r>Me<I><s>[i]</s>s<e>[/i]</e></I>sage</r>', + $message_parser->parse(true, true, true, true, true, true, true, false) + ); + + $this->assertSame($warn_msg, $message_parser->warn_msg); + } + + public function get_test_polls() + { + return array( + array( + array( + 'poll_title' => 'foo [b]bar[/b] baz', + 'poll_option_text' => "[i]foo[/i]\nbar\n[i]baz[/i]", + 'poll_max_options' => 3, + 'poll_options_size' => 3 + ), + array( + 'poll_title' => '<r>foo <B><s>[b]</s>bar<e>[/b]</e></B> baz</r>', + 'poll_option_text' => "<r><I><s>[i]</s>foo<e>[/i]</e></I></r>\n<t>bar</t>\n<r><I><s>[i]</s>baz<e>[/i]</e></I></r>", + 'poll_options' => array( + '<r><I><s>[i]</s>foo<e>[/i]</e></I></r>', + '<t>bar</t>', + '<r><I><s>[i]</s>baz<e>[/i]</e></I></r>' + ) + ) + ), + array( + array( + 'poll_title' => 'xxx', + 'poll_option_text' => "[quote]quote[/quote]\n:)", + 'poll_max_options' => 2, + 'poll_options_size' => 2 + ), + array( + 'poll_title' => '<t>xxx</t>', + 'poll_option_text' => "<t>[quote]quote[/quote]</t>\n<r><E>:)</E></r>", + 'poll_options' => array( + '<t>[quote]quote[/quote]</t>', + '<r><E>:)</E></r>' + ) + ), + array('You cannot use certain BBCodes: [quote].') + ), + array( + array( + 'poll_title' => 'xxx', + 'poll_option_text' => "[flash=12,34]http://example.org/x.swf[/flash]\n:)", + 'poll_max_options' => 2, + 'poll_options_size' => 2 + ), + array( + 'poll_title' => '<t>xxx</t>', + 'poll_option_text' => "<t>[flash=12,34]http://example.org/x.swf[/flash]</t>\n<r><E>:)</E></r>", + 'poll_options' => array( + '<t>[flash=12,34]http://example.org/x.swf[/flash]</t>', + '<r><E>:)</E></r>' + ) + ), + array('You cannot use certain BBCodes: [flash].') + ), + array( + array( + 'poll_title' => 'xxx', + 'poll_option_text' => "[b]x\ny[/b]", + 'poll_max_options' => 2, + 'poll_options_size' => 2 + ), + array( + 'poll_title' => '<t>xxx</t>', + 'poll_option_text' => "<r><B><s>[b]</s>x</B></r>\n<t>y[/b]</t>", + 'poll_options' => array( + '<r><B><s>[b]</s>x</B></r>', + '<t>y[/b]</t>', + ) + ) + ), + ); + } + + /** + * @dataProvider get_test_cases + */ + public function test_options($original, $expected, array $args, $setup = null, $warn_msg = array()) + { + $this->prepare_s9e_services($setup); + + $message_parser = new parse_message($original); + call_user_func_array(array($message_parser, 'parse'), $args); + + $this->assertSame($expected, $message_parser->message); + $this->assertSame($warn_msg, $message_parser->warn_msg); + } + + public function get_test_cases() + { + return array( + array( + '[b]bold[/b]', + '<r><B><s>[b]</s>bold<e>[/b]</e></B></r>', + array(true, true, true, true, true, true, true) + ), + array( + '[b]bold[/b]', + '<t>[b]bold[/b]</t>', + array(false, true, true, true, true, true, true) + ), + array( + 'http://example.org', + '<r><URL url="http://example.org">http://example.org</URL></r>', + array(true, true, true, true, true, true, true) + ), + array( + 'http://example.org', + '<t>http://example.org</t>', + array(true, false, true, true, true, true, true) + ), + array( + ':)', + '<r><E>:)</E></r>', + array(true, true, true, true, true, true, true) + ), + array( + ':)', + '<t>:)</t>', + array(true, true, false, true, true, true, true) + ), + array( + '[url=http://example.org][img]http://example.org/img.png[/img][/url]', + '<r><URL url="http://example.org"><s>[url=http://example.org]</s><IMG src="http://example.org/img.png"><s>[img]</s>http://example.org/img.png<e>[/img]</e></IMG><e>[/url]</e></URL></r>', + array(true, true, true, true, true, true, true) + ), + array( + '[url=http://example.org][img]http://example.org/img.png[/img][/url]', + '<r><URL url="http://example.org"><s>[url=http://example.org]</s>[img]http://example.org/img.png[/img]<e>[/url]</e></URL></r>', + array(true, true, true, false, true, true, true), + null, + array('You cannot use certain BBCodes: [img].') + ), + array( + '[flash=12,34]http://example.org/foo.swf[/flash]', + '<r><FLASH height="34" url="http://example.org/foo.swf" width="12"><s>[flash=12,34]</s><URL url="http://example.org/foo.swf">http://example.org/foo.swf</URL><e>[/flash]</e></FLASH></r>', + array(true, true, true, true, true, true, true) + ), + array( + '[flash=12,34]http://example.org/foo.swf[/flash]', + '<r>[flash=12,34]<URL url="http://example.org/foo.swf">http://example.org/foo.swf</URL>[/flash]</r>', + array(true, true, true, true, false, true, true), + null, + array('You cannot use certain BBCodes: [flash].') + ), + array( + '[quote="foo"]bar :)[/quote]', + '<r><QUOTE author="foo"><s>[quote="foo"]</s>bar <E>:)</E><e>[/quote]</e></QUOTE></r>', + array(true, true, true, true, true, true, true) + ), + array( + '[quote="foo"]bar :)[/quote]', + '<r>[quote="foo"]bar <E>:)</E>[/quote]</r>', + array(true, true, true, true, true, false, true), + null, + array('You cannot use certain BBCodes: [quote].') + ), + array( + '[url=http://example.org][img]http://example.org/img.png[/img][/url]', + '<r><URL url="http://example.org"><s>[url=http://example.org]</s><IMG src="http://example.org/img.png"><s>[img]</s>http://example.org/img.png<e>[/img]</e></IMG><e>[/url]</e></URL></r>', + array(true, true, true, true, true, true, true) + ), + array( + '[url=http://example.org][img]http://example.org/img.png[/img][/url]', + '<r>[url=http://example.org]<IMG src="http://example.org/img.png"><s>[img]</s>http://example.org/img.png<e>[/img]</e></IMG>[/url]</r>', + array(true, true, true, true, true, true, false), + null, + array('You cannot use certain BBCodes: [url].') + ), + array( + '[size=200]200[/size]', + '<r><SIZE size="200"><s>[size=200]</s>200<e>[/size]</e></SIZE></r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_font_size', 200); + } + ), + array( + '[size=200]200[/size]', + '<r><SIZE size="200"><s>[size=200]</s>200<e>[/size]</e></SIZE></r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_font_size', 0); + } + ), + array( + '[size=2000]2000[/size]', + '<t>[size=2000]2000[/size]</t>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_font_size', 200); + }, + array('You may only use fonts up to size 200.') + ), + array( + '[size=0]0[/size]', + '<t>[size=0]0[/size]</t>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_font_size', 200); + } + ), + array( + '[size=200]200[/size]', + '<r><SIZE size="200"><s>[size=200]</s>200<e>[/size]</e></SIZE></r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_sig_font_size', 200); + } + ), + array( + '[size=200]200[/size]', + '<t>[size=200]200[/size]</t>', + array(true, true, true, true, true, true, true, true, 'sig'), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_sig_font_size', 120); + }, + array('You may only use fonts up to size 120.') + ), + array( + '[img]http://example.org/100x100.png[/img]', + '<r>[img]<URL url="http://example.org/100x100.png">http://example.org/100x100.png</URL>[/img]</r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_img_height', 12); + }, + array('Your images may only be up to 12 pixels high.') + ), + array( + '[img]http://example.org/100x100.png[/img]', + '<r>[img]<URL url="http://example.org/100x100.png">http://example.org/100x100.png</URL>[/img]</r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_img_width', 34); + }, + array('Your images may only be up to 34 pixels wide.') + ), + array( + '[img]http://example.org/100x100.png[/img]', + '<r><IMG src="http://example.org/100x100.png"><s>[img]</s><URL url="http://example.org/100x100.png">http://example.org/100x100.png</URL><e>[/img]</e></IMG></r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_img_height', 0); + $phpbb_container->get('config')->set('max_post_img_width', 0); + } + ), + array( + '[img]http://example.org/100x100.png[/img]', + '<r><IMG src="http://example.org/100x100.png"><s>[img]</s><URL url="http://example.org/100x100.png">http://example.org/100x100.png</URL><e>[/img]</e></IMG></r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_img_height', 100); + $phpbb_container->get('config')->set('max_post_img_width', 100); + } + ), + array( + '[img]http://example.org/100x100.png[/img]', + '<r><IMG src="http://example.org/100x100.png"><s>[img]</s><URL url="http://example.org/100x100.png">http://example.org/100x100.png</URL><e>[/img]</e></IMG></r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_sig_img_height', 12); + $phpbb_container->get('config')->set('max_sig_img_width', 34); + } + ), + array( + '[img]http://example.org/404.png[/img]', + '<r>[img]<URL url="http://example.org/404.png">http://example.org/404.png</URL>[/img]</r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_img_height', 12); + }, + array('It was not possible to determine the dimensions of the image.') + ), + array( + '[flash=999,999]http://example.org/foo.swf[/flash]', + '<r>[flash=999,999]<URL url="http://example.org/foo.swf">http://example.org/foo.swf</URL>[/flash]</r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_img_height', 123); + }, + array('Your flash files may only be up to 123 pixels high.') + ), + array( + '[flash=999,999]http://example.org/foo.swf[/flash]', + '<r>[flash=999,999]<URL url="http://example.org/foo.swf">http://example.org/foo.swf</URL>[/flash]</r>', + array(true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_img_width', 456); + }, + array('Your flash files may only be up to 456 pixels wide.') + ), + array( + ':) :) :)', + '<r><E>:)</E> <E>:)</E> <E>:)</E></r>', + array(true, true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_smilies', 3); + } + ), + array( + ':) :) :) :)', + '<r><E>:)</E> <E>:)</E> <E>:)</E> :)</r>', + array(true, true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_smilies', 3); + }, + array('Your message contains too many smilies. The maximum number of smilies allowed is 3.') + ), + array( + ':) :) :) :)', + '<r><E>:)</E> <E>:)</E> <E>:)</E> <E>:)</E></r>', + array(true, true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_smilies', 0); + } + ), + array( + ':) :) :) :)', + '<r><E>:)</E> <E>:)</E> <E>:)</E> <E>:)</E></r>', + array(true, true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_sig_smilies', 3); + } + ), + array( + ':) :) :) :)', + '<r><E>:)</E> <E>:)</E> <E>:)</E> :)</r>', + array(true, true, true, true, true, true, true, true, 'sig'), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_sig_smilies', 3); + }, + array('Your message contains too many smilies. The maximum number of smilies allowed is 3.') + ), + array( + 'http://example.org http://example.org http://example.org', + '<r><URL url="http://example.org">http://example.org</URL> <URL url="http://example.org">http://example.org</URL> http://example.org</r>', + array(true, true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_urls', 2); + }, + array('Your message contains too many URLs. The maximum number of URLs allowed is 2.') + ), + array( + 'http://example.org http://example.org http://example.org', + '<r><URL url="http://example.org">http://example.org</URL> <URL url="http://example.org">http://example.org</URL> <URL url="http://example.org">http://example.org</URL></r>', + array(true, true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_post_urls', 0); + } + ), + array( + 'http://example.org http://example.org http://example.org', + '<r><URL url="http://example.org">http://example.org</URL> <URL url="http://example.org">http://example.org</URL> <URL url="http://example.org">http://example.org</URL></r>', + array(true, true, true, true, true, true, true, true), + function ($parser, $phpbb_container) + { + $phpbb_container->get('config')->set('max_sig_urls', 2); + } + ), + ); + } +} + +class phpbb_text_processing_message_parser_test_proxy +{ + protected $response; + + public function stream_open($url) + { + if (strpos($url, '100x100')) + { + // Return a 100 x 100 PNG image + $this->response = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAGQAAABkAQAAAABYmaj5AAAAE0lEQVR4AWOgKxgFo2AUjIJRAAAFeAABHs0ozQAAAABJRU5ErkJggg=='); + } + else + { + $this->response = '404 not found'; + } + + return true; + } + + public function stream_stat() + { + return false; + } + + public function stream_read($len) + { + $chunk = substr($this->response, 0, $len); + $this->response = substr($this->response, $len); + + return $chunk; + } + + public function stream_eof() + { + return ($this->response === false); + } +} diff --git a/tests/text_processing/smilies_test.php b/tests/text_processing/smilies_test.php new file mode 100644 index 0000000000..3bbe065d36 --- /dev/null +++ b/tests/text_processing/smilies_test.php @@ -0,0 +1,52 @@ +<?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_text_processing_smilies_test extends phpbb_test_case +{ + /** + * @dataProvider get_text_formatter_tests + */ + public function test_text_formatter($original, $expected) + { + $container = $this->get_test_case_helpers()->set_s9e_services(null, __DIR__ . '/fixtures/smilies.xml'); + $parser = $container->get('text_formatter.parser'); + $renderer = $container->get('text_formatter.renderer'); + + $this->assertSame($expected, $renderer->render($parser->parse($original))); + } + + public function get_text_formatter_tests() + { + return array( + array( + ':) beginning', + '<img class="smilies" src="phpBB/images/smilies/icon_e_smile.gif" alt=":)" title="Smile"> beginning' + ), + array( + 'end :)', + 'end <img class="smilies" src="phpBB/images/smilies/icon_e_smile.gif" alt=":)" title="Smile">' + ), + array( + ':)', + '<img class="smilies" src="phpBB/images/smilies/icon_e_smile.gif" alt=":)" title="Smile">' + ), + array( + 'xx (18) 8) xx', + 'xx (18) <img class="smilies" src="phpBB/images/smilies/custom.gif" alt="8)" title="8)"> xx' + ), + ); + } +} diff --git a/tests/text_processing/strip_bbcode_test.php b/tests/text_processing/strip_bbcode_test.php new file mode 100644 index 0000000000..827d8d4a52 --- /dev/null +++ b/tests/text_processing/strip_bbcode_test.php @@ -0,0 +1,42 @@ +<?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 dirname(__FILE__) . '/../../phpBB/includes/functions.php'; +require_once dirname(__FILE__) . '/../../phpBB/includes/functions_content.php'; + +class phpbb_text_processing_strip_bbcode_test extends phpbb_test_case +{ + public function test_legacy() + { + $original = '[b:20m4ill1]bold[/b:20m4ill1]'; + $expected = ' bold '; + + $actual = $original; + strip_bbcode($actual); + + $this->assertSame($expected, $actual, '20m4ill1'); + } + + public function test_s9e() + { + $phpbb_container = $this->get_test_case_helpers()->set_s9e_services(); + + $original = '<r><B><s>[b]</s>bold<e>[/b]</e></B></r>'; + $expected = ' bold '; + + $actual = $original; + strip_bbcode($actual); + + $this->assertSame($expected, $actual); + } +} diff --git a/tests/text_processing/tickets_data/PHPBB3-10002.html b/tests/text_processing/tickets_data/PHPBB3-10002.html new file mode 100644 index 0000000000..82990b2253 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10002.html @@ -0,0 +1,2 @@ +<blockquote class="uncited"><div><ul><li>one +<blockquote class="uncited"><div><ul><li>two</li></ul></div></blockquote></li></ul></div></blockquote>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10002.txt b/tests/text_processing/tickets_data/PHPBB3-10002.txt new file mode 100644 index 0000000000..fe2f29073f --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10002.txt @@ -0,0 +1,2 @@ +[quote][list][*]one +[quote][list][*]two[/list][/quote]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10122.html b/tests/text_processing/tickets_data/PHPBB3-10122.html new file mode 100644 index 0000000000..f0fb6115b2 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10122.html @@ -0,0 +1 @@ +<ul style="list-style-type: none"><li>This is my indented text</li></ul>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10122.txt b/tests/text_processing/tickets_data/PHPBB3-10122.txt new file mode 100644 index 0000000000..a5e059df66 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10122.txt @@ -0,0 +1 @@ +[list=none][*]This is my indented text[/list]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10425.html b/tests/text_processing/tickets_data/PHPBB3-10425.html new file mode 100644 index 0000000000..522b2f8858 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10425.html @@ -0,0 +1,3 @@ +<a href="http://ar.wikipedia.org/wiki/%D8%A7%D9%84%D8%B5%D9%81%D8%AD%D8%A9_%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9" class="postlink">http://ar.wikipedia.org/wiki/الصفحة_الرئيسية</a><br> +<a href="http://ar.wikipedia.org/wiki/%D8%A7%D9%84%D8%B5%D9%81%D8%AD%D8%A9_%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9" class="postlink">http://ar.wikipedia.org/wiki/الصفحة_الرئيسية</a><br> +<a href="http://ar.wikipedia.org/wiki/%D8%A7%D9%84%D8%B5%D9%81%D8%AD%D8%A9_%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9" class="postlink">link</a>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10425.txt b/tests/text_processing/tickets_data/PHPBB3-10425.txt new file mode 100644 index 0000000000..d93c0446b6 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10425.txt @@ -0,0 +1,3 @@ +http://ar.wikipedia.org/wiki/الصفحة_الرئيسية +[url]http://ar.wikipedia.org/wiki/الصفحة_الرئيسية[/url] +[url=http://ar.wikipedia.org/wiki/الصفحة_الرئيسية]link[/url]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10587.html b/tests/text_processing/tickets_data/PHPBB3-10587.html new file mode 100644 index 0000000000..dd0a483244 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10587.html @@ -0,0 +1,2 @@ +<a href="http://www.tx-gaming.net/warzone/tournament.php?tourney%5Bid%5D=34&action=brackets" class="postlink">http://www.tx-gaming.net/warzone/tournament.php?tourney[id]=34&action=brackets</a><br> +<a href="http://www.tx-gaming.net/warzone/tournament.php?tourney%5Bid%5D=34&action=brackets" class="postlink">link</a>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10587.txt b/tests/text_processing/tickets_data/PHPBB3-10587.txt new file mode 100644 index 0000000000..f81a35eb5f --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10587.txt @@ -0,0 +1,2 @@ +[url]http://www.tx-gaming.net/warzone/tournament.php?tourney[id]=34&action=brackets[/url] +[url="http://www.tx-gaming.net/warzone/tournament.php?tourney[id]=34&action=brackets"]link[/url]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10922.html b/tests/text_processing/tickets_data/PHPBB3-10922.html new file mode 100644 index 0000000000..cdf8316df0 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10922.html @@ -0,0 +1 @@ +<a href="mailto:user@example.org">user@example.org</a><a href="mailto:user@example.org">...</a>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10922.txt b/tests/text_processing/tickets_data/PHPBB3-10922.txt new file mode 100644 index 0000000000..348f8a1541 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10922.txt @@ -0,0 +1 @@ +[email]user@example.org[/email][email=user@example.org]...[/email]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10989.html b/tests/text_processing/tickets_data/PHPBB3-10989.html new file mode 100644 index 0000000000..f003ad3dfa --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10989.html @@ -0,0 +1,8 @@ +<blockquote><div><cite>Lorem wrote:</cite>[quote="Lorem"<blockquote class="uncited"><div> Suspendisse iaculis porta tempor. Nulla.</div></blockquote> + Nullam a tortor sit amet.</div></blockquote> + Proin ac mi eget magna. + +<blockquote><div><cite>Lorem wrote:</cite>Quisque fermentum tortor quis odio scelerisque consequat fermentum urna gravida. In semper vehicula condimentum. Donec suscipit ante imperdiet augue rhoncus.</div></blockquote> + +<br> +Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas quis odio orci, sit amet semper.
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-10989.txt b/tests/text_processing/tickets_data/PHPBB3-10989.txt new file mode 100644 index 0000000000..dc2430f210 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-10989.txt @@ -0,0 +1,8 @@ +[quote="Lorem"][quote="Lorem"[quote] Suspendisse iaculis porta tempor. Nulla.[/quote] + Nullam a tortor sit amet.[/quote] + Proin ac mi eget magna. + +[quote="Lorem"]Quisque fermentum tortor quis odio scelerisque consequat fermentum urna gravida. In semper vehicula condimentum. Donec suscipit ante imperdiet augue rhoncus.[/quote] + + +Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas quis odio orci, sit amet semper.
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-11153.html b/tests/text_processing/tickets_data/PHPBB3-11153.html new file mode 100644 index 0000000000..0f67ac4bc0 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-11153.html @@ -0,0 +1 @@ +<a href="mailto:user@example.org">...</a>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-11153.txt b/tests/text_processing/tickets_data/PHPBB3-11153.txt new file mode 100644 index 0000000000..d2794978d9 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-11153.txt @@ -0,0 +1 @@ +[myemail=user@example.org]...[/myemail]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-11153.xml b/tests/text_processing/tickets_data/PHPBB3-11153.xml new file mode 100644 index 0000000000..a7fc69520b --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-11153.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>myemail</value> + <value></value> + <value>1</value> + <value>[myemail={EMAIL}]{TEXT}[/myemail]</value> + <value><![CDATA[<a href="mailto:{EMAIL}">{TEXT}</a>]]></value> + <value><![CDATA[!\[myemail\=(([\w\!\#$\%\&'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%'\*\+\-\/\=\?\^\`{\|\}\~]|&)+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,63})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?))\](.*?)\[/myemail\]!ies]]></value> + <value><![CDATA['[myemail='.$this->bbcode_specialchars('${1}').':$uid]'.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', ''', '(', ')'), trim('${2}')).'[/myemail:$uid]']]></value> + <value><![CDATA[!\[myemail\=(([\w\!\#$\%\&'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%'\*\+\-\/\=\?\^\`{\|\}\~]|&)+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,63})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)):$uid\](.*?)\[/myemail:$uid\]!s]]></value> + <value><![CDATA[<a href="mailto:${1}">${2}</a>]]></value> + </row> + </table> +</dataset> diff --git a/tests/text_processing/tickets_data/PHPBB3-12195.html b/tests/text_processing/tickets_data/PHPBB3-12195.html new file mode 100644 index 0000000000..d8e0f8d523 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-12195.html @@ -0,0 +1 @@ +<a href="//example.org/" class="postlink"><img src="//example.org/img.png" alt="Image"></a>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-12195.txt b/tests/text_processing/tickets_data/PHPBB3-12195.txt new file mode 100644 index 0000000000..b66dbd5d96 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-12195.txt @@ -0,0 +1 @@ +[url=//example.org/][img]//example.org/img.png[/img][/url]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-3981.before.php b/tests/text_processing/tickets_data/PHPBB3-3981.before.php new file mode 100644 index 0000000000..1c326b52af --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-3981.before.php @@ -0,0 +1,21 @@ +<?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. +* +*/ + +function before_assert_phpbb3_3981($vars) +{ + if (!function_exists('idn_to_ascii')) + { + extract($vars); + $test->markTestSkipped('International URLs need idn_to_ascii()'); + } +} diff --git a/tests/text_processing/tickets_data/PHPBB3-3981.html b/tests/text_processing/tickets_data/PHPBB3-3981.html new file mode 100644 index 0000000000..e5f1b4561d --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-3981.html @@ -0,0 +1 @@ +<a href="http://www.xn--ndaaa.com" class="postlink">http://www.ööö.com</a>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-3981.txt b/tests/text_processing/tickets_data/PHPBB3-3981.txt new file mode 100644 index 0000000000..976823f1d1 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-3981.txt @@ -0,0 +1 @@ +[url]http://www.ööö.com[/url]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-7187.html b/tests/text_processing/tickets_data/PHPBB3-7187.html new file mode 100644 index 0000000000..9138779d29 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-7187.html @@ -0,0 +1 @@ +<blockquote class="uncited"><div><img class="smilies" src="phpBB/images/smilies/icon_e_geek.gif" alt=":geek:" title="Geek"> <img class="smilies" src="phpBB/images/smilies/icon_e_ugeek.gif" alt=":ugeek:" title="Uber Geek"></div></blockquote>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-7187.txt b/tests/text_processing/tickets_data/PHPBB3-7187.txt new file mode 100644 index 0000000000..584151a083 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-7187.txt @@ -0,0 +1 @@ +[quote]:geek: :ugeek:[/quote]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-7187.xml b/tests/text_processing/tickets_data/PHPBB3-7187.xml new file mode 100644 index 0000000000..d270b12619 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-7187.xml @@ -0,0 +1,33 @@ +<?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>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> +</dataset> diff --git a/tests/text_processing/tickets_data/PHPBB3-7275.after.php b/tests/text_processing/tickets_data/PHPBB3-7275.after.php new file mode 100644 index 0000000000..99f41d7839 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-7275.after.php @@ -0,0 +1,19 @@ +<?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. +* +*/ + +function after_assert_phpbb3_7275($vars) +{ + extract($vars); + decode_message($parsed_text); + $test->assertSame($original, $parsed_text); +} diff --git a/tests/text_processing/tickets_data/PHPBB3-7275.html b/tests/text_processing/tickets_data/PHPBB3-7275.html new file mode 100644 index 0000000000..12502833fd --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-7275.html @@ -0,0 +1 @@ +<div align="center"><img class="smilies" src="phpBB/images/smilies/icon_e_smile.gif" alt=":)" title="Smile"></div>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-7275.txt b/tests/text_processing/tickets_data/PHPBB3-7275.txt new file mode 100644 index 0000000000..8de97d67e0 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-7275.txt @@ -0,0 +1 @@ +[center]:)[/center]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-7275.xml b/tests/text_processing/tickets_data/PHPBB3-7275.xml new file mode 100644 index 0000000000..9e979afffb --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-7275.xml @@ -0,0 +1,49 @@ +<?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>center</value> + <value></value> + <value>1</value> + <value>[center]{TEXT}[/center]</value> + <value><![CDATA[<div align="center">{TEXT}</div>]]></value> + <value>!\[center\](.*?)\[/center\]!ies</value> + <value><![CDATA['[center:$uid]'.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', ''', '(', ')'), trim('${1}')).'[/center:$uid]']]></value> + <value>!\[center:$uid\](.*?)\[/center:$uid\]!s</value> + <value><![CDATA[<div align="center">${1}</div>]]></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>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> + </table> +</dataset> diff --git a/tests/text_processing/tickets_data/PHPBB3-9377.html b/tests/text_processing/tickets_data/PHPBB3-9377.html new file mode 100644 index 0000000000..dcfb79c173 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-9377.html @@ -0,0 +1 @@ +<span style="color:red">red <span style="color:blue">blue</span> red</span>
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-9377.txt b/tests/text_processing/tickets_data/PHPBB3-9377.txt new file mode 100644 index 0000000000..dfd71492c5 --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-9377.txt @@ -0,0 +1 @@ +[red]red [blue]blue[/blue] red[/red]
\ No newline at end of file diff --git a/tests/text_processing/tickets_data/PHPBB3-9377.xml b/tests/text_processing/tickets_data/PHPBB3-9377.xml new file mode 100644 index 0000000000..1d8ee3d53f --- /dev/null +++ b/tests/text_processing/tickets_data/PHPBB3-9377.xml @@ -0,0 +1,41 @@ +<?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>red</value> + <value></value> + <value>1</value> + <value>[red]{TEXT}[/red]</value> + <value><span style="color:red">{TEXT}</span></value> + <value>!\[red\](.*?)\[/red\]!ies</value> + <value>'[red:$uid]'.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', '&#39;', '&#40;', '&#41;'), trim('${1}')).'[/red:$uid]'</value> + <value>!\[red:$uid\](.*?)\[/red:$uid\]!s</value> + <value><span style="color:red">${1}</span></value> + </row> + + <row> + <value>14</value> + <value>blue</value> + <value></value> + <value>1</value> + <value>[blue]{TEXT}[/blue]</value> + <value><span style="color:blue">{TEXT}</span></value> + <value>!\[blue\](.*?)\[/blue\]!ies</value> + <value>'[blue:$uid]'.str_replace(array("\r\n", '\"', '\'', '(', ')'), array("\n", '"', '&#39;', '&#40;', '&#41;'), trim('${1}')).'[/blue:$uid]'</value> + <value>!\[blue:$uid\](.*?)\[/blue:$uid\]!s</value> + <value><span style="color:blue">${1}</span></value> + </row> + </table> +</dataset> diff --git a/tests/text_processing/tickets_test.php b/tests/text_processing/tickets_test.php new file mode 100644 index 0000000000..8c48a3f4a9 --- /dev/null +++ b/tests/text_processing/tickets_test.php @@ -0,0 +1,94 @@ +<?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__ . '/../../phpBB/includes/utf/utf_tools.php'; + +class phpbb_text_processing_tickets_test extends phpbb_test_case +{ + /** + * @dataProvider get_tickets_data + */ + public function test_tickets($ticket_id, $original, $expected, $fixture, $before_assert, $after_assert) + { + global $phpbb_container; + + $phpbb_container = new phpbb_mock_container_builder; + + $this->get_test_case_helpers()->set_s9e_services($phpbb_container, $fixture); + + $parser = $phpbb_container->get('text_formatter.parser'); + $renderer = $phpbb_container->get('text_formatter.renderer'); + + if (isset($before_assert)) + { + $test = $this; + $before_assert(get_defined_vars()); + } + + $parsed_text = $parser->parse($original); + + $this->assertSame($expected, $renderer->render($parsed_text)); + + if (isset($after_assert)) + { + $test = $this; + $after_assert(get_defined_vars()); + } + } + + public function get_tickets_data() + { + $tests = array(); + + foreach (glob(__DIR__ . '/tickets_data/*.txt') as $txt_filename) + { + $ticket_id = basename($txt_filename, '.txt'); + $html_filename = substr($txt_filename, 0, -3) . 'html'; + $xml_filename = substr($txt_filename, 0, -3) . 'xml'; + $before_filename = substr($txt_filename, 0, -3) . 'before.php'; + $after_filename = substr($txt_filename, 0, -3) . 'after.php'; + + if (!file_exists($xml_filename)) + { + $xml_filename = __DIR__ . '/../fixtures/empty.xml'; + } + + $before_assert = null; + if (file_exists($before_filename)) + { + include($before_filename); + $before_assert = 'before_assert_' . strtolower(str_replace('-', '_', $ticket_id)); + } + + $after_assert = null; + if (file_exists($after_filename)) + { + include($after_filename); + $after_assert = 'after_assert_' . strtolower(str_replace('-', '_', $ticket_id)); + } + + $tests[] = array( + $ticket_id, + file_get_contents($txt_filename), + file_get_contents($html_filename), + $xml_filename, + $before_assert, + $after_assert + ); + } + + return $tests; + } +} |