From 1ca58eedb8af6dc81bc6fd9baaa2e3c0b362f612 Mon Sep 17 00:00:00 2001 From: aB0t Date: Thu, 23 May 2024 10:43:19 +0200 Subject: [PATCH] update 2024-05-23 10:43:13 --- .../code.php | 2 +- .../settings.json | 2 +- .../README.md | 1 + .../code.php | 19 ++- .../code.power | 19 ++- .../README.md | 18 +- .../code.php | 158 ++++++++++++------ .../code.power | 158 ++++++++++++------ 8 files changed, 262 insertions(+), 115 deletions(-) diff --git a/src/1f28cb53-60d9-4db1-b517-3c7dc6b429ef/code.php b/src/1f28cb53-60d9-4db1-b517-3c7dc6b429ef/code.php index fad2335..5d79075 100644 --- a/src/1f28cb53-60d9-4db1-b517-3c7dc6b429ef/code.php +++ b/src/1f28cb53-60d9-4db1-b517-3c7dc6b429ef/code.php @@ -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; diff --git a/src/1f28cb53-60d9-4db1-b517-3c7dc6b429ef/settings.json b/src/1f28cb53-60d9-4db1-b517-3c7dc6b429ef/settings.json index 5fcef85..82a2a70 100644 --- a/src/1f28cb53-60d9-4db1-b517-3c7dc6b429ef/settings.json +++ b/src/1f28cb53-60d9-4db1-b517-3c7dc6b429ef/settings.json @@ -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 \r\n * @git 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": "" } \ No newline at end of file diff --git a/src/43134867-5cb8-4280-9be8-309fd2fd135f/README.md b/src/43134867-5cb8-4280-9be8-309fd2fd135f/README.md index b6eada5..4a5d637 100644 --- a/src/43134867-5cb8-4280-9be8-309fd2fd135f/README.md +++ b/src/43134867-5cb8-4280-9be8-309fd2fd135f/README.md @@ -13,6 +13,7 @@ abstract ActiveRegistry #Orange { # array $active # bool $addAsArray + # bool $uniqueArray + isActive() : bool + allActive() : array + setActive(mixed $value, $keys) : void diff --git a/src/43134867-5cb8-4280-9be8-309fd2fd135f/code.php b/src/43134867-5cb8-4280-9be8-309fd2fd135f/code.php index 861fcf9..e714bd2 100644 --- a/src/43134867-5cb8-4280-9be8-309fd2fd135f/code.php +++ b/src/43134867-5cb8-4280-9be8-309fd2fd135f/code.php @@ -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,7 +165,16 @@ abstract class ActiveRegistry implements Activeregistryinterface // Convert to array if it's not already an array $array = [$array]; } - $array[] = $value; + + if ($this->uniqueArray && in_array($value, $array)) + { + // we do nothing + return; + } + else + { + $array[] = $value; + } } else { diff --git a/src/43134867-5cb8-4280-9be8-309fd2fd135f/code.power b/src/43134867-5cb8-4280-9be8-309fd2fd135f/code.power index 01684b7..4c8bf95 100644 --- a/src/43134867-5cb8-4280-9be8-309fd2fd135f/code.power +++ b/src/43134867-5cb8-4280-9be8-309fd2fd135f/code.power @@ -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,7 +139,16 @@ // Convert to array if it's not already an array $array = [$array]; } - $array[] = $value; + + if ($this->uniqueArray && in_array($value, $array)) + { + // we do nothing + return; + } + else + { + $array[] = $value; + } } else { diff --git a/src/f3c04c28-bce4-422e-be93-7d163e4e342b/README.md b/src/f3c04c28-bce4-422e-be93-7d163e4e342b/README.md index d1fa0c7..d641a45 100644 --- a/src/f3c04c28-bce4-422e-be93-7d163e4e342b/README.md +++ b/src/f3c04c28-bce4-422e-be93-7d163e4e342b/README.md @@ -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 @@ -212,6 +213,13 @@ defaults by either leaving them unset or applying the provided default, properly ?string $defaultValue 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 ``` diff --git a/src/f3c04c28-bce4-422e-be93-7d163e4e342b/code.php b/src/f3c04c28-bce4-422e-be93-7d163e4e342b/code.php index 53c0591..694bbc9 100644 --- a/src/f3c04c28-bce4-422e-be93-7d163e4e342b/code.php +++ b/src/f3c04c28-bce4-422e-be93-7d163e4e342b/code.php @@ -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); + // check if update of default values is needed + 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,56 +497,52 @@ 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. + * @param string $currentType The current data type from the database schema. + * @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 */ - function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool + protected function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool { - // we only do this for Joomla 4+ - if ($this->currentVersion != 3) + // Normalize both input types to lowercase for case-insensitive comparison + $currentType = strtolower($currentType); + $expectedType = strtolower($expectedType); + + // Regex to extract the base data type and numeric parameters with named groups + $typePattern = '/^(?\w+)(\((?\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 - $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 - } - } - } + return true; // Base types differ } - // Perform a standard case-insensitive comparison for other types - if (strcasecmp($currentType, $expectedType) == 0) + // Define types where size and other modifiers are irrelevant + $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 { - 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); } } diff --git a/src/f3c04c28-bce4-422e-be93-7d163e4e342b/code.power b/src/f3c04c28-bce4-422e-be93-7d163e4e342b/code.power index e33b816..33941db 100644 --- a/src/f3c04c28-bce4-422e-be93-7d163e4e342b/code.power +++ b/src/f3c04c28-bce4-422e-be93-7d163e4e342b/code.power @@ -315,9 +315,16 @@ 'current' => $current->Type, 'expected' => $expected['type'] ]; + } - // check if update of default values is needed - $this->checkDefault($table, $column); + // check if update of default values is needed + 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,56 +470,52 @@ * * 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. + * @param string $currentType The current data type from the database schema. + * @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 */ - function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool + protected function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool { - // we only do this for Joomla 4+ - if ($this->currentVersion != 3) + // Normalize both input types to lowercase for case-insensitive comparison + $currentType = strtolower($currentType); + $expectedType = strtolower($expectedType); + + // Regex to extract the base data type and numeric parameters with named groups + $typePattern = '/^(?\w+)(\((?\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 - $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 - } - } - } + return true; // Base types differ } - // Perform a standard case-insensitive comparison for other types - if (strcasecmp($currentType, $expectedType) == 0) + // Define types where size and other modifiers are irrelevant + $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 { - 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); } \ No newline at end of file