update 2024-05-23 10:43:13 #7

Merged
Llewellyn merged 1 commits from dev into master 2024-05-23 08:44:05 +00:00
8 changed files with 262 additions and 115 deletions

View File

@ -12,7 +12,7 @@
namespace VDM\Joomla\Utilities; namespace VDM\Joomla\Utilities;
use Joomla\CMS\Filter\InputFilter; use Joomla\Filter\InputFilter;
use Joomla\CMS\Language\Language; use Joomla\CMS\Language\Language;
use VDM\Joomla\Utilities\Component\Helper; use VDM\Joomla\Utilities\Component\Helper;

View File

@ -22,6 +22,6 @@
"namespace": "[[[NamespacePrefix]]]\\Joomla\\Utilities.StringHelper", "namespace": "[[[NamespacePrefix]]]\\Joomla\\Utilities.StringHelper",
"description": "Some string tricks\r\n\r\n@since 3.0.9", "description": "Some string tricks\r\n\r\n@since 3.0.9",
"licensing_template": "\/**\r\n * @package Joomla.Component.Builder\r\n *\r\n * @created 3rd September, 2020\r\n * @author Llewellyn van der Merwe <https:\/\/dev.vdm.io>\r\n * @git Joomla Component Builder <https:\/\/git.vdm.dev\/joomla\/Component-Builder>\r\n * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.\r\n * @license GNU General Public License version 2 or later; see LICENSE.txt\r\n *\/\r\n", "licensing_template": "\/**\r\n * @package Joomla.Component.Builder\r\n *\r\n * @created 3rd September, 2020\r\n * @author Llewellyn van der Merwe <https:\/\/dev.vdm.io>\r\n * @git Joomla Component Builder <https:\/\/git.vdm.dev\/joomla\/Component-Builder>\r\n * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.\r\n * @license GNU General Public License version 2 or later; see LICENSE.txt\r\n *\/\r\n",
"head": "use Joomla\\CMS\\Filter\\InputFilter;\r\nuse Joomla\\CMS\\Language\\Language;", "head": "use Joomla\\Filter\\InputFilter;\r\nuse Joomla\\CMS\\Language\\Language;",
"composer": "" "composer": ""
} }

View File

@ -13,6 +13,7 @@
abstract ActiveRegistry #Orange { abstract ActiveRegistry #Orange {
# array $active # array $active
# bool $addAsArray # bool $addAsArray
# bool $uniqueArray
+ isActive() : bool + isActive() : bool
+ allActive() : array + allActive() : array
+ setActive(mixed $value, $keys) : void + setActive(mixed $value, $keys) : void

View File

