Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.2] Extension installer script enhancements #37091

Merged
merged 31 commits into from Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8002ec3
Basic setup
laoneo Feb 18, 2022
439a863
types
laoneo Feb 18, 2022
dd2ecb3
inject the container
laoneo Feb 18, 2022
2c55b95
cs
laoneo Feb 18, 2022
7323890
more docs
laoneo Feb 18, 2022
7d67080
more docs
laoneo Feb 18, 2022
607fa3f
Update libraries/src/Installer/InstallerAdapter.php
laoneo Feb 18, 2022
94d0e3c
Update libraries/src/Installer/InstallerAdapter.php
laoneo Feb 18, 2022
e4a6d9a
Update libraries/src/Installer/InstallerScript.php
laoneo Feb 18, 2022
f2c3240
Update libraries/src/Installer/InstallerScriptInterface.php
laoneo Feb 18, 2022
4495ac0
fix docs
laoneo Feb 19, 2022
a838c7c
Merge branch 'j4/installer/service' of github.com:Digital-Peak/joomla…
laoneo Feb 19, 2022
d0e82f9
better docs
laoneo Feb 19, 2022
19fd281
Merge branch '4.2-dev' into j4/installer/service
laoneo Feb 21, 2022
dbf12ac
Merge branch '4.2-dev' into j4/installer/service
laoneo Feb 25, 2022
ee2686c
Add deprecated message when a legacy script is used
laoneo Mar 2, 2022
45b4247
correct arguments
laoneo Mar 2, 2022
2b6eea5
Legacy returns alwas boolean and installer script doesnt't implement …
laoneo Mar 2, 2022
b472665
cast
laoneo Mar 2, 2022
f767dbe
Fallback container
laoneo Mar 2, 2022
d61b18f
deprecated trigger
laoneo Mar 2, 2022
c9a1afd
msg
laoneo Mar 2, 2022
9a87681
Merge branch '4.2-dev' into j4/installer/service
laoneo Mar 4, 2022
f33cc27
Use a child container
laoneo Mar 4, 2022
868ed85
Delegate
laoneo Mar 4, 2022
31648a0
cs
laoneo Mar 5, 2022
5bc8ca2
cs
laoneo Mar 5, 2022
a6582b5
Merge branch '4.2-dev' into j4/installer/service
laoneo Mar 7, 2022
7ed06af
Merge branch '4.2-dev' into j4/installer/service
laoneo Mar 9, 2022
3e26ed8
Merge branch '4.2-dev' into j4/installer/service
laoneo Mar 20, 2022
09a8547
Merge branch '4.2-dev' into j4/installer/service
laoneo Mar 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion libraries/src/Installer/Installer.php
Expand Up @@ -25,6 +25,7 @@
use Joomla\Database\Exception\ExecutionFailureException;
use Joomla\Database\Exception\PrepareStatementFailureException;
use Joomla\Database\ParameterType;
use Joomla\DI\ContainerAwareInterface;

/**
* Joomla base installer class
Expand Down Expand Up @@ -2682,6 +2683,13 @@ public function loadAdapter($adapter, $options = array())
return Factory::getContainer()->get($class);
}

return new $class($this, $this->getDbo(), $options);
$adapter = new $class($this, $this->getDbo(), $options);

if ($adapter instanceof ContainerAwareInterface)
{
$adapter->setContainer(Factory::getContainer());
}

return $adapter;
}
}
77 changes: 68 additions & 9 deletions libraries/src/Installer/InstallerAdapter.php
Expand Up @@ -20,14 +20,21 @@
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;
use Joomla\Database\DatabaseDriver;
use Joomla\DI\Container;
use Joomla\DI\ContainerAwareInterface;
use Joomla\DI\ContainerAwareTrait;
use Joomla\DI\Exception\ContainerNotFoundException;
use Joomla\DI\ServiceProviderInterface;

/**
* Abstract adapter for the installer.
*
* @since 3.4
*/
abstract class InstallerAdapter
abstract class InstallerAdapter implements ContainerAwareInterface
{
use ContainerAwareTrait;

/**
* Changelog URL of extensions
*
Expand Down Expand Up @@ -1007,23 +1014,75 @@ protected function setupScriptfile()
// If there is a manifest class file, lets load it; we'll copy it later (don't have dest yet)
$manifestScript = (string) $this->getManifest()->scriptfile;

if ($manifestScript)
// When no script file, do nothing
if (!$manifestScript)
{
return;
}

// Build a child container, so we do not overwrite the global one
// and start from scratch when multiple extensions are installed
try
{
$container = new Container($this->getContainer());
}
catch (ContainerNotFoundException $e)
{
@trigger_error('Container must be set.', E_USER_DEPRECATED);

// Fallback to the global container
$container = new Container(Factory::getContainer());
}

// The real location of the file
$manifestScriptFile = $this->parent->getPath('source') . '/' . $manifestScript;

// Load the file
$installer = require_once $manifestScriptFile;

// When the instance is a service provider, then register the container with it
if ($installer instanceof ServiceProviderInterface)
{
$manifestScriptFile = $this->parent->getPath('source') . '/' . $manifestScript;
$installer->register($container);
}

// When the returned object is an installer instance, use it directly
if ($installer instanceof InstallerScriptInterface)
{
$container->set(InstallerScriptInterface::class, $installer);
}

// When none is set, then use the legacy way
if (!$container->has(InstallerScriptInterface::class))
{
@trigger_error(
'Legacy installer files are deprecated and will be removed in 6.0. Use a service provider instead.',
E_USER_DEPRECATED
);

$classname = $this->getScriptClassName();

\JLoader::register($classname, $manifestScriptFile);

if (class_exists($classname))
if (!class_exists($classname))
{
// Create a new instance
$this->parent->manifestClass = new $classname($this);

// And set this so we can copy it later
$this->manifest_script = $manifestScript;
return;
}

$container->set(
InstallerScriptInterface::class,
function (Container $container) use ($classname)
{
return new LegacyInstallerScript(new $classname($this));
}
);
}

// Create a new instance
$this->parent->manifestClass = $container->get(InstallerScriptInterface::class);

// And set this so we can copy it later
$this->manifest_script = $manifestScript;
}

/**
Expand Down
76 changes: 76 additions & 0 deletions libraries/src/Installer/InstallerScriptInterface.php
@@ -0,0 +1,76 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

namespace Joomla\CMS\Installer;

\defined('_JEXEC') or die;

/**
* Base install script interface for use by extensions providing helper methods for common behaviours.
*
* @since __DEPLOY_VERSION__
*/
interface InstallerScriptInterface
{
/**
* Function called after the extension is installed.
*
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function install(InstallerAdapter $adapter): bool;

/**
* Function called after the extension is updated.
*
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function update(InstallerAdapter $adapter): bool;

/**
* Function called after the extension is uninstalled.
*
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function uninstall(InstallerAdapter $adapter): bool;

/**
* Function called before extension installation/update/removal procedure commences.
*
* @param string $type The type of change (install or discover_install, update, uninstall)
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function preflight(string $type, InstallerAdapter $adapter): bool;

/**
* Function called after extension installation/update/removal procedure commences.
laoneo marked this conversation as resolved.
Show resolved Hide resolved
*
* @param string $type The type of change (install or discover_install, update, uninstall)
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function postflight(string $type, InstallerAdapter $adapter): bool;
}
174 changes: 174 additions & 0 deletions libraries/src/Installer/LegacyInstallerScript.php
@@ -0,0 +1,174 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

namespace Joomla\CMS\Installer;

\defined('_JEXEC') or die;

/**
* Legacy installer script which delegates the methods to the internal instance when possible.
*
* @since __DEPLOY_VERSION__
*/
class LegacyInstallerScript implements InstallerScriptInterface
{
/**
* @var \stdClass
* @since __DEPLOY_VERSION__
*/
private $installerScript;

/**
* @param \stdClass $installerScript The script instance
*/
public function __construct($installerScript)
{
$this->installerScript = $installerScript;
}

/**
* Function called after the extension is installed.
*
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function install(InstallerAdapter $adapter): bool
{
if (!method_exists($this->installerScript, 'install'))
{
return true;
}

return (bool) $this->installerScript->install($adapter);
}

/**
* Function called after the extension is updated.
*
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function update(InstallerAdapter $adapter): bool
{
if (!method_exists($this->installerScript, 'update'))
{
return true;
}

return (bool) $this->installerScript->update($adapter);
}

/**
* Function called after the extension is uninstalled.
*
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function uninstall(InstallerAdapter $adapter): bool
{
if (!method_exists($this->installerScript, 'uninstall'))
{
return true;
}

return (bool) $this->installerScript->uninstall($adapter);
}

/**
* Function called before extension installation/update/removal procedure commences.
laoneo marked this conversation as resolved.
Show resolved Hide resolved
*
* @param string $type The type of change (install or discover_install, update, uninstall)
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function preflight(string $type, InstallerAdapter $adapter): bool
{
if (!method_exists($this->installerScript, 'preflight'))
{
return true;
}

return (bool) $this->installerScript->preflight($type, $adapter);
}

/**
* Function called after extension installation/update/removal procedure commences.
laoneo marked this conversation as resolved.
Show resolved Hide resolved
*
* @param string $type The type of change (install or discover_install, update, uninstall)
* @param InstallerAdapter $adapter The adapter calling this method
*
* @return boolean True on success
*
* @since __DEPLOY_VERSION__
*/
public function postflight(string $type, InstallerAdapter $adapter): bool
{
if (!method_exists($this->installerScript, 'postflight'))
{
return true;
}

return (bool) $this->installerScript->postflight($type, $adapter);
}

/**
* Sets the variable to the internal script.
*
* @param string $name The name of the variable
* @param mixed $value The value of the variable
*
* @return void
*
* @since __DEPLOY_VERSION__
*/
public function __set(string $name, $value)
{
$this->installerScript->$name = $value;
}

/**
* Returns the variable from the internal script.
*
* @param string $name The name of the variable
*
* @return mixed
*
* @since __DEPLOY_VERSION__
*/
public function __get(string $name)
{
return $this->installerScript->$name;
}

/**
* Calls the function with the given name on the internal script.
*
* @param string $name The name of the function
* @param array $arguments The arguments
*
* @return void
*
* @since __DEPLOY_VERSION__
*/
public function __call(string $name, array $arguments)
{
return call_user_func([$this->installerScript, $name], $arguments);
}
}