From c85215e9553c62e5a94661ba793e0af0b50ceb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mudr=C3=A1k?= <david@moodle.com> Date: Mon, 11 Jan 2021 20:57:05 +0100 Subject: [PATCH] MDL-70608 lang: Install langpacks asynchronously via ad hoc task When multiple language packs are selected for installation, perform the installation asynchronously in the background via ad hoc task. --- admin/tool/langimport/classes/controller.php | 21 +++- .../classes/task/install_langpacks.php | 109 ++++++++++++++++++ admin/tool/langimport/index.php | 20 +++- .../langimport/lang/en/tool_langimport.php | 4 + .../tests/behat/manage_langpacks.feature | 28 ++++- 5 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 admin/tool/langimport/classes/task/install_langpacks.php diff --git a/admin/tool/langimport/classes/controller.php b/admin/tool/langimport/classes/controller.php index b6520f6b818..82b6c32d1d4 100644 --- a/admin/tool/langimport/classes/controller.php +++ b/admin/tool/langimport/classes/controller.php @@ -82,7 +82,7 @@ class controller { $a->url = $this->installer->lang_pack_url($langcode); $a->dest = $CFG->dataroot.'/lang'; $this->errors[] = get_string('remotedownloaderror', 'error', $a); - throw new \moodle_exception('remotedownloaderror', 'error', $a); + throw new \moodle_exception('remotedownloaderror', 'error', '', $a); break; case \lang_installer::RESULT_INSTALLED: $updatedpacks++; @@ -221,4 +221,23 @@ class controller { public function lang_pack_url($langcode = '') { return $this->installer->lang_pack_url($langcode); } + + /** + * Schedule installation of the given language packs asynchronously via ad hoc task. + * + * @param string|array $langs array of langcodes or individual langcodes + */ + public function schedule_languagepacks_installation($langs): void { + global $USER; + + $task = new \tool_langimport\task\install_langpacks(); + $task->set_userid($USER->id); + $task->set_custom_data([ + 'langs' => $langs, + ]); + + \core\task\manager::queue_adhoc_task($task, true); + + $this->info[] = get_string('installscheduled', 'tool_langimport'); + } } diff --git a/admin/tool/langimport/classes/task/install_langpacks.php b/admin/tool/langimport/classes/task/install_langpacks.php new file mode 100644 index 00000000000..eb8d78f74c5 --- /dev/null +++ b/admin/tool/langimport/classes/task/install_langpacks.php @@ -0,0 +1,109 @@ +<?php +// This file is part of Moodle - https://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <https://www.gnu.org/licenses/>. + +namespace tool_langimport\task; + +/** + * Ad hoc task to install one or more language packs. + * + * @package tool_langimport + * @category task + * @copyright 2021 David Mudrák <david@moodle.com> + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class install_langpacks extends \core\task\adhoc_task { + + /** + * Execute the ad hoc task. + */ + public function execute(): void { + + $data = $this->get_custom_data(); + + if (empty($data->langs)) { + mtrace('No language packs to install'); + } + + get_string_manager()->reset_caches(); + + $controller = new \tool_langimport\controller(); + + \core_php_time_limit::raise(); + + try { + $controller->install_languagepacks($data->langs); + $this->notify_user_success($controller); + + } catch (\Throwable $e) { + $this->notify_user_error($e->getMessage()); + + } finally { + get_string_manager()->reset_caches(); + } + } + + /** + * Notify user that the task finished successfully. + * + * @param \tool_langimport\controller $controller + */ + protected function notify_user_success(\tool_langimport\controller $controller): void { + + $message = new \core\message\message(); + + $message->component = 'moodle'; + $message->name = 'notices'; + $message->userfrom = \core_user::get_noreply_user(); + $message->userto = $this->get_userid(); + $message->notification = 1; + $message->contexturl = (new \moodle_url('/admin/tool/langimport/index.php'))->out(false); + $message->contexturlname = get_string('pluginname', 'tool_langimport'); + + $message->subject = get_string('installfinished', 'tool_langimport'); + $message->fullmessage = '* ' . implode(PHP_EOL . '* ', $controller->info); + $message->fullmessageformat = FORMAT_MARKDOWN; + $message->fullmessagehtml = markdown_to_html($message->fullmessage); + $message->smallmessage = get_string('installfinished', 'tool_langimport'); + + message_send($message); + } + + /** + * Notify user that the task failed. + * + * @param string $error The error text + */ + protected function notify_user_error(string $error): void { + + $message = new \core\message\message(); + + $message->component = 'moodle'; + $message->name = 'notices'; + $message->userfrom = \core_user::get_noreply_user(); + $message->userto = $this->get_userid(); + $message->notification = 1; + $message->contexturl = (new \moodle_url('/admin/tool/langimport/index.php'))->out(false); + $message->contexturlname = get_string('pluginname', 'tool_langimport'); + + $message->subject = get_string('installfailed', 'tool_langimport'); + $message->fullmessage = $error; + $message->fullmessageformat = FORMAT_PLAIN; + $message->fullmessagehtml = text_to_html($message->fullmessage); + $message->smallmessage = get_string('installfailed', 'tool_langimport'); + + message_send($message); + } +} diff --git a/admin/tool/langimport/index.php b/admin/tool/langimport/index.php index 1180abc09a8..da57a7cbe90 100644 --- a/admin/tool/langimport/index.php +++ b/admin/tool/langimport/index.php @@ -66,8 +66,15 @@ get_string_manager()->reset_caches(); $controller = new tool_langimport\controller(); if (($mode == INSTALLATION_OF_SELECTED_LANG) and confirm_sesskey() and !empty($pack)) { - core_php_time_limit::raise(); - $controller->install_languagepacks($pack); + if (is_array($pack) && count($pack) > 1) { + // Installing multiple languages can take a while - perform it asynchronously in the background. + $controller->schedule_languagepacks_installation($pack); + + } else { + // Single language pack to be installed synchronously. It should be reasonably quick and can be used for debugging, too. + core_php_time_limit::raise(); + $controller->install_languagepacks($pack); + } } if ($mode == DELETION_OF_SELECTED_LANG and (!empty($uninstalllang) or !empty($confirmtounistall))) { @@ -159,6 +166,15 @@ if ($controller->errors) { \core\notification::error($info); } +// Inform about pending language packs installations. +foreach (\core\task\manager::get_adhoc_tasks('\tool_langimport\task\install_langpacks') as $installtask) { + $installtaskdata = $installtask->get_custom_data(); + + if (!empty($installtaskdata->langs)) { + \core\notification::info(get_string('installpending', 'tool_langimport', implode(', ', $installtaskdata->langs))); + } +} + if ($missingparents) { foreach ($missingparents as $l => $parent) { $a = new stdClass(); diff --git a/admin/tool/langimport/lang/en/tool_langimport.php b/admin/tool/langimport/lang/en/tool_langimport.php index 7fddab115f6..d9ca31005ff 100644 --- a/admin/tool/langimport/lang/en/tool_langimport.php +++ b/admin/tool/langimport/lang/en/tool_langimport.php @@ -25,6 +25,10 @@ $string['downloadnotavailable'] = 'Unable to connect to the download server. It is not possible to install or update the language packs automatically. Please download the appropriate ZIP file(s) from <a href="{$a->src}">{$a->src}</a> and unzip them manually to your data directory <code>{$a->dest}</code>'; $string['install'] = 'Install selected language pack(s)'; +$string['installfailed'] = 'Language packs installation failed!'; +$string['installfinished'] = 'Language packs installation finished.'; +$string['installpending'] = 'The following language packs will be installed soon: {$a}.'; +$string['installscheduled'] = 'Language packs scheduled for installation.'; $string['installedlangs'] = 'Installed language packs'; $string['langimport'] = 'Language import utility'; $string['langimportdisabled'] = 'Language import feature has been disabled. You have to update your language packs manually at the file-system level. Do not forget to purge string caches after you do so.'; diff --git a/admin/tool/langimport/tests/behat/manage_langpacks.feature b/admin/tool/langimport/tests/behat/manage_langpacks.feature index 6af3c4c8d32..1569dd6a42d 100644 --- a/admin/tool/langimport/tests/behat/manage_langpacks.feature +++ b/admin/tool/langimport/tests/behat/manage_langpacks.feature @@ -18,7 +18,22 @@ Feature: Manage language packs And the "Installed language packs" select box should contain "en_ar" And I navigate to "Reports > Live logs" in site administration And I should see "The language pack 'en_ar' was installed." - And I log out + + Scenario: Install multiple language packs asynchronously in the background + Given I log in as "admin" + And I navigate to "Language > Language packs" in site administration + And I set the field "Available language packs" to "en_us,en_us_k12" + When I press "Install selected language pack(s)" + Then I should see "Language packs scheduled for installation." + And I should see "The following language packs will be installed soon: en_us, en_us_k12." + And I trigger cron + And I am on homepage + And I navigate to "Language > Language packs" in site administration + And the "Installed language packs" select box should contain "en_us" + And the "Installed language packs" select box should contain "en_us_k12" + And I navigate to "Reports > Live logs" in site administration + And I should see "The language pack 'en_us' was installed." + And I should see "The language pack 'en_us_k12' was installed." @javascript Scenario: Search for available language pack @@ -39,7 +54,14 @@ Feature: Manage language packs And I should see "Language pack update completed" And I navigate to "Reports > Live logs" in site administration And I should see "The language pack 'en_ar' was updated." - And I log out + + Scenario: Inform admin that there are multiple installed languages and updating them all can take too long + Given outdated langpack 'en_ar' is installed + And outdated langpack 'en_us' is installed + And outdated langpack 'en_us_k12' is installed + When I log in as "admin" + And I navigate to "Language > Language packs" in site administration + Then I should see "Updating all installed language packs by clicking the button can take a long time and lead to timeouts." Scenario: Try to uninstall language pack Given I log in as "admin" @@ -55,7 +77,6 @@ Feature: Manage language packs And I navigate to "Reports > Live logs" in site administration And I should see "The language pack 'en_ar' was removed." And I should see "Language pack uninstalled" - And I log out Scenario: Try to uninstall English language pack Given I log in as "admin" @@ -65,4 +86,3 @@ Feature: Manage language packs Then I should see "The English language pack cannot be uninstalled." And I navigate to "Reports > Live logs" in site administration And I should not see "Language pack uninstalled" - And I log out