@ -40,6 +40,14 @@ abstract class ActiveRegistry implements Activeregistryinterface
**/ **/
protected bool $addAsArray = false; protected bool $addAsArray = false;
/**
* Base switch to keep array values unique
*
* @var boolean
* @since 3.2.2
**/
protected bool $uniqueArray = false;
/** /**
* Check if the registry has any content. * Check if the registry has any content.
* *
@ -157,7 +165,16 @@ abstract class ActiveRegistry implements Activeregistryinterface
// Convert to array if it's not already an array // Convert to array if it's not already an array
$array = [$array]; $array = [$array];
} }
$array[] = $value;
if ($this->uniqueArray && in_array($value, $array))
{
// we do nothing
return;
}
else
{
$array[] = $value;
}
} }
else else
{ {

View File

@ -14,6 +14,14 @@
**/ **/
protected bool $addAsArray = false; protected bool $addAsArray = false;
/**
* Base switch to keep array values unique
*
* @var boolean
* @since 3.2.2
**/
protected bool $uniqueArray = false;
/** /**
* Check if the registry has any content. * Check if the registry has any content.
* *
@ -131,7 +139,16 @@
// Convert to array if it's not already an array // Convert to array if it's not already an array
$array = [$array]; $array = [$array];
} }
$array[] = $value;
if ($this->uniqueArray && in_array($value, $array))
{
// we do nothing
return;
}
else
{
$array[] = $value;
}
} }
else else
{ {

View File

@ -30,10 +30,10 @@ abstract Schema #Orange {
# addMissingColumns(string $table, array $columns) : void # addMissingColumns(string $table, array $columns) : void
# checkColumnsDataType(string $table, array $columns) : void # checkColumnsDataType(string $table, array $columns) : void
# getColumnDefinition(string $table, string $field) : ?string # getColumnDefinition(string $table, string $field) : ?string
# checkDefault(string $table, string $column) : void # checkDefault(string $table, string $column) : bool
# updateColumnsDataType(string $table, array $columns) : void # updateColumnsDataType(string $table, array $columns) : void
# getTable(string $table) : string # getTable(string $table) : string
isDataTypeChangeSignificant(string $currentType, string $expectedType) : bool # isDataTypeChangeSignificant(string $currentType, string $expectedType) : bool
# adjustExistingDefaults(string $table, string $column, ...) : bool # adjustExistingDefaults(string $table, string $column, ...) : bool
# updateColumnDataType(string $updateString, string $table, ...) : bool # updateColumnDataType(string $updateString, string $table, ...) : bool
# getTableKeys() : string # getTableKeys() : string
@ -41,6 +41,7 @@ abstract Schema #Orange {
# setUniqueKey(array $column) : void # setUniqueKey(array $column) : void
# setKey(array $column) : void # setKey(array $column) : void
# getDefaultValue(string $type, ?string $defaultValue, ...) : string # getDefaultValue(string $type, ?string $defaultValue, ...) : string
# quote(mixed $value) : mixed
} }
note right of Schema::__construct note right of Schema::__construct
@ -117,7 +118,7 @@ note right of Schema::checkDefault
Check and Update the default values if needed, including existing data adjustments Check and Update the default values if needed, including existing data adjustments
since: 3.2.1 since: 3.2.1
return: void return: bool
end note end note
note left of Schema::updateColumnsDataType note left of Schema::updateColumnsDataType
@ -138,8 +139,8 @@ note left of Schema::isDataTypeChangeSignificant
Determines if the change in data type between two definitions is significant. Determines if the change in data type between two definitions is significant.
This function checks if there's a significant difference between the current This function checks if there's a significant difference between the current
data type and the expected data type that would require updating the database schema. data type and the expected data type that would require updating the database schema.
It ignores size and other modifiers for certain data types where MySQL considers It ignores display width for numeric types where MySQL considers these attributes
these attributes irrelevant for storage. irrelevant for storage but considers size and other modifiers for types like VARCHAR.
since: 3.2.1 since: 3.2.1
return: bool return: bool
@ -212,6 +213,13 @@ defaults by either leaving them unset or applying the provided default, properly
?string $defaultValue ?string $defaultValue
bool $pure = false bool $pure = false
end note end note
note left of Schema::quote
Set a value based on data type
since: 3.2.0
return: mixed
end note
@enduml @enduml
``` ```

View File

@ -342,9 +342,16 @@ abstract class Schema implements SchemaInterface
'current' => $current->Type, 'current' => $current->Type,
'expected' => $expected['type'] 'expected' => $expected['type']
]; ];
}
// check if update of default values is needed // check if update of default values is needed
$this->checkDefault($table, $column); if ($this->checkDefault($table, $column) && !isset($requireUpdate[$column]))
{
$requireUpdate[$column] = [
'column' => $column,
'current' => $current->Type,
'expected' => $expected['type']
];
} }
} }
@ -402,29 +409,23 @@ abstract class Schema implements SchemaInterface
* @param string $table The table to update. * @param string $table The table to update.
* @param string $column The column/field to check. * @param string $column The column/field to check.
* *
* @return void * @return bool
* @since 3.2.1 * @since 3.2.1
*/ */
protected function checkDefault(string $table, string $column): void protected function checkDefault(string $table, string $column): bool
{ {
// Retrieve the expected column configuration // Retrieve the expected column configuration
$expected = $this->table->get($table, $column, 'db'); $expected = $this->table->get($table, $column, 'db');
// Skip updates if the column is auto_increment // Skip updates if the column is auto_increment
if (isset($expected['auto_increment']) && $expected['auto_increment']) if (isset($expected['auto_increment']) && $expected['auto_increment'] === true)
{ {
return; return false;
} }
// Retrieve the current column configuration // Retrieve the current column configuration
$current = $this->columns[$column]; $current = $this->columns[$column];
// Check if default should be empty and current default is null, skip processing
if (strtoupper($expected['default']) === 'EMPTY' && $current->Default === NULL)
{
return;
}
// Determine the new default value based on the expected settings // Determine the new default value based on the expected settings
$type = $expected['type'] ?? 'TEXT'; $type = $expected['type'] ?? 'TEXT';
$db_default = isset($expected['default']) ? $expected['default'] : null; $db_default = isset($expected['default']) ? $expected['default'] : null;
@ -434,7 +435,17 @@ abstract class Schema implements SchemaInterface
if (is_numeric($newDefault) && $this->adjustExistingDefaults($table, $column, $current->Default, $newDefault)) if (is_numeric($newDefault) && $this->adjustExistingDefaults($table, $column, $current->Default, $newDefault))
{ {
$this->success[] = "Success: updated the ($column) defaults in $table table."; $this->success[] = "Success: updated the ($column) defaults in $table table.";
return true;
} }
if (is_string($expected['default']) && strtoupper($expected['default']) === 'EMPTY' &&
is_string($current->Default) && strpos($current->Default, 'EMPTY') !== false)
{
return true; // little fix
}
return false;
} }
/** /**
@ -486,56 +497,52 @@ abstract class Schema implements SchemaInterface
* *
* This function checks if there's a significant difference between the current * This function checks if there's a significant difference between the current
* data type and the expected data type that would require updating the database schema. * data type and the expected data type that would require updating the database schema.
* It ignores size and other modifiers for certain data types where MySQL considers * It ignores display width for numeric types where MySQL considers these attributes
* these attributes irrelevant for storage. * irrelevant for storage but considers size and other modifiers for types like VARCHAR.
* *
* @param string $currentType The current data type from the database schema. * @param string $currentType The current data type from the database schema.
* @param string $expectedType The expected data type to validate against. * @param string $expectedType The expected data type to validate against.
* *
* @return bool Returns true if the data type change is significant, otherwise false. * @return bool Returns true if the data type change is significant, otherwise false.
* @since 3.2.1 * @since 3.2.1
*/ */
function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool protected function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool
{ {
// we only do this for Joomla 4+ // Normalize both input types to lowercase for case-insensitive comparison
if ($this->currentVersion != 3) $currentType = strtolower($currentType);
$expectedType = strtolower($expectedType);
// Regex to extract the base data type and numeric parameters with named groups
$typePattern = '/^(?<datatype>\w+)(\((?<params>\d+(,\d+)?)\))?/';
// Match types and parameters
preg_match($typePattern, $currentType, $currentMatches);
preg_match($typePattern, $expectedType, $expectedMatches);
// Compare base types
if ($currentMatches['datatype'] !== $expectedMatches['datatype'])
{ {
// Normalize both input types to lowercase for case-insensitive comparison return true; // Base types differ
$currentType = strtolower($currentType);
$expectedType = strtolower($expectedType);
// Define types where size or other modifiers are irrelevant
$sizeIrrelevantTypes = [
'int', 'tinyint', 'smallint', 'mediumint', 'bigint', // Standard integer types
'int unsigned', 'tinyint unsigned', 'smallint unsigned', 'mediumint unsigned', 'bigint unsigned', // Unsigned integer types
];
// Check if the type involves size-irrelevant types
foreach ($sizeIrrelevantTypes as $type)
{
if (strpos($expectedType, $type) !== false)
{
// Remove any numeric sizes and modifiers for comparison
$pattern = '/\(\d+\)|unsigned|\s*/';
$cleanCurrentType = preg_replace($pattern, '', $currentType);
$cleanExpectedType = preg_replace($pattern, '', $expectedType);
// Compare the cleaned types
if ($cleanCurrentType === $cleanExpectedType)
{
return false; // No significant change
}
}
}
} }
// Perform a standard case-insensitive comparison for other types // Define types where size and other modifiers are irrelevant
if (strcasecmp($currentType, $expectedType) == 0) $sizeIrrelevantTypes = [
'int', 'tinyint', 'smallint', 'mediumint', 'bigint',
'float', 'double', 'decimal', 'numeric' // Numeric types where display width is irrelevant
];
// If the type is not in the size irrelevant list, compare full definitions
if (!in_array($currentMatches['datatype'], $sizeIrrelevantTypes))
{ {
return false; // No significant change return $currentType !== $expectedType; // Use full definition for types where size matters
} }
return true; // Significant datatype change detected // For size irrelevant types, only compare base type, ignoring size and unsigned
$currentBaseType = preg_replace('/\(\d+(,\d+)?\)|unsigned/', '', $currentType);
$expectedBaseType = preg_replace('/\(\d+(,\d+)?\)|unsigned/', '', $expectedType);
// Perform a final comparison for numeric types ignoring size
return $currentBaseType !== $expectedBaseType;
} }
/** /**
@ -684,7 +691,7 @@ abstract class Schema implements SchemaInterface
*/ */
protected function getDefaultValue(string $type, ?string $defaultValue, bool $pure = false): string protected function getDefaultValue(string $type, ?string $defaultValue, bool $pure = false): string
{ {
if ($defaultValue === null || strtoupper($defaultValue) === 'EMPTY') if ($defaultValue === null)
{ {
return ''; return '';
} }
@ -696,7 +703,52 @@ abstract class Schema implements SchemaInterface
} }
// Apply and quote the default value // Apply and quote the default value
return $pure ? $defaultValue : " DEFAULT " . $this->db->quote($defaultValue); $sql_default = $this->quote($defaultValue);
return $pure ? $defaultValue : " DEFAULT $sql_default";
}
/**
* Set a value based on data type
*
* @param mixed $value The value to set
*
* @return mixed
* @since 3.2.0
**/
protected function quote($value)
{
if ($value === null) // hmm the null does pose an issue (will keep an eye on this)
{
return 'NULL';
}
if (is_string($value) && strtoupper($value) === 'EMPTY')
{
return "''";
}
elseif (is_numeric($value))
{
if (filter_var($value, FILTER_VALIDATE_INT))
{
return (int) $value;
}
elseif (filter_var($value, FILTER_VALIDATE_FLOAT))
{
return (float) $value;
}
}
elseif (is_bool($value)) // not sure if this will work well (but its correct)
{
return $value ? 'TRUE' : 'FALSE';
}
// For date and datetime values
elseif ($value instanceof \DateTime)
{
return $this->db->quote($value->format('Y-m-d H:i:s'));
}
// For other data types, just escape it
return $this->db->quote($value);
} }
} }

