Skip to content

Commit ad937d9

Browse files
SniperSisterbembelimenHLeithnerbrianteemanrichard67
authored
[5.4] Automated Core Update Client (joomla#45143)
Joomla backend implementation of the automated core update client. --------- Co-authored-by: Benjamin Trenkle <[email protected]> Co-authored-by: Harald Leithner <[email protected]> Co-authored-by: Benjamin Trenkle <[email protected]> Co-authored-by: Brian Teeman <[email protected]> Co-authored-by: Richard Fath <[email protected]> Co-authored-by: Tobias Zulauf <[email protected]>
1 parent 7aa9eb5 commit ad937d9

File tree

46 files changed

+2561
-51
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2561
-51
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `locked`, `manifest_cache`, `params`, `custom_data`, `ordering`, `state`)
2+
SELECT 0, 'plg_quickicon_autoupdate', 'plugin', 'autoupdate', 'quickicon', 0, 1, 1, 0, 1, '', '', '', -1, 0
3+
WHERE NOT EXISTS (SELECT * FROM `#__extensions` e WHERE e.`type` = 'plugin' AND e.`element` = 'autoupdate' AND e.`folder` = 'quickicon' AND e.`client_id` = 0);
4+
5+
INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `locked`, `manifest_cache`, `params`, `custom_data`, `ordering`, `state`)
6+
SELECT 0, 'plg_webservices_joomlaupdate', 'plugin', 'joomlaupdate', 'webservices', 0, 1, 1, 0, 1, '', '', '', -1, 0
7+
WHERE NOT EXISTS (SELECT * FROM `#__extensions` e WHERE e.`type` = 'plugin' AND e.`element` = 'joomlaupdate' AND e.`folder` = 'webservices' AND e.`client_id` = 0);
8+
9+
INSERT INTO `#__mail_templates` (`template_id`, `extension`, `language`, `subject`, `body`, `htmlbody`, `attachments`, `params`) VALUES
10+
('com_joomlaupdate.update.success', 'com_joomlaupdate', '', 'COM_JOOMLAUPDATE_UPDATE_SUCCESS_MAIL_SUBJECT', 'COM_JOOMLAUPDATE_UPDATE_SUCCESS_MAIL_BODY', '', '', '{"tags":["newversion","oldversion","sitename","url"]}'),
11+
('com_joomlaupdate.update.failed', 'com_joomlaupdate', '', 'COM_JOOMLAUPDATE_UPDATE_FAILED_MAIL_SUBJECT', 'COM_JOOMLAUPDATE_UPDATE_FAILED_MAIL_BODY', '', '', '{"tags":["newversion","oldversion","sitename","url"]}');
12+
13+
-- add post-installation message for automated updates
14+
INSERT INTO `#__postinstall_messages` (`extension_id`, `title_key`, `description_key`, `action_key`, `language_extension`, `language_client_id`, `type`, `action_file`, `action`, `condition_file`, `condition_method`, `version_introduced`, `enabled`)
15+
SELECT `extension_id`, 'COM_JOOMLAUPDATE_POSTINSTALL_MSG_AUTOMATED_UPDATES_TITLE', 'COM_JOOMLAUPDATE_POSTINSTALL_MSG_AUTOMATED_UPDATES_DESCRIPTION', 'COM_JOOMLAUPDATE_POSTINSTALL_MSG_AUTOMATED_UPDATES_ACTION', 'com_joomlaupdate', 1, 'action', 'admin://components/com_joomlaupdate/postinstall/autoupdate.php', 'com_joomlaupdate_postinstall_autoupdate_action', 'admin://components/com_joomlaupdate/postinstall/autoupdate.php', 'com_joomlaupdate_postinstall_autoupdate_condition', '5.4.0', 1 FROM `#__extensions` WHERE `name` = 'files_joomla';
16+
17+
-- disable autostart for the previous tour
18+
UPDATE `#__guidedtours` SET `autostart` = 0 WHERE `uid` = 'joomla-whatsnew-5-3';
19+
20+
INSERT INTO `#__guidedtours` (`title`, `description`, `extensions`, `url`, `published`, `language`, `note`, `access`, `uid`, `autostart`, `created`, `created_by`, `modified`, `modified_by`) VALUES
21+
('COM_GUIDEDTOURS_TOUR_WHATSNEW_5_4_TITLE', 'COM_GUIDEDTOURS_TOUR_WHATSNEW_5_4_DESCRIPTION', '["com_cpanel"]', 'administrator/index.php', 1, '*', '', 1, 'joomla-whatsnew-5-4', 1, CURRENT_TIMESTAMP(), 0, CURRENT_TIMESTAMP(), 0);
22+
23+
INSERT INTO `#__guidedtour_steps` (`title`, `description`, `position`, `target`, `type`, `interactive_type`, `url`, `published`, `language`, `note`, `params`, `tour_id`, `created`, `created_by`, `modified`, `modified_by`)
24+
SELECT 'COM_GUIDEDTOURS_TOUR_WHATSNEW_5_4_STEP_0_TITLE', 'COM_GUIDEDTOURS_TOUR_WHATSNEW_5_4_STEP_0_DESCRIPTION', 'right', '#sidebarmenu nav > ul:first-of-type > li:last-child', 0, 1, '', 1, '*', '', '"{\"required\":1,\"requiredvalue\":\"\"}"', MAX(`id`), CURRENT_TIMESTAMP(), 0, CURRENT_TIMESTAMP(), 0
25+
FROM `#__guidedtours`
26+
WHERE `uid` = 'joomla-whatsnew-5-4';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "locked", "manifest_cache", "params", "custom_data", "ordering", "state")
2+
SELECT 0, 'plg_quickicon_autoupdate', 'plugin', 'autoupdate', 'quickicon', 0, 1, 1, 0, 1, '', '', '', -1, 0
3+
WHERE NOT EXISTS (SELECT * FROM "#__extensions" e WHERE e."type" = 'plugin' AND e."element" = 'autoupdate' AND e."folder" = 'quickicon' AND e."client_id" = 0);
4+
5+
INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "locked", "manifest_cache", "params", "custom_data", "ordering", "state")
6+
SELECT 0, 'plg_webservices_joomlaupdate', 'plugin', 'joomlaupdate', 'webservices', 0, 1, 1, 0, 1, '', '', '', -1, 0
7+
WHERE NOT EXISTS (SELECT * FROM "#__extensions" e WHERE e."type" = 'plugin' AND e."element" = 'joomlaupdate' AND e."folder" = 'webservices' AND e."client_id" = 0);
8+
9+
INSERT INTO "#__mail_templates" ("template_id", "extension", "language", "subject", "body", "htmlbody", "attachments", "params") VALUES
10+
('com_joomlaupdate.update.success', 'com_joomlaupdate', '', 'COM_JOOMLAUPDATE_UPDATE_SUCCESS_MAIL_SUBJECT', 'COM_JOOMLAUPDATE_UPDATE_SUCCESS_MAIL_BODY', '', '', '{"tags":["newversion","oldversion","sitename","url"]}'),
11+
('com_joomlaupdate.update.failed', 'com_joomlaupdate', '', 'COM_JOOMLAUPDATE_UPDATE_FAILED_MAIL_SUBJECT', 'COM_JOOMLAUPDATE_UPDATE_FAILED_MAIL_BODY', '', '', '{"tags":["newversion","oldversion","sitename","url"]}');
12+
13+
-- add post-installation message for automated updates
14+
INSERT INTO "#__postinstall_messages" ("extension_id", "title_key", "description_key", "action_key", "language_extension", "language_client_id", "type", "action_file", "action", "condition_file", "condition_method", "version_introduced", "enabled")
15+
SELECT "extension_id", 'COM_JOOMLAUPDATE_POSTINSTALL_MSG_AUTOMATED_UPDATES_TITLE', 'COM_JOOMLAUPDATE_POSTINSTALL_MSG_AUTOMATED_UPDATES_DESCRIPTION', 'COM_JOOMLAUPDATE_POSTINSTALL_MSG_AUTOMATED_UPDATES_ACTION', 'com_joomlaupdate', 1, 'action', 'admin://components/com_joomlaupdate/postinstall/autoupdate.php', 'com_joomlaupdate_postinstall_autoupdate_action', 'admin://components/com_joomlaupdate/postinstall/autoupdate.php', 'com_joomlaupdate_postinstall_autoupdate_condition', '5.4.0', 1 FROM "#__extensions" WHERE "name" = 'files_joomla';
16+
17+
-- disable autostart for the previous tour
18+
UPDATE "#__guidedtours" SET "autostart" = 0 WHERE "uid" = 'joomla-whatsnew-5-3';
19+
20+
INSERT INTO "#__guidedtours" ("title", "description", "extensions", "url", "published", "language", "note", "access", "uid", "autostart", "created", "created_by", "modified", "modified_by") VALUES
21+
('COM_GUIDEDTOURS_TOUR_WHATSNEW_5_4_TITLE', 'COM_GUIDEDTOURS_TOUR_WHATSNEW_5_4_DESCRIPTION', '["com_cpanel"]', 'administrator/index.php', 1, '*', '', 1, 'joomla-whatsnew-5-4', 1, CURRENT_TIMESTAMP, 0, CURRENT_TIMESTAMP, 0);
22+
23+
INSERT INTO "#__guidedtour_steps" ("title", "description", "position", "target", "type", "interactive_type", "url", "published", "language", "note", "params", "tour_id", "created", "created_by", "modified", "modified_by")
24+
SELECT 'COM_GUIDEDTOURS_TOUR_WHATSNEW_5_4_STEP_0_TITLE', 'COM_GUIDEDTOURS_TOUR_WHATSNEW_5_4_STEP_0_DESCRIPTION', 'right', '#sidebarmenu nav > ul:first-of-type > li:last-child', 0, 1, '', 1, '*', '', '"{\"required\":1,\"requiredvalue\":\"\"}"', MAX("id"), CURRENT_TIMESTAMP, 0, CURRENT_TIMESTAMP, 0
25+
FROM "#__guidedtours"
26+
WHERE "uid" = 'joomla-whatsnew-5-4';

