diff --git a/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php b/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php index 430b28db8f451..5d75b416ba537 100644 --- a/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php +++ b/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php @@ -891,10 +891,6 @@ public function cleanUp() $app = Factory::getApplication(); - // Trigger event after joomla update. - // @TODO: The event dispatched twice, here and at the end of current method. One of it should be removed. - $app->getDispatcher()->dispatch('onJoomlaAfterUpdate', new AfterJoomlaUpdateEvent('onJoomlaAfterUpdate')); - // Remove the update package. $tempdir = $app->get('tmp_path'); diff --git a/libraries/src/Application/AdministratorApplication.php b/libraries/src/Application/AdministratorApplication.php index 78a793c6c7896..bff7e62aceb20 100644 --- a/libraries/src/Application/AdministratorApplication.php +++ b/libraries/src/Application/AdministratorApplication.php @@ -189,7 +189,17 @@ protected function doExecute() * $this->input->getCmd('option'); or $this->input->getCmd('view'); * ex: due of the sef urls */ - $this->checkUserRequireReset('com_users', 'user', 'edit', 'com_users/user.edit,com_users/user.save,com_users/user.apply,com_login/logout'); + $this->checkUserRequiresReset('com_users', 'user', 'edit', [ + ['option' => 'com_users', 'task' => 'user.edit'], + ['option' => 'com_users', 'task' => 'user.save'], + ['option' => 'com_users', 'task' => 'user.apply'], + ['option' => 'com_users', 'view' => 'captivate'], + ['option' => 'com_login', 'task' => 'logout'], + ['option' => 'com_users', 'view' => 'methods'], + ['option' => 'com_users', 'view' => 'method'], + ['option' => 'com_users', 'task' => 'method.add'], + ['option' => 'com_users', 'task' => 'method.save'], + ]); // Dispatch the application $this->dispatch(); @@ -359,6 +369,21 @@ public function login($credentials, $options = []) $this->bootComponent('messages')->getMVCFactory() ->createModel('Messages', 'Administrator')->purge($this->getIdentity() ? $this->getIdentity()->id : 0); + + if ($result) { + // Check if the user is required to reset their password + $this->checkUserRequiresReset('com_users', 'user', 'edit', [ + ['option' => 'com_users', 'task' => 'user.edit'], + ['option' => 'com_users', 'task' => 'user.save'], + ['option' => 'com_users', 'task' => 'user.apply'], + ['option' => 'com_users', 'view' => 'captivate'], + ['option' => 'com_login', 'task' => 'logout'], + ['option' => 'com_users', 'view' => 'methods'], + ['option' => 'com_users', 'view' => 'method'], + ['option' => 'com_users', 'task' => 'method.add'], + ['option' => 'com_users', 'task' => 'method.save'], + ]); + } } return $result; diff --git a/libraries/src/Application/CMSApplication.php b/libraries/src/Application/CMSApplication.php index ef3cafa8ec7ad..2e9f38e5f20f4 100644 --- a/libraries/src/Application/CMSApplication.php +++ b/libraries/src/Application/CMSApplication.php @@ -365,8 +365,46 @@ public function execute() * @return void * * @throws \Exception + * @deprecated __DEPLOY_VERSION__ will be removed in 7.0 + * Use $this->checkUserRequiresReset() instead. */ protected function checkUserRequireReset($option, $view, $layout, $tasks) + { + $name = $this->getName(); + $urls = []; + + if ($this->get($name . '_reset_password_override', 0)) { + $tasks = $this->get($name . '_reset_password_tasks', ''); + } + + // Check task + if (!empty($tasks)) { + $tasks = explode(',', $tasks); + + foreach ($tasks as $task) { + [$option, $t] = explode('/', $task); + $urls[] = ['option' => $option, 'task' => $t]; + } + } + + $this->checkUserRequiresReset($option, $view, $layout, $urls); + } + + /** + * Check if the user is required to reset their password. + * + * If the user is required to reset their password will be redirected to the page that manage the password reset. + * + * @param string $option The option that manage the password reset + * @param string $view The view that manage the password reset + * @param string $layout The layout of the view that manage the password reset + * @param array $urls Multi-dimensional array of permitted urls. Ex: [['option' => 'com_users', 'view' => 'profile'],...] + * + * @return void + * + * @throws \Exception + */ + protected function checkUserRequiresReset($option, $view, $layout, $urls = []) { if ($this->getIdentity()->requireReset) { $redirect = false; @@ -383,23 +421,34 @@ protected function checkUserRequireReset($option, $view, $layout, $tasks) $option = $this->get($name . '_reset_password_option', ''); $view = $this->get($name . '_reset_password_view', ''); $layout = $this->get($name . '_reset_password_layout', ''); - $tasks = $this->get($name . '_reset_password_tasks', ''); + $urls = $this->get($name . '_reset_password_urls', $urls); } - $task = $this->input->getCmd('task', ''); + // If the current URL matches an entry in $urls, we do not redirect + if (\count($urls)) { + $found = false; + + foreach ($urls as $url) { + $found2 = false; - // Check task or option/view/layout - if (!empty($task)) { - $tasks = explode(',', $tasks); + foreach ($url as $key => $value) { + if ($this->input->getCmd($key) !== $value) { + $found2 = false; + break; + } + + $found2 = true; + } - // Check full task version "option/task" - if (array_search($this->input->getCmd('option', '') . '/' . $task, $tasks) === false) { - // Check short task version, must be on the same option of the view - if ($this->input->getCmd('option', '') !== $option || array_search($task, $tasks) === false) { - // Not permitted task - $redirect = true; + if ($found2) { + $found = true; + break; } } + + if (!$found) { + $redirect = true; + } } else { if ( $this->input->getCmd('option', '') !== $option || $this->input->getCmd('view', '') !== $view diff --git a/libraries/src/Application/SiteApplication.php b/libraries/src/Application/SiteApplication.php index 1d459f637afd2..61f4b7403b220 100644 --- a/libraries/src/Application/SiteApplication.php +++ b/libraries/src/Application/SiteApplication.php @@ -253,7 +253,19 @@ protected function doExecute() * $this->input->getCmd('option'); or $this->input->getCmd('view'); * ex: due of the sef urls */ - $this->checkUserRequireReset('com_users', 'profile', 'edit', 'com_users/profile.save,com_users/profile.apply,com_users/user.logout'); + $this->checkUserRequiresReset('com_users', 'profile', 'edit', [ + ['option' => 'com_users', 'task' => 'profile.save'], + ['option' => 'com_users', 'task' => 'profile.apply'], + ['option' => 'com_users', 'task' => 'user.logout'], + ['option' => 'com_users', 'task' => 'user.menulogout'], + ['option' => 'com_users', 'task' => 'captive.validate'], + ['option' => 'com_users', 'view' => 'captive'], + ['option' => 'com_users', 'view' => 'methods'], + ['option' => 'com_users', 'view' => 'method'], + ['option' => 'com_users', 'task' => 'method.add'], + ['option' => 'com_users', 'task' => 'method.save'], + ['option' => 'com_users', 'view' => 'profile', 'layout' => 'edit'], + ]); } // Dispatch the application @@ -664,7 +676,26 @@ public function login($credentials, $options = []) // Set the access control action to check. $options['action'] = 'core.login.site'; - return parent::login($credentials, $options); + $result = parent::login($credentials, $options); + + if (!($result instanceof \Exception) && $result) { + // Check if the user is required to reset their password + $this->checkUserRequiresReset('com_users', 'profile', 'edit', [ + ['option' => 'com_users', 'task' => 'profile.save'], + ['option' => 'com_users', 'task' => 'profile.apply'], + ['option' => 'com_users', 'task' => 'user.logout'], + ['option' => 'com_users', 'task' => 'user.menulogout'], + ['option' => 'com_users', 'task' => 'captive.validate'], + ['option' => 'com_users', 'view' => 'captive'], + ['option' => 'com_users', 'view' => 'methods'], + ['option' => 'com_users', 'view' => 'method'], + ['option' => 'com_users', 'task' => 'method.add'], + ['option' => 'com_users', 'task' => 'method.save'], + ['option' => 'com_users', 'view' => 'profile', 'layout' => 'edit'], + ]); + } + + return $result; } /** diff --git a/libraries/src/Console/ExtensionRemoveCommand.php b/libraries/src/Console/ExtensionRemoveCommand.php index 82931c5a8b736..67e81dfd95df6 100644 --- a/libraries/src/Console/ExtensionRemoveCommand.php +++ b/libraries/src/Console/ExtensionRemoveCommand.php @@ -175,7 +175,7 @@ protected function doExecute(InputInterface $input, OutputInterface $output): in $response = $this->ioStyle->ask('Are you sure you want to remove this extension?', 'yes/no'); - if (strtolower($response) === 'yes') { + if ((strtolower($response) === 'yes') || $input->getOption('no-interaction')) { // Get an installer object for the extension type $installer = Installer::getInstance(); $row = new Extension($this->getDatabase()); diff --git a/libraries/src/Installer/Adapter/ComponentAdapter.php b/libraries/src/Installer/Adapter/ComponentAdapter.php index 373792407d09e..03be708a35706 100644 --- a/libraries/src/Installer/Adapter/ComponentAdapter.php +++ b/libraries/src/Installer/Adapter/ComponentAdapter.php @@ -1360,6 +1360,7 @@ public function refreshManifestCache() $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); $this->parent->extension->manifest_cache = json_encode($manifest_details); $this->parent->extension->name = $manifest_details['name']; + $this->parent->extension->changelogurl = $manifest_details['changelogurl']; // Namespace is optional if (isset($manifest_details['namespace'])) { diff --git a/libraries/src/Installer/Adapter/FileAdapter.php b/libraries/src/Installer/Adapter/FileAdapter.php index f761d15275a8c..0d8b233e75f82 100644 --- a/libraries/src/Installer/Adapter/FileAdapter.php +++ b/libraries/src/Installer/Adapter/FileAdapter.php @@ -585,6 +585,7 @@ public function refreshManifestCache() $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); $this->parent->extension->manifest_cache = json_encode($manifest_details); $this->parent->extension->name = $manifest_details['name']; + $this->parent->extension->changelogurl = $manifest_details['changelogurl']; try { return $this->parent->extension->store(); diff --git a/libraries/src/Installer/Adapter/LanguageAdapter.php b/libraries/src/Installer/Adapter/LanguageAdapter.php index 564856059aff2..9333350f572d4 100644 --- a/libraries/src/Installer/Adapter/LanguageAdapter.php +++ b/libraries/src/Installer/Adapter/LanguageAdapter.php @@ -734,9 +734,11 @@ public function refreshManifestCache() $this->parent->manifest = $this->parent->isManifest($manifestPath); $this->parent->setPath('manifest', $manifestPath); + $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); $this->parent->extension->manifest_cache = json_encode($manifest_details); $this->parent->extension->name = $manifest_details['name']; + $this->parent->extension->changelogurl = $manifest_details['changelogurl']; if ($this->parent->extension->store()) { return true; diff --git a/libraries/src/Installer/Adapter/LibraryAdapter.php b/libraries/src/Installer/Adapter/LibraryAdapter.php index 3118f0fbdea0e..d55a4918051cd 100644 --- a/libraries/src/Installer/Adapter/LibraryAdapter.php +++ b/libraries/src/Installer/Adapter/LibraryAdapter.php @@ -494,6 +494,7 @@ public function refreshManifestCache() $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); $this->parent->extension->manifest_cache = json_encode($manifest_details); $this->parent->extension->name = $manifest_details['name']; + $this->parent->extension->changelogurl = $manifest_details['changelogurl']; try { return $this->parent->extension->store(); diff --git a/libraries/src/Installer/Adapter/ModuleAdapter.php b/libraries/src/Installer/Adapter/ModuleAdapter.php index 005732a8b5b32..f31fcb8807358 100644 --- a/libraries/src/Installer/Adapter/ModuleAdapter.php +++ b/libraries/src/Installer/Adapter/ModuleAdapter.php @@ -415,9 +415,11 @@ public function refreshManifestCache() $manifestPath = $client->path . '/modules/' . $this->parent->extension->element . '/' . $this->parent->extension->element . '.xml'; $this->parent->manifest = $this->parent->isManifest($manifestPath); $this->parent->setPath('manifest', $manifestPath); + $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); $this->parent->extension->manifest_cache = json_encode($manifest_details); $this->parent->extension->name = $manifest_details['name']; + $this->parent->extension->changelogurl = $manifest_details['changelogurl']; if ($this->parent->extension->store()) { return true; diff --git a/libraries/src/Installer/Adapter/PackageAdapter.php b/libraries/src/Installer/Adapter/PackageAdapter.php index 9c19ebb73fbf5..d6e441dd1c702 100644 --- a/libraries/src/Installer/Adapter/PackageAdapter.php +++ b/libraries/src/Installer/Adapter/PackageAdapter.php @@ -541,7 +541,6 @@ protected function storeExtension() $this->extension->name = $this->name; $this->extension->type = 'package'; $this->extension->element = $this->element; - $this->extension->changelogurl = $this->changelogurl; // There is no folder for packages $this->extension->folder = ''; @@ -552,6 +551,9 @@ protected function storeExtension() $this->extension->params = $this->parent->getParams(); } + // Update changelogurl + $this->extension->changelogurl = $this->changelogurl; + // Update the manifest cache for the entry $this->extension->manifest_cache = $this->parent->generateManifestCache(); @@ -717,6 +719,7 @@ public function refreshManifestCache() $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); $this->parent->extension->manifest_cache = json_encode($manifest_details); $this->parent->extension->name = $manifest_details['name']; + $this->parent->extension->changelogurl = $manifest_details['changelogurl']; try { return $this->parent->extension->store(); diff --git a/libraries/src/Installer/Adapter/PluginAdapter.php b/libraries/src/Installer/Adapter/PluginAdapter.php index 58d8df7c2bb41..6c88916efb059 100644 --- a/libraries/src/Installer/Adapter/PluginAdapter.php +++ b/libraries/src/Installer/Adapter/PluginAdapter.php @@ -611,10 +611,11 @@ public function refreshManifestCache() . $this->parent->extension->element . '.xml'; $this->parent->manifest = $this->parent->isManifest($manifestPath); $this->parent->setPath('manifest', $manifestPath); + $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); $this->parent->extension->manifest_cache = json_encode($manifest_details); - - $this->parent->extension->name = $manifest_details['name']; + $this->parent->extension->name = $manifest_details['name']; + $this->parent->extension->changelogurl = $manifest_details['changelogurl']; if ($this->parent->extension->store()) { return true; diff --git a/libraries/src/Installer/Adapter/TemplateAdapter.php b/libraries/src/Installer/Adapter/TemplateAdapter.php index 99c57b7b66989..07823c741bd1a 100644 --- a/libraries/src/Installer/Adapter/TemplateAdapter.php +++ b/libraries/src/Installer/Adapter/TemplateAdapter.php @@ -677,6 +677,7 @@ public function refreshManifestCache() $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest')); $this->parent->extension->manifest_cache = json_encode($manifest_details); $this->parent->extension->name = $manifest_details['name']; + $this->parent->extension->changelogurl = $manifest_details['changelogurl']; try { return $this->parent->extension->store(); diff --git a/libraries/src/Installer/Installer.php b/libraries/src/Installer/Installer.php index f72d5d76da69c..b18418bf9f9b7 100644 --- a/libraries/src/Installer/Installer.php +++ b/libraries/src/Installer/Installer.php @@ -2311,12 +2311,13 @@ public static function parseXMLInstallFile($path) $data['creationDate'] = ((string) $xml->creationDate) ?: Text::_('JLIB_UNKNOWN'); $data['author'] = ((string) $xml->author) ?: Text::_('JLIB_UNKNOWN'); - $data['copyright'] = (string) $xml->copyright; - $data['authorEmail'] = (string) $xml->authorEmail; - $data['authorUrl'] = (string) $xml->authorUrl; - $data['version'] = (string) $xml->version; - $data['description'] = (string) $xml->description; - $data['group'] = (string) $xml->group; + $data['copyright'] = (string) $xml->copyright; + $data['authorEmail'] = (string) $xml->authorEmail; + $data['authorUrl'] = (string) $xml->authorUrl; + $data['version'] = (string) $xml->version; + $data['description'] = (string) $xml->description; + $data['group'] = (string) $xml->group; + $data['changelogurl'] = (string) $xml->changelogurl; // Child template specific fields. if (isset($xml->inheritable)) { diff --git a/package-lock.json b/package-lock.json index 1497cd7ea5b64..2a86f34ca6e83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4479,9 +4479,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -8385,9 +8385,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", diff --git a/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php b/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php index ff9eedcc4e57b..5fe442701b753 100644 --- a/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php +++ b/plugins/system/privacyconsent/src/Extension/PrivacyConsent.php @@ -291,6 +291,7 @@ public function onAfterRoute() ($option == 'com_users' && $isAllowedUserTask) || ($option == 'com_content' && $view == 'article' && $id == $privacyArticleId) || ($option == 'com_users' && $view == 'profile' && $layout == 'edit') + || ($option == 'com_users' && $view == 'captive') ) { return; }