update 2024-04-16 16:05:26

This commit is contained in:
Robot 2024-04-16 16:05:26 +02:00
parent a39b1880d9
commit e328ad5ce3
Signed by: Robot
GPG Key ID: 14DECD44E7E1BB95
19 changed files with 241 additions and 1063 deletions

View File

@ -62,7 +62,7 @@ class Placeholder implements ServiceProviderInterface
*
* @param Container $container The DI container.
*
* @return Worker
* @return Reverse
* @since 3.2.0
*/
public function getPlaceholderReverse(Container $container): Reverse
@ -72,7 +72,8 @@ class Placeholder implements ServiceProviderInterface
$container->get('Placeholder'),
$container->get('Language'),
$container->get('Language.Extractor'),
$container->get('Power.Extractor')
$container->get('Power.Extractor'),
$container->get('Joomla.Power.Extractor')
);
}
}

View File

@ -35,7 +35,7 @@
*
* @param Container $container The DI container.
*
* @return Worker
* @return Reverse
* @since 3.2.0
*/
public function getPlaceholderReverse(Container $container): Reverse
@ -45,6 +45,7 @@
$container->get('Placeholder'),
$container->get('Language'),
$container->get('Language.Extractor'),
$container->get('Power.Extractor')
$container->get('Power.Extractor'),
$container->get('Joomla.Power.Extractor')
);
}

View File

@ -19,9 +19,10 @@ class Customcode #Gold {
# Placeholder $placeholder
# Extractor $extractor
# Power $power
# JoomlaPower $joomla
# External $external
# $db
+ __construct(?Config $config = null, ?Placeholder $placeholder = null, ...)
+ __construct(Config $config, Placeholder $placeholder, ...)
+ update(string $string, int $debug) : string
+ set(string $string, int $debug, ...) : string
+ get(?array $ids = null, bool $setLang = true, ...) : bool
@ -36,11 +37,12 @@ note right of Customcode::__construct
since: 3.2.0
arguments:
?Config $config = null
?Placeholder $placeholder = null
?Extractor $extractor = null
?Power $power = null
?External $external = null
Config $config
Placeholder $placeholder
Extractor $extractor
Power $power
JoomlaPower $joomla
External $external
end note
note right of Customcode::update

View File

@ -18,6 +18,7 @@ use VDM\Joomla\Componentbuilder\Compiler\Config;
use VDM\Joomla\Componentbuilder\Compiler\Placeholder;
use VDM\Joomla\Componentbuilder\Compiler\Language\Extractor;
use VDM\Joomla\Componentbuilder\Compiler\Power\Extractor as Power;
use VDM\Joomla\Componentbuilder\Compiler\JoomlaPower\Extractor as JoomlaPower;
use VDM\Joomla\Componentbuilder\Compiler\Customcode\External;
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Placefix;
use VDM\Joomla\Utilities\StringHelper;
@ -111,6 +112,14 @@ class Customcode implements CustomcodeInterface
**/
protected Power $power;
/**
* Joomla Power Extractor
*
* @var Power
* @since 3.2.0
**/
protected JoomlaPower $joomla;
/**
* Compiler Custom Code External
*
@ -129,23 +138,25 @@ class Customcode implements CustomcodeInterface
/**
* Constructor.
*
* @param Config|null $config The compiler config object.
* @param Placeholder|null $placeholder The compiler placeholder object.
* @param Extractor|null $extractor The compiler language extractor object.
* @param Power|null $power The compiler power extractor object.
* @param External|null $external The compiler external custom code object.
* @param \JDatabaseDriver $db The Database Driver object.
* @param Config $config The compiler config object.
* @param Placeholder $placeholder The compiler placeholder object.
* @param Extractor $extractor The compiler language extractor object.
* @param Power $power The compiler power extractor object.
* @param JoomlaPower $joomla The compiler joomla power extractor object.
* @param External $external The compiler external custom code object.
* @param \JDatabaseDriver $db The Database Driver object.
*
* @since 3.2.0
*/
public function __construct(?Config $config = null, ?Placeholder $placeholder = null,
?Extractor $extractor = null, ?Power $power = null, ?External $external = null)
public function __construct(Config $config, Placeholder $placeholder,
Extractor $extractor, Power $power, JoomlaPower $joomla, External $external)
{
$this->config = $config ?: Compiler::_('Config');
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
$this->extractor = $extractor ?: Compiler::_('Language.Extractor');
$this->power = $power ?: Compiler::_('Power.Extractor');
$this->external = $external ?: Compiler::_('Customcode.External');
$this->config = $config;
$this->placeholder = $placeholder;
$this->extractor = $extractor;
$this->power = $power;
$this->joomla = $joomla;
$this->external = $external;
$this->db = Factory::getDbo();
}
@ -170,8 +181,9 @@ class Customcode implements CustomcodeInterface
)
);
// extract any found super powers
// extract any found powers
$this->power->search($string);
$this->joomla->search($string);
}
// if debug
if ($debug)