administrator/components/com_joomlaupdate/config.xml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,60 @@
7272
</field>
7373

7474
</fieldset>
75+
<fieldset
76+
name="automated-updates"
77+
label="COM_JOOMLAUPDATE_CONFIG_AUTOMATED_UPDATES_LABEL"
78+
>
79+
<field
80+
name="autoupdate"
81+
type="radio"
82+
label="COM_JOOMLAUPDATE_CONFIG_AUTOUPDATE_LABEL"
83+
description="COM_JOOMLAUPDATE_CONFIG_AUTOUPDATE_DESC"
84+
default="0"
85+
layout="joomla.form.field.radio.switcher"
86+
showon="updatesource:default[AND]minimum_stability:4"
87+
validate="options"
88+
>
89+
<option value="0">JNO</option>
90+
<option value="1">JYES</option>
91+
</field>
92+
93+
<field
94+
name="autoupdate_status"
95+
type="hidden"
96+
/>
97+
98+
<field
99+
name="update_token"
100+
type="text"
101+
label="COM_JOOMLAUPDATE_CONFIG_UPDATE_TOKEN_LABEL"
102+
description="COM_JOOMLAUPDATE_CONFIG_UPDATE_TOKEN_DESC"
103+
readonly="true"
104+
showon="updatesource:default[AND]minimum_stability:4[AND]autoupdate:1"
105+
/>
106+
<field
107+
name="update_last_check"
108+
type="calendar"
109+
showtime="true"
110+
filter="user_utc"
111+
label="COM_JOOMLAUPDATE_CONFIG_UPDATE_LAST_CHECK_LABEL"
112+
description="COM_JOOMLAUPDATE_CONFIG_UPDATE_LAST_CHECK_DESC"
113+
readonly="true"
114+
showon="updatesource:default[AND]minimum_stability:4[AND]autoupdate:1"
115+
/>
116+
<field
117+
name="automated_updates_disabled"
118+
type="note"
119+
label="COM_JOOMLAUPDATE_CONFIG_AUTOMATED_UPDATES_DISABLED_LABEL"
120+
class="alert alert-info w-100"
121+
showon="updatesource!:default[OR]minimum_stability!:4"
122+
/>
123+
<field
124+
name="automated_updates_email"
125+
type="text"
126+
label="COM_JOOMLAUPDATE_CONFIG_AUTOUPDATE_UPDATE_EMAIL_LABEL"
127+
description="COM_JOOMLAUPDATE_CONFIG_AUTOUPDATE_UPDATE_EMAIL_DESCRIPTION"
128+
showon="updatesource:default[AND]minimum_stability:4[AND]autoupdate:1"
129+
/>
130+
</fieldset>
75131
</config>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/**
4+
* @package Joomla.Administrator
5+
* @subpackage com_joomlaupdate
6+
*
7+
* @copyright (C) 2025 Open Source Matters, Inc. <https://www.joomla.org>
8+
* @license GNU General Public License version 2 or later; see LICENSE.txt
9+
*/
10+
11+
use Joomla\CMS\Component\ComponentHelper;
12+
use Joomla\CMS\Factory;
13+
use Joomla\CMS\Table\Extension;
14+
use Joomla\Component\Joomlaupdate\Administrator\Enum\AutoupdateRegisterState;
15+
use Joomla\Component\Joomlaupdate\Administrator\Enum\AutoupdateState;
16+
use Joomla\Database\DatabaseInterface;
17+
use Joomla\Registry\Registry;
18+
19+
// phpcs:disable PSR1.Files.SideEffects
20+
\defined('_JEXEC') or die;
21+
// phpcs:enable PSR1.Files.SideEffects
22+
23+
/**
24+
* Post-installation message about the new Automated Update: condition check.
25+
*
26+
* Returns true it is disabled.
27+
*
28+
* @return bool
29+
* @since __DEPLOY_VERSION__
30+
*/
31+
function com_joomlaupdate_postinstall_autoupdate_condition(): bool
32+
{
33+
return AutoupdateState::tryFrom(ComponentHelper::getParams('com_joomlaupdate')->get('autoupdate', '0')) === AutoupdateState::Disabled;
34+
}
35+
36+
/**
37+
* Post-installation message about the new Automated Update: action.
38+
*
39+
* Enables the Automated Update.
40+
*
41+
* @return void
42+
* @since __DEPLOY_VERSION__
43+
*/
44+
function com_joomlaupdate_postinstall_autoupdate_action(): void
45+
{
46+
$db = Factory::getContainer()->get(DatabaseInterface::class);
47+
48+
// Get extension row
49+
$extension = new Extension($db);
50+
$extensionId = $extension->find(['element' => 'com_joomlaupdate']);
51+
$extension->load($extensionId);
52+
53+
// Set new update registration state
54+
$params = new Registry($extension->params);
55+
$params->set('autoupdate', AutoupdateState::Enabled);
56+
$params->set('autoupdate_status', AutoupdateRegisterState::Subscribe);
57+
58+
$extension->params = $params->toString();
59+
60+
if (!$extension->store()) {
61+
throw new \RuntimeException($extension->getError());
62+
}
63+
}

