diff --git a/config/controllers.yml b/config/controllers.yml index 3b6bde8bb..860496ac5 100644 --- a/config/controllers.yml +++ b/config/controllers.yml @@ -411,6 +411,12 @@ services: - [set_tool, ["@phpbb.titania.manage.tool.composer.rebuild_repo"]] parent: phpbb.titania.controller.manage.tool + phpbb.titania.controller.manage.demo.install_all: + class: phpbb\titania\controller\manage\tool\demo\install_all + calls: + - [set_tool, ["@phpbb.titania.manage.tool.demo.install_all"]] + parent: phpbb.titania.controller.manage.tool + phpbb.titania.controller.manage.search.reindex: shared: false calls: diff --git a/config/routes/manage.yml b/config/routes/manage.yml index d4764178e..5f31cc794 100644 --- a/config/routes/manage.yml +++ b/config/routes/manage.yml @@ -90,6 +90,10 @@ phpbb.titania.manage.contrib.update_release_topics: path: /administration/contrib/update_release_topics defaults: { _controller: phpbb.titania.controller.manage.contrib.update_release_topics:handle } +phpbb.titania.manage.demo.install_all: + path: /administration/demo/install_all + defaults: { _controller: phpbb.titania.controller.manage.demo.install_all:handle } + phpbb.titania.manage.search.reindex: path: /administration/search/reindex defaults: { _controller: phpbb.titania.controller.manage.search.reindex:handle } diff --git a/config/services.yml b/config/services.yml index b0343ebc5..e6b0f95d4 100644 --- a/config/services.yml +++ b/config/services.yml @@ -264,6 +264,14 @@ services: - '%tables.topics%' - '%tables.users%' + phpbb.titania.manage.tool.demo.install_all: + class: phpbb\titania\manage\tool\demo\install_all + arguments: + - '@dbal.conn' + - '@user' + - '@phpbb.titania.config' + - '@phpbb.titania.style.demo.manager' + phpbb.titania.manage.tool.search.reindex: class: phpbb\titania\manage\tool\search\reindex shared: false diff --git a/contribution/style/demo/manager.php b/contribution/style/demo/manager.php index 2933ee645..b21a11f73 100644 --- a/contribution/style/demo/manager.php +++ b/contribution/style/demo/manager.php @@ -47,6 +47,9 @@ class manager /** @var \titania_contribution */ protected $contrib; + /** @var array|null Map of style_path to style_active on the demo board */ + protected $installed_styles; + /** * Constructor. * @@ -76,10 +79,24 @@ public function __construct(\phpbb\user $user, $container, $ext_config, $php_ext */ public function configure($branch, $contrib, $package) { - $this->branch = $branch; $this->contrib = $contrib; $this->package = $package; + return $this->set_branch($branch); + } + + /** + * Set the branch and connect to its demo board. + * + * @param int $branch + * + * @return bool Returns false if no usable demo board is configured for the branch. + */ + public function set_branch($branch) + { + $this->branch = $branch; + $this->installed_styles = null; + if (empty($this->ext_config->demo_style_path[$this->branch])) { return false; @@ -99,6 +116,36 @@ public function configure($branch, $contrib, $package) return $this->db_connect(); } + /** + * Check whether a style is present and active on the demo board. + * + * The board's styles table and the style directory are both checked, so a + * wiped database or a wiped filesystem each count as not installed. + * + * @param string $dir_name Style directory name, as built by get_style_dir_name(). + * + * @return bool + */ + public function is_style_installed($dir_name) + { + if ($this->installed_styles === null) + { + $this->installed_styles = array(); + + $sql = 'SELECT style_path, style_active + FROM ' . $this->table_prefix . 'styles'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $this->installed_styles[$row['style_path']] = (bool) $row['style_active']; + } + $this->db->sql_freeresult($result); + } + + return !empty($this->installed_styles[$dir_name]) && is_dir($this->board_root_path . 'styles/' . $dir_name . '/'); + } + /** * Get result. * @@ -265,16 +312,28 @@ protected function db_connect() return true; } + /** + * Build the style directory name used on the demo board. + * + * @param string $contrib_name_clean + * @param int $contrib_id + * @return string + */ + public function get_style_dir_name($contrib_name_clean, $contrib_id) + { + return $contrib_name_clean . '_' . $contrib_id; + } + /** * Get style directory name. * * @param bool $name_only If false, returns the full path to the directory, * otherwise just the name. - * @retun string + * @return string */ public function get_style_dir($name_only = false) { - $dir_name = $this->contrib->contrib_name_clean . '_' . $this->contrib->contrib_id; + $dir_name = $this->get_style_dir_name($this->contrib->contrib_name_clean, $this->contrib->contrib_id); if ($name_only) { diff --git a/controller/manage/administration.php b/controller/manage/administration.php index 2febe8ca8..005a292f8 100644 --- a/controller/manage/administration.php +++ b/controller/manage/administration.php @@ -64,11 +64,25 @@ public function list_tools() ), ); + foreach ($this->ext_config->demo_style_path as $branch => $path) + { + if (empty($path)) + { + continue; + } + $tools['INSTALL_DEMO_STYLES_' . $branch] = array( + 'route' => 'phpbb.titania.manage.demo.install_all', + 'params' => array('branch' => $branch), + 'title' => $this->user->lang('INSTALL_DEMO_STYLES', $this->get_branch_name($branch)), + 'ajax' => true, + ); + } + foreach ($tools as $title => $info) { $this->template->assign_block_vars('tools', array( - 'L_TITLE' => $this->user->lang($title), - 'U_TITLE' => $this->helper->route($info['route']), + 'L_TITLE' => isset($info['title']) ? $info['title'] : $this->user->lang($title), + 'U_TITLE' => $this->helper->route($info['route'], isset($info['params']) ? $info['params'] : array()), 'S_AJAX' => $info['ajax'], )); } @@ -78,6 +92,23 @@ public function list_tools() return $this->helper->render('manage/administration.html', 'ADMINISTRATION'); } + /** + * Get the display name of a phpBB branch. + * + * @param int $branch Branch in the form of 33 for 3.3. + * @return string Returns the configured branch name, like phpBB 3.3.x. + */ + protected function get_branch_name($branch) + { + $versions = $this->ext_config->phpbb_versions; + + if (isset($versions[$branch]['name'])) + { + return $versions[$branch]['name']; + } + return 'phpBB ' . implode('.', str_split((string) $branch)); + } + /** * Check user's authorization. * diff --git a/controller/manage/tool/demo/install_all.php b/controller/manage/tool/demo/install_all.php new file mode 100644 index 000000000..d8355d3c1 --- /dev/null +++ b/controller/manage/tool/demo/install_all.php @@ -0,0 +1,84 @@ + + * @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\titania\controller\manage\tool\demo; + +use phpbb\exception\http_exception; +use phpbb\titania\controller\manage\tool\tool; + +class install_all extends tool +{ + /** + * {@inheritDoc} + * + * Tells the user how many styles are missing from the demo board before + * asking for confirmation. + * + * @throws http_exception If the branch has no usable demo board. + */ + protected function confirm_action() + { + $submit = $this->request->is_set('submit'); + $hash = $this->request->variable('hash', ''); + + if (confirm_box(true) || ($submit && check_link_hash($hash, 'titania_manage'))) + { + return true; + } + $pending = $this->tool + ->set_branch($this->request->variable('branch', 0)) + ->count_pending(); + + if ($pending === false) + { + throw new http_exception(200, 'INSTALL_DEMO_STYLES_NOT_CONFIGURED'); + } + confirm_box(false, $this->user->lang('INSTALL_DEMO_STYLES_CONFIRM', $pending)); + + return false; + } + + /** + * {@inheritDoc} + */ + protected function get_next_params(array $result) + { + return array_merge( + parent::get_next_params($result), + array( + 'branch' => $result['branch'], + 'installed' => $result['installed'], + 'already' => $result['already'], + 'skipped' => $result['skipped'], + 'failed' => $result['failed'], + ) + ); + } + + /** + * {@inheritDoc} + */ + protected function run() + { + return $this->tool + ->set_branch($this->request->variable('branch', 0)) + ->set_start($this->request->variable('start', 0)) + ->run( + $this->request->variable('installed', 0), + $this->request->variable('already', 0), + $this->request->variable('skipped', 0), + $this->request->variable('failed', 0) + ) + ; + } +} diff --git a/language/en/manage_tools.php b/language/en/manage_tools.php index 873b4cc0f..3cd93da98 100644 --- a/language/en/manage_tools.php +++ b/language/en/manage_tools.php @@ -59,6 +59,12 @@ 'SECTION_STATUS' => 'part %d of %d', 'TRUNCATING_SEARCH' => 'Truncating Search', + 'INSTALL_DEMO_STYLES' => 'Install all approved styles on the %s demo board', + 'INSTALL_DEMO_STYLES_COMPLETE' => 'Styles installed on the demo board: %1$d. Already installed and skipped: %2$d. Skipped with no package for this branch: %3$d. Failed to install: %4$d.', + 'INSTALL_DEMO_STYLES_CONFIRM' => '%d approved styles are not installed on this demo board. Are you sure you want to run this tool?', + 'INSTALL_DEMO_STYLES_NOT_CONFIGURED' => 'No style demo board is configured for this branch.', + 'INSTALL_DEMO_STYLES_PROGRESS' => '%1$d styles completed of %2$d. Please wait…', + 'PLEASE_WAIT' => 'Please wait...', 'REBUILD_COMPOSER_REPO' => 'Rebuild Composer repository', diff --git a/manage/tool/demo/install_all.php b/manage/tool/demo/install_all.php new file mode 100644 index 000000000..5700b99f4 --- /dev/null +++ b/manage/tool/demo/install_all.php @@ -0,0 +1,306 @@ + + * @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\titania\manage\tool\demo; + +use phpbb\db\driver\driver_interface as db_driver_interface; +use phpbb\titania\config\config as ext_config; +use phpbb\titania\contribution\style\demo\manager as demo_manager; +use phpbb\titania\ext; +use phpbb\titania\manage\tool\base; +use phpbb\user; + +class install_all extends base +{ + /** @var db_driver_interface */ + protected $db; + + /** @var user */ + protected $user; + + /** @var ext_config */ + protected $ext_config; + + /** @var demo_manager */ + protected $demo_manager; + + /** @var string */ + protected $contribs_table; + + /** @var string */ + protected $revisions_table; + + /** @var string */ + protected $revisions_phpbb_table; + + /** @var int */ + protected $branch = 0; + + /** @var bool|null Whether the branch has a usable demo board */ + protected $board_ready; + + /** @var int Each item extracts a package and calls the demo board hook, so keep batches small. */ + protected $limit = 5; + + /** + * Constructor + * + * @param db_driver_interface $db + * @param user $user + * @param ext_config $ext_config + * @param demo_manager $demo_manager + */ + public function __construct(db_driver_interface $db, user $user, ext_config $ext_config, demo_manager $demo_manager) + { + $this->db = $db; + $this->user = $user; + $this->ext_config = $ext_config; + $this->demo_manager = $demo_manager; + $table_prefix = $ext_config->__get('table_prefix'); + $this->contribs_table = $table_prefix . 'contribs'; + $this->revisions_table = $table_prefix . 'revisions'; + $this->revisions_phpbb_table = $table_prefix . 'revisions_phpbb'; + } + + /** + * Set the phpBB branch to install style demos for. + * + * @param int $branch + * @return $this + */ + public function set_branch($branch) + { + $this->branch = (int) $branch; + $this->board_ready = null; + + return $this; + } + + /** + * Check that the branch has a usable demo board and connect to it. + * + * @return bool + */ + protected function board_ready() + { + if ($this->board_ready === null) + { + $this->board_ready = $this->demo_manager->set_branch($this->branch); + } + + return $this->board_ready; + } + + /** + * Count the styles the tool would install on the demo board. + * + * A style counts when it is not installed on the demo board and its latest + * validated revision for the branch is approved and submitted, mirroring + * what get_download() will select when the tool runs. + * + * @return int|bool Returns false if the branch has no usable demo board. + */ + public function count_pending() + { + if (!$this->board_ready()) + { + return false; + } + $sql = 'SELECT c.contrib_id, c.contrib_name_clean, MAX(v.revision_id) AS revision_id + FROM ' . $this->contribs_table . ' c + JOIN ' . $this->revisions_phpbb_table . ' v + ON (v.contrib_id = c.contrib_id + AND v.phpbb_version_branch = ' . $this->branch . ' + AND v.revision_validated = 1) + WHERE c.contrib_type = ' . ext::TITANIA_TYPE_STYLE . ' + AND c.contrib_status = ' . ext::TITANIA_CONTRIB_APPROVED . ' + AND c.contrib_visible = 1 + GROUP BY c.contrib_id, c.contrib_name_clean'; + $result = $this->db->sql_query($sql); + $candidates = array(); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!$this->is_installed($row)) + { + $candidates[(int) $row['revision_id']] = true; + } + } + $this->db->sql_freeresult($result); + + if (empty($candidates)) + { + return 0; + } + $sql = 'SELECT COUNT(revision_id) AS cnt + FROM ' . $this->revisions_table . ' + WHERE ' . $this->db->sql_in_set('revision_id', array_keys($candidates)) . ' + AND revision_status = ' . ext::TITANIA_REVISION_APPROVED . ' + AND revision_submitted = 1'; + $result = $this->db->sql_query($sql); + $pending = (int) $this->db->sql_fetchfield('cnt'); + $this->db->sql_freeresult($result); + + return $pending; + } + + /** + * Run the tool. + * + * @param int $installed Styles installed by previous batches + * @param int $already Styles already installed, skipped by previous batches + * @param int $skipped Styles without a package, skipped by previous batches + * @param int $failed Styles failed in previous batches + * @return array + */ + public function run($installed = 0, $already = 0, $skipped = 0, $failed = 0) + { + if (!$this->board_ready()) + { + return array_merge( + $this->get_result('INSTALL_DEMO_STYLES_NOT_CONFIGURED', 0, false), + array( + 'branch' => $this->branch, + 'installed' => 0, + 'already' => 0, + 'skipped' => 0, + 'failed' => 0, + ) + ); + } + + $total = $this->get_total(); + + foreach ($this->get_batch() as $row) + { + if ($this->is_installed($row)) + { + $already++; + continue; + } + $contrib = new \titania_contribution; + + if ($contrib->load((int) $row['contrib_id']) === false) + { + $failed++; + continue; + } + $contrib->get_download(); + + if (empty($contrib->download[$this->branch])) + { + $skipped++; + continue; + } + $revision = new \titania_revision($contrib); + $revision->__set_array($contrib->download[$this->branch]); + + if ($contrib->type->install_demo($contrib, $revision) === '') + { + $failed++; + } + else + { + $installed++; + } + } + + $next_batch = $this->start + $this->limit; + + if ($next_batch >= $total) + { + $result = $this->get_result( + $this->user->lang('INSTALL_DEMO_STYLES_COMPLETE', $installed, $already, $skipped, $failed), + $total, + false + ); + } + else + { + $result = $this->get_result( + $this->user->lang('INSTALL_DEMO_STYLES_PROGRESS', $next_batch, $total), + $total, + $next_batch + ); + } + + return array_merge($result, array( + 'branch' => $this->branch, + 'installed' => $installed, + 'already' => $already, + 'skipped' => $skipped, + 'failed' => $failed, + )); + } + + /** + * Check whether a contribution's style is already installed on the demo board. + * + * @param array $row Contribution row with contrib_name_clean and contrib_id. + * @return bool + */ + protected function is_installed($row) + { + return $this->demo_manager->is_style_installed( + $this->demo_manager->get_style_dir_name($row['contrib_name_clean'], $row['contrib_id']) + ); + } + + /** + * {@inheritDoc} + */ + public function get_total() + { + if ($this->total === null) + { + $sql = 'SELECT COUNT(contrib_id) AS cnt + FROM ' . $this->contribs_table . ' + WHERE contrib_type = ' . ext::TITANIA_TYPE_STYLE . ' + AND contrib_status = ' . ext::TITANIA_CONTRIB_APPROVED . ' + AND contrib_visible = 1'; + $result = $this->db->sql_query($sql); + $this->total = (int) $this->db->sql_fetchfield('cnt'); + $this->db->sql_freeresult($result); + } + + return $this->total; + } + + /** + * Get the batch of contributions to process. + * + * @return array + */ + protected function get_batch() + { + $sql = 'SELECT contrib_id, contrib_name_clean + FROM ' . $this->contribs_table . ' + WHERE contrib_type = ' . ext::TITANIA_TYPE_STYLE . ' + AND contrib_status = ' . ext::TITANIA_CONTRIB_APPROVED . ' + AND contrib_visible = 1 + ORDER BY contrib_id'; + $result = $this->db->sql_query_limit($sql, $this->limit, $this->start); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * {@inheritDoc} + */ + public function get_route() + { + return 'phpbb.titania.manage.demo.install_all'; + } +}