View File

@ -76,6 +76,14 @@
**/
protected Power $power;
/**
* Joomla Power Extractor
*
* @var Power
* @since 3.2.0
**/
protected JoomlaPower $joomla;
/**
* Compiler Custom Code External
*
@ -94,23 +102,25 @@
/**
* Constructor.
*
* @param Config|null $config The compiler config object.
* @param Placeholder|null $placeholder The compiler placeholder object.
* @param Extractor|null $extractor The compiler language extractor object.
* @param Power|null $power The compiler power extractor object.
* @param External|null $external The compiler external custom code object.
* @param \JDatabaseDriver $db The Database Driver object.
* @param Config $config The compiler config object.
* @param Placeholder $placeholder The compiler placeholder object.
* @param Extractor $extractor The compiler language extractor object.
* @param Power $power The compiler power extractor object.
* @param JoomlaPower $joomla The compiler joomla power extractor object.
* @param External $external The compiler external custom code object.
* @param \JDatabaseDriver $db The Database Driver object.
*
* @since 3.2.0
*/
public function __construct(?Config $config = null, ?Placeholder $placeholder = null,
?Extractor $extractor = null, ?Power $power = null, ?External $external = null)
public function __construct(Config $config, Placeholder $placeholder,
Extractor $extractor, Power $power, JoomlaPower $joomla, External $external)
{
$this->config = $config ?: Compiler::_('Config');
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
$this->extractor = $extractor ?: Compiler::_('Language.Extractor');
$this->power = $power ?: Compiler::_('Power.Extractor');
$this->external = $external ?: Compiler::_('Customcode.External');
$this->config = $config;
$this->placeholder = $placeholder;
$this->extractor = $extractor;
$this->power = $power;
$this->joomla = $joomla;
$this->external = $external;
$this->db = Factory::getDbo();
}
@ -135,8 +145,9 @@
)
);
// extract any found super powers
// extract any found powers
$this->power->search($string);
$this->joomla->search($string);
}
// if debug
if ($debug)

View File

