Файловый менеджер - Редактировать - /home/opticamezl/www/newok/osmylicensesmanager.tar
Назад
script.installer.php 0000604 00000002413 15172151744 0010560 0 ustar 00 <?php /** * @package ShackExtensionSupport * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2025 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackExtensionSupport. * * ShackExtensionSupport 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 2 of the License, or * (at your option) any later version. * * ShackExtensionSupport 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 * along with ShackExtensionSupport. If not, see <https://www.gnu.org/licenses/>. */ use Alledia\Installer\AbstractScript; // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die(); require_once 'library/Installer/include.php'; // phpcs:enable PSR1.Files.SideEffects // phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace class PlgSystemOsmylicensesmanagerInstallerScript extends AbstractScript { } language/en-GB/en-GB.lib_allediainstaller.sys.ini 0000604 00000014004 15172151744 0015565 0 ustar 00 ; @package AllediaInstaller ; @contact www.joomlashack.com, help@joomlashack.com ; @copyright Copyright (C) 2016 Open Sources Training, LLC, All rights reserved ; @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL ; ; Note : All ini files need to be saved as UTF-8 - No BOM ; ; @TODO: this file should be removed once all extensions have been updated LIB_ALLEDIAINSTALLER_CHANGE_LICENSE_KEY = "Change my license key" LIB_ALLEDIAINSTALLER_I_DONT_REMEMBER_MY_KEY = "I don't remember my key!" LIB_ALLEDIAINSTALLER_INSTALL_CANCELLED = "Install process canceled." LIB_ALLEDIAINSTALLER_LICENSE_KEY_ERROR = "We found an error trying to save your license key. Please try again inside the Joomlashack License Key Manager plugin. You can find it by going to Extensions -> Plugins -> search for 'license'." LIB_ALLEDIAINSTALLER_LICENSE_KEY_SUCCESS = "Your license key was saved successfully!" LIB_ALLEDIAINSTALLER_LICENSE_KEYS_MANAGER_REQUIRED = "You are using a Joomlashack Pro extension, but you don't have the Joomlashack License Key Manager plugin installed. Please install it and configure your license key in order to be able to update this extension." LIB_ALLEDIAINSTALLER_LICENSE_KEYS_PLACEHOLDER = "license key" LIB_ALLEDIAINSTALLER_LICENSED_AS = "%s and all related extensions are licensed as %s" LIB_ALLEDIAINSTALLER_MANIFEST_NOT_FOUND = "Manifest file not found: %s" LIB_ALLEDIAINSTALLER_MSG_LICENSE_KEYS_EMPTY = "* Update requires a valid license key." LIB_ALLEDIAINSTALLER_OBSOLETE_UNINSTALLED_FAIL = "Uninstalling %s %s, failed" LIB_ALLEDIAINSTALLER_OBSOLETE_UNINSTALLED_SUCCESS = "Uninstalling %s %s was successful" LIB_ALLEDIAINSTALLER_PUBLISHED = "Published" LIB_ALLEDIAINSTALLER_RELATED_EXTENSIONS = "Related Extensions" LIB_ALLEDIAINSTALLER_RELATED_INSTALL = "%s %s was successfully installed" LIB_ALLEDIAINSTALLER_RELATED_INSTALL_FAIL = "Installation of %s %s failed" LIB_ALLEDIAINSTALLER_RELATED_NOT_UNINSTALLED = "The %s %s won't be uninstalled because of possible dependencies" LIB_ALLEDIAINSTALLER_RELATED_UNINSTALL = "%s %s was successfully removed" LIB_ALLEDIAINSTALLER_RELATED_UNINSTALL_FAIL = "Unable to remove %s %s" LIB_ALLEDIAINSTALLER_RELATED_UPDATE = "%s %s was successfully updated" LIB_ALLEDIAINSTALLER_RELATED_UPDATE_FAIL = "Update of %s %s failed" LIB_ALLEDIAINSTALLER_RELATED_UPDATE_STATE_FAILED = "Failed, version %s" LIB_ALLEDIAINSTALLER_RELATED_UPDATE_STATE_INSTALLED = "Installed v%s" LIB_ALLEDIAINSTALLER_RELATED_UPDATE_STATE_SKIPED = "Skipped %s, the version %s is already installed" LIB_ALLEDIAINSTALLER_RELEASE_V = "Release: v%s" LIB_ALLEDIAINSTALLER_SAVE_LICENSE_KEY = "Save my license key" LIB_ALLEDIAINSTALLER_SHOW_DETAILS = "Show more details..." LIB_ALLEDIAINSTALLER_SORTED = "Sorted [%s]" LIB_ALLEDIAINSTALLER_THANKS_INSTALL = "Thanks for installing %s!" LIB_ALLEDIAINSTALLER_THANKS_UPDATE = "Thanks for updating %s!" LIB_ALLEDIAINSTALLER_WRONG_MYSQL = "Sorry, %s requires at least MySQL %s." LIB_ALLEDIAINSTALLER_WRONG_PHP = "Sorry, %s requires at least php %s." LIB_ALLEDIAINSTALLER_WRONG_PLATFORM = "Sorry, %s requires Joomla! %s." LIB_ALLEDIAINSTALLER_WRONG_PREVIOUS = "Sorry, updating %s requires at least version %s to be installed first." ; Copied from lib_shackinstaller.sys while we transition from old extension LIB_SHACKINSTALLER_CHANGE_LICENSE_KEY = "Change my license key" LIB_SHACKINSTALLER_I_DONT_REMEMBER_MY_KEY = "I don't remember my key!" LIB_SHACKINSTALLER_INSTALL_CANCELLED = "Install process canceled." LIB_SHACKINSTALLER_LICENSE_KEY_ERROR = "We found an error trying to save your license key. Please try again inside the Joomlashack License Key Manager plugin. You can find it by going to Extensions -> Plugins -> search for 'license'." LIB_SHACKINSTALLER_LICENSE_KEY_SUCCESS = "Your license key was saved successfully!" LIB_SHACKINSTALLER_LICENSE_KEYS_MANAGER_REQUIRED = "You are using a Joomlashack Pro extension, but you don't have the Joomlashack License Key Manager plugin installed. Please install it and configure your license key in order to be able to update this extension." LIB_SHACKINSTALLER_LICENSE_KEYS_PLACEHOLDER = "license key" LIB_SHACKINSTALLER_LICENSED_AS = "%s and all related extensions are licensed as %s" LIB_SHACKINSTALLER_MANIFEST_NOT_FOUND = "Manifest file not found: %s" LIB_SHACKINSTALLER_MSG_LICENSE_KEYS_EMPTY = "* Update requires a valid license key." LIB_SHACKINSTALLER_OBSOLETE_UNINSTALLED_FAIL = "Uninstalling %s %s, failed" LIB_SHACKINSTALLER_OBSOLETE_UNINSTALLED_SUCCESS = "Uninstalling %s %s was successful" LIB_SHACKINSTALLER_PUBLISHED = "Published" LIB_SHACKINSTALLER_RELATED_EXTENSIONS = "Related Extensions" LIB_SHACKINSTALLER_RELATED_INSTALL = "%s %s was successfully installed" LIB_SHACKINSTALLER_RELATED_INSTALL_FAIL = "Installation of %s %s failed" LIB_SHACKINSTALLER_RELATED_NOT_UNINSTALLED = "This extension does not uninstall %s %s" LIB_SHACKINSTALLER_RELATED_NOT_UNINSTALLED_SYSTEM = "The %s %s won't be uninstalled because of possible dependencies" LIB_SHACKINSTALLER_RELATED_UNINSTALL = "%s %s was successfully removed" LIB_SHACKINSTALLER_RELATED_UNINSTALL_FAIL = "Unable to remove %s %s" LIB_SHACKINSTALLER_RELATED_UPDATE = "%s %s was successfully updated" LIB_SHACKINSTALLER_RELATED_UPDATE_FAIL = "Update of %s %s failed" LIB_SHACKINSTALLER_RELATED_UPDATE_STATE_FAILED = "Failed, version %s" LIB_SHACKINSTALLER_RELATED_UPDATE_STATE_INSTALLED = "Installed v%s" LIB_SHACKINSTALLER_RELATED_UPDATE_STATE_SKIPED = "Skipped %s, the version %s is already installed" LIB_SHACKINSTALLER_RELEASE_V = "Release: v%s" LIB_SHACKINSTALLER_SAVE_LICENSE_KEY = "Save my license key" LIB_SHACKINSTALLER_SHOW_DETAILS = "Show more details..." LIB_SHACKINSTALLER_SORTED = "Sorted [%s]" LIB_SHACKINSTALLER_THANKS_INSTALL = "Thanks for installing %s!" LIB_SHACKINSTALLER_THANKS_UPDATE = "Thanks for updating %s!" LIB_SHACKINSTALLER_WRONG_MYSQL = "Sorry, %s requires at least MySQL %s." LIB_SHACKINSTALLER_WRONG_PHP = "Sorry, %s requires at least php %s." LIB_SHACKINSTALLER_WRONG_PLATFORM = "Sorry, %s requires Joomla! %s." LIB_SHACKINSTALLER_WRONG_PREVIOUS = "Sorry, updating %s requires at least version %s to be installed first." language/en-GB/en-GB.plg_system_osmylicensesmanager.ini 0000604 00000002752 15172151744 0017136 0 ustar 00 ; @package ShackExtensionSupport ; @contact www.joomlashack.com, help@joomlashack.com ; @copyright 2016-2025 Joomlashack.com. All rights reserved ; @license https://www.gnu.org/licenses/gpl.html GNU/GPL ; ; This file is part of ShackExtensionSupport. ; ; ShackExtensionSupport 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 2 of the License, or ; (at your option) any later version. ; ; ShackExtensionSupport 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 ; along with ShackExtensionSupport. If not, see <https://www.gnu.org/licenses/>. ; ; Note : All ini files need to be saved as UTF-8 - No BOM PLG_SYSTEM_OSMYLICENSESMANAGER = "System - Joomlashack Extension Support" PLG_SYSTEM_OSMYLICENSESMANAGER_DESCRIPTION = "For Pro licensed Joomlashack extensions, you must have a valid License Key to receive extension updates. Get your license key here: <a href=\"https://www.joomlashack.com/account/key/\" target=\"_blank\">Your Pro License Key</a>." PLG_SYSTEM_OSMYLICENSESMANAGER_FIELD_LICENSE_KEYS_LABEL = "License Key" PLG_SYSTEM_OSMYLICENSESMANAGER_FIELD_LICENSE_KEYS_DESC = "Joomlashack license key to update Pro extensions." language/en-GB/en-GB.shackdefaultfiles.ini 0000604 00000002203 15172151744 0014270 0 ustar 00 ; @package ShackDefaultFiles ; @contact www.joomlashack.com, help@joomlashack.com ; @copyright 2021-2024 Joomlashack.com. All rights reserved ; @license https://www.gnu.org/licenses/gpl.html GNU/GPL ; ; This file is part of ShackDefaultFiles. ; ; ShackDefaultFiles 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 2 of the License, or ; (at your option) any later version. ; ; ShackDefaultFiles 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 ; along with ShackDefaultFiles. If not, see <https://www.gnu.org/licenses/>. ; ; Note : All ini files need to be saved as UTF-8 - No BOM SHACKDEFAULTFILES_GO_PRO = "Go Pro to access more features" SHACKDEFAULTFILES_LEAVE_A_REVIEW_ON_JED = "Leave a review on the JED" SHACKDEFAULTFILES_LIKE_THIS_EXTENSION = "Like this extension?" language/en-GB/en-GB.plg_system_osmylicensesmanager.sys.ini 0000604 00000002464 15172151744 0017753 0 ustar 00 ; @package ShackExtensionSupport ; @contact www.joomlashack.com, help@joomlashack.com ; @copyright 2016-2025 Joomlashack.com. All rights reserved ; @license https://www.gnu.org/licenses/gpl.html GNU/GPL ; ; This file is part of ShackExtensionSupport. ; ; ShackExtensionSupport 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 2 of the License, or ; (at your option) any later version. ; ; ShackExtensionSupport 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 ; along with ShackExtensionSupport. If not, see <https://www.gnu.org/licenses/>. ; ; Note : All ini files need to be saved as UTF-8 - No BOM PLG_SYSTEM_OSMYLICENSESMANAGER = "System - Joomlashack Extension Support" PLG_SYSTEM_OSMYLICENSESMANAGER_DESCRIPTION = "For Pro licensed Joomlashack extensions, you must have a valid License Key to receive extension updates. Get your license key here: <a href=\"https://www.joomlashack.com/account/key/\" target=\"_blank\">Your Pro License Key</a>." language/en-GB/en-GB.lib_shackinstaller.sys.ini 0000604 00000007764 15172151744 0015302 0 ustar 00 ; @package ShackInstaller ; @contact www.joomlashack.com, help@joomlashack.com ; @copyright 2016-2023 Joomlashack.com. All rights reserved ; @license https://www.gnu.org/licenses/gpl.html GNU/GPL ; ; This file is part of ShackInstaller. ; ; ShackInstaller 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 2 of the License, or ; (at your option) any later version. ; ; ShackInstaller 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 ; along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. ; ; Note : All ini files need to be saved as UTF-8 - No BOM LIB_SHACKINSTALLER_ABORT_INSTALL = "Another extension is preventing installation. Please disable it and try again: %s" LIB_SHACKINSTALLER_ABORT_UNINSTALL = "Another extension has prevented completing the uninstll. Additional, related extensions may need to uninstalled." LIB_SHACKINSTALLER_CHANGE_LICENSE_KEY = "Change my license key" LIB_SHACKINSTALLER_I_DONT_REMEMBER_MY_KEY = "I don't remember my key!" LIB_SHACKINSTALLER_INSTALL_CANCELLED = "Install process canceled." LIB_SHACKINSTALLER_LICENSE_KEY_ERROR = "We found an error trying to save your license key. Please try again inside the Joomlashack License Key Manager plugin. You can find it by going to Extensions -> Plugins -> search for 'license'." LIB_SHACKINSTALLER_LICENSE_KEY_SUCCESS = "Your license key was saved successfully!" LIB_SHACKINSTALLER_LICENSE_KEYS_MANAGER_REQUIRED = "You are using a Joomlashack Pro extension, but you don't have the Joomlashack License Key Manager plugin installed. Please install it and configure your license key in order to be able to update this extension." LIB_SHACKINSTALLER_LICENSE_KEYS_PLACEHOLDER = "license key" LIB_SHACKINSTALLER_LICENSED_AS = "%s and all related extensions are licensed as %s" LIB_SHACKINSTALLER_MANIFEST_NOT_FOUND = "Manifest file not found: %s" LIB_SHACKINSTALLER_MSG_LICENSE_KEYS_EMPTY = "* Update requires a valid license key." LIB_SHACKINSTALLER_OBSOLETE_UNINSTALLED_FAIL = "Uninstalling %s %s, failed" LIB_SHACKINSTALLER_OBSOLETE_UNINSTALLED_SUCCESS = "Uninstalling %s %s was successful" LIB_SHACKINSTALLER_PUBLISHED = "Published" LIB_SHACKINSTALLER_RELATED_EXTENSIONS = "Related Extensions" LIB_SHACKINSTALLER_RELATED_INSTALL = "%s %s was successfully installed" LIB_SHACKINSTALLER_RELATED_INSTALL_FAIL = "Installation of %s %s failed" LIB_SHACKINSTALLER_RELATED_NOT_UNINSTALLED = "This extension does not uninstall %s %s" LIB_SHACKINSTALLER_RELATED_NOT_UNINSTALLED_SYSTEM = "The %s %s won't be uninstalled because of possible dependencies" LIB_SHACKINSTALLER_RELATED_UNINSTALL = "%s %s was successfully removed" LIB_SHACKINSTALLER_RELATED_UNINSTALL_FAIL = "Unable to remove %s %s" LIB_SHACKINSTALLER_RELATED_UPDATE = "%s %s was successfully updated" LIB_SHACKINSTALLER_RELATED_UPDATE_FAIL = "Update of %s %s failed" LIB_SHACKINSTALLER_RELATED_UPDATE_STATE_FAILED = "Failed, version %s" LIB_SHACKINSTALLER_RELATED_UPDATE_STATE_INSTALLED = "Installed v%s" LIB_SHACKINSTALLER_RELATED_UPDATE_STATE_SKIPED = "Skipped %s, the version %s is already installed" LIB_SHACKINSTALLER_RELEASE_V = "Release: v%s" LIB_SHACKINSTALLER_SAVE_LICENSE_KEY = "Save my license key" LIB_SHACKINSTALLER_SHOW_DETAILS = "Show more details..." LIB_SHACKINSTALLER_SORTED = "Sorted [%s]" LIB_SHACKINSTALLER_THANKS_INSTALL = "Thanks for installing %s!" LIB_SHACKINSTALLER_THANKS_UPDATE = "Thanks for updating %s!" LIB_SHACKINSTALLER_WRONG_MYSQL = "Sorry, %s requires at least MySQL %s." LIB_SHACKINSTALLER_WRONG_PHP = "Sorry, %s requires at least php %s." LIB_SHACKINSTALLER_WRONG_PLATFORM = "Sorry, %s requires Joomla! %s." LIB_SHACKINSTALLER_WRONG_PREVIOUS = "Sorry, updating %s requires at least version %s to be installed first." library/Free/PluginHelper.php 0000604 00000007754 15172151744 0012220 0 ustar 00 <?php /** * @package ShackExtensionSupport * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2025 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackExtensionSupport. * * ShackExtensionSupport 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 2 of the License, or * (at your option) any later version. * * ShackExtensionSupport 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 * along with ShackExtensionSupport. If not, see <https://www.gnu.org/licenses/>. */ namespace Alledia\OSMyLicensesManager\Free; use Alledia\Framework\Joomla\Extension\Licensed; // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die(); // phpcs:enable PSR1.Files.SideEffects /** * PluginHelper class */ abstract class PluginHelper { /** * @var string */ protected static $downloadBaseURL = 'https://deploy.ostraining.com/client/download/'; /** * Update the license key on the plugin params * * @param ?string $licenseKeys * * @return bool */ public static function updateLicenseKeys(?string $licenseKeys = ''): bool { $licenseKeys = PluginHelper::sanitizeKey($licenseKeys); // Update the extension params $extension = new Licensed('osmylicensesmanager', 'plugin', 'system'); $extension->params->set('license-keys', $licenseKeys); $extension->storeParams(); return true; } /** * Detects if the passed URL is our download URL, returning a boolean value. * * @param string $url * * @return bool */ public static function isOurDownloadURL(string $url): bool { return preg_match('#^' . static::$downloadBaseURL . '#', $url) === 1; } /** * Removes the license key from the URL and returns it. * * @param string $url * * @return string */ public static function getURLWithoutLicenseKey(string $url): string { if (static::isOurDownloadURL($url)) { $url = preg_replace('#^(' . static::$downloadBaseURL . '(free|pro|paid)/[^/]+/[^/]+).*$#i', '$1', $url); $url .= '/'; } return $url; } /** * Sanitizes the license key, making sure we have only valid chars. * * @param string $key * * @return string */ public static function sanitizeKey(string $key): string { return preg_replace('/[^a-z0-9,]/i', '', $key); } /** * Appends the license key to the URL and returns it. We recognize the url * for designated generic extensions using a default license key to allow * legacy customers to download updates. * * @param string $url * @param string $keys * * @return string */ public static function appendLicenseKeyToURL(string $url, string $keys): string { if (static::isOurDownloadURL($url)) { $url = PluginHelper::getURLWithoutLicenseKey($url); $sanitizedKeys = static::sanitizeKey($keys); if ($keys) { $encodedKeys = base64_encode($sanitizedKeys); $url .= $encodedKeys; } } return $url; } /** * Detects the license type based on the URL. If no license is detected, * returns false * * @param string $url * * @return ?string */ public static function getLicenseTypeFromURL(string $url): ?string { if (preg_match('#^' . static::$downloadBaseURL . '(free|pro)/#', $url, $matches)) { return $matches[1]; } return null; } } library/Installer/AbstractScript.php 0000604 00000221616 15172151744 0013621 0 ustar 00 <?php /** * @package ShackInstaller * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2023 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackInstaller. * * ShackInstaller 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 2 of the License, or * (at your option) any later version. * * ShackInstaller 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 * along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. */ namespace Alledia\Installer; use Alledia\Installer\Extension\Licensed; use JEventDispatcher; use JFormFieldCustomFooter; use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Application\CMSApplicationInterface; use Joomla\CMS\Factory; use Joomla\CMS\Installer\Installer; use Joomla\CMS\Installer\InstallerAdapter; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\CMS\Table\Extension; use Joomla\CMS\Table\Table; use Joomla\CMS\Uri\Uri; use Joomla\CMS\Version; use Joomla\Component\Plugins\Administrator\Model\PluginModel; use Joomla\Database\DatabaseDriver; use Joomla\Database\DatabaseInterface; use Joomla\Event\DispatcherInterface; use Joomla\Filesystem\File; use Joomla\Filesystem\Folder; use Joomla\Registry\Registry; use SimpleXMLElement; use Throwable; // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die(); require_once 'include.php'; // phpcs:enable PSR1.Files.SideEffects abstract class AbstractScript { public const VERSION = '2.6.1'; protected const TYPE_INSTALL = 'install'; protected const TYPE_DISCOVER_INSTALL = 'discover_install'; protected const TYPE_UPDATE = 'update'; protected const TYPE_UNINSTALL = 'uninstall'; /** * @var bool */ protected $outputAllowed = true; /** * @var CMSApplication */ protected $app = null; /** * @var DatabaseDriver */ protected $dbo = null; /** * @var string */ protected $schemaVersion = null; /** * @var JEventDispatcher|DispatcherInterface */ protected $dispatcher = null; /** * @var Installer */ protected $installer = null; /** * @var SimpleXMLElement */ protected $manifest = null; /** * @var SimpleXMLElement */ protected $previousManifest = null; /** * @var string */ protected $mediaFolder = null; /** * @var string */ protected $element = null; /** * @var string[] */ protected $systemExtensions = [ 'library..allediaframework', 'plugin.system.osmylicensesmanager', ]; /** * @var bool */ protected $isLicensesManagerInstalled = false; /** * @var Licensed */ protected $license = null; /** * @var string */ protected $licenseKey = null; /** * @var string */ protected $footer = null; /** * @var string */ protected $mediaURL = null; /** * @var string */ protected $type = null; /** * @var string */ protected $group = null; /** * List of tables and respective columns * * @var array */ protected $columns = null; /** * @var object[] */ protected $tableColumns = []; /** * @var object[] */ protected $tableIndexes = []; /** * @var object[] */ protected $tableConstraints = []; /** * @var array */ protected $tables = null; /** * Flag to cancel the installation * * @var bool */ protected $cancelInstallation = false; /** * Feedback of the install by related extension * * @var array */ protected $relatedExtensionFeedback = []; /** * @var string */ protected $welcomeMessage = null; /** * @var bool */ protected $debug = false; /** * @param InstallerAdapter $parent * * @return void * @throws \Exception */ public function __construct(InstallerAdapter $parent) { $this->sendDebugMessage('ShackInstaller v' . static::VERSION); $this->sendDebugMessage('Base v' . SHACK_INSTALLER_VERSION); $this->sendDebugMessage(__METHOD__); $this->initProperties($parent); } /** * cross-version method for getting a new installer instance * * @return Installer */ private function getNewInstaller(): Installer { $installer = new Installer(); if (Version::MAJOR_VERSION > 5) { $installer->setDatabase(Factory::getContainer()->get(DatabaseInterface::class)); } return $installer; } /** * @param InstallerAdapter $parent * * @return bool * @throws \Exception */ protected function checkInheritance(InstallerAdapter $parent): bool { $parentClasses = class_parents($this); $scriptClassName = array_pop($parentClasses); $scriptClass = new \ReflectionClass($scriptClassName); $sourcePath = dirname($scriptClass->getFileName()); $sourceBase = strpos($sourcePath, JPATH_PLUGINS) === 0 ? 3 : 2; $sourceVersion = AbstractScript::VERSION ?? '0.0.0'; $sourcePath = $this->cleanPath($sourcePath); $targetPath = $this->cleanPath(SHACK_INSTALLER_BASE); if ($sourcePath != $targetPath && version_compare($sourceVersion, SHACK_INSTALLER_COMPATIBLE, 'lt')) { $source = join('/', array_slice(explode('/', $sourcePath), 0, $sourceBase)); $errorMessage = 'LIB_SHACKINSTALLER_ABORT_' . ($parent->getRoute() == 'uninstall' ? 'UNINSTALL' : 'INSTALL'); Factory::getApplication()->enqueueMessage(Text::sprintf($errorMessage, $source), 'error'); $this->cancelInstallation = true; return false; } return true; } /** * @param string $path * * @return string */ protected function cleanPath(string $path): string { return str_replace(DIRECTORY_SEPARATOR, '/', str_replace(JPATH_ROOT . '/', '', $path)); } /** * @param InstallerAdapter $parent * * @return void * @throws \Exception */ protected function initProperties(InstallerAdapter $parent): void { $this->sendDebugMessage(__METHOD__); $this->app = Factory::getApplication(); $this->outputAllowed = JPATH_BASE == JPATH_ADMINISTRATOR; $language = Factory::getLanguage(); $language->load('lib_shackinstaller.sys', realpath(__DIR__ . '/../..')); if ($this->checkInheritance($parent) == false) { return; } try { $this->dbo = Version::MAJOR_VERSION > 4 ? Factory::getContainer()->get(DatabaseInterface::class) : Factory::getDbo(); $this->installer = $parent->getParent(); $this->manifest = $this->installer->getManifest(); $this->schemaVersion = $this->getSchemaVersion(); if ($media = $this->manifest->media) { $this->mediaFolder = JPATH_SITE . '/' . $media['folder'] . '/' . $media['destination']; } $attributes = $this->manifest->attributes(); $this->type = (string)$attributes['type']; $this->group = (string)$attributes['group']; // Get the previous manifest for use in upgrades $targetPath = $this->installer->getPath('extension_administrator') ?: $this->installer->getPath('extension_root'); $manifestPath = $targetPath . '/' . basename($this->installer->getPath('manifest')); if (is_file($manifestPath)) { $this->previousManifest = simplexml_load_file($manifestPath); } // Determine basepath for localized files $basePath = $this->installer->getPath('source'); if (is_dir($basePath)) { if ($this->type == 'component' && $basePath != $targetPath) { // For components sourced by manifest, need to find the admin folder if ($files = $this->manifest->administration->files) { if ($files = (string)$files['folder']) { $basePath .= '/' . $files; } } } } else { $basePath = $this->getExtensionPath( $this->type, (string)$this->manifest->alledia->element, $this->group ); } // All the files we want to load $languageFiles = [ $this->getFullElement(), ]; // Load from localized or core language folder foreach ($languageFiles as $languageFile) { $language->load($languageFile, $basePath) || $language->load($languageFile, JPATH_ADMINISTRATOR); } } catch (Throwable $error) { $this->cancelInstallation = true; $this->sendErrorMessage($error); } } /** * @return JEventDispatcher|DispatcherInterface */ protected function getDispatcher() { if ($this->dispatcher === null) { if (Version::MAJOR_VERSION < 4) { $this->dispatcher = JEventDispatcher::getInstance(); } else { $this->dispatcher = $this->app->getDispatcher(); } } return $this->dispatcher; } /** * @param InstallerAdapter $parent * * @return bool */ final public function install(InstallerAdapter $parent): bool { $this->sendDebugMessage(__METHOD__); try { return $this->customInstall($parent); } catch (Throwable $error) { $this->sendErrorMessage($error); } return false; } // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps /** * @param InstallerAdapter $parent * * @return bool */ final public function discover_install(InstallerAdapter $parent): bool { $this->sendDebugMessage(__METHOD__); try { if ($this->install($parent)) { return $this->customDiscoverInstall($parent); } } catch (Throwable $error) { $this->sendErrorMessage($error); } return false; } // phpcs:enable PSR1.Methods.CamelCapsMethodName.NotCamelCaps /** * @param InstallerAdapter $parent * * @return bool */ final public function update(InstallerAdapter $parent): bool { $this->sendDebugMessage(__METHOD__); try { return $this->customUpdate($parent); } catch (Throwable $error) { $this->sendErrorMessage($error); } return false; } /** * @param string $type * @param InstallerAdapter $parent * * @return bool * @throws \Exception */ final public function preFlight(string $type, InstallerAdapter $parent): bool { if ($this->cancelInstallation) { $this->sendDebugMessage('CANCEL: ' . __METHOD__); return false; } try { $this->sendDebugMessage(__METHOD__); $success = true; if ($type === 'update') { $this->clearUpdateServers(); } if (in_array($type, [static::TYPE_INSTALL, static::TYPE_UPDATE])) { // Check minimum target Joomla Platform if (isset($this->manifest->alledia->targetplatform)) { $targetPlatform = (string)$this->manifest->alledia->targetplatform; if ($this->validateTargetVersion(JVERSION, $targetPlatform) == false) { // Platform version is invalid. Displays a warning and cancel the install $targetPlatform = str_replace('*', 'x', $targetPlatform); $msg = Text::sprintf('LIB_SHACKINSTALLER_WRONG_PLATFORM', $this->getName(), $targetPlatform); $this->sendMessage($msg, 'warning'); $success = false; } } // Check for minimum mysql version if ($targetMySqlVersion = $this->manifest->alledia->mysqlminimum) { $targetMySqlVersion = (string)$targetMySqlVersion; if ($this->dbo->getServerType() == 'mysql') { $dbVersion = $this->dbo->getVersion(); if (stripos($dbVersion, 'maria') !== false) { // For MariaDB this is a bit of a punt. We'll assume any version of Maria will do $dbVersion = $targetMySqlVersion; } if ($this->validateTargetVersion($dbVersion, $targetMySqlVersion) == false) { // mySQL version too low $minimumMySql = str_replace('*', 'x', $targetMySqlVersion); $msg = Text::sprintf('LIB_SHACKINSTALLER_WRONG_MYSQL', $this->getName(), $minimumMySql); $this->sendMessage($msg, 'warning'); $success = false; } } } // Check for minimum php version if (isset($this->manifest->alledia->phpminimum)) { $targetPhpVersion = (string)$this->manifest->alledia->phpminimum; if ($this->validateTargetVersion(phpversion(), $targetPhpVersion) == false) { // php version is too low $minimumPhp = str_replace('*', 'x', $targetPhpVersion); $msg = Text::sprintf('LIB_SHACKINSTALLER_WRONG_PHP', $this->getName(), $minimumPhp); $this->sendMessage($msg, 'warning'); $success = false; } } // Check for minimum previous version $targetVersion = (string)$this->manifest->alledia->previousminimum; if ($type == static::TYPE_UPDATE && $targetVersion) { if (!$this->validatePreviousVersion($targetVersion)) { // Previous minimum is not installed $minimumVersion = str_replace('*', 'x', $targetVersion); $msg = Text::sprintf('LIB_SHACKINSTALLER_WRONG_PREVIOUS', $this->getName(), $minimumVersion); $this->sendMessage($msg, 'warning'); $success = false; } } } if ($success) { $success = $this->customPreFlight($type, $parent); } if ($success) { if ( $type !== static::TYPE_UNINSTALL && empty($this->manifest->alledia->obsolete->preflight) == false ) { $this->clearObsolete($this->manifest->alledia->obsolete->preflight); } } $this->cancelInstallation = $success == false; return $success; } catch (Throwable $error) { $this->sendErrorMessage($error); } return false; } /** * @param string $type * @param InstallerAdapter $parent * * @return void * @throws \Exception */ final public function postFlight(string $type, InstallerAdapter $parent): void { $this->sendDebugMessage(__METHOD__); if ($this->cancelInstallation) { $this->sendMessage('LIB_SHACKINSTALLER_INSTALL_CANCELLED', 'warning'); return; } try { /* * Joomla 4 now calls postFlight on uninstalls. Which is kinda cool actually. * But this code is problematic in that scenario */ if ($type != static::TYPE_UNINSTALL) { $this->clearObsolete(); $this->installRelated(); $this->addAllediaAuthorshipToExtension(); $this->element = (string)$this->manifest->alledia->element; // Check and publish/reorder the plugin, if required if ( $this->type === 'plugin' && in_array($type, [static::TYPE_INSTALL, static::TYPE_DISCOVER_INSTALL]) ) { $this->publishThisPlugin(); $this->reorderThisPlugin(); } // If Free, remove any Pro library $license = $this->getLicense(); if (!$license->isPro()) { $proLibraryPath = $license->getProLibraryPath(); if (is_dir($proLibraryPath)) { Folder::delete($proLibraryPath); } } } $this->customPostFlight($type, $parent); if ($type != static::TYPE_UNINSTALL) { $this->displayWelcome($type); } } catch (Throwable $error) { $this->sendErrorMessage($error); } } /** * @param InstallerAdapter $parent * * @return void * @throws \Exception */ final public function uninstall(InstallerAdapter $parent): void { $this->sendDebugMessage(__METHOD__); try { $this->uninstallRelated(); $this->customUninstall($parent); } catch (Throwable $error) { $this->sendErrorMessage($error); } } /** * @param int $number * @param string $error * @param ?string $file * @param ?int $line * * @return void */ final public static function errorHandler(int $number, string $error, ?string $file = null, ?int $line = null): void { try { $codes = get_defined_constants(true); $codes = $codes['Core']; $codes = array_filter( $codes, function ($key) { return strpos($key, 'E_') === 0; }, ARRAY_FILTER_USE_KEY ); $name = array_search($number, $codes); Factory::getApplication()->enqueueMessage( sprintf('%s: %s<br>(%s) %s', $name, $error, $line ?: 'NA', $file ?: 'NA'), 'warning' ); } catch (Throwable $error) { // ignore } } /** * For use in subclasses * * @param string $type * @param InstallerAdapter $parent * * @return bool * @throws Throwable */ protected function customPreFlight(string $type, InstallerAdapter $parent): bool { return true; } /** * For use in subclasses * * @param InstallerAdapter $parent * * @return bool * @throws Throwable */ protected function customInstall(InstallerAdapter $parent): bool { return true; } /** * For use in subclasses * * @param InstallerAdapter $parent * * @return bool * @throws Throwable */ protected function customDiscoverInstall(InstallerAdapter $parent): bool { return true; } /** * For use in subclasses * * @param InstallerAdapter $parent * * @return bool * @throws Throwable */ protected function customUpdate(InstallerAdapter $parent): bool { return true; } /** * For use in subclassses * * @param string $type * @param InstallerAdapter $parent * * @return void * @throws Throwable */ protected function customPostFlight(string $type, InstallerAdapter $parent): void { } /** * For use in subclasses * * @param InstallerAdapter $parent * * @return void * @throws Throwable */ protected function customUninstall(InstallerAdapter $parent): void { } /** * @return void * @throws \Exception */ private function installRelated(): void { $this->sendDebugMessage(__METHOD__); if ($this->manifest->alledia->relatedExtensions) { $source = $this->installer->getPath('source'); $extensionsPath = $source . '/extensions'; $defaultAttributes = $this->manifest->alledia->relatedExtensions->attributes(); $defaultDowngrade = $this->getXmlValue($defaultAttributes['downgrade'], 'bool'); $defaultPublish = $this->getXmlValue($defaultAttributes['publish'], 'bool'); foreach ($this->manifest->alledia->relatedExtensions->extension as $extension) { $path = $extensionsPath . '/' . $this->getXmlValue($extension); if (is_dir($path)) { $type = $this->getXmlValue($extension['type']); $element = $this->getXmlValue($extension['element']); $group = $this->getXmlValue($extension['group']); $key = md5(join(':', [$type, $element, $group])); $this->sendDebugMessage( sprintf('Related: %s%s/%s', $type, $group ? ($group . '/') : '', $element) ); try { if ($type == 'plugin' && in_array($group, ['search', 'finder'])) { if (is_dir(JPATH_ADMINISTRATOR . '/components/com_' . $group) == false) { // skip search/finder plugins based on installed components $this->sendDebugMessage( sprintf( 'Skipped/Uninstalled plugin %s', ucwords($group . ' ' . $element) ) ); $this->uninstallExtension($type, $element, $group); continue; } } $current = $this->findExtension($type, $element, $group); $isNew = empty($current); $typeName = ucwords(trim($group . ' ' . $type)); // Get data from the manifest $tmpInstaller = $this->getNewInstaller(); $tmpInstaller->setPath('source', $path); $tmpInstaller->setPath('parent', $this->installer->getPath('source')); $newManifest = $tmpInstaller->getManifest(); $newVersion = (string)$newManifest->version; $this->storeFeedbackForRelatedExtension($key, 'name', (string)$newManifest->name); $downgrade = $this->getXmlValue($extension['downgrade'], 'bool', $defaultDowngrade); if (($isNew || $downgrade) == false) { $currentManifestPath = $this->getManifestPath($type, $element, $group); $currentManifest = $this->getInfoFromManifest($currentManifestPath); // Avoid to update for an outdated version $currentVersion = $currentManifest->get('version'); if (version_compare($currentVersion, $newVersion, '>')) { // Store the state of the install/update $this->storeFeedbackForRelatedExtension( $key, 'message', Text::sprintf( 'LIB_SHACKINSTALLER_RELATED_UPDATE_STATE_SKIPED', $newVersion, $currentVersion ) ); // Skip the installation for this extension continue; } } $text = 'LIB_SHACKINSTALLER_RELATED_' . ($isNew ? 'INSTALL' : 'UPDATE'); if ($tmpInstaller->install($path)) { $this->sendMessage(Text::sprintf($text, $typeName, $element)); if ($isNew) { $current = $this->findExtension($type, $element, $group); if (is_object($current)) { if ($type === 'plugin') { if ($this->getXmlValue($extension['publish'], 'bool', $defaultPublish)) { $current->publish(); $this->storeFeedbackForRelatedExtension($key, 'publish', true); } if ($ordering = $this->getXmlValue($extension['ordering'])) { $this->setPluginOrder($current, $ordering); $this->storeFeedbackForRelatedExtension($key, 'ordering', $ordering); } } } } $this->storeFeedbackForRelatedExtension( $key, 'message', Text::sprintf('LIB_SHACKINSTALLER_RELATED_UPDATE_STATE_INSTALLED', $newVersion) ); } else { $this->sendMessage(Text::sprintf($text . '_FAIL', $typeName, $element), 'error'); $this->storeFeedbackForRelatedExtension( $key, 'message', Text::sprintf( 'LIB_SHACKINSTALLER_RELATED_UPDATE_STATE_FAILED', $newVersion ) ); } unset($tmpInstaller); } catch (Throwable $error) { $this->sendErrorMessage($error, false); } } } } } /** * Uninstall the related extensions that are useless without the component * * @return void * @throws \Exception */ private function uninstallRelated(): void { if ($this->manifest->alledia->relatedExtensions) { $defaultAttributes = $this->manifest->alledia->relatedExtensions->attributes(); $defaultUninstall = $this->getXmlValue($defaultAttributes['uninstall'], 'bool'); foreach ($this->manifest->alledia->relatedExtensions->extension as $extension) { $type = $this->getXmlValue($extension['type']); $element = $this->getXmlValue($extension['element']); $group = $this->getXmlValue($extension['group']); $uninstall = $this->getXmlValue($extension['uninstall'], 'bool', $defaultUninstall); $systemExtension = in_array(join('.', [$type, $group, $element]), $this->systemExtensions); if ($uninstall && $systemExtension == false) { $this->uninstallExtension($type, $element, $group); } else { $message = 'LIB_SHACKINSTALLER_RELATED_NOT_UNINSTALLED' . ($systemExtension ? '_SYSTEM' : ''); if ($type == 'plugin') { $type = $group . ' ' . $type; } $this->sendDebugMessage(Text::sprintf($message, ucwords($type), $element)); } } } } /** * @param string $type * @param string $element * @param ?string $group * * @return void * @throws \Exception */ private function uninstallExtension(string $type, string $element, ?string $group = null): void { if ($extension = $this->findExtension($type, $element, $group)) { $installer = $this->getNewInstaller(); $success = $installer->uninstall($extension->get('type'), $extension->get('extension_id')); $msg = 'LIB_SHACKINSTALLER_RELATED_UNINSTALL' . ($success ? '' : '_FAIL'); if ($type == 'plugin') { $type = $group . ' ' . $type; } $this->sendMessage( Text::sprintf($msg, ucwords($type), $element), $success ? 'message' : 'error' ); } } /** * @param ?string $type * @param ?string $element * @param ?string $group * * @return ?Extension * @throws \Exception */ final protected function findExtension(?string $type, ?string $element, ?string $group = null): ?Extension { // @TODO: Why do we need to use JTable? /** @var Extension $row */ $row = Table::getInstance('extension'); $prefixes = [ 'component' => 'com_', 'module' => 'mod_', ]; // Fix the element, if the prefix is not found if (array_key_exists($type, $prefixes)) { if (substr_count($element, $prefixes[$type]) === 0) { $element = $prefixes[$type] . $element; } } $terms = [ 'type' => $type, 'element' => $element, ]; if ($type === 'plugin') { $terms['folder'] = $group; } $eid = $row->find($terms); if ($eid) { if ($row->load($eid) == false) { throw new \Exception($row->getError()); } return $row; } return null; } /** * Set requested ordering for selected plugin extension * Accepted ordering arguments: * (n<=1 | first) First within folder * (* | last) Last within folder * (before:element) Before the named plugin * (after:element) After the named plugin * * @param Extension $extension * @param string $order * * @return void */ final protected function setPluginOrder(Extension $extension, string $order): void { if ($extension->get('type') == 'plugin' && empty($order) == false) { $db = $this->dbo; $query = $db->getQuery(true); $query->select('extension_id, element'); $query->from('#__extensions'); $query->where([ $db->quoteName('folder') . ' = ' . $db->quote($extension->get('folder')), $db->quoteName('type') . ' = ' . $db->quote($extension->get('type')), ]); $query->order($db->quoteName('ordering')); $plugins = $db->setQuery($query)->loadObjectList('element'); // Set the order only if plugin already successfully installed if (array_key_exists($extension->get('element'), $plugins)) { $target = [ $extension->get('element') => $plugins[$extension->get('element')], ]; $others = array_diff_key($plugins, $target); if ((is_numeric($order) && $order <= 1) || $order == 'first') { // First in order $neworder = array_merge($target, $others); } elseif (($order == '*') || ($order == 'last')) { // Last in order $neworder = array_merge($others, $target); } elseif (preg_match('/^(before|after):(\S+)$/', $order, $match)) { // place before or after named plugin $place = $match[1]; $element = $match[2]; $neworder = []; $previous = ''; foreach ($others as $plugin) { if ( (($place == 'before') && ($plugin->element == $element)) || (($place == 'after') && ($previous == $element)) ) { $neworder = array_merge($neworder, $target); } $neworder[$plugin->element] = $plugin; $previous = $plugin->element; } if (count($neworder) < count($plugins)) { // Make it last if the requested plugin isn't installed $neworder = array_merge($neworder, $target); } } else { $neworder = []; } if (count($neworder) == count($plugins)) { // Only reorder if have a validated new order BaseDatabaseModel::addIncludePath( JPATH_ADMINISTRATOR . '/components/com_plugins/models', 'PluginsModels' ); // @TODO: Model class is (\PluginsModelPlugin) in J3 but this works either way /** @var PluginModel $model */ $model = BaseDatabaseModel::getInstance('Plugin', 'PluginsModel'); $ids = []; foreach ($neworder as $plugin) { $ids[] = $plugin->extension_id; } $order = range(1, count($ids)); $model->saveorder($ids, $order); } } } } /** * Delete obsolete files, folders and extensions. * Files and folders are identified from the site * root path. * * @param ?SimpleXMLElement $obsolete * * @return void * @throws \Exception */ final protected function clearObsolete(?SimpleXMLElement $obsolete = null): void { $obsolete = $obsolete ?: $this->manifest->alledia->obsolete; $this->sendDebugMessage(__METHOD__ . '<pre>' . print_r($obsolete, 1) . '</pre>'); $this->clearOldSystemPlugin(); if ($obsolete) { if ($obsolete->extension) { foreach ($obsolete->extension as $extension) { $type = $this->getXmlValue($extension['type']); $element = $this->getXmlValue($extension['element']); $group = $this->getXmlValue($extension['group']); $current = $this->findExtension($type, $element, $group); if (empty($current) == false) { // Try to uninstall $tmpInstaller = $this->getNewInstaller(); $uninstalled = $tmpInstaller->uninstall($type, $current->get('extension_id')); $typeName = ucfirst(trim(($group ?: '') . ' ' . $type)); if ($uninstalled) { $this->sendMessage( Text::sprintf( 'LIB_SHACKINSTALLER_OBSOLETE_UNINSTALLED_SUCCESS', strtolower($typeName), $element ) ); } else { $this->sendMessage( Text::sprintf( 'LIB_SHACKINSTALLER_OBSOLETE_UNINSTALLED_FAIL', strtolower($typeName), $element ), 'error' ); } } } } if ($obsolete->file) { foreach ($obsolete->file as $file) { $path = JPATH_ROOT . '/' . trim((string)$file, '/'); if (is_file($path)) { File::delete($path); } } } if ($obsolete->folder) { foreach ($obsolete->folder as $folder) { $path = JPATH_ROOT . '/' . trim((string)$folder, '/'); if (is_dir($path)) { Folder::delete($path); } } } } $oldLanguageFiles = Folder::files(JPATH_ADMINISTRATOR . '/language', '\.lib_allediainstaller\.', true, true); foreach ($oldLanguageFiles as $oldLanguageFile) { File::delete($oldLanguageFile); } } /** * Finds the extension row for the main extension * * @return ?Extension * @throws \Exception */ final protected function findThisExtension(): ?Extension { return $this->findExtension( $this->getXmlValue($this->manifest['type']), $this->getXmlValue($this->manifest->alledia->element), $this->getXmlValue($this->manifest['group']) ); } /** * Use this in preflight to clear out obsolete update servers when the url has changed. * * @return void * @throws \Exception */ final protected function clearUpdateServers(): void { if ($extension = $this->findThisExtension()) { $db = $this->dbo; $query = $db->getQuery(true) ->select($db->quoteName('update_site_id')) ->from($db->quoteName('#__update_sites_extensions')) ->where($db->quoteName('extension_id') . '=' . (int)$extension->get('extension_id')); if ($list = $db->setQuery($query)->loadColumn()) { $query = $db->getQuery(true) ->delete($db->quoteName('#__update_sites_extensions')) ->where($db->quoteName('extension_id') . '=' . (int)$extension->get('extension_id')); $db->setQuery($query)->execute(); array_walk($list, 'intval'); $query = $db->getQuery(true) ->delete($db->quoteName('#__update_sites')) ->where($db->quoteName('update_site_id') . ' IN (' . join(',', $list) . ')'); $db->setQuery($query)->execute(); } } } /** * Get the full element, like com_myextension, lib_extension * * @param ?string $type * @param ?string $element * @param ?string $group * * @return string */ final protected function getFullElement( ?string $type = null, ?string $element = null, ?string $group = null ): string { $prefixes = [ 'component' => 'com', 'plugin' => 'plg', 'template' => 'tpl', 'library' => 'lib', 'cli' => 'cli', 'module' => 'mod', 'file' => 'file', ]; $type = $type ?: $this->type; $element = $element ?: (string)$this->manifest->alledia->element; $group = $group ?: $this->group; $fullElement = $prefixes[$type] . '_'; if ($type === 'plugin') { $fullElement .= $group . '_'; } return $fullElement . $element; } /** * @return Licensed */ final protected function getLicense(): Licensed { if ($this->license === null) { $this->license = new Licensed( (string)$this->manifest->alledia->namespace, $this->type, $this->group ); } return $this->license; } /** * @param string $manifestPath * * @return Registry */ final protected function getInfoFromManifest(string $manifestPath): Registry { $info = new Registry(); if (is_file($manifestPath)) { $xml = simplexml_load_file($manifestPath); $attributes = (array)$xml->attributes(); $attributes = $attributes['@attributes']; foreach ($attributes as $attribute => $value) { $info->set($attribute, $value); } foreach ($xml->children() as $e) { if (!$e->children()) { $info->set($e->getName(), (string)$e); } } } else { $relativePath = str_replace(JPATH_SITE . '/', '', $manifestPath); $this->sendMessage( Text::sprintf('LIB_SHACKINSTALLER_MANIFEST_NOT_FOUND', $relativePath), 'error' ); } return $info; } /** * @param string $type * @param string $element * @param ?string $group * * @return string */ final protected function getExtensionPath(string $type, string $element, ?string $group = ''): string { $folders = [ 'component' => 'administrator/components/', 'plugin' => 'plugins/', 'template' => 'templates/', 'library' => 'libraries/', 'cli' => 'cli/', 'module' => 'modules/', 'file' => 'administrator/manifests/files/', ]; $basePath = JPATH_SITE . '/' . $folders[$type]; switch ($type) { case 'plugin': $basePath .= $group . '/'; break; case 'module': if (!preg_match('/^mod_/', $element)) { $basePath .= 'mod_'; } break; case 'component': if (!preg_match('/^com_/', $element)) { $basePath .= 'com_'; } break; case 'template': if (preg_match('/^tpl_/', $element)) { $element = str_replace('tpl_', '', $element); } break; } if ($type !== 'file') { $basePath .= $element; } return $basePath; } /** * @param string $type * @param string $element * @param ?string $group * * @return int */ final protected function getExtensionId(string $type, string $element, ?string $group = ''): int { $db = $this->dbo; $query = $db->getQuery(true) ->select('extension_id') ->from('#__extensions') ->where([ $db->quoteName('element') . ' = ' . $db->quote($element), $db->quoteName('folder') . ' = ' . $db->quote($group), $db->quoteName('type') . ' = ' . $db->quote($type), ]); $db->setQuery($query); return (int)$db->loadResult(); } /** * Get the path for the manifest file * * @return string The path */ final protected function getManifestPath($type, $element, $group = ''): string { switch ($type) { case 'library': case 'file': $folders = [ 'library' => 'libraries', 'file' => 'files', ]; $manifestPath = JPATH_SITE . '/administrator/manifests/' . $folders[$type] . '/' . $element . '.xml'; $installer = $this->getNewInstaller(); if (!file_exists($manifestPath) || !$installer->isManifest($manifestPath)) { $manifestPath = false; } break; default: $basePath = $this->getExtensionPath($type, $element, $group); $installer = $this->getNewInstaller(); $installer->setPath('source', $basePath); $installer->getManifest(); $manifestPath = $installer->getPath('manifest'); break; } return $manifestPath; } /** * Check if it needs to publish the extension * * @return void * @throws \Exception */ final protected function publishThisPlugin(): void { $attributes = $this->manifest->alledia->element->attributes(); $publish = (string)$attributes['publish']; if ($publish === 'true' || $publish === '1') { $extension = $this->findThisExtension(); $extension->publish(); } } /** * Check if it needs to reorder the extension * * @return void * @throws \Exception */ final protected function reorderThisPlugin(): void { $attributes = $this->manifest->alledia->element->attributes(); $ordering = (string)$attributes['ordering']; if ($ordering !== '') { $extension = $this->findThisExtension(); $this->setPluginOrder($extension, $ordering); } } /** * Stores feedback data for related extensions to display after install * * @param string $key * @param string $property * @param string $value * * @return void */ final protected function storeFeedbackForRelatedExtension(string $key, string $property, string $value): void { $this->sendDebugMessage(sprintf( '%s<br>**** %s-%s-%s<br><br>', __METHOD__, $key, $property, $value )); if (empty($this->relatedExtensionFeedback[$key])) { $this->relatedExtensionFeedback[$key] = []; } $this->relatedExtensionFeedback[$key][$property] = $value; } /** * This method add a mark to the extensions, allowing to detect our extensions * on the extensions table. * * @return void * @throws \Exception */ final protected function addAllediaAuthorshipToExtension(): void { if ($extension = $this->findThisExtension()) { $db = $this->dbo; // Update the extension $customData = json_decode($extension->get('custom_data')) ?: (object)[]; $customData->author = 'Joomlashack'; $query = $db->getQuery(true) ->update($db->quoteName('#__extensions')) ->set($db->quoteName('custom_data') . '=' . $db->quote(json_encode($customData))) ->where($db->quoteName('extension_id') . '=' . (int)$extension->get('extension_id')); $db->setQuery($query)->execute(); // Update the Alledia framework // @TODO: remove this after libraries be able to have a custom install script $query = $db->getQuery(true) ->update($db->quoteName('#__extensions')) ->set($db->quoteName('custom_data') . '=' . $db->quote('{"author":"Joomlashack"}')) ->where([ $db->quoteName('type') . '=' . $db->quote('library'), $db->quoteName('element') . '=' . $db->quote('allediaframework'), ]); $db->setQuery($query)->execute(); } } /** * Add styles to the output. Used because when the postFlight * method is called, we can't add stylesheets to the head. * * @param mixed $stylesheets * * @return void */ final protected function addStyle($stylesheets): void { if (is_string($stylesheets)) { $stylesheets = [$stylesheets]; } foreach ($stylesheets as $path) { if (file_exists($path)) { $style = file_get_contents($path); echo '<style>' . $style . '</style>'; } } } /** * On new component install, this will check and fix any menus * that may have been created in a previous installation. * * @return void * @throws \Exception */ final protected function fixMenus(): void { if ($this->type == 'component') { $db = $this->dbo; if ($extension = $this->findThisExtension()) { $id = $extension->get('extension_id'); $option = $extension->get('name'); $query = $db->getQuery(true) ->update('#__menu') ->set('component_id = ' . $db->quote($id)) ->where([ 'type = ' . $db->quote('component'), 'link LIKE ' . $db->quote("%option={$option}%"), ]); $db->setQuery($query)->execute(); // Check hidden admin menu option // @TODO: Remove after Joomla! incorporates this natively $menuElement = $this->manifest->administration->menu; if (in_array((string)$menuElement['hidden'], ['true', 'hidden'])) { $menu = Table::getInstance('Menu'); $menu->load(['component_id' => $id, 'client_id' => 1]); if ($menu->id) { $menu->delete(); } } } } } /** * Parses a conditional string, returning a Boolean value (default: false). * For now it only supports an extension name and * as version. * * @param string $expression * * @return bool * @throws \Exception */ final protected function parseConditionalExpression(string $expression): bool { $expression = strtolower($expression); $terms = explode('=', $expression); $firstTerm = array_shift($terms); if (count($terms) == 0) { return $firstTerm == 'true' || $firstTerm == '1'; } elseif (preg_match('/^(com_|plg_|mod_|lib_|tpl_|cli_)/', $firstTerm)) { // The first term is the name of an extension $info = $this->getExtensionInfoFromElement($firstTerm); $extension = $this->findExtension($info['type'], $firstTerm, $info['group']); // @TODO: compare the version, if specified, or different than * // @TODO: Check if the extension is enabled, not just installed if (empty($extension) == false) { return true; } } return false; } /** * Get extension's info from element string, or extension name * * @param string $element The extension name, as element * * @return string[] An associative array with information about the extension */ final protected function getExtensionInfoFromElement(string $element): array { $result = array_fill_keys( ['type', 'name', 'group', 'prefix', 'namespace'], null ); $types = [ 'com' => 'component', 'plg' => 'plugin', 'mod' => 'module', 'lib' => 'library', 'tpl' => 'template', 'cli' => 'cli', ]; $element = explode('_', $element, 3); $prefix = $result['prefix'] = array_shift($element); $name = array_pop($element); $group = array_pop($element); if (array_key_exists($prefix, $types)) { $result = array_merge( $result, [ 'type' => $types[$prefix], 'group' => $group, 'name' => $name, ] ); } $result['namespace'] = preg_replace_callback( '/^(os[a-z])(.*)/i', function ($matches) { return strtoupper($matches[1]) . $matches[2]; }, $name ?? '' ); return $result; } /** * Check if the actual version is at least the minimum target version. * * @param string $actualVersion * @param string $targetVersion * @param ?string $compare * * @return bool True, if the target version is greater than or equal to actual version */ final protected function validateTargetVersion( string $actualVersion, string $targetVersion, ?string $compare = null ): bool { if ($targetVersion === '.*') { // Any version is valid return true; } $targetVersion = str_replace('*', '0', $targetVersion); return version_compare($actualVersion, $targetVersion, $compare ?: 'ge'); } /** * @param string $targetVersion * @param ?string $compare * * @return bool */ final protected function validatePreviousVersion(string $targetVersion, ?string $compare = null): bool { if ($this->previousManifest) { $lastVersion = (string)$this->previousManifest->version; return $this->validateTargetVersion($lastVersion, $targetVersion, $compare); } return true; } /** * Get the extension name. If no custom name is set, uses the namespace * * @return string */ final protected function getName(): string { return (string)($this->manifest->alledia->name ?? $this->manifest->alledia->namespace); } /** * @param ?bool $force Force to get a fresh list of tables * * @return string[] List of tables */ final protected function getTables(?bool $force = false): array { if ($force || $this->tables === null) { $this->tables = $this->dbo->setQuery('SHOW TABLES')->loadColumn(); } return $this->tables; } /** * @param string $table * * @return bool */ final protected function findTable(string $table): bool { return in_array($this->dbo->replacePrefix($table), $this->getTables()); } /** * @param string[] $columnSpecs * * @return void * @TODO: allow use of specification array */ final protected function addColumns(array $columnSpecs): void { $db = $this->dbo; foreach ($columnSpecs as $columnId => $specification) { if (strpos($columnId, '.') !== false && empty($this->findColumn($columnId))) { [$table, $columnName] = explode('.', $columnId); $db->setQuery( sprintf( 'ALTER TABLE %s ADD COLUMN %s %s', $db->quoteName($table), $db->quoteName($columnName), $specification ) ) ->execute(); } } } /** * @param string[] $columnIds * * @return void */ final protected function dropColumns(array $columnIds): void { $db = $this->dbo; foreach ($columnIds as $columnId) { if (strpos($columnId, '.') !== false) { [$table, $column] = explode('.', $columnId); $db->setQuery( sprintf( 'ALTER TABLE %s DROP COLUMN %s', $db->quoteName($table), $column ) ) ->execute(); } } } /** * @param string $columnId * * @return ?object */ final protected function findColumn(string $columnId): ?object { if (strpos($columnId, '.') !== false) { $db = $this->dbo; [$table, $field] = explode('.', $columnId, 2); if (isset($this->tableColumns[$table]) == false) { $this->tableColumns[$table] = $db->setQuery('SHOW COLUMNS FROM ' . $db->quoteName($table)) ->loadObjectList(); } foreach ($this->tableColumns[$table] as $column) { if ($column->Field == $field) { return $column; } } } return null; } /** * @param array $indexes * * @return void */ final protected function addIndexes(array $indexes): void { $db = $this->dbo; foreach ($indexes as $indexId => $ordering) { if (strpos($indexId, '.') !== false) { $index = explode('.', $indexId); $indexTable = array_shift($index) ?: ''; $indexName = array_shift($index) ?: ''; $indexType = array_shift($index) ?: ''; if ($this->findIndex($indexTable . '.' . $indexName) == false) { $db->setQuery( sprintf( 'ALTER TABLE %s ADD %s INDEX %s(%s)', $db->quoteName($indexTable), $indexType, $db->quoteName($indexName), join(',', $ordering) ) ) ->execute(); } } } } /** * @param string[] $indexIds * * @return void */ final protected function dropIndexes(array $indexIds): void { $db = $this->dbo; foreach ($indexIds as $indexId) { if (strpos($indexId, '.') !== false) { if ($this->findIndex($indexId)) { [$table, $indexName] = explode('.', $indexId); $db->setQuery( sprintf( 'ALTER TABLE %s DROP INDEX %s', $db->quoteName($table), $db->quoteName($indexName) ) ) ->execute(); } } } } /** * @param string $indexId * * @return object[] */ final protected function findIndex(string $indexId): array { if (strpos($indexId, '.') !== false) { $db = $this->dbo; [$table, $indexName] = explode('.', $indexId); if (isset($this->tableIndexes[$table]) == false) { $this->tableIndexes[$table] = $db->setQuery('SHOW INDEX FROM ' . $db->quoteName($table)) ->loadObjectList(); } $indexes = []; foreach ($this->tableIndexes[$table] as $index) { if ($index->Key_name == $indexName) { $indexes[] = $index; } } return $indexes; } return []; } /** * @param string[] $constraintIds * * @return void */ final protected function dropConstraints(array $constraintIds): void { $db = $this->dbo; foreach ($constraintIds as $constraintId) { if (strpos($constraintId, '.') !== false && $this->findConstraint($constraintId)) { [$table, $constraintName] = explode('.', $constraintId); $db->setQuery( sprintf( 'ALTER TABLE %s DROP FOREIGN KEY %s', $db->quoteName($table), $db->quoteName($constraintName) ) ) ->execute(); } } } /** * @param string $constraintId * * @return object[] */ final protected function findConstraint(string $constraintId): array { if (strpos($constraintId, '.') !== false) { $db = $this->dbo; [$table, $constraint] = explode('.', $constraintId); if (isset($this->tableConstraints[$table]) == false) { $query = $db->getQuery(true) ->select('*') ->from('information_schema.KEY_COLUMN_USAGE') ->where('TABLE_NAME = ' . $db->quote($db->replacePrefix($table))); $this->tableConstraints[$table] = $db->setQuery($query)->loadObjectList(); } $items = []; foreach ($this->tableConstraints[$table] as $item) { if ($item->CONSTRAINT_NAME == $constraint) { $items[] = $item; } } return $items ?: []; } return []; } /** * @return ?string * @throws \Exception */ final protected function getSchemaVersion(): ?string { if ($extension = $this->findThisExtension()) { $query = $this->dbo->getQuery(true) ->select('version_id') ->from('#__schemas') ->where('extension_id = ' . $extension->get('extension_id')); return $this->dbo->setQuery($query)->loadResult(); } return null; } /** * @param string|string[] $queries * * @return bool|Throwable */ final protected function executeQuery($schemaVersion, $queries) { $this->sendDebugMessage(__METHOD__); $this->sendDebugMessage($this->schemaVersion . ' / ' . $schemaVersion); if ($this->schemaVersion && version_compare($this->schemaVersion, $schemaVersion, 'lt')) { $this->sendDebugMessage(sprintf('Running v%s Schema Updates', $schemaVersion)); $db = $this->dbo; try { foreach ((array)$queries as $query) { $this->sendDebugMessage($query); if ($db->setQuery($query)->execute() == false) { return new \Exception('Query Error: ' . $query); } } } catch (Throwable $error) { return $error; } } return true; } /** * Joomla 4 does a database check that has lots of problems with standard sql syntax * causing it to declare the database tables as not up to date and in some cases * generates various sql errors. This can optionally be called during Post Install to * clear out all update files and still maintain the latest schema version correctly. * * @param string $basePath * * @return void */ final protected function clearDBUpdateFiles(string $basePath): void { $this->sendDebugMessage(__METHOD__); $updatePath = $basePath . '/sql/updates'; if (is_dir($updatePath) && $files = Folder::files($updatePath, '\.sql$', true, true)) { $this->sendDebugMessage('Removing:<pre>' . print_r($files, 1) . '</pre>'); $final = reset($files); foreach ($files as $file) { $version = basename($file, '.sql'); $lastVersion = basename($final, '.sql'); if (version_compare($version, $lastVersion, 'gt')) { $final = $file; } File::delete($file); } if ($final) { File::write($final, ''); $this->sendDebugMessage('Wrote blank: ' . $final); } } } /** * @param SimpleXMLElement|string $element * @param ?string $type * @param mixed $default * * @return ?bool|string */ final protected function getXmlValue($element, ?string $type = 'string', $default = null) { $value = $element ? (string)$element : $default; switch ($type) { case 'bool': case 'boolean': $value = $element ? $value == 'true' || $value == '1' : (bool)$default; break; case 'string': default: if ($value) { $value = trim($value); } break; } return $value; } /** * @param string $text * @param string $type * * @return void */ final protected function sendMessage(string $text, string $type = 'message'): void { if ($this->outputAllowed) { try { $this->app = $this->app ?: Factory::getApplication(); $this->app->enqueueMessage($text, $type); } catch (Throwable $error) { // Give up trying to send a message normally } } } /** * @param Throwable $error * @param bool $cancel * * @return void */ final protected function sendErrorMessage(Throwable $error, bool $cancel = true): void { if ($cancel) { $this->cancelInstallation = true; } if ($this->outputAllowed) { $trace = $error->getTrace(); $trace = array_shift($trace); if (empty($trace['class'])) { $caller = basename($trace['file']); } else { $className = explode('\\', $trace['class']); $caller = array_pop($className); } $line = $trace['line']; $function = $trace['function'] ?? null; $file = $trace['file']; if ($function) { $message = sprintf('%s: %s<br>%s::%s() - %s', $line, $file, $caller, $function, $error->getMessage()); } else { $message = sprintf('%s:%s (%s) - %s', $line, $caller, $file, $error->getMessage()); } $this->sendMessage($message, 'error'); } } /** * @param string $text * * @return void */ final protected function sendDebugMessage(string $text): void { if ($this->debug) { $type = Version::MAJOR_VERSION == 3 ? 'Debug-' . get_class($this) : CMSApplicationInterface::MSG_DEBUG; $this->sendMessage($text, $type); } } /** * @param string $type * * @return void */ final protected function displayWelcome(string $type): void { if ($this->outputAllowed == false) { return; } $this->sendDebugMessage( sprintf( '%s<br>Parent: %s<br>Current: %s', __METHOD__, $this->installer->getPath('parent'), $this->installer->getPath('source') ) ); $license = $this->getLicense(); $name = $this->getName() . ($license->isPro() ? ' Pro' : ''); // Get the footer content $this->footer = ''; // Check if we have a dedicated config.xml file $configPath = $license->getExtensionPath() . '/config.xml'; if (is_file($configPath)) { $config = $license->getConfig(); if (empty($config) == false) { $footerElement = $config->xpath('//field[@type="customfooter"]'); } } else { $footerElement = $this->manifest->xpath('//field[@type="customfooter"]'); } if (empty($footerElement) == false) { if (class_exists('\\JFormFieldCustomFooter') == false) { // Custom footer field is not (and should not be) automatically loaded $customFooterPath = $license->getExtensionPath() . '/form/fields/customfooter.php'; if (is_file($customFooterPath)) { include_once $customFooterPath; } } if (class_exists('\\JFormFieldCustomFooter')) { $field = new JFormFieldCustomFooter(); $field->fromInstaller = true; $this->footer = $field->getInputUsingCustomElement($footerElement[0]); unset($field, $footerElement); } } else { $this->sendDebugMessage('No Footer element was found'); } // Show additional installation messages $extensionPath = $this->getExtensionPath( $this->type, (string)$this->manifest->alledia->element, $this->group ); // If Pro extension, includes the license form view if ($license->isPro()) { // Get the OSMyLicensesManager extension to handle the license key if ($licensesManagerExtension = new Licensed('osmylicensesmanager', 'plugin', 'system')) { if (isset($licensesManagerExtension->params)) { $this->licenseKey = $licensesManagerExtension->params->get('license-keys', ''); } else { $this->licenseKey = ''; } $this->isLicensesManagerInstalled = true; } $this->sendDebugMessage('License Manager plugin: ' . (int)$this->isLicensesManagerInstalled); } // Welcome message if (in_array($type, [static::TYPE_INSTALL, static::TYPE_DISCOVER_INSTALL])) { $string = 'LIB_SHACKINSTALLER_THANKS_INSTALL'; } else { $string = 'LIB_SHACKINSTALLER_THANKS_UPDATE'; } // Variables for the included template $this->welcomeMessage = Text::sprintf($string, $name); $this->mediaURL = Uri::root() . 'media/' . $license->getFullElement(); $this->addStyle($this->mediaFolder . '/css/installer.css'); /* * Include the template * Try to find the template in an alternative folder, since some extensions * which uses FOF will display the "Installers" view on admin, errouniously. * FOF look for views automatically reading the views folder. So on that * case we move the installer view to another folder. */ $path = $extensionPath . '/views/installer/tmpl/default.php'; if (is_file($path) == false) { $path = $extensionPath . '/alledia_views/installer/tmpl/default.php'; } $this->sendDebugMessage(sprintf('Welcome View (%s): %s', (int)is_file($path), $path)); if (is_file($path)) { include $path; } } /** * WARNIMG! This is duplicated from the Joomlashack Framework * * @param string $name * @param string $prefix * @param string $component * @param ?string $appName * @param ?array $options * * @return mixed * @throws \Exception */ protected function getJoomlaModel( string $name, string $prefix, string $component, ?string $appName = null, ?array $options = [] ) { $defaultApp = 'Site'; $appNames = [$defaultApp, 'Administrator']; $appName = ucfirst($appName ?: $defaultApp); $appName = in_array($appName, $appNames) ? $appName : $defaultApp; if (Version::MAJOR_VERSION < 4) { $basePath = $appName == 'Administrator' ? JPATH_ADMINISTRATOR : JPATH_SITE; $path = $basePath . '/components/' . $component; BaseDatabaseModel::addIncludePath($path . '/models'); Table::addIncludePath($path . '/tables'); $model = BaseDatabaseModel::getInstance($name, $prefix, $options); } else { $model = Factory::getApplication()->bootComponent($component) ->getMVCFactory()->createModel($name, $appName, $options); } return $model; } /** * Utility function to setting extension states * @param array $extensions * @param int $state * * @return array * @throws \Exception */ final protected function setExtensionState(array $extensions, int $state = 0): array { $states = []; if (in_array($state, [0, 1])) { foreach ($extensions as $extension) { $parts = explode('.', $extension); $element = array_pop($parts); $folder = null; switch (count($parts)) { case 1: $type = array_pop($parts); break; case 2: $folder = array_pop($parts); $type = array_pop($parts); break; default: // Badly structured extension identifier break 2; } if ($object = $this->findExtension($type, $element, $folder)) { $states[$extension] = (int)$object->get('enabled'); if ($states[$extension] != $state) { $this->sendDebugMessage( sprintf( '%s: %s', $extension, $state ? 'Enabled' : 'Disabled' ) ); $object->set('enabled', $state); $object->store(); } } } } return $states; } /** * If the old system plugin is installed, it requires special handling to avoid * fatal conflicts with its install script * * @return void * @throws \Exception */ final protected function clearOldSystemPlugin() { if ($this->findExtension('plugin', 'ossystem', 'system')) { if (class_exists('PlgSystemOSSystemInstallerScript') == false) { class_alias(static::class, 'PlgSystemOSSystemInstallerScript'); } } } } library/Installer/Extension/Licensed.php 0000604 00000007223 15172151744 0014367 0 ustar 00 <?php /** * @package ShackInstaller * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2023 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackInstaller. * * ShackInstaller 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 2 of the License, or * (at your option) any later version. * * ShackInstaller 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 * along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. */ namespace Alledia\Installer\Extension; defined('_JEXEC') or die(); use Alledia\Installer\AutoLoader; /** * Licensed class, for extensions with Free and Pro versions */ class Licensed extends Generic { /** * License type: free or pro * * @var string */ protected $license = null; /** * The path for the pro library * * @var string */ protected $proLibraryPath = null; /** * The path for the free library * * @var string */ protected $libraryPath = null; /** * @inheritDoc */ public function __construct(string $namespace, string $type, ?string $folder = '', string $basePath = JPATH_SITE) { parent::__construct($namespace, $type, $folder, $basePath); $this->license = $this->manifest ? strtolower($this->manifest->alledia->license) : 'free'; $this->getLibraryPath(); $this->getProLibraryPath(); } /** * Check if the license is pro * * @return bool True for pro license */ public function isPro(): bool { return $this->license === 'pro'; } /** * Check if the license is free * * @return bool */ public function isFree(): bool { return !$this->isPro(); } /** * Get the path for the free library, based on the extension type * * @return string The path for pro */ public function getLibraryPath(): string { if (empty($this->libraryPath)) { $basePath = $this->getExtensionPath(); $this->libraryPath = $basePath . '/library'; } return $this->libraryPath; } /** * Get path for the pro library, based on the extension type * * @return string The path for pro */ public function getProLibraryPath(): string { if ($this->proLibraryPath === null) { $basePath = $this->getLibraryPath(); $this->proLibraryPath = $basePath . '/Pro'; } return $this->proLibraryPath; } /** * Loads the library, if existent (including the Pro Library) * * @return bool * @throws \Exception */ public function loadLibrary(): bool { $libraryPath = $this->getLibraryPath(); // If we have a library path, lets load it if (is_dir($libraryPath)) { if ($this->isPro()) { // Check if the pro library exists if (!is_dir($this->getProLibraryPath())) { throw new \Exception("Pro library not found: {$this->type}, {$this->element}"); } } AutoLoader::register('Alledia\\' . $this->namespace, $libraryPath); return true; } return false; } } library/Installer/Extension/Generic.php 0000604 00000025645 15172151744 0014225 0 ustar 00 <?php /** * @package ShackInstaller * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2023 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackInstaller. * * ShackInstaller 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 2 of the License, or * (at your option) any later version. * * ShackInstaller 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 * along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. */ namespace Alledia\Installer\Extension; defined('_JEXEC') or die(); use Joomla\CMS\Factory; use Joomla\Registry\Registry; /** * Generic extension class */ class Generic { /** * The extension namespace * * @var string */ protected $namespace = null; /** * The extension type * * @var string */ protected $type = null; /** * The extension id * * @var int */ protected $id = null; /** * The extension name * * @var string */ protected $name = null; /** * The extension params * * @var Registry */ public $params = null; /** * The extension enable state * * @var bool */ protected $enabled = null; /** * The element of the extension * * @var string */ protected $element = null; /** * @var string */ protected $folder = null; /** * Base path * * @var string */ protected $basePath = null; /** * The manifest information * * @var \SimpleXMLElement */ public $manifest = null; /** * The config.xml information * * @var \SimpleXMLElement */ public $config = null; /** * Class constructor, set the extension type. * * @param string $namespace The element of the extension * @param string $type The type of extension * @param ?string $folder The folder for plugins (only) * @param string $basePath * * @return void */ public function __construct(string $namespace, string $type, ?string $folder = '', string $basePath = JPATH_SITE) { $this->type = $type; $this->element = strtolower($namespace); $this->folder = $folder; $this->basePath = $basePath; $this->namespace = $namespace; $this->getManifest(); $this->getDataFromDatabase(); } /** * Get information about this extension from the database * NOTE: This is duplicated code from the corresponding class in * \Alledia\Framework\Joomla\Extension\Generic */ protected function getDataFromDatabase() { $element = $this->getElementToDb(); // Load the extension info from database $db = Factory::getDbo(); $query = $db->getQuery(true) ->select([ $db->quoteName('extension_id'), $db->quoteName('name'), $db->quoteName('enabled'), $db->quoteName('params') ]) ->from('#__extensions') ->where($db->quoteName('type') . ' = ' . $db->quote($this->type)) ->where($db->quoteName('element') . ' = ' . $db->quote($element)); if ($this->type === 'plugin') { $query->where($db->quoteName('folder') . ' = ' . $db->quote($this->folder)); } $db->setQuery($query); $row = $db->loadObject(); if (is_object($row)) { $this->id = $row->extension_id; $this->name = $row->name; $this->enabled = (bool)$row->enabled; $this->params = new Registry($row->params); } else { $this->id = null; $this->name = null; $this->enabled = false; $this->params = new Registry(); } } /** * Check if the extension is enabled * * @return bool */ public function isEnabled(): bool { return (bool)$this->enabled; } /** * Get the path for the extension * * @return string The path */ public function getExtensionPath(): string { $folders = [ 'component' => 'administrator/components/', 'plugin' => 'plugins/', 'template' => 'templates/', 'library' => 'libraries/', 'cli' => 'cli/', 'module' => 'modules/' ]; $basePath = $this->basePath . '/' . $folders[$this->type]; switch ($this->type) { case 'plugin': $basePath .= $this->folder . '/'; break; case 'module': if (!preg_match('/^mod_/', $this->element)) { $basePath .= 'mod_'; } break; case 'component': if (!preg_match('/^com_/', $this->element)) { $basePath .= 'com_'; } break; } $basePath .= $this->element; return $basePath; } /** * Get the full element * * @return string */ public function getFullElement(): string { $prefixes = [ 'component' => 'com', 'plugin' => 'plg', 'template' => 'tpl', 'library' => 'lib', 'cli' => 'cli', 'module' => 'mod' ]; $fullElement = $prefixes[$this->type]; if ($this->type === 'plugin') { $fullElement .= '_' . $this->folder; } $fullElement .= '_' . $this->element; return $fullElement; } /** * Get the element to match the database records. * Only components and modules have the prefix. * * @return string The element */ public function getElementToDb(): string { $prefixes = [ 'component' => 'com_', 'module' => 'mod_' ]; $fullElement = ''; if (array_key_exists($this->type, $prefixes)) { if (!preg_match('/^' . $prefixes[$this->type] . '/', $this->element)) { $fullElement = $prefixes[$this->type]; } } $fullElement .= $this->element; return $fullElement; } /** * Get manifest path for this extension * * @return string */ public function getManifestPath(): string { $extensionPath = $this->getExtensionPath(); switch ($this->type) { case 'template': $fileName = 'templateDetails.xml'; break; case 'library': $fileName = $this->element . '.xml'; if (!is_file($extensionPath . '/' . $fileName)) { $extensionPath = JPATH_MANIFESTS . '/libraries'; } break; case 'module': $fileName = 'mod_' . $this->element . '.xml'; break; case 'pkg': $extensionPath = JPATH_MANIFESTS . '/packages'; $fileName = 'pkg_' . $this->element . '.xml'; break; case 'file': $extensionPath = JPATH_MANIFESTS . '/files'; $fileName = 'file_' . $this->element . '.xml'; break; default: $fileName = $this->element . '.xml'; break; } return $extensionPath . '/' . $fileName; } /** * Get extension information * * @param bool $force If true, force to load the manifest, ignoring the cached one * * @return ?\SimpleXMLElement */ public function getManifest(bool $force = false): ?\SimpleXMLElement { if ($this->manifest === null || $force) { $this->manifest = false; $path = $this->getManifestPath(); if (is_file($path)) { $this->manifest = simplexml_load_file($path); } } return $this->manifest ?: null; } /** * Get extension config file * * @param bool $force Force to reload the config file * * @return \SimpleXMLElement */ public function getConfig(bool $force = false) { if ($this->config === null || $force) { $path = $this->getExtensionPath() . '/config.xml'; $this->config = is_file($path) ? simplexml_load_file($path) : false; } return $this->config; } /** * Returns the update URL from database * * @return string */ public function getUpdateURL(): string { $db = Factory::getDbo(); $query = $db->getQuery(true) ->select('sites.location') ->from('#__update_sites AS sites') ->leftJoin('#__update_sites_extensions AS extensions ON (sites.update_site_id = extensions.update_site_id)') ->where('extensions.extension_id = ' . $this->id); return $db->setQuery($query)->loadResult(); } /** * Set the update URL * * @param string $url */ public function setUpdateURL(string $url) { $db = Factory::getDbo(); // Get the update site id $join = $db->quoteName('#__update_sites_extensions') . ' AS extensions ' . 'ON (sites.update_site_id = extensions.update_site_id)'; $query = $db->getQuery(true) ->select('sites.update_site_id') ->from($db->quoteName('#__update_sites') . ' AS sites') ->leftJoin($join) ->where('extensions.extension_id = ' . $this->id); $db->setQuery($query); $siteId = (int)$db->loadResult(); if (!empty($siteId)) { $query = $db->getQuery(true) ->update($db->quoteName('#__update_sites')) ->set($db->quoteName('location') . ' = ' . $db->quote($url)) ->where($db->quoteName('update_site_id') . ' = ' . $siteId); $db->setQuery($query); $db->execute(); } } /** * Store the params on the database * * @return void */ public function storeParams() { $db = Factory::getDbo(); $params = $db->quote($this->params->toString()); $id = $db->quote($this->id); $query = "UPDATE `#__extensions` SET params = {$params} WHERE extension_id = {$id}"; $db->setQuery($query); $db->execute(); } /** * Get extension name * * @return string */ public function getName(): string { return $this->name; } } library/Installer/Loader.php 0000604 00000006127 15172151744 0012075 0 ustar 00 <?php /** * @package ShackInstaller * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2023 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackInstaller. * * ShackInstaller 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 2 of the License, or * (at your option) any later version. * * ShackInstaller 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 * along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. */ namespace Alledia\Installer; use Alledia\Framework\Factory; use Joomla\CMS\Log\Log; defined('_JEXEC') or die(); abstract class Loader { protected static $logRegistered = false; /** * Safely include a PHP file, making sure it exists before import. * * This method will register a log message and display a warning for admins * in case the file is missed. * * @param string $path The file path you want to include * * @return bool * @throws \Exception */ public static function includeFile(string $path): bool { if (!static::$logRegistered) { Log::addLogger( ['text_file' => 'shackframework.loader.errors.php'], Log::ALL, ['allediaframework'] ); static::$logRegistered = true; } // Check if the file doesn't exist if (!is_file($path)) { $logMsg = 'Required file is missed: ' . $path; // Generate a backtrace to know from where the request cames if (version_compare(phpversion(), '5.4', '<')) { $backtrace = debug_backtrace(); } else { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); } if (!empty($backtrace)) { $logMsg .= sprintf( ' (%s:%s)', $backtrace[0]['file'], $backtrace[0]['line'] ); } // Register the log Log::add($logMsg, Log::ERROR, 'allediaframework'); // Warn admin users $app = Factory::getApplication(); if ($app->getName() == 'administrator') { $app->enqueueMessage( 'ShackInstaller Loader detected that a required file was not found! Please, check the logs.', 'error' ); } // Stand up a flag to warn a required file is missed if (!defined('SHACK_INSTALLER_MISSED_FILE')) { define('SHACK_INSTALLER_MISSED_FILE', true); } return false; } include_once $path; return true; } } library/Installer/include.php 0000604 00000003506 15172151744 0012310 0 ustar 00 <?php /** * @package ShackInstaller * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2023 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackInstaller. * * ShackInstaller 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 2 of the License, or * (at your option) any later version. * * ShackInstaller 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 * along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. */ use Alledia\Installer\AutoLoader; use Joomla\CMS\Version; // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die(); if (defined('SHACK_INSTALLER_BASE') == false) { define('SHACK_INSTALLER_BASE', __DIR__); require_once SHACK_INSTALLER_BASE . '/AutoLoader.php'; } AutoLoader::register('Alledia\\Installer', __DIR__, true); if (defined('SHACK_INSTALLER_VERSION') == false) { define('SHACK_INSTALLER_VERSION', '2.6.1'); define('SHACK_INSTALLER_COMPATIBLE', '2.4.0'); if (isset($reportErrors) == false) { $reportErrors = E_ALL ^ E_DEPRECATED ^ E_USER_DEPRECATED; if (Version::MAJOR_VERSION == 4) { // There is a bad line of code in Joomla 4 that runs during extension install/update $reportErrors = $reportErrors ^ E_NOTICE; } } if ($reportErrors) { set_error_handler('\\Alledia\\Installer\\AbstractScript::errorHandler', $reportErrors); } } library/Installer/AutoLoader.php 0000604 00000015140 15172151744 0012721 0 ustar 00 <?php /** * @package ShackInstaller * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2023 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackInstaller. * * ShackInstaller 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 2 of the License, or * (at your option) any later version. * * ShackInstaller 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 * along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. */ namespace Alledia\Installer; defined('_JEXEC') or die(); class AutoLoader { /** * Associative array where the key is a namespace prefix and the value * is an array of base directories for classes in that namespace. * * @var array */ protected static $prefixes = []; /** * Associative array of prefixes for loading specialized camelCase classes * where Uppercase letters in the class name indicate directory structure * * @var array */ protected static $camelPrefixes = []; /** * @var AutoLoader */ protected static $instance = null; /** * @param string $method */ protected static function registerLoader($method) { if (static::$instance === null) { static::$instance = new static(); } spl_autoload_register([static::$instance, $method]); } /** * Register a psr4 namespace * * @param string $prefix The namespace prefix. * @param string $baseDir A base directory for class files in the * namespace. * @param ?bool $prepend If true, prepend the base directory to the stack * instead of appending it; this causes it to be searched first rather * than last. * * @return void */ public static function register(string $prefix, string $baseDir, ?bool $prepend = false) { if (count(self::$prefixes) == 0) { static::registerLoader('loadClass'); } // normalize namespace prefix $prefix = trim($prefix, '\\') . '\\'; // normalize the base directory with a trailing separator $baseDir = rtrim($baseDir, '\\/') . '/'; // initialise the namespace prefix array if (empty(self::$prefixes[$prefix])) { self::$prefixes[$prefix] = []; } // retain the base directory for the namespace prefix if ($prepend) { array_unshift(self::$prefixes[$prefix], $baseDir); } else { array_push(self::$prefixes[$prefix], $baseDir); } } /** * Loads the class file for a given class name. * * @param string $class The fully-qualified class name. * * @return ?string The mapped file name on success */ protected function loadClass(string $class): ?string { $prefixes = explode('\\', $class); $className = ''; while ($prefixes) { $className = array_pop($prefixes) . $className; $prefix = join('\\', $prefixes) . '\\'; if ($filePath = $this->loadMappedFile($prefix, $className)) { return $filePath; } $className = '\\' . $className; } return null; } /** * Load the mapped file for a namespace prefix and class. * * @param string $prefix The namespace prefix. * @param string $className The relative class name. * * @return ?string path that was loaded */ protected function loadMappedFile(string $prefix, string $className): ?string { // are there any base directories for this namespace prefix? if (isset(self::$prefixes[$prefix]) === false) { return null; } // look through base directories for this namespace prefix foreach (self::$prefixes[$prefix] as $baseDir) { $path = $baseDir . str_replace('\\', '/', $className) . '.php'; if (is_file($path)) { require_once $path; return $path; } } return null; } /** * Register a base directory for classes organized using camelCase. * Class names beginning with the prefix will be automatically loaded * if there is a matching file in the directory tree starting with $baseDir. * File names and directory names are all expected to be lower case. * * Example: * * $prefix = 'Simplerenew' * $baseDir = '/library/joomla' * * A class name of: SimplerenewViewAdmin * Would be in : /library/joomla/view/admin.php * * This system is intended for situations where full name spacing is either * unavailable or impractical due to integration with other systems. * * @param string $prefix * @param string $baseDir * * @return void * @throws \Exception */ public static function registerCamelBase(string $prefix, string $baseDir) { if (!is_dir($baseDir)) { throw new \Exception("Cannot register '{$prefix}'. The requested base directory does not exist!'"); } if (count(self::$camelPrefixes) == 0) { // Register function on first call static::registerLoader('loadCamelClass'); } if (empty(self::$camelPrefixes[$prefix])) { self::$camelPrefixes[$prefix] = $baseDir; } } /** * Autoload a class using the camelCase structure * * @param string $class * * @return string */ protected function loadCamelClass(string $class): ?string { if (!class_exists($class)) { foreach (self::$camelPrefixes as $prefix => $baseDir) { if (strpos($class, $prefix) === 0) { $parts = preg_split('/(?<=[a-z])(?=[A-Z])/x', substr($class, strlen($prefix))); $file = strtolower(join('/', $parts)); $filePath = $baseDir . '/' . $file . '.php'; if (is_file($filePath)) { require_once $filePath; return $filePath; } } } } return null; } } include.php 0000604 00000003516 15172151744 0006710 0 ustar 00 <?php /** * @package ShackExtensionSupport * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2025 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackExtensionSupport. * * ShackExtensionSupport 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 2 of the License, or * (at your option) any later version. * * ShackExtensionSupport 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 * along with ShackExtensionSupport. If not, see <https://www.gnu.org/licenses/>. */ use Alledia\Framework\AutoLoader; use Joomla\CMS\Factory; // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die(); if (defined('ALLEDIA_FRAMEWORK_LOADED') == false) { $frameworkPath = JPATH_SITE . '/libraries/allediaframework/include.php'; $app = Factory::getApplication(); if ( (is_file($frameworkPath) && include $frameworkPath) == false && $app->isClient('administrator') ) { $app->enqueueMessage('[Joomlashack Extension Support] Joomlashack Framework not found', 'error'); } unset($app); } if ( defined('ALLEDIA_FRAMEWORK_LOADED') && defined('SHACKEXTENSIONSUPPORT_LOADED') == false ) { AutoLoader::register('\\Alledia\\OSMyLicensesManager', JPATH_PLUGINS . '/system/osmylicensesmanager/library'); define('SHACKEXTENSIONSUPPORT_LOADED', 1); } return defined('ALLEDIA_FRAMEWORK_LOADED') && defined('SHACKEXTENSIONSUPPORT_LOADED'); form/fields/layouts/alledia/customfooter.php 0000604 00000007514 15172151744 0015324 0 ustar 00 <?php /** * @package ShackDefaultFiles * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2018-2024 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackDefaultFiles. * * ShackDefaultFiles 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 2 of the License, or * (at your option) any later version. * * ShackDefaultFiles 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 * along with ShackDefaultFiles. If not, see <https://www.gnu.org/licenses/>. */ use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; defined('_JEXEC') or die(); /** * @var array $displayData */ $class = $displayData['class']; $media = $displayData['media']; $jslogo = $media . '/' . $displayData['jslogo']; $jedurl = $displayData['jedurl']; $showGoProAd = $displayData['showGoProAd']; $goProUrl = $displayData['goProUrl']; $fromInstaller = $displayData['fromInstaller']; $footerCss = HTMLHelper::_('stylesheet', $media . '/field_customfooter.css', ['relative' => true, 'pathOnly' => true]); $adminCss = HTMLHelper::_('stylesheet', $media . '/admin-default.css', ['relative' => true, 'pathOnly' => true]); ?> <link href="<?php echo $footerCss; ?>" rel="stylesheet"/> <link href="<?php echo $adminCss; ?>" rel="stylesheet"/> <div class="<?php echo $class; ?>"> <div> <?php if ($showGoProAd) : ?> <div class="gopro-ad"> <?php echo HTMLHelper::_( 'link', $goProUrl, Text::_('SHACKDEFAULTFILES_GO_PRO'), 'class="gopto-btn" target="_blank"' ); ?> </div> <?php endif; if ($jedurl) : ?> <div class="joomlashack-jedlink"> <?php echo Text::_('SHACKDEFAULTFILES_LIKE_THIS_EXTENSION'); echo ' ' . HTMLHelper::_( 'link', $jedurl, Text::_('SHACKDEFAULTFILES_LEAVE_A_REVIEW_ON_JED'), 'target="_blank"' ); echo ' ' . str_repeat("<i class=\"icon-star\"></i>", 5); ?> </div> <?php endif; ?> <div class="poweredby"> Powered by <?php echo HTMLHelper::_( 'link', 'https://www.joomlashack.com', HTMLHelper::_('image', $jslogo, 'Joomlashack', 'class="joomlashack-logo" width="150"', true), 'target="_blank"' ); ?> </div> <div class="joomlashack-copyright"> <?php echo '© ' . date('Y'); ?> Joomlashack.com. All rights reserved. </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { let footer = document.getElementsByClassName('joomlashack-footer').item(0), container = footer ? footer.parentElement : null, wrapper = null; if (container && container.classList.contains('controls')) { wrapper = document.getElementById('content') || document.querySelector('.container-popup'); } if (footer && wrapper) { wrapper.parentNode.insertBefore(footer, wrapper.nextSibling); } else if (footer) { footer.remove(); } }); </script> form/fields/customfooter.php 0000604 00000011103 15172151744 0012216 0 ustar 00 <?php /** * @package ShackDefaultFiles * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2015-2024 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackDefaultFiles. * * ShackDefaultFiles 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 2 of the License, or * (at your option) any later version. * * ShackDefaultFiles 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 * along with ShackDefaultFiles. If not, see <https://www.gnu.org/licenses/>. */ use Joomla\CMS\Factory; use Joomla\CMS\Form\FormField; // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die(); // phpcs:enable PSR1.Files.SideEffects // phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace /** * @property bool $fromInstaller */ class JFormFieldCustomFooter extends FormField { /** * @inheritdoc */ protected $layout = 'alledia.customfooter'; /** * @var bool */ protected $fromInstaller = false; /** * @inheritDoc */ public function __set($name, $value = null) { switch ($name) { case 'fromInstaller': $this->fromInstaller = (bool)$value; break; default: parent::__set($name, $value); } } /** * @inheritDoc */ public function setup(SimpleXMLElement $element, $value, $group = null) { if ($path = realpath(__DIR__ . '/../..')) { Factory::getLanguage()->load('shackdefaultfiles', $path); } return parent::setup($element, $value, $group); } /** * @inheritDoc */ protected function getInput() { return $this->getRenderer($this->layout)->render($this->getLayoutData()); } /** * @inheritDoc */ protected function getRenderer($layoutId = 'default') { $renderer = parent::getRenderer($layoutId); if ($layoutId == $this->layout) { $renderer->addIncludePath(__DIR__ . '/layouts'); } return $renderer; } /** * @inheritDoc */ protected function getLayoutData() { $displayData = parent::getLayoutData(); $requiredClasses = [ 'joomlashack-footer', 'row-fluid' ]; if ($this->fromInstaller) { $requiredClasses[] = 'installer'; } $classes = array_unique( array_filter( array_merge( preg_split('/\s/', $displayData['class']), $requiredClasses ) ) ); $goProUrl = (string)$this->element['showgoproad'] ?: '0'; $showGoProAd = !($goProUrl == '0' || $goProUrl == 'false'); if ($showGoProAd && !filter_var($goProUrl, FILTER_VALIDATE_URL)) { $goProUrl = 'https://www.joomlashack.com/plans'; } return array_merge( $displayData, [ 'class' => join(' ', $classes), 'media' => $this->element['media'], 'jslogo' => (string)$this->element['jslogo'] ?: 'joomlashack-logo.png', 'jshome' => (string)$this->element['jshome'] ?: 'https://www.joomlashack.com', 'jedurl' => (string)$this->element['jedurl'], 'fromInstaller' => $this->fromInstaller, 'showGoProAd' => $showGoProAd, 'goProUrl' => $goProUrl ] ); } /** * @param ?string $path * * @return string * @TODO: Doesn't seem to be useful */ protected function getStyle(?string $path): string { if ($path && is_file($path)) { return '<style>' . file_get_contents($path) . '</style>'; } return ''; } /** * @inheritDoc */ protected function getLabel() { return ''; } /** * @param SimpleXMLElement $element * * @return ?string */ public function getInputUsingCustomElement(SimpleXMLElement $element): ?string { $this->element = $element; $this->setup($element, null); return $this->getInput(); } } form/fields/subtitle.php 0000604 00000005535 15172151744 0011334 0 ustar 00 <?php /** * @package ShackDefaultFiles * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2015-2024 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackDefaultFiles. * * ShackDefaultFiles 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 2 of the License, or * (at your option) any later version. * * ShackDefaultFiles 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 * along with ShackDefaultFiles. If not, see <https://www.gnu.org/licenses/>. */ use Joomla\CMS\Form\FormHelper; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; defined('_JEXEC') or die(); FormHelper::loadFieldClass('Spacer'); class JFormFieldSubtitle extends JFormFieldSpacer { /** * @inheritDoc */ protected function getLabel() { $html = []; $class = $this->class ?: sprintf(' class="%s"', $this->class); $tag = $this->element['tag'] ? (string)$this->element['tag'] : 'h4'; $html[] = '<span class="spacer">'; $html[] = '<span class="before"></span>'; $html[] = '<span' . $class . '>'; if ((string)$this->element['hr'] == 'true') { $html[] = '<hr' . $class . '>'; } else { $label = ''; // Get the label text from the XML element, defaulting to the element name. $text = (string)$this->element['label'] ?: (string)$this->element['name']; $text = $this->translateLabel ? Text::_($text) : $text; // Build the class for the label. $class = $this->description ? 'hasTooltip' : ''; $class = $this->required == true ? $class . ' required' : $class; // Add the opening label tag and main attributes attributes. $label .= '<' . $tag . ' id="' . $this->id . '-lbl" class="' . $class . '"'; if ($this->description) { // Use description to build a tooltip. HTMLHelper::_('bootstrap.tooltip'); $label .= sprintf( ' title="%s"', HTMLHelper::tooltipText(trim($text, ':'), Text::_($this->description), 0) ); } // Add the label text and closing tag. $label .= '>' . $text . '</' . $tag . '>'; $html[] = $label; } $html[] = '</span>'; $html[] = '<span class="after"></span>'; $html[] = '</span>'; return implode('', $html); } } osmylicensesmanager.xml 0000604 00000005205 15172151744 0011343 0 ustar 00 <?xml version="1.0" encoding="utf-8"?> <extension type="plugin" group="system" method="upgrade"> <name>plg_system_osmylicensesmanager</name> <author>Joomlashack</author> <authorEmail>help@joomlashack.com</authorEmail> <authorUrl>https://www.joomlashack.com</authorUrl> <copyright>Copyright 2016-2025 Joomlashack. All rights reserved</copyright> <license>GNU GPL; see LICENSE file</license> <description>PLG_SYSTEM_OSMYLICENSESMANAGER_DESCRIPTION</description> <version>2.1.0</version> <creationDate>November 10 2025</creationDate> <variant>FREE</variant> <alledia> <element publish="true">osmylicensesmanager</element> <namespace>OSMyLicensesManager</namespace> <name>Joomlashack Extension Support</name> <license>free</license> <platform>3.9</platform> <phpminimum>7.4</phpminimum> <relatedExtensions> <extension type="library" element="allediaframework">ShackFramework</extension> </relatedExtensions> <include>ShackDefaultFiles</include> <obsolete> <file>/plugins/system/osmylicensesmanager/library/Free/UpdateHelper.php</file> <extension type="plugin" group="system" element="ossystem"/> </obsolete> </alledia> <scriptfile>script.installer.php</scriptfile> <files> <folder>form</folder> <folder>language</folder> <folder>library</folder> <folder>views</folder> <filename plugin="osmylicensesmanager">osmylicensesmanager.php</filename> <filename>include.php</filename> </files> <media destination="plg_system_osmylicensesmanager" folder="media"> <folder>css</folder> <folder>images</folder> </media> <config> <fields name="params"> <fieldset name="basic" addfieldpath="/plugins/system/osmylicensesmanager/form/fields"> <field name="license-keys" type="text" label="PLG_SYSTEM_OSMYLICENSESMANAGER_FIELD_LICENSE_KEYS_LABEL" description="PLG_SYSTEM_OSMYLICENSESMANAGER_FIELD_LICENSE_KEYS_DESC"/> <field type="customfooter" name="customfooter" media="plg_system_osmylicensesmanager"/> </fieldset> </fields> </config> <updateservers> <server type="extension" priority="1" name="Joomlashack Extension Support"><![CDATA[https://deploy.ostraining.com/client/update/free/stable/plg_system_osmylicensesmanager]]></server> </updateservers> </extension> osmylicensesmanager.php 0000604 00000010734 15172151744 0011335 0 ustar 00 <?php /** * @package ShackExtensionSupport * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2025 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackExtensionSupport. * * ShackExtensionSupport 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 2 of the License, or * (at your option) any later version. * * ShackExtensionSupport 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 * along with ShackExtensionSupport. If not, see <https://www.gnu.org/licenses/>. */ use Alledia\Framework\Joomla\Extension\AbstractPlugin; use Alledia\Framework\Joomla\Extension\Helper; use Alledia\OSMyLicensesManager\Free\PluginHelper; use Alledia\OSMyLicensesManager\PluginBase; use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Factory; use Joomla\CMS\Plugin\CMSPlugin; // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die(); if ((include 'include.php') == false) { class_alias(CMSPlugin::class, PluginBase::class); } else { class_alias(AbstractPlugin::class, PluginBase::class); } // phpcs:enable PSR1.Files.SideEffects // phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace class PlgSystemOSMyLicensesManager extends PluginBase { /** * @var CMSApplication */ protected $app = null; /** * @inheritdoc */ protected $namespace = 'OSMyLicensesManager'; /** * @var bool */ protected $enabled = null; /** * @return void */ public function onAfterInitialise(): void { $plugin = $this->app->input->getCmd('plugin'); $task = $this->app->input->getCmd('task'); $user = Factory::getUser(); if ( $this->isEnabled() && $this->app->isClient('administrator') && $plugin == 'system_osmylicensesmanager' && $task == 'license.save' && $user->guest == false ) { // The user is saving a license key from the installer screen $this->init(); $licenseKeys = $this->app->input->post->getString('license-keys', ''); $result = (object)[ 'success' => PluginHelper::updateLicenseKeys($licenseKeys), ]; echo json_encode($result); jexit(); } } /** * @return void * @throws Throwable */ public function onAfterRender(): void { $option = $this->app->input->getCmd('option'); $extension = $this->app->input->getCmd('extension'); if ( $this->isEnabled() && $this->app->isClient('administrator') && $option === 'com_categories' && $extension ) { $this->addCustomFooterToCategories($extension); } } /** * @param string $url * * @return void */ public function onInstallerBeforePackageDownload(string &$url): void { $licenseKeys = $this->params->get('license-keys', ''); if ( $this->isEnabled() && $licenseKeys && PluginHelper::getLicenseTypeFromURL($url) !== 'free' ) { $this->init(); $url = PluginHelper::appendLicenseKeyToURL($url, $licenseKeys); } } /** * @param ?string $element * * @return void * @throws Throwable */ protected function addCustomFooterToCategories(?string $element): void { if ($this->isEnabled() && $element) { // Check if the specified extension is from Alledia if ($extension = Helper::getExtensionForElement($element)) { if ($footer = $extension->getFooterMarkup()) { $this->app->setBody( str_replace('</section>', '</section>' . $footer, $this->app->getBody()) ); } } } } /** * @return bool */ protected function isEnabled(): bool { if ($this->enabled === null) { $this->enabled = $this instanceof AbstractPlugin; } return $this->enabled; } } views/installer/tmpl/default_license.php 0000604 00000014241 15172151744 0014516 0 ustar 00 <?php /** * @package ShackInstaller * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2023 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackInstaller. * * ShackInstaller 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 2 of the License, or * (at your option) any later version. * * ShackInstaller 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 * along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. */ use Alledia\Installer\AbstractScript; use Alledia\Installer\Extension\Generic; use Alledia\Installer\Extension\Licensed; use Joomla\CMS\Language\Text; use Joomla\CMS\Uri\Uri; defined('_JEXEC') or die(); /** * @var AbstractScript $this * @var string $type * @var Licensed $license * @var string $name * @var string $configPath * @var string $customFooterPath * @var string $extensionPath * @var Generic $licensesManagerExtension * @var string $string * @var string $path */ $licenseUpdate = Uri::root() . '/administrator/index.php?plugin=system_osmylicensesmanager&task=license.save'; if ($this->isLicensesManagerInstalled) : ?> <div class="joomlashack-license-form"> <?php if (!empty($this->licenseKey)) : ?> <a href="" class="joomlashack-installer-change-license-button btn btn-success"> <?php echo Text::_('LIB_SHACKINSTALLER_CHANGE_LICENSE_KEY'); ?> </a> <?php endif; ?> <div id="joomlashack-installer-license-panel"> <input type="text" name="joomlashack-license-keys" id="joomlashack-license-keys" value="<?php echo $this->licenseKey; ?>" class="form-control" placeholder="<?php echo Text::_('LIB_SHACKINSTALLER_LICENSE_KEYS_PLACEHOLDER'); ?>"/> <p class="joomlashack-empty-key-msg"> <?php echo Text::_('LIB_SHACKINSTALLER_MSG_LICENSE_KEYS_EMPTY'); ?> <a href="https://www.joomlashack.com/account/key/" target="_blank"> <?php echo Text::_('LIB_SHACKINSTALLER_I_DONT_REMEMBER_MY_KEY'); ?> </a> </p> <a id="joomlashack-license-save-button" class="btn btn-success" href="#"> <?php echo Text::_('LIB_SHACKINSTALLER_SAVE_LICENSE_KEY'); ?> </a> </div> <div id="joomlashack-installer-license-success" style="display: none"> <p><?php echo Text::_('LIB_SHACKINSTALLER_LICENSE_KEY_SUCCESS'); ?></p> </div> <div id="joomlashack-installer-license-error" style="display: none"> <p><?php echo Text::_('LIB_SHACKINSTALLER_LICENSE_KEY_ERROR'); ?></p> </div> </div> <script> (function() { let panel = document.getElementById('joomlashack-installer-license-panel'), updateButtons = document.getElementsByClassName('joomlashack-installer-change-license-button'), saveButton = document.getElementById('joomlashack-license-save-button'); if (panel) { if (updateButtons.length > 0) { panel.style.display = 'none'; Array.from(updateButtons).forEach(function(button) { button.addEventListener('click', function(event) { event.preventDefault(); panel.style.display = 'block'; this.style.display = 'none'; }) }); } if (saveButton) { saveButton.addEventListener('click', function(event) { event.preventDefault(); let request = new XMLHttpRequest(), data = new FormData(), keyField = document.getElementById('joomlashack-license-keys') data.append('license-keys', keyField.value) request.onreadystatechange = function(data) { if (this.readyState === XMLHttpRequest.DONE) { try { if (this.status === 200) { let result = JSON.parse(this.response), success = document.getElementById('joomlashack-installer-license-success'), error = document.getElementById('joomlashack-installer-license-error'); panel.style.display = 'none'; if (result.success) { success.style.display = 'block'; } else { error.style.display = 'block'; } } else { error.style.display = 'block'; } } catch (e) { panel.style.display = 'none'; error.style.display = 'block'; } } }; request.open('POST', '<?php echo $licenseUpdate; ?>'); request.send(data); }); } } })(); </script> <?php else : ?> <div class="error"> <?php echo Text::_('LIB_SHACKINSTALLER_LICENSE_KEYS_MANAGER_REQUIRED'); ?> </div> <?php endif; views/installer/tmpl/default.php 0000604 00000003564 15172151744 0013022 0 ustar 00 <?php /** * @package ShackInstaller * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2023 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackInstaller. * * ShackInstaller 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 2 of the License, or * (at your option) any later version. * * ShackInstaller 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 * along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. */ use Alledia\Installer\AbstractScript; use Alledia\Installer\Extension\Generic; use Alledia\Installer\Extension\Licensed; defined('_JEXEC') or die(); /** * @var AbstractScript $this * @var string $type * @var Licensed $license * @var string $name * @var string $configPath * @var string $customFooterPath * @var string $extensionPath * @var Generic $licensesManagerExtension * @var string $string * @var string $path */ ?> <div class="joomlashack-wrapper"> <div class="joomlashack-content"> <h2><?php echo $this->welcomeMessage; ?></h2> <?php if (is_file(__DIR__ . '/default_custom.php')) : include __DIR__ . '/default_custom.php'; endif; if ($license->isPro()) : include __DIR__ . '/default_license.php'; endif; include __DIR__ . "/default_info.php"; ?> <?php echo $this->footer; ?> </div> </div> views/installer/tmpl/default_info.php 0000604 00000007710 15172151744 0014032 0 ustar 00 <?php /** * @package ShackInstaller * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2016-2023 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackInstaller. * * ShackInstaller 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 2 of the License, or * (at your option) any later version. * * ShackInstaller 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 * along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>. */ use Alledia\Installer\AbstractScript; use Alledia\Installer\Extension\Generic; use Alledia\Installer\Extension\Licensed; use Joomla\CMS\Language\Text; defined('_JEXEC') or die(); /** * @var AbstractScript $this * @var string $type * @var Licensed $license * @var string $name * @var string $configPath * @var string $customFooterPath * @var string $extensionPath * @var Generic $licensesManagerExtension * @var string $string * @var string $path */ ?> <div class="joomlashack-details-container"> <a href="javascript:void(0);" id="joomlashack-installer-footer-toggler"> <?php echo Text::_('LIB_SHACKINSTALLER_SHOW_DETAILS'); ?> </a> <div id="joomlashack-installer-footer" style="display: none;"> <div class="joomlashack-license"> <?php echo Text::sprintf('LIB_SHACKINSTALLER_RELEASE_V', (string)$this->manifest->version); ?> </div> <br> <?php if (!empty($this->manifest->alledia->relatedExtensions)) : ?> <table class="joomlashack-related-table"> <thead> <tr> <th colspan="2"><?php echo Text::_('LIB_SHACKINSTALLER_RELATED_EXTENSIONS'); ?></th> </tr> </thead> <tbody> <?php foreach ($this->relatedExtensionFeedback as $data) : ?> <tr> <td><?php echo Text::_($data['name']); ?></td> <td> <?php $messages = [$data['message']]; if (isset($data['publish']) && $data['publish']) { $messages[] = Text::_('LIB_SHACKINSTALLER_PUBLISHED'); } if (isset($data['ordering'])) { $messages[] = Text::sprintf('LIB_SHACKINSTALLER_SORTED', $data['ordering']); } $messages = implode(', ', $messages); echo $messages; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> <div class="joomlashack-license"> <?php echo Text::sprintf( 'LIB_SHACKINSTALLER_LICENSED_AS', $this->getName(), '<a href="https://www.gnu.org/licenses/gpl-3.0">GNU/GPL v3.0</a>' ); ?>. </div> </div> </div> <script> (function() { let footer = document.getElementById('joomlashack-installer-footer'), toggle = document.getElementById('joomlashack-installer-footer-toggler'); if (footer && toggle) { toggle.addEventListener('click', function(event) { event.preventDefault(); footer.style.display = 'block'; this.style.display = 'none'; }); } })(); </script> views/footer/tmpl/default.php 0000604 00000003275 15172151744 0012322 0 ustar 00 <?php /** * @package ShackDefaultFiles * @contact www.joomlashack.com, help@joomlashack.com * @copyright 2015-2021 Joomlashack.com. All rights reserved * @license https://www.gnu.org/licenses/gpl.html GNU/GPL * * This file is part of ShackDefaultFiles. * * ShackDefaultFiles 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 2 of the License, or * (at your option) any later version. * * ShackDefaultFiles 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 * along with ShackDefaultFiles. If not, see <https://www.gnu.org/licenses/>. */ use Joomla\CMS\HTML\HTMLHelper; defined('_JEXEC') or die(); ?> <div class="row-fluid"> <div id="footer" class="span12"> <div> <a href="https://www.joomlashack.com"> <?php echo HTMLHelper::_( 'image', $this->option . '/joomlashack-logo.png', 'Joomlashack', ['width' => '150'], true ); ?> </a> </div> <br/> <div> Powered by <?php echo HTMLHelper::_( 'link', 'https://www.joomlashack.com', 'Joomlashack', ['target' => '_blank'] ); ?> </div> </div> </div>
| ver. 1.4 |
Github
|
.
| PHP 8.3.23 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка