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;
use Joomla\CMS\Filter\InputFilter;
use Joomla\Filter\InputFilter;
use Joomla\CMS\Language\Language;
use VDM\Joomla\Utilities\Component\Helper;

View File

@ -22,6 +22,6 @@
"namespace": "[[[NamespacePrefix]]]\\Joomla\\Utilities.StringHelper",
"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",
"head": "use Joomla\\CMS\\Filter\\InputFilter;\r\nuse Joomla\\CMS\\Language\\Language;",
"head": "use Joomla\\Filter\\InputFilter;\r\nuse Joomla\\CMS\\Language\\Language;",
"composer": ""
}

View File

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

View File

@ -40,6 +40,14 @@ abstract class ActiveRegistry implements Activeregistryinterface
**/
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.
*
@ -157,8 +165,17 @@ abstract class ActiveRegistry implements Activeregistryinterface
// Convert to array if it's not already an array
$array = [$array];
}
if ($this->uniqueArray && in_array($value, $array))
{
// we do nothing
return;
}
else
{
$array[] = $value;
}
}
else
{
if (is_string($value) || is_numeric($value))

View File

@ -14,6 +14,14 @@
**/
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.
*
@ -131,8 +139,17 @@
// Convert to array if it's not already an array
$array = [$array];
}
if ($this->uniqueArray && in_array($value, $array))
{
// we do nothing
return;
}
else
{
$array[] = $value;
}
}
else
{
if (is_string($value) || is_numeric($value))

View File

@ -30,10 +30,10 @@ abstract Schema #Orange {
# addMissingColumns(string $table, array $columns) : void
# checkColumnsDataType(string $table, array $columns) : void
# getColumnDefinition(string $table, string $field) : ?string
# checkDefault(string $table, string $column) : void
# checkDefault(string $table, string $column) : bool
# updateColumnsDataType(string $table, array $columns) : void
# getTable(string $table) : string
isDataTypeChangeSignificant(string $currentType, string $expectedType) : bool
# isDataTypeChangeSignificant(string $currentType, string $expectedType) : bool
# adjustExistingDefaults(string $table, string $column, ...) : bool
# updateColumnDataType(string $updateString, string $table, ...) : bool
# getTableKeys() : string
@ -41,6 +41,7 @@ abstract Schema #Orange {
# setUniqueKey(array $column) : void
# setKey(array $column) : void
# getDefaultValue(string $type, ?string $defaultValue, ...) : string
# quote(mixed $value) : mixed
}
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
since: 3.2.1
return: void
return: bool
end note
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.
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.
It ignores size and other modifiers for certain data types where MySQL considers
these attributes irrelevant for storage.
It ignores display width for numeric types where MySQL considers these attributes
irrelevant for storage but considers size and other modifiers for types like VARCHAR.
since: 3.2.1
return: bool
@ -213,6 +214,13 @@ defaults by either leaving them unset or applying the provided default, properly
bool $pure = false
end note
note left of Schema::quote
Set a value based on data type
since: 3.2.0
return: mixed
end note
@enduml
```

View File

@ -342,9 +342,16 @@ abstract class Schema implements SchemaInterface
'current' => $current->Type,
'expected' => $expected['type']
];
}
// 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 $column The column/field to check.
*
* @return void
* @return bool
* @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
$expected = $this->table->get($table, $column, 'db');
// 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
$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
$type = $expected['type'] ?? 'TEXT';
$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))
{
$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,8 +497,8 @@ abstract class Schema implements SchemaInterface
*
* 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.
* It ignores size and other modifiers for certain data types where MySQL considers
* these attributes irrelevant for storage.
* It ignores display width for numeric types where MySQL considers these attributes
* 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 $expectedType The expected data type to validate against.
@ -495,47 +506,43 @@ abstract class Schema implements SchemaInterface
* @return bool Returns true if the data type change is significant, otherwise false.
* @since 3.2.1
*/
function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool
{
// we only do this for Joomla 4+
if ($this->currentVersion != 3)
protected function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool
{
// Normalize both input types to lowercase for case-insensitive comparison
$currentType = strtolower($currentType);
$expectedType = strtolower($expectedType);
// Define types where size or other modifiers are irrelevant
// 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'])
{
return true; // Base types differ
}
// Define types where size and 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
'int', 'tinyint', 'smallint', 'mediumint', 'bigint',
'float', 'double', 'decimal', 'numeric' // Numeric types where display width is irrelevant
];
// Check if the type involves size-irrelevant types
foreach ($sizeIrrelevantTypes as $type)
// If the type is not in the size irrelevant list, compare full definitions
if (!in_array($currentMatches['datatype'], $sizeIrrelevantTypes))
{
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
}
}
}
return $currentType !== $expectedType; // Use full definition for types where size matters
}
// Perform a standard case-insensitive comparison for other types
if (strcasecmp($currentType, $expectedType) == 0)
{
return false; // No significant change
}
// 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);
return true; // Significant datatype change detected
// 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
{
if ($defaultValue === null || strtoupper($defaultValue) === 'EMPTY')
if ($defaultValue === null)
{
return '';
}
@ -696,7 +703,52 @@ abstract class Schema implements SchemaInterface
}
// 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,
'expected' => $expected['type']
];
}
// 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 $column The column/field to check.
*
* @return void
* @return bool
* @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
$expected = $this->table->get($table, $column, 'db');
// 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
$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
$type = $expected['type'] ?? 'TEXT';
$db_default = isset($expected['default']) ? $expected['default'] : null;
@ -407,7 +408,17 @@
if (is_numeric($newDefault) && $this->adjustExistingDefaults($table, $column, $current->Default, $newDefault))
{
$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,8 +470,8 @@
*
* 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.
* It ignores size and other modifiers for certain data types where MySQL considers
* these attributes irrelevant for storage.
* It ignores display width for numeric types where MySQL considers these attributes
* 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 $expectedType The expected data type to validate against.
@ -468,47 +479,43 @@
* @return bool Returns true if the data type change is significant, otherwise false.
* @since 3.2.1
*/
function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool
{
// we only do this for Joomla 4+
if ($this->currentVersion != 3)
protected function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool
{
// Normalize both input types to lowercase for case-insensitive comparison
$currentType = strtolower($currentType);
$expectedType = strtolower($expectedType);
// Define types where size or other modifiers are irrelevant
// 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'])
{
return true; // Base types differ
}
// Define types where size and 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
'int', 'tinyint', 'smallint', 'mediumint', 'bigint',
'float', 'double', 'decimal', 'numeric' // Numeric types where display width is irrelevant
];
// Check if the type involves size-irrelevant types
foreach ($sizeIrrelevantTypes as $type)
// If the type is not in the size irrelevant list, compare full definitions
if (!in_array($currentMatches['datatype'], $sizeIrrelevantTypes))
{
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
}
}
}
return $currentType !== $expectedType; // Use full definition for types where size matters
}
// Perform a standard case-insensitive comparison for other types
if (strcasecmp($currentType, $expectedType) == 0)
{
return false; // No significant change
}
// 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);
return true; // Significant datatype change detected
// 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
{
if ($defaultValue === null || strtoupper($defaultValue) === 'EMPTY')
if ($defaultValue === null)
{
return '';
}
@ -669,5 +676,50 @@
}
// 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);
}