@ -12,43 +12,47 @@
"system_name": "JCB.Compiler.Customcode",
"type": "class",
"use_selection": {
"use_selection3": {
"use_selection0": {
"use": "d910d8b8-4c23-4f3e-8cda-438f2d2bd7ac",
"as": "Compiler"
},
"use_selection4": {
"use_selection1": {
"use": "fa4bf18e-301e-42e3-91fb-6e0096c07adc",
"as": "default"
},
"use_selection5": {
"use_selection2": {
"use": "06453ada-e370-49f0-b262-e3f5a8ed0c2c",
"as": "default"
},
"use_selection6": {
"use_selection3": {
"use": "d33b9278-c409-4aec-a047-b72f56712391",
"as": "default"
},
"use_selection7": {
"use_selection4": {
"use": "eeb03266-22fd-45bb-953a-961bb6be3a54",
"as": "Power"
},
"use_selection8": {
"use_selection5": {
"use": "82505f3f-297f-4d75-a581-929ab3e93689",
"as": "JoomlaPower"
},
"use_selection6": {
"use": "1cef0266-99e2-40d8-919f-c2ce32226b0a",
"as": "default"
},
"use_selection9": {
"use_selection7": {
"use": "500f3a7f-c16d-4dd4-81b2-2df6776b5388",
"as": "default"
},
"use_selection1": {
"use_selection8": {
"use": "1f28cb53-60d9-4db1-b517-3c7dc6b429ef",
"as": "default"
},
"use_selection0": {
"use_selection9": {
"use": "0a59c65c-9daf-4bc9-baf4-e063ff9e6a8a",
"as": "default"
},
"use_selection2": {
"use_selection10": {
"use": "db87c339-5bb6-4291-a7ef-2c48ea1b06bc",
"as": "default"
}

View File

@ -235,6 +235,10 @@ final class JoomlaPower implements PowerInterface
// all powers linked to it
$this->state[$guid] = true;
echo '<pre>';
var_dump($this->active[$guid]);
exit;
// set the approved super power values
$this->setSuperPowers($guid);

View File

@ -198,6 +198,10 @@
// all powers linked to it
$this->state[$guid] = true;
echo '<pre>';
var_dump($this->active[$guid]);
exit;
// set the approved super power values
$this->setSuperPowers($guid);

View File

@ -174,16 +174,18 @@ class Router
// We can only work with ID if the [main get] is a [getItem] dynamicGet for this site view.
$key = ($view['settings']->main_get->gettype == 1) ? 'id' : null;
$view_selected = $selection['view'] ?? null;
$name_selected = $selection['name'] ?? null;
// Construct the enriched view object.
return (object) [
'view' => $view['settings']->code,
'View' => $view['settings']->Code,
'stable' => ($selection['view'] === $view['settings']->code), // sanity check
'target_view' => $selection['view'],
'table' => $selection['table'],
'table_name' => $selection['name'],
'alias' => $this->getSiteViewAliasKey($selection['name'] ?? null, $adminViews),
'stable' => ($view_selected === $view['settings']->code), // sanity check
'target_view' => $view_selected,
'table' => $selection['table'] ?? null,
'table_name' => $name_selected,
'alias' => $this->getSiteViewAliasKey($name_selected, $adminViews),
'key' => $key,
'form' => false
];

View File

@ -145,16 +145,18 @@
// We can only work with ID if the [main get] is a [getItem] dynamicGet for this site view.
$key = ($view['settings']->main_get->gettype == 1) ? 'id' : null;
$view_selected = $selection['view'] ?? null;
$name_selected = $selection['name'] ?? null;
// Construct the enriched view object.
return (object) [
'view' => $view['settings']->code,
'View' => $view['settings']->Code,
'stable' => ($selection['view'] === $view['settings']->code), // sanity check
'target_view' => $selection['view'],
'table' => $selection['table'],
'table_name' => $selection['name'],
'alias' => $this->getSiteViewAliasKey($selection['name'] ?? null, $adminViews),
'stable' => ($view_selected === $view['settings']->code), // sanity check
'target_view' => $view_selected,
'table' => $selection['table'] ?? null,
'table_name' => $name_selected,
'alias' => $this->getSiteViewAliasKey($name_selected, $adminViews),
'key' => $key,
'form' => false
];

View File

@ -12,44 +12,10 @@
```uml
@startuml
class Injector << (F,LightGreen) >> #RoyalBlue {
# update(array $guids, string $code) : string
# inspect(object $power, ?array $useStatements, ...) : ?string
# buildNamespaceStatment(object $power) : string
# extractUseStatements(string $useStatement, string $className, ...) : array
# isUseStatementEqual(string $useStatement, string $namespaceStatement) : bool
# extractClassNameOrAlias(string $useStatement) : ?string
# getUniqueName(string $name, object $power) : string
# extractLastNameFromNamespace(string $namespace) : ?string
# removeLastNameFromNamespace(string $namespace) : string
# shouldAddTraitStatement(object $power) : bool
# handleTraitLogic(string $name, object $power, ...) : void
# addUseStatement(string $name, string $className, ...) : void
# addUseStatements(string $code, ?array $useStatements) : string
# addLines(string $code, string $lines) : string
# addLinesAfterDefinedLine(string $code, string $lines) : string
}
note right of Injector::update
Update the code
since: 3.2.0
return: string
end note
note left of Injector::inspect
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.
since: 3.2.0
return: ?string
arguments:
object $power
?array $useStatements
?array $traits
end note
note right of Injector::buildNamespaceStatment
Builds the namespace statement from the power object's namespace and class name.
@ -57,112 +23,12 @@ note right of Injector::buildNamespaceStatment
return: string
end note
note left of Injector::extractUseStatements
Extracts and processes use statements to find if the current class name is already used.
It identifies any potential naming conflicts.
since: 3.2.0
return: array
arguments:
string $useStatement
string $className
?array $useStatements
end note
note right of Injector::isUseStatementEqual
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.
return: bool
end note
note left of Injector::extractClassNameOrAlias
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'
since: 3.2.0
return: ?string
end note
note right of Injector::getUniqueName
Ensures the name for the use statement is unique, avoiding conflicts with other classes.
since: 3.2.0
return: string
end note
note left of Injector::extractLastNameFromNamespace
Extracts the last part of a namespace string, which is typically the class name.
since: 3.2.0
return: ?string
end note
note right of Injector::removeLastNameFromNamespace
Removes the last name from the namespace.
since: 3.2.0
return: string
end note
note left of Injector::shouldAddTraitStatement
Determines whether a trait statement should be added.
since: 3.2.0
return: bool
end note
note right of Injector::handleTraitLogic
Handles specific logic for traits, such as checking if the trait is already used.
since: 3.2.0
return: void
arguments:
string $name
object $power
?array $traits
end note
note left of Injector::addUseStatement
Adds a use statement to the class if it's not already present.
since: 3.2.0
return: void
arguments:
string $name
string $className
string $namespaceStatement
end note
note right of Injector::addUseStatements
Insert a line before the class declaration in the given class code.
since: 3.2.0
return: string
end note
note left of Injector::addLines
Insert a line before the class declaration in the given class code.
since: 3.2.0
return: string
end note
note right of Injector::addLinesAfterDefinedLine
Inserts a new line after the defined('_JEXEC') line.
since: 3.2.0
return: string
end note
@enduml
```

View File

@ -22,86 +22,6 @@ use VDM\Joomla\Componentbuilder\Compiler\Power\Injector as ExtendingInjector;
*/
final class Injector extends ExtendingInjector implements InjectorInterface
{
/**
* 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.
*
@ -115,109 +35,6 @@ final class Injector extends ExtendingInjector implements InjectorInterface
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.
*
@ -269,236 +86,6 @@ final class Injector extends ExtendingInjector implements InjectorInterface
}
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;
}
}

View File

@ -1,83 +1,3 @@
/**
* 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.
*
@ -91,109 +11,6 @@
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.
*
@ -245,234 +62,4 @@
}
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;
}

View File

@ -82,6 +82,7 @@ class Customcode implements ServiceProviderInterface
$container->get('Placeholder'),
$container->get('Language.Extractor'),
$container->get('Power.Extractor'),
$container->get('Joomla.Power.Extractor'),
$container->get('Customcode.External')
);
}

View File

@ -48,6 +48,7 @@
$container->get('Placeholder'),
$container->get('Language.Extractor'),
$container->get('Power.Extractor'),
$container->get('Joomla.Power.Extractor'),
$container->get('Customcode.External')
);
}

View File

@ -16,11 +16,13 @@ class Reverse #Gold {
# Language $language
# Extractor $extractor
# Power $power
+ __construct(?Config $config = null, ?Placeholder $placeholder = null, ...)
# JoomlaPower $joomla
+ __construct(Config $config, Placeholder $placeholder, ...)
+ engine(string $string, array $placeholders, ...) : string
# setReverse(string $updateString, string $string, ...) : string
# reverseSuperPowers(string $updateString, string $string, ...) : string
# getReversePower(array $powers, array $useStatements) : ?array
# reverseJoomlaPowers(string $updateString, string $string, ...) : string
# getReversePower(array $powers, array $useStatements, ...) : ?array
# reverseLanguage(string $updateString, string $string, ...) : string
# reverseCustomCode(string $updateString, string $string) : string
}
@ -31,14 +33,15 @@ note right of Reverse::__construct
since: 3.2.0
arguments:
?Config $config = null
?Placeholder $placeholder = null
?Language $language = null
?Extractor $extractor = null
?Power $power = null
Config $config
Placeholder $placeholder
Language $language
Extractor $extractor
Power $power
JoomlaPower $joomla
end note
note right of Reverse::engine
note left of Reverse::engine
Reverse Engineer the dynamic placeholders (TODO hmmmm this is not ideal)
since: 3.2.0
@ -67,7 +70,7 @@ note right of Reverse::setReverse
?array $useStatements
end note
note right of Reverse::reverseSuperPowers
note left of Reverse::reverseSuperPowers
Set the super powers keys for the reveres process
since: 3.2.0
@ -79,11 +82,28 @@ note right of Reverse::reverseSuperPowers
?array $useStatements
end note
note right of Reverse::getReversePower
note right of Reverse::reverseJoomlaPowers
Set the joomla powers keys for the reveres process
since: 3.2.0
return: string
arguments:
string $updateString
string $string
?array $useStatements
end note
note left of Reverse::getReversePower
Set the super powers keys for the reveres process
since: 3.2.0
return: ?array
arguments:
array $powers
array $useStatements
string $target
end note
note right of Reverse::reverseLanguage
@ -98,7 +118,7 @@ note right of Reverse::reverseLanguage
string $target
end note
note right of Reverse::reverseCustomCode
note left of Reverse::reverseCustomCode
Set the custom code placeholder for the reveres process
since: 3.2.0

View File

@ -21,6 +21,7 @@ use VDM\Joomla\Componentbuilder\Compiler\Placeholder;
use VDM\Joomla\Componentbuilder\Compiler\Language;
use VDM\Joomla\Componentbuilder\Compiler\Language\Extractor;
use VDM\Joomla\Componentbuilder\Compiler\Power\Extractor as Power;
use VDM\Joomla\Componentbuilder\Compiler\JoomlaPower\Extractor as JoomlaPower;
/**
@ -70,27 +71,36 @@ class Reverse
**/
protected Power $power;
/**
* Joomla Power Extractor
*
* @var Power
* @since 3.2.1
**/
protected JoomlaPower $joomla;
/**
* Constructor.
*
* @param Config|null $config The compiler config object.
* @param Placeholder|null $placeholder The compiler placeholder object.
* @param Language|null $language The compiler language object.
* @param Extractor|null $extractor The compiler language extractor object.
* @param Power|null $power The compiler power extractor object.
* @param Config $config The compiler config object.
* @param Placeholder $placeholder The compiler placeholder object.
* @param Language $language The compiler language object.
* @param Extractor $extractor The compiler language extractor object.
* @param Power $power The compiler power extractor object.
*
* @since 3.2.0
*/
public function __construct(
?Config $config = null, ?Placeholder $placeholder = null,
?Language $language = null, ?Extractor $extractor = null,
?Power $power = null)
Config $config, Placeholder $placeholder,
Language $language, Extractor $extractor,
Power $power, JoomlaPower $joomla)
{
$this->config = $config ?: Compiler::_('Config');
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
$this->language = $language ?: Compiler::_('Language');
$this->extractor = $extractor ?: Compiler::_('Language.Extractor');
$this->power = $power ?: Compiler::_('Power.Extractor');
$this->config = $config;
$this->placeholder = $placeholder;
$this->language = $language;
$this->extractor = $extractor;
$this->power = $power;
$this->joomla = $joomla;
}
/**
@ -138,8 +148,9 @@ class Reverse
protected function setReverse(string $updateString, string $string,
string $target, ?array $useStatements): string
{
// we have to reverse engineer to super powers
// we have to reverse engineer of powers
$updateString = $this->reverseSuperPowers($updateString, $string, $useStatements);
$updateString = $this->reverseJoomlaPowers($updateString, $string, $useStatements);
// reverse engineer the language strings
$updateString = $this->reverseLanguage($updateString, $string, $target);
@ -165,7 +176,30 @@ class Reverse
{
// only if we have use statements can we reverse engineer this
if ($useStatements !== null && ($powers = $this->power->reverse($string)) !== null &&
($reverse = $this->getReversePower($powers, $useStatements)) !== null)
($reverse = $this->getReversePower($powers, $useStatements, 'Super')) !== null)
{
return $this->placeholder->update($updateString, $reverse);
}
return $updateString;
}
/**
* Set the joomla powers keys for the reveres process
*
* @param string $updateString The string to update
* @param string $string The string to use for super power update
* @param array|null $useStatements The file use statements (needed for super powers)
*
* @return string
* @since 3.2.0
*/
protected function reverseJoomlaPowers(string $updateString, string $string,
?array $useStatements): string
{
// only if we have use statements can we reverse engineer this
if ($useStatements !== null && ($powers = $this->joomla->reverse($string)) !== null &&
($reverse = $this->getReversePower($powers, $useStatements, 'Joomla')) !== null)
{
return $this->placeholder->update($updateString, $reverse);
}
@ -178,11 +212,12 @@ class Reverse
*
* @param array $powers The powers found in the database text
* @param array $useStatements The file use statements
* @param string $target The power target type
*
* @return array|null
* @since 3.2.0
*/
protected function getReversePower(array $powers, array $useStatements): ?array
protected function getReversePower(array $powers, array $useStatements, string $target): ?array
{
$matching_statements = [];
foreach ($useStatements as $use_statement)
@ -211,7 +246,7 @@ class Reverse
{
$guid = array_search($namespace, $powers);
$matching_statements[$class_name] =
'Super_'.'_'.'_' . str_replace('-', '_', $guid) . '_'.'_'.'_Power';
$target . '_'.'_'.'_' . str_replace('-', '_', $guid) . '_'.'_'.'_Power';
}
}

View File

@ -38,27 +38,36 @@
**/
protected Power $power;
/**
* Joomla Power Extractor
*
* @var Power
* @since 3.2.1
**/
protected JoomlaPower $joomla;
/**
* Constructor.
*
* @param Config|null $config The compiler config object.
* @param Placeholder|null $placeholder The compiler placeholder object.
* @param Language|null $language The compiler language object.
* @param Extractor|null $extractor The compiler language extractor object.
* @param Power|null $power The compiler power extractor object.
* @param Config $config The compiler config object.
* @param Placeholder $placeholder The compiler placeholder object.
* @param Language $language The compiler language object.
* @param Extractor $extractor The compiler language extractor object.
* @param Power $power The compiler power extractor object.
*
* @since 3.2.0
*/
public function __construct(
?Config $config = null, ?Placeholder $placeholder = null,
?Language $language = null, ?Extractor $extractor = null,
?Power $power = null)
Config $config, Placeholder $placeholder,
Language $language, Extractor $extractor,
Power $power, JoomlaPower $joomla)
{
$this->config = $config ?: Compiler::_('Config');
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
$this->language = $language ?: Compiler::_('Language');
$this->extractor = $extractor ?: Compiler::_('Language.Extractor');
$this->power = $power ?: Compiler::_('Power.Extractor');
$this->config = $config;
$this->placeholder = $placeholder;
$this->language = $language;
$this->extractor = $extractor;
$this->power = $power;
$this->joomla = $joomla;
}
/**
@ -106,8 +115,9 @@
protected function setReverse(string $updateString, string $string,
string $target, ?array $useStatements): string
{
// we have to reverse engineer to super powers
// we have to reverse engineer of powers
$updateString = $this->reverseSuperPowers($updateString, $string, $useStatements);
$updateString = $this->reverseJoomlaPowers($updateString, $string, $useStatements);
// reverse engineer the language strings
$updateString = $this->reverseLanguage($updateString, $string, $target);
@ -133,7 +143,30 @@
{
// only if we have use statements can we reverse engineer this
if ($useStatements !== null && ($powers = $this->power->reverse($string)) !== null &&
($reverse = $this->getReversePower($powers, $useStatements)) !== null)
($reverse = $this->getReversePower($powers, $useStatements, 'Super')) !== null)
{
return $this->placeholder->update($updateString, $reverse);
}
return $updateString;
}
/**
* Set the joomla powers keys for the reveres process
*
* @param string $updateString The string to update
* @param string $string The string to use for super power update
* @param array|null $useStatements The file use statements (needed for super powers)
*
* @return string
* @since 3.2.0
*/
protected function reverseJoomlaPowers(string $updateString, string $string,
?array $useStatements): string
{
// only if we have use statements can we reverse engineer this
if ($useStatements !== null && ($powers = $this->joomla->reverse($string)) !== null &&
($reverse = $this->getReversePower($powers, $useStatements, 'Joomla')) !== null)
{
return $this->placeholder->update($updateString, $reverse);
}
@ -146,11 +179,12 @@
*
* @param array $powers The powers found in the database text
* @param array $useStatements The file use statements
* @param string $target The power target type
*
* @return array|null
* @since 3.2.0
*/
protected function getReversePower(array $powers, array $useStatements): ?array
protected function getReversePower(array $powers, array $useStatements, string $target): ?array
{
$matching_statements = [];
foreach ($useStatements as $use_statement)
@ -179,7 +213,7 @@
{
$guid = array_search($namespace, $powers);
$matching_statements[$class_name] =
'Super_'.'_'.'_' . str_replace('-', '_', $guid) . '_'.'_'.'_Power';
$target . '_'.'_'.'_' . str_replace('-', '_', $guid) . '_'.'_'.'_Power';
}
}

View File

@ -45,6 +45,10 @@
"use_selection8": {
"use": "eeb03266-22fd-45bb-953a-961bb6be3a54",
"as": "Power"
},
"use_selection9": {
"use": "82505f3f-297f-4d75-a581-929ab3e93689",
"as": "JoomlaPower"
}
},
"namespace": "VDM\\Joomla\\Componentbuilder.Compiler.Placeholder.Reverse",