Skip to content

Instantly share code, notes, and snippets.

@Septdir
Last active June 26, 2024 10:36
Show Gist options
  • Save Septdir/8f16ca1dd5e71d5247fa4e3b564f53e0 to your computer and use it in GitHub Desktop.
Save Septdir/8f16ca1dd5e71d5247fa4e3b564f53e0 to your computer and use it in GitHub Desktop.
Joomla Install Script

Установочный скрипт для Joomla

Пример установочного скрипта для Joomla

Joomla Install Script

Joomla Install Script Example

; @package Joomla Install Script
; @version __DEPLOY_VERSION__
; @author Septdir Workshop - septdir.com
; @copyright Copyright (c) 2018 - 2021 Septdir Workshop. All rights reserved.
; @license GNU/GPL license: https://www.gnu.org/copyleft/gpl.html
; @link https://www.septdir.com/
; Note : All ini files need to be saved as UTF-8
TYPE_NAME = "Joomla Install Script"
TYPE_NAME_DESCRIPTION = "Joomla Install Script Example"
TYPE_NAME_ERROR_COMPATIBLE_PHP = "This version is compatible only with PHP %s and later"
TYPE_NAME_ERROR_COMPATIBLE_JOOMLA = "This version is compatible only with Joomla %s and later"
TYPE_NAME_ERROR_COMPATIBLE_DATABASE = "This version is compatible only with MySQL %s or MariaDB %s and later"
; @package Joomla Install Script
; @version __DEPLOY_VERSION__
; @author Septdir Workshop - septdir.com
; @copyright Copyright (c) 2018 - 2021 Septdir Workshop. All rights reserved.
; @license GNU/GPL license: https://www.gnu.org/copyleft/gpl.html
; @link https://www.septdir.com/
; Note : All ini files need to be saved as UTF-8
TYPE_NAME = "Установочный скрипт для Joomla"
TYPE_NAME_DESCRIPTION = "Пример установочного скрипта для Joomla"
TYPE_NAME_ERROR_COMPATIBLE_PHP = "Данная версия совместима только с PHP %s и более поздними версиями"
TYPE_NAME_ERROR_COMPATIBLE_JOOMLA = "Данная версия совместима только с Joomla %s и более поздними версиями"
TYPE_NAME_ERROR_COMPATIBLE_DATABASE = "Данная версия совместима только с MySQL %s или MariaDB %s и более поздними версиями"
<?php
/*
* @package Joomla Install Script
* @version __DEPLOY_VERSION__
* @author Septdir Workshop - septdir.com
* @copyright Copyright (c) 2018 - 2021 Septdir Workshop. All rights reserved.
* @license GNU/GPL license: https://www.gnu.org/copyleft/gpl.html
* @link https://www.septdir.com/
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Application\AdministratorApplication;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerScriptInterface;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Version;
use Joomla\Database\DatabaseDriver;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
return new class () implements ServiceProviderInterface {
public function register(Container $container)
{
$container->set(InstallerScriptInterface::class,
new class ($container->get(AdministratorApplication::class)) implements InstallerScriptInterface {
/**
* The application object
*
* @var AdministratorApplication
*
* @since __DEPLOY_VERSION__
*/
protected AdministratorApplication $app;
/**
* The Database object.
*
* @var DatabaseDriver
*
* @since __DEPLOY_VERSION__
*/
protected DatabaseDriver $db;
/**
* Minimum Joomla version required to install the extension.
*
* @var string
*
* @since __DEPLOY_VERSION__
*/
protected string $minimumJoomla = '';
/**
* Minimum PHP version required to install the extension.
*
* @var string
*
* @since __DEPLOY_VERSION__
*/
protected string $minimumPhp = '';
/**
* Minimum MySQL version required to install the extension.
*
* @var string
*
* @since __DEPLOY_VERSION__
*/
protected string $minimumMySQL = '';
/**
* Minimum MariaDb version required to install the extension.
*
* @var string
*
* @since __DEPLOY_VERSION__
*/
protected string $minimumMariaDb = '';
/**
* Language constant for errors.
*
* @var string
*
* @since __DEPLOY_VERSION__
*/
protected string $constant = "";
/**
* Extension params for check.
*
* @var array
*
* @since __DEPLOY_VERSION__
*/
protected array $extensionParams = [];
/**
* Update methods.
*
* @var array
*
* @since __DEPLOY_VERSION__
*/
protected array $updateMethods = [];
/**
* Constructor.
*
* @param AdministratorApplication $app The application object.
*
* @since __DEPLOY_VERSION__
*/
public function __construct(AdministratorApplication $app)
{
$this->app = $app;
$this->db = Factory::getContainer()->get('DatabaseDriver');
}
/**
* 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
{
$this->enablePlugin($adapter);
return true;
}
/**
* 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
{
// Refresh media version
(new Version())->refreshMediaVersion();
return true;
}
/**
* 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
{
return true;
}
/**
* 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
{
// Check compatible
if (!$this->checkCompatible())
{
return false;
}
return true;
}
/**
* 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 ($type !== 'uninstall')
{
// Parse layouts
$this->parseLayouts($installer->getManifest()->layouts, $installer);
// Check databases
$this->checkTables($adapter);
// Check root record
$this->checkRootRecord('');
// Check extension params
$this->checkExtensionParams($adapter);
// Run updates script
if ($type === 'update')
{
foreach ($this->updateMethods as $method)
{
if (method_exists($this, $method))
{
$this->$method($adapter);
}
}
}
}
return true;
}
/**
* Enable plugin after installation.
*
* @param InstallerAdapter $adapter Parent object calling object.
*
* @since __DEPLOY_VERSION__
*/
protected function enablePlugin(InstallerAdapter $adapter)
{
// Prepare plugin object
$plugin = new \stdClass();
$plugin->type = 'plugin';
$plugin->element = $adapter->getElement();
$plugin->folder = (string) $adapter->getParent()->manifest->attributes()['group'];
$plugin->enabled = 1;
// Update record
$this->db->updateObject('#__extensions', $plugin, ['type', 'element', 'folder']);
}
/**
* Method to check compatible.
*
* @throws \Exception
*
* @return bool True on success, False on failure.
*
* @since __DEPLOY_VERSION__
*/
protected function checkCompatible(): bool
{
$app = Factory::getApplication();
// Check joomla version
if (!(new Version())->isCompatible($this->minimumJoomla))
{
$app->enqueueMessage(Text::sprintf($this->constant . '_ERROR_COMPATIBLE_JOOMLA', $this->minimumJoomla),
'error');
return false;
}
// Check PHP
if (!(version_compare(PHP_VERSION, $this->minimumPhp) >= 0))
{
$app->enqueueMessage(Text::sprintf($this->constant . '_ERROR_COMPATIBLE_PHP', $this->minimumPhp),
'error');
return false;
}
// Check database version
$db = $this->db;
$serverType = $db->getServerType();
$serverVersion = $db->getVersion();
if ($serverType == 'mysql' && stripos($serverVersion, 'mariadb') !== false)
{
$serverVersion = preg_replace('/^5\.5\.5-/', '', $serverVersion);
if (!(version_compare($serverVersion, $this->minimumMariaDb) >= 0))
{
$app->enqueueMessage(Text::sprintf($this->constant . '_ERROR_COMPATIBLE_DATABASE',
$this->minimumMySQL, $this->minimumMariaDb), 'error');
return false;
}
}
elseif ($serverType == 'mysql' && !(version_compare($serverVersion, $this->minimumMySQL) >= 0))
{
$app->enqueueMessage(Text::sprintf($this->constant . '_ERROR_COMPATIBLE_DATABASE',
$this->minimumMySQL, $this->minimumMariaDb), 'error');
return false;
}
return true;
}
/**
* Method to parse through a layouts element of the installation manifest and take appropriate action.
*
* @param SimpleXMLElement|null $element The XML node to process.
* @param Installer|null $installer Installer calling object.
*
* @return bool True on success.
*
* @since 1.0.0
*/
public function parseLayouts(SimpleXMLElement $element = null, Installer $installer = null): bool
{
if (!$element || !count($element->children()))
{
return false;
}
// Get destination
$folder = ((string) $element->attributes()->destination) ? '/' . $element->attributes()->destination : null;
$destination = Path::clean(JPATH_ROOT . '/layouts' . $folder);
// Get source
$folder = (string) $element->attributes()->folder;
$source = ($folder && file_exists($installer->getPath('source') . '/' . $folder))
? $installer->getPath('source') . '/' . $folder : $installer->getPath('source');
// Prepare files
$copyFiles = [];
foreach ($element->children() as $file)
{
$path['src'] = Path::clean($source . '/' . $file);
$path['dest'] = Path::clean($destination . '/' . $file);
// Is this path a file or folder?
$path['type'] = $file->getName() === 'folder' ? 'folder' : 'file';
if (basename($path['dest']) !== $path['dest'])
{
$newdir = dirname($path['dest']);
if (!Folder::create($newdir))
{
Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), Log::WARNING, 'jerror');
return false;
}
}
$copyFiles[] = $path;
}
return $installer->copyFiles($copyFiles, true);
}
/**
* Method to create database tables in not exist.
*
* @param InstallerAdapter $adapter Parent object calling object.
*
* @since __DEPLOY_VERSION__
*/
protected function checkTables(InstallerAdapter $adapter)
{
if ($sql = file_get_contents($adapter->getParent()->getPath('extension_administrator')
. '/sql/install.mysql.utf8.sql'))
{
$db = $this->db;
foreach ($db->splitSql($sql) as $query)
{
$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));
try
{
$db->execute();
}
catch (JDataBaseExceptionExecuting $e)
{
Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $e->getMessage()), Log::WARNING, 'jerror');
}
}
}
}
/**
* Method to create root record if don't exist.
*
* @param string|null $table Table name.
*
* @since __DEPLOY_VERSION__
*/
protected function checkRootRecord(string $table = null)
{
$db = $this->db;
// Get base categories
$query = $db->getQuery(true)
->select('id')
->from($table)
->where('id = 1');
$db->setQuery($query);
// Add root in not found
if (empty($db->loadResult()))
{
$root = new \stdClass();
$root->id = 1;
$root->parent_id = 0;
$root->lft = 0;
$root->rgt = 1;
$root->level = 0;
$root->path = '';
$root->alias = 'root';
$root->type = 'category';
$root->state = 1;
$db->insertObject($table, $root);
}
}
/**
* Method to check extension params and set if needed.
*
* @param InstallerAdapter $adapter Parent object calling object.
*
* @since __DEPLOY_VERSION__
*/
protected function checkExtensionParams(InstallerAdapter $adapter)
{
if (!empty($this->extensionParams))
{
$element = $adapter->getElement();
$folder = (string) $adapter->getParent()->manifest->attributes()['group'];
// Get extension
$db = $this->db;
$query = $db->getQuery(true)
->select(['extension_id', 'params'])
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = :element')
->bind(':element', $element);
if (!empty($folder))
{
$query->where($db->quoteName('folder') . ' = :folder')
->bind(':folder', $folder);
}
if ($extension = $db->setQuery($query)->loadObject())
{
$extension->params = new Registry($extension->params);
// Check params
$needUpdate = false;
foreach ($this->extensionParams as $path => $value)
{
if (!$extension->params->exists($path))
{
$needUpdate = true;
$extension->params->set($path, $value);
}
}
// Update
if ($needUpdate)
{
$extension->params = (string) $extension->params;
$db->updateObject('#__extensions', $extension, 'extension_id');
}
}
}
}
/**
* Method to parse through a layouts element of the installation manifest and remove the files that were installed.
*
* @param SimpleXMLElement|null $element The XML node to process.
*
* @return bool True on success.
*
* @since __DEPLOY_VERSION__
*/
protected function removeLayouts(SimpleXMLElement $element = null): bool
{
if (!$element || !count($element->children()))
{
return false;
}
// Get the array of file nodes to process
$files = $element->children();
// Get source
$folder = ((string) $element->attributes()->destination) ? '/' . $element->attributes()->destination : null;
$source = Path::clean(JPATH_ROOT . '/layouts' . $folder);
// Process each file in the $files array (children of $tagName).
foreach ($files as $file)
{
$path = Path::clean($source . '/' . $file);
// Actually delete the files/folders
if (is_dir($path))
{
$val = Folder::delete($path);
}
else
{
$val = File::delete($path);
}
if ($val === false)
{
Log::add('Failed to delete ' . $path, Log::WARNING, 'jerror');
return false;
}
}
if (!empty($folder))
{
Folder::delete($source);
}
return true;
}
});
}
};
<?xml version="1.0" encoding="utf-8"?>
<extension version="3.9" type="type" method="upgrade">
<name>TYPE_NAME</name>
<description>TYPE_NAME_DESCRIPTION</description>
<scriptfile>script.php</scriptfile>
<languages folder="language">
<language tag="en-GB">en-GB/en-GB.type_name.sys.ini</language>
<language tag="ru-RU">ru-RU/ru-RU.type_name.sys.ini</language>
</languages>
<layouts destination="type/name" folder="layouts">
<folder>folder_name</folder>
<filename>filename.php</filename>
</layouts>
<cli destination="" folder="cli">
<folder>folder_name</folder>
<filename>filename.php</filename>
</cli>
</extension>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment