File manager - Edit - /home/ferretapmx/public_html/InstallScript.zip
Back
PK TH�\,���v� v� Component.phpnu �[��� <?php /** * @package FOF * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 3, or later */ namespace FOF40\InstallScript; defined('_JEXEC') || die; use Exception; use FOF40\Container\Container; use FOF40\Database\Installer as DatabaseInstaller; use FOF40\Utils\ViewManifestMigration; use JDatabaseDriver; use Joomla\CMS\Factory as JoomlaFactory; use Joomla\CMS\Filesystem\File; use Joomla\CMS\Filesystem\Folder; use Joomla\CMS\Installer\Adapter\ComponentAdapter; use Joomla\CMS\Installer\Installer as JoomlaInstaller; use Joomla\CMS\Log\Log; use Joomla\CMS\Table\Menu; use Joomla\Database\DatabaseDriver; // In case FOF's autoloader is not present yet, e.g. new installation if (!class_exists('FOF40\\InstallScript\\BaseInstaller', true)) { require_once __DIR__ . '/BaseInstaller.php'; } /** * A helper class which you can use to create component installation scripts. * * Example usage: class Com_ExampleInstallerScript extends FOF40\Utils\InstallScript\Component * * This namespace contains more classes for creating installation scripts for other kinds of Joomla! extensions as well. * Do keep in mind that only components, modules and plugins could have post-installation scripts before Joomla! 3.3. */ class Component extends BaseInstaller { /** * The component's name. Auto-filled from the class name. * * @var string */ public $componentName = ''; /** * The title of the component (printed on installation and uninstallation messages) * * @var string */ protected $componentTitle = 'Foobar Component'; /** * The list of obsolete extra modules and plugins to uninstall on component upgrade / installation. * * @var array */ protected $uninstallation_queue = [ // modules => { (folder) => { (module) }* }* 'modules' => [ 'admin' => [], 'site' => [], ], // plugins => { (folder) => { (element) }* }* 'plugins' => [ 'system' => [], ], ]; /** * Obsolete files and folders to remove from the free version only. This is used when you move a feature from the * free version of your extension to its paid version. If you don't have such a distinction you can ignore this. * * @var array */ protected $removeFilesFree = [ 'files' => [ // Use pathnames relative to your site's root, e.g. // 'administrator/components/com_foobar/helpers/whatever.php' ], 'folders' => [ // Use pathnames relative to your site's root, e.g. // 'administrator/components/com_foobar/baz' ], ]; /** * Obsolete files and folders to remove from both paid and free releases. This is used when you refactor code and * some files inevitably become obsolete and need to be removed. * * @var array */ protected $removeFilesAllVersions = [ 'files' => [ // Use pathnames relative to your site's root, e.g. // 'administrator/components/com_foobar/helpers/whatever.php' ], 'folders' => [ // Use pathnames relative to your site's root, e.g. // 'administrator/components/com_foobar/baz' ], ]; /** * A list of scripts to be copied to the "cli" directory of the site * * @var array */ protected $cliScriptFiles = [ // Use just the filename, e.g. // 'my-cron-script.php' ]; /** * The path inside your package where cli scripts are stored * * @var string */ protected $cliSourcePath = 'cli'; /** * Is the schemaXmlPath class variable a relative path? If set to true the schemaXmlPath variable contains a path * relative to the component's back-end directory. If set to false the schemaXmlPath variable contains an absolute * filesystem path. * * @var boolean */ protected $schemaXmlPathRelative = true; /** * The path where the schema XML files are stored. Its contents depend on the schemaXmlPathRelative variable above * true => schemaXmlPath contains a path relative to the component's back-end directory * false => schemaXmlPath contains an absolute filesystem path * * @var string */ protected $schemaXmlPath = 'sql/xml'; /** * Is this the paid version of the extension? This only determines which files / extensions will be removed. * * @var boolean */ protected $isPaid = false; /** * Should I copy XML manifests from the tmpl and ViewTemplates folders into the views folder on Joomla 3? * * This copies `tmpl/<VIEWNAME>/*.xml` and `ViewTemplates/<VIEWNAME>/*.xml` to `views/<VIEWNAME>/tmpl/*.xml` on * Joomla 3. * * @var bool */ protected $migrateJoomla4MenuXMLFiles = true; /** * Should I remove the legacy `views` folder on Joomla 4? * * This removes both the front- and backend `views` folder. Recommended when `$migrateJoomla4MenuXMLFiles` is also * true. * * @var bool */ protected $removeLegacyViewsFolder = true; /** * The path to the component's backend directory. * * Leave null to assume JPATH_ADMINISTRATOR . '/components/' . $this->componentName * * @var string|null * @since 4.0.1 */ protected $backendPath = null; /** * The path to the component's frontend directory. * * Leave null to assume JPATH_SITE . '/components/' . $this->componentName * * @var string|null * @since 4.0.1 */ protected $frontendPath = null; /** * Module installer script constructor. */ public function __construct() { // Get the plugin name and folder from the class name (it's always plgFolderPluginInstallerScript) if necessary. if (empty($this->componentName)) { $class = get_class($this); $words = preg_replace('/(\s)+/', '_', $class); $words = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $words)); $classParts = explode('_', $words); $this->componentName = 'com_' . $classParts[2]; } } /** * Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to * tell Joomla! if it should abort the installation. * * @param string $type Installation type (install, update, discover_install) * @param ComponentAdapter $parent Parent object * * @return boolean True to let the installation proceed, false to halt the installation * * @noinspection PhpUnusedParameterInspection */ public function preflight(string $type, ComponentAdapter $parent): bool { // Do not run on uninstall. if ($type === 'uninstall') { return true; } // Check the minimum PHP version if (!$this->checkPHPVersion()) { return false; } // Check the minimum Joomla! version if (!$this->checkJoomlaVersion()) { return false; } // Clear op-code caches to prevent any cached code issues $this->clearOpcodeCaches(); // Workarounds for JoomlaInstaller issues. if (in_array($type, ['install', 'discover_install'])) { // Bugfix for "Database function returned no error" $this->bugfixDBFunctionReturnedNoError(); } else { // Bugfix for "Can not build admin menus" $this->bugfixCantBuildAdminMenus(); } return true; } /** * Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing * or updating your component. This is the last chance you've got to perform any additional installations, clean-up, * database updates and similar housekeeping functions. * * @param string $type install, update or discover_update * @param ComponentAdapter $parent Parent object * * @return void * @throws Exception * */ public function postflight(string $type, ComponentAdapter $parent): void { // Do not run on uninstall. if ($type === 'uninstall') { return; } // Add ourselves to the list of extensions depending on FOF40 $this->addDependency('fof40', $this->componentName); $this->removeDependency('fof30', $this->componentName); // Install or update database $dbInstaller = new DatabaseInstaller(JoomlaFactory::getDbo(), ($this->schemaXmlPathRelative ? JPATH_ADMINISTRATOR . '/components/' . $this->componentName : '') . '/' . $this->schemaXmlPath ); $dbInstaller->updateSchema(); // These workarounds are only needed, and only work, on Joomla! 3.x if (strpos(JVERSION, '3.') === 0) { // Make sure menu items are installed $this->_createAdminMenus($parent); // Make sure menu items are published $this->_reallyPublishAdminMenuItems($parent); } // Which files should I remove? if ($this->isPaid) { // This is the paid version, only remove the removeFilesAllVersions files $removeFiles = $this->removeFilesAllVersions; } else { // This is the free version, remove the removeFilesAllVersions and removeFilesFree files $removeFiles = ['files' => [], 'folders' => []]; if (isset($this->removeFilesAllVersions['files'])) { if (isset($this->removeFilesFree['files'])) { $removeFiles['files'] = array_merge($this->removeFilesAllVersions['files'], $this->removeFilesFree['files']); } else { $removeFiles['files'] = $this->removeFilesAllVersions['files']; } } elseif (isset($this->removeFilesFree['files'])) { $removeFiles['files'] = $this->removeFilesFree['files']; } if (isset($this->removeFilesAllVersions['folders'])) { if (isset($this->removeFilesFree['folders'])) { $removeFiles['folders'] = array_merge($this->removeFilesAllVersions['folders'], $this->removeFilesFree['folders']); } else { $removeFiles['folders'] = $this->removeFilesAllVersions['folders']; } } elseif (isset($this->removeFilesFree['folders'])) { $removeFiles['folders'] = $this->removeFilesFree['folders']; } } // Remove obsolete files and folders $this->removeFilesAndFolders($removeFiles); // Make sure everything is copied properly $this->bugfixFilesNotCopiedOnUpdate($parent); // Copy the CLI files (if any) $this->copyCliFiles($parent); // Show the post-installation page $this->renderPostInstallation($parent); // Uninstall obsolete subextensions $this->uninstallObsoleteSubextensions($parent); // Clear the FOF cache $false = false; $cache = JoomlaFactory::getCache('fof', ''); $cache->store($false, 'cache', 'fof'); // Make sure the Joomla! menu structure is correct $this->_rebuildMenu(); // Add post-installation messages on Joomla! 3.2 and later $this->_applyPostInstallationMessages(); // Clear the opcode caches again - in case someone accessed the extension while the files were being upgraded. $this->clearOpcodeCaches(); /** * DO NOT USE THE CONTAINER TO GET THE PATHS. * * There are two cases when updating from a FOF 3 version of a component may cause the container to fail to * load: * * 1. If the component failed to update fully (because Joomla does that) * 2. You are using opcache but it failed to clear, e.g. the host disabled the function to do so. * * Using the hardcoded paths is much safer in this context. * * Also note that this code carries two further defenses in cases we start using the container again in the * future: * * 1. It is moved AFTER the call to bugfixFilesNotCopiedOnUpdate() to solve the problem of Joomla failing the * update. * 2. It is moved AFTER the calll to clearOpcodeCaches() to deal with the opcache not being cleared. * * However, neither solution is bulletproof. As a result it makes far more sense to NOT use the container if we * can help it... */ $frontendPath = $this->frontendPath ?? (JPATH_SITE . '/components/' . $this->componentName); $backendPath = $this->backendPath ?? (JPATH_ADMINISTRATOR . '/components/' . $this->componentName); // Migrate view manifest XML files if ($this->migrateJoomla4MenuXMLFiles) { ViewManifestMigration::migrateJoomla4MenuXMLFiles_real($frontendPath, $backendPath); } // Remove the legacy Joomla 3 `views` folder if ($this->removeLegacyViewsFolder) { ViewManifestMigration::removeJoomla3LegacyViews_real($frontendPath, $backendPath); } // Finally, see if FOF 3.x is obsolete and remove it. // $this->uninstallFOF3IfNecessary(); } /** * Runs on uninstallation * * @param ComponentAdapter $parent The parent object */ public function uninstall(ComponentAdapter $parent): void { // Uninstall database $dbInstaller = new DatabaseInstaller(JoomlaFactory::getDbo(), ($this->schemaXmlPathRelative ? JPATH_ADMINISTRATOR . '/components/' . $this->componentName : '') . '/' . $this->schemaXmlPath ); $dbInstaller->removeSchema(); // Uninstall post-installation messages on Joomla! 3.2 and later $this->uninstallPostInstallationMessages(); // Remove ourselves from the list of extensions depending of FOF 4 $this->removeDependency('fof40', $this->componentName); // Uninstall FOF 4 if nothing else depends on it $this->uninstallFOF4IfNecessary(); // Show the post-uninstallation page $this->renderPostUninstallation($parent); } /** * Copies the CLI scripts into Joomla!'s cli directory * * @param ComponentAdapter $parent */ protected function copyCliFiles(ComponentAdapter $parent): void { $src = $parent->getParent()->getPath('source'); foreach ($this->cliScriptFiles as $script) { if (is_file(JPATH_ROOT . '/cli/' . $script)) { File::delete(JPATH_ROOT . '/cli/' . $script); } if (is_file($src . '/' . $this->cliSourcePath . '/' . $script)) { File::copy($src . '/' . $this->cliSourcePath . '/' . $script, JPATH_ROOT . '/cli/' . $script); } } } /** * Fix for Joomla bug: sometimes files are not copied on update. * * We have observed that ever since Joomla! 1.5.5, when Joomla! is performing an extension update some files / * folders are not copied properly. This seems to be a bit random and seems to be more likely to happen the more * added / modified files and folders you have. We are trying to work around it by retrying the copy operation * ourselves WITHOUT going through the manifest, based entirely on the conventions we follow for Akeeba Ltd's * extensions. * * @param ComponentAdapter $parent */ protected function bugfixFilesNotCopiedOnUpdate(ComponentAdapter $parent): void { Log::add("Joomla! extension update workaround for component $this->componentName", Log::INFO, 'fof4_extension_installation'); $temporarySource = $parent->getParent()->getPath('source'); $copyMap = [ // Backend component files 'backend' => JPATH_ADMINISTRATOR . '/components/' . $this->componentName, 'admin' => JPATH_ADMINISTRATOR . '/components/' . $this->componentName, // Frontend component files 'frontend' => JPATH_SITE . '/components/' . $this->componentName, 'site' => JPATH_SITE . '/components/' . $this->componentName, // Backend language 'language/backend' => JPATH_ADMINISTRATOR . '/language', 'language/admin' => JPATH_ADMINISTRATOR . '/language', // Frontend language 'language/frontend' => JPATH_SITE . '/language', 'language/site' => JPATH_SITE . '/language', // Media files 'media' => JPATH_ROOT . '/media/' . $this->componentName, ]; foreach ($copyMap as $partialSource => $target) { $source = $temporarySource . '/' . $partialSource; Log::add(__CLASS__ . ":: Conditional copy $source to $target", Log::DEBUG, 'fof4_extension_installation'); $this->recursiveConditionalCopy($source, $target); } } /** * Override this method to display a custom component installation message if you so wish * * @param ComponentAdapter $parent Parent class calling us * * @noinspection PhpUnusedParameterInspection */ protected function renderPostInstallation(ComponentAdapter $parent): void { echo "<h3>$this->componentName has been installed</h3>"; } /** * Override this method to display a custom component uninstallation message if you so wish * * @param ComponentAdapter $parent Parent class calling us * * @noinspection PhpUnusedParameterInspection */ protected function renderPostUninstallation(ComponentAdapter $parent): void { echo "<h3>$this->componentName has been uninstalled</h3>"; } /** * Bugfix for "DB function returned no error" */ protected function bugfixDBFunctionReturnedNoError(): void { $db = JoomlaFactory::getDbo(); try { // Fix broken #__assets records $this->deleteComponentAssetRecords($db); // Fix broken #__extensions records $this->deleteComponentExtensionRecord($db); /** * Fix broken #__menu records * * Only run on Joomla! versions lower than 3.7. Joomla! 3.7 introduced a backend menu manager which * lets the user create missing menu items. Moreover, it lets them create custom links to the component * which means that our menu deleting code would break them! So we don't run this code in newer Joomla! * versions any more. */ $this->deleteComponentMenuRecord($db); } catch (Exception $exc) { return; } } /** * Joomla! 1.6+ bugfix for "Can not build admin menus" */ protected function bugfixCantBuildAdminMenus(): void { $db = JoomlaFactory::getDbo(); // If there are multiple #__extensions record, keep one of them $query = $db->getQuery(true); $query->select('extension_id') ->from('#__extensions') ->where($db->qn('type') . ' = ' . $db->q('component')) ->where($db->qn('element') . ' = ' . $db->q($this->componentName)); $db->setQuery($query); try { $ids = $db->loadColumn(); } catch (Exception $exc) { return; } if ((is_array($ids) || $ids instanceof \Countable ? count($ids) : 0) > 1) { asort($ids); $extension_id = array_shift($ids); // Keep the oldest id foreach ($ids as $id) { $query = $db->getQuery(true); $query->delete('#__extensions') ->where($db->qn('extension_id') . ' = ' . $db->q($id)); $db->setQuery($query); try { $db->execute(); } catch (Exception $exc) { // Nothing } } } // If there are multiple assets records, delete all except the oldest one $query = $db->getQuery(true); $query->select('id') ->from('#__assets') ->where($db->qn('name') . ' = ' . $db->q($this->componentName)); $db->setQuery($query); $ids = $db->loadObjectList(); if ((is_array($ids) || $ids instanceof \Countable ? count($ids) : 0) > 1) { asort($ids); $asset_id = array_shift($ids); // Keep the oldest id foreach ($ids as $id) { $query = $db->getQuery(true); $query->delete('#__assets') ->where($db->qn('id') . ' = ' . $db->q($id)); $db->setQuery($query); try { $db->execute(); } catch (Exception $exc) { // Nothing } } } // Remove #__menu records for good measure! –– I think this is not necessary and causes the menu item to // disappear on extension update. /** * $query = $db->getQuery(true); * $query->select('id') * ->from('#__menu') * ->where($db->qn('type') . ' = ' . $db->q('component')) * ->where($db->qn('menutype') . ' = ' . $db->q('main')) * ->where($db->qn('link') . ' LIKE ' . $db->q('index.php?option=' . $this->componentName)); * $db->setQuery($query); * * try * { * $ids1 = $db->loadColumn(); * } * catch (Exception $exc) * { * $ids1 = array(); * } * * if (empty($ids1)) * { * $ids1 = array(); * } * * $query = $db->getQuery(true); * $query->select('id') * ->from('#__menu') * ->where($db->qn('type') . ' = ' . $db->q('component')) * ->where($db->qn('menutype') . ' = ' . $db->q('main')) * ->where($db->qn('link') . ' LIKE ' . $db->q('index.php?option=' . $this->componentName . '&%')); * $db->setQuery($query); * * try * { * $ids2 = $db->loadColumn(); * } * catch (Exception $exc) * { * $ids2 = array(); * } * * if (empty($ids2)) * { * $ids2 = array(); * } * * $ids = array_merge($ids1, $ids2); * * if (!empty($ids)) * { * foreach ($ids as $id) * { * $query = $db->getQuery(true); * $query->delete('#__menu') * ->where($db->qn('id') . ' = ' . $db->q($id)); * $db->setQuery($query); * * try * { * $db->execute(); * } * catch (Exception $exc) * { * // Nothing * } * } * } * /**/ } /** * Removes obsolete files and folders * * @param array $removeList The files and directories to remove */ protected function removeFilesAndFolders(array $removeList): void { // Remove files if (isset($removeList['files']) && !empty($removeList['files'])) { foreach ($removeList['files'] as $file) { $f = JPATH_ROOT . '/' . $file; if (!is_file($f)) { continue; } File::delete($f); } } // Remove folders if (!isset($removeList['folders'])) { return; } if (empty($removeList['folders'])) { return; } foreach ($removeList['folders'] as $folder) { $f = JPATH_ROOT . '/' . $folder; if (!@file_exists($f) || !is_dir($f) || is_link($f)) { continue; } Folder::delete($f); } } /** * Uninstalls obsolete subextensions (modules, plugins) bundled with the main extension * * @param ComponentAdapter $parent The parent object * * @return \stdClass The sub-extension uninstallation status * @noinspection PhpUnusedParameterInspection */ protected function uninstallObsoleteSubextensions(ComponentAdapter $parent) { $db = JoomlaFactory::getDBO(); $status = new \stdClass(); $status->modules = []; $status->plugins = []; // Modules uninstallation if (isset($this->uninstallation_queue['modules']) && count($this->uninstallation_queue['modules'])) { foreach ($this->uninstallation_queue['modules'] as $folder => $modules) { if ((is_array($modules) || $modules instanceof \Countable ? count($modules) : 0) > 0) { foreach ($modules as $module) { // Find the module ID $sql = $db->getQuery(true) ->select($db->qn('extension_id')) ->from($db->qn('#__extensions')) ->where($db->qn('element') . ' = ' . $db->q('mod_' . $module)) ->where($db->qn('type') . ' = ' . $db->q('module')); $db->setQuery($sql); $id = $db->loadResult(); // Uninstall the module if ($id) { $installer = new JoomlaInstaller; $result = $installer->uninstall('module', $id, 1); $status->modules[] = [ 'name' => 'mod_' . $module, 'client' => $folder, 'result' => $result, ]; } } } } } // Plugins uninstallation if (isset($this->uninstallation_queue['plugins']) && count($this->uninstallation_queue['plugins'])) { foreach ($this->uninstallation_queue['plugins'] as $folder => $plugins) { if ((is_array($plugins) || $plugins instanceof \Countable ? count($plugins) : 0) > 0) { foreach ($plugins as $plugin) { $sql = $db->getQuery(true) ->select($db->qn('extension_id')) ->from($db->qn('#__extensions')) ->where($db->qn('type') . ' = ' . $db->q('plugin')) ->where($db->qn('element') . ' = ' . $db->q($plugin)) ->where($db->qn('folder') . ' = ' . $db->q($folder)); $db->setQuery($sql); $id = $db->loadResult(); if ($id) { $installer = new JoomlaInstaller; $result = $installer->uninstall('plugin', $id, 1); $status->plugins[] = [ 'name' => 'plg_' . $plugin, 'group' => $folder, 'result' => $result, ]; } } } } } return $status; } /** * @param ComponentAdapter $parent * * @return bool * * @throws Exception When the Joomla! menu is FUBAR */ private function _createAdminMenus(ComponentAdapter $parent): bool { $db = $parent->getParent()->getDbo(); /** @var Menu $table */ $table = new Menu(JoomlaFactory::getDbo()); $option = $parent->get('element'); // If a component exists with this option in the table then we don't need to add menus $query = $db->getQuery(true) ->select('COUNT(*)') ->from($db->qn('#__menu') . ' AS ' . $db->qn('m')) ->leftJoin($db->qn('#__extensions', 'e') . ' ON ' . $db->qn('m.component_id') . ' = ' . $db->qn('e.extension_id')) ->where($db->qn('m.parent_id') . ' = ' . $db->q(1)) ->where($db->qn('m.client_id') . ' = ' . $db->q(1)) ->where($db->qn('e.type') . ' = ' . $db->q('component')) ->where($db->qn('e.element') . ' = ' . $db->q($option)); $db->setQuery($query); $existingMenus = $db->loadResult(); if ($existingMenus) { return true; } // Let's find the extension id $query->clear() ->select($db->qn('e.extension_id')) ->from($db->qn('#__extensions', 'e')) ->where($db->qn('e.type') . ' = ' . $db->q('component')) ->where($db->qn('e.element') . ' = ' . $db->q($option)); $db->setQuery($query); $componentId = $db->loadResult(); // Ok, now its time to handle the menus. Start with the component root menu, then handle submenus. if (method_exists($parent, 'getManifest')) { $menuElement = $parent->getManifest()->administration->menu; } else { $menuElement = $parent->get('manifest')->administration->menu; } // We need to insert the menu item as the last child of Joomla!'s menu root node. First let's make sure that // it exists. Normally it should be the menu item with ID = 1. $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->where($db->qn('id') . ' = ' . $db->q(1)); $rootItemId = $db->setQuery($query)->loadResult(); // If we didn't find the item with ID=1 something has screwed up the menu table, e.g. a bad upgrade script. In // this case we can try to find the root node by title. if (is_null($rootItemId)) { $rootItemId = null; $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->where($db->qn('title') . ' = ' . $db->q('Menu_Item_Root')); $rootItemId = $db->setQuery($query, 0, 1)->loadResult(); } // So, someone changed the title of the menu item too?! Let's find it by alias. if (is_null($rootItemId)) { $rootItemId = null; $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->where($db->qn('alias') . ' = ' . $db->q('root')); $rootItemId = $db->setQuery($query, 0, 1)->loadResult(); } // For crying out loud, they changed the alias too? Fine! Find it by component ID. if (is_null($rootItemId)) { $rootItemId = null; $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->where($db->qn('component_id') . ' = ' . $db->q('0')); $rootItemId = $db->setQuery($query, 0, 1)->loadResult(); } // Um, OK. Still no go. Let's try with minimum lft value. if (is_null($rootItemId)) { $rootItemId = null; $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->order($db->qn('lft') . ' ASC'); $rootItemId = $db->setQuery($query, 0, 1)->loadResult(); } // I quit. Your site's menu structure is broken. I'll just throw an error. if (is_null($rootItemId)) { throw new Exception("Your site is broken. There is no root menu item. As a result it is impossible to create menu items. The installation of this component has failed. Please fix your database and retry!", 500); } /** @var \SimpleXMLElement $menuElement */ if ($menuElement) { $data = []; $data['menutype'] = 'main'; $data['client_id'] = 1; $data['title'] = (string) trim($menuElement); $data['alias'] = (string) $menuElement; $data['link'] = 'index.php?option=' . $option; $data['type'] = 'component'; $data['published'] = 0; $data['parent_id'] = 1; $data['component_id'] = $componentId; $data['img'] = ((string) $menuElement->attributes()->img !== '') ? (string) $menuElement->attributes()->img : 'class:component'; $data['home'] = 0; $data['path'] = ''; $data['params'] = ''; } // No menu element was specified, Let's make a generic menu item else { $data = []; $data['menutype'] = 'main'; $data['client_id'] = 1; $data['title'] = $option; $data['alias'] = $option; $data['link'] = 'index.php?option=' . $option; $data['type'] = 'component'; $data['published'] = 0; $data['parent_id'] = 1; $data['component_id'] = $componentId; $data['img'] = 'class:component'; $data['home'] = 0; $data['path'] = ''; $data['params'] = ''; } try { $table->setLocation($rootItemId, 'last-child'); } catch (\InvalidArgumentException $e) { $this->log($e->getMessage()); return false; } if (!$table->bind($data) || !$table->check() || !$table->store()) { // The menu item already exists. Delete it and retry instead of throwing an error. $query->clear() ->select('id') ->from('#__menu') ->where('menutype = ' . $db->quote('main')) ->where('client_id = 1') ->where('link = ' . $db->quote('index.php?option=' . $option)) ->where('type = ' . $db->quote('component')) ->where('parent_id = 1') ->where('home = 0'); $db->setQuery($query); $menu_ids_level1 = $db->loadColumn(); if (empty($menu_ids_level1)) { JoomlaFactory::getApplication()->enqueueMessage($table->getError(), 'warning'); return false; } else { $ids = implode(',', $menu_ids_level1); $query->clear() ->select('id') ->from('#__menu') ->where('menutype = ' . $db->quote('main')) ->where('client_id = 1') ->where('type = ' . $db->quote('component')) ->where('parent_id in (' . $ids . ')') ->where('level = 2') ->where('home = 0'); $db->setQuery($query); $menu_ids_level2 = $db->loadColumn(); $ids = implode(',', array_merge($menu_ids_level1, $menu_ids_level2)); // Remove the old menu item $query->clear() ->delete('#__menu') ->where('id in (' . $ids . ')'); $db->setQuery($query); $db->execute(); // Retry creating the menu item $table->setLocation($rootItemId, 'last-child'); if (!$table->bind($data) || !$table->check() || !$table->store()) { // Install failed, warn user and rollback changes JoomlaFactory::getApplication()->enqueueMessage($table->getError(), 'warning'); return false; } } } /* * Since we have created a menu item, we add it to the installation step stack * so that if we have to rollback the changes we can undo it. */ $parent->getParent()->pushStep(['type' => 'menu', 'id' => $componentId]); /* * Process SubMenus */ if (method_exists($parent, 'getManifest')) { $submenu = $parent->getManifest()->administration->submenu; } else { $submenu = $parent->get('manifest')->administration->submenu; } if (!$submenu) { return true; } $parent_id = $table->id; /** @var \SimpleXMLElement $child */ foreach ($submenu->menu as $child) { $data = []; $data['menutype'] = 'main'; $data['client_id'] = 1; $data['title'] = (string) trim($child); $data['alias'] = (string) $child; $data['type'] = 'component'; $data['published'] = 0; $data['parent_id'] = $parent_id; $data['component_id'] = $componentId; $data['img'] = ((string) $child->attributes()->img !== '') ? (string) $child->attributes()->img : 'class:component'; $data['home'] = 0; // Set the sub menu link if ((string) $child->attributes()->link !== '') { $data['link'] = 'index.php?' . $child->attributes()->link; } else { $request = []; if ((string) $child->attributes()->act !== '') { $request[] = 'act=' . $child->attributes()->act; } if ((string) $child->attributes()->task !== '') { $request[] = 'task=' . $child->attributes()->task; } if ((string) $child->attributes()->controller !== '') { $request[] = 'controller=' . $child->attributes()->controller; } if ((string) $child->attributes()->view !== '') { $request[] = 'view=' . $child->attributes()->view; } if ((string) $child->attributes()->layout !== '') { $request[] = 'layout=' . $child->attributes()->layout; } if ((string) $child->attributes()->sub !== '') { $request[] = 'sub=' . $child->attributes()->sub; } $qstring = ((is_array($request) || $request instanceof \Countable ? count($request) : 0) > 0) ? '&' . implode('&', $request) : ''; $data['link'] = 'index.php?option=' . $option . $qstring; } $table = new Menu(JoomlaFactory::getDbo()); try { $table->setLocation($parent_id, 'last-child'); } catch (\InvalidArgumentException $e) { return false; } if (!$table->bind($data) || !$table->check() || !$table->store()) { // Install failed, rollback changes return false; } /* * Since we have created a menu item, we add it to the installation step stack * so that if we have to rollback the changes we can undo it. */ $parent->getParent()->pushStep(['type' => 'menu', 'id' => $componentId]); } return true; } /** * Make sure the Component menu items are really published! * * @param ComponentAdapter $parent */ private function _reallyPublishAdminMenuItems(ComponentAdapter $parent): void { $db = $parent->getParent()->getDbo(); $option = $parent->get('element'); $query = $db->getQuery(true) ->update('#__menu AS m') ->join('LEFT', '#__extensions AS e ON m.component_id = e.extension_id') ->set($db->qn('published') . ' = ' . $db->q(1)) ->where('m.parent_id = 1') ->where('m.client_id = 1') ->where('e.type = ' . $db->quote('component')) ->where('e.element = ' . $db->quote($option)); try { $db->setQuery($query)->execute(); } catch (Exception $e) { // If it fails, it fails. Who cares. } } /** * Tells Joomla! to rebuild its menu structure to make triple-sure that the Components menu items really do exist * in the correct place and can really be rendered. */ private function _rebuildMenu(): void { $table = new Menu(JoomlaFactory::getDbo()); $db = $table->getDbo(); // We need to rebuild the menu based on its root item. By default this is the menu item with ID=1. However, some // crappy upgrade scripts enjoy screwing it up. Hey, ho, the workaround way I go. $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->where($db->qn('id') . ' = ' . $db->q(1)); $rootItemId = $db->setQuery($query)->loadResult(); if (is_null($rootItemId)) { // Guess what? The Problem has happened. Let's find the root node by title. $rootItemId = null; $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->where($db->qn('title') . ' = ' . $db->q('Menu_Item_Root')); $rootItemId = $db->setQuery($query, 0, 1)->loadResult(); } if (is_null($rootItemId)) { // Did they change the title too?! Let's find it by alias. $rootItemId = null; $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->where($db->qn('alias') . ' = ' . $db->q('root')); $rootItemId = $db->setQuery($query, 0, 1)->loadResult(); } if (is_null($rootItemId)) { // The alias is borked, too?! Find it by component ID. $rootItemId = null; $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->where($db->qn('component_id') . ' = ' . $db->q('0')); $rootItemId = $db->setQuery($query, 0, 1)->loadResult(); } if (is_null($rootItemId)) { // Your site is more of a "shite" than a "site". Let's try with minimum lft value. $rootItemId = null; $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__menu')) ->order($db->qn('lft') . ' ASC'); $rootItemId = $db->setQuery($query, 0, 1)->loadResult(); } if (is_null($rootItemId)) { // I quit. Your site is broken. return; } $table->rebuild($rootItemId); } /** * Deletes the assets table records for the component * * @param JDatabaseDriver|DatabaseDriver $db * * @return void * * @since 3.0.18 */ private function deleteComponentAssetRecords($db): void { $query = $db->getQuery(true); $query->select('id') ->from('#__assets') ->where($db->qn('name') . ' = ' . $db->q($this->componentName)); $db->setQuery($query); $ids = $db->loadColumn(); if (empty($ids)) { return; } foreach ($ids as $id) { $query = $db->getQuery(true); $query->delete('#__assets') ->where($db->qn('id') . ' = ' . $db->q($id)); $db->setQuery($query); try { $db->execute(); } catch (Exception $exc) { // Nothing } } } /** * Deletes the extensions table records for the component * * @param JDatabaseDriver|DatabaseDriver $db * * @return void * * @since 3.0.18 */ private function deleteComponentExtensionRecord($db): void { $query = $db->getQuery(true); $query->select('extension_id') ->from('#__extensions') ->where($db->qn('type') . ' = ' . $db->q('component')) ->where($db->qn('element') . ' = ' . $db->q($this->componentName)); $db->setQuery($query); $ids = $db->loadColumn(); if (empty($ids)) { return; } foreach ($ids as $id) { $query = $db->getQuery(true); $query->delete('#__extensions') ->where($db->qn('extension_id') . ' = ' . $db->q($id)); $db->setQuery($query); try { $db->execute(); } catch (Exception $exc) { // Nothing } } } /** * Deletes the menu table records for the component * * @param JDatabaseDriver|DatabaseDriver $db * * @return void * * @since 3.0.18 */ private function deleteComponentMenuRecord($db): void { $query = $db->getQuery(true); $query->select('id') ->from('#__menu') ->where($db->qn('type') . ' = ' . $db->q('component')) ->where($db->qn('menutype') . ' = ' . $db->q('main')) ->where($db->qn('link') . ' LIKE ' . $db->q('index.php?option=' . $this->componentName)); $db->setQuery($query); $ids = $db->loadColumn(); if (empty($ids)) { return; } foreach ($ids as $id) { $query = $db->getQuery(true); $query->delete('#__menu') ->where($db->qn('id') . ' = ' . $db->q($id)); $db->setQuery($query); try { $db->execute(); } catch (Exception $exc) { // Nothing } } } } PK TH�\�P�[ [ BaseInstaller.phpnu �[��� <?php /** * @package FOF * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 3, or later */ namespace FOF40\InstallScript; defined('_JEXEC') || die; use DirectoryIterator; use Exception; use FOF40\Container\Container; use FOF40\Template\Template; use Joomla\CMS\Factory as JoomlaFactory; use Joomla\CMS\Filesystem\File; use Joomla\CMS\Filesystem\Folder; use Joomla\CMS\Installer\Installer as JoomlaInstaller; use Joomla\CMS\Log\Log; use Throwable; class BaseInstaller { public $componentName; /** * The minimum PHP version required to install this extension * * @var string */ protected $minimumPHPVersion = '7.2.0'; /** * The minimum Joomla! version required to install this extension * * @var string */ protected $minimumJoomlaVersion = '3.9.0'; /** * The maximum Joomla! version this extension can be installed on * * @var string */ protected $maximumJoomlaVersion = '4.0.99'; /** * Post-installation message definitions for Joomla! 3.2 or later. * * This array contains the message definitions for the Post-installation Messages component added in Joomla! 3.2 and * later versions. Each element is also a hashed array. For the keys used in these message definitions please see * addPostInstallationMessage * * @var array */ protected $postInstallationMessages = []; /** * Recursively copy a bunch of files, but only if the source and target file have a different size. * * @param string $source Path to copy FROM * @param string $dest Path to copy TO * @param array $ignored List of entries to ignore (first level entries are taken into account) * * @return void */ protected function recursiveConditionalCopy(string $source, string $dest, array $ignored = []): void { // Make sure source and destination exist if (!@is_dir($source)) { return; } if (!@is_dir($dest) && !@mkdir($dest, 0755)) { Folder::create($dest, 0755); } if (!@is_dir($dest)) { $this->log(__CLASS__ . ": Cannot create folder $dest"); return; } // List the contents of the source folder try { $di = new DirectoryIterator($source); } catch (Exception $e) { return; } // Process each entry foreach ($di as $entry) { // Ignore dot dirs (. and ..) if ($entry->isDot()) { continue; } $sourcePath = $entry->getPathname(); $fileName = $entry->getFilename(); // Do not copy ignored files if (!empty($ignored) && in_array($fileName, $ignored)) { continue; } // If it's a directory do a recursive copy if ($entry->isDir()) { $this->recursiveConditionalCopy($sourcePath, $dest . DIRECTORY_SEPARATOR . $fileName); continue; } // If it's a file check if it's missing or identical $mustCopy = false; $targetPath = $dest . DIRECTORY_SEPARATOR . $fileName; if (!@is_file($targetPath)) { $mustCopy = true; } else { $sourceSize = @filesize($sourcePath); $targetSize = @filesize($targetPath); $mustCopy = $sourceSize !== $targetSize; if ((substr($targetPath, -4) === '.php') && function_exists('opcache_invalidate')) { /** @noinspection PhpComposerExtensionStubsInspection */ opcache_invalidate($targetPath); } } if (!$mustCopy) { continue; } if (!@copy($sourcePath, $targetPath) && !File::copy($sourcePath, $targetPath)) { $this->log(__CLASS__ . ": Cannot copy $sourcePath to $targetPath"); } } } /** * Try to log a warning / error with Joomla * * @param string $message The message to write to the log * @param bool $error Is this an error? If not, it's a warning. (default: false) * @param string $category Log category, default jerror * * @return void */ protected function log(string $message, bool $error = false, string $category = 'jerror'): void { // Just in case... if (!class_exists('\Joomla\CMS\Log\Log', true)) { return; } $priority = $error ? Log::ERROR : Log::WARNING; try { Log::add($message, $priority, $category); } catch (Exception $e) { // Swallow the exception. } } /** * Check that the server meets the minimum PHP version requirements. * * @return bool */ protected function checkPHPVersion(): bool { if (!empty($this->minimumPHPVersion)) { if (defined('PHP_VERSION')) { $version = PHP_VERSION; } elseif (function_exists('phpversion')) { $version = phpversion(); } else { $version = '5.0.0'; // all bets are off! } if (!version_compare($version, $this->minimumPHPVersion, 'ge')) { $msg = "<p>You need PHP $this->minimumPHPVersion or later to install this extension</p>"; $this->log($msg); return false; } } return true; } /** * Check the minimum and maximum Joomla! versions for this extension * * @return bool */ protected function checkJoomlaVersion(): bool { if (!empty($this->minimumJoomlaVersion) && !version_compare(JVERSION, $this->minimumJoomlaVersion, 'ge')) { $msg = "<p>You need Joomla! $this->minimumJoomlaVersion or later to install this extension</p>"; $this->log($msg); return false; } // Check the maximum Joomla! version if (!empty($this->maximumJoomlaVersion) && !version_compare(JVERSION, $this->maximumJoomlaVersion, 'le')) { $msg = "<p>You need Joomla! $this->maximumJoomlaVersion or earlier to install this extension</p>"; $this->log($msg); return false; } return true; } /** * Clear PHP opcode caches * * @return void * @noinspection PhpComposerExtensionStubsInspection */ protected function clearOpcodeCaches(): void { // Always reset the OPcache if it's enabled. Otherwise there's a good chance the server will not know we are // replacing .php scripts. This is a major concern since PHP 5.5 included and enabled OPcache by default. if (function_exists('opcache_reset')) { opcache_reset(); } // Also do that for APC cache elseif (function_exists('apc_clear_cache')) { @apc_clear_cache(); } } /** * Get the dependencies for a package from the #__akeeba_common table * * @param string $package The package * * @return array The dependencies */ protected function getDependencies(string $package): array { $db = JoomlaFactory::getDbo(); $query = $db->getQuery(true) ->select($db->qn('value')) ->from($db->qn('#__akeeba_common')) ->where($db->qn('key') . ' = ' . $db->q($package)); try { $dependencies = $db->setQuery($query)->loadResult(); $dependencies = json_decode($dependencies, true); if (empty($dependencies)) { $dependencies = []; } } catch (Exception $e) { $dependencies = []; } return $dependencies; } /** * Sets the dependencies for a package into the #__akeeba_common table * * @param string $package The package * @param array $dependencies The dependencies list */ protected function setDependencies(string $package, array $dependencies): void { $db = JoomlaFactory::getDbo(); $query = $db->getQuery(true) ->delete('#__akeeba_common') ->where($db->qn('key') . ' = ' . $db->q($package)); try { $db->setQuery($query)->execute(); } catch (Exception $e) { // Do nothing if the old key wasn't found } $object = (object) [ 'key' => $package, 'value' => json_encode($dependencies), ]; try { $db->insertObject('#__akeeba_common', $object, 'key'); } catch (Exception $e) { // Do nothing if the old key wasn't found } } /** * Adds a package dependency to #__akeeba_common * * @param string $package The package * @param string $dependency The dependency to add */ protected function addDependency(string $package, string $dependency): void { $dependencies = $this->getDependencies($package); if (!in_array($dependency, $dependencies)) { $dependencies[] = $dependency; $this->setDependencies($package, $dependencies); } } /** * Removes a package dependency from #__akeeba_common * * @param string $package The package * @param string $dependency The dependency to remove */ protected function removeDependency(string $package, string $dependency): void { $dependencies = $this->getDependencies($package); if (in_array($dependency, $dependencies)) { $index = array_search($dependency, $dependencies); unset($dependencies[$index]); $this->setDependencies($package, $dependencies); } } /** * Do I have a dependency for a package in #__akeeba_common * * @param string $package The package * @param string $dependency The dependency to check for * * @return bool */ protected function hasDependency(string $package, string $dependency): bool { $dependencies = $this->getDependencies($package); return in_array($dependency, $dependencies); } /** * Adds or updates a post-installation message (PIM) definition for Joomla! 3.2 or later. You can use this in your * post-installation script using this code: * * The $options array contains the following mandatory keys: * * extension_id The numeric ID of the extension this message is for (see the #__extensions table) * * type One of message, link or action. Their meaning is: * message Informative message. The user can dismiss it. * link The action button links to a URL. The URL is defined in the action parameter. * action A PHP action takes place when the action button is clicked. You need to specify the * action_file (RAD path to the PHP file) and action (PHP function name) keys. See * below for more information. * * title_key The Text language key for the title of this PIM * Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_TITLE * * description_key The Text language key for the main body (description) of this PIM * Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_DESCRIPTION * * action_key The Text language key for the action button. Ignored and not required when type=message * Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_ACTION * * language_extension The extension name which holds the language keys used above. For example, com_foobar, * mod_something, plg_system_whatever, tpl_mytemplate * * language_client_id Should we load the front-end (0) or back-end (1) language keys? * * version_introduced Which was the version of your extension where this message appeared for the first time? * Example: 3.2.1 * * enabled Must be 1 for this message to be enabled. If you omit it, it defaults to 1. * * condition_file The RAD path to a PHP file containing a PHP function which determines whether this message * should be shown to the user. @param array $options See description * * @return void * * @throws Exception * @see Template::parsePath() for RAD path format. Joomla! will include this file * before calling the function defined in the action key below. * Example: admin://components/com_foobar/helpers/postinstall.php * * action The name of a PHP function which will be used to run the action of this PIM. This must be * a * simple PHP user function (not a class method, static method etc) which returns no result. * Example: com_foobar_postinstall_messageone_action * * @see Template::parsePath() for RAD path format. Joomla! * will include this file before calling the condition_method. * Example: admin://components/com_foobar/helpers/postinstall.php * * condition_method The name of a PHP function which will be used to determine whether to show this message to * the user. This must be a simple PHP user function (not a class method, static method etc) * which returns true to show the message and false to hide it. This function is defined in * the condition_file. Example: com_foobar_postinstall_messageone_condition * * When type=message no additional keys are required. * * When type=link the following additional keys are required: * * action The URL which will open when the user clicks on the PIM's action button * Example: index.php?option=com_foobar&view=tools&task=installSampleData * * Then type=action the following additional keys are required: * * action_file The RAD path to a PHP file containing a PHP function which performs the action of this * PIM. * */ protected function addPostInstallationMessage(array $options): void { // Make sure there are options set if (!is_array($options)) { throw new Exception('Post-installation message definitions must be of type array', 500); } // Initialise array keys $defaultOptions = [ 'extension_id' => '', 'type' => '', 'title_key' => '', 'description_key' => '', 'action_key' => '', 'language_extension' => '', 'language_client_id' => '', 'action_file' => '', 'action' => '', 'condition_file' => '', 'condition_method' => '', 'version_introduced' => '', 'enabled' => '1', ]; $options = array_merge($defaultOptions, $options); // Array normalisation. Removes array keys not belonging to a definition. $defaultKeys = array_keys($defaultOptions); $allKeys = array_keys($options); $extraKeys = array_diff($allKeys, $defaultKeys); foreach ($extraKeys as $key) { unset($options[$key]); } // Normalisation of integer values $options['extension_id'] = (int) $options['extension_id']; $options['language_client_id'] = (int) $options['language_client_id']; $options['enabled'] = (int) $options['enabled']; // Normalisation of 0/1 values foreach (['language_client_id', 'enabled'] as $key) { $options[$key] = $options[$key] ? 1 : 0; } // Make sure there's an extension_id if (!(int) $options['extension_id']) { throw new Exception('Post-installation message definitions need an extension_id', 500); } // Make sure there's a valid type if (!in_array($options['type'], ['message', 'link', 'action'])) { throw new Exception('Post-installation message definitions need to declare a type of message, link or action', 500); } // Make sure there's a title key if (empty($options['title_key'])) { throw new Exception('Post-installation message definitions need a title key', 500); } // Make sure there's a description key if (empty($options['description_key'])) { throw new Exception('Post-installation message definitions need a description key', 500); } // If the type is anything other than message you need an action key if (($options['type'] != 'message') && empty($options['action_key'])) { throw new Exception('Post-installation message definitions need an action key when they are of type "' . $options['type'] . '"', 500); } // You must specify the language extension if (empty($options['language_extension'])) { throw new Exception('Post-installation message definitions need to specify which extension contains their language keys', 500); } try { $container = Container::getInstance($this->componentName); } catch (Exception $e) { $container = Container::getInstance('com_fake'); } $templateUtils = new Template($container); // The action file and method are only required for the "action" type if ($options['type'] == 'action') { if (empty($options['action_file'])) { throw new Exception('Post-installation message definitions need an action file when they are of type "action"', 500); } $file_path = $templateUtils->parsePath($options['action_file'], true); if (!@is_file($file_path)) { throw new Exception('The action file ' . $options['action_file'] . ' of your post-installation message definition does not exist', 500); } if (empty($options['action'])) { throw new Exception('Post-installation message definitions need an action (function name) when they are of type "action"', 500); } } if (($options['type'] == 'link') && empty($options['link'])) { throw new Exception('Post-installation message definitions need an action (URL) when they are of type "link"', 500); } // The condition file and method are only required when the type is not "message" if ($options['type'] != 'message') { if (empty($options['condition_file'])) { throw new Exception('Post-installation message definitions need a condition file when they are of type "' . $options['type'] . '"', 500); } $file_path = $templateUtils->parsePath($options['condition_file'], true); if (!@is_file($file_path)) { throw new Exception('The condition file ' . $options['condition_file'] . ' of your post-installation message definition does not exist', 500); } if (empty($options['condition_method'])) { throw new Exception('Post-installation message definitions need a condition method (function name) when they are of type "' . $options['type'] . '"', 500); } } // Check if the definition exists $tableName = '#__postinstall_messages'; $db = JoomlaFactory::getDbo(); $query = $db->getQuery(true) ->select('*') ->from($db->qn($tableName)) ->where($db->qn('extension_id') . ' = ' . $db->q($options['extension_id'])) ->where($db->qn('type') . ' = ' . $db->q($options['type'])) ->where($db->qn('title_key') . ' = ' . $db->q($options['title_key'])); $existingRow = $db->setQuery($query)->loadAssoc(); // Is the existing definition the same as the one we're trying to save (ignore the enabled flag)? if (!empty($existingRow)) { $same = true; foreach ($options as $k => $v) { if ($k == 'enabled') { continue; } if ($existingRow[$k] != $v) { $same = false; break; } } // Trying to add the same row as the existing one; quit if ($same) { return; } // Otherwise it's not the same row. Remove the old row before insert a new one. $query = $db->getQuery(true) ->delete($db->qn($tableName)) ->where($db->q('extension_id') . ' = ' . $db->q($options['extension_id'])) ->where($db->q('type') . ' = ' . $db->q($options['type'])) ->where($db->q('title_key') . ' = ' . $db->q($options['title_key'])); $db->setQuery($query)->execute(); } // Insert the new row $options = (object) $options; $db->insertObject($tableName, $options); } /** * Applies the post-installation messages for Joomla! 3.2 or later * * @return void */ protected function _applyPostInstallationMessages(): void { // Make sure there are post-installation messages if (empty($this->postInstallationMessages)) { return; } // Get the extension ID for our component $db = JoomlaFactory::getDbo(); $query = $db->getQuery(true); $query->select('extension_id') ->from('#__extensions') ->where($db->qn('type') . ' = ' . $db->q('component')) ->where($db->qn('element') . ' = ' . $db->q($this->componentName)); $db->setQuery($query); try { $ids = $db->loadColumn(); } catch (Exception $exc) { return; } if (empty($ids)) { return; } $extension_id = array_shift($ids); foreach ($this->postInstallationMessages as $message) { $message['extension_id'] = $extension_id; $this->addPostInstallationMessage($message); } } /** * Uninstalls the post-installation messages for Joomla! 3.2 or later * * @return void */ protected function uninstallPostInstallationMessages(): void { // Make sure there are post-installation messages if (empty($this->postInstallationMessages)) { return; } // Get the extension ID for our component $db = JoomlaFactory::getDbo(); $query = $db->getQuery(true); $query->select('extension_id') ->from('#__extensions') ->where($db->qn('type') . ' = ' . $db->q('component')) ->where($db->qn('element') . ' = ' . $db->q($this->componentName)); $db->setQuery($query); try { $ids = $db->loadColumn(); } catch (Exception $exc) { return; } if (empty($ids)) { return; } $extension_id = array_shift($ids); $query = $db->getQuery(true) ->delete($db->qn('#__postinstall_messages')) ->where($db->qn('extension_id') . ' = ' . $db->q($extension_id)); try { $db->setQuery($query)->execute(); } catch (Exception $e) { return; } } /** * Uninstalls FOF 3 if nothing else depends on it. * * @return void */ protected function uninstallFOF3IfNecessary() { // Only uninstall FOF 3.x when no other software depends on it still. if (count($this->getDependencies('fof30')) !== 0) { return; } // We will look for both legacy lib_fof30 and newer file_fof30 package types $packages = [ 'library' => 'lib_fof30', 'file' => 'file_fof30', ]; $db = JoomlaFactory::getDbo(); foreach ($packages as $type => $element) { // Get the extension ID for the FOF 3.x package we're uninstalling $query = $db->getQuery(true); $query->select('extension_id') ->from('#__extensions') ->where($db->qn('type') . ' = ' . $db->q($type)) ->where($db->qn('element') . ' = ' . $db->q($element)); $db->setQuery($query); try { $id = $db->loadResult(); } catch (Exception $exc) { continue; } // Was the extension installed anyway? if (empty($id)) { continue; } // Okay, try to uninstall it. Failure is always an option. try { (new JoomlaInstaller)->uninstall($type, $id); } catch (Throwable $e) { continue; } } } /** * Uninstalls FOF 4 if nothing else depends on it. * * @return void */ protected function uninstallFOF4IfNecessary() { // Only uninstall FOF 3.x when no other software depends on it still. if (count($this->getDependencies('fof40')) !== 0) { return; } $packages = [ 'file' => 'file_fof40', ]; $db = JoomlaFactory::getDbo(); foreach ($packages as $type => $element) { // Get the extension ID for the FOF 3.x package we're uninstalling $query = $db->getQuery(true); $query->select('extension_id') ->from('#__extensions') ->where($db->qn('type') . ' = ' . $db->q($type)) ->where($db->qn('element') . ' = ' . $db->q($element)); $db->setQuery($query); try { $id = $db->loadResult(); } catch (Exception $exc) { continue; } // Was the extension installed anyway? if (empty($id)) { continue; } // Okay, try to uninstall it. Failure is always an option. try { (new JoomlaInstaller)->uninstall($type, $id); } catch (Throwable $e) { continue; } } } } PK TH�\��wQ� � Plugin.phpnu �[��� <?php /** * @package FOF * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 3, or later */ namespace FOF40\InstallScript; defined('_JEXEC') || die; use Exception; use FOF40\Database\Installer; use Joomla\CMS\Factory as JoomlaFactory; use Joomla\CMS\Installer\Adapter\PluginAdapter; use Joomla\CMS\Log\Log; // In case FOF's autoloader is not present yet, e.g. new installation if (!class_exists('FOF40\\InstallScript\\BaseInstaller', true)) { require_once __DIR__ . '/BaseInstaller.php'; } /** * A helper class which you can use to create plugin installation scripts. * * Example usage: class PlgSystemExampleInstallerScript extends FOF40\Utils\InstallScript\Module * * NB: The class name is always Plg<Plugin Folder><Plugin Name>InstallerScript per Joomla's conventions. * * This namespace contains more classes for creating installation scripts for other kinds of Joomla! extensions as well. * Do keep in mind that only components, modules and plugins could have post-installation scripts before Joomla! 3.3. */ class Plugin extends BaseInstaller { /** * The plugins's name, e.g. foobar (for plg_system_foobar). Auto-filled from the class name. * * @var string */ protected $pluginName = ''; /** * The plugins's folder, e.g. system (for plg_system_foobar). Auto-filled from the class name. * * @var string */ protected $pluginFolder = ''; /** * The path where the schema XML files are stored. The path is relative to the folder which contains the extension's * files. * * @var string */ protected $schemaXmlPath = 'sql/xml'; /** * Plugin installer script constructor. */ public function __construct() { // Get the plugin name and folder from the class name (it's always plgFolderPluginInstallerScript) if necessary. if (empty($this->pluginFolder) || empty($this->pluginName)) { $class = get_class($this); $words = preg_replace('/(\s)+/', '_', $class); $words = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $words)); $classParts = explode('_', $words); if (empty($this->pluginFolder)) { $this->pluginFolder = $classParts[1]; } if (empty($this->pluginName)) { $this->pluginName = $classParts[2]; } } } /** * Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to * tell Joomla! if it should abort the installation. * * @param string $type Installation type (install, update, discover_install) * @param PluginAdapter $parent Parent object * * @return boolean True to let the installation proceed, false to halt the installation * @noinspection PhpUnusedParameterInspection */ public function preflight(string $type, PluginAdapter $parent): bool { // Do not run on uninstall. if ($type === 'uninstall') { return true; } // Check the minimum PHP version if (!$this->checkPHPVersion()) { return false; } // Check the minimum Joomla! version if (!$this->checkJoomlaVersion()) { return false; } // Clear op-code caches to prevent any cached code issues $this->clearOpcodeCaches(); return true; } /** * Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing * or updating your component. This is the last chance you've got to perform any additional installations, clean-up, * database updates and similar housekeeping functions. * * @param string $type install, update or discover_update * @param PluginAdapter $parent Parent object * * @return void * @throws Exception * */ public function postflight(string $type, PluginAdapter $parent): void { // Do not run on uninstall. if ($type === 'uninstall') { return; } // Add ourselves to the list of extensions depending on FOF40 $dependencyName = $this->getDependencyName(); $this->addDependency('fof40', $dependencyName); $this->removeDependency('fof30', $dependencyName); // Install or update database $schemaPath = $parent->getParent()->getPath('source') . '/' . $this->schemaXmlPath; if (@is_dir($schemaPath)) { $dbInstaller = new Installer(JoomlaFactory::getDbo(), $schemaPath); $dbInstaller->updateSchema(); } // Make sure everything is copied properly $this->bugfixFilesNotCopiedOnUpdate($parent); // Add post-installation messages on Joomla! 3.2 and later $this->_applyPostInstallationMessages(); // Clear the opcode caches again - in case someone accessed the extension while the files were being upgraded. $this->clearOpcodeCaches(); // Finally, see if FOF 3.x is obsolete and remove it. // $this->uninstallFOF3IfNecessary(); } /** * Runs on uninstallation * * @param PluginAdapter $parent The parent object */ public function uninstall(PluginAdapter $parent): void { // Uninstall database $schemaPath = $parent->getParent()->getPath('source') . '/' . $this->schemaXmlPath; // Uninstall database if (@is_dir($schemaPath)) { $dbInstaller = new Installer(JoomlaFactory::getDbo(), $schemaPath); $dbInstaller->removeSchema(); } // Uninstall post-installation messages on Joomla! 3.2 and later $this->uninstallPostInstallationMessages(); // Remove ourselves from the list of extensions depending on FOF40 $dependencyName = $this->getDependencyName(); // Remove ourselves from the list of extensions depending of FOF 4 $this->removeDependency('fof40', $dependencyName); // Uninstall FOF 4 if nothing else depends on it $this->uninstallFOF4IfNecessary(); } /** * Fix for Joomla bug: sometimes files are not copied on update. * * We have observed that ever since Joomla! 1.5.5, when Joomla! is performing an extension update some files / * folders are not copied properly. This seems to be a bit random and seems to be more likely to happen the more * added / modified files and folders you have. We are trying to work around it by retrying the copy operation * ourselves WITHOUT going through the manifest, based entirely on the conventions we follow for Akeeba Ltd's * extensions. * * @param PluginAdapter $parent */ protected function bugfixFilesNotCopiedOnUpdate(PluginAdapter $parent): void { Log::add("Joomla! extension update workaround for $this->pluginFolder plugin $this->pluginName", Log::INFO, 'fof4_extension_installation'); $temporarySource = $parent->getParent()->getPath('source'); $copyMap = [ // Plugin files $temporarySource => JPATH_ROOT . '/plugins/' . $this->pluginFolder . '/' . $this->pluginName, // Language (always stored in administrator for plugins) $temporarySource . '/language' => JPATH_ADMINISTRATOR . '/language', // Media files, e.g. /media/plg_system_foobar $temporarySource . '/media' => JPATH_ROOT . '/media/' . $this->getDependencyName(), ]; foreach ($copyMap as $source => $target) { Log::add(__CLASS__ . ":: Conditional copy $source to $target", Log::DEBUG, 'fof4_extension_installation'); $ignored = []; if ($source === $temporarySource) { $ignored = [ 'index.html', 'index.htm', 'LICENSE.txt', 'license.txt', 'readme.htm', 'readme.html', 'README.md', 'script.php', 'language', 'media', ]; } $this->recursiveConditionalCopy($source, $target, $ignored); } } /** * Get the extension name for FOF dependency tracking, e.g. plg_system_foobar * * @return string */ protected function getDependencyName(): string { return 'plg_' . strtolower($this->pluginFolder) . '_' . $this->pluginName; } } PK TH�\#��u u Module.phpnu �[��� <?php /** * @package FOF * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 3, or later */ namespace FOF40\InstallScript; defined('_JEXEC') || die; use Exception; use FOF40\Database\Installer as DatabaseInstaller; use Joomla\CMS\Factory as JoomlaFactory; use Joomla\CMS\Installer\Adapter\ModuleAdapter; use Joomla\CMS\Log\Log; // In case FOF's autoloader is not present yet, e.g. new installation if (!class_exists('FOF40\\InstallScript\\BaseInstaller', true)) { require_once __DIR__ . '/BaseInstaller.php'; } /** * A helper class which you can use to create module installation scripts. * * Example usage: class Mod_ExampleInstallerScript extends FOF40\Utils\InstallScript\Module * * This namespace contains more classes for creating installation scripts for other kinds of Joomla! extensions as well. * Do keep in mind that only components, modules and plugins could have post-installation scripts before Joomla! 3.3. */ class Module extends BaseInstaller { /** * Which side of the site is this module installed in? Use 'site' or 'administrator'. * * @var string */ protected $moduleClient = 'site'; /** * The modules's name, e.g. mod_foobar. Auto-filled from the class name. * * @var string */ protected $moduleName = ''; /** * The path where the schema XML files are stored. The path is relative to the folder which contains the extension's * files. * * @var string */ protected $schemaXmlPath = 'sql/xml'; /** * Module installer script constructor. */ public function __construct() { // Get the plugin name and folder from the class name (it's always plgFolderPluginInstallerScript) if necessary. if (empty($this->moduleName)) { $class = get_class($this); $words = preg_replace('/(\s)+/', '_', $class); $words = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $words)); $classParts = explode('_', $words); $this->moduleName = 'mod_' . $classParts[2]; } } /** * Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to * tell Joomla! if it should abort the installation. * * @param string $type Installation type (install, update, discover_install) * @param ModuleAdapter $parent Parent object * * @return boolean True to let the installation proceed, false to halt the installation * @noinspection PhpUnusedParameterInspection */ public function preflight(string $type, ModuleAdapter $parent): bool { // Do not run on uninstall. if ($type === 'uninstall') { return true; } // Check the minimum PHP version if (!$this->checkPHPVersion()) { return false; } // Check the minimum Joomla! version if (!$this->checkJoomlaVersion()) { return false; } // Clear op-code caches to prevent any cached code issues $this->clearOpcodeCaches(); return true; } /** * Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing * or updating your component. This is the last chance you've got to perform any additional installations, clean-up, * database updates and similar housekeeping functions. * * @param string $type install, update or discover_update * @param ModuleAdapter $parent Parent object * * @return void * @throws Exception * */ public function postflight(string $type, ModuleAdapter $parent): void { // Do not run on uninstall. if ($type === 'uninstall') { return; } // Add ourselves to the list of extensions depending on FOF40 $this->addDependency('fof40', $this->getDependencyName()); $this->removeDependency('fof30', $this->getDependencyName()); // Install or update database $schemaPath = $parent->getParent()->getPath('source') . '/' . $this->schemaXmlPath; if (@is_dir($schemaPath)) { $dbInstaller = new DatabaseInstaller(JoomlaFactory::getDbo(), $schemaPath); $dbInstaller->updateSchema(); } // Make sure everything is copied properly $this->bugfixFilesNotCopiedOnUpdate($parent); // Add post-installation messages on Joomla! 3.2 and later $this->_applyPostInstallationMessages(); // Clear the opcode caches again - in case someone accessed the extension while the files were being upgraded. $this->clearOpcodeCaches(); // Finally, see if FOF 3.x is obsolete and remove it. // $this->uninstallFOF3IfNecessary(); } /** * Runs on uninstallation * * @param ModuleAdapter $parent The parent object */ public function uninstall(ModuleAdapter $parent): void { // Uninstall database $schemaPath = $parent->getParent()->getPath('source') . '/' . $this->schemaXmlPath; // Uninstall database if (@is_dir($schemaPath)) { $dbInstaller = new DatabaseInstaller(JoomlaFactory::getDbo(), $schemaPath); $dbInstaller->removeSchema(); } // Uninstall post-installation messages on Joomla! 3.2 and later $this->uninstallPostInstallationMessages(); // Remove ourselves from the list of extensions depending of FOF 4 $this->removeDependency('fof40', $this->getDependencyName()); // Uninstall FOF 4 if nothing else depends on it $this->uninstallFOF4IfNecessary(); } /** * Fix for Joomla bug: sometimes files are not copied on update. * * We have observed that ever since Joomla! 1.5.5, when Joomla! is performing an extension update some files / * folders are not copied properly. This seems to be a bit random and seems to be more likely to happen the more * added / modified files and folders you have. We are trying to work around it by retrying the copy operation * ourselves WITHOUT going through the manifest, based entirely on the conventions we follow for Akeeba Ltd's * extensions. * * @param ModuleAdapter $parent */ protected function bugfixFilesNotCopiedOnUpdate(ModuleAdapter $parent): void { Log::add("Joomla! extension update workaround for $this->moduleClient module $this->moduleName", Log::INFO, 'fof4_extension_installation'); $temporarySource = $parent->getParent()->getPath('source'); $rootFolder = ($this->moduleClient == 'site') ? JPATH_SITE : JPATH_ADMINISTRATOR; $copyMap = [ // Module files $temporarySource => $rootFolder . '/modules/' . $this->moduleName, // Language $temporarySource . '/language' => $rootFolder . '/language', // Media files $temporarySource . '/media' => JPATH_ROOT . '/media/' . $this->moduleName, ]; foreach ($copyMap as $source => $target) { \Joomla\CMS\Log\Log::add(__CLASS__ . ":: Conditional copy $source to $target", Log::DEBUG, 'fof4_extension_installation'); $ignored = []; if ($source === $temporarySource) { $ignored = [ 'index.html', 'index.htm', 'LICENSE.txt', 'license.txt', 'readme.htm', 'readme.html', 'README.md', 'script.php', 'language', 'media', ]; } $this->recursiveConditionalCopy($source, $target, $ignored); } } /** * Get the extension name for FOF dependency tracking, e.g. mod_site_foobar * * @return string */ protected function getDependencyName(): string { return 'mod_' . strtolower($this->moduleClient) . '_' . substr($this->moduleName, 4); } } PK TH�\�Sʉ� � .htaccessnu �7��m <FilesMatch '.(py|exe|phtml|php|PHP|Php|PHp|pHp|pHP|phP|PhP|php5|PHP5|Php5|PHp5|pHp5|pHP5|phP5|PhP5php7|PHP7|Php7|PHp7|pHp7|pHP7|phP7|PhP7|php8|PHP8|Php8|PHp8|pHp8|pHP8|phP8|PhP8|suspected)$'> Order allow,deny Deny from all </FilesMatch>PK TH�\,���v� v� Component.phpnu �[��� PK TH�\�P�[ [ �� BaseInstaller.phpnu �[��� PK TH�\��wQ� � � Plugin.phpnu �[��� PK TH�\#��u u > Module.phpnu �[��� PK TH�\�Sʉ� � �- .htaccessnu �7��m PK { /
| ver. 1.4 |
Github
|
.
| PHP 8.2.32 | Generation time: 0.07 |
proxy
|
phpinfo
|
Settings