3df40214f3
Repo Health / Access control (push) Failing after 4s
Standards Compliance / Secret Scanning (push) Failing after 2s
Standards Compliance / License Header Validation (push) Successful in 2s
Standards Compliance / Repository Structure Validation (push) Successful in 3s
Standards Compliance / Coding Standards Check (push) Failing after 3s
Standards Compliance / Workflow Configuration Check (push) Failing after 3s
Standards Compliance / Documentation Quality Check (push) Successful in 3s
Standards Compliance / README Completeness Check (push) Successful in 2s
Standards Compliance / Git Repository Hygiene (push) Successful in 3s
Standards Compliance / Version Consistency Check (push) Successful in 35s
Standards Compliance / Line Length Check (push) Failing after 3s
Standards Compliance / File Naming Standards (push) Successful in 2s
Standards Compliance / Insecure Code Pattern Detection (push) Successful in 3s
Standards Compliance / Script Integrity Validation (push) Successful in 39s
Standards Compliance / Code Complexity Analysis (push) Successful in 35s
Standards Compliance / Dead Code Detection (push) Successful in 4s
Standards Compliance / File Size Limits (push) Successful in 2s
Standards Compliance / Binary File Detection (push) Successful in 4s
Standards Compliance / TODO/FIXME Tracking (push) Successful in 3s
Standards Compliance / Code Duplication Detection (push) Successful in 39s
Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 34s
Standards Compliance / Broken Link Detection (push) Successful in 4s
Standards Compliance / API Documentation Coverage (push) Successful in 2s
Standards Compliance / Accessibility Check (push) Successful in 3s
Standards Compliance / Performance Metrics (push) Successful in 2s
Standards Compliance / Unused Dependencies Check (push) Successful in 39s
Standards Compliance / Enterprise Readiness Check (push) Failing after 37s
Standards Compliance / Terraform Configuration Validation (push) Successful in 5s
Standards Compliance / Repository Health Check (push) Failing after 34s
Repo Health / Release configuration (push) Has been skipped
Repo Health / Scripts governance (push) Has been skipped
Repo Health / Repository health (push) Has been skipped
Standards Compliance / Compliance Summary (push) Failing after 1s
Update MokoCassiopeia Payload / update-payload (push) Failing after 10s
If MokoOnyx is installed, lock it and set as default. Unlock MokoCassiopeia to allow uninstall. Falls back to MokoCassiopeia if Onyx not present. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1095 lines
27 KiB
PHP
1095 lines
27 KiB
PHP
<?php
|
|
/**
|
|
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
|
*
|
|
* This file is part of a Moko Consulting project.
|
|
*
|
|
* SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License (./LICENSE.md).
|
|
*
|
|
* FILE INFORMATION
|
|
* DEFGROUP: Joomla.Plugin
|
|
* INGROUP: MokoWaaS
|
|
* REPO: https://github.com/mokoconsulting-tech/mokowaas
|
|
* VERSION: 02.01.08
|
|
* PATH: /src/script.php
|
|
* BRIEF: Installation script for MokoWaaS plugin
|
|
* NOTE: Handles installation, update, and uninstallation tasks including language override deployment
|
|
*/
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\Installer\InstallerAdapter;
|
|
use Joomla\CMS\Installer\InstallerScriptInterface;
|
|
use Joomla\CMS\Language\Text;
|
|
use Joomla\CMS\Log\Log;
|
|
use Joomla\Filesystem\File;
|
|
use Joomla\Filesystem\Folder;
|
|
|
|
/**
|
|
* Installation script for MokoWaaS plugin
|
|
*
|
|
* This script handles the installation and uninstallation of language override files
|
|
* to Joomla's global language override directories.
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
class plgSystemMokoWaaSInstallerScript implements InstallerScriptInterface
|
|
{
|
|
/**
|
|
* Minimum Joomla version required to install the extension.
|
|
*
|
|
* @var string
|
|
* @since 02.01.08
|
|
*/
|
|
private $minimumJoomla = '5.0.0';
|
|
|
|
/**
|
|
* Minimum PHP version required to install the extension.
|
|
*
|
|
* @var string
|
|
* @since 02.01.08
|
|
*/
|
|
private $minimumPhp = '8.1.0';
|
|
|
|
/**
|
|
* Language tags supported by this plugin.
|
|
*
|
|
* @var array
|
|
* @since 02.01.08
|
|
*/
|
|
private $languageTags = ['en-GB', 'en-US'];
|
|
|
|
/**
|
|
* Called before any type of action.
|
|
*
|
|
* @param string $type Which action is happening (install|uninstall|discover_install|update)
|
|
* @param InstallerAdapter $adapter The object responsible for running this script
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
public function preflight($type, $adapter): bool
|
|
{
|
|
// Check minimum Joomla version
|
|
if (version_compare(JVERSION, $this->minimumJoomla, '<'))
|
|
{
|
|
Factory::getApplication()->enqueueMessage(
|
|
sprintf('This extension requires Joomla %s or later.', $this->minimumJoomla),
|
|
'error'
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// Check minimum PHP version
|
|
if (version_compare(PHP_VERSION, $this->minimumPhp, '<'))
|
|
{
|
|
Factory::getApplication()->enqueueMessage(
|
|
sprintf('This extension requires PHP %s or later.', $this->minimumPhp),
|
|
'error'
|
|
);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called after any type of action.
|
|
*
|
|
* @param string $type Which action is happening (install|uninstall|discover_install|update)
|
|
* @param InstallerAdapter $adapter The object responsible for running this script
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
public function postflight($type, $adapter): bool
|
|
{
|
|
// Only install overrides on install or update
|
|
if ($type === 'install' || $type === 'update')
|
|
{
|
|
$this->enableAndLockPlugin();
|
|
$this->ensureMokoCassiopeia();
|
|
$this->installLanguageOverrides();
|
|
$this->updateLoginSupportUrls();
|
|
$this->updateAtumBranding();
|
|
$this->registerActionLogExtension();
|
|
$this->sendInstallNotification($type);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called on installation.
|
|
*
|
|
* @param InstallerAdapter $adapter The object responsible for running this script
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
public function install(InstallerAdapter $adapter): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called on update.
|
|
*
|
|
* @param InstallerAdapter $adapter The object responsible for running this script
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
public function update(InstallerAdapter $adapter): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called on uninstallation.
|
|
*
|
|
* @param InstallerAdapter $adapter The object responsible for running this script
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
public function uninstall(InstallerAdapter $adapter): bool
|
|
{
|
|
$this->sendInstallNotification('uninstall');
|
|
$this->uninstallLanguageOverrides();
|
|
$this->unregisterActionLogExtension();
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Sentinel comment that marks the start of MokoWaaS overrides inside a Joomla override file. */
|
|
/**
|
|
* Enable, lock, and protect the plugin in #__extensions.
|
|
*
|
|
* Runs on both install and update so existing installs get locked.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function enableAndLockPlugin()
|
|
{
|
|
$db = Factory::getDbo();
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('enabled') . ' = 1')
|
|
->set($db->quoteName('locked') . ' = 0')
|
|
->set($db->quoteName('protected') . ' = 1')
|
|
->where($db->quoteName('element') . ' = '
|
|
. $db->quote('mokowaas'))
|
|
->where($db->quoteName('folder') . ' = '
|
|
. $db->quote('system'))
|
|
->where($db->quoteName('type') . ' = '
|
|
. $db->quote('plugin'))
|
|
);
|
|
$db->execute();
|
|
}
|
|
|
|
/**
|
|
* Ensure the active Moko template is installed and locked.
|
|
*
|
|
* Prefers MokoOnyx (successor). Falls back to MokoCassiopeia.
|
|
* If MokoOnyx is found, lock it and unlock MokoCassiopeia.
|
|
* If only MokoCassiopeia is found, lock it as before.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.00.03
|
|
*/
|
|
private function ensureMokoCassiopeia()
|
|
{
|
|
$db = Factory::getDbo();
|
|
|
|
// Check for MokoOnyx first (successor template)
|
|
$query = $db->getQuery(true)
|
|
->select([$db->quoteName('extension_id'), $db->quoteName('enabled')])
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('mokoonyx'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
|
|
$onyx = $db->setQuery($query)->loadObject();
|
|
|
|
if ($onyx)
|
|
{
|
|
// Lock and protect MokoOnyx
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('enabled') . ' = 1')
|
|
->set($db->quoteName('locked') . ' = 1')
|
|
->set($db->quoteName('protected') . ' = 1')
|
|
->where($db->quoteName('extension_id') . ' = ' . (int) $onyx->extension_id)
|
|
)->execute();
|
|
|
|
$this->setDefaultTemplate('mokoonyx', 0);
|
|
|
|
// Unlock MokoCassiopeia if present (allow uninstall)
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('locked') . ' = 0')
|
|
->set($db->quoteName('protected') . ' = 0')
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('mokocassiopeia'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('template'))
|
|
)->execute();
|
|
|
|
return;
|
|
}
|
|
|
|
// Fallback: MokoCassiopeia
|
|
$query = $db->getQuery(true)
|
|
->select([$db->quoteName('extension_id'), $db->quoteName('enabled')])
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('mokocassiopeia'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
|
|
$template = $db->setQuery($query)->loadObject();
|
|
|
|
if ($template)
|
|
{
|
|
// Lock, protect, and set as default
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('enabled') . ' = 1')
|
|
->set($db->quoteName('locked') . ' = 1')
|
|
->set($db->quoteName('protected') . ' = 1')
|
|
->where($db->quoteName('extension_id') . ' = '
|
|
. (int) $template->extension_id)
|
|
);
|
|
$db->execute();
|
|
|
|
$this->setDefaultTemplate('mokocassiopeia', 0);
|
|
|
|
return;
|
|
}
|
|
|
|
// Template not installed — install from bundled payload
|
|
$pluginPath = JPATH_PLUGINS . '/system/mokowaas';
|
|
$payloadZip = $pluginPath . '/payload/mokocassiopeia.zip';
|
|
|
|
if (!file_exists($payloadZip))
|
|
{
|
|
Factory::getApplication()->enqueueMessage(
|
|
'MokoCassiopeia payload not found at '
|
|
. $payloadZip,
|
|
'warning'
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
$tmpDir = JPATH_ROOT . '/tmp/mokocassiopeia';
|
|
|
|
try
|
|
{
|
|
|
|
// Extract the bundled zip
|
|
$archive = new \Joomla\Archive\Archive();
|
|
$archive->extract($payloadZip, $tmpDir);
|
|
|
|
// Release zips should have templateDetails.xml at root
|
|
// or one level deep
|
|
$installDir = $tmpDir;
|
|
|
|
if (!file_exists($tmpDir . '/templateDetails.xml'))
|
|
{
|
|
$xmlFiles = glob($tmpDir . '/*/templateDetails.xml');
|
|
|
|
if (!empty($xmlFiles))
|
|
{
|
|
$installDir = dirname($xmlFiles[0]);
|
|
}
|
|
else
|
|
{
|
|
Factory::getApplication()->enqueueMessage(
|
|
'MokoCassiopeia: templateDetails.xml not '
|
|
. 'found in archive.',
|
|
'warning'
|
|
);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
$installer = \Joomla\CMS\Installer\Installer::getInstance();
|
|
|
|
if ($installer->install($installDir))
|
|
{
|
|
$this->ensureMokoCassiopeia();
|
|
|
|
Factory::getApplication()->enqueueMessage(
|
|
'MokoCassiopeia installed and locked.',
|
|
'message'
|
|
);
|
|
}
|
|
else
|
|
{
|
|
Factory::getApplication()->enqueueMessage(
|
|
'MokoCassiopeia installation failed.',
|
|
'warning'
|
|
);
|
|
}
|
|
}
|
|
catch (\Exception $e)
|
|
{
|
|
Factory::getApplication()->enqueueMessage(
|
|
'MokoCassiopeia error: ' . $e->getMessage(),
|
|
'warning'
|
|
);
|
|
}
|
|
finally
|
|
{
|
|
if (is_dir($tmpDir))
|
|
{
|
|
Folder::delete($tmpDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set a template as the default for a given client.
|
|
*
|
|
* @param string $template Template element name
|
|
* @param int $clientId 0 = site, 1 = admin
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.01.01
|
|
*/
|
|
private function setDefaultTemplate($template, $clientId)
|
|
{
|
|
$db = Factory::getDbo();
|
|
|
|
// Unset all other defaults for this client
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__template_styles'))
|
|
->set($db->quoteName('home') . ' = 0')
|
|
->where($db->quoteName('client_id') . ' = ' . $clientId)
|
|
->where($db->quoteName('home') . ' = 1')
|
|
);
|
|
$db->execute();
|
|
|
|
// Set our template as default
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__template_styles'))
|
|
->set($db->quoteName('home') . ' = 1')
|
|
->where($db->quoteName('template') . ' = '
|
|
. $db->quote($template))
|
|
->where($db->quoteName('client_id') . ' = ' . $clientId)
|
|
);
|
|
$db->execute();
|
|
}
|
|
|
|
/**
|
|
* Parse an updates.xml and return the download URL.
|
|
*
|
|
* @param string $updatesUrl URL to the updates.xml file
|
|
*
|
|
* @return string|null Download URL or null on failure
|
|
*
|
|
* @since 02.01.01
|
|
*/
|
|
/**
|
|
* Send email notification on install or update.
|
|
*
|
|
* @param string $type install or update
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.01.02
|
|
*/
|
|
private function sendInstallNotification($type)
|
|
{
|
|
try
|
|
{
|
|
$config = Factory::getApplication()->getConfig();
|
|
$siteName = $config->get('sitename', 'Joomla Site');
|
|
$siteUrl = \Joomla\CMS\Uri\Uri::root();
|
|
// Read version from manifest XML
|
|
$manifestFile = JPATH_PLUGINS
|
|
. '/system/mokowaas/mokowaas.xml';
|
|
$version = '?.?.?';
|
|
|
|
if (file_exists($manifestFile))
|
|
{
|
|
$xml = simplexml_load_file($manifestFile);
|
|
|
|
if ($xml && isset($xml->version))
|
|
{
|
|
$version = (string) $xml->version;
|
|
}
|
|
}
|
|
|
|
$mailer = Factory::getMailer();
|
|
$mailer->addRecipient('webmaster@mokoconsulting.tech');
|
|
$mailer->setSubject(
|
|
sprintf('[%s] MokoWaaS %s — %s',
|
|
$siteName, $type, $version)
|
|
);
|
|
$mailer->setBody(
|
|
sprintf(
|
|
"MokoWaaS plugin was %sd on %s\n\n"
|
|
. "Version: %s\n"
|
|
. "Site: %s\n"
|
|
. "Time: %s\n"
|
|
. "PHP: %s\n"
|
|
. "Joomla: %s\n",
|
|
$type,
|
|
$siteName,
|
|
$version,
|
|
$siteUrl,
|
|
date('Y-m-d H:i:s T'),
|
|
PHP_VERSION,
|
|
JVERSION
|
|
)
|
|
);
|
|
$mailer->isHtml(false);
|
|
$mailer->Send();
|
|
}
|
|
catch (\Exception $e)
|
|
{
|
|
// Don't break install if email fails
|
|
}
|
|
}
|
|
|
|
private const BLOCK_START = '; ===== BEGIN MokoWaaS Overrides (do not edit this block) =====';
|
|
|
|
/** Sentinel comment that marks the end of MokoWaaS overrides inside a Joomla override file. */
|
|
private const BLOCK_END = '; ===== END MokoWaaS Overrides =====';
|
|
|
|
/**
|
|
* Build the placeholder → value map from the plugin's saved params.
|
|
*
|
|
* On first install the params row may not exist yet, so every value
|
|
* falls back to a sensible default.
|
|
*
|
|
* @return array Associative array of placeholder => replacement value
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function getPlaceholders()
|
|
{
|
|
$params = $this->getPluginParams();
|
|
|
|
return [
|
|
'{{BRAND_NAME}}' => $params->get('brand_name', 'MokoWaaS'),
|
|
'{{COMPANY_NAME}}' => $params->get('company_name', 'Moko Consulting'),
|
|
'{{SUPPORT_URL}}' => $params->get('support_url', 'https://mokoconsulting.tech'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Load the plugin's saved params from the database.
|
|
*
|
|
* @return \Joomla\Registry\Registry
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function getPluginParams()
|
|
{
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select($db->quoteName('params'))
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('mokowaas'))
|
|
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'));
|
|
|
|
$db->setQuery($query);
|
|
$json = $db->loadResult();
|
|
|
|
return new \Joomla\Registry\Registry($json ?: '{}');
|
|
}
|
|
|
|
/**
|
|
* Resolve placeholders in an array of language strings.
|
|
*
|
|
* @param array $strings Key/value pairs (values may contain {{…}} tokens)
|
|
*
|
|
* @return array The same array with placeholders replaced
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function resolvePlaceholders(array $strings)
|
|
{
|
|
$placeholders = $this->getPlaceholders();
|
|
$search = array_keys($placeholders);
|
|
$replace = array_values($placeholders);
|
|
|
|
foreach ($strings as $key => $value)
|
|
{
|
|
$strings[$key] = str_replace($search, $replace, $value);
|
|
}
|
|
|
|
return $strings;
|
|
}
|
|
|
|
/**
|
|
* Install language override files to Joomla's global override directories.
|
|
*
|
|
* Reads each source override template shipped with the plugin, resolves
|
|
* {{BRAND_NAME}} etc. from plugin params, then merges the resolved keys
|
|
* into the destination file inside a clearly delimited block. Existing
|
|
* overrides outside the block are never touched.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function installLanguageOverrides()
|
|
{
|
|
$app = Factory::getApplication();
|
|
$pluginPath = JPATH_PLUGINS . '/system/mokowaas';
|
|
|
|
$overrideSets = [
|
|
// [source folder relative to plugin, Joomla destination base]
|
|
['language/overrides', JPATH_ROOT . '/language/overrides', 'frontend'],
|
|
['administrator/language/overrides', JPATH_ADMINISTRATOR . '/language/overrides', 'administrator'],
|
|
];
|
|
|
|
foreach ($overrideSets as [$sourceDir, $destDir, $label])
|
|
{
|
|
foreach ($this->languageTags as $tag)
|
|
{
|
|
$source = $pluginPath . '/' . $sourceDir . '/' . $tag . '.override.ini';
|
|
$dest = $destDir . '/' . $tag . '.override.ini';
|
|
|
|
if (!file_exists($source))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!is_dir($destDir))
|
|
{
|
|
Folder::create($destDir);
|
|
}
|
|
|
|
$pluginOverrides = $this->resolvePlaceholders($this->parseLanguageFile($source));
|
|
|
|
if (empty($pluginOverrides))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ($this->mergeOverridesIntoFile($dest, $pluginOverrides))
|
|
{
|
|
$app->enqueueMessage(
|
|
sprintf('Installed %s language overrides for %s', $label, $tag),
|
|
'message'
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$app->enqueueMessage(
|
|
sprintf('Failed to install %s language overrides for %s', $label, $tag),
|
|
'warning'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the mod_loginsupport module params to point to
|
|
* Moko Consulting URLs at install time.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function updateLoginSupportUrls()
|
|
{
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select([$db->quoteName('id'), $db->quoteName('params')])
|
|
->from($db->quoteName('#__modules'))
|
|
->where($db->quoteName('module') . ' = '
|
|
. $db->quote('mod_loginsupport'));
|
|
|
|
$db->setQuery($query);
|
|
$modules = $db->loadObjectList();
|
|
|
|
if (empty($modules))
|
|
{
|
|
return;
|
|
}
|
|
|
|
$supportUrls = [
|
|
'forum_url' => 'https://mokoconsulting.tech/support',
|
|
'documentation_url' => 'https://mokoconsulting.tech/kb',
|
|
'news_url' => 'https://mokoconsulting.tech/news',
|
|
];
|
|
|
|
foreach ($modules as $module)
|
|
{
|
|
$params = new \Joomla\Registry\Registry(
|
|
$module->params ?: '{}'
|
|
);
|
|
|
|
foreach ($supportUrls as $key => $url)
|
|
{
|
|
$params->set($key, $url);
|
|
}
|
|
|
|
$update = $db->getQuery(true)
|
|
->update($db->quoteName('#__modules'))
|
|
->set($db->quoteName('params') . ' = '
|
|
. $db->quote($params->toString()))
|
|
->where($db->quoteName('id') . ' = '
|
|
. (int) $module->id);
|
|
|
|
$db->setQuery($update);
|
|
$db->execute();
|
|
}
|
|
|
|
Factory::getApplication()->enqueueMessage(
|
|
'Updated login support URLs.', 'message'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Set Atum admin template branding params at install time.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function updateAtumBranding()
|
|
{
|
|
$mediaBase = 'media/plg_system_mokowaas/';
|
|
|
|
$expected = [
|
|
'logoBrandLarge' => $mediaBase . 'logo.png',
|
|
'logoBrandSmall' => $mediaBase . 'favicon_256.png',
|
|
'loginLogo' => $mediaBase . 'logo.png',
|
|
'logoBrandLargeAlt' => '',
|
|
'logoBrandSmallAlt' => '',
|
|
'loginLogoAlt' => '',
|
|
'emptyLogoBrandLargeAlt' => '1',
|
|
'emptyLogoBrandSmallAlt' => '1',
|
|
'emptyLoginLogoAlt' => '1',
|
|
'hue' => 'hsl(219, 44%, 18%)',
|
|
'special-color' => '#1a2744',
|
|
'link-color' => '#0051ad',
|
|
];
|
|
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select([$db->quoteName('id'), $db->quoteName('params')])
|
|
->from($db->quoteName('#__template_styles'))
|
|
->where($db->quoteName('template') . ' = '
|
|
. $db->quote('atum'))
|
|
->where($db->quoteName('client_id') . ' = 1');
|
|
|
|
$db->setQuery($query);
|
|
$styles = $db->loadObjectList();
|
|
|
|
if (empty($styles))
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach ($styles as $style)
|
|
{
|
|
$params = new \Joomla\Registry\Registry(
|
|
$style->params ?: '{}'
|
|
);
|
|
|
|
foreach ($expected as $key => $value)
|
|
{
|
|
$params->set($key, $value);
|
|
}
|
|
|
|
$update = $db->getQuery(true)
|
|
->update($db->quoteName('#__template_styles'))
|
|
->set($db->quoteName('params') . ' = '
|
|
. $db->quote($params->toString()))
|
|
->where($db->quoteName('id') . ' = '
|
|
. (int) $style->id);
|
|
|
|
$db->setQuery($update);
|
|
$db->execute();
|
|
}
|
|
|
|
Factory::getApplication()->enqueueMessage(
|
|
'Updated Atum template branding.', 'message'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register the plugin in #__action_logs_extensions so it appears
|
|
* as a filterable extension in System > Action Logs.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function registerActionLogExtension()
|
|
{
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select('COUNT(*)')
|
|
->from($db->quoteName('#__action_logs_extensions'))
|
|
->where($db->quoteName('extension') . ' = '
|
|
. $db->quote('plg_system_mokowaas'));
|
|
|
|
$db->setQuery($query);
|
|
|
|
if ((int) $db->loadResult() > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
$row = (object) ['extension' => 'plg_system_mokowaas'];
|
|
$db->insertObject('#__action_logs_extensions', $row);
|
|
|
|
Factory::getApplication()->enqueueMessage(
|
|
'Registered MokoWaaS in Action Logs.', 'message'
|
|
);
|
|
|
|
// Register content type config for display formatting
|
|
$configQuery = $db->getQuery(true)
|
|
->select('COUNT(*)')
|
|
->from($db->quoteName('#__action_log_config'))
|
|
->where($db->quoteName('type_alias') . ' = '
|
|
. $db->quote('plg_system_mokowaas'));
|
|
|
|
$db->setQuery($configQuery);
|
|
|
|
if ((int) $db->loadResult() === 0)
|
|
{
|
|
$config = (object) [
|
|
'type_title' => 'MokoWaaS',
|
|
'type_alias' => 'plg_system_mokowaas',
|
|
'id_holder' => '',
|
|
'title_holder' => '',
|
|
'table_name' => '',
|
|
'text_prefix' => 'PLG_SYSTEM_MOKOWAAS',
|
|
];
|
|
|
|
$db->insertObject('#__action_log_config', $config);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the plugin from #__action_logs_extensions on uninstall.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function unregisterActionLogExtension()
|
|
{
|
|
$db = Factory::getDbo();
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__action_logs_extensions'))
|
|
->where($db->quoteName('extension') . ' = '
|
|
. $db->quote('plg_system_mokowaas'))
|
|
);
|
|
$db->execute();
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__action_log_config'))
|
|
->where($db->quoteName('type_alias') . ' = '
|
|
. $db->quote('plg_system_mokowaas'))
|
|
);
|
|
$db->execute();
|
|
}
|
|
|
|
/**
|
|
* Remove only MokoWaaS overrides from Joomla's global override files.
|
|
*
|
|
* Strips the delimited MokoWaaS block and any duplicate keys that appear
|
|
* outside the block (safety net for upgrades from older versions that wrote
|
|
* keys inline). All other content is preserved verbatim.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function uninstallLanguageOverrides()
|
|
{
|
|
$app = Factory::getApplication();
|
|
$pluginPath = JPATH_PLUGINS . '/system/mokowaas';
|
|
|
|
$overrideSets = [
|
|
['language/overrides', JPATH_ROOT . '/language/overrides', 'frontend'],
|
|
['administrator/language/overrides', JPATH_ADMINISTRATOR . '/language/overrides', 'administrator'],
|
|
];
|
|
|
|
foreach ($overrideSets as [$sourceDir, $destDir, $label])
|
|
{
|
|
foreach ($this->languageTags as $tag)
|
|
{
|
|
$source = $pluginPath . '/' . $sourceDir . '/' . $tag . '.override.ini';
|
|
$dest = $destDir . '/' . $tag . '.override.ini';
|
|
|
|
if (!file_exists($dest))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$pluginKeys = array_keys($this->parseLanguageFile($source));
|
|
|
|
if ($this->removeOverridesFromFile($dest, $pluginKeys))
|
|
{
|
|
$app->enqueueMessage(
|
|
sprintf('Removed %s language overrides for %s', $label, $tag),
|
|
'message'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merge plugin overrides into an existing Joomla override file.
|
|
*
|
|
* The method:
|
|
* 1. Reads the destination file (if it exists) and preserves every line.
|
|
* 2. Strips any previous MokoWaaS block so it can be rewritten cleanly.
|
|
* 3. Collects keys that exist outside the block (user-set overrides).
|
|
* 4. Appends a MokoWaaS block containing only keys NOT already
|
|
* defined by the user — existing customisations are never touched.
|
|
*
|
|
* @param string $dest Absolute path to the Joomla override file
|
|
* @param array $overrides Key/value pairs to inject
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function mergeOverridesIntoFile($dest, array $overrides)
|
|
{
|
|
$existingLines = [];
|
|
|
|
if (file_exists($dest))
|
|
{
|
|
$existingLines = file($dest, FILE_IGNORE_NEW_LINES);
|
|
}
|
|
|
|
// Strip any previous MokoWaaS block
|
|
$existingLines = $this->stripMokoWaaSBlock($existingLines);
|
|
|
|
// Collect keys already defined outside the block (user overrides)
|
|
$userKeys = [];
|
|
|
|
foreach ($existingLines as $line)
|
|
{
|
|
$trimmed = trim($line);
|
|
|
|
if ($trimmed !== '' && $trimmed[0] !== ';')
|
|
{
|
|
if (preg_match('/^([A-Z0-9_]+)\s*=/i', $trimmed, $m))
|
|
{
|
|
$userKeys[] = strtoupper($m[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove trailing blank lines so the block starts cleanly
|
|
while (!empty($existingLines)
|
|
&& trim(end($existingLines)) === '')
|
|
{
|
|
array_pop($existingLines);
|
|
}
|
|
|
|
// Build the MokoWaaS block — skip keys the user already set
|
|
$block = [];
|
|
$block[] = '';
|
|
$block[] = self::BLOCK_START;
|
|
$block[] = '; Auto-generated on '
|
|
. date('Y-m-d H:i:s') . ' — do not edit manually.';
|
|
|
|
foreach ($overrides as $key => $value)
|
|
{
|
|
if (!in_array(strtoupper($key), $userKeys, true))
|
|
{
|
|
$block[] = strtoupper($key) . '="' . $value . '"';
|
|
}
|
|
}
|
|
|
|
$block[] = self::BLOCK_END;
|
|
$block[] = '';
|
|
|
|
$content = implode("\n", array_merge($existingLines, $block));
|
|
|
|
return File::write($dest, $content);
|
|
}
|
|
|
|
/**
|
|
* Remove MokoWaaS overrides from an existing Joomla override file.
|
|
*
|
|
* Strips the delimited block and any stray keys that match, then rewrites
|
|
* the file. If the file would be empty (or comments-only) it is deleted.
|
|
*
|
|
* @param string $dest Absolute path to the override file
|
|
* @param array $keys The override keys to remove (uppercase)
|
|
*
|
|
* @return boolean True on success
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function removeOverridesFromFile($dest, array $keys)
|
|
{
|
|
if (!file_exists($dest))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$lines = file($dest, FILE_IGNORE_NEW_LINES);
|
|
|
|
// Strip the MokoWaaS block
|
|
$lines = $this->stripMokoWaaSBlock($lines);
|
|
|
|
// Also strip any stray keys that match (legacy installs)
|
|
$upperKeys = array_map('strtoupper', $keys);
|
|
$cleaned = [];
|
|
|
|
foreach ($lines as $line)
|
|
{
|
|
$trimmed = trim($line);
|
|
|
|
if ($trimmed !== '' && $trimmed[0] !== ';')
|
|
{
|
|
if (preg_match('/^([A-Z0-9_]+)\s*=/i', $trimmed, $m))
|
|
{
|
|
if (in_array(strtoupper($m[1]), $upperKeys, true))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
$cleaned[] = $line;
|
|
}
|
|
|
|
// Check whether any real keys remain
|
|
$hasKeys = false;
|
|
|
|
foreach ($cleaned as $line)
|
|
{
|
|
$trimmed = trim($line);
|
|
|
|
if ($trimmed !== '' && $trimmed[0] !== ';')
|
|
{
|
|
$hasKeys = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$hasKeys)
|
|
{
|
|
return File::delete($dest);
|
|
}
|
|
|
|
return File::write($dest, implode("\n", $cleaned) . "\n");
|
|
}
|
|
|
|
/**
|
|
* Remove the MokoWaaS sentinel block from an array of file lines.
|
|
*
|
|
* @param array $lines Lines of the file (no trailing newlines)
|
|
*
|
|
* @return array Lines with the block removed
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function stripMokoWaaSBlock(array $lines)
|
|
{
|
|
$out = [];
|
|
$inBlock = false;
|
|
|
|
foreach ($lines as $line)
|
|
{
|
|
if (trim($line) === self::BLOCK_START)
|
|
{
|
|
$inBlock = true;
|
|
continue;
|
|
}
|
|
|
|
if (trim($line) === self::BLOCK_END)
|
|
{
|
|
$inBlock = false;
|
|
continue;
|
|
}
|
|
|
|
if (!$inBlock)
|
|
{
|
|
$out[] = $line;
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Parse a language INI file and return the strings as an associative array.
|
|
*
|
|
* @param string $filePath The path to the language file
|
|
*
|
|
* @return array Array of language strings (key => value)
|
|
*
|
|
* @since 02.01.08
|
|
*/
|
|
private function parseLanguageFile($filePath)
|
|
{
|
|
$strings = [];
|
|
|
|
if (!file_exists($filePath))
|
|
{
|
|
return $strings;
|
|
}
|
|
|
|
$content = file_get_contents($filePath);
|
|
$lines = explode("\n", $content);
|
|
|
|
foreach ($lines as $line)
|
|
{
|
|
$line = trim($line);
|
|
|
|
// Skip empty lines and comments
|
|
if ($line === '' || $line[0] === ';')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Parse KEY="VALUE" format
|
|
if (preg_match('/^([A-Z0-9_]+)="(.+)"$/i', $line, $matches))
|
|
{
|
|
$strings[strtoupper($matches[1])] = $matches[2];
|
|
}
|
|
}
|
|
|
|
return $strings;
|
|
}
|
|
}
|