Skip to content

Commit

Permalink
Merge pull request #37091 from Digital-Peak/j4/installer/service
Browse files Browse the repository at this point in the history
[4.2] Extension installer script enhancements
  • Loading branch information
roland-d committed Mar 24, 2022
2 parents 31c9dd1 + 09a8547 commit b46a392
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 10 deletions.
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.
*
* @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.
*
* @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.
*
* @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);
}
}

0 comments on commit b46a392

Please sign in to comment.