Files
MokoOnyx/source/script.php
T
Jonathan Miller 0fe28e83ee
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 16s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 44s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 8s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 3s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
refactor: rename src/ to source/
2026-06-20 10:52:32 -05:00

785 lines
28 KiB
PHP

<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Template install/update/uninstall script.
* Joomla calls the methods in this class automatically during template
* install, update, and uninstall via the <scriptfile> element in
* templateDetails.xml.
*
* On first install, detects MokoCassiopeia and migrates template styles,
* parameters, menu assignments, and user files automatically.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerScriptInterface;
use Joomla\CMS\Log\Log;
class Tpl_MokoonyxInstallerScript implements InstallerScriptInterface
{
private const MIN_PHP = '8.1.0';
private const MIN_JOOMLA = '4.4.0';
private const OLD_NAME = 'mokocassiopeia';
private const NEW_NAME = 'mokoonyx';
private const OLD_DISPLAY = 'MokoCassiopeia';
private const NEW_DISPLAY = 'MokoOnyx';
public function preflight(string $type, InstallerAdapter $parent): bool
{
if ($type === 'update') {
$this->saveDownloadKey();
}
if (version_compare(PHP_VERSION, self::MIN_PHP, '<')) {
Factory::getApplication()->enqueueMessage(
sprintf('MokoOnyx requires PHP %s or later. You are running PHP %s.', self::MIN_PHP, PHP_VERSION),
'error'
);
return false;
}
if (version_compare(JVERSION, self::MIN_JOOMLA, '<')) {
Factory::getApplication()->enqueueMessage(
sprintf('MokoOnyx requires Joomla %s or later. You are running Joomla %s.', self::MIN_JOOMLA, JVERSION),
'error'
);
return false;
}
return true;
}
public function install(InstallerAdapter $parent): bool
{
$this->logMessage('MokoOnyx template installed.');
return true;
}
public function update(InstallerAdapter $parent): bool
{
$this->logMessage('MokoOnyx template updated.');
$synced = $this->syncCustomVariables($parent);
if ($synced > 0) {
Factory::getApplication()->enqueueMessage(
sprintf(
'MokoOnyx: %d new CSS variable(s) were added to your custom palette files. '
. 'Review them in your light.custom.css and/or dark.custom.css to customise the new defaults.',
$synced
),
'notice'
);
}
return true;
}
public function uninstall(InstallerAdapter $parent): bool
{
$this->logMessage('MokoOnyx template uninstalled.');
return true;
}
public function postflight(string $type, InstallerAdapter $parent): bool
{
$this->restoreDownloadKey();
$this->warnMissingLicenseKey();
if ($type === 'install' || $type === 'update') {
$this->migrateFromCassiopeia();
$this->replaceCassiopeiaReferences();
$this->clearFaviconStamp();
$this->cleanMediaFolder();
$this->removeDeletedFiles();
$this->removeDuplicateExtensions();
$this->lockExtension();
}
return true;
}
/**
* Replace MokoCassiopeia references in article content and module content.
*/
private function replaceCassiopeiaReferences(): void
{
$db = Factory::getDbo();
// Replace in article content (introtext + fulltext)
foreach (['introtext', 'fulltext'] as $col) {
try {
$query = $db->getQuery(true)
->update('#__content')
->set(
$db->quoteName($col) . ' = REPLACE(REPLACE('
. $db->quoteName($col) . ', '
. $db->quote(self::OLD_DISPLAY) . ', '
. $db->quote(self::NEW_DISPLAY) . '), '
. $db->quote(self::OLD_NAME) . ', '
. $db->quote(self::NEW_NAME) . ')'
)
->where(
'(' . $db->quoteName($col) . ' LIKE ' . $db->quote('%' . self::OLD_DISPLAY . '%')
. ' OR ' . $db->quoteName($col) . ' LIKE ' . $db->quote('%' . self::OLD_NAME . '%') . ')'
);
$db->setQuery($query)->execute();
$n = $db->getAffectedRows();
if ($n > 0) {
$this->logMessage("Replaced MokoCassiopeia in {$n} content row(s) ({$col}).");
}
} catch (\Throwable $e) {
$this->logMessage('Content replacement failed (' . $col . '): ' . $e->getMessage(), 'warning');
}
}
// Replace in module content (custom HTML modules etc.)
try {
$query = $db->getQuery(true)
->update('#__modules')
->set(
$db->quoteName('content') . ' = REPLACE(REPLACE('
. $db->quoteName('content') . ', '
. $db->quote(self::OLD_DISPLAY) . ', '
. $db->quote(self::NEW_DISPLAY) . '), '
. $db->quote(self::OLD_NAME) . ', '
. $db->quote(self::NEW_NAME) . ')'
)
->where(
'(' . $db->quoteName('content') . ' LIKE ' . $db->quote('%' . self::OLD_DISPLAY . '%')
. ' OR ' . $db->quoteName('content') . ' LIKE ' . $db->quote('%' . self::OLD_NAME . '%') . ')'
);
$db->setQuery($query)->execute();
$n = $db->getAffectedRows();
if ($n > 0) {
$this->logMessage("Replaced MokoCassiopeia in {$n} module(s).");
}
} catch (\Throwable $e) {
$this->logMessage('Module replacement failed: ' . $e->getMessage(), 'warning');
}
}
/**
* Delete the favicon stamp file so favicons and site.webmanifest
* are regenerated on the next page load after install/update.
* Also removes the old /images/favicons/ location.
*/
private function clearFaviconStamp(): void
{
// Clear new location stamp
$stampFile = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME . '/images/favicons/.favicon_generated';
if (is_file($stampFile)) {
@unlink($stampFile);
$this->logMessage('Cleared favicon stamp — will regenerate on next page load.');
}
// Remove old /images/favicons/ directory from previous versions
$oldDir = JPATH_ROOT . '/images/favicons';
if (is_dir($oldDir)) {
$files = glob($oldDir . '/*');
if ($files) {
foreach ($files as $file) {
@unlink($file);
}
}
@unlink($oldDir . '/.favicon_generated');
@rmdir($oldDir);
$this->logMessage('Removed old favicon directory: images/favicons/');
}
// Remove any favicon files left in the site root
$rootFavicons = ['favicon.ico', 'favicon.png', 'apple-touch-icon.png', 'site.webmanifest'];
foreach ($rootFavicons as $file) {
$path = JPATH_ROOT . '/' . $file;
if (is_file($path)) {
@unlink($path);
$this->logMessage('Removed root favicon: ' . $file);
}
}
}
/**
* Detect MokoCassiopeia and create matching MokoOnyx styles with the same params.
* Creates a MokoOnyx style copy for each MokoCassiopeia style.
*/
private function migrateFromCassiopeia(): void
{
$db = Factory::getDbo();
// Get all MokoCassiopeia styles
$query = $db->getQuery(true)
->select('*')
->from('#__template_styles')
->where($db->quoteName('template') . ' = ' . $db->quote(self::OLD_NAME))
->where($db->quoteName('client_id') . ' = 0');
$oldStyles = $db->setQuery($query)->loadObjectList();
if (empty($oldStyles)) {
$this->logMessage('No MokoCassiopeia styles found — fresh install.');
return;
}
$this->logMessage('MokoCassiopeia detected — creating ' . count($oldStyles) . ' matching MokoOnyx style(s).');
// Get the installer-created default MokoOnyx style (to apply params to it)
$query = $db->getQuery(true)
->select('id')
->from('#__template_styles')
->where($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME))
->where($db->quoteName('client_id') . ' = 0')
->order($db->quoteName('id') . ' ASC');
$defaultOnyxId = (int) $db->setQuery($query, 0, 1)->loadResult();
$firstStyle = true;
foreach ($oldStyles as $oldStyle) {
$newTitle = str_replace(self::OLD_DISPLAY, self::NEW_DISPLAY, $oldStyle->title);
$newTitle = str_replace(self::OLD_NAME, self::NEW_NAME, $newTitle);
$params = is_string($oldStyle->params)
? str_replace(self::OLD_NAME, self::NEW_NAME, $oldStyle->params)
: $oldStyle->params;
if ($firstStyle && $defaultOnyxId) {
// Update the installer-created default style with the first MokoCassiopeia style's params
$update = $db->getQuery(true)
->update('#__template_styles')
->set($db->quoteName('params') . ' = ' . $db->quote($params))
->set($db->quoteName('title') . ' = ' . $db->quote($newTitle))
->where('id = ' . $defaultOnyxId);
$db->setQuery($update)->execute();
// Set as default if MokoCassiopeia was default
if ($oldStyle->home == 1) {
$db->setQuery(
$db->getQuery(true)
->update('#__template_styles')
->set($db->quoteName('home') . ' = 1')
->where('id = ' . $defaultOnyxId)
)->execute();
$db->setQuery(
$db->getQuery(true)
->update('#__template_styles')
->set($db->quoteName('home') . ' = 0')
->where('id = ' . (int) $oldStyle->id)
)->execute();
$this->logMessage('Set MokoOnyx as default site template.');
}
$this->logMessage("Updated default MokoOnyx style with params: {$newTitle}");
$firstStyle = false;
continue;
}
// For additional styles: create new MokoOnyx style copies
$newStyle = clone $oldStyle;
unset($newStyle->id);
$newStyle->template = self::NEW_NAME;
$newStyle->title = $newTitle;
$newStyle->home = 0;
$newStyle->params = $params;
try {
$db->insertObject('#__template_styles', $newStyle, 'id');
$this->logMessage("Created MokoOnyx style: {$newTitle}");
} catch (\Throwable $e) {
$this->logMessage("Failed to create style {$newTitle}: " . $e->getMessage(), 'warning');
}
}
// 2. Copy user files (custom themes, user.css, user.js)
$this->copyUserFiles();
// 3. Notify admin
Factory::getApplication()->enqueueMessage(
'<strong>MokoOnyx has been installed as a replacement for MokoCassiopeia.</strong><br>'
. 'Your template settings and custom files have been migrated automatically. '
. 'MokoOnyx is now your active site template. '
. 'You can safely uninstall MokoCassiopeia from Extensions &rarr; Manage.',
'success'
);
}
/**
* Copy user-specific files from MokoCassiopeia media to MokoOnyx media.
*/
private function copyUserFiles(): void
{
$oldMedia = JPATH_ROOT . '/media/templates/site/' . self::OLD_NAME;
$newMedia = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME;
if (!is_dir($oldMedia) || !is_dir($newMedia)) {
return;
}
$userFiles = [
'css/theme/light.custom.css',
'css/theme/dark.custom.css',
'css/theme/light.custom.min.css',
'css/theme/dark.custom.min.css',
'css/user.css',
'css/user.min.css',
'js/user.js',
'js/user.min.js',
];
$copied = 0;
foreach ($userFiles as $relPath) {
$src = $oldMedia . '/' . $relPath;
$dst = $newMedia . '/' . $relPath;
if (is_file($src) && !is_file($dst)) {
$dstDir = dirname($dst);
if (!is_dir($dstDir)) {
mkdir($dstDir, 0755, true);
}
copy($src, $dst);
$copied++;
}
}
if ($copied > 0) {
$this->logMessage("Copied {$copied} user file(s) from MokoCassiopeia.");
}
}
private function syncCustomVariables(InstallerAdapter $parent): int
{
$templateDir = $parent->getParent()->getPath('source');
$syncScript = $templateDir . '/sync_custom_vars.php';
if (!is_file($syncScript)) {
$this->logMessage('CSS variable sync script not found at: ' . $syncScript, 'warning');
return 0;
}
require_once $syncScript;
if (!class_exists('MokoCssVarSync')) {
$this->logMessage('MokoCssVarSync class not found after loading script.', 'warning');
return 0;
}
try {
$results = MokoCssVarSync::run(JPATH_ROOT);
$totalAdded = 0;
foreach ($results as $filePath => $result) {
$totalAdded += count($result['added']);
if (!empty($result['added'])) {
$this->logMessage(sprintf('CSS sync: added %d variable(s) to %s', count($result['added']), basename($filePath)));
}
}
return $totalAdded;
} catch (\Throwable $e) {
$this->logMessage('CSS variable sync failed: ' . $e->getMessage(), 'error');
return 0;
}
}
/**
* Lock the extension to prevent uninstallation via Extension Manager.
*/
private function lockExtension(): void
{
$db = Factory::getDbo();
try {
$query = $db->getQuery(true)
->update('#__extensions')
->set($db->quoteName('locked') . ' = 1')
->where($db->quoteName('element') . ' = ' . $db->quote(self::NEW_NAME))
->where($db->quoteName('type') . ' = ' . $db->quote('template'));
$db->setQuery($query)->execute();
if ($db->getAffectedRows() > 0) {
$this->logMessage('MokoOnyx extension locked.');
}
} catch (\Throwable $e) {
$this->logMessage('Failed to lock extension: ' . $e->getMessage(), 'warning');
}
}
/**
* Clean the template media folder on install/update.
*
* - Removes stale .min files (regenerated automatically by MokoMinifyHelper)
* - Removes deprecated/renamed files from previous versions
* - Removes unminified vendor files (vendors ship .min only)
*/
private function cleanMediaFolder(): void
{
$mediaRoot = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME;
if (!is_dir($mediaRoot)) {
return;
}
$removed = 0;
// 1. Delete all .min.css and .min.js in project dirs (MokoMinifyHelper rebuilds them)
// Skip vendor/ — those are pre-minified originals
$projectDirs = ['css', 'js'];
foreach ($projectDirs as $dir) {
$path = $mediaRoot . '/' . $dir;
if (!is_dir($path)) continue;
$this->deleteMinFilesRecursive($path, $removed);
}
// 2. Remove unminified vendor files (vendors ship .min only)
$vendorUnminified = [
'vendor/fa7free/css/all.css',
'vendor/fa7free/css/brands.css',
'vendor/fa7free/css/fontawesome.css',
'vendor/fa7free/css/regular.css',
'vendor/fa7free/css/solid.css',
];
foreach ($vendorUnminified as $relPath) {
$file = $mediaRoot . '/' . $relPath;
if (is_file($file)) {
@unlink($file);
$removed++;
}
}
// 3. Remove deprecated files from previous versions
$deprecated = [
'css/custom.css', // Renamed to css/user.css
'js/custom.js', // Renamed to js/user.js
'css/template-rtl.css', // No longer used
];
foreach ($deprecated as $relPath) {
$file = $mediaRoot . '/' . $relPath;
if (is_file($file)) {
@unlink($file);
$removed++;
}
}
if ($removed > 0) {
$this->logMessage("Cleaned media folder: removed {$removed} stale/deprecated file(s).");
}
}
/**
* Recursively delete *.min.css and *.min.js in a directory.
*/
private function deleteMinFilesRecursive(string $dir, int &$count): void
{
$entries = scandir($dir);
if (!$entries) return;
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') continue;
$full = $dir . '/' . $entry;
if (is_dir($full)) {
$this->deleteMinFilesRecursive($full, $count);
} elseif (preg_match('/\.min\.(css|js)$/', $entry)) {
@unlink($full);
$count++;
}
}
}
/**
* Remove duplicate MokoOnyx extension entries from #__extensions.
*
* Re-installs or migrations can leave ghost rows. We keep the one
* that is locked (the active template) and delete any extras.
* Also removes stale MokoCassiopeia entries if present.
*/
private function removeDuplicateExtensions(): void
{
$db = Factory::getDbo();
// Find all MokoOnyx template entries
$rows = $db->setQuery(
$db->getQuery(true)
->select(['extension_id', 'locked'])
->from('#__extensions')
->where($db->quoteName('element') . ' = ' . $db->quote(self::NEW_NAME))
->where($db->quoteName('type') . ' = ' . $db->quote('template'))
->order('locked DESC, extension_id ASC')
)->loadObjectList();
if (count($rows) > 1) {
$keep = (int) $rows[0]->extension_id;
$removed = 0;
for ($i = 1; $i < count($rows); $i++) {
$staleId = (int) $rows[$i]->extension_id;
$db->setQuery(
$db->getQuery(true)
->delete('#__update_sites_extensions')
->where('extension_id = ' . $staleId)
)->execute();
$db->setQuery(
$db->getQuery(true)
->delete('#__extensions')
->where('extension_id = ' . $staleId)
)->execute();
$removed++;
}
if ($removed > 0) {
$this->logMessage("Removed {$removed} duplicate MokoOnyx extension(s). Kept ID {$keep}.");
}
}
// Remove faulty "template-mokoonyx" duplicate (wrong element name from bad install)
$faultyId = (int) $db->setQuery(
$db->getQuery(true)
->select('extension_id')
->from('#__extensions')
->where($db->quoteName('element') . ' = ' . $db->quote('template-mokoonyx'))
->where($db->quoteName('type') . ' = ' . $db->quote('template'))
)->loadResult();
if ($faultyId) {
$db->setQuery(
$db->getQuery(true)
->delete('#__update_sites_extensions')
->where('extension_id = ' . $faultyId)
)->execute();
$db->setQuery(
$db->getQuery(true)
->delete('#__extensions')
->where('extension_id = ' . $faultyId)
)->execute();
$db->setQuery(
$db->getQuery(true)
->delete('#__template_styles')
->where($db->quoteName('template') . ' = ' . $db->quote('template-mokoonyx'))
)->execute();
$this->logMessage('Removed faulty template-mokoonyx duplicate extension.');
}
// Remove stale MokoCassiopeia if not set as default
$oldExt = (int) $db->setQuery(
$db->getQuery(true)
->select('extension_id')
->from('#__extensions')
->where($db->quoteName('element') . ' = ' . $db->quote(self::OLD_NAME))
->where($db->quoteName('type') . ' = ' . $db->quote('template'))
)->loadResult();
if ($oldExt) {
$isDefault = (int) $db->setQuery(
$db->getQuery(true)
->select('COUNT(*)')
->from('#__template_styles')
->where($db->quoteName('template') . ' = ' . $db->quote(self::OLD_NAME))
->where($db->quoteName('home') . ' = 1')
)->loadResult();
if ($isDefault === 0) {
$db->setQuery(
$db->getQuery(true)
->delete('#__update_sites_extensions')
->where('extension_id = ' . $oldExt)
)->execute();
$db->setQuery(
$db->getQuery(true)
->delete('#__extensions')
->where('extension_id = ' . $oldExt)
)->execute();
$db->setQuery(
$db->getQuery(true)
->delete('#__template_styles')
->where($db->quoteName('template') . ' = ' . $db->quote(self::OLD_NAME))
)->execute();
$this->logMessage('Removed stale MokoCassiopeia extension and styles.');
}
}
}
/**
* Remove files and directories that were shipped in previous versions
* but have since been deleted from the package.
*
* Joomla's installer never deletes files on upgrade — it only
* adds/overwrites. This method fills that gap so stale overrides
* and deprecated assets don't linger on disk.
*
* Maintain this list: when you delete a file from the repo, add its
* path here (relative to the template root) so existing installs
* get cleaned up on the next update.
*/
private function removeDeletedFiles(): void
{
$templateRoot = JPATH_ROOT . '/templates/' . self::NEW_NAME;
// Paths relative to templates/mokoonyx/
$deletedFiles = [
// JoomGallery template overrides — removed in 02.19.00
'html/com_joomgallery/category/default.php',
'html/com_joomgallery/category/default_cat.php',
'html/com_joomgallery/category/index.html',
'html/com_joomgallery/gallery/default.php',
'html/com_joomgallery/gallery/index.html',
'html/com_joomgallery/image/default.php',
'html/com_joomgallery/image/index.html',
];
// Directories to remove (only if empty after file deletion)
$deletedDirs = [
'html/com_joomgallery/image',
'html/com_joomgallery/gallery',
'html/com_joomgallery/category',
'html/com_joomgallery',
];
$removed = 0;
foreach ($deletedFiles as $relPath) {
$file = $templateRoot . '/' . $relPath;
if (is_file($file)) {
@unlink($file);
$removed++;
}
}
foreach ($deletedDirs as $relPath) {
$dir = $templateRoot . '/' . $relPath;
if (is_dir($dir)) {
// Only remove if empty
$entries = @scandir($dir);
if ($entries && count($entries) <= 2) { // . and .. only
@rmdir($dir);
}
}
}
if ($removed > 0) {
$this->logMessage("Removed {$removed} deprecated file(s) from previous versions.");
}
}
private function logMessage(string $message, string $priority = 'info'): void
{
$priorities = [
'info' => Log::INFO,
'warning' => Log::WARNING,
'error' => Log::ERROR,
];
Log::addLogger(
['text_file' => 'mokoonyx.log.php'],
Log::ALL,
['mokoonyx']
);
Log::add($message, $priorities[$priority] ?? Log::INFO, 'mokoonyx');
}
private ?string $savedDownloadKey = null;
private function saveDownloadKey(): void
{
try {
$db = Factory::getDbo();
$db->setQuery(
$db->getQuery(true)
->select($db->quoteName('us.extra_query'))
->from($db->quoteName('#__update_sites', 'us'))
->join('INNER', $db->quoteName('#__update_sites_extensions', 'use') . ' ON use.update_site_id = us.update_site_id')
->join('INNER', $db->quoteName('#__extensions', 'e') . ' ON e.extension_id = use.extension_id')
->where($db->quoteName('e.element') . ' = ' . $db->quote('mokoonyx'))
->setLimit(1)
);
$key = $db->loadResult();
if (!empty($key)) {
$this->savedDownloadKey = $key;
}
} catch (\Throwable $e) {
}
}
private function restoreDownloadKey(): void
{
if ($this->savedDownloadKey === null) {
return;
}
try {
$db = Factory::getDbo();
$db->setQuery(
$db->getQuery(true)
->select($db->quoteName('us.update_site_id'))
->from($db->quoteName('#__update_sites', 'us'))
->join('INNER', $db->quoteName('#__update_sites_extensions', 'use') . ' ON use.update_site_id = us.update_site_id')
->join('INNER', $db->quoteName('#__extensions', 'e') . ' ON e.extension_id = use.extension_id')
->where($db->quoteName('e.element') . ' = ' . $db->quote('mokoonyx'))
->setLimit(1)
);
$siteId = (int) $db->loadResult();
if ($siteId > 0) {
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__update_sites'))
->set($db->quoteName('extra_query') . ' = ' . $db->quote($this->savedDownloadKey))
->where($db->quoteName('update_site_id') . ' = ' . $siteId)
)->execute();
}
} catch (\Throwable $e) {
}
}
private function warnMissingLicenseKey(): void
{
try {
$db = Factory::getDbo();
$db->setQuery(
$db->getQuery(true)
->select([$db->quoteName('update_site_id'), $db->quoteName('extra_query')])
->from($db->quoteName('#__update_sites'))
->where('(' . $db->quoteName('name') . ' LIKE ' . $db->quote('%MokoOnyx%') . ' OR ' . $db->quoteName('location') . ' LIKE ' . $db->quote('%MokoOnyx%') . ')')
->setLimit(1)
);
$site = $db->loadObject();
if ($site) {
$eq = (string) ($site->extra_query ?? '');
if (!empty($eq) && strpos($eq, 'dlid=') !== false) {
parse_str($eq, $p);
if (!empty($p['dlid'])) {
return;
}
}
$editUrl = 'index.php?option=com_installer&task=updatesite.edit&update_site_id=' . (int) $site->update_site_id;
} else {
$editUrl = 'index.php?option=com_installer&view=updatesites';
}
Factory::getApplication()->enqueueMessage(
'<strong>Moko Consulting License Key Required</strong> — '
. 'No download key is configured. Updates will not be available until a valid license key is entered. '
. '<a href="' . htmlspecialchars($editUrl, ENT_QUOTES, 'UTF-8') . '" class="btn btn-sm btn-warning ms-2">Enter License Key</a>',
'warning'
);
} catch (\Throwable $e) {
}
}
}