Release of v5.0.0-alpha8
Add power path override option on component level. Fix the sql build feature. #1032.
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 4th September, 2022
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/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\Power;
|
||||
|
||||
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Config;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Builder\ContentOne as Content;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Line;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Indent;
|
||||
use VDM\Joomla\Utilities\ArrayHelper;
|
||||
|
||||
|
||||
/**
|
||||
* Compiler Autoloader
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
/**
|
||||
* The Power Class.
|
||||
*
|
||||
* @var Power
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Power $power;
|
||||
|
||||
/**
|
||||
* The Config Class.
|
||||
*
|
||||
* @var Config
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Config $config;
|
||||
|
||||
/**
|
||||
* The ContentOne Class.
|
||||
*
|
||||
* @var Content
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Content $content;
|
||||
|
||||
/**
|
||||
* Helper Class Autoloader
|
||||
*
|
||||
* @var string
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected string $helper = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Power $power The Power Class.
|
||||
* @param Config $config The Config Class.
|
||||
* @param Content $content The ContentOne Class.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __construct(Power $power, Config $config, Content $content)
|
||||
{
|
||||
$this->power = $power;
|
||||
$this->config = $config;
|
||||
$this->content = $content;
|
||||
|
||||
// reset all autoloaders power placeholders
|
||||
$this->content->set('ADMIN_POWER_HELPER', '');
|
||||
$this->content->set('SITE_POWER_HELPER', '');
|
||||
$this->content->set('CUSTOM_POWER_AUTOLOADER', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the autoloader into the active content array
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function set()
|
||||
{
|
||||
if (ArrayHelper::check($this->power->namespace))
|
||||
{
|
||||
/************************* IMPORTANT SORT NOTICE ***********************************************
|
||||
* make sure the name space values are sorted from the longest string to the shortest
|
||||
* so that the search do not mistakenly match a shorter namespace before a longer one
|
||||
* that has the same short namespace for example:
|
||||
* NameSpace\SubName\Sub <- will always match first
|
||||
* NameSpace\SubName\SubSubName
|
||||
* Should the shorter namespace be listed [first] it will match both of these:
|
||||
* NameSpace\SubName\Sub\ClassName
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^
|
||||
* NameSpace\SubName\SubSubName\ClassName
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^
|
||||
***********************************************************************************************/
|
||||
uksort($this->power->namespace, fn($a, $b) => strlen((string) $b) - strlen((string) $a));
|
||||
|
||||
// check if we are using a plugin
|
||||
if ($this->loadPluginAutoloader())
|
||||
{
|
||||
$this->content->set('PLUGIN_POWER_AUTOLOADER', $this->getPluginAutoloader());
|
||||
}
|
||||
|
||||
// load to the helper class
|
||||
if ($this->loadHelperAutoloader())
|
||||
{
|
||||
// load to admin helper class
|
||||
$this->content->add('ADMIN_POWER_HELPER', $this->getHelperAutoloader());
|
||||
|
||||
// load to site helper class if needed
|
||||
if ($this->loadSiteAutoloader())
|
||||
{
|
||||
$this->content->add('SITE_POWER_HELPER', $this->getHelperAutoloader());
|
||||
}
|
||||
}
|
||||
|
||||
// to add to custom files
|
||||
$this->content->add('CUSTOM_POWER_AUTOLOADER', $this->getHelperAutoloader());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we load the plugin autoloader
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function loadPluginAutoloader(): bool
|
||||
{
|
||||
return $this->content->exists('PLUGIN_POWER_AUTOLOADER');
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we load the helper class autoloader
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function loadHelperAutoloader(): bool
|
||||
{
|
||||
// for now we load it if the plugin is not loaded
|
||||
// but we may want to add a switch that
|
||||
// controls this behaviour.
|
||||
// return !$this->loadPluginAutoloader();
|
||||
// lets load it anyway (can't break anything)
|
||||
// but we will still like a switch for this
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we load the autoloader in site area
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function loadSiteAutoloader(): bool
|
||||
{
|
||||
return (!$this->config->remove_site_folder || !$this->config->remove_site_edit_folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get helper autoloader code
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getPluginAutoloader(): string
|
||||
{
|
||||
// load the code
|
||||
$code = [];
|
||||
|
||||
// if we should not load in the site are
|
||||
if (($script = $this->getBLockSiteLoading()) !== null)
|
||||
{
|
||||
$code[] = $script;
|
||||
}
|
||||
|
||||
// add the composer stuff here
|
||||
if (($script = $this->getComposer(2)) !== null)
|
||||
{
|
||||
$code[] = $script;
|
||||
}
|
||||
|
||||
// get the helper autoloader
|
||||
if (($script = $this->getAutoloader(2)) !== null)
|
||||
{
|
||||
$code[] = $script;
|
||||
}
|
||||
|
||||
// if we have any
|
||||
if (!empty($code))
|
||||
{
|
||||
return PHP_EOL . PHP_EOL . implode(PHP_EOL . PHP_EOL, $code);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get helper autoloader code
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getHelperAutoloader(): string
|
||||
{
|
||||
// check if it was already build
|
||||
if (!empty($this->helper))
|
||||
{
|
||||
return $this->helper;
|
||||
}
|
||||
|
||||
// load the code
|
||||
$code = [];
|
||||
|
||||
// add the composer stuff here
|
||||
if (($script = $this->getComposer(0)) !== null)
|
||||
{
|
||||
$code[] = $script;
|
||||
}
|
||||
|
||||
// get the helper autoloader
|
||||
if (($script = $this->getAutoloader(0)) !== null)
|
||||
{
|
||||
$code[] = $script;
|
||||
}
|
||||
|
||||
// if we have any
|
||||
if (!empty($code))
|
||||
{
|
||||
$this->helper = PHP_EOL . PHP_EOL . implode(PHP_EOL . PHP_EOL, $code);
|
||||
}
|
||||
|
||||
return $this->helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get code that will block the plugin from loading
|
||||
* the autoloader in the site area
|
||||
*
|
||||
* @return string|null
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getBLockSiteLoading(): ?string
|
||||
{
|
||||
// if we should not load in the site are
|
||||
if (!$this->loadSiteAutoloader())
|
||||
{
|
||||
// we add code to prevent this plugin from triggering on the site area
|
||||
$not_site = [];
|
||||
$not_site[] = Indent::_(2) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' do not run the autoloader in the site area';
|
||||
$not_site[] = Indent::_(2) . 'if ($this->app->isClient(\'site\'))';
|
||||
$not_site[] = Indent::_(2) . '{';
|
||||
$not_site[] = Indent::_(3) . 'return;';
|
||||
$not_site[] = Indent::_(2) . '}';
|
||||
|
||||
return implode(PHP_EOL, $not_site);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get autoloader code
|
||||
*
|
||||
* @param int $tabSpace The dynamic tab spacer
|
||||
*
|
||||
* @return string|null
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getAutoloader(int $tabSpace): ?string
|
||||
{
|
||||
if (($size = ArrayHelper::check($this->power->namespace)) > 0)
|
||||
{
|
||||
// we start building the spl_autoload_register function call
|
||||
$autoload_method = [];
|
||||
$autoload_method[] = Indent::_($tabSpace) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' register additional namespace';
|
||||
$autoload_method[] = Indent::_($tabSpace) . '\spl_autoload_register(function ($class) {';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' project-specific base directories and namespace prefix';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '$search = [';
|
||||
|
||||
// counter to manage the comma in the actual array
|
||||
$counter = 1;
|
||||
foreach ($this->power->namespace as $base_dir => $prefix)
|
||||
{
|
||||
// don't add the ending comma on last value
|
||||
if ($size == $counter)
|
||||
{
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . "'" . $this->config->get('jcb_powers_path', 'libraries/jcb_powers') . "/$base_dir' => '" . implode('\\\\', $prefix) . "'";
|
||||
}
|
||||
else
|
||||
{
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . "'" . $this->config->get('jcb_powers_path', 'libraries/jcb_powers') . "/$base_dir' => '" . implode('\\\\', $prefix) . "',";
|
||||
}
|
||||
$counter++;
|
||||
}
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '];';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '// Start the search and load if found';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '$found = false;';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '$found_base_dir = "";';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '$found_len = 0;';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . 'foreach ($search as $base_dir => $prefix)';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '{';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' does the class use the namespace prefix?';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . '$len = strlen($prefix);';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . 'if (strncmp($prefix, $class, $len) === 0)';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . '{';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(3) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' we have a match so load the values';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(3) . '$found = true;';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(3) . '$found_base_dir = $base_dir;';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(3) . '$found_len = $len;';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(3) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' done here';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(3) . 'break;';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . '}';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '}';
|
||||
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' check if we found a match';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . 'if (!$found)';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '{';
|
||||
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' not found so move to the next registered autoloader';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . 'return;';
|
||||
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '}';
|
||||
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' get the relative class name';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '$relative_class = substr($class, $found_len);';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' replace the namespace prefix with the base directory, replace namespace';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '// separators with directory separators in the relative class name, append';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '// with .php';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . "\$file = JPATH_ROOT . '/' . \$found_base_dir . '/src' . str_replace('\\\\', '/', \$relative_class) . '.php';";
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' if the file exists, require it';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . 'if (file_exists($file))';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '{';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(2) . 'require $file;';
|
||||
$autoload_method[] = Indent::_($tabSpace) . Indent::_(1) . '}';
|
||||
$autoload_method[] = Indent::_($tabSpace) . '});';
|
||||
|
||||
return implode(PHP_EOL, $autoload_method);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the composer autoloader routine
|
||||
*
|
||||
* @param int $tabSpace The dynamic tab spacer
|
||||
*
|
||||
* @return string|null
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getComposer(int $tabSpace): ?string
|
||||
{
|
||||
if (ArrayHelper::check($this->power->composer))
|
||||
{
|
||||
// load the composer routine
|
||||
$composer_routine = [];
|
||||
|
||||
// counter to manage the comma in the actual array
|
||||
$add_once = [];
|
||||
foreach ($this->power->composer as $access_point)
|
||||
{
|
||||
// don't add the ending comma on last value
|
||||
if (empty($add_once[$access_point]))
|
||||
{
|
||||
$composer_routine[] = Indent::_($tabSpace) . "\$composer_autoloader = JPATH_LIBRARIES . '/$access_point';";
|
||||
$composer_routine[] = Indent::_($tabSpace) . 'if (file_exists($composer_autoloader))';
|
||||
$composer_routine[] = Indent::_($tabSpace) . "{";
|
||||
$composer_routine[] = Indent::_($tabSpace) . Indent::_(1) . 'require_once $composer_autoloader;';
|
||||
$composer_routine[] = Indent::_($tabSpace) . "}";
|
||||
|
||||
$add_once[$access_point] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// this is just about the [autoloader or autoloaders] in the comment ;)
|
||||
if (count($add_once) == 1)
|
||||
{
|
||||
array_unshift($composer_routine, Indent::_($tabSpace) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' add the autoloader for the composer classes');
|
||||
}
|
||||
else
|
||||
{
|
||||
array_unshift($composer_routine, Indent::_($tabSpace) . '//'
|
||||
. Line::_(__Line__, __Class__) . ' add the autoloaders for the composer classes');
|
||||
}
|
||||
|
||||
return implode(PHP_EOL, $composer_routine);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 4th September, 2022
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/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\Power;
|
||||
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use VDM\Joomla\Utilities\GuidHelper;
|
||||
|
||||
|
||||
/**
|
||||
* Compiler Power Extractor
|
||||
* @since 3.2.0
|
||||
*/
|
||||
final class Extractor
|
||||
{
|
||||
/**
|
||||
* The pattern to get the powers
|
||||
*
|
||||
* @var string
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected string $pattern = '/Super_'.'_'.'_[a-zA-Z0-9_]+_'.'_'.'_Power/';
|
||||
|
||||
/**
|
||||
* Powers GUID's
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected array $powers = [];
|
||||
|
||||
/**
|
||||
* Database object to query local DB
|
||||
*
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = Factory::getDbo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Super Powers from the code string
|
||||
*
|
||||
* @param string $code The code
|
||||
*
|
||||
* @return array|null
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function get_(): ?array
|
||||
{
|
||||
return $this->powers !== [] ? $this->powers : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Super Powers from the code string
|
||||
*
|
||||
* @param string $code The code
|
||||
*
|
||||
* @return array|null
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function get(string $code): ?array
|
||||
{
|
||||
$matches = [];
|
||||
preg_match_all($this->pattern, $code, $matches);
|
||||
|
||||
$found = $matches[0];
|
||||
|
||||
if (!empty($found))
|
||||
{
|
||||
return $this->map($found);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Super Powers from the code string
|
||||
*
|
||||
* @param string $code The code
|
||||
*
|
||||
* @return array|null
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function reverse(string $code): ?array
|
||||
{
|
||||
$matches = [];
|
||||
preg_match_all($this->pattern, $code, $matches);
|
||||
|
||||
$found = $matches[0];
|
||||
|
||||
if (!empty($found) && ($guids = $this->filter($found)) !== null)
|
||||
{
|
||||
return $this->namespaces($guids);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Super Powers from the code string and load it
|
||||
*
|
||||
* @param string $code The code
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function search(string $code)
|
||||
{
|
||||
$matches = [];
|
||||
preg_match_all($this->pattern, $code, $matches);
|
||||
|
||||
$found = $matches[0];
|
||||
|
||||
if (!empty($found))
|
||||
{
|
||||
$this->load($found);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the Super Powers found
|
||||
*
|
||||
* @param array $found The found Super Powers
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function load(array $found)
|
||||
{
|
||||
foreach ($found as $super_power)
|
||||
{
|
||||
$guid = str_replace(['Super_'.'_'.'_', '_'.'_'.'_Power'], '', $super_power);
|
||||
$guid = str_replace('_', '-', $guid);
|
||||
|
||||
if (GuidHelper::valid($guid))
|
||||
{
|
||||
$this->powers[$guid] = 1; // 1 force the power to be added
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the Super Powers to GUIDs
|
||||
*
|
||||
* @param array $found The found Super Powers
|
||||
*
|
||||
* @return array
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function map(array $found): ?array
|
||||
{
|
||||
$guids = [];
|
||||
|
||||
foreach ($found as $super_power)
|
||||
{
|
||||
$guid = str_replace(['Super_'.'_'.'_', '_'.'_'.'_Power'], '', $super_power);
|
||||
$guid = str_replace('_', '-', $guid);
|
||||
|
||||
if (GuidHelper::valid($guid))
|
||||
{
|
||||
$guids[$super_power] = $guid;
|
||||
}
|
||||
}
|
||||
|
||||
return $guids !== [] ? $guids : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the Super Powers to GUIDs
|
||||
*
|
||||
* @param array $found The found Super Powers
|
||||
*
|
||||
* @return array
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function filter(array $found): ?array
|
||||
{
|
||||
$guids = [];
|
||||
|
||||
foreach ($found as $super_power)
|
||||
{
|
||||
$guid = str_replace(['Super_'.'_'.'_', '_'.'_'.'_Power'], '', $super_power);
|
||||
$guid = str_replace('_', '-', $guid);
|
||||
|
||||
if (GuidHelper::valid($guid))
|
||||
{
|
||||
$guids[$guid] = $guid;
|
||||
}
|
||||
}
|
||||
|
||||
return $guids !== [] ? array_values($guids) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete namespace strings of the guids passed as an array.
|
||||
*
|
||||
* @param array $guids The guids to filter the results
|
||||
*
|
||||
* @return array|null The result namespaces with their guids
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected function namespaces(array $guids): ?array
|
||||
{
|
||||
$query = $this->db->getQuery(true);
|
||||
$query->select(
|
||||
'DISTINCT REPLACE('
|
||||
. $this->db->quoteName('namespace')
|
||||
. ', ".", "\\\") AS full_namespace, '
|
||||
. $this->db->quoteName('guid')
|
||||
)
|
||||
->from($this->db->quoteName('#__componentbuilder_power'))
|
||||
->where($this->db->quoteName('guid') . ' IN (' . implode(',', array_map([$this->db, 'quote'], $guids)) . ')');
|
||||
$this->db->setQuery($query);
|
||||
$this->db->execute();
|
||||
|
||||
if ($this->db->getNumRows())
|
||||
{
|
||||
return $this->db->loadAssocList('guid', 'full_namespace');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,487 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 4th September, 2022
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/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\Power;
|
||||
|
||||
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Config;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Builder\ContentOne as Content;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Builder\ContentMulti as Contents;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power\Parser;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power\Repo\Readme as RepoReadme;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power\Repos\Readme as ReposReadme;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Placeholder;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Interfaces\EventInterface as Event;
|
||||
use VDM\Joomla\Utilities\StringHelper;
|
||||
use VDM\Joomla\Utilities\ArrayHelper;
|
||||
use VDM\Joomla\Utilities\ObjectHelper;
|
||||
|
||||
|
||||
/**
|
||||
* Compiler Power Infusion
|
||||
* @since 3.2.0
|
||||
*/
|
||||
class Infusion
|
||||
{
|
||||
/**
|
||||
* The Config Class.
|
||||
*
|
||||
* @var Config
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Config $config;
|
||||
|
||||
/**
|
||||
* The Power Class.
|
||||
*
|
||||
* @var Power
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Power $power;
|
||||
|
||||
/**
|
||||
* The ContentOne Class.
|
||||
*
|
||||
* @var Content
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Content $content;
|
||||
|
||||
/**
|
||||
* The ContentMulti Class.
|
||||
*
|
||||
* @var Contents
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Contents $contents;
|
||||
|
||||
/**
|
||||
* The Parser Class.
|
||||
*
|
||||
* @var Parser
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Parser $parser;
|
||||
|
||||
/**
|
||||
* The Readme Class.
|
||||
*
|
||||
* @var RepoReadme
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected RepoReadme $reporeadme;
|
||||
|
||||
/**
|
||||
* The Readme Class.
|
||||
*
|
||||
* @var ReposReadme
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected ReposReadme $reposreadme;
|
||||
|
||||
/**
|
||||
* The Placeholder Class.
|
||||
*
|
||||
* @var Placeholder
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Placeholder $placeholder;
|
||||
|
||||
/**
|
||||
* The EventInterface Class.
|
||||
*
|
||||
* @var Event
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Event $event;
|
||||
|
||||
/**
|
||||
* Power linker values
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected array $linker = [
|
||||
'add_head' => 'add_head',
|
||||
'unchanged_description' => 'description',
|
||||
'extends' => 'extends',
|
||||
'unchanged_extends_custom' => 'extends_custom',
|
||||
'guid' => 'guid',
|
||||
'unchanged_head' => 'head',
|
||||
'use_selection' => 'use_selection',
|
||||
'implements' => 'implements',
|
||||
'unchanged_implements_custom' => 'implements_custom',
|
||||
'load_selection' => 'load_selection',
|
||||
'name' => 'name',
|
||||
'power_version' => 'power_version',
|
||||
'system_name' => 'system_name',
|
||||
'type' => 'type',
|
||||
'unchanged_namespace' => 'namespace',
|
||||
'unchanged_composer' => 'composer',
|
||||
'add_licensing_template' => 'add_licensing_template',
|
||||
'unchanged_licensing_template' => 'licensing_template'
|
||||
];
|
||||
|
||||
/**
|
||||
* Power Infusion Tracker
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected array $done = [];
|
||||
|
||||
/**
|
||||
* Power Content Infusion Tracker
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected array $content_done = [];
|
||||
|
||||
/**
|
||||
* Path Infusion Tracker
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected array $path_done = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Config $config The Config Class.
|
||||
* @param Power $power The Power Class.
|
||||
* @param Content $content The ContentOne Class.
|
||||
* @param Contents $contents The ContentMulti Class.
|
||||
* @param Parser $parser The Parser Class.
|
||||
* @param RepoReadme $reporeadme The Readme Class.
|
||||
* @param ReposReadme $reposreadme The Readme Class.
|
||||
* @param Placeholder $placeholder The Placeholder Class.
|
||||
* @param Event $event The EventInterface Class.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __construct(Config $config, Power $power, Content $content,
|
||||
Contents $contents, Parser $parser, RepoReadme $reporeadme,
|
||||
ReposReadme $reposreadme, Placeholder $placeholder,
|
||||
Event $event)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->power = $power;
|
||||
$this->content = $content;
|
||||
$this->contents = $contents;
|
||||
$this->parser = $parser;
|
||||
$this->reporeadme = $reporeadme;
|
||||
$this->reposreadme = $reposreadme;
|
||||
$this->placeholder = $placeholder;
|
||||
$this->event = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Infuse the powers data with the content
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function set()
|
||||
{
|
||||
// parse all powers main code
|
||||
$this->parsePowers();
|
||||
|
||||
// set the powers
|
||||
$this->setSuperPowers();
|
||||
|
||||
// set the powers
|
||||
$this->setPowers();
|
||||
}
|
||||
|
||||
/**
|
||||
* We parse the powers to get the class map of all methods
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function parsePowers()
|
||||
{
|
||||
// we only do this if super powers are active
|
||||
if ($this->config->add_super_powers && ArrayHelper::check($this->power->superpowers))
|
||||
{
|
||||
foreach ($this->power->active as $guid => &$power)
|
||||
{
|
||||
if (isset($this->done[$guid]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ObjectHelper::check($power) && isset($power->main_class_code) &&
|
||||
StringHelper::check($power->main_class_code))
|
||||
{
|
||||
// only parse those approved
|
||||
if ($power->approved == 1)
|
||||
{
|
||||
$power->main_class_code = $this->placeholder->update($power->main_class_code, $this->content->allActive());
|
||||
$power->parsed_class_code = $this->parser->code($power->main_class_code);
|
||||
}
|
||||
}
|
||||
|
||||
// do each power just once
|
||||
$this->done[$guid] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Super Powers details
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function setSuperPowers()
|
||||
{
|
||||
// infuse super powers details if set
|
||||
if ($this->config->add_super_powers && ArrayHelper::check($this->power->superpowers))
|
||||
{
|
||||
foreach ($this->power->superpowers as $path => $powers)
|
||||
{
|
||||
if (isset($this->path_done[$path]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = StringHelper::safe($path);
|
||||
|
||||
// Trigger Event: jcb_ce_onBeforeInfuseSuperPowerDetails
|
||||
$this->event->trigger(
|
||||
'jcb_ce_onBeforeInfuseSuperPowerDetails', [&$path, &$key, &$powers]
|
||||
);
|
||||
|
||||
// we add and all missing powers
|
||||
if (isset($this->power->old_superpowers[$path]))
|
||||
{
|
||||
$this->mergePowers($powers, $this->power->old_superpowers[$path]);
|
||||
}
|
||||
|
||||
// POWERREADME
|
||||
$this->contents->set("{$key}|POWERREADME", $this->reposreadme->get($powers));
|
||||
|
||||
// sort all powers
|
||||
$this->sortPowers($powers);
|
||||
|
||||
// POWERINDEX
|
||||
$this->contents->set("{$key}|POWERINDEX", $this->index($powers));
|
||||
|
||||
// Trigger Event: jcb_ce_onAfterInfuseSuperPowerDetails
|
||||
$this->event->trigger(
|
||||
'jcb_ce_onAfterInfuseSuperPowerDetails', [&$path, &$key, &$powers]
|
||||
);
|
||||
|
||||
// do each path just once
|
||||
$this->path_done[$path] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the old missing powers found in local repository back into the index
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function mergePowers(array &$powers, array &$old)
|
||||
{
|
||||
foreach ($old as $guid => $values)
|
||||
{
|
||||
if (!isset($powers[$guid]))
|
||||
{
|
||||
$powers[$guid] = $values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort Powers
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function sortPowers(array &$powers)
|
||||
{
|
||||
ksort($powers, SORT_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Powers code
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function setPowers()
|
||||
{
|
||||
// infuse powers data if set
|
||||
if (ArrayHelper::check($this->power->active))
|
||||
{
|
||||
foreach ($this->power->active as $guid => $power)
|
||||
{
|
||||
if (isset($this->content_done[$guid]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ObjectHelper::check($power))
|
||||
{
|
||||
// Trigger Event: jcb_ce_onBeforeInfusePowerData
|
||||
$this->event->trigger(
|
||||
'jcb_ce_onBeforeInfusePowerData', [&$power]
|
||||
);
|
||||
|
||||
// POWERCODE
|
||||
$this->contents->set("{$power->key}|POWERCODE", $this->code($power));
|
||||
|
||||
// CODEPOWER
|
||||
$this->contents->set("{$power->key}|CODEPOWER", $this->raw($power));
|
||||
|
||||
// POWERLINKER
|
||||
$this->contents->set("{$power->key}|POWERLINKER", $this->linker($power));
|
||||
|
||||
// POWERLINKER
|
||||
$this->contents->set("{$power->key}|POWERREADME", $this->reporeadme->get($power));
|
||||
|
||||
// Trigger Event: jcb_ce_onAfterInfusePowerData
|
||||
$this->event->trigger(
|
||||
'jcb_ce_onAfterInfusePowerData', [&$power]
|
||||
);
|
||||
}
|
||||
|
||||
// do each power just once
|
||||
$this->content_done[$guid] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Super Power Index
|
||||
*
|
||||
* @param array $powers All powers of this super power.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function index(array &$powers): string
|
||||
{
|
||||
return json_encode($powers, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Power code
|
||||
*
|
||||
* @param object $power A power object.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function code(object &$power): string
|
||||
{
|
||||
$code = [];
|
||||
|
||||
// set the name space
|
||||
$code[] = 'namespace ' . $power->_namespace . ';' . PHP_EOL;
|
||||
|
||||
// check if we have header data
|
||||
if (StringHelper::check($power->head))
|
||||
{
|
||||
$code[] = PHP_EOL . $power->head;
|
||||
}
|
||||
|
||||
// add description if set
|
||||
if (StringHelper::check($power->description))
|
||||
{
|
||||
// check if this is escaped
|
||||
if (strpos((string) $power->description, '/*') === false)
|
||||
{
|
||||
// make this description escaped
|
||||
$power->description = '/**' . PHP_EOL . ' * ' . implode(PHP_EOL . ' * ', explode(PHP_EOL, (string) $power->description)) . PHP_EOL . ' */';
|
||||
}
|
||||
$code[] = PHP_EOL . $power->description;
|
||||
}
|
||||
|
||||
// build power declaration
|
||||
$declaration = $power->type . ' ' . $power->class_name;
|
||||
|
||||
// check if we have extends
|
||||
if (StringHelper::check($power->extends_name))
|
||||
{
|
||||
$declaration .= ' extends ' . $power->extends_name;
|
||||
}
|
||||
|
||||
// check if we have implements
|
||||
if (ArrayHelper::check($power->implement_names))
|
||||
{
|
||||
$declaration .= ' implements ' . implode(', ', $power->implement_names);
|
||||
}
|
||||
|
||||
$code[] = $declaration;
|
||||
$code[] = '{';
|
||||
|
||||
// add the main code if set
|
||||
if (StringHelper::check($power->main_class_code))
|
||||
{
|
||||
$code[] = $power->main_class_code;
|
||||
}
|
||||
|
||||
$code[] = '}' . PHP_EOL;
|
||||
|
||||
return $this->placeholder->update(implode(PHP_EOL, $code), $this->content->allActive());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Raw (unchanged) Power code
|
||||
*
|
||||
* @param object $power A power object.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function raw(object &$power): string
|
||||
{
|
||||
// add the raw main code if set
|
||||
if (isset($power->unchanged_main_class_code) && StringHelper::check($power->unchanged_main_class_code))
|
||||
{
|
||||
return $power->unchanged_main_class_code;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Power Linker
|
||||
*
|
||||
* @param object $power A power object.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function linker(object &$power): string
|
||||
{
|
||||
$linker = [];
|
||||
|
||||
// set the linking values
|
||||
foreach ($power as $key => $value)
|
||||
{
|
||||
if (isset($this->linker[$key]))
|
||||
{
|
||||
$linker[$this->linker[$key]] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return json_encode($linker, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,616 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 4th September, 2022
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/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\Power;
|
||||
|
||||
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power\Extractor;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power\Parser;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Placeholder;
|
||||
|
||||
|
||||
/**
|
||||
* Compiler Power Injector
|
||||
* @since 3.2.0
|
||||
*/
|
||||
final class Injector
|
||||
{
|
||||
/**
|
||||
* Power Objects
|
||||
*
|
||||
* @var Power
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected Power $power;
|
||||
|
||||
/**
|
||||
* Compiler Powers Extractor
|
||||
*
|
||||
* @var Extractor
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected Extractor $extractor;
|
||||
|
||||
/**
|
||||
* Compiler Powers Parser
|
||||
*
|
||||
* @var Parser
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected Parser $parser;
|
||||
|
||||
/**
|
||||
* Compiler Placeholder
|
||||
*
|
||||
* @var Placeholder
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Placeholder $placeholder;
|
||||
|
||||
/**
|
||||
* Super Power Update Map
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected array $map = [];
|
||||
|
||||
/**
|
||||
* Insert Use Statements
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected array $useStatements = [];
|
||||
|
||||
/**
|
||||
* Insert Trait Statements
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected array $traits = [];
|
||||
|
||||
/**
|
||||
* Other Statements
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected array $other = [];
|
||||
|
||||
/**
|
||||
* Duplicate Statements
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected array $duplicate = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Power|null $power The power object.
|
||||
* @param Extractor|null $extractor The powers extractor object.
|
||||
* @param Parser|null $parser The powers parser object.
|
||||
* @param Placeholder|null $placeholder The compiler placeholder object.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __construct(?Power $power = null, ?Extractor $extractor = null,
|
||||
?Parser $parser = null, ?Placeholder $placeholder = null)
|
||||
{
|
||||
$this->power = $power ?: Compiler::_('Power');
|
||||
$this->extractor = $extractor ?: Compiler::_('Power.Extractor');
|
||||
$this->parser = $parser ?: Compiler::_('Power.Parser');
|
||||
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the powers found in the code
|
||||
*
|
||||
* @param string $code The class code
|
||||
*
|
||||
* @return string The updated code
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function power(string $code): string
|
||||
{
|
||||
if (($guids = $this->extractor->get($code)) !== null)
|
||||
{
|
||||
return $this->update($guids, $code);
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the code
|
||||
*
|
||||
* @param array $guids The Power guids found
|
||||
* @param string $code The class code
|
||||
*
|
||||
* @return string The updated code
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function update(array $guids, string $code): string
|
||||
{
|
||||
$use_statements = $this->parser->getUseStatements($code);
|
||||
$traits = $this->parser->getTraits(
|
||||
$this->parser->getClassCode($code) ?? ''
|
||||
);
|
||||
|
||||
// reset with each file
|
||||
$this->map = [];
|
||||
$this->useStatements = [];
|
||||
$this->traits = [];
|
||||
$this->other = [];
|
||||
$this->duplicate = [];
|
||||
|
||||
foreach ($guids as $key => $guid)
|
||||
{
|
||||
if (($power = $this->power->get($guid)) !== null)
|
||||
{
|
||||
if (($name = $this->inspect($power, $use_statements, $traits)) !== null)
|
||||
{
|
||||
$this->map[$key] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update
|
||||
if ($this->map !== [])
|
||||
{
|
||||
if ($this->useStatements !== [])
|
||||
{
|
||||
$code = $this->addUseStatements($code, $use_statements);
|
||||
}
|
||||
|
||||
return $this->placeholder->update($code, $this->map);
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect the super power to determine the necessary class name based on use statements and traits.
|
||||
* It checks if the given power (class, trait, etc.) already has a corresponding use statement
|
||||
* and handles the naming accordingly to avoid conflicts.
|
||||
*
|
||||
* @param object $power The power object containing type, namespace, and class name.
|
||||
* @param array|null $useStatements Array of existing use statements in the code.
|
||||
* @param array|null $traits Array of existing traits used in the code.
|
||||
*
|
||||
* @return string|null The determined class name, or null if the type is not valid.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function inspect(object $power, ?array $useStatements, ?array $traits): ?string
|
||||
{
|
||||
$namespaceStatement = $this->buildNamespaceStatment($power);
|
||||
|
||||
$use_extracted = $this->extractUseStatements($namespaceStatement, $power->class_name, $useStatements);
|
||||
|
||||
$name = $use_extracted['found'] ?? $power->class_name;
|
||||
|
||||
$name = $this->getUniqueName($name, $power);
|
||||
|
||||
$this->handleTraitLogic($name, $power, $traits);
|
||||
|
||||
if (!$use_extracted['hasStatement'])
|
||||
{
|
||||
$this->addUseStatement($name, $power->class_name, $namespaceStatement);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the namespace statement from the power object's namespace and class name.
|
||||
*
|
||||
* @param object $power The power object.
|
||||
*
|
||||
* @return string The constructed use statement.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function buildNamespaceStatment(object $power): string
|
||||
{
|
||||
return $power->_namespace . '\\' . $power->class_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and processes use statements to find if the current class name is already used.
|
||||
* It identifies any potential naming conflicts.
|
||||
*
|
||||
* @param string $useStatement The search statement of the current class.
|
||||
* @param string $className The class name of the power object.
|
||||
* @param array|null $useStatements The existing use statements.
|
||||
*
|
||||
* @return array An array with keys 'found' and 'hasStatement'.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function extractUseStatements(string $useStatement, string $className, ?array $useStatements): array
|
||||
{
|
||||
$results = ['found' => null, 'hasStatement' => false];
|
||||
|
||||
if ($useStatements !== null)
|
||||
{
|
||||
foreach ($useStatements as $use_statement)
|
||||
{
|
||||
$class_name = $this->extractClassNameOrAlias($use_statement);
|
||||
|
||||
if ($this->isUseStatementEqual($use_statement, $useStatement))
|
||||
{
|
||||
if ($results['found'] === null)
|
||||
{
|
||||
$results['found'] = $class_name;
|
||||
$results['hasStatement'] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO we need to backport fix these
|
||||
$this->duplicate[$use_statement] = $class_name;
|
||||
}
|
||||
}
|
||||
elseif ($className === $class_name)
|
||||
{
|
||||
$this->other[$className] = $class_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the namespace statement is already declared in the current use statements.
|
||||
*
|
||||
* This method uses a regular expression to check for an exact match of the full statement,
|
||||
* taking into account the possibility of an alias being used.
|
||||
*
|
||||
* @param string $useStatement The existing use statement to check against.
|
||||
* @param string $namespaceStatement The search statement to search for (without the trailing semicolon, or use prefix).
|
||||
*
|
||||
* @return bool True if the full statement is found, false otherwise.
|
||||
*/
|
||||
protected function isUseStatementEqual(string $useStatement, string $namespaceStatement): bool
|
||||
{
|
||||
// Create a regular expression pattern to match the full statement
|
||||
// The pattern checks for the start of the statement, optional whitespace,
|
||||
// and an optional alias after the full statement.
|
||||
$pattern = '/^use\s+' . preg_quote($namespaceStatement, '/') . '(?:\s+as\s+\w+)?;$/';
|
||||
|
||||
// Perform the regex match to check if the use statement is equal to the search statment
|
||||
return (bool) preg_match($pattern, $useStatement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the class name or alias from a use statement.
|
||||
*
|
||||
* This method parses a PHP 'use' statement and extracts either the class name or its alias.
|
||||
* If the statement doesn't match the expected format, or if no class name or alias is found,
|
||||
* the method returns null.
|
||||
*
|
||||
* Example:
|
||||
* - 'use Namespace\ClassName;' -> returns 'ClassName'
|
||||
* - 'use Namespace\ClassName as Alias;' -> returns 'Alias'
|
||||
*
|
||||
* @param string $useStatement The use statement from which to extract the class name or alias.
|
||||
*
|
||||
* @return string|null The class name or alias if found, null otherwise.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function extractClassNameOrAlias(string $useStatement): ?string
|
||||
{
|
||||
// If the input doesn't start with 'use ', assume it's just the namespace without a use statement
|
||||
if (strpos($useStatement, 'use ') !== 0)
|
||||
{
|
||||
return $this->extractLastNameFromNamespace($useStatement);
|
||||
}
|
||||
|
||||
// Regular expression to extract the class name and alias from the use statement
|
||||
$pattern = '/use\s+(?P<namespace>[\w\\\\]+?)(?:\s+as\s+(?P<alias>\w+))?;/';
|
||||
|
||||
if (preg_match($pattern, $useStatement, $matches))
|
||||
{
|
||||
// Return the alias if it exists; otherwise, return the last part of the namespace (class name)
|
||||
return $matches['alias'] ?? $this->extractLastNameFromNamespace($matches['namespace']);
|
||||
}
|
||||
|
||||
// Return null if no match is found
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the name for the use statement is unique, avoiding conflicts with other classes.
|
||||
*
|
||||
* @param string $name The current name
|
||||
* @param object $power The power object containing type, namespace, and class name.
|
||||
*
|
||||
* @return string The unique name
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function getUniqueName(string $name, object $power): string
|
||||
{
|
||||
// set search namespace
|
||||
$namespace = ($name !== $power->class_name) ? $this->buildNamespaceStatment($power) : $power->_namespace;
|
||||
|
||||
// check if we need to update the name
|
||||
if (isset($this->other[$name]))
|
||||
{
|
||||
// if the name is already used
|
||||
while (isset($this->other[$name]))
|
||||
{
|
||||
if (($tmp = $this->extractClassNameOrAlias($namespace)) !== null)
|
||||
{
|
||||
$name = ucfirst($tmp) . $name;
|
||||
$namespace = $this->removeLastNameFromNamespace($namespace);
|
||||
}
|
||||
else
|
||||
{
|
||||
$name = 'Unique' . $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// also loop new found use statements
|
||||
if (isset($this->useStatements[$name]))
|
||||
{
|
||||
// if the name is already used
|
||||
while (isset($this->useStatements[$name]))
|
||||
{
|
||||
if (($tmp = $this->extractClassNameOrAlias($namespace)) !== null)
|
||||
{
|
||||
$name = ucfirst($tmp) . $name;
|
||||
$namespace = $this->removeLastNameFromNamespace($namespace);
|
||||
}
|
||||
else
|
||||
{
|
||||
$name = 'Unique' . $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.0
|
||||
*/
|
||||
protected 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.0
|
||||
*/
|
||||
protected 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a trait statement should be added.
|
||||
*
|
||||
* @param object $power The power object.
|
||||
*
|
||||
* @return bool True if a trait statement should be added, false otherwise.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function shouldAddTraitStatement(object $power): bool
|
||||
{
|
||||
return $power->type === 'trait';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles specific logic for traits, such as checking if the trait is already used.
|
||||
*
|
||||
* @param string $name The current name.
|
||||
* @param object $power The power object containing type, namespace, and class name.
|
||||
* @param array|null $traits The traits used in the code.
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function handleTraitLogic(string $name, object $power, ?array $traits): void
|
||||
{
|
||||
if ($this->shouldAddTraitStatement($power) && $traits !== null)
|
||||
{
|
||||
foreach ($traits as $trait)
|
||||
{
|
||||
if ($trait === $name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the trait
|
||||
$this->traits[$name] = 'use ' . $name . ';';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a use statement to the class if it's not already present.
|
||||
*
|
||||
* @param string $name The name to use.
|
||||
* @param string $className The class name of the power object.
|
||||
* @param string $namespaceStatement The search statement to search for (without the trailing semicolon, or use prefix).
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function addUseStatement(string &$name, string $className, string $namespaceStatement): void
|
||||
{
|
||||
if ($name !== $className)
|
||||
{
|
||||
$statement = 'use ' . $namespaceStatement . ' as ' . $name . ';';
|
||||
}
|
||||
else
|
||||
{
|
||||
$statement = 'use ' . $namespaceStatement . ';';
|
||||
}
|
||||
|
||||
$this->useStatements[$name] = $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a line before the class declaration in the given class code.
|
||||
*
|
||||
* @param string $code The class code
|
||||
* @param array|null $useStatements The existing use statements
|
||||
*
|
||||
* @return string The modified file content
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function addUseStatements(string $code, ?array $useStatements): string
|
||||
{
|
||||
if ($useStatements !== null)
|
||||
{
|
||||
// we add the use statements using existing use statements
|
||||
$key = end($useStatements);
|
||||
|
||||
array_unshift($this->useStatements, $key);
|
||||
|
||||
return $this->placeholder->update($code, [$key => implode(PHP_EOL, array_values($this->useStatements))]);
|
||||
}
|
||||
|
||||
return $this->addLines($code, implode(PHP_EOL, array_values($this->useStatements)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a line before the class declaration in the given class code.
|
||||
*
|
||||
* @param string $code The class code
|
||||
* @param string $lines The new lines to insert
|
||||
*
|
||||
* @return string The modified file content
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function addLines(string $code, string $lines): string
|
||||
{
|
||||
// Pattern to match class, final class, abstract class, interface, and trait
|
||||
$pattern = '/(?:class|final class|abstract class|interface|trait)\s+[a-zA-Z0-9_]+\s*(?:extends\s+[a-zA-Z0-9_]+\s*)?(?:implements\s+[a-zA-Z0-9_]+(?:\s*,\s*[a-zA-Z0-9_]+)*)?\s*\{/s';
|
||||
|
||||
// Find the position of the class declaration
|
||||
preg_match($pattern, $code, $matches, PREG_OFFSET_CAPTURE);
|
||||
$class_declaration_pos = $matches[0][1] ?? null;
|
||||
|
||||
if (null !== $class_declaration_pos)
|
||||
{
|
||||
// Find the position of the last newline character before the class declaration
|
||||
$last_newline_pos = strrpos($code, PHP_EOL, -(strlen($code) - $class_declaration_pos));
|
||||
|
||||
// Find the position of the comment block right before the class declaration
|
||||
$comment_pattern = '/\s*\*\/\s*$/m';
|
||||
$insert_pos = null;
|
||||
if (preg_match($comment_pattern, $code, $comment_matches, PREG_OFFSET_CAPTURE, $last_newline_pos))
|
||||
{
|
||||
$insert_pos = (int) $comment_matches[0][1] + strlen($comment_matches[0][0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find the last empty line before the class declaration
|
||||
$empty_line_pattern = '/(^|\r\n|\r|\n)[\s]*($|\r\n|\r|\n)/';
|
||||
if (preg_match($empty_line_pattern, $code, $empty_line_matches, PREG_OFFSET_CAPTURE, $last_newline_pos))
|
||||
{
|
||||
$insert_pos = (int) $empty_line_matches[0][1] + strlen($empty_line_matches[0][0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the new line at the found position
|
||||
if (null !== $insert_pos)
|
||||
{
|
||||
return substr_replace($code, $lines . PHP_EOL, $insert_pos, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// last try targeting the defined line
|
||||
return $this->addLinesAfterDefinedLine($code, $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new line after the defined('_JEXEC') line.
|
||||
*
|
||||
* @param string $code The class code
|
||||
* @param string $lines The new lines to insert
|
||||
*
|
||||
* @return string The modified file content
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function addLinesAfterDefinedLine(string $code, string $lines): string
|
||||
{
|
||||
// Patterns to match the defined('_JEXEC') and defined('JPATH_BASE') lines
|
||||
$patterns = [
|
||||
"/defined\('_JEXEC'\)(.*?)\s*;/",
|
||||
"/\\defined\('_JEXEC'\)(.*?)\s*;/",
|
||||
"/defined\('JPATH_BASE'\)(.*?)\s*;/",
|
||||
"/\\defined\('JPATH_BASE'\)(.*?)\s*;/",
|
||||
];
|
||||
|
||||
$insert_pos = null;
|
||||
|
||||
// Iterate through the patterns and try to find a match
|
||||
foreach ($patterns as $pattern)
|
||||
{
|
||||
preg_match($pattern, $code, $matches, PREG_OFFSET_CAPTURE);
|
||||
$defined_line_pos = $matches[0][1] ?? null;
|
||||
|
||||
if ($defined_line_pos !== null)
|
||||
{
|
||||
// Find the position of the newline character after the defined() line
|
||||
$next_lines_pos = strpos($code, PHP_EOL, (int) $defined_line_pos + strlen($matches[0][0]));
|
||||
|
||||
// Insert the new line at the found position
|
||||
if ($next_lines_pos !== false)
|
||||
{
|
||||
$insert_pos = $next_lines_pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the new line at the found position
|
||||
if ($insert_pos !== null)
|
||||
{
|
||||
$code = substr_replace($code, PHP_EOL . $lines, $insert_pos, 0);
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,644 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 4th September, 2022
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/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\Power;
|
||||
|
||||
|
||||
/**
|
||||
* Compiler Power Parser
|
||||
* Very basic php class methods parser, does not catch all edge-cases!
|
||||
* Use this only on code that are following standard good practices
|
||||
* Suggested improvements are welcome
|
||||
* @since 3.2.0
|
||||
*/
|
||||
final class Parser
|
||||
{
|
||||
/**
|
||||
* Get properties and method declarations and other details from the given code.
|
||||
*
|
||||
* @param string $code The code containing class properties & methods
|
||||
*
|
||||
* @return array An array of properties & method declarations of the given code
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function code(string $code): array
|
||||
{
|
||||
return [
|
||||
'properties' => $this->properties($code),
|
||||
'methods' => $this->methods($code)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class body
|
||||
*
|
||||
* @param string $code The class as a string
|
||||
*
|
||||
* @return string|null The class body, or null if not found
|
||||
* @since 3.2.0
|
||||
**/
|
||||
public function getClassCode(string $code): ?string
|
||||
{
|
||||
// Match class, final class, abstract class, interface, and trait
|
||||
$pattern = '/(?:class|final class|abstract class|interface|trait)\s+[a-zA-Z0-9_]+\s*(?:extends\s+[a-zA-Z0-9_]+\s*)?(?:implements\s+[a-zA-Z0-9_]+(?:\s*,\s*[a-zA-Z0-9_]+)*)?\s*\{/s';
|
||||
|
||||
// Split the input code based on the class declaration pattern
|
||||
$parts = preg_split($pattern, $code, 2, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$body = $parts[1] ?? '';
|
||||
|
||||
if ($body !== '')
|
||||
{
|
||||
// Remove leading and trailing white space
|
||||
$body = trim($body);
|
||||
|
||||
// Remove the first opening curly brace if it exists
|
||||
if (mb_substr($body, 0, 1) === '{')
|
||||
{
|
||||
$body = mb_substr($body, 1);
|
||||
}
|
||||
|
||||
// Remove the last closing curly brace if it exists
|
||||
if (mb_substr($body, -1) === '}')
|
||||
{
|
||||
$body = mb_substr($body, 0, -1);
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
// No class body found, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class license
|
||||
*
|
||||
* @param string $code The class as a string
|
||||
*
|
||||
* @return string|null The class license, or null if not found
|
||||
* @since 3.2.0
|
||||
**/
|
||||
public function getClassLicense(string $code): ?string
|
||||
{
|
||||
// Check if the file starts with '<?php'
|
||||
if (substr($code, 0, 5) !== '<?php')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Trim the '<?php' part
|
||||
$code = ltrim(substr($code, 5));
|
||||
|
||||
// Check if the next part starts with '/*'
|
||||
if (substr($code, 0, 2) !== '/*')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the position of the closing comment '*/'
|
||||
$endCommentPos = strpos($code, '*/');
|
||||
|
||||
// If the closing comment '*/' is found, extract and return the license
|
||||
if ($endCommentPos !== false)
|
||||
{
|
||||
$license = substr($code, 2, $endCommentPos - 2);
|
||||
return trim($license);
|
||||
}
|
||||
|
||||
// No license found, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the first consecutive `use` statements from the given PHP class.
|
||||
*
|
||||
* @param string $code The PHP class as a string
|
||||
*
|
||||
* @return array|null An array of consecutive `use` statements
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function getUseStatements(string $code): ?array
|
||||
{
|
||||
// Match class, final class, abstract class, interface, and trait
|
||||
$pattern = '/(?:class|final class|abstract class|interface|trait)\s+[a-zA-Z0-9_]+\s*(?:extends\s+[a-zA-Z0-9_]+\s*)?(?:implements\s+[a-zA-Z0-9_]+(?:\s*,\s*[a-zA-Z0-9_]+)*)?\s*\{/s';
|
||||
|
||||
// Split the input code based on the class declaration pattern
|
||||
$parts = preg_split($pattern, $code, 2, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$header = $parts[0] ?? '';
|
||||
|
||||
$use_statements = [];
|
||||
$found_first_use = false;
|
||||
|
||||
if ($header !== '')
|
||||
{
|
||||
$lines = explode(PHP_EOL, $header);
|
||||
|
||||
foreach ($lines as $line)
|
||||
{
|
||||
if (strpos($line, 'use ') === 0)
|
||||
{
|
||||
$use_statements[] = trim($line);
|
||||
$found_first_use = true;
|
||||
}
|
||||
elseif ($found_first_use && trim($line) === '')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $found_first_use ? $use_statements : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts trait use statements from the given code.
|
||||
*
|
||||
* @param string $code The code containing class traits
|
||||
*
|
||||
* @return array|null An array of trait names
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function getTraits(string $code): ?array
|
||||
{
|
||||
// regex to target trait use statements
|
||||
$traitPattern = '/^\s*use\s+[\p{L}0-9\\\\_]+(?:\s*,\s*[\p{L}0-9\\\\_]+)*\s*;/mu';
|
||||
|
||||
preg_match_all($traitPattern, $code, $matches, PREG_SET_ORDER);
|
||||
|
||||
if ($matches != [])
|
||||
{
|
||||
$traitNames = [];
|
||||
|
||||
foreach ($matches as $n => $match)
|
||||
{
|
||||
$declaration = $match[0] ?? null;
|
||||
|
||||
if ($declaration !== null)
|
||||
{
|
||||
$names = preg_replace('/\s*use\s+/', '', $declaration);
|
||||
$names = preg_replace('/\s*;/', '', $names);
|
||||
$names = preg_split('/\s*,\s*/', $names);
|
||||
|
||||
$traitNames = array_merge($traitNames, $names);
|
||||
}
|
||||
}
|
||||
|
||||
return $traitNames;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts properties declarations and other details from the given code.
|
||||
*
|
||||
* @param string $code The code containing class properties
|
||||
*
|
||||
* @return array|null An array of properties declarations and details
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function properties(string $code): ?array
|
||||
{
|
||||
// regex to target all properties
|
||||
$access = '(?<access>var|public|protected|private)';
|
||||
$type = '(?<type>(?:\?|)[\p{L}0-9\\\\]*\s+)?';
|
||||
$static = '(?<static>static)?';
|
||||
$name = '\$(?<name>\p{L}[\p{L}0-9]*)';
|
||||
$default = '(?:\s*=\s*(?<default>\[[^\]]*\]|\d+|\'[^\']*?\'|"[^"]*?"|false|true|null))?';
|
||||
$property_pattern = "/\b{$access}\s*{$type}{$static}\s*{$name}{$default};/u";
|
||||
|
||||
preg_match_all($property_pattern, $code, $matches, PREG_SET_ORDER);
|
||||
|
||||
if ($matches != [])
|
||||
{
|
||||
$properties = [];
|
||||
foreach ($matches as $n => $match)
|
||||
{
|
||||
$declaration = $match[0] ?? null;
|
||||
|
||||
if (is_string($declaration))
|
||||
{
|
||||
$comment = $this->extractDocBlock($code, $declaration);
|
||||
$declaration = trim(preg_replace('/\s{2,}/', ' ',
|
||||
preg_replace('/[\r\n]+/', ' ', $declaration)));
|
||||
|
||||
$properties[] = [
|
||||
'name' => isset($match['name']) ? '$' . $match['name'] : 'error',
|
||||
'access' => $match['access'] ?? 'public',
|
||||
'type' => isset($match['type']) ? trim($match['type']) : null,
|
||||
'static' => (bool) $match['static'] ?? false,
|
||||
'default' => $match['default'] ?? null,
|
||||
'comment' => $comment,
|
||||
'declaration' => $declaration
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts method declarations and other details from the given code.
|
||||
*
|
||||
* @param string $code The code containing class methods
|
||||
*
|
||||
* @return array|null An array of method declarations and details
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function methods(string $code): ?array
|
||||
{
|
||||
// regex to target all methods/functions
|
||||
$final_modifier = '(?P<final_modifier>final)?\s*';
|
||||
$abstract_modifier = '(?P<abstract_modifier>abstract)?\s*';
|
||||
$access_modifier = '(?P<access_modifier>public|protected|private)?\s*';
|
||||
$static_modifier = '(?P<static_modifier>static)?\s*';
|
||||
$modifier = "{$final_modifier}{$abstract_modifier}{$access_modifier}{$static_modifier}";
|
||||
$name = '(?P<name>\w+)';
|
||||
$arguments = '(?P<arguments>\(.*?\))?';
|
||||
$return_type = '(?P<return_type>\s*:\s*(?:\?[\w\\\\]+|\\\\?[\w\\\\]+(?:\|\s*(?:\?[\w\\\\]+|\\\\?[\w\\\\]+))*)?)?';
|
||||
$method_pattern = "/(^\s*?\b{$modifier}function\s+{$name}{$arguments}{$return_type})/sm";
|
||||
|
||||
preg_match_all($method_pattern, $code, $matches, PREG_SET_ORDER);
|
||||
|
||||
if ($matches != [])
|
||||
{
|
||||
$methods = [];
|
||||
foreach ($matches as $n => $match)
|
||||
{
|
||||
$full_declaration = $match[0] ?? null;
|
||||
|
||||
if (is_string($full_declaration))
|
||||
{
|
||||
$comment = $this->extractDocBlock($code, $full_declaration);
|
||||
|
||||
$full_declaration = trim(preg_replace('/\s{2,}/', ' ',
|
||||
preg_replace('/[\r\n]+/', ' ', $full_declaration)));
|
||||
|
||||
// extract method's body
|
||||
$start_pos = strpos($code, $full_declaration) + strlen($full_declaration);
|
||||
$method_body = $this->extractMethodBody($code, $start_pos);
|
||||
|
||||
// now load what we found
|
||||
$methods[] = [
|
||||
'name' => $match['name'] ?? 'error',
|
||||
'access' => $match['access_modifier'] ?? 'public',
|
||||
'static' => (bool) $match['static_modifier'] ?? false,
|
||||
'final' => (bool) $match['final_modifier'] ?? false,
|
||||
'abstract' => (bool) $match['abstract_modifier'] ?? false,
|
||||
'return_type' => $this->extractReturnType($match['return_type'] ?? null, $comment),
|
||||
'since' => $this->extractSinceVersion($comment),
|
||||
'deprecated' => $this->extractDeprecatedVersion($comment),
|
||||
'arguments' => $this->extractFunctionArgumentDetails($comment, $match['arguments'] ?? null),
|
||||
'comment' => $comment,
|
||||
'declaration' => str_replace(["\r\n", "\r", "\n"], '', $full_declaration),
|
||||
'body' => $method_body
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the PHPDoc block for a given function declaration.
|
||||
*
|
||||
* @param string $code The source code containing the function
|
||||
* @param string $declaration The part of the function declaration
|
||||
*
|
||||
* @return string|null The PHPDoc block, or null if not found
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function extractDocBlock(string $code, string $declaration): ?string
|
||||
{
|
||||
// Split the code string with the function declaration
|
||||
$parts = explode($declaration, $code);
|
||||
if (count($parts) < 2)
|
||||
{
|
||||
// Function declaration not found in the code
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the part with the comment (if any)
|
||||
$comment = $parts[0];
|
||||
|
||||
// Split the last part using the comment block start marker
|
||||
$commentParts = preg_split('/(})?\s+(?=\s*\/\*)(\*)?/', $comment);
|
||||
|
||||
// Get the last comment block
|
||||
$lastCommentPart = end($commentParts);
|
||||
|
||||
// Search for the comment block in the last comment part
|
||||
if (preg_match('/(\/\*\*[\s\S]*?\*\/)\s*$/u', $lastCommentPart, $matches))
|
||||
{
|
||||
$comment = $matches[1] ?? null;
|
||||
// check if we actually have a comment
|
||||
if ($comment)
|
||||
{
|
||||
return $this->removeWhiteSpaceFromComment($comment);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts method body based on starting position of method declaration.
|
||||
*
|
||||
* @param string $code The class code
|
||||
* @param string $startPos The starting position of method declaration
|
||||
*
|
||||
* @return string|null Method body or null if not found
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function extractMethodBody(string $code, int $startPos): ?string
|
||||
{
|
||||
$braces_count = 0;
|
||||
$in_method = false;
|
||||
$method_body = "";
|
||||
|
||||
for ($i = $startPos; $i < strlen($code); $i++) {
|
||||
if ($code[$i] === '{')
|
||||
{
|
||||
$braces_count++;
|
||||
if (!$in_method)
|
||||
{
|
||||
$in_method = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($code[$i] === '}')
|
||||
{
|
||||
$braces_count--;
|
||||
}
|
||||
|
||||
if ($in_method)
|
||||
{
|
||||
$method_body .= $code[$i];
|
||||
}
|
||||
|
||||
if ($braces_count <= 0 && $in_method)
|
||||
{
|
||||
// remove the closing brace
|
||||
$method_body = substr($method_body, 0, -1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $in_method ? $method_body : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the function argument details.
|
||||
*
|
||||
* @param string|null $comment The function comment if found
|
||||
* @param string|null $arguments The arguments found on function declaration
|
||||
*
|
||||
* @return array|null The function argument details
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function extractFunctionArgumentDetails(?string $comment, ?string $arguments): ?array
|
||||
{
|
||||
$arg_types_from_declaration = $this->extractArgTypesArguments($arguments);
|
||||
$arg_types_from_comments = null;
|
||||
|
||||
if ($comment)
|
||||
{
|
||||
$arg_types_from_comments = $this->extractArgTypesFromComment($comment);
|
||||
}
|
||||
|
||||
// merge the types
|
||||
if ($arg_types_from_declaration)
|
||||
{
|
||||
return $this->mergeArgumentTypes($arg_types_from_declaration, $arg_types_from_comments);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the function return type.
|
||||
*
|
||||
* @param string|null $returnType The return type found in declaration
|
||||
* @param string|null $comment The function comment if found
|
||||
*
|
||||
* @return string|null The function return type
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function extractReturnType(?string $returnType, ?string $comment): ?string
|
||||
{
|
||||
if ($returnType === null && $comment)
|
||||
{
|
||||
return $this->extractReturnTypeFromComment($comment);
|
||||
}
|
||||
|
||||
return trim(trim($returnType, ':'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts argument types from a given comment.
|
||||
*
|
||||
* @param string $comment The comment containing the argument types
|
||||
*
|
||||
* @return array|null An array of argument types
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function extractArgTypesFromComment(string $comment): ?array
|
||||
{
|
||||
preg_match_all('/@param\s+((?:[^\s|]+(?:\|)?)+)?\s+\$([^\s]+)/', $comment, $matches, PREG_SET_ORDER);
|
||||
|
||||
if ($matches !== [])
|
||||
{
|
||||
$arg_types = [];
|
||||
|
||||
foreach ($matches as $match)
|
||||
{
|
||||
$arg = $match[2] ?? null;
|
||||
$type = $match[1] ?: null;
|
||||
if (is_string($arg))
|
||||
{
|
||||
$arg_types['$' .$arg] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
return $arg_types;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts argument types from a given declaration.
|
||||
*
|
||||
* @param string|null $arguments The arguments found on function declaration
|
||||
*
|
||||
* @return array|null An array of argument types
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function extractArgTypesArguments(?string $arguments): ?array
|
||||
{
|
||||
if ($arguments)
|
||||
{
|
||||
$args = preg_split('/,(?![^()\[\]]*(\)|\]))/', trim($arguments, '()'));
|
||||
if ($args !== [])
|
||||
{
|
||||
$argument_types = [];
|
||||
foreach ($args as $arg)
|
||||
{
|
||||
$eqPos = strpos($arg, '=');
|
||||
|
||||
if ($eqPos !== false)
|
||||
{
|
||||
$arg_parts = [
|
||||
substr($arg, 0, $eqPos),
|
||||
substr($arg, $eqPos + 1)
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$arg_parts = [$arg];
|
||||
}
|
||||
|
||||
if (preg_match('/(?:(\??(?:\w+|\\\\[\w\\\\]+)(?:\|\s*\??(?:\w+|\\\\[\w\\\\]+))*)\s+)?\$(\w+)/', $arg_parts[0], $arg_matches))
|
||||
{
|
||||
$type = $arg_matches[1] ?: null;
|
||||
$name = $arg_matches[2] ?: null;
|
||||
$default = isset($arg_parts[1]) ? preg_replace('/\s{2,}/', ' ',
|
||||
preg_replace('/[\r\n]+/', ' ', trim($arg_parts[1]))) : null;
|
||||
|
||||
if (is_string($name))
|
||||
{
|
||||
$argument_types['$' . $name] = [
|
||||
'type' => $type,
|
||||
'default' => $default,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $argument_types;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts return type from a given declaration.
|
||||
*
|
||||
* @param string $comment The comment containing the return type
|
||||
*
|
||||
* @return string|null The return type
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function extractReturnTypeFromComment(string $comment): ?string
|
||||
{
|
||||
if (preg_match('/@return\s+((?:[^\s|]+(?:\|)?)+)/', $comment, $matches))
|
||||
{
|
||||
return $matches[1] ?: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the version number from the @since tag in the given comment.
|
||||
*
|
||||
* @param string|null $comment The comment containing the @since tag and version number
|
||||
*
|
||||
* @return string|null The extracted version number or null if not found
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function extractSinceVersion(?string $comment): ?string
|
||||
{
|
||||
if (is_string($comment) && preg_match('/@since\s+(v?\d+(?:\.\d+)*(?:-(?:alpha|beta|rc)\d*)?)/', $comment, $matches))
|
||||
{
|
||||
return $matches[1] ?: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the version number from the deprecated tag in the given comment.
|
||||
*
|
||||
* @param string|null $comment The comment containing the deprecated tag and version number
|
||||
*
|
||||
* @return string|null The extracted version number or null if not found
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function extractDeprecatedVersion(?string $comment): ?string
|
||||
{
|
||||
if (is_string($comment) && preg_match('/@deprecated\s+(v?\d+(?:\.\d+)*(?:-(?:alpha|beta|rc)\d*)?)/', $comment, $matches))
|
||||
{
|
||||
return $matches[1] ?: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all white space from each line of the comment
|
||||
*
|
||||
* @param string $comment The function declaration containing the return type
|
||||
*
|
||||
* @return string The return comment
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function removeWhiteSpaceFromComment(string $comment): string
|
||||
{
|
||||
// Remove comment markers and leading/trailing whitespace
|
||||
$comment = preg_replace('/^\/\*\*[\r\n\s]*|[\r\n\s]*\*\/$/m', '', $comment);
|
||||
$comment = preg_replace('/^[\s]*\*[\s]?/m', '', $comment);
|
||||
|
||||
// Split the comment into lines
|
||||
$lines = preg_split('/\r\n|\r|\n/', $comment);
|
||||
|
||||
// Remove white spaces from each line
|
||||
$trimmedLines = array_map('trim', $lines);
|
||||
|
||||
// Join the lines back together
|
||||
return implode("\n", array_filter($trimmedLines));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the types from the comments and the arguments.
|
||||
*
|
||||
* @param array $argTypesFromDeclaration An array of argument types and default values from the declaration
|
||||
* @param array|null $argTypesFromComments An array of argument types from the comments
|
||||
*
|
||||
* @return array A merged array of argument information
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function mergeArgumentTypes(array $argTypesFromDeclaration, ?array $argTypesFromComments): array
|
||||
{
|
||||
$mergedArguments = [];
|
||||
|
||||
foreach ($argTypesFromDeclaration as $name => $declarationInfo)
|
||||
{
|
||||
$mergedArguments[$name] = [
|
||||
'name' => $name,
|
||||
'type' => $declarationInfo['type'] ?: $argTypesFromComments[$name] ?? null,
|
||||
'default' => $declarationInfo['default'] ?: null,
|
||||
];
|
||||
}
|
||||
|
||||
return $mergedArguments;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,459 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 4th September, 2022
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/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\Power;
|
||||
|
||||
|
||||
/**
|
||||
* Compiler Power Plantuml Builder
|
||||
* @since 3.2.0
|
||||
*/
|
||||
class Plantuml
|
||||
{
|
||||
/**
|
||||
* Get a namespace diagram of a group of class
|
||||
*
|
||||
* @param string $namespace the namespace name
|
||||
* @param string $classes the ready build class uml
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function namespaceDiagram(string $namespace, string $classes): string
|
||||
{
|
||||
$namespace_depth = substr_count($namespace, '\\');
|
||||
$namespace_color = $this->getNamespaceColor($namespace_depth);
|
||||
|
||||
// Set the scale of the diagram
|
||||
// $plant_uml = "scale 0.8\n\n";
|
||||
|
||||
// Add namespace
|
||||
$plant_uml = "namespace $namespace #$namespace_color {\n\n";
|
||||
|
||||
// Add class
|
||||
$plant_uml .= $classes;
|
||||
|
||||
$plant_uml .= "}\n";
|
||||
|
||||
return $plant_uml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a class basic diagram of a class
|
||||
*
|
||||
* @param array $power the class being built
|
||||
* @param array $code the class code being built
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function classBasicDiagram(array $power, array $code): string
|
||||
{
|
||||
// Set some global values
|
||||
$class_name = $power['name'];
|
||||
$class_type = $power['type'];
|
||||
|
||||
// set the class color
|
||||
$class_color = $this->getClassColor($class_type);
|
||||
|
||||
// set the class type label
|
||||
$type_label = $this->getClassTypeLable($class_type);
|
||||
|
||||
// set the class type tag
|
||||
$type_tag = $this->getClassTypeTag($class_type);
|
||||
|
||||
// Add class
|
||||
$plant_uml = "\n $type_label $class_name $type_tag #$class_color {\n";
|
||||
|
||||
// Add properties
|
||||
if (isset($code['properties']) && is_array($code['properties']))
|
||||
{
|
||||
$plant_uml .= $this->generatePropertiesPlantUML($code['properties'], ' ');
|
||||
}
|
||||
|
||||
// Add methods
|
||||
if (isset($code['methods']) && is_array($code['methods']))
|
||||
{
|
||||
$plant_uml .= $this->generateBasicMethodsPlantUML($code['methods']);
|
||||
}
|
||||
|
||||
$plant_uml .= " }\n";
|
||||
|
||||
return $plant_uml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a class detailed diagram of a class
|
||||
*
|
||||
* @param array $power the class being built
|
||||
* @param array $code the class code being built
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function classDetailedDiagram(array $power, array $code): string
|
||||
{
|
||||
// Set some global values
|
||||
$class_name = $power['name'];
|
||||
$class_type = $power['type'];
|
||||
|
||||
// set the class color
|
||||
$class_color = $this->getClassColor($class_type);
|
||||
|
||||
// set the class type label
|
||||
$type_label = $this->getClassTypeLable($class_type);
|
||||
|
||||
// set the class type tag
|
||||
$type_tag = $this->getClassTypeTag($class_type);
|
||||
|
||||
// Add class
|
||||
$plant_uml = "\n$type_label $class_name $type_tag #$class_color {\n";
|
||||
|
||||
// Add properties
|
||||
if (isset($code['properties']) && is_array($code['properties']))
|
||||
{
|
||||
$plant_uml .= $this->generatePropertiesPlantUML($code['properties'], ' ');
|
||||
}
|
||||
|
||||
// Add methods
|
||||
if (isset($code['methods']) && is_array($code['methods']))
|
||||
{
|
||||
list($methods_plant_uml, $notes) = $this->generateDetailedMethodsPlantUML($code['methods'], $class_name);
|
||||
$plant_uml .= $methods_plant_uml;
|
||||
}
|
||||
|
||||
$plant_uml .= "}\n";
|
||||
|
||||
if (!empty($notes))
|
||||
{
|
||||
$plant_uml .= $this->generateNotesPlantUML($notes);
|
||||
}
|
||||
|
||||
return $plant_uml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate properties PlantUML
|
||||
*
|
||||
* @param array $properties
|
||||
* @param string $space
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function generatePropertiesPlantUML(array $properties, string $space): string
|
||||
{
|
||||
$plant_uml = "";
|
||||
|
||||
foreach ($properties as $property)
|
||||
{
|
||||
$access_sign = $this->getAccessSign($property['access']);
|
||||
$static = $property['static'] ? '{static} ' : '';
|
||||
$type = $property['type'] ? $property['type'] . ' ' : '';
|
||||
$plant_uml .= "{$space}$access_sign $static{$type}{$property['name']}\n";
|
||||
}
|
||||
|
||||
return $plant_uml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate detailed methods PlantUML
|
||||
*
|
||||
* @param array $methods
|
||||
* @param string $class_name
|
||||
*
|
||||
* @return array
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function generateDetailedMethodsPlantUML(array $methods, string $class_name): array
|
||||
{
|
||||
$plant_uml = "";
|
||||
$notes = [];
|
||||
|
||||
foreach ($methods as $method)
|
||||
{
|
||||
$notes = $this->generateMethodNotes($method, $class_name, $notes);
|
||||
|
||||
$access_sign = $this->getAccessSign($method['access']);
|
||||
|
||||
$arguments = '';
|
||||
if ($method['arguments'])
|
||||
{
|
||||
$arguments = $this->generateMethodArgumentsAndNotes(
|
||||
$method['arguments'], $class_name, $method['name'], $notes);
|
||||
|
||||
$arguments = implode(', ', $arguments);
|
||||
}
|
||||
|
||||
$static = $method['static'] ? '{static} ' : '';
|
||||
$abstract = $method['abstract'] ? '{abstract} ' : '';
|
||||
$return_type = $method['return_type'] ? " : {$method['return_type']}" : '';
|
||||
|
||||
$plant_uml .= " $access_sign {$abstract}$static{$method['name']}({$arguments})$return_type\n";
|
||||
}
|
||||
|
||||
return [$plant_uml, $notes];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate basic methods PlantUML
|
||||
*
|
||||
* @param array $properties
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function generateBasicMethodsPlantUML(array $methods): string
|
||||
{
|
||||
$plant_uml = "";
|
||||
|
||||
foreach ($methods as $method)
|
||||
{
|
||||
$access_sign = $this->getAccessSign($method['access']);
|
||||
$static = $method['static'] ? '{static} ' : '';
|
||||
$abstract = $method['abstract'] ? '{abstract} ' : '';
|
||||
$return_type = $method['return_type'] ? " : {$method['return_type']}" : '';
|
||||
$plant_uml .= " $access_sign {$abstract}$static{$method['name']}()$return_type\n";
|
||||
}
|
||||
|
||||
return $plant_uml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate method arguments and notes
|
||||
*
|
||||
* @param array $arguments
|
||||
* @param string $class_name
|
||||
* @param string $method_name
|
||||
* @param array $notes
|
||||
*
|
||||
* @return array
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function generateMethodArgumentsAndNotes(array $arguments, string $class_name,
|
||||
string $method_name, array &$notes): array
|
||||
{
|
||||
$formatted_arguments = [];
|
||||
$notes_bucket = [];
|
||||
$limit = 2;
|
||||
|
||||
foreach ($arguments as $name => $arg)
|
||||
{
|
||||
$arg_type = $arg['type'] ? "{$arg['type']} " : '';
|
||||
$arg_default = $arg['default'] ? " = {$arg['default']}" : '';
|
||||
$arg_statment = "{$arg_type}$name{$arg_default}";
|
||||
|
||||
if ($limit == 0)
|
||||
{
|
||||
$formatted_arguments[] = "...";
|
||||
$limit = -1;
|
||||
}
|
||||
elseif ($limit > 0)
|
||||
{
|
||||
$formatted_arguments[] = $arg_statment;
|
||||
$limit--;
|
||||
}
|
||||
|
||||
$notes_bucket[] = $arg_statment;
|
||||
}
|
||||
|
||||
if ($limit == -1)
|
||||
{
|
||||
$notes["{$class_name}::{$method_name}"][] = "\n arguments:\n " . implode("\n ", $notes_bucket);
|
||||
}
|
||||
|
||||
return $formatted_arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate method notes
|
||||
*
|
||||
* @param array $method
|
||||
* @param string $class_name
|
||||
* @param array $notes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function generateMethodNotes(array $method, string $class_name, array &$notes): array
|
||||
{
|
||||
$notes_key = "{$class_name}::{$method['name']}";
|
||||
|
||||
if (is_string($method['comment']) && strlen($method['comment']) > 4)
|
||||
{
|
||||
$notes[$notes_key][] = trim(preg_replace("/^@.*[\r\n]*/m", '', $method['comment'])) . "\n";
|
||||
}
|
||||
|
||||
if (is_string($method['since']) && strlen($method['since']) > 3)
|
||||
{
|
||||
$notes[$notes_key][] = "since: {$method['since']}";
|
||||
}
|
||||
|
||||
if (is_string($method['return_type']) && strlen($method['return_type']) > 1)
|
||||
{
|
||||
$notes[$notes_key][] = "return: {$method['return_type']}";
|
||||
}
|
||||
|
||||
if (is_string($method['deprecated']) && strlen($method['deprecated']) > 3)
|
||||
{
|
||||
$notes[$notes_key][] = "deprecated: {$method['deprecated']}";
|
||||
}
|
||||
|
||||
return $notes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate notes PlantUML
|
||||
*
|
||||
* @param array $notes
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function generateNotesPlantUML(array $notes): string
|
||||
{
|
||||
$plant_uml = "";
|
||||
$note_count = count($notes);
|
||||
|
||||
$positions = ['right', 'left'];
|
||||
$position_index = 0;
|
||||
|
||||
foreach ($notes as $area => $note)
|
||||
{
|
||||
if ($note_count <= 7)
|
||||
{
|
||||
$position = 'right';
|
||||
}
|
||||
else
|
||||
{
|
||||
$position = $positions[$position_index % 2];
|
||||
$position_index++;
|
||||
}
|
||||
|
||||
$plant_uml .= "\nnote $position of {$area}\n";
|
||||
$plant_uml .= " " . implode("\n ", $note) . "\n";
|
||||
$plant_uml .= "end note\n";
|
||||
}
|
||||
|
||||
return $plant_uml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the access sign based on the access level.
|
||||
*
|
||||
* @param string $access The access level.
|
||||
*
|
||||
* @return string The corresponding access sign.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getAccessSign(string $access): string
|
||||
{
|
||||
switch ($access)
|
||||
{
|
||||
case 'private':
|
||||
return '-';
|
||||
case 'protected':
|
||||
return '#';
|
||||
case 'public':
|
||||
return '+';
|
||||
case 'var':
|
||||
return '+';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct class type.
|
||||
*
|
||||
* @param string $type The class type.
|
||||
*
|
||||
* @return string The correct class type label.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getClassTypeLable(string $type): string
|
||||
{
|
||||
$class_type_updater = [
|
||||
'final class' => 'class',
|
||||
'abstract class' => 'abstract',
|
||||
'trait' => 'class'
|
||||
];
|
||||
|
||||
return $class_type_updater[$type] ?? $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extra class type tag.
|
||||
*
|
||||
* @param string $type The class type.
|
||||
*
|
||||
* @return string The correct class type label.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getClassTypeTag(string $type): string
|
||||
{
|
||||
$class_type_updater = [
|
||||
'final class' => '<< (F,LightGreen) >>',
|
||||
'trait' => '<< (T,Orange) >>'
|
||||
];
|
||||
|
||||
return $class_type_updater[$type] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class color based on class type.
|
||||
*
|
||||
* @param string $classType The class type.
|
||||
*
|
||||
* @return string The corresponding color.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getClassColor(string $classType): string
|
||||
{
|
||||
$class_colors = [
|
||||
'class' => 'Gold',
|
||||
'final class' => 'RoyalBlue',
|
||||
'abstract class' => 'Orange',
|
||||
'interface' => 'Lavender',
|
||||
'trait' => 'Turquoise'
|
||||
];
|
||||
|
||||
return $class_colors[$classType] ?? 'Green';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get namespace color based on namespace depth.
|
||||
*
|
||||
* @param int $namespaceDepth The depth of the namespace.
|
||||
*
|
||||
* @return string The corresponding color.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function getNamespaceColor(int $namespaceDepth): string
|
||||
{
|
||||
$namespace_colors = [
|
||||
'lightgrey',
|
||||
'Azure',
|
||||
'DarkCyan',
|
||||
'Olive',
|
||||
'LightGreen',
|
||||
'DeepSkyBlue',
|
||||
'Wheat',
|
||||
'Coral',
|
||||
'Beige',
|
||||
'DeepPink',
|
||||
'DeepSkyBlue'
|
||||
];
|
||||
|
||||
return $namespace_colors[$namespaceDepth % count($namespace_colors)] ?? 'lightgrey';
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 4th September, 2022
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/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\Power\Repo;
|
||||
|
||||
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power\Plantuml;
|
||||
|
||||
|
||||
/**
|
||||
* Compiler Power Repo Readme
|
||||
* @since 3.2.0
|
||||
*/
|
||||
class Readme
|
||||
{
|
||||
/**
|
||||
* Power Objects
|
||||
*
|
||||
* @var Power
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected Power $power;
|
||||
|
||||
/**
|
||||
* Compiler Powers Plantuml Builder
|
||||
*
|
||||
* @var Plantuml
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected Plantuml $plantuml;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Power|null $power The power object.
|
||||
* @param Plantuml|null $plantuml The powers plantuml builder object.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __construct(?Power $power = null, ?Plantuml $plantuml = null)
|
||||
{
|
||||
$this->power = $power ?: Compiler::_('Power');
|
||||
$this->plantuml = $plantuml ?: Compiler::_('Power.Plantuml');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Power Readme
|
||||
*
|
||||
* @param object $power A power details.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function get(object $power): string
|
||||
{
|
||||
// build readme
|
||||
$readme = ["```
|
||||
██████╗ ██████╗ ██╗ ██╗███████╗██████╗
|
||||
██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗
|
||||
██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝
|
||||
██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗
|
||||
██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║
|
||||
╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝
|
||||
```"];
|
||||
// add the class diagram
|
||||
$parsed_class_code = [];
|
||||
if (isset($power->parsed_class_code) && is_array($power->parsed_class_code))
|
||||
{
|
||||
$parsed_class_code = $power->parsed_class_code;
|
||||
}
|
||||
|
||||
$readme[] = "# " . $power->type . " " . $power->code_name . " (Details)";
|
||||
$readme[] = "> namespace: **" . $power->_namespace . "**";
|
||||
if ($power->extends != 0)
|
||||
{
|
||||
$readme[] = "> extends: **" . $power->extends_name . "**";
|
||||
}
|
||||
$readme[] = "```uml\n@startuml" . $this->plantuml->classDetailedDiagram(
|
||||
['name' => $power->code_name, 'type' => $power->type],
|
||||
$parsed_class_code
|
||||
) . " \n@enduml\n```";
|
||||
|
||||
// yes you can remove this, but why?
|
||||
$readme[] = "\n---\n```
|
||||
██╗ ██████╗██████╗
|
||||
██║██╔════╝██╔══██╗
|
||||
██║██║ ██████╔╝
|
||||
██ ██║██║ ██╔══██╗
|
||||
╚█████╔╝╚██████╗██████╔╝
|
||||
╚════╝ ╚═════╝╚═════╝
|
||||
```\n> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder)\n\n";
|
||||
|
||||
return implode("\n", $readme);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
@@ -0,0 +1,353 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 4th September, 2022
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/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\Power\Repos;
|
||||
|
||||
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power\Plantuml;
|
||||
|
||||
|
||||
/**
|
||||
* Compiler Power Repos Readme
|
||||
* @since 3.2.0
|
||||
*/
|
||||
class Readme
|
||||
{
|
||||
/**
|
||||
* Power Objects
|
||||
*
|
||||
* @var Power
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected Power $power;
|
||||
|
||||
/**
|
||||
* Compiler Powers Plantuml Builder
|
||||
*
|
||||
* @var Plantuml
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected Plantuml $plantuml;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Power|null $power The power object.
|
||||
* @param Plantuml|null $plantuml The powers plantuml builder object.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __construct(?Power $power = null, ?Plantuml $plantuml = null)
|
||||
{
|
||||
$this->power = $power ?: Compiler::_('Power');
|
||||
$this->plantuml = $plantuml ?: Compiler::_('Power.Plantuml');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Super Power Readme
|
||||
*
|
||||
* @param array $powers All powers of this super power.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function get(array $powers): string
|
||||
{
|
||||
// build readme
|
||||
$readme = ["```
|
||||
███████╗██╗ ██╗██████╗ ███████╗██████╗
|
||||
██╔════╝██║ ██║██╔══██╗██╔════╝██╔══██╗
|
||||
███████╗██║ ██║██████╔╝█████╗ ██████╔╝
|
||||
╚════██║██║ ██║██╔═══╝ ██╔══╝ ██╔══██╗
|
||||
███████║╚██████╔╝██║ ███████╗██║ ██║
|
||||
╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
|
||||
██████╗ ██████╗ ██╗ ██╗███████╗██████╗ ███████╗
|
||||
██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗██╔════╝
|
||||
██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝███████╗
|
||||
██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗╚════██║
|
||||
██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║███████║
|
||||
╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝╚══════╝
|
||||
```"];
|
||||
|
||||
// default description of super powers
|
||||
$readme[] = "\n### What is JCB Super Powers?\nThe Joomla Component Builder (JCB) Super Power features are designed to enhance JCB's functionality and streamline the development process. These Super Powers enable developers to efficiently manage and share their custom powers across multiple JCB instances through repositories hosted on [https://git.vdm.dev/[username]/[repository-name]](https://git.vdm.dev). JCB Super Powers are managed using a combination of layers, events, tasks, methods, switches, and algorithms, which work together to provide powerful customization and extensibility options. More details on JCB Super Powers can be found in the [Super Powers Documentation](https://git.vdm.dev/joomla/super-powers/wiki).\n\nIn summary, JCB Super Powers offer a flexible and efficient way to manage and share functionalities between JCB instances. By utilizing a sophisticated system of layers, events, tasks, methods, switches, and algorithms, developers can seamlessly integrate JCB core powers and their custom powers. For more information on how to work with JCB Super Powers, refer to the [Super Powers User Guide](https://git.vdm.dev/joomla/super-powers/wiki).\n\n### What can I find here?\nThis repository contains an index (see below) of all the approved powers within the JCB GUI. During the compilation of a component, these powers are automatically added to the repository, ensuring a well-organized and accessible collection of functionalities.\n";
|
||||
|
||||
// get the readme body
|
||||
$readme[] = $this->readmeBuilder($powers);
|
||||
|
||||
// yes you can remove this, but why?
|
||||
$readme[] = "\n---\n```
|
||||
██╗ ██████╗ ██████╗ ███╗ ███╗██╗ █████╗
|
||||
██║██╔═══██╗██╔═══██╗████╗ ████║██║ ██╔══██╗
|
||||
██║██║ ██║██║ ██║██╔████╔██║██║ ███████║
|
||||
██ ██║██║ ██║██║ ██║██║╚██╔╝██║██║ ██╔══██║
|
||||
╚█████╔╝╚██████╔╝╚██████╔╝██║ ╚═╝ ██║███████╗██║ ██║
|
||||
╚════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
|
||||
██████╗ ██████╗ ███╗ ███╗██████╗ ██████╗ ███╗ ██╗███████╗███╗ ██╗████████╗
|
||||
██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔═══██╗████╗ ██║██╔════╝████╗ ██║╚══██╔══╝
|
||||
██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║█████╗ ██╔██╗ ██║ ██║
|
||||
██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██║██║╚██╗██║██╔══╝ ██║╚██╗██║ ██║
|
||||
╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚██████╔╝██║ ╚████║███████╗██║ ╚████║ ██║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═══╝ ╚═╝
|
||||
██████╗ ██╗ ██╗██╗██╗ ██████╗ ███████╗██████╗
|
||||
██╔══██╗██║ ██║██║██║ ██╔══██╗██╔════╝██╔══██╗
|
||||
██████╔╝██║ ██║██║██║ ██║ ██║█████╗ ██████╔╝
|
||||
██╔══██╗██║ ██║██║██║ ██║ ██║██╔══╝ ██╔══██╗
|
||||
██████╔╝╚██████╔╝██║███████╗██████╔╝███████╗██║ ██║
|
||||
╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═╝
|
||||
```\n> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder)\n\n";
|
||||
|
||||
return implode("\n", $readme);
|
||||
}
|
||||
|
||||
/**
|
||||
* The readme builder
|
||||
*
|
||||
* @param array $classes The powers.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function readmeBuilder(array &$powers): string
|
||||
{
|
||||
$classes = [];
|
||||
foreach ($powers as $guid => $power)
|
||||
{
|
||||
// add to the sort bucket
|
||||
$classes[] = [
|
||||
'namespace' => $power['namespace'],
|
||||
'type' => $power['type'],
|
||||
'name' => $power['name'],
|
||||
'link' => $this->indexLinkPower($power)
|
||||
];
|
||||
}
|
||||
|
||||
return $this->readmeModel($classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort and model the readme classes
|
||||
*
|
||||
* @param array $classes The powers.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function readmeModel(array &$classes): string
|
||||
{
|
||||
$this->sortClasses($classes, $this->defineTypeOrder());
|
||||
|
||||
return $this->generateIndex($classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the index string for classes
|
||||
*
|
||||
* @param array $classes The sorted classes
|
||||
*
|
||||
* @return string The index string
|
||||
*/
|
||||
private function generateIndex(array &$classes): string
|
||||
{
|
||||
$result = "# Index of powers\n";
|
||||
$current_namespace = null;
|
||||
|
||||
foreach ($classes as $class)
|
||||
{
|
||||
if ($class['namespace'] !== $current_namespace)
|
||||
{
|
||||
$current_namespace = $class['namespace'];
|
||||
$result .= "\n- **Namespace**: [{$current_namespace}](#" .
|
||||
strtolower(str_replace('\\', '-', $current_namespace)) . ")\n";
|
||||
}
|
||||
|
||||
// Add the class details
|
||||
$result .= "\n - " . $class['link'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the order of types for sorting purposes
|
||||
*
|
||||
* @return array The order of types
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function defineTypeOrder(): array
|
||||
{
|
||||
return [
|
||||
'interface' => 1,
|
||||
'abstract' => 2,
|
||||
'abstract class' => 2,
|
||||
'final' => 3,
|
||||
'final class' => 3,
|
||||
'class' => 4,
|
||||
'trait' => 5
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the flattened array using a single sorting function
|
||||
*
|
||||
* @param array $classes The classes to sort
|
||||
* @param array $typeOrder The order of types
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function sortClasses(array &$classes, array $typeOrder): void
|
||||
{
|
||||
usort($classes, function ($a, $b) use ($typeOrder) {
|
||||
$namespaceDiff = $this->compareNamespace($a, $b);
|
||||
|
||||
if ($namespaceDiff !== 0)
|
||||
{
|
||||
return $namespaceDiff;
|
||||
}
|
||||
|
||||
$typeDiff = $this->compareType($a, $b, $typeOrder);
|
||||
|
||||
if ($typeDiff !== 0)
|
||||
{
|
||||
return $typeDiff;
|
||||
}
|
||||
|
||||
return $this->compareName($a, $b);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the namespace of two classes
|
||||
*
|
||||
* @param array $a First class
|
||||
* @param array $b Second class
|
||||
*
|
||||
* @return int Comparison result
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function compareNamespace(array $a, array $b): int
|
||||
{
|
||||
$namespaceDepthDiff = substr_count($a['namespace'], '\\') - substr_count($b['namespace'], '\\');
|
||||
|
||||
if ($namespaceDepthDiff === 0)
|
||||
{
|
||||
return strcmp($a['namespace'], $b['namespace']);
|
||||
}
|
||||
|
||||
return $namespaceDepthDiff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the type of two classes
|
||||
*
|
||||
* @param array $a First class
|
||||
* @param array $b Second class
|
||||
* @param array $typeOrder The order of types
|
||||
*
|
||||
* @return int Comparison result
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function compareType(array $a, array $b, array $typeOrder): int
|
||||
{
|
||||
return $typeOrder[$a['type']] <=> $typeOrder[$b['type']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the name of two classes
|
||||
*
|
||||
* @param array $a First class
|
||||
* @param array $b Second class
|
||||
*
|
||||
* @return int Comparison result
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function compareName(array $a, array $b): int
|
||||
{
|
||||
return strcmp($a['name'], $b['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Link to the power in this repository
|
||||
*
|
||||
* @param array $power The power details.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function indexLinkPower(array &$power): string
|
||||
{
|
||||
$type = $power['type'] ?? 'error';
|
||||
$name = $power['name'] ?? 'error';
|
||||
return '**' . $type . ' ' . $name . "** | "
|
||||
. $this->linkPowerRepo($power) . ' | '
|
||||
. $this->linkPowerCode($power) . ' | '
|
||||
. $this->linkPowerSettings($power) . ' | '
|
||||
. $this->linkPowerSPK($power);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Link to the power in this repository
|
||||
*
|
||||
* @param array $power The power details.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function linkPowerRepo(array &$power): string
|
||||
{
|
||||
$path = $power['path'] ?? 'error';
|
||||
return '[Details](' . $path . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Link to the power settings in this repository
|
||||
*
|
||||
* @param array $power The power details.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function linkPowerCode(array &$power): string
|
||||
{
|
||||
$code = $power['code'] ?? 'error';
|
||||
return '[Code](' . $code . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Link to the power settings in this repository
|
||||
*
|
||||
* @param array $power The power details.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function linkPowerSettings(array &$power): string
|
||||
{
|
||||
$settings = $power['settings'] ?? 'error';
|
||||
return '[Settings](' . $settings . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SuperPowerKey (SPK)
|
||||
*
|
||||
* @param array $power The power details.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function linkPowerSPK(array &$power): string
|
||||
{
|
||||
$spk = $power['spk'] ?? 'error';
|
||||
return $spk;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
@@ -0,0 +1,486 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Component.Builder
|
||||
*
|
||||
* @created 4th September, 2022
|
||||
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
||||
* @git Joomla Component Builder <https://git.vdm.dev/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\Power;
|
||||
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Power;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Config;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Registry;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Interfaces\EventInterface;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Counter;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Paths;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Folder;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Utilities\File;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Files;
|
||||
use VDM\Joomla\Utilities\ObjectHelper;
|
||||
use VDM\Joomla\Utilities\ArrayHelper;
|
||||
use VDM\Joomla\Utilities\StringHelper;
|
||||
use VDM\Joomla\Utilities\JsonHelper;
|
||||
use VDM\Joomla\Utilities\FileHelper;
|
||||
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Placefix;
|
||||
|
||||
|
||||
/**
|
||||
* Power Structure Builder Class
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
class Structure
|
||||
{
|
||||
/**
|
||||
* we track the creation of htaccess files
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected array $htaccess = [];
|
||||
|
||||
/**
|
||||
* Power Build Tracker
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected array $done = [];
|
||||
|
||||
/**
|
||||
* Path Build Tracker
|
||||
*
|
||||
* @var array
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected array $path_done = [];
|
||||
|
||||
/**
|
||||
* Power Objects
|
||||
*
|
||||
* @var Power
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected Power $power;
|
||||
|
||||
/**
|
||||
* Compiler Config
|
||||
*
|
||||
* @var Config
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Config $config;
|
||||
|
||||
/**
|
||||
* The compiler registry
|
||||
*
|
||||
* @var Registry
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Registry $registry;
|
||||
|
||||
/**
|
||||
* Compiler Event
|
||||
*
|
||||
* @var EventInterface
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected EventInterface $event;
|
||||
|
||||
/**
|
||||
* Compiler Counter
|
||||
*
|
||||
* @var Counter
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Counter $counter;
|
||||
|
||||
/**
|
||||
* Compiler Utilities Paths
|
||||
*
|
||||
* @var Paths
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Paths $paths;
|
||||
|
||||
/**
|
||||
* Compiler Utilities Folder
|
||||
*
|
||||
* @var Folder
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Folder $folder;
|
||||
|
||||
/**
|
||||
* Compiler Utilities File
|
||||
*
|
||||
* @var File
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected File $file;
|
||||
|
||||
/**
|
||||
* Compiler Utilities Files
|
||||
*
|
||||
* @var Files
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected Files $files;
|
||||
|
||||
/**
|
||||
* Database object to query local DB
|
||||
*
|
||||
* @var CMSApplication
|
||||
* @since 3.2.0
|
||||
**/
|
||||
protected CMSApplication $app;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Power|null $power The power object.
|
||||
* @param Config|null $config The compiler config object.
|
||||
* @param Registry|null $registry The compiler registry object.
|
||||
* @param EventInterface|null $event The compiler event api object.
|
||||
* @param Counter|null $counter The compiler counter object.
|
||||
* @param Paths|null $paths The compiler paths object.
|
||||
* @param Folder|null $folder The compiler folder object.
|
||||
* @param File|null $file The compiler file object.
|
||||
* @param Files|null $files The compiler files object.
|
||||
* @param CMSApplication|null $app The CMS Application object.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __construct(?Power $power = null, ?Config $config = null,
|
||||
?Registry $registry = null, ?EventInterface $event = null,
|
||||
?Counter $counter = null, ?Paths $paths = null, ?Folder $folder = null,
|
||||
?File $file = null, ?Files $files = null, ?CMSApplication $app = null)
|
||||
{
|
||||
$this->power = $power ?: Compiler::_('Power');
|
||||
$this->config = $config ?: Compiler::_('Config');
|
||||
$this->registry = $registry ?: Compiler::_('Registry');
|
||||
$this->event = $event ?: Compiler::_('Event');
|
||||
$this->counter = $counter ?: Compiler::_('Utilities.Counter');
|
||||
$this->paths = $paths ?: Compiler::_('Utilities.Paths');
|
||||
$this->folder = $folder ?: Compiler::_('Utilities.Folder');
|
||||
$this->file = $file ?: Compiler::_('Utilities.File');
|
||||
$this->files = $files ?: Compiler::_('Utilities.Files');
|
||||
$this->app = $app ?: Factory::getApplication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Powers files, folders
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
if (ArrayHelper::check($this->power->active))
|
||||
{
|
||||
// Trigger Event: jcb_ce_onBeforeSetModules
|
||||
$this->event->trigger(
|
||||
'jcb_ce_onBeforeBuildPowers'
|
||||
);
|
||||
|
||||
// set super power details
|
||||
$this->setSuperPowerDetails();
|
||||
|
||||
foreach ($this->power->active as $guid => $power)
|
||||
{
|
||||
if (isset($this->done[$guid]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ObjectHelper::check($power)
|
||||
&& isset($power->path)
|
||||
&& StringHelper::check(
|
||||
$power->path
|
||||
))
|
||||
{
|
||||
// activate dynamic folders
|
||||
$this->setDynamicFolders();
|
||||
|
||||
// power path
|
||||
$power->full_path = $this->paths->component_path . '/'
|
||||
. $power->path;
|
||||
$power->full_path_jcb = $this->paths->component_path . '/'
|
||||
. $power->path_jcb;
|
||||
$power->full_path_parent = $this->paths->component_path . '/'
|
||||
. $power->path_parent;
|
||||
|
||||
// set the power paths
|
||||
$this->registry->set('dynamic_paths.' . $power->key, $power->full_path_parent);
|
||||
|
||||
// create the power folder if it does not exist
|
||||
// we do it like this to add html files to each part
|
||||
$this->folder->create($power->full_path_jcb);
|
||||
$this->folder->create($power->full_path_parent);
|
||||
$this->folder->create($power->full_path);
|
||||
|
||||
$bom = '<?php' . PHP_EOL . '// A POWER FILE' .
|
||||
PHP_EOL . Placefix::_h('BOM') . PHP_EOL;
|
||||
|
||||
// add custom override if found
|
||||
if ($power->add_licensing_template == 2)
|
||||
{
|
||||
$bom = '<?php' . PHP_EOL . $power->licensing_template;
|
||||
}
|
||||
|
||||
// set the main power php file
|
||||
$this->createFile($bom . PHP_EOL . Placefix::_h('POWERCODE') . PHP_EOL,
|
||||
$power->full_path, $power->file_name . '.php', $power->key);
|
||||
|
||||
// set super power files
|
||||
$this->setSuperPowerFiles($power, $bom);
|
||||
|
||||
// set htaccess once per path
|
||||
$this->setHtaccess($power);
|
||||
|
||||
// do each power just once
|
||||
$this->done[$guid] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file with optional custom content and save it to the given path.
|
||||
*
|
||||
* @param string $content The content.
|
||||
* @param string $fullPath The full path to the destination folder.
|
||||
* @param string $fileName The file name without the extension.
|
||||
* @param string $key The key to append the file details.
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function createFile(string $content, string $fullPath, string $fileName, string $key)
|
||||
{
|
||||
$file_details = [
|
||||
'path' => $fullPath . '/' . $fileName,
|
||||
'name' => $fileName,
|
||||
'zip' => $fileName
|
||||
];
|
||||
|
||||
// Write the content to the file
|
||||
$this->file->write($file_details['path'], $content);
|
||||
|
||||
// Append the file details to the files array
|
||||
$this->files->appendArray($key, $file_details);
|
||||
|
||||
// Increment the file counter
|
||||
$this->counter->file++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the .htaccess for this power path
|
||||
*
|
||||
* @param object $power The power object
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function setHtaccess(object &$power)
|
||||
{
|
||||
if (!isset($this->htaccess[$power->path_jcb]))
|
||||
{
|
||||
// set the htaccess data
|
||||
$data = '# Apache 2.4+' . PHP_EOL .
|
||||
'<IfModule mod_authz_core.c>' . PHP_EOL .
|
||||
' Require all denied' . PHP_EOL .
|
||||
'</IfModule>' . PHP_EOL . PHP_EOL .
|
||||
'# Apache 2.0-2.2' . PHP_EOL .
|
||||
'<IfModule !mod_authz_core.c>' . PHP_EOL .
|
||||
' Deny from all' . PHP_EOL .
|
||||
'</IfModule>' . PHP_EOL;
|
||||
|
||||
// now we must add the .htaccess file
|
||||
$fileDetails = array('path' => $power->full_path_jcb . '/.htaccess',
|
||||
'name' => '.htaccess',
|
||||
'zip' => '.htaccess');
|
||||
$this->file->write(
|
||||
$fileDetails['path'], $data
|
||||
);
|
||||
$this->files->appendArray($power->key, $fileDetails);
|
||||
|
||||
// count the file created
|
||||
$this->counter->file++;
|
||||
|
||||
// now we must add the htaccess.txt file where the zip package my not get the [.] files
|
||||
$fileDetails = array('path' => $power->full_path_jcb . '/htaccess.txt',
|
||||
'name' => 'htaccess.txt',
|
||||
'zip' => 'htaccess.txt');
|
||||
$this->file->write(
|
||||
$fileDetails['path'], $data
|
||||
);
|
||||
$this->files->appendArray($power->key, $fileDetails);
|
||||
|
||||
// count the file created
|
||||
$this->counter->file++;
|
||||
|
||||
// now we must add the web.config file
|
||||
$fileDetails = array('path' => $power->full_path_jcb . '/web.config',
|
||||
'name' => 'web.config',
|
||||
'zip' => 'web.config');
|
||||
$this->file->write(
|
||||
$fileDetails['path'],
|
||||
'<?xml version="1.0"?>' . PHP_EOL .
|
||||
' <system.web>' . PHP_EOL .
|
||||
' <authorization>' . PHP_EOL .
|
||||
' <deny users="*" />' . PHP_EOL .
|
||||
' </authorization>' . PHP_EOL .
|
||||
' </system.web>' . PHP_EOL .
|
||||
'</configuration>' . PHP_EOL
|
||||
);
|
||||
$this->files->appendArray($power->key, $fileDetails);
|
||||
|
||||
// count the file created
|
||||
$this->counter->file++;
|
||||
|
||||
// we set these files only once
|
||||
$this->htaccess[$power->path_jcb] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the dynamic folders
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function setDynamicFolders()
|
||||
{
|
||||
// check if we should add the dynamic folder moving script to the installer script
|
||||
if (!$this->registry->get('set_move_folders_install_script'))
|
||||
{
|
||||
$function = 'setDynamicF0ld3rs';
|
||||
$script = 'script.php';
|
||||
if ($this->config->get('joomla_version', 3) != 3)
|
||||
{
|
||||
$function = 'moveFolders';
|
||||
$script = 'ComponentnameInstallerScript.php';
|
||||
}
|
||||
|
||||
// add the setDynamicF0ld3rs() method to the install script.php file
|
||||
$this->registry->set('set_move_folders_install_script', true);
|
||||
|
||||
// set message that this was done (will still add a tutorial link later)
|
||||
$this->app->enqueueMessage(
|
||||
Text::_('COM_COMPONENTBUILDER_HR_HTHREEDYNAMIC_FOLDERS_WERE_DETECTEDHTHREE'),
|
||||
'Notice'
|
||||
);
|
||||
$this->app->enqueueMessage(
|
||||
Text::sprintf('COM_COMPONENTBUILDER_A_METHOD_S_WAS_ADDED_TO_THE_INSTALL_BSB_OF_THIS_PACKAGE_TO_INSURE_THAT_THE_FOLDERS_ARE_COPIED_INTO_THE_CORRECT_PLACE_WHEN_THIS_COMPONENT_IS_INSTALLED',
|
||||
$function, $script
|
||||
),
|
||||
'Notice'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the super powers details structure
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function setSuperPowerDetails()
|
||||
{
|
||||
if ($this->config->add_super_powers && ArrayHelper::check($this->power->superpowers))
|
||||
{
|
||||
foreach ($this->power->superpowers as $path => $powers)
|
||||
{
|
||||
if (isset($this->path_done[$path]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// get existing files
|
||||
$this->loadExistingSuperPower($path);
|
||||
|
||||
// create the path if it does not exist
|
||||
$this->folder->create($path, false);
|
||||
|
||||
$key = StringHelper::safe($path);
|
||||
|
||||
// set the super powers readme file
|
||||
$this->createFile(Placefix::_h('POWERREADME'),
|
||||
$path, 'README.md', $key);
|
||||
|
||||
// set the super power index file
|
||||
$this->createFile(Placefix::_h('POWERINDEX'), $path,
|
||||
'super-powers.json', $key);
|
||||
|
||||
// do each path just once
|
||||
$this->path_done[$path] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the super power file paths
|
||||
*
|
||||
* @param object $power The power object
|
||||
* @param string $bom The bom for the top of the PHP files
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function setSuperPowerFiles(object &$power, string $bom)
|
||||
{
|
||||
if ($this->config->add_super_powers && is_array($power->super_power_paths) && $power->super_power_paths !== [])
|
||||
{
|
||||
foreach ($power->super_power_paths as $path)
|
||||
{
|
||||
// create the path if it does not exist
|
||||
$this->folder->create($path, false);
|
||||
|
||||
// set the super power php file
|
||||
$this->createFile($bom . PHP_EOL . Placefix::_h('POWERCODE') . PHP_EOL,
|
||||
$path, 'code.php', $power->key);
|
||||
|
||||
// set the super power php RAW file
|
||||
$this->createFile(Placefix::_h('CODEPOWER'),
|
||||
$path, 'code.power', $power->key);
|
||||
|
||||
// set the super power json file
|
||||
$this->createFile(Placefix::_h('POWERLINKER'), $path,
|
||||
'settings.json', $power->key);
|
||||
|
||||
// set the super power readme file
|
||||
$this->createFile(Placefix::_h('POWERREADME'), $path,
|
||||
'README.md', $power->key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the super power file paths
|
||||
*
|
||||
* @param string $repository The super power repository
|
||||
*
|
||||
* @return void
|
||||
* @since 3.2.0
|
||||
*/
|
||||
private function loadExistingSuperPower(string $repository)
|
||||
{
|
||||
if (!isset($this->power->old_superpowers[$repository]) && ($content = FileHelper::getContent($repository . '/super-powers.json', null)) !== null &&
|
||||
JsonHelper::check($content))
|
||||
{
|
||||
$this->power->old_superpowers[$repository] = json_decode($content, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
Reference in New Issue
Block a user