Skip to content

Commit da7b953

Browse files
authored
[6.0] Fulfill InstallerScriptInterface with a trait (joomla#44381)
* Add InstallerScriptTrait * Deprecate InstallerScript * Fix wrong path call * Remove deprecations * Remove deprecation of class * Rename method * Add set/getApplication * Add custom pre/postflight which can be overwritten by the developer * Apply suggestions from code review Co-authored-by: Harald Leithner <[email protected]> * Update libraries/src/Installer/InstallerScriptTrait.php * Fix CS * Remove unneded check * Update libraries/src/Installer/InstallerScriptTrait.php
1 parent 229d7e7 commit da7b953

File tree

2 files changed

+335
-0
lines changed

2 files changed

+335
-0
lines changed

libraries/src/Installer/InstallerAdapter.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,10 @@ function (Container $container) use ($classname) {
997997
// Create a new instance
998998
$this->parent->manifestClass = $container->get(InstallerScriptInterface::class);
999999

1000+
if (method_exists($this->parent->manifestClass, 'setApplication')) {
1001+
$this->parent->manifestClass->setApplication(Factory::getApplication());
1002+
}
1003+
10001004
// Set the database
10011005
if ($this->parent->manifestClass instanceof DatabaseAwareInterface) {
10021006
$this->parent->manifestClass->setDatabase($container->get(DatabaseInterface::class));
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
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\Installer;
11+
12+
use Joomla\CMS\Application\ApplicationHelper;
13+
use Joomla\CMS\Application\CMSApplicationInterface;
14+
use Joomla\CMS\Language\Text;
15+
use Joomla\CMS\Log\Log;
16+
use Joomla\Filesystem\File;
17+
use Joomla\Filesystem\Folder;
18+
use Joomla\Filesystem\Path;
19+
20+
trait InstallerScriptTrait
21+
{
22+
/**
23+
* The extension name. This should be set in the installer script.
24+
*
25+
* @var string
26+
* @since __DEPLOY_VERSION__
27+
*/
28+
protected string $extension;
29+
30+
/**
31+
* Minimum PHP version required to install the extension
32+
*
33+
* @var string
34+
* @since __DEPLOY_VERSION__
35+
*/
36+
protected string $minimumPhp;
37+
38+
/**
39+
* Minimum Joomla! version required to install the extension
40+
*
41+
* @var string
42+
* @since __DEPLOY_VERSION__
43+
*/
44+
protected string $minimumJoomla;
45+
46+
/**
47+
* Allow downgrades of your extension
48+
*
49+
* Use at your own risk as if there is a change in functionality people may wish to downgrade.
50+
*
51+
* @var boolean
52+
* @since __DEPLOY_VERSION__
53+
*/
54+
protected bool $allowDowngrades = false;
55+
56+
/**
57+
* A list of files to be deleted
58+
*
59+
* @var array
60+
* @since __DEPLOY_VERSION__
61+
*/
62+
protected array $deleteFiles = [];
63+
64+
/**
65+
* A list of folders to be deleted
66+
*
67+
* @var array
68+
* @since __DEPLOY_VERSION__
69+
*/
70+
protected array $deleteFolders = [];
71+
72+
/**
73+
* The application object
74+
*
75+
* @var CMSApplicationInterface
76+
*
77+
* @since __DEPLOY_VERSION__
78+
*/
79+
private CMSApplicationInterface $application;
80+
81+
/**
82+
* Function called after the extension is installed.
83+
*
84+
* @param InstallerAdapter $adapter The adapter calling this method
85+
*
86+
* @return boolean True on success
87+
*
88+
* @since __DEPLOY_VERSION__
89+
*/
90+
public function install(InstallerAdapter $adapter): bool
91+
{
92+
return true;
93+
}
94+
95+
/**
96+
* Function called after the extension is updated.
97+
*
98+
* @param InstallerAdapter $adapter The adapter calling this method
99+
*
100+
* @return boolean True on success
101+
*
102+
* @since __DEPLOY_VERSION__
103+
*/
104+
public function update(InstallerAdapter $adapter): bool
105+
{
106+
return true;
107+
}
108+
109+
/**
110+
* Function called after the extension is uninstalled.
111+
*
112+
* @param InstallerAdapter $adapter The adapter calling this method
113+
*
114+
* @return boolean True on success
115+
*
116+
* @since __DEPLOY_VERSION__
117+
*/
118+
public function uninstall(InstallerAdapter $adapter): bool
119+
{
120+
return true;
121+
}
122+
123+
/**
124+
* Function called before extension installation/update/removal procedure commences.
125+
*
126+
* @param string $type The type of change (install or discover_install, update, uninstall)
127+
* @param InstallerAdapter $adapter The adapter calling this method
128+
*
129+
* @return boolean True on success
130+
*
131+
* @since __DEPLOY_VERSION__
132+
*/
133+
public function preflight(string $type, InstallerAdapter $adapter): bool
134+
{
135+
$this->extension = $adapter->getName();
136+
137+
if (!$this->checkCompatibility($type, $adapter)) {
138+
return false;
139+
}
140+
141+
if (!$this->checkDowngrade($type, $adapter)) {
142+
return false;
143+
}
144+
145+
return true;
146+
}
147+
148+
/**
149+
* Function called after extension installation/update/removal procedure commences.
150+
*
151+
* @param string $type The type of change (install or discover_install, update, uninstall)
152+
* @param InstallerAdapter $adapter The adapter calling this method
153+
*
154+
* @return boolean True on success
155+
*
156+
* @since __DEPLOY_VERSION__
157+
*/
158+
public function postflight(string $type, InstallerAdapter $adapter): bool
159+
{
160+
$this->removeFiles();
161+
162+
return true;
163+
}
164+
165+
/**
166+
* Check if the extension passes the minimum requirements to be installed
167+
*
168+
* @return boolean True on success
169+
*
170+
* @since __DEPLOY_VERSION__
171+
*/
172+
protected function checkCompatibility(string $type, InstallerAdapter $adapter): bool
173+
{
174+
// Check for the minimum PHP version before continuing
175+
if (!empty($this->minimumPhp) && version_compare(PHP_VERSION, $this->minimumPhp, '<')) {
176+
Log::add(Text::sprintf('JLIB_INSTALLER_MINIMUM_PHP', $this->minimumPhp), Log::WARNING, 'jerror');
177+
178+
return false;
179+
}
180+
181+
// Check for the minimum Joomla version before continuing
182+
if (!empty($this->minimumJoomla) && version_compare(JVERSION, $this->minimumJoomla, '<')) {
183+
Log::add(Text::sprintf('JLIB_INSTALLER_MINIMUM_JOOMLA', $this->minimumJoomla), Log::WARNING, 'jerror');
184+
185+
return false;
186+
}
187+
188+
return true;
189+
}
190+
191+
/**
192+
* Check if the extension is allowed to be downgraded
193+
*
194+
* @return boolean False when downgrade not allowed and new version is lower than old version otherwise true
195+
*
196+
* @since __DEPLOY_VERSION__
197+
*/
198+
protected function checkDowngrade(string $type, InstallerAdapter $adapter): bool
199+
{
200+
if ($type !== 'update' || $this->allowDowngrades) {
201+
return true;
202+
}
203+
204+
$oldManifest = $this->getOldManifest($adapter);
205+
206+
if ($oldManifest !== null && $oldManifest->version && version_compare($oldManifest->version, $adapter->getManifest()->version, '>')) {
207+
Log::add(Text::_('JLIB_INSTALLER_ERROR_DOWNGRADE'), Log::WARNING, 'jerror');
208+
209+
return false;
210+
}
211+
212+
return true;
213+
}
214+
215+
/**
216+
* Returns the manifest file if it exists or null
217+
*
218+
* @param InstallerAdapter $adapter
219+
*
220+
* @return SimpleXMLElement|null
221+
*
222+
* @since __DEPLOY_VERSION__
223+
*/
224+
protected function getOldManifest(InstallerAdapter $adapter): ?\SimpleXMLElement
225+
{
226+
$client = ApplicationHelper::getClientInfo('administrator', true);
227+
228+
$pathname = 'extension_' . $client->name;
229+
230+
$manifestPath = $adapter->getParent()->getPath($pathname) . '/' . $adapter->getParent()->getPath('manifest');
231+
232+
return is_file($manifestPath) ? $adapter->getParent()->isManifest($manifestPath) : null;
233+
}
234+
235+
/**
236+
* Remove the files and folders in the given array from
237+
*
238+
* @return void
239+
*
240+
* @since __DEPLOY_VERSION__
241+
*/
242+
protected function removeFiles(): void
243+
{
244+
if (!empty($this->deleteFiles)) {
245+
foreach ($this->deleteFiles as $file) {
246+
if (is_file(Path::check(JPATH_ROOT . $file)) && !File::delete(JPATH_ROOT . $file)) {
247+
Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER', $file));
248+
}
249+
}
250+
}
251+
252+
if (!empty($this->deleteFolders)) {
253+
foreach ($this->deleteFolders as $folder) {
254+
if (is_dir(Path::check(JPATH_ROOT . $folder)) && !Folder::delete(JPATH_ROOT . $folder)) {
255+
Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER', $folder));
256+
}
257+
}
258+
}
259+
}
260+
261+
/**
262+
* Returns the internal application or null when not set.
263+
*
264+
* @return CMSApplicationInterface|null
265+
*
266+
* @since __DEPLOY_VERSION__
267+
*/
268+
protected function getApplication(): ?CMSApplicationInterface
269+
{
270+
return $this->application;
271+
}
272+
273+
/**
274+
* Sets the application to use.
275+
*
276+
* @param CMSApplicationInterface $application The application
277+
*
278+
* @return void
279+
*
280+
* @since __DEPLOY_VERSION__
281+
*/
282+
public function setApplication(CMSApplicationInterface $application): void
283+
{
284+
$this->application = $application;
285+
}
286+
287+
/**
288+
* Creates the dashboard menu module
289+
*
290+
* @param string $dashboard The name of the dashboard
291+
* @param string $preset The name of the menu preset
292+
*
293+
* @return void
294+
*
295+
* @throws \Exception
296+
* @since __DEPLOY_VERSION__
297+
*/
298+
protected function addDashboardMenuModule(string $dashboard, string $preset)
299+
{
300+
$model = $this->getApplication()->bootComponent('com_modules')->getMVCFactory()->createModel('Module', 'Administrator', ['ignore_request' => true]);
301+
$module = [
302+
'id' => 0,
303+
'asset_id' => 0,
304+
'language' => '*',
305+
'note' => '',
306+
'published' => 1,
307+
'assignment' => 0,
308+
'client_id' => 1,
309+
'showtitle' => 0,
310+
'content' => '',
311+
'module' => 'mod_submenu',
312+
'position' => 'cpanel-' . $dashboard,
313+
];
314+
315+
// Try to get a translated module title, otherwise fall back to a fixed string.
316+
$titleKey = strtoupper('COM_' . $this->extension . '_DASHBOARD_' . $dashboard . '_TITLE');
317+
$title = Text::_($titleKey);
318+
$module['title'] = ($title === $titleKey) ? ucfirst($dashboard) . ' Dashboard' : $title;
319+
320+
$module['access'] = (int) $this->getApplication()->get('access', 1);
321+
$module['params'] = [
322+
'menutype' => '*',
323+
'preset' => $preset,
324+
'style' => 'System-none',
325+
];
326+
327+
if (!$model->save($module)) {
328+
$this->getApplication()->enqueueMessage(Text::sprintf('JLIB_INSTALLER_ERROR_COMP_INSTALL_FAILED_TO_CREATE_DASHBOARD', $model->getError()));
329+
}
330+
}
331+
}

0 commit comments

Comments
 (0)