administrator/components/com_joomlaupdate/src/Controller/UpdateController.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010

1111
namespace Joomla\Component\Joomlaupdate\Administrator\Controller;
1212

13+
use Joomla\CMS\Component\ComponentHelper;
1314
use Joomla\CMS\Factory;
1415
use Joomla\CMS\Installer\Installer;
1516
use Joomla\CMS\Language\Text;
1617
use Joomla\CMS\Log\Log;
1718
use Joomla\CMS\MVC\Controller\BaseController;
1819
use Joomla\CMS\Response\JsonResponse;
1920
use Joomla\CMS\Session\Session;
21+
use Joomla\CMS\Updater\Updater;
22+
use Joomla\Component\Joomlaupdate\Administrator\Enum\AutoupdateRegisterState;
2023
use Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel;
2124

2225
// phpcs:disable PSR1.Files.SideEffects
@@ -712,4 +715,60 @@ public function ajax()
712715

713716
$this->app->close();
714717
}
718+
719+
/**
720+
* Fetch and report health status of the automated updates in \JSON format, for AJAX requests
721+
*
722+
* @return void
723+
*
724+
* @since __DEPLOY_VERSION__
725+
*/
726+
public function healthstatus()
727+
{
728+
if (!Session::checkToken('get')) {
729+
$this->app->setHeader('status', 403, true);
730+
$this->app->sendHeaders();
731+
echo Text::_('JINVALID_TOKEN_NOTICE');
732+
$this->app->close();
733+
}
734+
735+
$params = ComponentHelper::getParams('com_joomlaupdate');
736+
737+
// Edge case: the current state requires the registration, i.e. because it's a new installation
738+
$registrationState = AutoupdateRegisterState::tryFrom($params->get('autoupdate_status', 0));
739+
740+
if (
741+
$this->app->getIdentity()->authorise('core.admin', 'com_joomlaupdate')
742+
&& $registrationState === AutoupdateRegisterState::Subscribe
743+
) {
744+
/** @var UpdateModel $model */
745+
$model = $this->getModel('Update');
746+
$result = $model->changeAutoUpdateRegistration($registrationState);
747+
748+
$result = [
749+
'active' => true,
750+
'healthy' => $result,
751+
];
752+
753+
echo json_encode($result);
754+
755+
$this->app->close();
756+
}
757+
758+
// Default case: connection already configured, check update source and date
759+
$lastCheck = date_create_from_format('Y-m-d H:i:s', $params->get('update_last_check', ''));
760+
761+
$result = [
762+
'active' => (
763+
(int) $params->get('autoupdate')
764+
&& $params->get('updatesource', 'default') === 'default'
765+
&& (int) $params->get('minimum_stability', Updater::STABILITY_STABLE) === Updater::STABILITY_STABLE
766+
),
767+
'healthy' => $lastCheck !== false && $lastCheck->diff(new \DateTime())->days < 4,
768+
];
769+
770+
echo json_encode($result);
771+
772+
$this->app->close();
773+
}
715774
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/**
4+
* @package Joomla.Administrator
5+
* @subpackage com_joomlaupdate
6+
*
7+
* @copyright (C) 2025 Open Source Matters, Inc. <https://www.joomla.org>
8+
* @license GNU General Public License version 2 or later; see LICENSE.txt
9+
*/
10+
11+
namespace Joomla\Component\Joomlaupdate\Administrator\Enum;
12+
13+
// phpcs:disable PSR1.Files.SideEffects
14+
\defined('_JEXEC') or die;
15+
// phpcs:enable PSR1.Files.SideEffects
16+
17+
/**
18+
* Autoupdate State Enum
19+
*/
20+
enum AutoupdateRegisterState: int
21+
{
22+
case Unsubscribe = -1;
23+
case Unsubscribed = 0;
24+
case Subscribe = 1;
25+
case Subscribed = 2;
26+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/**
4+
* @package Joomla.Administrator
5+
* @subpackage com_joomlaupdate
6+
*
7+
* @copyright (C) 2025 Open Source Matters, Inc. <https://www.joomla.org>
8+
* @license GNU General Public License version 2 or later; see LICENSE.txt
9+
*/
10+
11+
namespace Joomla\Component\Joomlaupdate\Administrator\Enum;
12+
13+
// phpcs:disable PSR1.Files.SideEffects
14+
\defined('_JEXEC') or die;
15+
// phpcs:enable PSR1.Files.SideEffects
16+
17+
/**
18+
* Autoupdate State Enum
19+
*/
20+
enum AutoupdateState: int
21+
{
22+
case Disabled = 0;
23+
case Enabled = 1;
24+
}

0 commit comments

Comments
 (0)