diff --git a/phpBB/config/default/container/services_content.yml b/phpBB/config/default/container/services_content.yml index 8b240067e4..e2d53784c2 100644 --- a/phpBB/config/default/container/services_content.yml +++ b/phpBB/config/default/container/services_content.yml @@ -67,6 +67,14 @@ services: - '@controller.helper' - '@dispatcher' + posting.lock: + class: phpbb\lock\posting + shared: false + arguments: + - '@cache.driver' + - '@config' + - '@request' + viewonline_helper: class: phpbb\viewonline_helper arguments: diff --git a/phpBB/phpbb/lock/posting.php b/phpBB/phpbb/lock/posting.php new file mode 100644 index 0000000000..d912475890 --- /dev/null +++ b/phpBB/phpbb/lock/posting.php @@ -0,0 +1,82 @@ + + * @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\lock; + +use phpbb\cache\driver\driver_interface as cache_interface; +use phpbb\config\config; +use phpbb\request\request_interface; + +class posting +{ + /** @var cache_interface */ + private $cache; + + /** @var config */ + private $config; + + /** @var request_interface */ + private $request; + + /** @var string */ + private $lock_name = ''; + + /** + * Constructor for posting lock + * + * @param cache_interface $cache + * @param config $config + * @param request_interface $request + */ + public function __construct(cache_interface $cache, config $config, request_interface $request) + { + $this->cache = $cache; + $this->config = $config; + $this->request = $request; + } + + /** + * Get lock name + * @return string Lock name + */ + private function lock_name(): string + { + if ($this->lock_name) + { + return $this->lock_name; + } + + $creation_time = abs($this->request->variable('creation_time', 0)); + $token = $this->request->variable('form_token', ''); + + return sha1(((string) $creation_time) . $token) . '_posting_lock'; + } + + /** + * Acquire lock for current posting form submission + * + * @return bool True if lock could be acquired, false if not + */ + public function acquire(): bool + { + // Lock is held for session, cannot acquire it + if ($this->cache->_exists($this->lock_name())) + { + return false; + } + + $this->cache->put($this->lock_name(), true, $this->config['flood_interval']); + + return true; + } +} \ No newline at end of file diff --git a/phpBB/posting.php b/phpBB/posting.php index 724d8a8df1..7b3d2243ab 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -1429,7 +1429,10 @@ if ($submit || $preview || $refresh) // Store message, sync counters if (!count($error) && $submit) { - if ($submit) + /** @var \phpbb\lock\posting $posting_lock */ + $posting_lock = $phpbb_container->get('posting.lock'); + + if ($posting_lock->acquire()) { // Lock/Unlock Topic $change_topic_status = $post_data['topic_status']; @@ -1620,6 +1623,11 @@ if ($submit || $preview || $refresh) redirect($redirect_url); } + else + { + // Posting was already locked before, hence form submission was already attempted once and is now invalid + $error[] = $language->lang('FORM_INVALID'); + } } } diff --git a/phpBB/styles/prosilver/template/ajax.js b/phpBB/styles/prosilver/template/ajax.js index 79187470d8..e01e7eb4d4 100644 --- a/phpBB/styles/prosilver/template/ajax.js +++ b/phpBB/styles/prosilver/template/ajax.js @@ -337,6 +337,29 @@ $('[data-ajax]').each(function() { } }); +// Prevent accidental double submission of form +$('[data-prevent-flood] input[type=submit]').click(function(event) { + const $submitButton = $(this); // Store the button element + const $form = $submitButton.closest('form'); + + // Always add the disabled class for visual feedback + $submitButton.addClass('disabled'); + + // Submit form if it hasn't been submitted yet + if (!$form.prop('data-form-submitted')) { + $form.prop('data-form-submitted', true); + + return; + } + + // Prevent default submission for subsequent clicks within 5 seconds + event.preventDefault(); + + setTimeout(() => { + $form.prop('removeProp', 'data-form-submitted'); + $submitButton.removeClass('disabled'); // Re-enable after 5 seconds + }, 5000); +}); /** * This simply appends #preview to the action of the diff --git a/phpBB/styles/prosilver/template/posting_editor.html b/phpBB/styles/prosilver/template/posting_editor.html index 12790360d6..d3b2ecd68e 100644 --- a/phpBB/styles/prosilver/template/posting_editor.html +++ b/phpBB/styles/prosilver/template/posting_editor.html @@ -100,7 +100,7 @@
-
+
{S_HIDDEN_ADDRESS_FIELD} {S_HIDDEN_FIELDS}