View File

@ -315,9 +315,16 @@
'current' => $current->Type, 'current' => $current->Type,
'expected' => $expected['type'] 'expected' => $expected['type']
]; ];
}
// check if update of default values is needed // check if update of default values is needed
$this->checkDefault($table, $column); if ($this->checkDefault($table, $column) && !isset($requireUpdate[$column]))
{
$requireUpdate[$column] = [
'column' => $column,
'current' => $current->Type,
'expected' => $expected['type']
];
} }
} }
@ -375,29 +382,23 @@
* @param string $table The table to update. * @param string $table The table to update.
* @param string $column The column/field to check. * @param string $column The column/field to check.
* *
* @return void * @return bool
* @since 3.2.1 * @since 3.2.1
*/ */
protected function checkDefault(string $table, string $column): void protected function checkDefault(string $table, string $column): bool
{ {
// Retrieve the expected column configuration // Retrieve the expected column configuration
$expected = $this->table->get($table, $column, 'db'); $expected = $this->table->get($table, $column, 'db');
// Skip updates if the column is auto_increment // Skip updates if the column is auto_increment
if (isset($expected['auto_increment']) && $expected['auto_increment']) if (isset($expected['auto_increment']) && $expected['auto_increment'] === true)
{ {
return; return false;
} }
// Retrieve the current column configuration // Retrieve the current column configuration
$current = $this->columns[$column]; $current = $this->columns[$column];
// Check if default should be empty and current default is null, skip processing
if (strtoupper($expected['default']) === 'EMPTY' && $current->Default === NULL)
{
return;
}
// Determine the new default value based on the expected settings // Determine the new default value based on the expected settings
$type = $expected['type'] ?? 'TEXT'; $type = $expected['type'] ?? 'TEXT';
$db_default = isset($expected['default']) ? $expected['default'] : null; $db_default = isset($expected['default']) ? $expected['default'] : null;
@ -407,7 +408,17 @@
if (is_numeric($newDefault) && $this->adjustExistingDefaults($table, $column, $current->Default, $newDefault)) if (is_numeric($newDefault) && $this->adjustExistingDefaults($table, $column, $current->Default, $newDefault))
{ {
$this->success[] = "Success: updated the ($column) defaults in $table table."; $this->success[] = "Success: updated the ($column) defaults in $table table.";
return true;
} }
if (is_string($expected['default']) && strtoupper($expected['default']) === 'EMPTY' &&
is_string($current->Default) && strpos($current->Default, 'EMPTY') !== false)
{
return true; // little fix
}
return false;
} }
/** /**
@ -459,56 +470,52 @@
* *
* This function checks if there's a significant difference between the current * This function checks if there's a significant difference between the current
* data type and the expected data type that would require updating the database schema. * data type and the expected data type that would require updating the database schema.
* It ignores size and other modifiers for certain data types where MySQL considers * It ignores display width for numeric types where MySQL considers these attributes
* these attributes irrelevant for storage. * irrelevant for storage but considers size and other modifiers for types like VARCHAR.
* *
* @param string $currentType The current data type from the database schema. * @param string $currentType The current data type from the database schema.
* @param string $expectedType The expected data type to validate against. * @param string $expectedType The expected data type to validate against.
* *
* @return bool Returns true if the data type change is significant, otherwise false. * @return bool Returns true if the data type change is significant, otherwise false.
* @since 3.2.1 * @since 3.2.1
*/ */
function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool protected function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool
{ {
// we only do this for Joomla 4+ // Normalize both input types to lowercase for case-insensitive comparison
if ($this->currentVersion != 3) $currentType = strtolower($currentType);
$expectedType = strtolower($expectedType);
// Regex to extract the base data type and numeric parameters with named groups
$typePattern = '/^(?<datatype>\w+)(\((?<params>\d+(,\d+)?)\))?/';
// Match types and parameters
preg_match($typePattern, $currentType, $currentMatches);
preg_match($typePattern, $expectedType, $expectedMatches);
// Compare base types
if ($currentMatches['datatype'] !== $expectedMatches['datatype'])
{ {
// Normalize both input types to lowercase for case-insensitive comparison return true; // Base types differ
$currentType = strtolower($currentType);
$expectedType = strtolower($expectedType);
// Define types where size or other modifiers are irrelevant
$sizeIrrelevantTypes = [
'int', 'tinyint', 'smallint', 'mediumint', 'bigint', // Standard integer types
'int unsigned', 'tinyint unsigned', 'smallint unsigned', 'mediumint unsigned', 'bigint unsigned', // Unsigned integer types
];
// Check if the type involves size-irrelevant types
foreach ($sizeIrrelevantTypes as $type)
{
if (strpos($expectedType, $type) !== false)
{
// Remove any numeric sizes and modifiers for comparison
$pattern = '/\(\d+\)|unsigned|\s*/';
$cleanCurrentType = preg_replace($pattern, '', $currentType);
$cleanExpectedType = preg_replace($pattern, '', $expectedType);
// Compare the cleaned types
if ($cleanCurrentType === $cleanExpectedType)
{
return false; // No significant change
}
}
}
} }
// Perform a standard case-insensitive comparison for other types // Define types where size and other modifiers are irrelevant
if (strcasecmp($currentType, $expectedType) == 0) $sizeIrrelevantTypes = [
'int', 'tinyint', 'smallint', 'mediumint', 'bigint',
'float', 'double', 'decimal', 'numeric' // Numeric types where display width is irrelevant
];
// If the type is not in the size irrelevant list, compare full definitions
if (!in_array($currentMatches['datatype'], $sizeIrrelevantTypes))
{ {
return false; // No significant change return $currentType !== $expectedType; // Use full definition for types where size matters
} }
return true; // Significant datatype change detected // For size irrelevant types, only compare base type, ignoring size and unsigned
$currentBaseType = preg_replace('/\(\d+(,\d+)?\)|unsigned/', '', $currentType);
$expectedBaseType = preg_replace('/\(\d+(,\d+)?\)|unsigned/', '', $expectedType);
// Perform a final comparison for numeric types ignoring size
return $currentBaseType !== $expectedBaseType;
} }
/** /**
@ -657,7 +664,7 @@
*/ */
protected function getDefaultValue(string $type, ?string $defaultValue, bool $pure = false): string protected function getDefaultValue(string $type, ?string $defaultValue, bool $pure = false): string
{ {
if ($defaultValue === null || strtoupper($defaultValue) === 'EMPTY') if ($defaultValue === null)
{ {
return ''; return '';
} }
@ -669,5 +676,50 @@
} }
// Apply and quote the default value // Apply and quote the default value
return $pure ? $defaultValue : " DEFAULT " . $this->db->quote($defaultValue); $sql_default = $this->quote($defaultValue);
return $pure ? $defaultValue : " DEFAULT $sql_default";
}
/**
* Set a value based on data type
*
* @param mixed $value The value to set
*
* @return mixed
* @since 3.2.0
**/
protected function quote($value)
{
if ($value === null) // hmm the null does pose an issue (will keep an eye on this)
{
return 'NULL';
}
if (is_string($value) && strtoupper($value) === 'EMPTY')
{
return "''";
}
elseif (is_numeric($value))
{
if (filter_var($value, FILTER_VALIDATE_INT))
{
return (int) $value;
}
elseif (filter_var($value, FILTER_VALIDATE_FLOAT))
{
return (float) $value;
}
}
elseif (is_bool($value)) // not sure if this will work well (but its correct)
{
return $value ? 'TRUE' : 'FALSE';
}
// For date and datetime values
elseif ($value instanceof \DateTime)
{
return $this->db->quote($value->format('Y-m-d H:i:s'));
}
// For other data types, just escape it
return $this->db->quote($value);
} }