diff --git a/README.md b/README.md index b806ebd..85561f4 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ This repository contains an index (see below) of all the approved powers within - **final class Items** | [Details](src/21bca8a4-5b28-41c4-843e-8097f0ba7cca) | [Code](src/21bca8a4-5b28-41c4-843e-8097f0ba7cca/code.php) | [Settings](src/21bca8a4-5b28-41c4-843e-8097f0ba7cca/settings.json) | SPK: `Super---21bca8a4_5b28_41c4_843e_8097f0ba7cca---Power` - **final class MultiSubform** | [Details](src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9) | [Code](src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/code.php) | [Settings](src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/settings.json) | SPK: `Super---e0198c3f_777a_4a0b_87b7_e6a198afc8f9---Power` - **final class Subform** | [Details](src/85785701-07b2-4f81-bc1e-0f423700c254) | [Code](src/85785701-07b2-4f81-bc1e-0f423700c254/code.php) | [Settings](src/85785701-07b2-4f81-bc1e-0f423700c254/settings.json) | SPK: `Super---85785701_07b2_4f81_bc1e_0f423700c254---Power` + - **final class UsersSubform** | [Details](src/46b98346-ec98-42b3-a393-96c7d1282b1c) | [Code](src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.php) | [Settings](src/46b98346-ec98-42b3-a393-96c7d1282b1c/settings.json) | SPK: `Super---46b98346_ec98_42b3_a393_96c7d1282b1c---Power` + - **trait Guid** | [Details](src/5acded67-0e3d-4c6b-a6ea-b533b076de0c) | [Code](src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/code.php) | [Settings](src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/settings.json) | SPK: `Super---5acded67_0e3d_4c6b_a6ea_b533b076de0c---Power` - **Namespace**: [VDM\Joomla\Database](#vdm-joomla-database) - **final class Delete** | [Details](src/92291f1f-f248-4ec0-9f2a-3d47c49eeac1) | [Code](src/92291f1f-f248-4ec0-9f2a-3d47c49eeac1/code.php) | [Settings](src/92291f1f-f248-4ec0-9f2a-3d47c49eeac1/settings.json) | SPK: `Super---92291f1f_f248_4ec0_9f2a_3d47c49eeac1---Power` @@ -103,6 +105,9 @@ This repository contains an index (see below) of all the approved powers within - **final class Schema** | [Details](src/b3d2ec33-76d4-4c3b-bb2c-86ac14a221ce) | [Code](src/b3d2ec33-76d4-4c3b-bb2c-86ac14a221ce/code.php) | [Settings](src/b3d2ec33-76d4-4c3b-bb2c-86ac14a221ce/settings.json) | SPK: `Super---b3d2ec33_76d4_4c3b_bb2c_86ac14a221ce---Power` - **final class SchemaChecker** | [Details](src/709d7294-9a43-46e2-b64e-d16a16f0eab1) | [Code](src/709d7294-9a43-46e2-b64e-d16a16f0eab1/code.php) | [Settings](src/709d7294-9a43-46e2-b64e-d16a16f0eab1/settings.json) | SPK: `Super---709d7294_9a43_46e2_b64e_d16a16f0eab1---Power` +- **Namespace**: [VDM\Joomla\Componentbuilder\Utilities](#vdm-joomla-componentbuilder-utilities) + + - **abstract class UserHelper** | [Details](src/7832a726-87b6-4e95-887e-7b725d3fab8f) | [Code](src/7832a726-87b6-4e95-887e-7b725d3fab8f/code.php) | [Settings](src/7832a726-87b6-4e95-887e-7b725d3fab8f/settings.json) | SPK: `Super---7832a726_87b6_4e95_887e_7b725d3fab8f---Power` - **Namespace**: [VDM\Joomla\Data\Action](#vdm-joomla-data-action) - **class Delete** | [Details](src/3fc72954-a303-4cac-b53c-554be38b85e7) | [Code](src/3fc72954-a303-4cac-b53c-554be38b85e7/code.php) | [Settings](src/3fc72954-a303-4cac-b53c-554be38b85e7/settings.json) | SPK: `Super---3fc72954_a303_4cac_b53c_554be38b85e7---Power` @@ -112,6 +117,7 @@ This repository contains an index (see below) of all the approved powers within - **Namespace**: [VDM\Joomla\Interfaces\Data](#vdm-joomla-interfaces-data) - **interface DeleteInterface** | [Details](src/d8f9ba53-c490-4e8b-8e9f-6757224e069c) | [Code](src/d8f9ba53-c490-4e8b-8e9f-6757224e069c/code.php) | [Settings](src/d8f9ba53-c490-4e8b-8e9f-6757224e069c/settings.json) | SPK: `Super---d8f9ba53_c490_4e8b_8e9f_6757224e069c---Power` + - **interface GuidInterface** | [Details](src/576685fd-263c-46bb-9fdc-1f5eb234cbb6) | [Code](src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/code.php) | [Settings](src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/settings.json) | SPK: `Super---576685fd_263c_46bb_9fdc_1f5eb234cbb6---Power` - **interface InsertInterface** | [Details](src/03bbc8d5-86e8-4d2f-ae5f-0d44a4f7af13) | [Code](src/03bbc8d5-86e8-4d2f-ae5f-0d44a4f7af13/code.php) | [Settings](src/03bbc8d5-86e8-4d2f-ae5f-0d44a4f7af13/settings.json) | SPK: `Super---03bbc8d5_86e8_4d2f_ae5f_0d44a4f7af13---Power` - **interface ItemInterface** | [Details](src/05744dd3-4030-4cf8-8dda-a93ab809b473) | [Code](src/05744dd3-4030-4cf8-8dda-a93ab809b473/code.php) | [Settings](src/05744dd3-4030-4cf8-8dda-a93ab809b473/settings.json) | SPK: `Super---05744dd3_4030_4cf8_8dda_a93ab809b473---Power` - **interface ItemsInterface** | [Details](src/7212e4db-371f-4cfd-8122-32e9bb100d83) | [Code](src/7212e4db-371f-4cfd-8122-32e9bb100d83/code.php) | [Settings](src/7212e4db-371f-4cfd-8122-32e9bb100d83/settings.json) | SPK: `Super---7212e4db_371f_4cfd_8122_32e9bb100d83---Power` diff --git a/src/46b98346-ec98-42b3-a393-96c7d1282b1c/README.md b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/README.md new file mode 100644 index 0000000..620e57e --- /dev/null +++ b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/README.md @@ -0,0 +1,172 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# final class UsersSubform (Details) +> namespace: **VDM\Joomla\Data** + +```uml +@startuml +class UsersSubform << (F,LightGreen) >> #RoyalBlue { + # Items $items + # string $table + # array $user + + __construct(Items $items, ?string $table = null) + + table(string $table) : self + + get(string $linkValue, string $linkKey, ...) : ?array + + set(mixed $items, string $indexKey, ...) : bool + + getTable() : string + # initializeUserProperties() : void + - purge(array $items, string $indexKey, ...) : void + - getUsersDetails(array $items) : array + - getUserDetails(array $item) : void + - converter(array $items, array $keySet, ...) : array + - process(mixed $items, string $indexKey, ...) : array + - setUserDetails(array $item) : int +} + +note right of UsersSubform::__construct + Constructor. + + since: 3.2.2 +end note + +note left of UsersSubform::table + Set the current active table + + since: 3.2.2 + return: self +end note + +note right of UsersSubform::get + Get a subform items + + since: 3.2.2 + return: ?array + + arguments: + string $linkValue + string $linkKey + string $field + array $get +end note + +note left of UsersSubform::set + Set a subform items + + since: 3.2.2 + return: bool + + arguments: + mixed $items + string $indexKey + string $linkKey + string $linkValue +end note + +note right of UsersSubform::getTable + Get the current active table + + since: 3.2.2 + return: string +end note + +note left of UsersSubform::initializeUserProperties + Initializes the user properties. + + since: 5.0.2 + return: void +end note + +note right of UsersSubform::purge + Purge all items no longer in subform + + since: 3.2.2 + return: void + + arguments: + array $items + string $indexKey + string $linkKey + string $linkValue +end note + +note left of UsersSubform::getUsersDetails + Get the users details found in the user table. + + since: 5.0.2 + return: array +end note + +note right of UsersSubform::getUserDetails + Get the user details found in the user table. + + since: 5.0.2 + return: void +end note + +note left of UsersSubform::converter + Filters the specified keys from an array of objects or arrays, converts them to arrays, +and sets them by association with a specified key and an incrementing integer. + + since: 3.2.2 + return: array + + arguments: + array $items + array $keySet + string $field +end note + +note right of UsersSubform::process + Processes an array of arrays based on the specified key. + + since: 3.2.2 + return: array + + arguments: + mixed $items + string $indexKey + string $linkKey + string $linkValue +end note + +note left of UsersSubform::setUserDetails + Set the user details. + + since: 5.0.2 + return: int +end note + +@enduml +``` + +The Power feature in JCB allows you to write PHP classes and their implementations, making it easy to include them in your Joomla project. JCB handles linking, autoloading, namespacing, and folder structure creation for you. + +By using the SPK (Super Power Key) in your custom code (replacing the class name in your code with the SPK), JCB will automatically pull the power from the repository into your project. This makes it available in your JCB instance, allowing you to edit it and include the class in your generated Joomla component. + +JCB uses placeholders like [[[`NamespacePrefix`]]] and [[[`ComponentNamespace`]]] in namespacing to prevent collisions and improve reusability across different JCB systems. You can also set the **JCB powers path** globally or per component under the **Dynamic Integration** tab, providing flexibility and easy maintainability. + +To add this specific Power to your project in JCB: + +> simply use this SPK +``` +Super---46b98346_ec98_42b3_a393_96c7d1282b1c---Power +``` +> remember to replace the `---` with `___` to activate this Power in your code + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.php b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.php new file mode 100644 index 0000000..9ff8918 --- /dev/null +++ b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.php @@ -0,0 +1,403 @@ + + * @git Joomla Component Builder + * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace VDM\Joomla\Data; + + +use Joomla\CMS\Factory; +use VDM\Joomla\Interfaces\Data\ItemsInterface as Items; +use VDM\Joomla\Data\Guid; +use VDM\Joomla\Componentbuilder\Utilities\UserHelper; +use VDM\Joomla\Interfaces\Data\GuidInterface; +use VDM\Joomla\Interfaces\Data\SubformInterface; + + +/** + * CRUD the user data of any sub-form to another view (table) + * + * @since 5.0.2 + */ +final class UsersSubform implements GuidInterface, SubformInterface +{ + /** + * The Globally Unique Identifier. + * + * @since 5.0.2 + */ + use Guid; + + /** + * The Items Class. + * + * @var Items + * @since 3.2.2 + */ + protected Items $items; + + /** + * Table Name + * + * @var string + * @since 3.2.1 + */ + protected string $table; + + /** + * The user properties + * + * @var array + * @since 5.0.2 + */ + protected array $user; + + /** + * Constructor. + * + * @param Items $items The items Class. + * @param string|null $table The table name. + * + * @since 3.2.2 + */ + public function __construct(Items $items, ?string $table = null) + { + $this->items = $items; + if ($table !== null) + { + $this->table = $table; + } + + // Retrieve the user properties + $this->initializeUserProperties(); + } + + /** + * Set the current active table + * + * @param string $table The table that should be active + * + * @return self + * @since 3.2.2 + */ + public function table(string $table): self + { + $this->table = $table; + + return $this; + } + + /** + * Get a subform items + * + * @param string $linkValue The value of the link key in child table. + * @param string $linkKey The link key on which the items where linked in the child table. + * @param string $field The parent field name of the subform in the parent view. + * @param array $get The array get:set of the keys of each row in the subform. + * + * @return array|null The subform + * @since 3.2.2 + */ + public function get(string $linkValue, string $linkKey, string $field, array $get): ?array + { + if (($items = $this->items->table($this->getTable())->get([$linkValue], $linkKey)) !== null) + { + return $this->converter( + $this->getUsersDetails($items), + $get, + $field + ); + } + + return null; + } + + /** + * Set a subform items + * + * @param mixed $items The list of items from the subform to set + * @param string $indexKey The index key on which the items should be observed as it relates to insert/update/delete. + * @param string $linkKey The link key on which the items where linked in the child table. + * @param string $linkValue The value of the link key in child table. + * + * @return bool + * @since 3.2.2 + */ + public function set(mixed $items, string $indexKey, string $linkKey, string $linkValue): bool + { + $items = $this->process($items, $indexKey, $linkKey, $linkValue); + + $this->purge($items, $indexKey, $linkKey, $linkValue); + + if (empty($items)) + { + return true; // nothing to set (already purged) + } + + return $this->items->table($this->getTable())->set( + $items, $indexKey + ); + } + + /** + * Get the current active table + * + * @return string + * @since 3.2.2 + */ + public function getTable(): string + { + return $this->table; + } + + /** + * Initializes the user properties. + * + * @return void + * @since 5.0.2 + */ + protected function initializeUserProperties(): void + { + $user = UserHelper::getUserById(0); + + // Populate user properties array excluding the 'id' + foreach (get_object_vars($user) as $property => $value) + { + if ($property !== 'id') + { + $this->user[$property] = $property; + } + } + $this->user['password2'] = 'password2'; + } + + /** + * Purge all items no longer in subform + * + * @param array $items The list of items to set. + * @param string $indexKey The index key on which the items should be observed as it relates to insert/update/delete + * @param string $linkKey The link key on which the items where linked in the child table. + * @param string $linkValue The value of the link key in child table. + * + * @return void + * @since 3.2.2 + */ + private function purge(array $items, string $indexKey, string $linkKey, string $linkValue): void + { + // Get the current index values from the database + $currentIndexValues = $this->items->table($this->getTable())->values([$linkValue], $linkKey, $indexKey); + + if ($currentIndexValues !== null) + { + // Check if the items array is empty + if (empty($items)) + { + // Set activeIndexValues to an empty array if items is empty + $activeIndexValues = []; + } + else + { + // Extract the index values from the items array + $activeIndexValues = array_values(array_map(function($item) use ($indexKey) { + return $item[$indexKey] ?? null; + }, $items)); + } + + // Find the index values that are no longer in the items array + $inactiveIndexValues = array_diff($currentIndexValues, $activeIndexValues); + + // Delete the inactive index values + if (!empty($inactiveIndexValues)) + { + $this->items->table($this->getTable())->delete($inactiveIndexValues, $indexKey); + + // $this->deleteUsers($inactiveIndexValues); (soon) + } + } + } + + /** + * Get the users details found in the user table. + * + * @param array $items Array of objects or arrays to be filtered. + * + * @return array + * @since 5.0.2 + */ + private function getUsersDetails(array $items): array + { + foreach ($items as $index => &$item) + { + $item = (array) $item; + $this->getUserDetails($item); + } + + return $items; + } + + /** + * Get the user details found in the user table. + * + * @param array $item The user map array + * + * @return void + * @since 5.0.2 + */ + private function getUserDetails(array &$item): void + { + // Validate the user_id and ensure it is numeric and greater than 0 + if (empty($item['user_id']) || !is_numeric($item['user_id']) || $item['user_id'] <= 0) + { + return; + } + + // Retrieve the user by ID + $user = UserHelper::getUserById((int)$item['user_id']); + + // Verify if the user exists and the ID matches + if ($user && $user->id === (int) $item['user_id']) + { + // Iterate over public properties of the user object + foreach (get_object_vars($user) as $property => $value) + { + // Avoid overwriting the id in the item + if ($property !== 'id') + { + $item[$property] = $value; + } + } + } + } + + /** + * Filters the specified keys from an array of objects or arrays, converts them to arrays, + * and sets them by association with a specified key and an incrementing integer. + * + * @param array $items Array of objects or arrays to be filtered. + * @param array $keySet Array of keys to retain in each item. + * @param string $field The field prefix for the resulting associative array. + * + * @return array Array of filtered arrays set by association. + * @since 3.2.2 + */ + private function converter(array $items, array $keySet, string $field): array + { + /** + * Filters keys for a single item and converts it to an array. + * + * @param object|array $item The item to filter. + * @param array $keySet The keys to retain. + * + * @return array The filtered array. + * @since 3.2.2 + */ + $filterKeys = function ($item, array $keySet) { + $filteredArray = []; + foreach ($keySet as $key) { + if (is_object($item) && property_exists($item, $key)) { + $filteredArray[$key] = $item->{$key}; + } elseif (is_array($item) && array_key_exists($key, $item)) { + $filteredArray[$key] = $item[$key]; + } + } + return $filteredArray; + }; + + $result = []; + foreach ($items as $index => $item) + { + $filteredArray = $filterKeys($item, $keySet); + $result[$field . $index] = $filteredArray; + } + + return $result; + } + + /** + * Processes an array of arrays based on the specified key. + * + * @param mixed $items Array of arrays to be processed. + * @param string $indexKey The index key on which the items should be observed as it relates to insert/update/delete + * @param string $linkKey The link key on which the items where linked in the child table. + * @param string $linkValue The value of the link key in child table. + * + * @return array The processed array of arrays. + * @since 3.2.2 + */ + private function process($items, string $indexKey, string $linkKey, string $linkValue): array + { + $items = is_array($items) ? $items : []; + foreach ($items as &$item) + { + $value = $item[$indexKey] ?? ''; + switch ($indexKey) { + case 'guid': + if (empty($value)) + { + // set INDEX + $item[$indexKey] = $this->getGuid($indexKey); + } + break; + case 'id': + if (empty($value)) + { + $item[$indexKey] = 0; + } + break; + case 'user_id': + $item[$indexKey] = $this->setUserDetails($item); + break; + default: + // No action for other keys if empty + break; + } + // set LINK + $item[$linkKey] = $linkValue; + } + + return array_values($items); + } + + /** + * Set the user details. + * + * @param array $item The user details + * + * @return int + * @since 5.0.2 + */ + private function setUserDetails(array &$item): int + { + $user = []; + + // now load the user ID + if (isset($item['user_id']) && is_numeric($item['user_id']) && $item['user_id'] > 0) + { + $user['id'] = (int) $item['user_id']; + } + + foreach ($this->user as $property) + { + if (isset($item[$property])) + { + $user[$property] = $item[$property]; + unset($item[$property]); + } + } + + try { + return UserHelper::save($user); + } catch(\Exception $e) { + Factory::getApplication()->enqueueMessage($e->getMessage(), 'warning'); + } + + return 0; + } +} + diff --git a/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.power b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.power new file mode 100644 index 0000000..4c477e5 --- /dev/null +++ b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/code.power @@ -0,0 +1,372 @@ + /** + * The Globally Unique Identifier. + * + * @since 5.0.2 + */ + use Guid; + + /** + * The Items Class. + * + * @var Items + * @since 3.2.2 + */ + protected Items $items; + + /** + * Table Name + * + * @var string + * @since 3.2.1 + */ + protected string $table; + + /** + * The user properties + * + * @var array + * @since 5.0.2 + */ + protected array $user; + + /** + * Constructor. + * + * @param Items $items The items Class. + * @param string|null $table The table name. + * + * @since 3.2.2 + */ + public function __construct(Items $items, ?string $table = null) + { + $this->items = $items; + if ($table !== null) + { + $this->table = $table; + } + + // Retrieve the user properties + $this->initializeUserProperties(); + } + + /** + * Set the current active table + * + * @param string $table The table that should be active + * + * @return self + * @since 3.2.2 + */ + public function table(string $table): self + { + $this->table = $table; + + return $this; + } + + /** + * Get a subform items + * + * @param string $linkValue The value of the link key in child table. + * @param string $linkKey The link key on which the items where linked in the child table. + * @param string $field The parent field name of the subform in the parent view. + * @param array $get The array get:set of the keys of each row in the subform. + * + * @return array|null The subform + * @since 3.2.2 + */ + public function get(string $linkValue, string $linkKey, string $field, array $get): ?array + { + if (($items = $this->items->table($this->getTable())->get([$linkValue], $linkKey)) !== null) + { + return $this->converter( + $this->getUsersDetails($items), + $get, + $field + ); + } + + return null; + } + + /** + * Set a subform items + * + * @param mixed $items The list of items from the subform to set + * @param string $indexKey The index key on which the items should be observed as it relates to insert/update/delete. + * @param string $linkKey The link key on which the items where linked in the child table. + * @param string $linkValue The value of the link key in child table. + * + * @return bool + * @since 3.2.2 + */ + public function set(mixed $items, string $indexKey, string $linkKey, string $linkValue): bool + { + $items = $this->process($items, $indexKey, $linkKey, $linkValue); + + $this->purge($items, $indexKey, $linkKey, $linkValue); + + if (empty($items)) + { + return true; // nothing to set (already purged) + } + + return $this->items->table($this->getTable())->set( + $items, $indexKey + ); + } + + /** + * Get the current active table + * + * @return string + * @since 3.2.2 + */ + public function getTable(): string + { + return $this->table; + } + + /** + * Initializes the user properties. + * + * @return void + * @since 5.0.2 + */ + protected function initializeUserProperties(): void + { + $user = UserHelper::getUserById(0); + + // Populate user properties array excluding the 'id' + foreach (get_object_vars($user) as $property => $value) + { + if ($property !== 'id') + { + $this->user[$property] = $property; + } + } + $this->user['password2'] = 'password2'; + } + + /** + * Purge all items no longer in subform + * + * @param array $items The list of items to set. + * @param string $indexKey The index key on which the items should be observed as it relates to insert/update/delete + * @param string $linkKey The link key on which the items where linked in the child table. + * @param string $linkValue The value of the link key in child table. + * + * @return void + * @since 3.2.2 + */ + private function purge(array $items, string $indexKey, string $linkKey, string $linkValue): void + { + // Get the current index values from the database + $currentIndexValues = $this->items->table($this->getTable())->values([$linkValue], $linkKey, $indexKey); + + if ($currentIndexValues !== null) + { + // Check if the items array is empty + if (empty($items)) + { + // Set activeIndexValues to an empty array if items is empty + $activeIndexValues = []; + } + else + { + // Extract the index values from the items array + $activeIndexValues = array_values(array_map(function($item) use ($indexKey) { + return $item[$indexKey] ?? null; + }, $items)); + } + + // Find the index values that are no longer in the items array + $inactiveIndexValues = array_diff($currentIndexValues, $activeIndexValues); + + // Delete the inactive index values + if (!empty($inactiveIndexValues)) + { + $this->items->table($this->getTable())->delete($inactiveIndexValues, $indexKey); + + // $this->deleteUsers($inactiveIndexValues); (soon) + } + } + } + + /** + * Get the users details found in the user table. + * + * @param array $items Array of objects or arrays to be filtered. + * + * @return array + * @since 5.0.2 + */ + private function getUsersDetails(array $items): array + { + foreach ($items as $index => &$item) + { + $item = (array) $item; + $this->getUserDetails($item); + } + + return $items; + } + + /** + * Get the user details found in the user table. + * + * @param array $item The user map array + * + * @return void + * @since 5.0.2 + */ + private function getUserDetails(array &$item): void + { + // Validate the user_id and ensure it is numeric and greater than 0 + if (empty($item['user_id']) || !is_numeric($item['user_id']) || $item['user_id'] <= 0) + { + return; + } + + // Retrieve the user by ID + $user = UserHelper::getUserById((int)$item['user_id']); + + // Verify if the user exists and the ID matches + if ($user && $user->id === (int) $item['user_id']) + { + // Iterate over public properties of the user object + foreach (get_object_vars($user) as $property => $value) + { + // Avoid overwriting the id in the item + if ($property !== 'id') + { + $item[$property] = $value; + } + } + } + } + + /** + * Filters the specified keys from an array of objects or arrays, converts them to arrays, + * and sets them by association with a specified key and an incrementing integer. + * + * @param array $items Array of objects or arrays to be filtered. + * @param array $keySet Array of keys to retain in each item. + * @param string $field The field prefix for the resulting associative array. + * + * @return array Array of filtered arrays set by association. + * @since 3.2.2 + */ + private function converter(array $items, array $keySet, string $field): array + { + /** + * Filters keys for a single item and converts it to an array. + * + * @param object|array $item The item to filter. + * @param array $keySet The keys to retain. + * + * @return array The filtered array. + * @since 3.2.2 + */ + $filterKeys = function ($item, array $keySet) { + $filteredArray = []; + foreach ($keySet as $key) { + if (is_object($item) && property_exists($item, $key)) { + $filteredArray[$key] = $item->{$key}; + } elseif (is_array($item) && array_key_exists($key, $item)) { + $filteredArray[$key] = $item[$key]; + } + } + return $filteredArray; + }; + + $result = []; + foreach ($items as $index => $item) + { + $filteredArray = $filterKeys($item, $keySet); + $result[$field . $index] = $filteredArray; + } + + return $result; + } + + /** + * Processes an array of arrays based on the specified key. + * + * @param mixed $items Array of arrays to be processed. + * @param string $indexKey The index key on which the items should be observed as it relates to insert/update/delete + * @param string $linkKey The link key on which the items where linked in the child table. + * @param string $linkValue The value of the link key in child table. + * + * @return array The processed array of arrays. + * @since 3.2.2 + */ + private function process($items, string $indexKey, string $linkKey, string $linkValue): array + { + $items = is_array($items) ? $items : []; + foreach ($items as &$item) + { + $value = $item[$indexKey] ?? ''; + switch ($indexKey) { + case 'guid': + if (empty($value)) + { + // set INDEX + $item[$indexKey] = $this->getGuid($indexKey); + } + break; + case 'id': + if (empty($value)) + { + $item[$indexKey] = 0; + } + break; + case 'user_id': + $item[$indexKey] = $this->setUserDetails($item); + break; + default: + // No action for other keys if empty + break; + } + // set LINK + $item[$linkKey] = $linkValue; + } + + return array_values($items); + } + + /** + * Set the user details. + * + * @param array $item The user details + * + * @return int + * @since 5.0.2 + */ + private function setUserDetails(array &$item): int + { + $user = []; + + // now load the user ID + if (isset($item['user_id']) && is_numeric($item['user_id']) && $item['user_id'] > 0) + { + $user['id'] = (int) $item['user_id']; + } + + foreach ($this->user as $property) + { + if (isset($item[$property])) + { + $user[$property] = $item[$property]; + unset($item[$property]); + } + } + + try { + return UserHelper::save($user); + } catch(\Exception $e) { + Factory::getApplication()->enqueueMessage($e->getMessage(), 'warning'); + } + + return 0; + } \ No newline at end of file diff --git a/src/46b98346-ec98-42b3-a393-96c7d1282b1c/settings.json b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/settings.json new file mode 100644 index 0000000..1c0c77a --- /dev/null +++ b/src/46b98346-ec98-42b3-a393-96c7d1282b1c/settings.json @@ -0,0 +1,35 @@ +{ + "add_head": "1", + "add_licensing_template": "2", + "extends": "", + "guid": "46b98346-ec98-42b3-a393-96c7d1282b1c", + "implements": [ + "576685fd-263c-46bb-9fdc-1f5eb234cbb6", + "34959721-415b-4b5e-8002-3d1fc84b3b2b" + ], + "load_selection": null, + "name": "UsersSubform", + "power_version": "1.0.0", + "system_name": "VDM.Data.UsersSubform", + "type": "final class", + "use_selection": { + "use_selection0": { + "use": "7212e4db-371f-4cfd-8122-32e9bb100d83", + "as": "Items" + }, + "use_selection1": { + "use": "5acded67-0e3d-4c6b-a6ea-b533b076de0c", + "as": "default" + }, + "use_selection2": { + "use": "7832a726-87b6-4e95-887e-7b725d3fab8f", + "as": "default" + } + }, + "extendsinterfaces": null, + "namespace": "[[[NamespacePrefix]]]\\Joomla\\Data.UsersSubform", + "description": "CRUD the user data of any sub-form to another view (table)\r\n\r\n@since 5.0.2", + "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\\Factory;", + "composer": "" +} \ No newline at end of file diff --git a/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/README.md b/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/README.md new file mode 100644 index 0000000..17de856 --- /dev/null +++ b/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/README.md @@ -0,0 +1,54 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# interface GuidInterface (Details) +> namespace: **VDM\Joomla\Interfaces\Data** + +```uml +@startuml +interface GuidInterface #Lavender { + + getGuid(string $key) : string +} + +note right of GuidInterface::getGuid + Returns a GUIDv4 string. +This function uses the best cryptographically secure method +available on the platform with a fallback to an older, less secure version. + + since: 5.0.2 + return: string +end note + +@enduml +``` + +The Power feature in JCB allows you to write PHP classes and their implementations, making it easy to include them in your Joomla project. JCB handles linking, autoloading, namespacing, and folder structure creation for you. + +By using the SPK (Super Power Key) in your custom code (replacing the class name in your code with the SPK), JCB will automatically pull the power from the repository into your project. This makes it available in your JCB instance, allowing you to edit it and include the class in your generated Joomla component. + +JCB uses placeholders like [[[`NamespacePrefix`]]] and [[[`ComponentNamespace`]]] in namespacing to prevent collisions and improve reusability across different JCB systems. You can also set the **JCB powers path** globally or per component under the **Dynamic Integration** tab, providing flexibility and easy maintainability. + +To add this specific Power to your project in JCB: + +> simply use this SPK +``` +Super---576685fd_263c_46bb_9fdc_1f5eb234cbb6---Power +``` +> remember to replace the `---` with `___` to activate this Power in your code + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/code.php b/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/code.php new file mode 100644 index 0000000..2e3fff5 --- /dev/null +++ b/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/code.php @@ -0,0 +1,36 @@ + + * @git Joomla Component Builder + * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace VDM\Joomla\Interfaces\Data; + + +/** + * Globally Unique Identifier Interface + * + * @since 5.0.2 + */ +interface GuidInterface +{ + /** + * Returns a GUIDv4 string. + * + * This function uses the best cryptographically secure method + * available on the platform with a fallback to an older, less secure version. + * + * @param string $key The key to check and modify values. + * + * @return string A GUIDv4 string. + * + * @since 5.0.2 + */ + public function getGuid(string $key): string; +} + diff --git a/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/code.power b/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/code.power new file mode 100644 index 0000000..98fba9e --- /dev/null +++ b/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/code.power @@ -0,0 +1,13 @@ + /** + * Returns a GUIDv4 string. + * + * This function uses the best cryptographically secure method + * available on the platform with a fallback to an older, less secure version. + * + * @param string $key The key to check and modify values. + * + * @return string A GUIDv4 string. + * + * @since 5.0.2 + */ + public function getGuid(string $key): string; \ No newline at end of file diff --git a/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/settings.json b/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/settings.json new file mode 100644 index 0000000..34f9290 --- /dev/null +++ b/src/576685fd-263c-46bb-9fdc-1f5eb234cbb6/settings.json @@ -0,0 +1,19 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "", + "guid": "576685fd-263c-46bb-9fdc-1f5eb234cbb6", + "implements": null, + "load_selection": null, + "name": "GuidInterface", + "power_version": "1.0.0", + "system_name": "VDM.Interfaces.Data.GuidInterface", + "type": "interface", + "use_selection": null, + "extendsinterfaces": null, + "namespace": "[[[NamespacePrefix]]]\\Joomla\\Interfaces.Data.GuidInterface", + "description": "Globally Unique Identifier Interface\r\n\r\n@since 5.0.2", + "licensing_template": "\/**\r\n * @package Joomla.Component.Builder\r\n *\r\n * @created 4th September, 2022\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": "", + "composer": "" +} \ No newline at end of file diff --git a/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/README.md b/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/README.md new file mode 100644 index 0000000..6996198 --- /dev/null +++ b/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/README.md @@ -0,0 +1,70 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# trait Guid (Details) +> namespace: **VDM\Joomla\Data** + +```uml +@startuml +class Guid << (T,Orange) >> #Turquoise { + + getGuid(string $key) : string + - fallbackGuid(string $key) : string + - checkGuid(string $guid, string $key) : string +} + +note right of Guid::getGuid + Returns a GUIDv4 string. +This function uses the best cryptographically secure method +available on the platform with a fallback to an older, less secure version. + + since: 5.0.2 + return: string +end note + +note right of Guid::fallbackGuid + Generates a fallback GUIDv4 using less secure methods. + + since: 5.0.2 + return: string +end note + +note right of Guid::checkGuid + Checks if the GUID value is unique and does not already exist. + + since: 5.0.2 + return: string +end note + +@enduml +``` + +The Power feature in JCB allows you to write PHP classes and their implementations, making it easy to include them in your Joomla project. JCB handles linking, autoloading, namespacing, and folder structure creation for you. + +By using the SPK (Super Power Key) in your custom code (replacing the class name in your code with the SPK), JCB will automatically pull the power from the repository into your project. This makes it available in your JCB instance, allowing you to edit it and include the class in your generated Joomla component. + +JCB uses placeholders like [[[`NamespacePrefix`]]] and [[[`ComponentNamespace`]]] in namespacing to prevent collisions and improve reusability across different JCB systems. You can also set the **JCB powers path** globally or per component under the **Dynamic Integration** tab, providing flexibility and easy maintainability. + +To add this specific Power to your project in JCB: + +> simply use this SPK +``` +Super---5acded67_0e3d_4c6b_a6ea_b533b076de0c---Power +``` +> remember to replace the `---` with `___` to activate this Power in your code + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/code.php b/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/code.php new file mode 100644 index 0000000..454fa4d --- /dev/null +++ b/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/code.php @@ -0,0 +1,110 @@ + + * @git Joomla Component Builder + * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace VDM\Joomla\Data; + + +/** + * Globally Unique Identifier + * + * @since 5.0.2 + */ +trait Guid +{ + /** + * Returns a GUIDv4 string. + * + * This function uses the best cryptographically secure method + * available on the platform with a fallback to an older, less secure version. + * + * @param string $key The key to check and modify values. + * + * @return string A GUIDv4 string. + * + * @since 5.0.2 + */ + public function getGuid(string $key): string + { + // Windows: Use com_create_guid if available + if (function_exists('com_create_guid')) + { + $guid = trim(\com_create_guid(), '{}'); + return $this->checkGuid($guid, $key); + } + + // Unix-based systems: Use openssl_random_pseudo_bytes if available + if (function_exists('random_bytes')) + { + try { + $data = random_bytes(16); + } catch (Exception $e) { + // Handle the error appropriately (logging, throwing, etc.) + return $this->fallbackGuid($key); + } + + // Set the version to 0100 and the bits 6-7 to 10 as per RFC 4122 + $data[6] = chr(ord($data[6]) & 0x0f | 0x40); + $data[8] = chr(ord($data[8]) & 0x3f | 0x80); + + $guid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); + return $this->checkGuid($guid, $key); + } + + // Fallback to older methods if secure methods are not available + return $this->fallbackGuid($key); + } + + /** + * Generates a fallback GUIDv4 using less secure methods. + * + * @param string $key The key to check and modify values. + * + * @return string A GUIDv4 string. + * + * @since 5.0.2 + */ + private function fallbackGuid(string $key): string + { + $charid = strtolower(md5(uniqid(random_int(0, PHP_INT_MAX), true))); + $guidv4 = sprintf( + '%s-%s-%s-%s-%s', + substr($charid, 0, 8), + substr($charid, 8, 4), + substr($charid, 12, 4), + substr($charid, 16, 4), + substr($charid, 20, 12) + ); + + return $this->checkGuid($guidv4, $key); + } + + /** + * Checks if the GUID value is unique and does not already exist. + * + * @param string $guid The GUID value to check. + * @param string $key The key to check and modify values. + * + * @return string The unique GUID value. + * + * @since 5.0.2 + */ + private function checkGuid(string $guid, string $key): string + { + // Check that the GUID does not already exist + if ($this->items->table($this->getTable())->values([$guid], $key)) + { + return $this->getGuid($key); + } + + return $guid; + } +} + diff --git a/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/code.power b/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/code.power new file mode 100644 index 0000000..79b6f82 --- /dev/null +++ b/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/code.power @@ -0,0 +1,87 @@ + /** + * Returns a GUIDv4 string. + * + * This function uses the best cryptographically secure method + * available on the platform with a fallback to an older, less secure version. + * + * @param string $key The key to check and modify values. + * + * @return string A GUIDv4 string. + * + * @since 5.0.2 + */ + public function getGuid(string $key): string + { + // Windows: Use com_create_guid if available + if (function_exists('com_create_guid')) + { + $guid = trim(\com_create_guid(), '{}'); + return $this->checkGuid($guid, $key); + } + + // Unix-based systems: Use openssl_random_pseudo_bytes if available + if (function_exists('random_bytes')) + { + try { + $data = random_bytes(16); + } catch (Exception $e) { + // Handle the error appropriately (logging, throwing, etc.) + return $this->fallbackGuid($key); + } + + // Set the version to 0100 and the bits 6-7 to 10 as per RFC 4122 + $data[6] = chr(ord($data[6]) & 0x0f | 0x40); + $data[8] = chr(ord($data[8]) & 0x3f | 0x80); + + $guid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); + return $this->checkGuid($guid, $key); + } + + // Fallback to older methods if secure methods are not available + return $this->fallbackGuid($key); + } + + /** + * Generates a fallback GUIDv4 using less secure methods. + * + * @param string $key The key to check and modify values. + * + * @return string A GUIDv4 string. + * + * @since 5.0.2 + */ + private function fallbackGuid(string $key): string + { + $charid = strtolower(md5(uniqid(random_int(0, PHP_INT_MAX), true))); + $guidv4 = sprintf( + '%s-%s-%s-%s-%s', + substr($charid, 0, 8), + substr($charid, 8, 4), + substr($charid, 12, 4), + substr($charid, 16, 4), + substr($charid, 20, 12) + ); + + return $this->checkGuid($guidv4, $key); + } + + /** + * Checks if the GUID value is unique and does not already exist. + * + * @param string $guid The GUID value to check. + * @param string $key The key to check and modify values. + * + * @return string The unique GUID value. + * + * @since 5.0.2 + */ + private function checkGuid(string $guid, string $key): string + { + // Check that the GUID does not already exist + if ($this->items->table($this->getTable())->values([$guid], $key)) + { + return $this->getGuid($key); + } + + return $guid; + } \ No newline at end of file diff --git a/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/settings.json b/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/settings.json new file mode 100644 index 0000000..74c9b5f --- /dev/null +++ b/src/5acded67-0e3d-4c6b-a6ea-b533b076de0c/settings.json @@ -0,0 +1,19 @@ +{ + "add_head": "0", + "add_licensing_template": "2", + "extends": "", + "guid": "5acded67-0e3d-4c6b-a6ea-b533b076de0c", + "implements": null, + "load_selection": null, + "name": "Guid", + "power_version": "1.0.0", + "system_name": "VDM.Data.Guid", + "type": "trait", + "use_selection": null, + "extendsinterfaces": null, + "namespace": "[[[NamespacePrefix]]]\\Joomla\\Data.Guid", + "description": "Globally Unique Identifier\r\n\r\n@since 5.0.2", + "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": "", + "composer": "" +} \ No newline at end of file diff --git a/src/640b5352-fb09-425f-a26e-cd44eda03f15/README.md b/src/640b5352-fb09-425f-a26e-cd44eda03f15/README.md index c00857c..f255751 100644 --- a/src/640b5352-fb09-425f-a26e-cd44eda03f15/README.md +++ b/src/640b5352-fb09-425f-a26e-cd44eda03f15/README.md @@ -12,6 +12,7 @@ ```uml @startuml abstract Helper #Orange { + + {static} setParams(string $target, mixed $value, ...) : mixed + {static} getParams(?string $option = null) : Registry + {static} setOption(?string $option) : void + {static} getOption(?string $default = 'empty') : ?string @@ -21,65 +22,82 @@ abstract Helper #Orange { + {static} getManifest(?string $option = null) : ?object + {static} methodExists(string $method, ?string $option = null) : bool + {static} _(string $method, array $arguments = [], ...) : mixed + + {static} getModel(string $type, string $prefix = 'Administrator', ...) : BaseDatabaseModel + - {static} getPrefixFromModelPath(string $path) : string } -note right of Helper::getParams +note right of Helper::setParams + Sets a parameter value for the given target in the specified option's params. +If no option is provided, it falls back to the default option. +This method updates the parameters for a given extension in the database, +only if the new value differs from the existing one. + + since: 5.0.3 + return: mixed + + arguments: + string $target + mixed $value + ?string $option = null +end note + +note left of Helper::getParams Gets the parameter object for the component since: 3.0.11 return: Registry end note -note left of Helper::setOption +note right of Helper::setOption Set the component option since: 3.2.0 return: void end note -note right of Helper::getOption +note left of Helper::getOption Get the component option since: 3.0.11 return: ?string end note -note left of Helper::getCode +note right of Helper::getCode Gets the component code name since: 3.0.11 return: ?string end note -note right of Helper::get +note left of Helper::get Gets the component abstract helper class since: 3.0.11 return: ?string end note -note left of Helper::getNamespace +note right of Helper::getNamespace Gets the component namespace if set since: 3.0.11 return: ?string end note -note right of Helper::getManifest +note left of Helper::getManifest Gets the component abstract helper class since: 3.0.11 return: ?object end note -note left of Helper::methodExists +note right of Helper::methodExists Check if the helper class of this component has a method since: 3.0.11 return: bool end note -note right of Helper::_ +note left of Helper::_ Check if the helper class of this component has a method, and call it with the arguments since: 3.2.0 @@ -90,6 +108,26 @@ note right of Helper::_ array $arguments = [] ?string $option = null end note + +note right of Helper::getModel + Returns a Model object based on the specified type, prefix, and configuration. + + since: 5.0.3 + return: BaseDatabaseModel + + arguments: + string $type + string $prefix = 'Administrator' + ?string $option = null + array $config = [] +end note + +note left of Helper::getPrefixFromModelPath + Get the prefix from the model path + + since: 5.0.3 + return: string +end note @enduml ``` diff --git a/src/640b5352-fb09-425f-a26e-cd44eda03f15/code.php b/src/640b5352-fb09-425f-a26e-cd44eda03f15/code.php index 5660a49..a5e8977 100644 --- a/src/640b5352-fb09-425f-a26e-cd44eda03f15/code.php +++ b/src/640b5352-fb09-425f-a26e-cd44eda03f15/code.php @@ -14,9 +14,11 @@ namespace VDM\Joomla\Utilities\Component; use Joomla\CMS\Factory; use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\Input\Input; use Joomla\Registry\Registry; use VDM\Joomla\Utilities\String\NamespaceHelper; +use VDM\Joomla\Utilities\StringHelper; /** @@ -50,6 +52,57 @@ abstract class Helper */ protected static array $params = []; + /** + * Sets a parameter value for the given target in the specified option's params. + * If no option is provided, it falls back to the default option. + * + * This method updates the parameters for a given extension in the database, + * only if the new value differs from the existing one. + * + * @param string $target The parameter name to be updated. + * @param mixed $value The value to set for the parameter. + * @param string|null $option The optional extension element name. Defaults to null, which will use the default option. + * + * @return mixed The previous value of the parameter before it was updated. + * @since 5.0.3 + */ + public static function setParams(string $target, $value, ?string $option = null) + { + // Ensure that an option is specified, defaulting to the system's option if not provided. + if (empty($option)) + { + $option = static::getOption(); + } + + // Retrieve current parameters for the specified option. + $params = static::getParams($option); + + // Get the current value of the target parameter. + $was = $params->get($target, null); + + // Only proceed if the new value differs from the current value. + if ($was !== $value) + { + // Update the parameter value. + $params->set($target, $value); + + // Obtain a database connection instance. + $db = Factory::getDBO(); + $query = $db->getQuery(true); + + // Build and execute the query to update the parameters in the database. + $query->update('#__extensions AS a') + ->set('a.params = ' . $db->quote((string) $params)) + ->where('a.element = ' . $db->quote((string) $option)); + + $db->setQuery($query); + $db->execute(); + } + + // Return the previous value of the parameter. + return $was; + } + /** * Gets the parameter object for the component * @@ -64,16 +117,16 @@ abstract class Helper // check that we have an option if (empty($option)) { - $option = self::getOption(); + $option = static::getOption(); } // get global value - if (!isset(self::$params[$option]) || !self::$params[$option] instanceof Registry) + if (!isset(static::$params[$option]) || !static::$params[$option] instanceof Registry) { - self::$params[$option] = ComponentHelper::getParams($option); + static::$params[$option] = ComponentHelper::getParams($option); } - return self::$params[$option]; + return static::$params[$option]; } /** @@ -86,7 +139,7 @@ abstract class Helper */ public static function setOption(?string $option): void { - self::$option = $option; + static::$option = $option; } /** @@ -99,13 +152,13 @@ abstract class Helper */ public static function getOption(?string $default = 'empty'): ?string { - if (empty(self::$option)) + if (empty(static::$option)) { // get the option from the url input - self::$option = (new Input)->getString('option', null); + static::$option = (new Input)->getString('option', null); } - if (empty(self::$option)) + if (empty(static::$option)) { $app = Factory::getApplication(); @@ -113,16 +166,16 @@ abstract class Helper if (method_exists($app, 'getInput')) { // get the option from the application - self::$option = $app->getInput()->getCmd('option', $default); + static::$option = $app->getInput()->getCmd('option', $default); } else { // Use the default value if getInput method does not exist - self::$option = $default; + static::$option = $default; } } - return self::$option; + return static::$option; } /** @@ -139,7 +192,7 @@ abstract class Helper // check that we have an option if (empty($option)) { - $option = self::getOption(); + $option = static::getOption(); } // option with com_ if (is_string($option) && strpos($option, 'com_') === 0) @@ -164,7 +217,7 @@ abstract class Helper { // check that we have an option // and get the code name from it - if (($code_name = self::getCode($option, null)) !== null) + if (($code_name = static::getCode($option, null)) !== null) { // we build the helper class name $helper_name = '\\' . \ucfirst($code_name) . 'Helper'; @@ -176,7 +229,7 @@ abstract class Helper } // try loading namespace - if (($namespace = self::getNamespace($option)) !== null) + if (($namespace = static::getNamespace($option)) !== null) { $name = \ucfirst($code_name) . 'Helper'; $namespace_helper = '\\' . $namespace . '\Administrator\Helper\\' . NamespaceHelper::safeSegment($name); // TODO target site or admin locations not just admin... @@ -202,7 +255,7 @@ abstract class Helper */ public static function getNamespace(?string $option = null): ?string { - $manifest = self::getManifest($option); + $manifest = static::getManifest($option); return $manifest->namespace ?? null; } @@ -220,13 +273,13 @@ abstract class Helper public static function getManifest(?string $option = null): ?object { if ($option === null - && ($option = self::getOption($option)) === null) + && ($option = static::getOption($option)) === null) { return null; } // get global manifest_cache values - if (!isset(self::$manifest[$option])) + if (!isset(static::$manifest[$option])) { $db = Factory::getDbo(); $query = $db->getQuery(true); @@ -240,14 +293,14 @@ abstract class Helper try { $manifest = $db->loadResult(); - self::$manifest[$option] = json_decode($manifest); + static::$manifest[$option] = json_decode($manifest); } catch (\Exception $e) { // Handle the database error appropriately. - self::$manifest[$option] = null; + static::$manifest[$option] = null; } } - return self::$manifest[$option]; + return static::$manifest[$option]; } /** @@ -263,7 +316,7 @@ abstract class Helper public static function methodExists(string $method, ?string $option = null): bool { // get the helper class - return ($helper = self::get($option, null)) !== null && + return ($helper = static::get($option, null)) !== null && method_exists($helper, $method); } @@ -280,7 +333,7 @@ abstract class Helper public static function _(string $method, array $arguments = [], ?string $option = null) { // get the helper class - if (($helper = self::get($option, null)) !== null && + if (($helper = static::get($option, null)) !== null && method_exists($helper, $method)) { // we know this is not ideal... @@ -291,6 +344,77 @@ abstract class Helper return null; } - + + /** + * Returns a Model object based on the specified type, prefix, and configuration. + * + * @param string $type The model type to instantiate. Must not be empty. + * @param string $prefix Prefix for the model class name. Optional, defaults to 'Administrator'. + * @param string|null $option The component option. Optional, defaults to the component's option. + * @param array $config Configuration array for the model. Optional, defaults to an empty array. + * + * @return BaseDatabaseModel The instantiated model object. + * + * @throws \InvalidArgumentException If the $type parameter is empty. + * @throws \Exception For other errors that may occur during model creation. + * + * @since 5.0.3 + */ + public static function getModel(string $type, string $prefix = 'Administrator', + ?string $option = null, array $config = []): BaseDatabaseModel + { + // Ensure the $type parameter is not empty + if (empty($type)) + { + throw new \InvalidArgumentException('The $type parameter cannot be empty when calling Component Helper getModel method.'); + } + + // Ensure the $option parameter is set, defaulting to the component's option if not provided + if (empty($option)) + { + $option = static::getOption(); + } + + // Normalize the model type name if the first character is not uppercase + if (!ctype_upper($type[0])) + { + $type = StringHelper::safe($type, 'F'); + } + + // Normalize the prefix if it's not 'Site' or 'Administrator' + if ($prefix !== 'Site' && $prefix !== 'Administrator') + { + $prefix = static::getPrefixFromModelPath($prefix); + } + + // Instantiate and return the model using the MVCFactory + return Factory::getApplication() + ->bootComponent($option) + ->getMVCFactory() + ->createModel($type, $prefix, $config); + } + + /** + * Get the prefix from the model path + * + * @param string $path The model path + * + * @return string The prefix value + * @since 5.0.3 + */ + private static function getPrefixFromModelPath(string $path): string + { + // Check if $path starts with JPATH_ADMINISTRATOR path + if (str_starts_with($path, JPATH_ADMINISTRATOR . '/components/')) + { + return 'Administrator'; + } + // Check if $path starts with JPATH_SITE path + elseif (str_starts_with($path, JPATH_SITE . '/components/')) + { + return 'Site'; + } + return 'Administrator'; + } } diff --git a/src/640b5352-fb09-425f-a26e-cd44eda03f15/code.power b/src/640b5352-fb09-425f-a26e-cd44eda03f15/code.power index adc7052..2f0f10a 100644 --- a/src/640b5352-fb09-425f-a26e-cd44eda03f15/code.power +++ b/src/640b5352-fb09-425f-a26e-cd44eda03f15/code.power @@ -22,6 +22,57 @@ */ protected static array $params = []; + /** + * Sets a parameter value for the given target in the specified option's params. + * If no option is provided, it falls back to the default option. + * + * This method updates the parameters for a given extension in the database, + * only if the new value differs from the existing one. + * + * @param string $target The parameter name to be updated. + * @param mixed $value The value to set for the parameter. + * @param string|null $option The optional extension element name. Defaults to null, which will use the default option. + * + * @return mixed The previous value of the parameter before it was updated. + * @since 5.0.3 + */ + public static function setParams(string $target, $value, ?string $option = null) + { + // Ensure that an option is specified, defaulting to the system's option if not provided. + if (empty($option)) + { + $option = static::getOption(); + } + + // Retrieve current parameters for the specified option. + $params = static::getParams($option); + + // Get the current value of the target parameter. + $was = $params->get($target, null); + + // Only proceed if the new value differs from the current value. + if ($was !== $value) + { + // Update the parameter value. + $params->set($target, $value); + + // Obtain a database connection instance. + $db = Factory::getDBO(); + $query = $db->getQuery(true); + + // Build and execute the query to update the parameters in the database. + $query->update('#__extensions AS a') + ->set('a.params = ' . $db->quote((string) $params)) + ->where('a.element = ' . $db->quote((string) $option)); + + $db->setQuery($query); + $db->execute(); + } + + // Return the previous value of the parameter. + return $was; + } + /** * Gets the parameter object for the component * @@ -36,16 +87,16 @@ // check that we have an option if (empty($option)) { - $option = self::getOption(); + $option = static::getOption(); } // get global value - if (!isset(self::$params[$option]) || !self::$params[$option] instanceof Registry) + if (!isset(static::$params[$option]) || !static::$params[$option] instanceof Registry) { - self::$params[$option] = ComponentHelper::getParams($option); + static::$params[$option] = ComponentHelper::getParams($option); } - return self::$params[$option]; + return static::$params[$option]; } /** @@ -58,7 +109,7 @@ */ public static function setOption(?string $option): void { - self::$option = $option; + static::$option = $option; } /** @@ -71,13 +122,13 @@ */ public static function getOption(?string $default = 'empty'): ?string { - if (empty(self::$option)) + if (empty(static::$option)) { // get the option from the url input - self::$option = (new Input)->getString('option', null); + static::$option = (new Input)->getString('option', null); } - if (empty(self::$option)) + if (empty(static::$option)) { $app = Factory::getApplication(); @@ -85,16 +136,16 @@ if (method_exists($app, 'getInput')) { // get the option from the application - self::$option = $app->getInput()->getCmd('option', $default); + static::$option = $app->getInput()->getCmd('option', $default); } else { // Use the default value if getInput method does not exist - self::$option = $default; + static::$option = $default; } } - return self::$option; + return static::$option; } /** @@ -111,7 +162,7 @@ // check that we have an option if (empty($option)) { - $option = self::getOption(); + $option = static::getOption(); } // option with com_ if (is_string($option) && strpos($option, 'com_') === 0) @@ -136,7 +187,7 @@ { // check that we have an option // and get the code name from it - if (($code_name = self::getCode($option, null)) !== null) + if (($code_name = static::getCode($option, null)) !== null) { // we build the helper class name $helper_name = '\\' . \ucfirst($code_name) . 'Helper'; @@ -148,7 +199,7 @@ } // try loading namespace - if (($namespace = self::getNamespace($option)) !== null) + if (($namespace = static::getNamespace($option)) !== null) { $name = \ucfirst($code_name) . 'Helper'; $namespace_helper = '\\' . $namespace . '\Administrator\Helper\\' . NamespaceHelper::safeSegment($name); // TODO target site or admin locations not just admin... @@ -174,7 +225,7 @@ */ public static function getNamespace(?string $option = null): ?string { - $manifest = self::getManifest($option); + $manifest = static::getManifest($option); return $manifest->namespace ?? null; } @@ -192,13 +243,13 @@ public static function getManifest(?string $option = null): ?object { if ($option === null - && ($option = self::getOption($option)) === null) + && ($option = static::getOption($option)) === null) { return null; } // get global manifest_cache values - if (!isset(self::$manifest[$option])) + if (!isset(static::$manifest[$option])) { $db = Factory::getDbo(); $query = $db->getQuery(true); @@ -212,14 +263,14 @@ try { $manifest = $db->loadResult(); - self::$manifest[$option] = json_decode($manifest); + static::$manifest[$option] = json_decode($manifest); } catch (\Exception $e) { // Handle the database error appropriately. - self::$manifest[$option] = null; + static::$manifest[$option] = null; } } - return self::$manifest[$option]; + return static::$manifest[$option]; } /** @@ -235,7 +286,7 @@ public static function methodExists(string $method, ?string $option = null): bool { // get the helper class - return ($helper = self::get($option, null)) !== null && + return ($helper = static::get($option, null)) !== null && method_exists($helper, $method); } @@ -252,7 +303,7 @@ public static function _(string $method, array $arguments = [], ?string $option = null) { // get the helper class - if (($helper = self::get($option, null)) !== null && + if (($helper = static::get($option, null)) !== null && method_exists($helper, $method)) { // we know this is not ideal... @@ -263,3 +314,75 @@ return null; } + + /** + * Returns a Model object based on the specified type, prefix, and configuration. + * + * @param string $type The model type to instantiate. Must not be empty. + * @param string $prefix Prefix for the model class name. Optional, defaults to 'Administrator'. + * @param string|null $option The component option. Optional, defaults to the component's option. + * @param array $config Configuration array for the model. Optional, defaults to an empty array. + * + * @return BaseDatabaseModel The instantiated model object. + * + * @throws \InvalidArgumentException If the $type parameter is empty. + * @throws \Exception For other errors that may occur during model creation. + * + * @since 5.0.3 + */ + public static function getModel(string $type, string $prefix = 'Administrator', + ?string $option = null, array $config = []): BaseDatabaseModel + { + // Ensure the $type parameter is not empty + if (empty($type)) + { + throw new \InvalidArgumentException('The $type parameter cannot be empty when calling Component Helper getModel method.'); + } + + // Ensure the $option parameter is set, defaulting to the component's option if not provided + if (empty($option)) + { + $option = static::getOption(); + } + + // Normalize the model type name if the first character is not uppercase + if (!ctype_upper($type[0])) + { + $type = StringHelper::safe($type, 'F'); + } + + // Normalize the prefix if it's not 'Site' or 'Administrator' + if ($prefix !== 'Site' && $prefix !== 'Administrator') + { + $prefix = static::getPrefixFromModelPath($prefix); + } + + // Instantiate and return the model using the MVCFactory + return Factory::getApplication() + ->bootComponent($option) + ->getMVCFactory() + ->createModel($type, $prefix, $config); + } + + /** + * Get the prefix from the model path + * + * @param string $path The model path + * + * @return string The prefix value + * @since 5.0.3 + */ + private static function getPrefixFromModelPath(string $path): string + { + // Check if $path starts with JPATH_ADMINISTRATOR path + if (str_starts_with($path, JPATH_ADMINISTRATOR . '/components/')) + { + return 'Administrator'; + } + // Check if $path starts with JPATH_SITE path + elseif (str_starts_with($path, JPATH_SITE . '/components/')) + { + return 'Site'; + } + return 'Administrator'; + } \ No newline at end of file diff --git a/src/640b5352-fb09-425f-a26e-cd44eda03f15/settings.json b/src/640b5352-fb09-425f-a26e-cd44eda03f15/settings.json index cd34e7e..609d4ef 100644 --- a/src/640b5352-fb09-425f-a26e-cd44eda03f15/settings.json +++ b/src/640b5352-fb09-425f-a26e-cd44eda03f15/settings.json @@ -13,12 +13,16 @@ "use_selection0": { "use": "ce8cf834-6bac-44fb-941c-861f7e046cc0", "as": "default" + }, + "use_selection1": { + "use": "1f28cb53-60d9-4db1-b517-3c7dc6b429ef", + "as": "default" } }, "extendsinterfaces": null, "namespace": "[[[NamespacePrefix]]]\\Joomla\\Utilities.Component.Helper", "description": "Some component helper\r\n\r\n@since 3.0.11", "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\\Factory;\r\nuse Joomla\\CMS\\Component\\ComponentHelper;\r\nuse Joomla\\Input\\Input;\r\nuse Joomla\\Registry\\Registry;", + "head": "use Joomla\\CMS\\Factory;\r\nuse Joomla\\CMS\\Component\\ComponentHelper;\r\nuse Joomla\\CMS\\MVC\\Model\\BaseDatabaseModel;\r\nuse Joomla\\Input\\Input;\r\nuse Joomla\\Registry\\Registry;", "composer": "" } \ No newline at end of file diff --git a/src/7832a726-87b6-4e95-887e-7b725d3fab8f/README.md b/src/7832a726-87b6-4e95-887e-7b725d3fab8f/README.md new file mode 100644 index 0000000..b4ab1e4 --- /dev/null +++ b/src/7832a726-87b6-4e95-887e-7b725d3fab8f/README.md @@ -0,0 +1,141 @@ +``` +██████╗ ██████╗ ██╗ ██╗███████╗██████╗ +██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗ +██████╔╝██║ ██║██║ █╗ ██║█████╗ ██████╔╝ +██╔═══╝ ██║ ██║██║███╗██║██╔══╝ ██╔══██╗ +██║ ╚██████╔╝╚███╔███╔╝███████╗██║ ██║ +╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝ +``` +# abstract class UserHelper (Details) +> namespace: **VDM\Joomla\Componentbuilder\Utilities** + +```uml +@startuml +abstract UserHelper #Orange { + + {static} save(array $credentials, int $autologin, ...) : int + + {static} create(array $credentials, int $autologin, ...) : int + + {static} update(array $userDetails) : int + + {static} getUserById(int $id) : User + + {static} getUserIdByUsername(string $username) : ?int + + {static} getUserIdByEmail(string $email) : ?int + # {static} getModelByMode(int $mode) : BaseDatabaseModel + # {static} prepareUserData(array $credentials, int $mode) : array + - {static} adminRegister(BaseDatabaseModel $model, array $data) : int + - {static} handlePostRegistration(int $userId, int $autologin, ...) : int +} + +note right of UserHelper::save + Save user details by either creating a new user or updating an existing user. + + since: 5.0.3 + return: int + + arguments: + array $credentials + int $autologin + array $params = ['useractivation' => 0, 'sendpassword' => 1] + int $mode = 1 +end note + +note left of UserHelper::create + Create a user and update the given table. + + since: 5.0.3 + return: int + + arguments: + array $credentials + int $autologin + array $params = ['useractivation' => 0, 'sendpassword' => 1] + int $mode = 1 +end note + +note right of UserHelper::update + Update user details. + + since: 5.0.3 + return: int +end note + +note left of UserHelper::getUserById + Method to get an instance of a user for the given id. + + since: 5.0.3 + return: User +end note + +note right of UserHelper::getUserIdByUsername + Retrieve the user ID by username. + + since: 5.0.3 + return: ?int +end note + +note left of UserHelper::getUserIdByEmail + Retrieve the user ID by email. + + since: 5.0.3 + return: ?int +end note + +note right of UserHelper::getModelByMode + Load the correct user model based on the registration mode. + + since: 5.0.3 + return: BaseDatabaseModel +end note + +note left of UserHelper::prepareUserData + Prepare user data array for registration or update. + + since: 5.0.3 + return: array +end note + +note right of UserHelper::adminRegister + Handle the registration process for admin mode. + + since: 5.0.3 + return: int +end note + +note left of UserHelper::handlePostRegistration + Handle post-registration processes like auto-login. + + since: 5.0.3 + return: int + + arguments: + int $userId + int $autologin + array $credentials +end note + +@enduml +``` + +The Power feature in JCB allows you to write PHP classes and their implementations, making it easy to include them in your Joomla project. JCB handles linking, autoloading, namespacing, and folder structure creation for you. + +By using the SPK (Super Power Key) in your custom code (replacing the class name in your code with the SPK), JCB will automatically pull the power from the repository into your project. This makes it available in your JCB instance, allowing you to edit it and include the class in your generated Joomla component. + +JCB uses placeholders like [[[`NamespacePrefix`]]] and [[[`ComponentNamespace`]]] in namespacing to prevent collisions and improve reusability across different JCB systems. You can also set the **JCB powers path** globally or per component under the **Dynamic Integration** tab, providing flexibility and easy maintainability. + +To add this specific Power to your project in JCB: + +> simply use this SPK +``` +Super---7832a726_87b6_4e95_887e_7b725d3fab8f---Power +``` +> remember to replace the `---` with `___` to activate this Power in your code + +--- +``` + ██╗ ██████╗██████╗ + ██║██╔════╝██╔══██╗ + ██║██║ ██████╔╝ +██ ██║██║ ██╔══██╗ +╚█████╔╝╚██████╗██████╔╝ + ╚════╝ ╚═════╝╚═════╝ +``` +> Build with [Joomla Component Builder](https://git.vdm.dev/joomla/Component-Builder) + diff --git a/src/7832a726-87b6-4e95-887e-7b725d3fab8f/code.php b/src/7832a726-87b6-4e95-887e-7b725d3fab8f/code.php new file mode 100644 index 0000000..d85a3fc --- /dev/null +++ b/src/7832a726-87b6-4e95-887e-7b725d3fab8f/code.php @@ -0,0 +1,413 @@ + + * @git Joomla Component Builder + * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace VDM\Joomla\Componentbuilder\Utilities; + + +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; +use Joomla\CMS\User\User; +use Joomla\CMS\User\UserHelper as JoomlaUserHelper; +use Joomla\CMS\MVC\Model\BaseDatabaseModel; +use VDM\Joomla\Utilities\Component\Helper as Component; +use VDM\Component\Componentbuilder\Administrator\Helper\ComponentbuilderHelper; + + +/** + * Create & Update User [Save] + * + * @since 5.0.2 + */ +abstract class UserHelper +{ + /** + * Save user details by either creating a new user or updating an existing user. + * + * @param array $credentials User credentials including 'name', 'username', 'email', 'password', and 'password2'. + * @param int $autologin Flag to determine whether to auto-login the user after registration. + * @param array $params Parameters for user activation, password sending, and user registration allowance. + * @param int $mode Mode of registration: 1 = Site Registration, 0 = Admin Registration, 2 = Custom Helper Method. + * + * @return int User ID on success. + * + * @throws \InvalidArgumentException If required credentials are missing. + * @throws \RuntimeException If the user update or creation fails. + * + * @since 5.0.3 + */ + public static function save(array $credentials, int $autologin = 0, + array $params = ['useractivation' => 0, 'sendpassword' => 1], int $mode = 1): int + { + // can not continue without an email + if (empty($credentials['email'])) + { + throw new \InvalidArgumentException(Text::_('COM_COMPONENTBUILDER_CAN_NOT_SAVE_USER_WITHOUT_EMAIL_VALUE')); + } + + // Ensure the 'username' key exists in the credentials array, set to an empty string if not provided. + $username = $credentials['username'] ?? $credentials['email']; + + // Check for an existing user by email or username. + $existingEmailUserId = static::getUserIdByEmail($credentials['email']); + $existingUserId = static::getUserIdByUsername($username); + + // If either the email or username already exists, handle the update logic. + if ($existingEmailUserId !== null || $existingUserId !== null || (isset($credentials['id']) && $credentials['id'] > 0)) + { + // Prevent updating other users or reusing an email by different users. + if ( + ( + isset($credentials['id']) && + ( + ($existingEmailUserId !== null && $existingEmailUserId != $credentials['id']) || + ($existingUserId !== null && $existingUserId != $credentials['id']) + ) + ) || ($existingUserId !== null && $existingEmailUserId !== null && $existingEmailUserId != $existingUserId) + ) + { + throw new \RuntimeException(Text::sprintf('COM_COMPONENTBUILDER_USER_ID_MISMATCH_DETECTED_WHEN_TRYING_TO_SAVE_S_S_CREDENTIALS', $username, $credentials['email'])); + } + + // Update the existing user. + $credentials['id'] = $credentials['id'] ?? $existingEmailUserId ?? $existingUserId; + return static::update($credentials); + } + + // Create a new user if no existing user is found. + return static::create($credentials, $autologin, $params, $mode); + } + + /** + * Create a user and update the given table. + * + * @param array $credentials User credentials including 'name', 'username', 'email', 'password', and 'password2'. + * @param int $autologin Flag to determine whether to auto-login the user after registration. + * @param array $params Parameters for user activation, password sending, and user registration allowance. + * @param int $mode Mode of registration: 1 = Site Registration, 0 = Admin Registration, 2 = Custom Helper Method. + * + * @return int User ID on success. + * + * @throws \RuntimeException If user creation fails. + * + * @since 5.0.3 + */ + public static function create(array $credentials, int $autologin = 0, + array $params = ['useractivation' => 0, 'sendpassword' => 1], int $mode = 1): int + { + $lang = Factory::getLanguage(); + $lang->load('com_users', JPATH_SITE, 'en-GB', true); + + // Handle custom registration mode + if ($mode === 2 && method_exists(ComponentbuilderHelper::class, 'registerUser')) + { + $params['autologin'] = $autologin; + $userId = ComponentbuilderHelper::registerUser($credentials, $params); + + if (is_numeric($userId)) + { + return $userId; + } + + throw new \RuntimeException(Text::_('COM_COMPONENTBUILDER_USER_CREATION_FAILED')); + } + + // Check if we have params/config + if (ArrayHelper::check($params)) + { + // Make changes to user config + foreach ($params as $param => $set) + { + // If you know of a better path, let me know + $params[$param] = Component::setParams($param, $set, 'com_users'); + } + } + + // Fallback to Site Registrations if mode is set to 2 but the method doesn't exist + $mode = $mode === 2 ? 1 : $mode; + + // Load the appropriate user model + $model = static::getModelByMode($mode); + + // Set default values for missing credentials + $credentials['username'] = $credentials['username'] ?? $credentials['email']; + + // Prepare user data + $data = static::prepareUserData($credentials, $mode); + + // Handle user creation + $userId = $mode === 1 ? $model->register($data) : static::adminRegister($model, $data); + + // Check if we have params + if (ArrayHelper::check($params)) + { + // Change user params/config back + foreach ($params as $param => $set) + { + // If you know of a better path, let me know + Component::setParams($param, $set, 'com_users'); + } + } + + if (is_numeric($userId) && $userId > 0) + { + // Handle post-registration processes + return static::handlePostRegistration($userId, $autologin, $credentials); + } + + $error_messages = ''; + if (method_exists($model, 'getError')) + { + $errors = $model->getError(); + if (!empty($errors)) + { + $error_messages = '
' . implode('
', $errors); + } + } + + throw new \RuntimeException( + Text::sprintf('COM_COMPONENTBUILDER_USER_S_S_CREATION_FAILEDS', + (string) $credentials['username'], + (string) $credentials['email'], + $error_messages + ) + ); + } + + /** + * Update user details. + * + * @param array $userDetails Array containing user details to be updated. + * + * @return int Updated user ID on success. + * + * @throws \RuntimeException If user update fails. + * + * @since 5.0.3 + */ + public static function update(array $userDetails): int + { + $lang = Factory::getLanguage(); + $lang->load('com_users', JPATH_ADMINISTRATOR, 'en-GB', true); + + $model = Component::getModel('User', 'Administrator', 'com_users'); + + // Set default values for missing credentials + $userDetails['username'] = $userDetails['username'] ?? $userDetails['email']; + + // Prepare user data for update + $data = [ + 'id' => $userDetails['id'], + 'username' => $userDetails['username'], + 'name' => $userDetails['name'], + 'email' => $userDetails['email'], + 'password' => $userDetails['password'] ?? null, + 'password2' => $userDetails['password2'] ?? null, + 'block' => 0 + ]; + + // set groups if found + if (isset($userDetails['groups']) && ArrayHelper::check($userDetails['groups'])) + { + $data['groups'] = $userDetails['groups']; + } + + // Update the user + if ($model->save($data)) + { + return $userDetails['id']; + } + + $error_messages = ''; + if (method_exists($model, 'getError')) + { + $errors = $model->getError(); + if (!empty($errors)) + { + $error_messages = '
' . implode('
', $errors); + } + } + + throw new \RuntimeException( + Text::sprintf('COM_COMPONENTBUILDER_UPDATE_OF_USER_S_S_FAILEDS', + (string) $userDetails['username'], + (string) $userDetails['email'], + (string) $error_messages + ) + ); + } + + /** + * Method to get an instance of a user for the given id. + * + * @param int $id The id + * + * @return User + * + * @since 5.0.3 + */ + public static function getUserById(int $id): User + { + return new User($id); + } + + /** + * Retrieve the user ID by username. + * + * @param string $username The username to check. + * + * @return int|null The user ID if the user exists, null otherwise. + * + * @since 5.0.3 + */ + public static function getUserIdByUsername(string $username): ?int + { + $userId = JoomlaUserHelper::getUserId($username); + return $userId ?: null; + } + + /** + * Retrieve the user ID by email. + * + * @param string $email The email address to check. + * + * @return int|null The user ID if the user exists, null otherwise. + * + * @since 5.0.3 + */ + public static function getUserIdByEmail(string $email): ?int + { + // Initialise some variables + $db = Factory::getDbo(); + $query = $db->getQuery(true) + ->select($db->quoteName('id')) + ->from($db->quoteName('#__users')) + ->where($db->quoteName('email') . ' = :email') + ->bind(':email', $email) + ->setLimit(1); + $db->setQuery($query); + + $userId = $db->loadResult(); + return $userId ?: null; + } + + /** + * Load the correct user model based on the registration mode. + * + * @param int $mode The registration mode. + * + * @return BaseDatabaseModel The appropriate user model. + * + * @since 5.0.3 + */ + protected static function getModelByMode(int $mode): BaseDatabaseModel + { + if ($mode === 1) + { + return Component::getModel('Registration', 'Site', 'com_users'); + } + + return Component::getModel('User', 'Administrator', 'com_users'); + } + + /** + * Prepare user data array for registration or update. + * + * @param array $credentials User credentials. + * @param int $mode The registration mode. + * + * @return array The prepared user data array. + * + * @since 5.0.3 + */ + protected static function prepareUserData(array $credentials, int $mode) + { + $data = [ + 'username' => $credentials['username'], + 'name' => $credentials['name'], + 'block' => 0 + ]; + + if ($mode === 1) + { + $data['email1'] = $credentials['email']; + } + else + { + $data['email'] = $credentials['email']; + $data['registerDate'] = Factory::getDate()->toSql(); + } + + if ($mode === 1 && empty($credentials['password'])) + { + $credentials['password'] = StringHelper::random(10); + $credentials['password2'] = $credentials['password']; + } + + if (!empty($credentials['password']) && !empty($credentials['password2'])) + { + $data['password1'] = $credentials['password']; + $data['password2'] = $credentials['password2']; + } + + if ($mode === 0 && isset($credentials['groups']) && ArrayHelper::check($credentials['groups'])) + { + $data['groups'] = $credentials['groups']; + } + + return $data; + } + + /** + * Handle the registration process for admin mode. + * + * @param BaseDatabaseModel $model The user model. + * @param array $data The user data. + * + * @return int The ID of the created user. + * + * @since 5.0.3 + */ + private static function adminRegister(BaseDatabaseModel $model, array $data): int + { + $model->save($data); + + return $model->getState('user.id', 0); + } + + /** + * Handle post-registration processes like auto-login. + * + * @param int $userId The ID of the created user. + * @param int $autologin Flag to determine whether to auto-login the user. + * @param array $credentials The user credentials. + * + * @return int The user ID on success. + * + * @since 5.0.3 + */ + private static function handlePostRegistration(int $userId, int $autologin, array $credentials): int + { + if ($autologin && isset($credentials['password'])) + { + try + { + Factory::getApplication()->login($credentials); + } + catch (\Exception $e) + { + // we might need to redirect here? + } + } + + return $userId; + } +} + diff --git a/src/7832a726-87b6-4e95-887e-7b725d3fab8f/code.power b/src/7832a726-87b6-4e95-887e-7b725d3fab8f/code.power new file mode 100644 index 0000000..933a77a --- /dev/null +++ b/src/7832a726-87b6-4e95-887e-7b725d3fab8f/code.power @@ -0,0 +1,381 @@ + /** + * Save user details by either creating a new user or updating an existing user. + * + * @param array $credentials User credentials including 'name', 'username', 'email', 'password', and 'password2'. + * @param int $autologin Flag to determine whether to auto-login the user after registration. + * @param array $params Parameters for user activation, password sending, and user registration allowance. + * @param int $mode Mode of registration: 1 = Site Registration, 0 = Admin Registration, 2 = Custom Helper Method. + * + * @return int User ID on success. + * + * @throws \InvalidArgumentException If required credentials are missing. + * @throws \RuntimeException If the user update or creation fails. + * + * @since 5.0.3 + */ + public static function save(array $credentials, int $autologin = 0, + array $params = ['useractivation' => 0, 'sendpassword' => 1], int $mode = 1): int + { + // can not continue without an email + if (empty($credentials['email'])) + { + throw new \InvalidArgumentException(Text::_('Can not save user without email value.')); + } + + // Ensure the 'username' key exists in the credentials array, set to an empty string if not provided. + $username = $credentials['username'] ?? $credentials['email']; + + // Check for an existing user by email or username. + $existingEmailUserId = static::getUserIdByEmail($credentials['email']); + $existingUserId = static::getUserIdByUsername($username); + + // If either the email or username already exists, handle the update logic. + if ($existingEmailUserId !== null || $existingUserId !== null || (isset($credentials['id']) && $credentials['id'] > 0)) + { + // Prevent updating other users or reusing an email by different users. + if ( + ( + isset($credentials['id']) && + ( + ($existingEmailUserId !== null && $existingEmailUserId != $credentials['id']) || + ($existingUserId !== null && $existingUserId != $credentials['id']) + ) + ) || ($existingUserId !== null && $existingEmailUserId !== null && $existingEmailUserId != $existingUserId) + ) + { + throw new \RuntimeException(Text::sprintf('User ID mismatch detected when trying to save %s (%s) credentials.', $username, $credentials['email'])); + } + + // Update the existing user. + $credentials['id'] = $credentials['id'] ?? $existingEmailUserId ?? $existingUserId; + return static::update($credentials); + } + + // Create a new user if no existing user is found. + return static::create($credentials, $autologin, $params, $mode); + } + + /** + * Create a user and update the given table. + * + * @param array $credentials User credentials including 'name', 'username', 'email', 'password', and 'password2'. + * @param int $autologin Flag to determine whether to auto-login the user after registration. + * @param array $params Parameters for user activation, password sending, and user registration allowance. + * @param int $mode Mode of registration: 1 = Site Registration, 0 = Admin Registration, 2 = Custom Helper Method. + * + * @return int User ID on success. + * + * @throws \RuntimeException If user creation fails. + * + * @since 5.0.3 + */ + public static function create(array $credentials, int $autologin = 0, + array $params = ['useractivation' => 0, 'sendpassword' => 1], int $mode = 1): int + { + $lang = Factory::getLanguage(); + $lang->load('com_users', JPATH_SITE, 'en-GB', true); + + // Handle custom registration mode + if ($mode === 2 && method_exists(ComponentbuilderHelper::class, 'registerUser')) + { + $params['autologin'] = $autologin; + $userId = ComponentbuilderHelper::registerUser($credentials, $params); + + if (is_numeric($userId)) + { + return $userId; + } + + throw new \RuntimeException(Text::_('User creation failed!')); + } + + // Check if we have params/config + if (ArrayHelper::check($params)) + { + // Make changes to user config + foreach ($params as $param => $set) + { + // If you know of a better path, let me know + $params[$param] = Component::setParams($param, $set, 'com_users'); + } + } + + // Fallback to Site Registrations if mode is set to 2 but the method doesn't exist + $mode = $mode === 2 ? 1 : $mode; + + // Load the appropriate user model + $model = static::getModelByMode($mode); + + // Set default values for missing credentials + $credentials['username'] = $credentials['username'] ?? $credentials['email']; + + // Prepare user data + $data = static::prepareUserData($credentials, $mode); + + // Handle user creation + $userId = $mode === 1 ? $model->register($data) : static::adminRegister($model, $data); + + // Check if we have params + if (ArrayHelper::check($params)) + { + // Change user params/config back + foreach ($params as $param => $set) + { + // If you know of a better path, let me know + Component::setParams($param, $set, 'com_users'); + } + } + + if (is_numeric($userId) && $userId > 0) + { + // Handle post-registration processes + return static::handlePostRegistration($userId, $autologin, $credentials); + } + + $error_messages = ''; + if (method_exists($model, 'getError')) + { + $errors = $model->getError(); + if (!empty($errors)) + { + $error_messages = '
' . implode('
', $errors); + } + } + + throw new \RuntimeException( + Text::sprintf('User %s (%s) creation failed!%s', + (string) $credentials['username'], + (string) $credentials['email'], + $error_messages + ) + ); + } + + /** + * Update user details. + * + * @param array $userDetails Array containing user details to be updated. + * + * @return int Updated user ID on success. + * + * @throws \RuntimeException If user update fails. + * + * @since 5.0.3 + */ + public static function update(array $userDetails): int + { + $lang = Factory::getLanguage(); + $lang->load('com_users', JPATH_ADMINISTRATOR, 'en-GB', true); + + $model = Component::getModel('User', 'Administrator', 'com_users'); + + // Set default values for missing credentials + $userDetails['username'] = $userDetails['username'] ?? $userDetails['email']; + + // Prepare user data for update + $data = [ + 'id' => $userDetails['id'], + 'username' => $userDetails['username'], + 'name' => $userDetails['name'], + 'email' => $userDetails['email'], + 'password' => $userDetails['password'] ?? null, + 'password2' => $userDetails['password2'] ?? null, + 'block' => 0 + ]; + + // set groups if found + if (isset($userDetails['groups']) && ArrayHelper::check($userDetails['groups'])) + { + $data['groups'] = $userDetails['groups']; + } + + // Update the user + if ($model->save($data)) + { + return $userDetails['id']; + } + + $error_messages = ''; + if (method_exists($model, 'getError')) + { + $errors = $model->getError(); + if (!empty($errors)) + { + $error_messages = '
' . implode('
', $errors); + } + } + + throw new \RuntimeException( + Text::sprintf('Update of user %s (%s) failed!%s', + (string) $userDetails['username'], + (string) $userDetails['email'], + (string) $error_messages + ) + ); + } + + /** + * Method to get an instance of a user for the given id. + * + * @param int $id The id + * + * @return User + * + * @since 5.0.3 + */ + public static function getUserById(int $id): User + { + return new User($id); + } + + /** + * Retrieve the user ID by username. + * + * @param string $username The username to check. + * + * @return int|null The user ID if the user exists, null otherwise. + * + * @since 5.0.3 + */ + public static function getUserIdByUsername(string $username): ?int + { + $userId = JoomlaUserHelper::getUserId($username); + return $userId ?: null; + } + + /** + * Retrieve the user ID by email. + * + * @param string $email The email address to check. + * + * @return int|null The user ID if the user exists, null otherwise. + * + * @since 5.0.3 + */ + public static function getUserIdByEmail(string $email): ?int + { + // Initialise some variables + $db = Factory::getDbo(); + $query = $db->getQuery(true) + ->select($db->quoteName('id')) + ->from($db->quoteName('#__users')) + ->where($db->quoteName('email') . ' = :email') + ->bind(':email', $email) + ->setLimit(1); + $db->setQuery($query); + + $userId = $db->loadResult(); + return $userId ?: null; + } + + /** + * Load the correct user model based on the registration mode. + * + * @param int $mode The registration mode. + * + * @return BaseDatabaseModel The appropriate user model. + * + * @since 5.0.3 + */ + protected static function getModelByMode(int $mode): BaseDatabaseModel + { + if ($mode === 1) + { + return Component::getModel('Registration', 'Site', 'com_users'); + } + + return Component::getModel('User', 'Administrator', 'com_users'); + } + + /** + * Prepare user data array for registration or update. + * + * @param array $credentials User credentials. + * @param int $mode The registration mode. + * + * @return array The prepared user data array. + * + * @since 5.0.3 + */ + protected static function prepareUserData(array $credentials, int $mode) + { + $data = [ + 'username' => $credentials['username'], + 'name' => $credentials['name'], + 'block' => 0 + ]; + + if ($mode === 1) + { + $data['email1'] = $credentials['email']; + } + else + { + $data['email'] = $credentials['email']; + $data['registerDate'] = Factory::getDate()->toSql(); + } + + if ($mode === 1 && empty($credentials['password'])) + { + $credentials['password'] = StringHelper::random(10); + $credentials['password2'] = $credentials['password']; + } + + if (!empty($credentials['password']) && !empty($credentials['password2'])) + { + $data['password1'] = $credentials['password']; + $data['password2'] = $credentials['password2']; + } + + if ($mode === 0 && isset($credentials['groups']) && ArrayHelper::check($credentials['groups'])) + { + $data['groups'] = $credentials['groups']; + } + + return $data; + } + + /** + * Handle the registration process for admin mode. + * + * @param BaseDatabaseModel $model The user model. + * @param array $data The user data. + * + * @return int The ID of the created user. + * + * @since 5.0.3 + */ + private static function adminRegister(BaseDatabaseModel $model, array $data): int + { + $model->save($data); + + return $model->getState('user.id', 0); + } + + /** + * Handle post-registration processes like auto-login. + * + * @param int $userId The ID of the created user. + * @param int $autologin Flag to determine whether to auto-login the user. + * @param array $credentials The user credentials. + * + * @return int The user ID on success. + * + * @since 5.0.3 + */ + private static function handlePostRegistration(int $userId, int $autologin, array $credentials): int + { + if ($autologin && isset($credentials['password'])) + { + try + { + Factory::getApplication()->login($credentials); + } + catch (\Exception $e) + { + // we might need to redirect here? + } + } + + return $userId; + } \ No newline at end of file diff --git a/src/7832a726-87b6-4e95-887e-7b725d3fab8f/settings.json b/src/7832a726-87b6-4e95-887e-7b725d3fab8f/settings.json new file mode 100644 index 0000000..9f950db --- /dev/null +++ b/src/7832a726-87b6-4e95-887e-7b725d3fab8f/settings.json @@ -0,0 +1,31 @@ +{ + "add_head": "1", + "add_licensing_template": "2", + "extends": "", + "guid": "7832a726-87b6-4e95-887e-7b725d3fab8f", + "implements": null, + "load_selection": { + "load_selection0": { + "load": "0a59c65c-9daf-4bc9-baf4-e063ff9e6a8a" + }, + "load_selection1": { + "load": "1f28cb53-60d9-4db1-b517-3c7dc6b429ef" + } + }, + "name": "UserHelper", + "power_version": "1.0.0", + "system_name": "Joomla.Utilities.UserHelper", + "type": "abstract class", + "use_selection": { + "use_selection0": { + "use": "640b5352-fb09-425f-a26e-cd44eda03f15", + "as": "Component" + } + }, + "extendsinterfaces": null, + "namespace": "[[[NamespacePrefix]]]\\Joomla\\[[[ComponentNamespace]]].Utilities.UserHelper", + "description": "Create & Update User [Save]\r\n\r\n@since 5.0.2", + "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\\Factory;\r\nuse Joomla\\CMS\\Language\\Text;\r\nuse Joomla\\CMS\\User\\User;\r\nuse Joomla\\CMS\\User\\UserHelper as JoomlaUserHelper;\r\nuse Joomla\\CMS\\MVC\\Model\\BaseDatabaseModel;", + "composer": "" +} \ No newline at end of file diff --git a/src/85785701-07b2-4f81-bc1e-0f423700c254/README.md b/src/85785701-07b2-4f81-bc1e-0f423700c254/README.md index 064c3a6..957af58 100644 --- a/src/85785701-07b2-4f81-bc1e-0f423700c254/README.md +++ b/src/85785701-07b2-4f81-bc1e-0f423700c254/README.md @@ -22,7 +22,6 @@ class Subform << (F,LightGreen) >> #RoyalBlue { - purge(array $items, string $indexKey, ...) : void - converter(array $items, array $keySet, ...) : array - process(mixed $items, string $indexKey, ...) : array - - setGuid(string $key, bool $trim = true) : string } note right of Subform::__construct @@ -109,18 +108,6 @@ note left of Subform::process string $linkKey string $linkValue end note - -note right of Subform::setGuid - Returns a GUIDv4 string -Thanks to Dave Pearson (and other) -https://www.php.net/manual/en/function.com-create-guid.php#119168 -Uses the best cryptographically secure method -for all supported platforms with fallback to an older, -less secure version. - - since: 3.0.9 - return: string -end note @enduml ``` diff --git a/src/85785701-07b2-4f81-bc1e-0f423700c254/code.php b/src/85785701-07b2-4f81-bc1e-0f423700c254/code.php index a005218..7025b0b 100644 --- a/src/85785701-07b2-4f81-bc1e-0f423700c254/code.php +++ b/src/85785701-07b2-4f81-bc1e-0f423700c254/code.php @@ -13,6 +13,8 @@ namespace VDM\Joomla\Data; use VDM\Joomla\Interfaces\Data\ItemsInterface as Items; +use VDM\Joomla\Data\Guid; +use VDM\Joomla\Interfaces\Data\GuidInterface; use VDM\Joomla\Interfaces\Data\SubformInterface; @@ -21,10 +23,17 @@ use VDM\Joomla\Interfaces\Data\SubformInterface; * * @since 3.2.2 */ -final class Subform implements SubformInterface +final class Subform implements GuidInterface, SubformInterface { /** - * The ItemsInterface Class. + * The Globally Unique Identifier. + * + * @since 5.0.2 + */ + use Guid; + + /** + * The Items Class. * * @var Items * @since 3.2.2 @@ -42,7 +51,7 @@ final class Subform implements SubformInterface /** * Constructor. * - * @param Items $items The ItemsInterface Class. + * @param Items $items The Items Class. * @param string|null $table The table name. * * @since 3.2.2 @@ -238,7 +247,7 @@ final class Subform implements SubformInterface if (empty($value)) { // set INDEX - $item[$indexKey] = $this->setGuid($indexKey); + $item[$indexKey] = $this->getGuid($indexKey); } break; case 'id': @@ -256,70 +265,6 @@ final class Subform implements SubformInterface } return array_values($items); - } - - /** - * Returns a GUIDv4 string - * - * Thanks to Dave Pearson (and other) - * https://www.php.net/manual/en/function.com-create-guid.php#119168 - * - * Uses the best cryptographically secure method - * for all supported platforms with fallback to an older, - * less secure version. - * - * @param string $key The key to check and modify values. - * @param bool $trim - * - * @return string - * - * @since 3.0.9 - */ - private function setGuid(string $key, bool $trim = true): string - { - // Windows - if (function_exists('com_create_guid')) - { - if ($trim) - { - return trim(\com_create_guid(), '{}'); - } - return \com_create_guid(); - } - - // set the braces if needed - $lbrace = $trim ? "" : chr(123); // "{" - $rbrace = $trim ? "" : chr(125); // "}" - - // OSX/Linux - if (function_exists('openssl_random_pseudo_bytes')) - { - $data = \openssl_random_pseudo_bytes(16); - $data[6] = chr( ord($data[6]) & 0x0f | 0x40); // set version to 0100 - $data[8] = chr( ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 - return $lbrace . vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)) . $lbrace; - } - - // Fallback (PHP 4.2+) - mt_srand((double) microtime() * 10000); - $charid = strtolower( md5( uniqid( rand(), true))); - $hyphen = chr(45); // "-" - $guidv4 = $lbrace. - substr($charid, 0, 8). $hyphen. - substr($charid, 8, 4). $hyphen. - substr($charid, 12, 4). $hyphen. - substr($charid, 16, 4). $hyphen. - substr($charid, 20, 12). - $rbrace; - - // check that it does not already exist (one in a billion chance ;) - // but we do it any way... - if ($this->items->table($this->getTable())->values([$guidv4], $key)) - { - return $this->setGuid($key); - } - - return $guidv4; } } diff --git a/src/85785701-07b2-4f81-bc1e-0f423700c254/code.power b/src/85785701-07b2-4f81-bc1e-0f423700c254/code.power index 9394b02..dfdfdcc 100644 --- a/src/85785701-07b2-4f81-bc1e-0f423700c254/code.power +++ b/src/85785701-07b2-4f81-bc1e-0f423700c254/code.power @@ -1,5 +1,12 @@ /** - * The ItemsInterface Class. + * The Globally Unique Identifier. + * + * @since 5.0.2 + */ + use Guid; + + /** + * The Items Class. * * @var Items * @since 3.2.2 @@ -17,7 +24,7 @@ /** * Constructor. * - * @param Items $items The ItemsInterface Class. + * @param Items $items The Items Class. * @param string|null $table The table name. * * @since 3.2.2 @@ -213,7 +220,7 @@ if (empty($value)) { // set INDEX - $item[$indexKey] = $this->setGuid($indexKey); + $item[$indexKey] = $this->getGuid($indexKey); } break; case 'id': @@ -231,68 +238,4 @@ } return array_values($items); - } - - /** - * Returns a GUIDv4 string - * - * Thanks to Dave Pearson (and other) - * https://www.php.net/manual/en/function.com-create-guid.php#119168 - * - * Uses the best cryptographically secure method - * for all supported platforms with fallback to an older, - * less secure version. - * - * @param string $key The key to check and modify values. - * @param bool $trim - * - * @return string - * - * @since 3.0.9 - */ - private function setGuid(string $key, bool $trim = true): string - { - // Windows - if (function_exists('com_create_guid')) - { - if ($trim) - { - return trim(\com_create_guid(), '{}'); - } - return \com_create_guid(); - } - - // set the braces if needed - $lbrace = $trim ? "" : chr(123); // "{" - $rbrace = $trim ? "" : chr(125); // "}" - - // OSX/Linux - if (function_exists('openssl_random_pseudo_bytes')) - { - $data = \openssl_random_pseudo_bytes(16); - $data[6] = chr( ord($data[6]) & 0x0f | 0x40); // set version to 0100 - $data[8] = chr( ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 - return $lbrace . vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)) . $lbrace; - } - - // Fallback (PHP 4.2+) - mt_srand((double) microtime() * 10000); - $charid = strtolower( md5( uniqid( rand(), true))); - $hyphen = chr(45); // "-" - $guidv4 = $lbrace. - substr($charid, 0, 8). $hyphen. - substr($charid, 8, 4). $hyphen. - substr($charid, 12, 4). $hyphen. - substr($charid, 16, 4). $hyphen. - substr($charid, 20, 12). - $rbrace; - - // check that it does not already exist (one in a billion chance ;) - // but we do it any way... - if ($this->items->table($this->getTable())->values([$guidv4], $key)) - { - return $this->setGuid($key); - } - - return $guidv4; } \ No newline at end of file diff --git a/src/85785701-07b2-4f81-bc1e-0f423700c254/settings.json b/src/85785701-07b2-4f81-bc1e-0f423700c254/settings.json index a9a7946..f6b166b 100644 --- a/src/85785701-07b2-4f81-bc1e-0f423700c254/settings.json +++ b/src/85785701-07b2-4f81-bc1e-0f423700c254/settings.json @@ -4,6 +4,7 @@ "extends": "", "guid": "85785701-07b2-4f81-bc1e-0f423700c254", "implements": [ + "576685fd-263c-46bb-9fdc-1f5eb234cbb6", "34959721-415b-4b5e-8002-3d1fc84b3b2b" ], "load_selection": null, @@ -15,6 +16,10 @@ "use_selection0": { "use": "7212e4db-371f-4cfd-8122-32e9bb100d83", "as": "Items" + }, + "use_selection1": { + "use": "5acded67-0e3d-4c6b-a6ea-b533b076de0c", + "as": "default" } }, "extendsinterfaces": null, diff --git a/src/a5daf189-3345-4b13-8716-c51f686f545b/README.md b/src/a5daf189-3345-4b13-8716-c51f686f545b/README.md index 239925d..f2a7bf6 100644 --- a/src/a5daf189-3345-4b13-8716-c51f686f545b/README.md +++ b/src/a5daf189-3345-4b13-8716-c51f686f545b/README.md @@ -20,6 +20,7 @@ class Data #Gold { + getItem(Container $container) : Item + getItems(Container $container) : Items + getSubform(Container $container) : Subform + + getUsersSubform(Container $container) : UsersSubform + getMultiSubform(Container $container) : MultiSubform } @@ -79,7 +80,14 @@ note left of Data::getSubform return: Subform end note -note right of Data::getMultiSubform +note right of Data::getUsersSubform + Get The Users Subform Class. + + since: 5.0.2 + return: UsersSubform +end note + +note left of Data::getMultiSubform Get The MultiSubform Class. since: 3.2.0 diff --git a/src/a5daf189-3345-4b13-8716-c51f686f545b/code.php b/src/a5daf189-3345-4b13-8716-c51f686f545b/code.php index 7d305c1..37326b7 100644 --- a/src/a5daf189-3345-4b13-8716-c51f686f545b/code.php +++ b/src/a5daf189-3345-4b13-8716-c51f686f545b/code.php @@ -21,6 +21,7 @@ use VDM\Joomla\Data\Action\Delete; use VDM\Joomla\Data\Item; use VDM\Joomla\Data\Items; use VDM\Joomla\Data\Subform; +use VDM\Joomla\Data\UsersSubform; use VDM\Joomla\Data\MultiSubform; @@ -62,6 +63,9 @@ class Data implements ServiceProviderInterface $container->alias(Subform::class, 'Data.Subform') ->share('Data.Subform', [$this, 'getSubform'], true); + $container->alias(UsersSubform::class, 'Data.UsersSubform') + ->share('Data.UsersSubform', [$this, 'getUsersSubform'], true); + $container->alias(MultiSubform::class, 'Data.MultiSubform') ->share('Data.MultiSubform', [$this, 'getMultiSubform'], true); } @@ -182,6 +186,21 @@ class Data implements ServiceProviderInterface ); } + /** + * Get The Users Subform Class. + * + * @param Container $container The DI container. + * + * @return UsersSubform + * @since 5.0.2 + */ + public function getUsersSubform(Container $container): UsersSubform + { + return new UsersSubform( + $container->get('Data.Items') + ); + } + /** * Get The MultiSubform Class. * diff --git a/src/a5daf189-3345-4b13-8716-c51f686f545b/code.power b/src/a5daf189-3345-4b13-8716-c51f686f545b/code.power index bd195dd..89ab5df 100644 --- a/src/a5daf189-3345-4b13-8716-c51f686f545b/code.power +++ b/src/a5daf189-3345-4b13-8716-c51f686f545b/code.power @@ -29,6 +29,9 @@ $container->alias(Subform::class, 'Data.Subform') ->share('Data.Subform', [$this, 'getSubform'], true); + $container->alias(UsersSubform::class, 'Data.UsersSubform') + ->share('Data.UsersSubform', [$this, 'getUsersSubform'], true); + $container->alias(MultiSubform::class, 'Data.MultiSubform') ->share('Data.MultiSubform', [$this, 'getMultiSubform'], true); } @@ -149,6 +152,21 @@ ); } + /** + * Get The Users Subform Class. + * + * @param Container $container The DI container. + * + * @return UsersSubform + * @since 5.0.2 + */ + public function getUsersSubform(Container $container): UsersSubform + { + return new UsersSubform( + $container->get('Data.Items') + ); + } + /** * Get The MultiSubform Class. * diff --git a/src/a5daf189-3345-4b13-8716-c51f686f545b/settings.json b/src/a5daf189-3345-4b13-8716-c51f686f545b/settings.json index 5de45f7..91f0cc6 100644 --- a/src/a5daf189-3345-4b13-8716-c51f686f545b/settings.json +++ b/src/a5daf189-3345-4b13-8716-c51f686f545b/settings.json @@ -40,6 +40,10 @@ "use": "85785701-07b2-4f81-bc1e-0f423700c254", "as": "default" }, + "use_selection8": { + "use": "46b98346-ec98-42b3-a393-96c7d1282b1c", + "as": "default" + }, "use_selection7": { "use": "e0198c3f-777a-4a0b-87b7-e6a198afc8f9", "as": "default" diff --git a/src/bfd1d6d5-56c1-4fe9-9fee-1c5910e1f5d8/code.php b/src/bfd1d6d5-56c1-4fe9-9fee-1c5910e1f5d8/code.php index 2f23b77..f01e9a8 100644 --- a/src/bfd1d6d5-56c1-4fe9-9fee-1c5910e1f5d8/code.php +++ b/src/bfd1d6d5-56c1-4fe9-9fee-1c5910e1f5d8/code.php @@ -7019,7 +7019,7 @@ final class Table extends BaseTable implements Tableinterface 'db' => [ 'type' => 'TINYINT(1)', 'default' => '0', - 'null_switch' => 'NOT NULL', + 'null_switch' => 'NULL', 'unique_key' => false, 'key' => true, ], diff --git a/src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/code.php b/src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/code.php index 24853e8..4b80165 100644 --- a/src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/code.php +++ b/src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/code.php @@ -445,7 +445,10 @@ final class MultiSubform implements MultiSubformInterface { if ($this->validSetMap($map)) { - return $this->setSubformData($subform[$key], $map, [$table => $subform]); + // will delete all existing linked items [IF EMPTY] :( not ideal, but real + $data = (empty($subform[$key]) || !is_array($subform[$key])) ? [] : $subform[$key]; + + return $this->setSubformData($data, $map, [$table => $subform]); } return false; diff --git a/src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/code.power b/src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/code.power index 3c6b90d..463e2cf 100644 --- a/src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/code.power +++ b/src/e0198c3f-777a-4a0b-87b7-e6a198afc8f9/code.power @@ -420,7 +420,10 @@ { if ($this->validSetMap($map)) { - return $this->setSubformData($subform[$key], $map, [$table => $subform]); + // will delete all existing linked items [IF EMPTY] :( not ideal, but real + $data = (empty($subform[$key]) || !is_array($subform[$key])) ? [] : $subform[$key]; + + return $this->setSubformData($data, $map, [$table => $subform]); } return false; diff --git a/super-powers.json b/super-powers.json index c1f70ff..05e95b7 100644 --- a/super-powers.json +++ b/super-powers.json @@ -274,6 +274,17 @@ "spk": "Super---43134867_5cb8_4280_9be8_309fd2fd135f---Power", "guid": "43134867-5cb8-4280-9be8-309fd2fd135f" }, + "46b98346-ec98-42b3-a393-96c7d1282b1c": { + "name": "UsersSubform", + "type": "final class", + "namespace": "VDM\\Joomla\\Data", + "code": "src\/46b98346-ec98-42b3-a393-96c7d1282b1c\/code.php", + "power": "src\/46b98346-ec98-42b3-a393-96c7d1282b1c\/code.power", + "settings": "src\/46b98346-ec98-42b3-a393-96c7d1282b1c\/settings.json", + "path": "src\/46b98346-ec98-42b3-a393-96c7d1282b1c", + "spk": "Super---46b98346_ec98_42b3_a393_96c7d1282b1c---Power", + "guid": "46b98346-ec98-42b3-a393-96c7d1282b1c" + }, "4815e1c7-a433-443d-a112-d1e03d7df84b": { "name": "Database", "type": "class", @@ -362,6 +373,17 @@ "spk": "Super---52a1d14f_304a_431c_8fa4_411179942db5---Power", "guid": "52a1d14f-304a-431c-8fa4-411179942db5" }, + "576685fd-263c-46bb-9fdc-1f5eb234cbb6": { + "name": "GuidInterface", + "type": "interface", + "namespace": "VDM\\Joomla\\Interfaces\\Data", + "code": "src\/576685fd-263c-46bb-9fdc-1f5eb234cbb6\/code.php", + "power": "src\/576685fd-263c-46bb-9fdc-1f5eb234cbb6\/code.power", + "settings": "src\/576685fd-263c-46bb-9fdc-1f5eb234cbb6\/settings.json", + "path": "src\/576685fd-263c-46bb-9fdc-1f5eb234cbb6", + "spk": "Super---576685fd_263c_46bb_9fdc_1f5eb234cbb6---Power", + "guid": "576685fd-263c-46bb-9fdc-1f5eb234cbb6" + }, "584747d1-3a86-453d-b7a3-a2219de8d777": { "name": "Model", "type": "abstract class", @@ -384,6 +406,17 @@ "spk": "Super---59b1a2ea_d77e_4040_ac8c_e65cd8743e9b---Power", "guid": "59b1a2ea-d77e-4040-ac8c-e65cd8743e9b" }, + "5acded67-0e3d-4c6b-a6ea-b533b076de0c": { + "name": "Guid", + "type": "trait", + "namespace": "VDM\\Joomla\\Data", + "code": "src\/5acded67-0e3d-4c6b-a6ea-b533b076de0c\/code.php", + "power": "src\/5acded67-0e3d-4c6b-a6ea-b533b076de0c\/code.power", + "settings": "src\/5acded67-0e3d-4c6b-a6ea-b533b076de0c\/settings.json", + "path": "src\/5acded67-0e3d-4c6b-a6ea-b533b076de0c", + "spk": "Super---5acded67_0e3d_4c6b_a6ea_b533b076de0c---Power", + "guid": "5acded67-0e3d-4c6b-a6ea-b533b076de0c" + }, "5f0205fa-5c43-424a-af7d-abc943c17c8c": { "name": "SchemaChecker", "type": "abstract class", @@ -505,6 +538,17 @@ "spk": "Super---728ee726_3f0f_4762_899d_f8c9430cee58---Power", "guid": "728ee726-3f0f-4762-899d-f8c9430cee58" }, + "7832a726-87b6-4e95-887e-7b725d3fab8f": { + "name": "UserHelper", + "type": "abstract class", + "namespace": "VDM\\Joomla\\Componentbuilder\\Utilities", + "code": "src\/7832a726-87b6-4e95-887e-7b725d3fab8f\/code.php", + "power": "src\/7832a726-87b6-4e95-887e-7b725d3fab8f\/code.power", + "settings": "src\/7832a726-87b6-4e95-887e-7b725d3fab8f\/settings.json", + "path": "src\/7832a726-87b6-4e95-887e-7b725d3fab8f", + "spk": "Super---7832a726_87b6_4e95_887e_7b725d3fab8f---Power", + "guid": "7832a726-87b6-4e95-887e-7b725d3fab8f" + }, "7c1fb50f-8fb1-4627-8705-6fedf7182ca5": { "name": "Upsert", "type": "final class",