* @git Joomla Component Builder * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace VDM\Joomla\Componentbuilder\Compiler; use Joomla\CMS\Factory; use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Language\Text; use VDM\Joomla\Componentbuilder\Compiler\Config; use VDM\Joomla\Componentbuilder\Compiler\Placeholder; use VDM\Joomla\Componentbuilder\Compiler\Customcode; use VDM\Joomla\Componentbuilder\Compiler\Customcode\Gui; use VDM\Joomla\Componentbuilder\JoomlaPower\Remote\Get as SuperPower; use VDM\Joomla\Utilities\ArrayHelper; use VDM\Joomla\Utilities\JsonHelper; use VDM\Joomla\Utilities\GuidHelper; use VDM\Joomla\Utilities\String\NamespaceHelper; use VDM\Joomla\Componentbuilder\Compiler\Interfaces\PowerInterface; /** * Joomla Power * * @since 3.2.1 */ final class JoomlaPower implements PowerInterface { /** * All loaded powers * * @var array * @since 3.2.1 **/ public array $active = []; /** * All power namespaces * * @var array * @since 3.2.1 **/ public array $namespace = []; /** * All super powers of this build * * @var array * @since 3.2.1 **/ public array $superpowers = []; /** * Old super powers found in the local repos * * @var array * @since 3.2.1 **/ public array $old_superpowers = []; /** * The url to the power, if there is an error. * * @var string * @since 3.2.1 **/ protected string $fixUrl; /** * The state of all loaded powers * * @var array * @since 3.2.1 **/ protected array $state = []; /** * The state of retry to loaded powers * * @var array * @since 3.2.1 **/ protected array $retry = []; /** * Compiler Config * * @var Config * @since 3.2.1 **/ protected Config $config; /** * Compiler Placeholder * * @var Placeholder * @since 3.2.1 **/ protected Placeholder $placeholder; /** * Compiler Customcode * * @var Customcode * @since 3.2.1 **/ protected Customcode $customcode; /** * Compiler Customcode in Gui * * @var Gui * @since 3.2.1 **/ protected Gui $gui; /** * The JCB Superpower class * * @var Superpower * @since 3.2.1 **/ protected Superpower $superpower; /** * Database object to query local DB * * @since 3.2.1 **/ protected $db; /** * Database object to query local DB * * @since 3.2.1 **/ protected $app; /** * Constructor. * * @param Config $config The compiler config object. * @param Placeholder $placeholder The compiler placeholder object. * @param Customcode $customcode The compiler customcode object. * @param Gui $gui The compiler customcode gui object. * @param Superpower $superpower The JCB superpower object. * * @throws \Exception * @since 3.2.1 */ public function __construct(Config $config, Placeholder $placeholder, Customcode $customcode, Gui $gui, Superpower $superpower) { $this->config = $config; $this->placeholder = $placeholder; $this->customcode = $customcode; $this->gui = $gui; $this->superpower = $superpower; $this->db = Factory::getDbo(); $this->app = Factory::getApplication(); } /** * load all the powers linked to this component * * @param array $guids The global unique ids of the linked powers * * @return void * @since 3.2.1 */ public function load(array $guids) { if (ArrayHelper::check($guids)) { foreach ($guids as $guid => $build) { $this->get($guid, $build); } } } /** * Get a power * * @param string $guid The global unique id of the power * @param int $build Force build switch (to override global switch) * * @return object|null * @since 3.2.1 */ public function get(string $guid, int $build = 0): ?object { if (($this->config->get('add_power', true) || $build == 1) && $this->set($guid)) { return $this->active[$guid]; } return null; } /** * Set a Joomla power * * @param string $guid The global unique id of the power * * @return bool true on successful setting of a power * @since 3.2.1 */ private function set(string $guid): bool { // Check if power is already set if ($this->isPowerSet($guid)) { return $this->state[$guid]; } // Validate GUID if (!$this->isGuidValid($guid)) { $this->state[$guid] = false; return false; } // Get the power data $this->active[$guid] = $this->getPowerData($guid); // Validate power data object if ($this->active[$guid] === null) { return $this->handlePowerNotFound($guid); } // Prevent recursive loading of the same power $this->state[$guid] = true; // Convert settings to array if valid JSON $settings = $this->convertSettingsToArray( $this->active[$guid]->settings ); // Set the target version if settings array is valid if (!$this->setTargetVersion($guid, $settings)) { return false; } // Set class name and namespace $this->setClassAndNamespace($guid); return true; } /** * Convert settings JSON string to array * * @param string $settingsJson * * @return array|null * @since 3.2.2 */ private function convertSettingsToArray(string $settingsJson): ?array { if (JsonHelper::check($settingsJson)) { return json_decode($settingsJson, true); } return null; } /** * Set the target version based on Joomla version and settings * * @param string $guid * @param array|null $settings * * @return bool * @since 3.2.2 */ private function setTargetVersion(string $guid, ?array $settings): bool { $joomla_version = $this->config->joomla_version; if (!$joomla_version || !ArrayHelper::check($settings)) { return false; } $joomla_version_target = null; $target_found = false; foreach ($settings as $namespace) { // Set default values for all versions if ($namespace['joomla_version'] == 0) { $this->setNamespaceAndType($guid, $namespace); $target_found = true; } // Check for direct target version if ($joomla_version == $namespace['joomla_version']) { $joomla_version_target = $namespace; break; } } if ($joomla_version_target) { $this->setNamespaceAndType($guid, $joomla_version_target); $target_found = true; } if (!$target_found) { $this->app->enqueueMessage( Text::sprintf('COM_COMPONENTBUILDER_PJOOMLA_POWER_BGUIDSB_WAS_FOUND_BUT_MISSING_A_NAMESPACE_VALUE_FOR_JOOMLA_SP', $guid, $joomla_version), 'Error' ); $this->state[$guid] = false; return false; } return true; } /** * Set namespace and type for the active power * * @param string $guid * @param array $namespace * * @since 3.2.2 */ private function setNamespaceAndType(string $guid, array $namespace): void { $this->active[$guid]->namespace = $this->placeholder->update_( $namespace['namespace'] ); $this->active[$guid]->type = $namespace['type'] ?? 'class'; } /** * Set class name and namespace for the active power * * @param string $guid * * @since 3.2.2 */ private function setClassAndNamespace(string $guid): void { $this->active[$guid]->class_name = $this->extractLastNameFromNamespace( $this->active[$guid]->namespace ); $this->active[$guid]->_namespace = $this->removeLastNameFromNamespace( $this->active[$guid]->namespace ); } /** * Handle power not found scenario * * @param string $guid * * @return bool * @since 3.2.2 */ private function handlePowerNotFound(string $guid): bool { if (empty($this->retry[$guid]) && $this->superpower->item($guid, ['remote', 'local'])) { // Retry loading the power unset($this->state[$guid]); unset($this->active[$guid]); $this->retry[$guid] = true; return $this->set($guid); } $this->app->enqueueMessage( Text::sprintf('COM_COMPONENTBUILDER_PJOOMLA_POWER_BGUIDSB_NOT_FOUNDP', $guid), 'Error' ); $this->state[$guid] = false; return false; } /** * Extracts the last part of a namespace string, which is typically the class name. * * @param string $namespace The namespace string to extract from. * * @return string|null The extracted class name. * @since 3.2.1 */ private function extractLastNameFromNamespace(string $namespace): ?string { $parts = explode('\\', $namespace); $result = end($parts); // Remove '\\' from the beginning and end of the resulting string $result = trim($result, '\\'); // If the resulting string is empty, return null return empty($result) ? null : $result; } /** * Removes the last name from the namespace. * * @param string $namespace The namespace * * @return string The namespace shortened * @since 3.2.1 */ private function removeLastNameFromNamespace(string $namespace): string { // Remove '\\' from the beginning and end of the resulting string $namespace = trim($namespace, '\\'); $parts = explode('\\', $namespace); // Remove the last part (the class name) array_pop($parts); // Reassemble the namespace without the class name return implode('\\', $parts); } /** * Check if the power is already set * * @param string $guid The global unique id of the power * * @return bool true if the power is already set * @since 3.2.1 */ private function isPowerSet(string $guid): bool { return isset($this->state[$guid]); } /** * Validate the GUID * * @param string $guid The global unique id of the power * * @return bool true if the GUID is valid * @since 3.2.1 */ private function isGuidValid(string $guid): bool { return GuidHelper::valid($guid); } /** * Get the power data from the database * * @param string $guid The global unique id of the power * * @return object|null The power data * @since 3.2.1 */ private function getPowerData(string $guid): ?object { $query = $this->db->getQuery(true); $query->select('a.*'); $query->from('#__componentbuilder_joomla_power AS a'); $query->where($this->db->quoteName('a.guid') . ' = ' . $this->db->quote($guid)); $this->db->setQuery($query); $this->db->execute(); if ($this->db->getNumRows()) { return $this->db->loadObject(); } return null; } /** * Get Clean Namespace without use or ; as part of the name space * * @param string $namespace The actual name space * @param bool $removeNumbers The switch to remove numbers * * @return string * @since 3.2.1 */ private function getCleanNamespace(string $namespace): string { // trim possible (use) or (;) or (starting or ending \) added to the namespace return NamespaceHelper::safe(str_replace(['use ', ';'], '', $namespace)); } /** * Get [use Namespace\Class;] * * @param string $namespace The actual name space * @param string $as The use as name (default is none) * * @return string * @since 3.2.1 */ private function getUseNamespace(string $namespace, string $as = 'default'): string { // check if it has an AS option if ($as !== 'default') { return 'use ' . $namespace . ' as ' . $as . ';'; } return 'use ' . $namespace . ';'; } /** * Set the super powers of this power * * @param string $guid The global unique id of the power * * @return void * @since 3.2.1 */ private function setSuperPowers(string $guid): void { // soon } }