Skip to content

Commit 941268e

Browse files
authored
[5.4] Implement autoupdate plugin events (#45696)
This PR adds a new plugin event that allows 3rd party extensions to stop automatted updates. This allows 3rd party developers to i.e. enforce special conditions like "only perform auto updates if a backup using my backup extension has been performed in the last 24h". Furthermore, this PR also fixed the version number in the "failed update" notfication that is sent when an update is blocked by a plugin. Side note: A new "after update" event is not required, as the default after update event will be called.
1 parent 26315d8 commit 941268e

File tree

7 files changed

+89
-12
lines changed

7 files changed

+89
-12
lines changed

administrator/components/com_joomlaupdate/src/Model/NotificationModel.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ final class NotificationModel extends BaseDatabaseModel
3939
*
4040
* @param string $type The type of notification to send. This is the last key for the mail template
4141
* @param string $oldVersion The old version from before the update
42+
* @param string $newVersion The new version from after the update
4243
*
4344
* @return void
4445
*
4546
* @since 5.4.0
4647
*/
47-
public function sendNotification($type, $oldVersion): void
48+
public function sendNotification($type, $oldVersion, $newVersion): void
4849
{
4950
$params = ComponentHelper::getParams('com_joomlaupdate');
5051

@@ -58,7 +59,6 @@ public function sendNotification($type, $oldVersion): void
5859
$app = Factory::getApplication();
5960
$jLanguage = $app->getLanguage();
6061
$sitename = $app->get('sitename');
61-
$newVersion = (new Version())->getShortVersion();
6262

6363
$substitutions = [
6464
'oldversion' => $oldVersion,

administrator/components/com_joomlaupdate/src/Model/UpdateModel.php

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Joomla\CMS\Authentication\Authentication;
1414
use Joomla\CMS\Component\ComponentHelper;
1515
use Joomla\CMS\Event\Extension\AfterJoomlaUpdateEvent;
16+
use Joomla\CMS\Event\Extension\BeforeJoomlaAutoupdateEvent;
1617
use Joomla\CMS\Event\Extension\BeforeJoomlaUpdateEvent;
1718
use Joomla\CMS\Extension\ExtensionHelper;
1819
use Joomla\CMS\Factory;
@@ -22,6 +23,7 @@
2223
use Joomla\CMS\Installer\Installer;
2324
use Joomla\CMS\Language\Text;
2425
use Joomla\CMS\Log\Log;
26+
use Joomla\CMS\MVC\Controller\Exception\CheckinCheckout;
2527
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
2628
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
2729
use Joomla\CMS\Plugin\PluginHelper;
@@ -39,6 +41,7 @@
3941
use Joomla\Filesystem\File;
4042
use Joomla\Registry\Registry;
4143
use Joomla\Utilities\ArrayHelper;
44+
use Tobscure\JsonApi\Exception\InvalidParameterException;
4245

4346
// phpcs:disable PSR1.Files.SideEffects
4447
\defined('_JEXEC') or die;
@@ -532,21 +535,37 @@ public function prepareAutoUpdate(string $targetVersion): array
532535
$fileInformation = $this->download();
533536

534537
if ($fileInformation['version'] !== $targetVersion) {
535-
throw new \Exception(Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_VERSION_WRONG'), 410);
538+
throw new InvalidParameterException(Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_VERSION_WRONG'), 410);
536539
}
537540

538541
if ($fileInformation['check'] === false) {
539-
throw new \Exception(Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_CHECKSUM_WRONG'), 410);
542+
throw new InvalidParameterException(Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_CHECKSUM_WRONG'), 410);
540543
}
541544

542545
if (!$this->createUpdateFile($fileInformation['basename'])) {
543-
throw new \Exception('Could not write update file', 410);
546+
throw new InvalidParameterException(Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_COULD_NOT_WRITE_UPDATE_FILE'), 410);
544547
}
545548

546549
Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $fileInformation['basename']), Log::INFO, 'Update');
547550

548551
$app = Factory::getApplication();
549552

553+
// Run preparation plugin trigger
554+
PluginHelper::importPlugin('installer');
555+
556+
$eventResult = $this->getDispatcher()->dispatch(
557+
'onBeforeJoomlaAutoupdate',
558+
new BeforeJoomlaAutoupdateEvent(
559+
'onBeforeJoomlaAutoupdate'
560+
)
561+
);
562+
563+
if ($eventResult->getArgument('stoppedUpdate')) {
564+
Log::add(Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_STOPPED_BY_PLUGIN'), Log::ERROR, 'Update');
565+
566+
throw new CheckinCheckout(Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_STOPPED_BY_PLUGIN'), 503);
567+
}
568+
550569
return [
551570
'password' => $app->getUserState('com_joomlaupdate.password'),
552571
'filesize' => $app->getUserState('com_joomlaupdate.filesize'),
@@ -824,7 +843,7 @@ public function createUpdateFile($basename = null): bool
824843
$app = Factory::getApplication();
825844

826845
// Trigger event before joomla update.
827-
$app->getDispatcher()->dispatch('onJoomlaBeforeUpdate', new BeforeJoomlaUpdateEvent('onJoomlaBeforeUpdate'));
846+
$this->getDispatcher()->dispatch('onJoomlaBeforeUpdate', new BeforeJoomlaUpdateEvent('onJoomlaBeforeUpdate'));
828847

829848
// Get the absolute path to site's root.
830849
$siteroot = JPATH_SITE;
@@ -1161,7 +1180,7 @@ public function cleanUp()
11611180
$oldVersion = $app->getUserState('com_joomlaupdate.oldversion');
11621181

11631182
// Trigger event after joomla update.
1164-
$app->getDispatcher()->dispatch('onJoomlaAfterUpdate', new AfterJoomlaUpdateEvent('onJoomlaAfterUpdate', [
1183+
$this->getDispatcher()->dispatch('onJoomlaAfterUpdate', new AfterJoomlaUpdateEvent('onJoomlaAfterUpdate', [
11651184
'oldVersion' => $oldVersion ?: '',
11661185
]));
11671186
$app->setUserState('com_joomlaupdate.oldversion', null);

administrator/language/en-GB/com_joomlaupdate.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,15 @@ COM_JOOMLAUPDATE_VIEW_DEFAULT_UPLOAD_INTRO="You can use this feature to update J
207207
COM_JOOMLAUPDATE_VIEW_UPDATE_BYTESEXTRACTED="Bytes extracted"
208208
COM_JOOMLAUPDATE_VIEW_UPDATE_BYTESREAD="Bytes read"
209209
COM_JOOMLAUPDATE_VIEW_UPDATE_CHECKSUM_WRONG="File Checksum Failed"
210+
COM_JOOMLAUPDATE_VIEW_UPDATE_COULD_NOT_WRITE_UPDATE_FILE="Could not write update file."
210211
COM_JOOMLAUPDATE_VIEW_UPDATE_DOWNLOADFAILED="Download of update package failed."
211212
COM_JOOMLAUPDATE_VIEW_UPDATE_FILESEXTRACTED="Files extracted"
212213
COM_JOOMLAUPDATE_VIEW_UPDATE_FINALISE_CONFIRM_AND_CONTINUE="Confirm & Continue"
213214
COM_JOOMLAUPDATE_VIEW_UPDATE_FINALISE_HEAD="Joomla Update is finishing and cleaning up"
214215
COM_JOOMLAUPDATE_VIEW_UPDATE_FINALISE_HEAD_DESC="To complete the update Process please confirm your identity by re-entering the login information for your site "%s" below."
215216
COM_JOOMLAUPDATE_VIEW_UPDATE_ITEMS="items"
216217
COM_JOOMLAUPDATE_VIEW_UPDATE_PERCENT="Percent complete"
218+
COM_JOOMLAUPDATE_VIEW_UPDATE_STOPPED_BY_PLUGIN="Update stopped by plugin."
217219
COM_JOOMLAUPDATE_VIEW_UPDATE_VERSION_WRONG="The version of the update package and the requested version do not match, try to refresh the update information."
218220
COM_JOOMLAUPDATE_VIEW_UPLOAD_CAPTIVE_INTRO_BODY="Make sure that the update file you have uploaded comes from the official Joomla download page. Afterwards, please confirm that you want to install it by re-entering the login information for your site "%s" below."
219221
COM_JOOMLAUPDATE_VIEW_UPLOAD_CAPTIVE_INTRO_HEAD="Are you sure you want to install the file you uploaded?"

api/components/com_joomlaupdate/src/Controller/NotificationController.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
namespace Joomla\Component\Joomlaupdate\Api\Controller;
1212

1313
use Joomla\CMS\Language\Text;
14+
use Joomla\Component\Joomlaupdate\Administrator\Model\NotificationModel;
1415
use Joomla\Component\Joomlaupdate\Api\View\Updates\JsonapiView;
1516

1617
// phpcs:disable PSR1.Files.SideEffects
@@ -50,10 +51,11 @@ public function failed()
5051
$this->validateUpdateToken();
5152

5253
$fromVersion = $this->input->json->getString('fromVersion', null);
54+
$toVersion = $this->input->json->getString('toVersion', null);
5355

5456
$view = $this->prepareView();
5557

56-
$view->notification('failed', $fromVersion);
58+
$view->notification('failed', $fromVersion, $toVersion);
5759

5860
return $this;
5961
}
@@ -70,10 +72,11 @@ public function success()
7072
$this->validateUpdateToken();
7173

7274
$fromVersion = $this->input->json->getString('fromVersion', null);
75+
$toVersion = $this->input->json->getString('toVersion', null);
7376

7477
$view = $this->prepareView();
7578

76-
$view->notification('success', $fromVersion);
79+
$view->notification('success', $fromVersion, $toVersion);
7780

7881
return $this;
7982
}
@@ -101,7 +104,7 @@ protected function prepareView()
101104
throw new \RuntimeException($e->getMessage());
102105
}
103106

104-
/** @var UpdateModel $model */
107+
/** @var NotificationModel $model */
105108
$model = $this->getModel('Notification', 'Administrator', ['ignore_request' => true, 'state' => $this->modelState]);
106109

107110
if (!$model) {

api/components/com_joomlaupdate/src/View/Notification/JsonapiView.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class JsonapiView extends BaseApiView
3333
*
3434
* @since 5.4.0
3535
*/
36-
public function notification($type, $oldVersion)
36+
public function notification($type, $oldVersion, $newVersion)
3737
{
3838
/**
3939
* @var UpdateModel $model
@@ -43,7 +43,7 @@ public function notification($type, $oldVersion)
4343

4444
try {
4545
// Perform the finalization action
46-
$model->sendNotification($type, $oldVersion);
46+
$model->sendNotification($type, $oldVersion, $newVersion);
4747
} catch (\Throwable $e) {
4848
$success = false;
4949
}

libraries/src/Event/CoreEventAware.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ trait CoreEventAware
156156
'onExtensionAfterSave' => Model\AfterSaveEvent::class,
157157
'onExtensionAfterDelete' => Model\AfterDeleteEvent::class,
158158
'onExtensionChangeState' => Model\BeforeChangeStateEvent::class,
159+
'onJoomlaBeforeAutoupdate' => Extension\BeforeJoomlaAutoupdateEvent::class,
159160
'onJoomlaBeforeUpdate' => Extension\BeforeJoomlaUpdateEvent::class,
160161
'onJoomlaAfterUpdate' => Extension\AfterJoomlaUpdateEvent::class,
161162
// Installer
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/**
4+
* Joomla! Content Management System
5+
*
6+
* @copyright (C) 2025 Open Source Matters, Inc. <https://www.joomla.org>
7+
* @license GNU General Public License version 2 or later; see LICENSE.txt
8+
*/
9+
10+
namespace Joomla\CMS\Event\Extension;
11+
12+
// phpcs:disable PSR1.Files.SideEffects
13+
\defined('_JEXEC') or die;
14+
// phpcs:enable PSR1.Files.SideEffects
15+
16+
/**
17+
* Class for Joomla Auto Update events
18+
*
19+
* @since __DEPLOY_VERSION__
20+
*/
21+
class BeforeJoomlaAutoupdateEvent extends AbstractJoomlaUpdateEvent
22+
{
23+
/**
24+
* Constructor.
25+
*
26+
* @param string $name The event name.
27+
* @param array $arguments The event arguments.
28+
*
29+
* @throws \BadMethodCallException
30+
*
31+
* @since __DEPLOY_VERSION__
32+
*/
33+
public function __construct($name, array $arguments = [])
34+
{
35+
$arguments['stoppedUpdate'] = false;
36+
37+
parent::__construct($name, $arguments);
38+
}
39+
40+
/**
41+
* Set stop parameter to true
42+
*
43+
* @return void
44+
*
45+
* @since __DEPLOY_VERSION__
46+
*/
47+
public function stopUpdate()
48+
{
49+
$this->arguments['stoppedUpdate'] = true;
50+
$this->stopPropagation();
51+
}
52+
}

0 commit comments

Comments
 (0)