diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml
index 73260f7a92..aa997b8360 100644
--- a/phpBB/config/default/container/services.yml
+++ b/phpBB/config/default/container/services.yml
@@ -23,6 +23,7 @@ imports:
- { resource: services_notification.yml }
- { resource: services_password.yml }
- { resource: services_php.yml }
+ - { resource: services_posting.yml }
- { resource: services_profilefield.yml }
- { resource: services_report.yml }
- { resource: services_routing.yml }
diff --git a/phpBB/config/default/container/services_console.yml b/phpBB/config/default/container/services_console.yml
index 74cfade99d..f23d205b8f 100644
--- a/phpBB/config/default/container/services_console.yml
+++ b/phpBB/config/default/container/services_console.yml
@@ -246,6 +246,40 @@ services:
tags:
- { name: console.command }
+ console.command.searchindex.list_all:
+ class: phpbb\console\command\searchindex\list_all
+ arguments:
+ - '@config'
+ - '@language'
+ - '@search.backend_collection'
+ - '@user'
+ tags:
+ - { name: console.command }
+
+ console.command.searchindex.create:
+ class: phpbb\console\command\searchindex\create
+ arguments:
+ - '@language'
+ - '@log'
+ - '@post.helper'
+ - '@search.backend_factory'
+ - '@search.state_helper'
+ - '@user'
+ tags:
+ - { name: console.command }
+
+ console.command.searchindex.delete:
+ class: phpbb\console\command\searchindex\delete
+ arguments:
+ - '@language'
+ - '@log'
+ - '@post.helper'
+ - '@search.backend_factory'
+ - '@search.state_helper'
+ - '@user'
+ tags:
+ - { name: console.command }
+
console.command.thumbnail.delete:
class: phpbb\console\command\thumbnail\delete
arguments:
diff --git a/phpBB/config/default/container/services_posting.yml b/phpBB/config/default/container/services_posting.yml
new file mode 100644
index 0000000000..aa1a3e1959
--- /dev/null
+++ b/phpBB/config/default/container/services_posting.yml
@@ -0,0 +1,5 @@
+services:
+ post.helper:
+ class: phpbb\posting\post_helper
+ arguments:
+ - '@dbal.conn'
diff --git a/phpBB/config/default/container/services_search.yml b/phpBB/config/default/container/services_search.yml
index 1dba3732be..fceb254eaa 100644
--- a/phpBB/config/default/container/services_search.yml
+++ b/phpBB/config/default/container/services_search.yml
@@ -1,5 +1,11 @@
services:
+ search.state_helper:
+ class: phpbb\search\state_helper
+ arguments:
+ - '@config'
+ - '@search.backend_factory'
+
# Search backends
search.fulltext.mysql:
class: phpbb\search\backend\fulltext_mysql
@@ -9,6 +15,7 @@ services:
- '@dispatcher'
- '@language'
- '@user'
+ - '%tables.search_results%'
- '%core.root_path%'
- '%core.php_ext%'
tags:
@@ -22,6 +29,9 @@ services:
- '@dispatcher'
- '@language'
- '@user'
+ - '%tables.search_results%'
+ - '%tables.search_wordlist%'
+ - '%tables.search_wordmatch%'
- '%core.root_path%'
- '%core.php_ext%'
tags:
@@ -35,6 +45,7 @@ services:
- '@dispatcher'
- '@language'
- '@user'
+ - '%tables.search_results%'
- '%core.root_path%'
- '%core.php_ext%'
tags:
diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php
index b89c4e3779..71e9ed7a2e 100644
--- a/phpBB/includes/acp/acp_main.php
+++ b/phpBB/includes/acp/acp_main.php
@@ -652,16 +652,9 @@ class acp_main
$search_backend_factory = $phpbb_container->get('search.backend_factory');
$search = $search_backend_factory->get_active();
}
- catch (RuntimeException $e)
+ catch (\phpbb\search\exception\no_search_backend_found_exception $e)
{
- if (strpos($e->getMessage(), 'No service found') === 0)
- {
- trigger_error('NO_SUCH_SEARCH_MODULE');
- }
- else
- {
- throw $e;
- }
+ trigger_error('NO_SUCH_SEARCH_MODULE');
}
if (!$search->index_created())
diff --git a/phpBB/includes/acp/acp_search.php b/phpBB/includes/acp/acp_search.php
index e2cbea4956..f13b9c8dea 100644
--- a/phpBB/includes/acp/acp_search.php
+++ b/phpBB/includes/acp/acp_search.php
@@ -21,6 +21,7 @@ use phpbb\language\language;
use phpbb\log\log;
use phpbb\request\request;
use phpbb\search\search_backend_factory;
+use phpbb\search\state_helper;
use phpbb\template\template;
use phpbb\user;
@@ -35,10 +36,6 @@ class acp_search
public $tpl_name;
public $page_title;
- protected const STATE_SEARCH_TYPE = 0;
- protected const STATE_ACTION = 1;
- protected const STATE_POST_COUNTER = 2;
-
/** @var config */
protected $config;
@@ -57,6 +54,9 @@ class acp_search
/** @var search_backend_factory */
protected $search_backend_factory;
+ /** @var state_helper */
+ protected $search_state_helper;
+
/** @var template */
protected $template;
@@ -79,6 +79,7 @@ class acp_search
$this->request = $request;
$this->search_backend_collection = $phpbb_container->get('search.backend_collection');
$this->search_backend_factory = $phpbb_container->get('search.backend_factory');
+ $this->search_state_helper = $phpbb_container->get('search.state_helper');
$this->template = $template;
$this->user = $user;
$this->phpbb_admin_path = $phpbb_admin_path;
@@ -272,7 +273,6 @@ class acp_search
public function index(string $id, string $mode): void
{
$action = $this->request->variable('action', '');
- $state = !empty($this->config['search_indexing_state']) ? explode(',', $this->config['search_indexing_state']) : [];
if ($action && !$this->request->is_set_post('cancel'))
{
@@ -284,7 +284,7 @@ class acp_search
case 'create':
case 'delete':
- $this->index_action($id, $mode, $action, $state);
+ $this->index_action($id, $mode, $action);
break;
default:
@@ -296,13 +296,12 @@ class acp_search
// If clicked to cancel the indexing progress (acp_search_index_inprogress form)
if ($this->request->is_set_post('cancel'))
{
- $state = [];
- $this->save_state($state);
+ $this->search_state_helper->clear_state();
}
- if (!empty($state))
+ if ($this->search_state_helper->is_action_in_progress())
{
- $this->index_inprogress($id, $mode, $state[self::STATE_ACTION]);
+ $this->index_inprogress($id, $mode);
}
else
{
@@ -325,8 +324,8 @@ class acp_search
foreach ($this->search_backend_collection as $search)
{
$this->template->assign_block_vars('backends', [
- 'NAME' => $search->get_name(),
- 'TYPE' => $search->get_type(),
+ 'NAME' => $search->get_name(),
+ 'TYPE' => $search->get_type(),
'S_ACTIVE' => $search->get_type() === $this->config['search_type'],
'S_HIDDEN_FIELDS' => build_hidden_fields(['search_type' => $search->get_type()]),
@@ -336,8 +335,8 @@ class acp_search
}
$this->template->assign_vars([
- 'U_ACTION' => $this->u_action . '&hash=' . generate_link_hash('acp_search'),
- 'UA_PROGRESS_BAR' => addslashes($this->u_action . '&action=progress_bar'),
+ 'U_ACTION' => $this->u_action . '&hash=' . generate_link_hash('acp_search'),
+ 'UA_PROGRESS_BAR' => addslashes($this->u_action . '&action=progress_bar'),
]);
}
@@ -346,13 +345,14 @@ class acp_search
*
* @param string $id
* @param string $mode
- * @param string $action Action in progress: 'create' or 'delete'
*/
- private function index_inprogress(string $id, string $mode, string $action): void
+ private function index_inprogress(string $id, string $mode): void
{
$this->tpl_name = 'acp_search_index_inprogress';
$this->page_title = 'ACP_SEARCH_INDEX';
+ $action = $this->search_state_helper->action();
+
$this->template->assign_vars([
'U_ACTION' => $this->u_action . '&action=' . $action . '&hash=' . generate_link_hash('acp_search'),
'UA_PROGRESS_BAR' => addslashes($this->u_action . '&action=progress_bar'),
@@ -368,9 +368,8 @@ class acp_search
* @param string $id
* @param string $mode
* @param string $action
- * @param array $state
*/
- private function index_action(string $id, string $mode, string $action, array $state): void
+ private function index_action(string $id, string $mode, string $action): void
{
// For some this may be of help...
@ini_set('memory_limit', '128M');
@@ -381,29 +380,23 @@ class acp_search
}
// Entering here for the first time
- if (empty($state))
+ if (!$this->search_state_helper->is_action_in_progress())
{
if ($this->request->is_set_post('search_type', ''))
{
- $state = [
- self::STATE_SEARCH_TYPE => $this->request->variable('search_type', ''),
- self::STATE_ACTION => $action,
- self::STATE_POST_COUNTER => 0
- ];
+ $this->search_state_helper->init($this->request->variable('search_type', ''), $action);
}
else
{
trigger_error($this->language->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING);
}
-
- $this->save_state($state); // Create new state in the database
}
- $type = $state[self::STATE_SEARCH_TYPE];
- $action = $state[self::STATE_ACTION];
- $post_counter = &$state[self::STATE_POST_COUNTER];
-
// Execute create/delete
+ $type = $this->search_state_helper->type();
+ $action = $this->search_state_helper->action();
+ $post_counter = $this->search_state_helper->counter();
+
$search = $this->search_backend_factory->get($type);
try
@@ -411,7 +404,7 @@ class acp_search
$status = ($action == 'create') ? $search->create_index($post_counter) : $search->delete_index($post_counter);
if ($status) // Status is not null, so action is in progress....
{
- $this->save_state($state); // update $post_counter in $state in the database
+ $this->search_state_helper->update_counter($status['post_counter']);
$u_action = append_sid($this->phpbb_admin_path . "index." . $this->php_ex, "i=$id&mode=$mode&action=$action&hash=" . generate_link_hash('acp_search'), false);
meta_refresh(1, $u_action);
@@ -423,13 +416,13 @@ class acp_search
}
catch (Exception $e)
{
- $this->save_state([]); // Unexpected error, cancel action
+ $this->search_state_helper->clear_state(); // Unexpected error, cancel action
trigger_error($e->getMessage() . adm_back_link($this->u_action) . $this->close_popup_js(), E_USER_WARNING);
}
$search->tidy();
- $this->save_state([]); // finished operation, cancel action
+ $this->search_state_helper->clear_state(); // finished operation, cancel action
$log_operation = ($action == 'create') ? 'LOG_SEARCH_INDEX_CREATED' : 'LOG_SEARCH_INDEX_REMOVED';
$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, $log_operation, false, [$search->get_name()]);
@@ -473,14 +466,4 @@ class acp_search
"// ]]>\n" .
"\n";
}
-
- /**
- * @param array $state
- */
- private function save_state(array $state = []): void
- {
- ksort($state);
-
- $this->config->set('search_indexing_state', implode(',', $state), true);
- }
}
diff --git a/phpBB/includes/functions_admin.php b/phpBB/includes/functions_admin.php
index fa4dac7e57..27f9292ac5 100644
--- a/phpBB/includes/functions_admin.php
+++ b/phpBB/includes/functions_admin.php
@@ -1092,16 +1092,9 @@ function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync =
$search_backend_factory = $phpbb_container->get('search.backend_factory');
$search = $search_backend_factory->get_active();
}
- catch (RuntimeException $e)
+ catch (\phpbb\search\exception\no_search_backend_found_exception $e)
{
- if (strpos($e->getMessage(), 'No service found') === 0)
- {
- trigger_error('NO_SUCH_SEARCH_MODULE');
- }
- else
- {
- throw $e;
- }
+ trigger_error('NO_SUCH_SEARCH_MODULE');
}
$search->index_remove($post_ids, $poster_ids, $forum_ids);
diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php
index 770280195c..80f0e31e74 100644
--- a/phpBB/includes/functions_posting.php
+++ b/phpBB/includes/functions_posting.php
@@ -2356,16 +2356,9 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll_ary, &$data
$search_backend_factory = $phpbb_container->get('search.backend_factory');
$search = $search_backend_factory->get_active();
}
- catch (RuntimeException $e)
+ catch (\phpbb\search\exception\no_search_backend_found_exception $e)
{
- if (strpos($e->getMessage(), 'No service found') === 0)
- {
- trigger_error('NO_SUCH_SEARCH_MODULE');
- }
- else
- {
- throw $e;
- }
+ trigger_error('NO_SUCH_SEARCH_MODULE');
}
$search->index($mode, (int) $data_ary['post_id'], $data_ary['message'], $subject, $poster_id, (int) $data_ary['forum_id']);
diff --git a/phpBB/includes/mcp/mcp_main.php b/phpBB/includes/mcp/mcp_main.php
index b5ad7ffc8c..7dab25074f 100644
--- a/phpBB/includes/mcp/mcp_main.php
+++ b/phpBB/includes/mcp/mcp_main.php
@@ -1397,16 +1397,9 @@ function mcp_fork_topic($topic_ids)
$search_backend_factory = $phpbb_container->get('search.backend_factory');
$search = $search_backend_factory->get_active();
}
- catch (RuntimeException $e)
+ catch (\phpbb\search\exception\no_search_backend_found_exception $e)
{
- if (strpos($e->getMessage(), 'No service found') === 0)
- {
- trigger_error('NO_SUCH_SEARCH_MODULE');
- }
- else
- {
- throw $e;
- }
+ trigger_error('NO_SUCH_SEARCH_MODULE');
}
$search_mode = 'post';
}
diff --git a/phpBB/includes/mcp/mcp_post.php b/phpBB/includes/mcp/mcp_post.php
index bb402b048d..8e7c0342e8 100644
--- a/phpBB/includes/mcp/mcp_post.php
+++ b/phpBB/includes/mcp/mcp_post.php
@@ -637,16 +637,9 @@ function change_poster(&$post_info, $userdata)
$search_backend_factory = $phpbb_container->get('search.backend_factory');
$search = $search_backend_factory->get_active();
}
- catch (RuntimeException $e)
+ catch (\phpbb\search\exception\no_search_backend_found_exception $e)
{
- if (strpos($e->getMessage(), 'No service found') === 0)
- {
- trigger_error('NO_SUCH_SEARCH_MODULE');
- }
- else
- {
- throw $e;
- }
+ trigger_error('NO_SUCH_SEARCH_MODULE');
}
$search->index_remove([], [$post_info['user_id'], $userdata['user_id']], []);
diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php
index 69c7800b81..77f6bea757 100644
--- a/phpBB/includes/mcp/mcp_topic.php
+++ b/phpBB/includes/mcp/mcp_topic.php
@@ -680,16 +680,9 @@ function split_topic($action, $topic_id, $to_forum_id, $subject)
$search_backend_factory = $phpbb_container->get('search.backend_factory');
$search = $search_backend_factory->get_active();
}
- catch (RuntimeException $e)
+ catch (\phpbb\search\exception\no_search_backend_found_exception $e)
{
- if (strpos($e->getMessage(), 'No service found') === 0)
- {
- trigger_error('NO_SUCH_SEARCH_MODULE');
- }
- else
- {
- throw $e;
- }
+ trigger_error('NO_SUCH_SEARCH_MODULE');
}
$search->index('edit', (int) $first_post_data['post_id'], $first_post_data['post_text'], $subject, (int) $first_post_data['poster_id'], (int) $first_post_data['forum_id']);
diff --git a/phpBB/language/en/acp/search.php b/phpBB/language/en/acp/search.php
index a79c67fd8e..183ccf0d7b 100644
--- a/phpBB/language/en/acp/search.php
+++ b/phpBB/language/en/acp/search.php
@@ -91,6 +91,8 @@ $lang = array_merge($lang, array(
'GENERAL_SEARCH_SETTINGS' => 'General search settings',
'GO_TO_SEARCH_INDEX' => 'Go to search index page',
+ 'INVALID_ACTION' => 'Invalid action',
+
'INDEX_STATS' => 'Index statistics',
'INDEXING_IN_PROGRESS' => 'Indexing in progress',
'INDEXING_IN_PROGRESS_EXPLAIN' => 'The search backend is currently indexing all posts on the board. This can take from a few minutes to a few hours depending on your board’s size.',
diff --git a/phpBB/language/en/cli.php b/phpBB/language/en/cli.php
index bdd76a32e5..80240b9cf2 100644
--- a/phpBB/language/en/cli.php
+++ b/phpBB/language/en/cli.php
@@ -83,6 +83,10 @@ $lang = array_merge($lang, array(
'CLI_DESCRIPTION_SET_ATOMIC_CONFIG' => 'Sets a configuration option’s value only if the old matches the current value',
'CLI_DESCRIPTION_SET_CONFIG' => 'Sets a configuration option’s value',
+ 'CLI_DESCRIPTION_SEARCHINDEX_DELETE' => 'Delete search index.',
+ 'CLI_DESCRIPTION_SEARCHINDEX_CREATE' => 'Create search index.',
+ 'CLI_DESCRIPTION_SEARCHINDEX_LIST' => 'List all search backends.',
+
'CLI_DESCRIPTION_THUMBNAIL_DELETE' => 'Delete all existing thumbnails.',
'CLI_DESCRIPTION_THUMBNAIL_GENERATE' => 'Generate all missing thumbnails.',
'CLI_DESCRIPTION_THUMBNAIL_RECREATE' => 'Recreate all thumbnails.',
@@ -142,6 +146,16 @@ $lang = array_merge($lang, array(
'CLI_REPARSER_REPARSE_REPARSING_START' => 'Reparsing %s...',
'CLI_REPARSER_REPARSE_SUCCESS' => 'Reparsing ended with success',
+ 'CLI_SEARCHINDEX_SEARCH_BACKEND_NAME' => 'Backend class',
+ 'CLI_SEARCHINDEX_BACKEND_NOT_FOUND' => 'Search module not found',
+ 'CLI_SEARCHINDEX_CREATE_SUCCESS' => 'Search index created successfully',
+ 'CLI_SEARCHINDEX_CREATE_FAILURE' => 'Error creating search index',
+ 'CLI_SEARCHINDEX_DELETE_SUCCESS' => 'Search index deleted successfully',
+ 'CLI_SEARCHINDEX_DELETE_FAILURE' => 'Error deleting search index',
+ 'CLI_SEARCHINDEX_ACTION_IN_PROGRESS' => 'There is an action currently in progress. CLI doesn’t support incomplete index/delete actions, please solve it from the ACP.',
+ 'CLI_SEARCHINDEX_ACTIVE_NOT_INDEXED' => 'Active search backend isn’t indexed',
+ 'CLI_SEARCHINDEX_BACKEND_NOT_AVAILABLE' => 'Search backend isn’t available.',
+
// In all the case %1$s is the logical name of the file and %2$s the real name on the filesystem
// eg: big_image.png (2_a51529ae7932008cf8454a95af84cacd) generated.
'CLI_THUMBNAIL_DELETED' => '%1$s (%2$s) deleted.',
diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php
index 6a10e8b846..8d71ca2952 100644
--- a/phpBB/language/en/common.php
+++ b/phpBB/language/en/common.php
@@ -215,6 +215,9 @@ $lang = array_merge($lang, array(
2 => 'Downloaded %d times',
),
+ 'DI_SERVICE_NOT_FOUND' => 'Service for class "%1$s" not found in collection.',
+ 'DI_MULTIPLE_SERVICE_DEFINITIONS' => 'More than one service definitions found for class "%1$s" in collection.',
+
'EDIT_POST' => 'Edit post',
'ELLIPSIS' => '…',
'EMAIL' => 'Email', // Short form for EMAIL_ADDRESS
@@ -715,6 +718,7 @@ $lang = array_merge($lang, array(
'SEARCH_UNANSWERED' => 'Unanswered topics',
'SEARCH_UNREAD' => 'Unread posts',
'SEARCH_USER_POSTS' => 'Search user’s posts',
+ 'SEARCH_BACKEND_NOT_FOUND' => 'No search backend could be found.',
'SECONDS' => 'Seconds',
'SEE_ALL' => 'See All',
'SELECT' => 'Select',
diff --git a/phpBB/phpbb/console/command/reparser/list_all.php b/phpBB/phpbb/console/command/reparser/list_all.php
index ae90c1a68f..83c301e00f 100644
--- a/phpBB/phpbb/console/command/reparser/list_all.php
+++ b/phpBB/phpbb/console/command/reparser/list_all.php
@@ -49,10 +49,8 @@ class list_all extends \phpbb\console\command\command
*/
protected function configure()
{
- $this
- ->setName('reparser:list')
- ->setDescription($this->user->lang('CLI_DESCRIPTION_REPARSER_LIST'))
- ;
+ $this->setName('reparser:list')
+ ->setDescription($this->user->lang('CLI_DESCRIPTION_REPARSER_LIST'));
}
/**
diff --git a/phpBB/phpbb/console/command/searchindex/create.php b/phpBB/phpbb/console/command/searchindex/create.php
new file mode 100644
index 0000000000..2b368a475d
--- /dev/null
+++ b/phpBB/phpbb/console/command/searchindex/create.php
@@ -0,0 +1,165 @@
+
+ * @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\console\command\searchindex;
+
+use phpbb\console\command\command;
+use phpbb\language\language;
+use phpbb\log\log;
+use phpbb\posting\post_helper;
+use phpbb\search\exception\no_search_backend_found_exception;
+use phpbb\search\search_backend_factory;
+use phpbb\search\state_helper;
+use phpbb\user;
+use Symfony\Component\Console\Command\Command as symfony_command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class create extends command
+{
+ /** @var language */
+ protected $language;
+
+ /** @var log */
+ protected $log;
+
+ /** @var post_helper */
+ protected $post_helper;
+
+ /** @var search_backend_factory */
+ protected $search_backend_factory;
+
+ /** @var state_helper */
+ protected $state_helper;
+
+ /**
+ * Construct method
+ *
+ * @param language $language
+ * @param log $log
+ * @param post_helper $post_helper
+ * @param search_backend_factory $search_backend_factory
+ * @param state_helper $state_helper
+ * @param user $user
+ */
+ public function __construct(language $language, log $log, post_helper $post_helper, search_backend_factory $search_backend_factory, state_helper $state_helper, user $user)
+ {
+ $this->language = $language;
+ $this->log = $log;
+ $this->post_helper = $post_helper;
+ $this->search_backend_factory = $search_backend_factory;
+ $this->state_helper = $state_helper;
+
+ $this->language->add_lang(array('acp/common', 'acp/search'));
+
+ parent::__construct($user);
+ }
+
+ /**
+ * Sets the command name and description
+ *
+ * @return void
+ */
+ protected function configure()
+ {
+ $this->setName('searchindex:create')
+ ->setDescription($this->language->lang('CLI_DESCRIPTION_SEARCHINDEX_CREATE'))
+ ->addArgument(
+ 'search-backend',
+ InputArgument::REQUIRED,
+ $this->language->lang('CLI_SEARCHINDEX_SEARCH_BACKEND_NAME')
+ );
+ }
+
+ /**
+ * Executes the command searchindex:create
+ *
+ * Create search index
+ *
+ * @param InputInterface $input The input stream used to get the options
+ * @param OutputInterface $output The output stream, used to print messages
+ *
+ * @return int 0 if all is well, 1 if any errors occurred
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+
+ $io->section($this->language->lang('CLI_DESCRIPTION_SEARCHINDEX_CREATE'));
+
+ $search_backend = $input->getArgument('search-backend');
+
+ try
+ {
+ $search = $this->search_backend_factory->get($search_backend);
+ $name = $search->get_name();
+ }
+ catch (no_search_backend_found_exception $e)
+ {
+ $io->error($this->language->lang('CLI_SEARCHINDEX_BACKEND_NOT_FOUND', $search_backend));
+ return symfony_command::FAILURE;
+ }
+
+ if ($this->state_helper->is_action_in_progress())
+ {
+ $io->error($this->language->lang('CLI_SEARCHINDEX_ACTION_IN_PROGRESS', $search_backend));
+ return symfony_command::FAILURE;
+ }
+
+ if (!$search->is_available())
+ {
+ $io->error($this->language->lang('CLI_SEARCHINDEX_BACKEND_NOT_AVAILABLE', $search_backend));
+ return symfony_command::FAILURE;
+ }
+
+ try
+ {
+ $progress = $this->create_progress_bar($this->post_helper->get_max_post_id(), $io, $output, true);
+ $progress->setMessage('');
+ $progress->start();
+
+ $this->state_helper->init($search->get_type(), 'create');
+
+ $counter = 0;
+ while (($status = $search->create_index($counter)) !== null)
+ {
+ $this->state_helper->update_counter($status['post_counter']);
+
+ $progress->setProgress($status['post_counter']);
+ $progress->setMessage(round($status['rows_per_second'], 2) . ' rows/s');
+ }
+
+ $progress->finish();
+
+ $io->newLine(2);
+ }
+ catch (\Exception $e)
+ {
+ $this->state_helper->clear_state(); // Unexpected error, cancel action
+ $io->error($e->getMessage()); // Show also exception message like in acp
+ $io->error($this->language->lang('CLI_SEARCHINDEX_CREATE_FAILURE', $name));
+ return symfony_command::FAILURE;
+ }
+
+ $search->tidy();
+
+ $this->state_helper->clear_state();
+
+ $this->log->add('admin', ANONYMOUS, '', 'LOG_SEARCH_INDEX_CREATED', false, array($name));
+ $io->success($this->language->lang('CLI_SEARCHINDEX_CREATE_SUCCESS', $name));
+
+ return symfony_command::SUCCESS;
+ }
+}
diff --git a/phpBB/phpbb/console/command/searchindex/delete.php b/phpBB/phpbb/console/command/searchindex/delete.php
new file mode 100644
index 0000000000..b17622d68c
--- /dev/null
+++ b/phpBB/phpbb/console/command/searchindex/delete.php
@@ -0,0 +1,165 @@
+
+ * @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\console\command\searchindex;
+
+use phpbb\console\command\command;
+use phpbb\language\language;
+use phpbb\log\log;
+use phpbb\posting\post_helper;
+use phpbb\search\exception\no_search_backend_found_exception;
+use phpbb\search\search_backend_factory;
+use phpbb\search\state_helper;
+use phpbb\user;
+use Symfony\Component\Console\Command\Command as symfony_command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class delete extends command
+{
+ /** @var language */
+ protected $language;
+
+ /** @var log */
+ protected $log;
+
+ /** @var post_helper */
+ protected $post_helper;
+
+ /** @var search_backend_factory */
+ protected $search_backend_factory;
+
+ /** @var state_helper */
+ protected $state_helper;
+
+ /**
+ * Construct method
+ *
+ * @param language $language
+ * @param log $log
+ * @param post_helper $post_helper
+ * @param search_backend_factory $search_backend_factory
+ * @param state_helper $state_helper
+ * @param user $user
+ */
+ public function __construct(language $language, log $log, post_helper $post_helper, search_backend_factory $search_backend_factory, state_helper $state_helper, user $user)
+ {
+ $this->language = $language;
+ $this->log = $log;
+ $this->post_helper = $post_helper;
+ $this->search_backend_factory = $search_backend_factory;
+ $this->state_helper = $state_helper;
+
+ $this->language->add_lang(array('acp/common', 'acp/search'));
+
+ parent::__construct($user);
+ }
+
+ /**
+ * Sets the command name and description
+ *
+ * @return void
+ */
+ protected function configure()
+ {
+ $this->setName('searchindex:delete')
+ ->setDescription($this->language->lang('CLI_DESCRIPTION_SEARCHINDEX_DELETE'))
+ ->addArgument(
+ 'search-backend',
+ InputArgument::REQUIRED,
+ $this->language->lang('CLI_SEARCHINDEX_SEARCH_BACKEND_NAME')
+ );
+ }
+
+ /**
+ * Executes the command searchindex:delete
+ *
+ * Delete search index
+ *
+ * @param InputInterface $input The input stream used to get the options
+ * @param OutputInterface $output The output stream, used to print messages
+ *
+ * @return int 0 if all is well, 1 if any errors occurred
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+
+ $io->section($this->language->lang('CLI_DESCRIPTION_SEARCHINDEX_DELETE'));
+
+ $search_backend = $input->getArgument('search-backend');
+
+ try
+ {
+ $search = $this->search_backend_factory->get($search_backend);
+ $name = $search->get_name();
+ }
+ catch (no_search_backend_found_exception $e)
+ {
+ $io->error($this->language->lang('CLI_SEARCHINDEX_BACKEND_NOT_FOUND', $search_backend));
+ return symfony_command::FAILURE;
+ }
+
+ if ($this->state_helper->is_action_in_progress())
+ {
+ $io->error($this->language->lang('CLI_SEARCHINDEX_ACTION_IN_PROGRESS', $search_backend));
+ return symfony_command::FAILURE;
+ }
+
+ if (!$search->is_available())
+ {
+ $io->error($this->language->lang('CLI_SEARCHINDEX_BACKEND_NOT_AVAILABLE', $search_backend));
+ return symfony_command::FAILURE;
+ }
+
+ try
+ {
+ $progress = $this->create_progress_bar($this->post_helper->get_max_post_id(), $io, $output, true);
+ $progress->setMessage('');
+ $progress->start();
+
+ $this->state_helper->init($search->get_type(), 'delete');
+
+ $counter = 0;
+ while (($status = $search->delete_index($counter)) !== null)
+ {
+ $this->state_helper->update_counter($status['post_counter']);
+
+ $progress->setProgress($status['post_counter']);
+ $progress->setMessage(round($status['rows_per_second'], 2) . ' rows/s');
+ }
+
+ $progress->finish();
+
+ $io->newLine(2);
+ }
+ catch (\Exception $e)
+ {
+ $this->state_helper->clear_state(); // Unexpected error, cancel action
+ $io->error($e->getMessage()); // Show also exception message like in acp
+ $io->error($this->language->lang('CLI_SEARCHINDEX_DELETE_FAILURE', $name));
+ return symfony_command::FAILURE;
+ }
+
+ $search->tidy();
+
+ $this->state_helper->clear_state();
+
+ $this->log->add('admin', ANONYMOUS, '', 'LOG_SEARCH_INDEX_REMOVED', false, array($name));
+ $io->success($this->language->lang('CLI_SEARCHINDEX_DELETE_SUCCESS', $name));
+
+ return symfony_command::SUCCESS;
+ }
+}
diff --git a/phpBB/phpbb/console/command/searchindex/list_all.php b/phpBB/phpbb/console/command/searchindex/list_all.php
new file mode 100644
index 0000000000..e88949fd2a
--- /dev/null
+++ b/phpBB/phpbb/console/command/searchindex/list_all.php
@@ -0,0 +1,96 @@
+
+ * @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\console\command\searchindex;
+
+use phpbb\config\config;
+use phpbb\console\command\command;
+use phpbb\di\service_collection;
+use phpbb\language\language;
+use phpbb\user;
+use Symfony\Component\Console\Command\Command as symfony_command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+class list_all extends command
+{
+ /** @var config */
+ protected $config;
+
+ /** @var language */
+ protected $language;
+
+ /** @var service_collection */
+ protected $search_backend_collection;
+
+ /**
+ * Construct method
+ *
+ * @param config $config
+ * @param language $language
+ * @param service_collection $search_backend_collection
+ * @param user $user
+ */
+ public function __construct(config $config, language $language, service_collection $search_backend_collection, user $user)
+ {
+ $this->config = $config;
+ $this->language = $language;
+ $this->search_backend_collection = $search_backend_collection;
+
+ parent::__construct($user);
+ }
+
+ /**
+ * Sets the command name and description
+ *
+ * @return void
+ */
+ protected function configure()
+ {
+ $this->setName('searchindex:list')
+ ->setDescription($this->language->lang('CLI_DESCRIPTION_SEARCHINDEX_LIST'));
+ }
+
+ /**
+ * Executes the command searchindex:list
+ *
+ * List all search backends.
+ *
+ * @param InputInterface $input The input stream used to get the options
+ * @param OutputInterface $output The output stream, used to print messages
+ *
+ * @return int 0 if all is well, 1 if any errors occurred
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+
+ $search_backends = [];
+ foreach ($this->search_backend_collection as $search_backend)
+ {
+ $name = $search_backend->get_type();
+ $active = ($name === $this->config['search_type']) ? '(' . $this->language->lang('ACTIVE') . ') ' : '';
+ $search_backends[] = '' . $name . ' ' . $active . $search_backend->get_name();
+
+ if ($name === $this->config['search_type'] && !$search_backend->index_created())
+ {
+ $io->error($this->language->lang('CLI_SEARCHINDEX_ACTIVE_NOT_INDEXED'));
+ }
+ }
+
+ $io->listing($search_backends);
+
+ return symfony_command::SUCCESS;
+ }
+}
diff --git a/phpBB/phpbb/cron/task/core/tidy_search.php b/phpBB/phpbb/cron/task/core/tidy_search.php
index d3f5d41bc0..a29a403627 100644
--- a/phpBB/phpbb/cron/task/core/tidy_search.php
+++ b/phpBB/phpbb/cron/task/core/tidy_search.php
@@ -15,6 +15,7 @@ namespace phpbb\cron\task\core;
use phpbb\config\config;
use phpbb\cron\task\base;
+use phpbb\di\exception\di_exception;
use phpbb\search\backend\search_backend_interface;
use phpbb\search\search_backend_factory;
@@ -88,7 +89,7 @@ class tidy_search extends base
$this->active_search = $this->search_backend_factory->get_active();
}
}
- catch (\RuntimeException $e)
+ catch (di_exception $e)
{
return false;
}
diff --git a/phpBB/phpbb/di/exception/di_exception.php b/phpBB/phpbb/di/exception/di_exception.php
new file mode 100644
index 0000000000..b4f3984b39
--- /dev/null
+++ b/phpBB/phpbb/di/exception/di_exception.php
@@ -0,0 +1,20 @@
+
+ * @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\di\exception;
+
+use phpbb\exception\runtime_exception;
+
+class di_exception extends runtime_exception
+{
+}
diff --git a/phpBB/phpbb/di/exception/multiple_service_definitions_exception.php b/phpBB/phpbb/di/exception/multiple_service_definitions_exception.php
new file mode 100644
index 0000000000..e29d47d26c
--- /dev/null
+++ b/phpBB/phpbb/di/exception/multiple_service_definitions_exception.php
@@ -0,0 +1,18 @@
+
+ * @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\di\exception;
+
+class multiple_service_definitions_exception extends di_exception
+{
+}
diff --git a/phpBB/phpbb/di/exception/service_not_found_exception.php b/phpBB/phpbb/di/exception/service_not_found_exception.php
new file mode 100644
index 0000000000..79c69c85f6
--- /dev/null
+++ b/phpBB/phpbb/di/exception/service_not_found_exception.php
@@ -0,0 +1,18 @@
+
+ * @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\di\exception;
+
+class service_not_found_exception extends di_exception
+{
+}
diff --git a/phpBB/phpbb/di/service_collection.php b/phpBB/phpbb/di/service_collection.php
index 80fc011606..5c108bbb84 100644
--- a/phpBB/phpbb/di/service_collection.php
+++ b/phpBB/phpbb/di/service_collection.php
@@ -13,6 +13,8 @@
namespace phpbb\di;
+use phpbb\di\exception\multiple_service_definitions_exception;
+use phpbb\di\exception\service_not_found_exception;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -105,7 +107,7 @@ class service_collection extends \ArrayObject
{
if ($service_id !== null)
{
- throw new \RuntimeException('More than one service definitions found for class "'.$class.'" in collection.');
+ throw new multiple_service_definitions_exception('DI_MULTIPLE_SERVICE_DEFINITIONS', [$class]);
}
$service_id = $id;
@@ -114,7 +116,7 @@ class service_collection extends \ArrayObject
if ($service_id === null)
{
- throw new \RuntimeException('No service found for class "'.$class.'" in collection.');
+ throw new service_not_found_exception('DI_SERVICE_NOT_FOUND', [$class]);
}
return $this->offsetGet($service_id);
diff --git a/phpBB/phpbb/install/module/install_data/task/create_search_index.php b/phpBB/phpbb/install/module/install_data/task/create_search_index.php
index c8f77c105f..9f80e5e784 100644
--- a/phpBB/phpbb/install/module/install_data/task/create_search_index.php
+++ b/phpBB/phpbb/install/module/install_data/task/create_search_index.php
@@ -133,6 +133,9 @@ class create_search_index extends database_task
$this->phpbb_dispatcher,
$container->get('language'),
$this->user,
+ SEARCH_RESULTS_TABLE,
+ SEARCH_WORDLIST_TABLE,
+ SEARCH_WORDMATCH_TABLE,
$this->phpbb_root_path,
$this->php_ext
);
diff --git a/phpBB/phpbb/posting/post_helper.php b/phpBB/phpbb/posting/post_helper.php
new file mode 100644
index 0000000000..2fbba9688b
--- /dev/null
+++ b/phpBB/phpbb/posting/post_helper.php
@@ -0,0 +1,43 @@
+
+ * @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\posting;
+
+use phpbb\db\driver\driver_interface;
+
+class post_helper
+{
+ /**
+ * @var driver_interface
+ */
+ protected $db;
+
+ public function __construct(driver_interface $db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * Get last post id
+ */
+ public function get_max_post_id(): int
+ {
+ $sql = 'SELECT MAX(post_id) as max_post_id
+ FROM '. POSTS_TABLE;
+ $result = $this->db->sql_query($sql);
+ $max_post_id = (int) $this->db->sql_fetchfield('max_post_id');
+ $this->db->sql_freeresult($result);
+
+ return $max_post_id;
+ }
+}
diff --git a/phpBB/phpbb/search/backend/base.php b/phpBB/phpbb/search/backend/base.php
index 4003e102ef..e195e0f4c0 100644
--- a/phpBB/phpbb/search/backend/base.php
+++ b/phpBB/phpbb/search/backend/base.php
@@ -1,15 +1,15 @@
-* @license GNU General Public License, version 2 (GPL-2.0)
-*
-* For full copyright and license information, please see
-* the docs/CREDITS.txt file.
-*
-*/
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited
+ * @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\search\backend;
@@ -19,9 +19,9 @@ use phpbb\db\driver\driver_interface;
use phpbb\user;
/**
-* optional base class for search plugins providing simple caching based on ACM
-* and functions to retrieve ignore_words and synonyms
-*/
+ * optional base class for search plugins providing simple caching based on ACM
+ * and functions to retrieve ignore_words and synonyms
+ */
abstract class base implements search_backend_interface
{
public const SEARCH_RESULT_NOT_IN_CACHE = 0;
@@ -51,20 +51,27 @@ abstract class base implements search_backend_interface
*/
protected $user;
+ /**
+ * @var string
+ */
+ protected $search_results_table;
+
/**
* Constructor.
*
- * @param service $cache
- * @param config $config
- * @param driver_interface $db
- * @param user $user
+ * @param service $cache
+ * @param config $config
+ * @param driver_interface $db
+ * @param user $user
+ * @param string $search_results_table
*/
- public function __construct(service $cache, config $config, driver_interface $db, user $user)
+ public function __construct(service $cache, config $config, driver_interface $db, user $user, string $search_results_table)
{
$this->cache = $cache;
$this->config = $config;
$this->db = $db;
$this->user = $user;
+ $this->search_results_table = $search_results_table;
}
/**
@@ -180,7 +187,7 @@ abstract class base implements search_backend_interface
if (!empty($keywords) || count($author_ary))
{
$sql = 'SELECT search_time
- FROM ' . SEARCH_RESULTS_TABLE . '
+ FROM ' . $this->search_results_table . '
WHERE search_key = \'' . $this->db->sql_escape($search_key) . '\'';
$result = $this->db->sql_query($sql);
@@ -193,7 +200,7 @@ abstract class base implements search_backend_interface
'search_authors' => ' ' . implode(' ', $author_ary) . ' '
);
- $sql = 'INSERT INTO ' . SEARCH_RESULTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary);
+ $sql = 'INSERT INTO ' . $this->search_results_table . ' ' . $this->db->sql_build_array('INSERT', $sql_ary);
$this->db->sql_query($sql);
}
$this->db->sql_freeresult($result);
@@ -253,7 +260,7 @@ abstract class base implements search_backend_interface
}
$this->cache->put('_search_results_' . $search_key, $store, $this->config['search_store_results']);
- $sql = 'UPDATE ' . SEARCH_RESULTS_TABLE . '
+ $sql = 'UPDATE ' . $this->search_results_table . '
SET search_time = ' . time() . '
WHERE search_key = \'' . $this->db->sql_escape($search_key) . '\'';
$this->db->sql_query($sql);
@@ -280,7 +287,7 @@ abstract class base implements search_backend_interface
}
$sql = 'SELECT search_key
- FROM ' . SEARCH_RESULTS_TABLE . "
+ FROM ' . $this->search_results_table . "
WHERE search_keywords LIKE '%*%' $sql_where";
$result = $this->db->sql_query($sql);
@@ -301,7 +308,7 @@ abstract class base implements search_backend_interface
}
$sql = 'SELECT search_key
- FROM ' . SEARCH_RESULTS_TABLE . "
+ FROM ' . $this->search_results_table . "
WHERE $sql_where";
$result = $this->db->sql_query($sql);
@@ -313,7 +320,7 @@ abstract class base implements search_backend_interface
}
$sql = 'DELETE
- FROM ' . SEARCH_RESULTS_TABLE . '
+ FROM ' . $this->search_results_table . '
WHERE search_time < ' . (time() - (int) $this->config['search_store_results']);
$this->db->sql_query($sql);
}
@@ -329,7 +336,7 @@ abstract class base implements search_backend_interface
$starttime = microtime(true);
$row_count = 0;
- while (still_on_time() && $post_counter <= $max_post_id)
+ while (still_on_time() && $post_counter < $max_post_id)
{
$rows = $this->get_posts_batch_after($post_counter);
@@ -346,7 +353,13 @@ abstract class base implements search_backend_interface
$this->index('post', (int) $row['post_id'], $row['post_text'], $row['post_subject'], (int) $row['poster_id'], (int) $row['forum_id']);
}
$row_count++;
- $post_counter = $row['post_id'];
+ $post_counter = (int) $row['post_id'];
+ }
+
+ // With cli process only one batch each time to be able to track progress
+ if (PHP_SAPI === 'cli')
+ {
+ break;
}
}
@@ -357,7 +370,7 @@ abstract class base implements search_backend_interface
$this->tidy();
$this->config['num_posts'] = $num_posts;
- if ($post_counter < $max_post_id)
+ if ($post_counter < $max_post_id) // If there are still post to index
{
$totaltime = microtime(true) - $starttime;
$rows_per_second = $row_count / $totaltime;
@@ -382,7 +395,8 @@ abstract class base implements search_backend_interface
$starttime = microtime(true);
$row_count = 0;
- while (still_on_time() && $post_counter <= $max_post_id)
+
+ while (still_on_time() && $post_counter < $max_post_id)
{
$rows = $this->get_posts_batch_after($post_counter);
$ids = $posters = $forum_ids = array();
@@ -399,9 +413,15 @@ abstract class base implements search_backend_interface
$this->index_remove($ids, $posters, $forum_ids);
$post_counter = $ids[count($ids) - 1];
}
+
+ // With cli process only one batch each time to be able to track progress
+ if (PHP_SAPI === 'cli')
+ {
+ break;
+ }
}
- if ($post_counter < $max_post_id)
+ if ($post_counter < $max_post_id) // If there are still post delete from index
{
$totaltime = microtime(true) - $starttime;
$rows_per_second = $row_count / $totaltime;
diff --git a/phpBB/phpbb/search/backend/fulltext_mysql.php b/phpBB/phpbb/search/backend/fulltext_mysql.php
index 3e47cb4668..7635b503fa 100644
--- a/phpBB/phpbb/search/backend/fulltext_mysql.php
+++ b/phpBB/phpbb/search/backend/fulltext_mysql.php
@@ -17,8 +17,8 @@ use phpbb\config\config;
use phpbb\db\driver\driver_interface;
use phpbb\event\dispatcher_interface;
use phpbb\language\language;
+use phpbb\search\exception\search_exception;
use phpbb\user;
-use RuntimeException;
/**
* Fulltext search for MySQL
@@ -72,19 +72,20 @@ class fulltext_mysql extends base implements search_backend_interface
* Constructor
* Creates a new \phpbb\search\backend\fulltext_mysql, which is used as a search backend
*
- * @param config $config Config object
- * @param driver_interface $db Database object
- * @param dispatcher_interface $phpbb_dispatcher Event dispatcher object
- * @param language $language
- * @param user $user User object
- * @param string $phpbb_root_path Relative path to phpBB root
- * @param string $phpEx PHP file extension
+ * @param config $config Config object
+ * @param driver_interface $db Database object
+ * @param dispatcher_interface $phpbb_dispatcher Event dispatcher object
+ * @param language $language
+ * @param user $user User object
+ * @param string $search_results_table
+ * @param string $phpbb_root_path Relative path to phpBB root
+ * @param string $phpEx PHP file extension
*/
- public function __construct(config $config, driver_interface $db, dispatcher_interface $phpbb_dispatcher, language $language, user $user, string $phpbb_root_path, string $phpEx)
+ public function __construct(config $config, driver_interface $db, dispatcher_interface $phpbb_dispatcher, language $language, user $user, string $search_results_table, string $phpbb_root_path, string $phpEx)
{
global $cache;
- parent::__construct($cache, $config, $db, $user);
+ parent::__construct($cache, $config, $db, $user, $search_results_table);
$this->phpbb_dispatcher = $phpbb_dispatcher;
$this->language = $language;
@@ -916,7 +917,7 @@ class fulltext_mysql extends base implements search_backend_interface
// Make sure we can actually use MySQL with fulltext indexes
if ($error = $this->init())
{
- throw new RuntimeException($error);
+ throw new search_exception($error);
}
if (empty($this->stats))
@@ -975,7 +976,7 @@ class fulltext_mysql extends base implements search_backend_interface
$this->db->sql_query($sql_query);
}
- $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);
+ $this->db->sql_query('TRUNCATE TABLE ' . $this->search_results_table);
return null;
}
@@ -988,7 +989,7 @@ class fulltext_mysql extends base implements search_backend_interface
// Make sure we can actually use MySQL with fulltext indexes
if ($error = $this->init())
{
- throw new RuntimeException($error);
+ throw new search_exception($error);
}
if (empty($this->stats))
@@ -1041,7 +1042,7 @@ class fulltext_mysql extends base implements search_backend_interface
$this->db->sql_query($sql_query);
}
- $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);
+ $this->db->sql_query('TRUNCATE TABLE ' . $this->search_results_table);
return null;
}
diff --git a/phpBB/phpbb/search/backend/fulltext_native.php b/phpBB/phpbb/search/backend/fulltext_native.php
index a5c9bb8202..aefc73b8cd 100644
--- a/phpBB/phpbb/search/backend/fulltext_native.php
+++ b/phpBB/phpbb/search/backend/fulltext_native.php
@@ -93,30 +93,42 @@ class fulltext_native extends base implements search_backend_interface
*/
protected $phpbb_dispatcher;
- /**
- * @var language
- */
+ /** @var language */
protected $language;
+ /** @var string */
+ protected $search_wordlist_table;
+
+ /** @var string */
+ protected $search_wordmatch_table;
+
/**
* Initialises the fulltext_native search backend with min/max word length
*
- * @param config $config Config object
- * @param driver_interface $db Database object
- * @param dispatcher_interface $phpbb_dispatcher Event dispatcher object
- * @param language $language
- * @param user $user User object
- * @param string $phpbb_root_path phpBB root path
- * @param string $phpEx PHP file extension
+ * @param config $config Config object
+ * @param driver_interface $db Database object
+ * @param dispatcher_interface $phpbb_dispatcher Event dispatcher object
+ * @param language $language
+ * @param user $user User object
+ * @param string $search_results_table
+ * @param string $search_wordlist_table
+ * @param string $search_wordmatch_table
+ * @param string $phpbb_root_path phpBB root path
+ * @param string $phpEx PHP file extension
*/
- public function __construct(config $config, driver_interface $db, dispatcher_interface $phpbb_dispatcher, language $language, user $user, string $phpbb_root_path, string $phpEx)
+ public function __construct(config $config, driver_interface $db, dispatcher_interface $phpbb_dispatcher,
+ language $language, user $user, string $search_results_table, string $search_wordlist_table,
+ string $search_wordmatch_table, string $phpbb_root_path, string $phpEx)
{
global $cache;
- parent::__construct($cache, $config, $db, $user);
+ parent::__construct($cache, $config, $db, $user, $search_results_table);
$this->phpbb_dispatcher = $phpbb_dispatcher;
$this->language = $language;
+ $this->search_wordlist_table = $search_wordlist_table;
+ $this->search_wordmatch_table = $search_wordmatch_table;
+
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $phpEx;
@@ -335,7 +347,7 @@ class fulltext_native extends base implements search_backend_interface
if (count($exact_words))
{
$sql = 'SELECT word_id, word_text, word_common
- FROM ' . SEARCH_WORDLIST_TABLE . '
+ FROM ' . $this->search_wordlist_table . '
WHERE ' . $this->db->sql_in_set('word_text', $exact_words) . '
ORDER BY word_count ASC';
$result = $this->db->sql_query($sql);
@@ -607,8 +619,8 @@ class fulltext_native extends base implements search_backend_interface
$sql_array = array(
'SELECT' => ($type == 'posts') ? 'DISTINCT p.post_id' : 'DISTINCT p.topic_id',
'FROM' => array(
- SEARCH_WORDMATCH_TABLE => array(),
- SEARCH_WORDLIST_TABLE => array(),
+ $this->search_wordmatch_table => array(),
+ $this->search_wordlist_table => array(),
),
'LEFT_JOIN' => array(array(
'FROM' => array(POSTS_TABLE => 'p'),
@@ -660,7 +672,7 @@ class fulltext_native extends base implements search_backend_interface
if (is_string($id))
{
$sql_array['LEFT_JOIN'][] = array(
- 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num),
+ 'FROM' => array($this->search_wordlist_table => 'w' . $w_num),
'ON' => "w$w_num.word_text LIKE $id"
);
$word_ids[] = "w$w_num.word_id";
@@ -680,7 +692,7 @@ class fulltext_native extends base implements search_backend_interface
}
else if (is_string($subquery))
{
- $sql_array['FROM'][SEARCH_WORDLIST_TABLE][] = 'w' . $w_num;
+ $sql_array['FROM'][$this->search_wordlist_table][] = 'w' . $w_num;
$sql_where[] = "w$w_num.word_text LIKE $subquery";
$sql_where[] = "m$m_num.word_id = w$w_num.word_id";
@@ -693,7 +705,7 @@ class fulltext_native extends base implements search_backend_interface
$sql_where[] = "m$m_num.word_id = $subquery";
}
- $sql_array['FROM'][SEARCH_WORDMATCH_TABLE][] = 'm' . $m_num;
+ $sql_array['FROM'][$this->search_wordmatch_table][] = 'm' . $m_num;
if ($title_match)
{
@@ -712,7 +724,7 @@ class fulltext_native extends base implements search_backend_interface
if (is_string($subquery))
{
$sql_array['LEFT_JOIN'][] = array(
- 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num),
+ 'FROM' => array($this->search_wordlist_table => 'w' . $w_num),
'ON' => "w$w_num.word_text LIKE $subquery"
);
@@ -726,7 +738,7 @@ class fulltext_native extends base implements search_backend_interface
if (count($this->must_not_contain_ids))
{
$sql_array['LEFT_JOIN'][] = array(
- 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num),
+ 'FROM' => array($this->search_wordmatch_table => 'm' . $m_num),
'ON' => $this->db->sql_in_set("m$m_num.word_id", $this->must_not_contain_ids) . (($title_match) ? " AND m$m_num.$title_match" : '') . " AND m$m_num.post_id = m0.post_id"
);
@@ -742,7 +754,7 @@ class fulltext_native extends base implements search_backend_interface
if (is_string($id))
{
$sql_array['LEFT_JOIN'][] = array(
- 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num),
+ 'FROM' => array($this->search_wordlist_table => 'w' . $w_num),
'ON' => "w$w_num.word_text LIKE $id"
);
$id = "w$w_num.word_id";
@@ -752,7 +764,7 @@ class fulltext_native extends base implements search_backend_interface
}
$sql_array['LEFT_JOIN'][] = array(
- 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num),
+ 'FROM' => array($this->search_wordmatch_table => 'm' . $m_num),
'ON' => "m$m_num.word_id = $id AND m$m_num.post_id = m0.post_id" . (($title_match) ? " AND m$m_num.$title_match" : '')
);
$is_null_joins[] = "m$m_num.word_id IS NULL";
@@ -1310,7 +1322,7 @@ class fulltext_native extends base implements search_backend_interface
$words['del']['title'] = array();
$sql = 'SELECT w.word_id, w.word_text, m.title_match
- FROM ' . SEARCH_WORDLIST_TABLE . ' w, ' . SEARCH_WORDMATCH_TABLE . " m
+ FROM ' . $this->search_wordlist_table . ' w, ' . $this->search_wordmatch_table . " m
WHERE m.post_id = $post_id
AND w.word_id = m.word_id";
$result = $this->db->sql_query($sql);
@@ -1379,7 +1391,7 @@ class fulltext_native extends base implements search_backend_interface
if (count($unique_add_words))
{
$sql = 'SELECT word_id, word_text
- FROM ' . SEARCH_WORDLIST_TABLE . '
+ FROM ' . $this->search_wordlist_table . '
WHERE ' . $this->db->sql_in_set('word_text', $unique_add_words);
$result = $this->db->sql_query($sql);
@@ -1401,7 +1413,7 @@ class fulltext_native extends base implements search_backend_interface
$sql_ary[] = array('word_text' => (string) $word, 'word_count' => 0);
}
$this->db->sql_return_on_error(true);
- $this->db->sql_multi_insert(SEARCH_WORDLIST_TABLE, $sql_ary);
+ $this->db->sql_multi_insert($this->search_wordlist_table, $sql_ary);
$this->db->sql_return_on_error(false);
}
unset($new_words, $sql_ary);
@@ -1424,13 +1436,13 @@ class fulltext_native extends base implements search_backend_interface
$sql_in[] = $cur_words[$word_in][$word];
}
- $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . '
+ $sql = 'DELETE FROM ' . $this->search_wordmatch_table . '
WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . '
AND post_id = ' . intval($post_id) . "
AND title_match = $title_match";
$this->db->sql_query($sql);
- $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '
+ $sql = 'UPDATE ' . $this->search_wordlist_table . '
SET word_count = word_count - 1
WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . '
AND word_count > 0';
@@ -1447,13 +1459,13 @@ class fulltext_native extends base implements search_backend_interface
if (count($word_ary))
{
- $sql = 'INSERT INTO ' . SEARCH_WORDMATCH_TABLE . ' (post_id, word_id, title_match)
+ $sql = 'INSERT INTO ' . $this->search_wordmatch_table . ' (post_id, word_id, title_match)
SELECT ' . (int) $post_id . ', word_id, ' . (int) $title_match . '
- FROM ' . SEARCH_WORDLIST_TABLE . '
+ FROM ' . $this->search_wordlist_table . '
WHERE ' . $this->db->sql_in_set('word_text', $word_ary);
$this->db->sql_query($sql);
- $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '
+ $sql = 'UPDATE ' . $this->search_wordlist_table . '
SET word_count = word_count + 1
WHERE ' . $this->db->sql_in_set('word_text', $word_ary);
$this->db->sql_query($sql);
@@ -1479,7 +1491,7 @@ class fulltext_native extends base implements search_backend_interface
if (count($post_ids))
{
$sql = 'SELECT w.word_id, w.word_text, m.title_match
- FROM ' . SEARCH_WORDMATCH_TABLE . ' m, ' . SEARCH_WORDLIST_TABLE . ' w
+ FROM ' . $this->search_wordmatch_table . ' m, ' . $this->search_wordlist_table . ' w
WHERE ' . $this->db->sql_in_set('m.post_id', $post_ids) . '
AND w.word_id = m.word_id';
$result = $this->db->sql_query($sql);
@@ -1501,7 +1513,7 @@ class fulltext_native extends base implements search_backend_interface
if (count($title_word_ids))
{
- $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '
+ $sql = 'UPDATE ' . $this->search_wordlist_table . '
SET word_count = word_count - 1
WHERE ' . $this->db->sql_in_set('word_id', $title_word_ids) . '
AND word_count > 0';
@@ -1510,7 +1522,7 @@ class fulltext_native extends base implements search_backend_interface
if (count($message_word_ids))
{
- $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '
+ $sql = 'UPDATE ' . $this->search_wordlist_table . '
SET word_count = word_count - 1
WHERE ' . $this->db->sql_in_set('word_id', $message_word_ids) . '
AND word_count > 0';
@@ -1520,7 +1532,7 @@ class fulltext_native extends base implements search_backend_interface
unset($title_word_ids);
unset($message_word_ids);
- $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . '
+ $sql = 'DELETE FROM ' . $this->search_wordmatch_table . '
WHERE ' . $this->db->sql_in_set('post_id', $post_ids);
$this->db->sql_query($sql);
}
@@ -1549,7 +1561,7 @@ class fulltext_native extends base implements search_backend_interface
$common_threshold = ((double) $this->config['fulltext_native_common_thres']) / 100.0;
// First, get the IDs of common words
$sql = 'SELECT word_id, word_text
- FROM ' . SEARCH_WORDLIST_TABLE . '
+ FROM ' . $this->search_wordlist_table . '
WHERE word_count > ' . floor($this->config['num_posts'] * $common_threshold) . '
OR word_common = 1';
$result = $this->db->sql_query($sql);
@@ -1565,7 +1577,7 @@ class fulltext_native extends base implements search_backend_interface
if (count($sql_in))
{
// Flag the words
- $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . '
+ $sql = 'UPDATE ' . $this->search_wordlist_table . '
SET word_common = 1
WHERE ' . $this->db->sql_in_set('word_id', $sql_in);
$this->db->sql_query($sql);
@@ -1575,7 +1587,7 @@ class fulltext_native extends base implements search_backend_interface
$this->config->set('search_last_gc', time(), false);
// Delete the matches
- $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . '
+ $sql = 'DELETE FROM ' . $this->search_wordmatch_table . '
WHERE ' . $this->db->sql_in_set('word_id', $sql_in);
$this->db->sql_query($sql);
}
@@ -1603,15 +1615,15 @@ class fulltext_native extends base implements search_backend_interface
switch ($this->db->get_sql_layer())
{
case 'sqlite3':
- $sql_queries[] = 'DELETE FROM ' . SEARCH_WORDLIST_TABLE;
- $sql_queries[] = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE;
- $sql_queries[] = 'DELETE FROM ' . SEARCH_RESULTS_TABLE;
+ $sql_queries[] = 'DELETE FROM ' . $this->search_wordlist_table;
+ $sql_queries[] = 'DELETE FROM ' . $this->search_wordmatch_table;
+ $sql_queries[] = 'DELETE FROM ' . $this->search_results_table;
break;
default:
- $sql_queries[] = 'TRUNCATE TABLE ' . SEARCH_WORDLIST_TABLE;
- $sql_queries[] = 'TRUNCATE TABLE ' . SEARCH_WORDMATCH_TABLE;
- $sql_queries[] = 'TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE;
+ $sql_queries[] = 'TRUNCATE TABLE ' . $this->search_wordlist_table;
+ $sql_queries[] = 'TRUNCATE TABLE ' . $this->search_wordmatch_table;
+ $sql_queries[] = 'TRUNCATE TABLE ' . $this->search_results_table;
break;
}
@@ -1672,8 +1684,8 @@ class fulltext_native extends base implements search_backend_interface
*/
protected function get_stats()
{
- $this->stats['total_words'] = $this->db->get_estimated_row_count(SEARCH_WORDLIST_TABLE);
- $this->stats['total_matches'] = $this->db->get_estimated_row_count(SEARCH_WORDMATCH_TABLE);
+ $this->stats['total_words'] = $this->db->get_estimated_row_count($this->search_wordlist_table);
+ $this->stats['total_matches'] = $this->db->get_estimated_row_count($this->search_wordmatch_table);
}
/**
diff --git a/phpBB/phpbb/search/backend/fulltext_postgres.php b/phpBB/phpbb/search/backend/fulltext_postgres.php
index bd2c24224c..efd6ecc148 100644
--- a/phpBB/phpbb/search/backend/fulltext_postgres.php
+++ b/phpBB/phpbb/search/backend/fulltext_postgres.php
@@ -17,8 +17,8 @@ use phpbb\config\config;
use phpbb\db\driver\driver_interface;
use phpbb\event\dispatcher_interface;
use phpbb\language\language;
+use phpbb\search\exception\search_exception;
use phpbb\user;
-use RuntimeException;
/**
* Fulltext search for PostgreSQL
@@ -84,19 +84,20 @@ class fulltext_postgres extends base implements search_backend_interface
* Constructor
* Creates a new \phpbb\search\backend\fulltext_postgres, which is used as a search backend
*
- * @param config $config Config object
- * @param driver_interface $db Database object
- * @param dispatcher_interface $phpbb_dispatcher Event dispatcher object
- * @param language $language
- * @param user $user User object
- * @param string $phpbb_root_path Relative path to phpBB root
- * @param string $phpEx PHP file extension
+ * @param config $config Config object
+ * @param driver_interface $db Database object
+ * @param dispatcher_interface $phpbb_dispatcher Event dispatcher object
+ * @param language $language
+ * @param user $user User object
+ * @param string $search_results_table
+ * @param string $phpbb_root_path Relative path to phpBB root
+ * @param string $phpEx PHP file extension
*/
- public function __construct(config $config, driver_interface $db, dispatcher_interface $phpbb_dispatcher, language $language, user $user, string $phpbb_root_path, string $phpEx)
+ public function __construct(config $config, driver_interface $db, dispatcher_interface $phpbb_dispatcher, language $language, user $user, string $search_results_table, string $phpbb_root_path, string $phpEx)
{
global $cache;
- parent::__construct($cache, $config, $db, $user);
+ parent::__construct($cache, $config, $db, $user, $search_results_table);
$this->phpbb_dispatcher = $phpbb_dispatcher;
$this->language = $language;
@@ -871,7 +872,7 @@ class fulltext_postgres extends base implements search_backend_interface
// Make sure we can actually use PostgreSQL with fulltext indexes
if ($error = $this->init())
{
- throw new RuntimeException($error);
+ throw new search_exception($error);
}
if (empty($this->stats))
@@ -917,7 +918,7 @@ class fulltext_postgres extends base implements search_backend_interface
$this->db->sql_query($sql_query);
}
- $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);
+ $this->db->sql_query('TRUNCATE TABLE ' . $this->search_results_table);
return null;
}
@@ -930,7 +931,7 @@ class fulltext_postgres extends base implements search_backend_interface
// Make sure we can actually use PostgreSQL with fulltext indexes
if ($error = $this->init())
{
- throw new RuntimeException($error);
+ throw new search_exception($error);
}
if (empty($this->stats))
@@ -976,7 +977,7 @@ class fulltext_postgres extends base implements search_backend_interface
$this->db->sql_query($sql_query);
}
- $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);
+ $this->db->sql_query('TRUNCATE TABLE ' . $this->search_results_table);
return null;
}
diff --git a/phpBB/phpbb/search/exception/action_in_progress_exception.php b/phpBB/phpbb/search/exception/action_in_progress_exception.php
new file mode 100644
index 0000000000..9187f2bff6
--- /dev/null
+++ b/phpBB/phpbb/search/exception/action_in_progress_exception.php
@@ -0,0 +1,18 @@
+
+ * @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\search\exception;
+
+class action_in_progress_exception extends search_exception
+{
+}
diff --git a/phpBB/phpbb/search/exception/no_action_in_progress_exception.php b/phpBB/phpbb/search/exception/no_action_in_progress_exception.php
new file mode 100644
index 0000000000..2f8a9e28c5
--- /dev/null
+++ b/phpBB/phpbb/search/exception/no_action_in_progress_exception.php
@@ -0,0 +1,18 @@
+
+ * @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\search\exception;
+
+class no_action_in_progress_exception extends search_exception
+{
+}
diff --git a/phpBB/phpbb/search/exception/no_search_backend_found_exception.php b/phpBB/phpbb/search/exception/no_search_backend_found_exception.php
new file mode 100644
index 0000000000..752db7ce17
--- /dev/null
+++ b/phpBB/phpbb/search/exception/no_search_backend_found_exception.php
@@ -0,0 +1,18 @@
+
+ * @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\search\exception;
+
+class no_search_backend_found_exception extends search_exception
+{
+}
diff --git a/phpBB/phpbb/search/exception/search_exception.php b/phpBB/phpbb/search/exception/search_exception.php
new file mode 100644
index 0000000000..c1b904d5f4
--- /dev/null
+++ b/phpBB/phpbb/search/exception/search_exception.php
@@ -0,0 +1,20 @@
+
+ * @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\search\exception;
+
+use phpbb\exception\runtime_exception;
+
+class search_exception extends runtime_exception
+{
+}
diff --git a/phpBB/phpbb/search/search_backend_factory.php b/phpBB/phpbb/search/search_backend_factory.php
index eac31885ed..019d3f6804 100644
--- a/phpBB/phpbb/search/search_backend_factory.php
+++ b/phpBB/phpbb/search/search_backend_factory.php
@@ -14,8 +14,10 @@
namespace phpbb\search;
use phpbb\config\config;
+use phpbb\di\exception\service_not_found_exception;
use phpbb\di\service_collection;
use phpbb\search\backend\search_backend_interface;
+use phpbb\search\exception\no_search_backend_found_exception;
class search_backend_factory
{
@@ -46,16 +48,29 @@ class search_backend_factory
*
* @param string $class
*
+ * @throws no_search_backend_found_exception
+ *
* @return search_backend_interface
*/
public function get(string $class): search_backend_interface
{
- return $this->search_backends->get_by_class($class);
+ try
+ {
+ $search = $this->search_backends->get_by_class($class);
+ }
+ catch (service_not_found_exception $e)
+ {
+ throw new no_search_backend_found_exception('SEARCH_BACKEND_NOT_FOUND', [], $e);
+ }
+
+ return $search;
}
/**
* Obtains active search backend
*
+ * @throws no_search_backend_found_exception
+ *
* @return search_backend_interface
*/
public function get_active(): search_backend_interface
diff --git a/phpBB/phpbb/search/state_helper.php b/phpBB/phpbb/search/state_helper.php
new file mode 100644
index 0000000000..b024c3dfe4
--- /dev/null
+++ b/phpBB/phpbb/search/state_helper.php
@@ -0,0 +1,181 @@
+
+ * @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\search;
+
+use phpbb\config\config;
+use phpbb\search\exception\action_in_progress_exception;
+use phpbb\search\exception\no_action_in_progress_exception;
+use phpbb\search\exception\no_search_backend_found_exception;
+use phpbb\search\exception\search_exception;
+
+class state_helper
+{
+ protected const STATE_SEARCH_TYPE = 0;
+ protected const STATE_ACTION = 1;
+ protected const STATE_POST_COUNTER = 2;
+
+ /** @var config */
+ protected $config;
+
+ /** @var search_backend_factory */
+ protected $search_backend_factory;
+
+ /**
+ * Constructor.
+ *
+ * @param config $config
+ * @param search_backend_factory $search_backend_factory
+ */
+ public function __construct(config $config, search_backend_factory $search_backend_factory)
+ {
+ $this->config = $config;
+ $this->search_backend_factory = $search_backend_factory;
+ }
+
+ /**
+ * Returns whether there is an action in progress
+ *
+ * @return bool
+ */
+ public function is_action_in_progress(): bool
+ {
+ return !empty($this->config['search_indexing_state']);
+ }
+
+ /**
+ * @return string The class name of the search backend
+ *
+ * @throws no_action_in_progress_exception If there is no action in progress
+ */
+ public function type(): string
+ {
+ $state = $this->load_state();
+
+ return $state[self::STATE_SEARCH_TYPE];
+ }
+
+ /**
+ * @return string The action that is being executed, can be 'create' or 'delete'
+ *
+ * @throws no_action_in_progress_exception If there is no action in progress
+ */
+ public function action(): string
+ {
+ $state = $this->load_state();
+
+ return $state[self::STATE_ACTION];
+ }
+
+ /**
+ * @return int The post counter
+ *
+ * @throws no_action_in_progress_exception If there is no action in progress
+ */
+ public function counter(): int
+ {
+ $state = $this->load_state();
+
+ return $state[self::STATE_POST_COUNTER];
+ }
+
+ /**
+ * Start a indexing or delete process.
+ *
+ * @param string $search_type
+ * @param string $action
+ *
+ * @throws action_in_progress_exception If there is an action in progress
+ * @throws no_search_backend_found_exception If search backend don't exist
+ * @throws search_exception If action isn't valid
+ */
+ public function init(string $search_type, string $action): void
+ {
+ // It's not possible to start a new process when there is one already running
+ if ($this->is_action_in_progress())
+ {
+ throw new action_in_progress_exception();
+ }
+
+ // Make sure the search type exists (if not, throw an exception)
+ $this->search_backend_factory->get($search_type);
+
+ // Make sure the action is correct (just in case)
+ if (!in_array($action, ['create', 'delete']))
+ {
+ throw new search_exception('INVALID_ACTION');
+ }
+
+ $state = [
+ self::STATE_SEARCH_TYPE => $search_type,
+ self::STATE_ACTION => $action,
+ self::STATE_POST_COUNTER => 0
+ ];
+
+ $this->save_state($state);
+ }
+
+ /**
+ * Set the post counter
+ *
+ * @param int $counter
+ *
+ * @throws no_action_in_progress_exception If there is no action in progress
+ */
+ public function update_counter(int $counter): void
+ {
+ $state = $this->load_state();
+
+ $state[self::STATE_POST_COUNTER] = $counter;
+
+ $this->save_state($state);
+ }
+
+ /**
+ * Clear the state
+ */
+ public function clear_state(): void
+ {
+ $this->save_state();
+ }
+
+ /**
+ * Load the state from the database
+ *
+ * @return array
+ *
+ * @throws no_action_in_progress_exception If there is no action in progress
+ */
+ private function load_state(): array
+ {
+ // Is not possible to execute an action over state if it's empty
+ if (!$this->is_action_in_progress())
+ {
+ throw new no_action_in_progress_exception();
+ }
+
+ return explode(',', $this->config['search_indexing_state']);
+ }
+
+ /**
+ * Save the specified state in the database
+ *
+ * @param array $state
+ */
+ private function save_state(array $state = []): void
+ {
+ ksort($state);
+
+ $this->config->set('search_indexing_state', implode(',', $state));
+ }
+}
diff --git a/phpBB/search.php b/phpBB/search.php
index f786964620..68325bda7f 100644
--- a/phpBB/search.php
+++ b/phpBB/search.php
@@ -299,16 +299,9 @@ if ($keywords || $author || $author_id || $search_id || $submit)
$search_backend_factory = $phpbb_container->get('search.backend_factory');
$search = $search_backend_factory->get_active();
}
- catch (RuntimeException $e)
+ catch (\phpbb\search\exception\no_search_backend_found_exception $e)
{
- if (strpos($e->getMessage(), 'No service found') === 0)
- {
- trigger_error('NO_SUCH_SEARCH_MODULE');
- }
- else
- {
- throw $e;
- }
+ trigger_error('NO_SUCH_SEARCH_MODULE');
}
// let the search module split up the keywords
diff --git a/tests/console/searchindex/create_test.php b/tests/console/searchindex/create_test.php
new file mode 100644
index 0000000000..8d4958442c
--- /dev/null
+++ b/tests/console/searchindex/create_test.php
@@ -0,0 +1,92 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+use phpbb\console\command\searchindex\create;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Tester\CommandTester;
+
+require_once __DIR__ . '/phpbb_console_searchindex_base.php';
+require_once __DIR__ . '/../../mock/search_backend_mock.php';
+
+class phpbb_console_searchindex_create_test extends phpbb_console_searchindex_base
+{
+ public function get_command_tester()
+ {
+ $application = new Application();
+ $application->add(new create(
+ $this->language,
+ $this->log,
+ $this->post_helper,
+ $this->search_backend_factory,
+ $this->state_helper,
+ $this->user
+ ));
+
+ $command = $application->find('searchindex:create');
+
+ return new CommandTester($command);
+ }
+
+ public function test_create()
+ {
+ $command_tester = $this->get_command_tester();
+
+ $command_tester->execute([
+ 'search-backend' => 'search_backend_mock',
+ ]);
+
+ $this->assertEquals(Command::SUCCESS, $command_tester->getStatusCode());
+ $this->assertStringContainsString('CLI_SEARCHINDEX_CREATE_SUCCESS', $command_tester->getDisplay());
+ }
+
+ public function test_create_when_search_backend_dont_exist()
+ {
+ $command_tester = $this->get_command_tester();
+
+ $command_tester->execute([
+ 'search-backend' => 'missing',
+ ]);
+
+ $this->assertEquals(Command::FAILURE, $command_tester->getStatusCode());
+ $this->assertStringContainsString('CLI_SEARCHINDEX_BACKEND_NOT_FOUND', $command_tester->getDisplay());
+ }
+
+ public function test_create_when_action_in_progress()
+ {
+ $this->config['search_indexing_state'] = ['not', 'empty'];
+
+ $command_tester = $this->get_command_tester();
+
+ $command_tester->execute([
+ 'search-backend' => 'search_backend_mock',
+ ]);
+
+ $this->assertEquals(Command::FAILURE, $command_tester->getStatusCode());
+ $this->assertStringContainsString('CLI_SEARCHINDEX_ACTION_IN_PROGRESS', $command_tester->getDisplay());
+
+ $this->config['search_indexing_state'] = [];
+ }
+
+ public function test_create_when_search_backend_not_available()
+ {
+ $command_tester = $this->get_command_tester();
+
+ $command_tester->execute([
+ 'search-backend' => 'search_backend_mock_not_available',
+ ]);
+
+ $this->assertEquals(Command::FAILURE, $command_tester->getStatusCode());
+ $this->assertStringContainsString('CLI_SEARCHINDEX_BACKEND_NOT_AVAILABLE', $command_tester->getDisplay());
+ }
+}
diff --git a/tests/console/searchindex/delete_test.php b/tests/console/searchindex/delete_test.php
new file mode 100644
index 0000000000..ac7762effd
--- /dev/null
+++ b/tests/console/searchindex/delete_test.php
@@ -0,0 +1,92 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+use phpbb\console\command\searchindex\delete;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Tester\CommandTester;
+
+require_once __DIR__ . '/phpbb_console_searchindex_base.php';
+require_once __DIR__ . '/../../mock/search_backend_mock.php';
+
+class phpbb_console_searchindex_delete_test extends phpbb_console_searchindex_base
+{
+ public function get_command_tester()
+ {
+ $application = new Application();
+ $application->add(new delete(
+ $this->language,
+ $this->log,
+ $this->post_helper,
+ $this->search_backend_factory,
+ $this->state_helper,
+ $this->user
+ ));
+
+ $command = $application->find('searchindex:delete');
+
+ return new CommandTester($command);
+ }
+
+ public function test_delete()
+ {
+ $command_tester = $this->get_command_tester();
+
+ $command_tester->execute([
+ 'search-backend' => 'search_backend_mock',
+ ]);
+
+ $this->assertEquals(Command::SUCCESS, $command_tester->getStatusCode());
+ $this->assertStringContainsString('CLI_SEARCHINDEX_DELETE_SUCCESS', $command_tester->getDisplay());
+ }
+
+ public function test_delete_when_search_backend_dont_exist()
+ {
+ $command_tester = $this->get_command_tester();
+
+ $command_tester->execute([
+ 'search-backend' => 'missing',
+ ]);
+
+ $this->assertEquals(Command::FAILURE, $command_tester->getStatusCode());
+ $this->assertStringContainsString('CLI_SEARCHINDEX_BACKEND_NOT_FOUND', $command_tester->getDisplay());
+ }
+
+ public function test_delete_when_action_in_progress()
+ {
+ $this->config['search_indexing_state'] = ['not', 'empty'];
+
+ $command_tester = $this->get_command_tester();
+
+ $command_tester->execute([
+ 'search-backend' => 'search_backend_mock',
+ ]);
+
+ $this->assertEquals(Command::FAILURE, $command_tester->getStatusCode());
+ $this->assertStringContainsString('CLI_SEARCHINDEX_ACTION_IN_PROGRESS', $command_tester->getDisplay());
+
+ $this->config['search_indexing_state'] = [];
+ }
+
+ public function test_delete_when_search_backend_not_available()
+ {
+ $command_tester = $this->get_command_tester();
+
+ $command_tester->execute([
+ 'search-backend' => 'search_backend_mock_not_available',
+ ]);
+
+ $this->assertEquals(Command::FAILURE, $command_tester->getStatusCode());
+ $this->assertStringContainsString('CLI_SEARCHINDEX_BACKEND_NOT_AVAILABLE', $command_tester->getDisplay());
+ }
+}
diff --git a/tests/console/searchindex/list_all_test.php b/tests/console/searchindex/list_all_test.php
new file mode 100644
index 0000000000..7968032644
--- /dev/null
+++ b/tests/console/searchindex/list_all_test.php
@@ -0,0 +1,49 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+use phpbb\console\command\searchindex\list_all;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Tester\CommandTester;
+
+require_once __DIR__ . '/phpbb_console_searchindex_base.php';
+require_once __DIR__ . '/../../mock/search_backend_mock.php';
+
+class phpbb_console_searchindex_list_all_test extends phpbb_console_searchindex_base
+{
+ public function get_command_tester()
+ {
+ $application = new Application();
+ $application->add(new list_all(
+ $this->config,
+ $this->language,
+ $this->search_backend_collection,
+ $this->user
+ ));
+
+ $command = $application->find('searchindex:list');
+
+ return new CommandTester($command);
+ }
+
+ public function test_list()
+ {
+ $command_tester = $this->get_command_tester();
+
+ $command_tester->execute([]);
+
+ $this->assertEquals(Command::SUCCESS, $command_tester->getStatusCode());
+ $this->assertStringContainsString('Mock search backend', $command_tester->getDisplay());
+ $this->assertStringContainsString('ACTIVE', $command_tester->getDisplay());
+ }
+}
diff --git a/tests/console/searchindex/phpbb_console_searchindex_base.php b/tests/console/searchindex/phpbb_console_searchindex_base.php
new file mode 100644
index 0000000000..209d12281a
--- /dev/null
+++ b/tests/console/searchindex/phpbb_console_searchindex_base.php
@@ -0,0 +1,93 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+use phpbb\config\config;
+use phpbb\di\service_collection;
+use phpbb\language\language;
+use phpbb\log\log;
+use phpbb\posting\post_helper;
+use phpbb\search\search_backend_factory;
+use phpbb\search\state_helper;
+use phpbb\user;
+
+require_once __DIR__ . '/../../mock/search_backend_mock.php';
+require_once __DIR__ . '/../../mock/search_backend_mock_not_available.php';
+
+class phpbb_console_searchindex_base extends phpbb_test_case
+{
+ /** @var config */
+ protected $config;
+
+ /** @var language */
+ protected $language;
+
+ /** @var log */
+ protected $log;
+
+ /** @var post_helper */
+ protected $post_helper;
+
+ /** @var user */
+ protected $user;
+
+ /** @var search_backend_factory */
+ protected $search_backend_factory;
+
+ /** @var state_helper */
+ protected $state_helper;
+
+ /** @var service_collection */
+ protected $search_backend_collection;
+
+ protected function setUp(): void
+ {
+ global $phpbb_root_path, $phpEx;
+
+ $this->config = new \phpbb\config\config([
+ 'search_indexing_state' => [],
+ 'search_type' => 'search_backend_mock'
+ ]);
+
+ $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
+ $this->language = new \phpbb\language\language($lang_loader);
+
+ $this->log = $this->createMock('\phpbb\log\log');
+
+ $this->post_helper = $this->createMock('\phpbb\posting\post_helper');
+ $this->post_helper
+ ->method('get_max_post_id')
+ ->willReturn(1000);
+
+ $this->user = $this->createMock('\phpbb\user');
+
+ $phpbb_container = new phpbb_mock_container_builder();
+ $this->search_backend_collection = new \phpbb\di\service_collection($phpbb_container);
+
+ $search_backend_mock = new search_backend_mock();
+ $this->search_backend_collection->add('search_backend_mock');
+ $this->search_backend_collection->add_service_class('search_backend_mock', 'search_backend_mock');
+ $phpbb_container->set('search_backend_mock', $search_backend_mock);
+
+ $search_backend_mock_not_available = new search_backend_mock_not_available();
+ $this->search_backend_collection->add('search_backend_mock_not_available');
+ $this->search_backend_collection->add_service_class('search_backend_mock_not_available', 'search_backend_mock_not_available');
+ $phpbb_container->set('search_backend_mock_not_available', $search_backend_mock_not_available);
+
+ $this->search_backend_factory = new search_backend_factory($this->config, $this->search_backend_collection);
+
+ $this->state_helper = new state_helper($this->config, $this->search_backend_factory);
+
+ parent::setUp();
+ }
+}
+
diff --git a/tests/di/service_collection_test.php b/tests/di/service_collection_test.php
index fd0e13e48b..7fc3e94e41 100644
--- a/tests/di/service_collection_test.php
+++ b/tests/di/service_collection_test.php
@@ -57,7 +57,7 @@ class phpbb_service_collection_test extends \phpbb_test_case
public function test_get_by_class_many_services_exception()
{
$this->expectException('RuntimeException');
- $this->expectExceptionMessage('More than one service definitions found for class "bar_class" in collection.');
+ $this->expectExceptionMessage('DI_MULTIPLE_SERVICE_DEFINITIONS');
$this->service_collection->get_by_class('bar_class');
}
@@ -65,7 +65,7 @@ class phpbb_service_collection_test extends \phpbb_test_case
public function test_get_by_class_no_service_exception()
{
$this->expectException('RuntimeException');
- $this->expectExceptionMessage('No service found for class "baz_class" in collection.');
+ $this->expectExceptionMessage('DI_SERVICE_NOT_FOUND');
$this->service_collection->get_by_class('baz_class');
}
diff --git a/tests/mock/search_backend_mock.php b/tests/mock/search_backend_mock.php
new file mode 100644
index 0000000000..84dd26385e
--- /dev/null
+++ b/tests/mock/search_backend_mock.php
@@ -0,0 +1,117 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+use phpbb\search\backend\search_backend_interface;
+
+class search_backend_mock implements search_backend_interface
+{
+ public $index_created = false;
+
+ public function get_name(): string
+ {
+ return 'Mock search backend';
+ }
+
+ public function is_available(): bool
+ {
+ return true;
+ }
+
+ public function init()
+ {
+ return false;
+ }
+
+ public function get_search_query(): string
+ {
+ return '';
+ }
+
+ public function get_common_words(): array
+ {
+ return [];
+ }
+
+ public function get_word_length()
+ {
+ return false;
+ }
+
+ public function split_keywords(string &$keywords, string $terms): bool
+ {
+ return false;
+ }
+
+ public function keyword_search(string $type, string $fields, string $terms, array $sort_by_sql, string $sort_key, string $sort_dir, string $sort_days, array $ex_fid_ary, string $post_visibility, int $topic_id, array $author_ary, string $author_name, array &$id_ary, int &$start, int $per_page)
+ {
+ return 0;
+ }
+
+ public function author_search(string $type, bool $firstpost_only, array $sort_by_sql, string $sort_key, string $sort_dir, string $sort_days, array $ex_fid_ary, string $post_visibility, int $topic_id, array $author_ary, string $author_name, array &$id_ary, int &$start, int $per_page)
+ {
+ return 0;
+ }
+
+ public function supports_phrase_search(): bool
+ {
+ return false;
+ }
+
+ public function index(string $mode, int $post_id, string &$message, string &$subject, int $poster_id, int $forum_id)
+ {
+ // Nothing
+ }
+
+ public function index_remove(array $post_ids, array $author_ids, array $forum_ids): void
+ {
+ // Nothing
+ }
+
+ public function tidy(): void
+ {
+ // Nothing
+ }
+
+ public function create_index(int &$post_counter = 0): ?array
+ {
+ $this->index_created = true;
+ return null;
+ }
+
+ public function delete_index(int &$post_counter = 0): ?array
+ {
+ $this->index_created = true;
+ return null;
+ }
+
+ public function index_created(): bool
+ {
+ return $this->index_created;
+ }
+
+ public function index_stats()
+ {
+ return [];
+ }
+
+ public function get_acp_options(): array
+ {
+ return [];
+ }
+
+ public function get_type(): string
+ {
+ return static::class;
+ }
+}
+
diff --git a/tests/mock/search_backend_mock_not_available.php b/tests/mock/search_backend_mock_not_available.php
new file mode 100644
index 0000000000..5b299d334f
--- /dev/null
+++ b/tests/mock/search_backend_mock_not_available.php
@@ -0,0 +1,25 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+class search_backend_mock_not_available extends search_backend_mock
+{
+ public function get_name(): string
+ {
+ return 'Mock unavailable search backend';
+ }
+
+ public function is_available(): bool
+ {
+ return false;
+ }
+}
diff --git a/tests/search/mysql_test.php b/tests/search/mysql_test.php
index 1e822b92af..fe7326bb3e 100644
--- a/tests/search/mysql_test.php
+++ b/tests/search/mysql_test.php
@@ -40,6 +40,6 @@ class phpbb_search_mysql_test extends phpbb_search_common_test_case
$this->db = $this->new_dbal();
$phpbb_dispatcher = new phpbb_mock_event_dispatcher();
$class = self::get_search_wrapper('\phpbb\search\backend\fulltext_mysql');
- $this->search = new $class($config, $this->db, $phpbb_dispatcher, $language, $user, $phpbb_root_path, $phpEx);
+ $this->search = new $class($config, $this->db, $phpbb_dispatcher, $language, $user, SEARCH_RESULTS_TABLE, $phpbb_root_path, $phpEx);
}
}
diff --git a/tests/search/native_test.php b/tests/search/native_test.php
index 6d8a03f4aa..90702655d0 100644
--- a/tests/search/native_test.php
+++ b/tests/search/native_test.php
@@ -38,7 +38,7 @@ class phpbb_search_native_test extends phpbb_search_test_case
$class = self::get_search_wrapper('\phpbb\search\backend\fulltext_native');
$config['fulltext_native_min_chars'] = 2;
$config['fulltext_native_max_chars'] = 14;
- $this->search = new $class($config, $this->db, $phpbb_dispatcher, $language, $user, $phpbb_root_path, $phpEx);
+ $this->search = new $class($config, $this->db, $phpbb_dispatcher, $language, $user, SEARCH_RESULTS_TABLE, SEARCH_WORDLIST_TABLE, SEARCH_WORDMATCH_TABLE, $phpbb_root_path, $phpEx);
}
public function keywords()
diff --git a/tests/search/postgres_test.php b/tests/search/postgres_test.php
index 545ffafd50..18c5a46f81 100644
--- a/tests/search/postgres_test.php
+++ b/tests/search/postgres_test.php
@@ -40,6 +40,6 @@ class phpbb_search_postgres_test extends phpbb_search_common_test_case
$this->db = $this->new_dbal();
$phpbb_dispatcher = new phpbb_mock_event_dispatcher();
$class = self::get_search_wrapper('\phpbb\search\backend\fulltext_postgres');
- $this->search = new $class($config, $this->db, $phpbb_dispatcher, $language, $user, $phpbb_root_path, $phpEx);
+ $this->search = new $class($config, $this->db, $phpbb_dispatcher, $language, $user, SEARCH_RESULTS_TABLE, $phpbb_root_path, $phpEx);
}
}