diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dd8310b0..cac1d9fbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,6 @@ -# v4.0.4-alpha3 +# v4.0.4-beta1 -- Fix Save failed issue in dynamicGet. #1148 -- Move all [TEXT, EDITOR, TEXTAREA] fields from [NOT NULL] to [NULL] -- Add the DateHelper class and improve the date methods. -- Add simple SessionHelper class. +- Add first classes for the new import engine. # v4.0.4-alpha @@ -13,7 +10,11 @@ - Added new import powers for custom import of spreadsheets. - Move the setDocument and _prepareDocument above the display in the site view and custom admin view. - Update the trashhelper layout to work in Joomla 5. -- Add AllowDynamicProperties (Joomla 4+5) to view class to allow Custom Dynamic Get methods to work without issues. +- Add AllowDynamicProperties (Joomla 4+5) to view class to allow Custom Dynamic Get methods to work without issues. +- Fix Save failed issue in dynamicGet. #1148 +- Move all [TEXT, EDITOR, TEXTAREA] fields from [NOT NULL] to [NULL] +- Add the DateHelper class and improve the date methods. +- Add simple SessionHelper class. # v4.0.3 diff --git a/ComponentbuilderInstallerScript.php b/ComponentbuilderInstallerScript.php index 4355a7fd6..3545b2656 100644 --- a/ComponentbuilderInstallerScript.php +++ b/ComponentbuilderInstallerScript.php @@ -3270,7 +3270,7 @@ class Com_ComponentbuilderInstallerScript implements InstallerScriptInterface echo '
-

Upgrade to Version 4.0.4-alpha3 Was Successful! Let us know if anything is not working as expected.

'; +

Upgrade to Version 4.0.4-beta1 Was Successful! Let us know if anything is not working as expected.

'; // Add/Update component in the action logs extensions table. $this->setActionLogsExtensions(); diff --git a/README.md b/README.md index 603d690cf..f841cc40b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The Component Builder for [Joomla](https://extensions.joomla.org/extension/compo Whether you're a seasoned [Joomla](https://extensions.joomla.org/extension/component-builder/) developer, or have just started, Component Builder will save you lots of time and money. A real must have! -You can install it quite easily and with no limitations. On [gitea](https://git.vdm.dev/joomla/Component-Builder/tags) is the latest release (4.0.4-alpha3) with **ALL** its features and **ALL** concepts totally open-source and free! +You can install it quite easily and with no limitations. On [gitea](https://git.vdm.dev/joomla/Component-Builder/tags) is the latest release (4.0.4-beta1) with **ALL** its features and **ALL** concepts totally open-source and free! > Watch Quick Build of a Hello World component in [JCB on Youtube](https://www.youtube.com/watch?v=IQfsLYIeblk&list=PLQRGFI8XZ_wtGvPQZWBfDzzlERLQgpMRE&index=45) @@ -144,14 +144,14 @@ TODO + *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io) + *Name*: [Component Builder](https://git.vdm.dev/joomla/Component-Builder) + *First Build*: 30th April, 2015 -+ *Last Build*: 13th October, 2024 -+ *Version*: 4.0.4-alpha3 ++ *Last Build*: 7th November, 2024 ++ *Version*: 4.0.4-beta1 + *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved. + *License*: GNU General Public License version 2 or later; see LICENSE.txt -+ *Line count*: **869405** ++ *Line count*: **888054** + *Field count*: **2098** -+ *File count*: **6007** -+ *Folder count*: **621** ++ *File count*: **6207** ++ *Folder count*: **624** > This **component** was build with a [Joomla](https://extensions.joomla.org/extension/component-builder/) [Automated Component Builder](https://www.joomlacomponentbuilder.com). > Developed by [Llewellyn van der Merwe](mailto:joomla@vdm.io) diff --git a/admin/README.txt b/admin/README.txt index 603d690cf..f841cc40b 100644 --- a/admin/README.txt +++ b/admin/README.txt @@ -9,7 +9,7 @@ The Component Builder for [Joomla](https://extensions.joomla.org/extension/compo Whether you're a seasoned [Joomla](https://extensions.joomla.org/extension/component-builder/) developer, or have just started, Component Builder will save you lots of time and money. A real must have! -You can install it quite easily and with no limitations. On [gitea](https://git.vdm.dev/joomla/Component-Builder/tags) is the latest release (4.0.4-alpha3) with **ALL** its features and **ALL** concepts totally open-source and free! +You can install it quite easily and with no limitations. On [gitea](https://git.vdm.dev/joomla/Component-Builder/tags) is the latest release (4.0.4-beta1) with **ALL** its features and **ALL** concepts totally open-source and free! > Watch Quick Build of a Hello World component in [JCB on Youtube](https://www.youtube.com/watch?v=IQfsLYIeblk&list=PLQRGFI8XZ_wtGvPQZWBfDzzlERLQgpMRE&index=45) @@ -144,14 +144,14 @@ TODO + *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io) + *Name*: [Component Builder](https://git.vdm.dev/joomla/Component-Builder) + *First Build*: 30th April, 2015 -+ *Last Build*: 13th October, 2024 -+ *Version*: 4.0.4-alpha3 ++ *Last Build*: 7th November, 2024 ++ *Version*: 4.0.4-beta1 + *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved. + *License*: GNU General Public License version 2 or later; see LICENSE.txt -+ *Line count*: **869405** ++ *Line count*: **888054** + *Field count*: **2098** -+ *File count*: **6007** -+ *Folder count*: **621** ++ *File count*: **6207** ++ *Folder count*: **624** > This **component** was build with a [Joomla](https://extensions.joomla.org/extension/component-builder/) [Automated Component Builder](https://www.joomlacomponentbuilder.com). > Developed by [Llewellyn van der Merwe](mailto:joomla@vdm.io) diff --git a/admin/language/en-GB/en-GB.com_componentbuilder.ini b/admin/language/en-GB/en-GB.com_componentbuilder.ini index ef64933a9..2e5b57b84 100644 --- a/admin/language/en-GB/en-GB.com_componentbuilder.ini +++ b/admin/language/en-GB/en-GB.com_componentbuilder.ini @@ -4570,6 +4570,7 @@ COM_COMPONENTBUILDER_DYNAMIC_GET_YES="Yes" COM_COMPONENTBUILDER_DYNAMIC_GET_YY="yy" COM_COMPONENTBUILDER_DYNAMIC_GET_Z="z" COM_COMPONENTBUILDER_DYNAMIC_GET_ZZ="zz" +COM_COMPONENTBUILDER_D_ROWS_PROCESSED_SUCCESS_RATE_TWOF_IMPORT_SUCCESSFUL="%d rows processed. Success rate: %.2f%%. Import successful!" COM_COMPONENTBUILDER_EDIT="Edit" COM_COMPONENTBUILDER_EDITCREATE_SITE_VIEW="Edit/Create Site View" COM_COMPONENTBUILDER_EDITING="Editing" @@ -5440,6 +5441,7 @@ COM_COMPONENTBUILDER_ID="id" COM_COMPONENTBUILDER_ID_MISMATCH_WAS_DETECTED_WITH_THE_SSSS_GUI_CODE_FIELD_SO_THE_PLACEHOLDER_WAS_NOT_SET="ID mismatch was detected with the %s.%s.%s.%s GUI code field. So the placeholder was not set." COM_COMPONENTBUILDER_IEMAILI_BSB="Email: %s" COM_COMPONENTBUILDER_IMPORT_BY_GUID_ONLY="Import by GUID only!" +COM_COMPONENTBUILDER_IMPORT_FAILED_D_ROWS_PROCESSED_WITH_ONLY_D_SUCCESSES_ERROR_RATE_TWOF="Import failed. %d rows processed with only %d successes. Error rate: %.2f%%." COM_COMPONENTBUILDER_IMPORT_S="Import %s" COM_COMPONENTBUILDER_IMPORT_SELECT_FILE_FOR_JOOMLA_COMPONENTS="Select the file to import data to joomla_components." COM_COMPONENTBUILDER_IMPORT_SELECT_FILE_FOR_LANGUAGE_TRANSLATIONS="Select the file to import data to language_translations." @@ -7598,6 +7600,7 @@ COM_COMPONENTBUILDER_NO_NAMESPACE_FOUND="No Namespace Found" COM_COMPONENTBUILDER_NO_NEED_TO_GET_IT_SINCE_IT_IS_ALREADY_IN_SYNC_WITH_YOUR_LOCAL_VERSION="No need to get it since it is already in sync with your local version" COM_COMPONENTBUILDER_NO_PATHS_FOUND="No Paths Found" COM_COMPONENTBUILDER_NO_RESULTS_MATCH="No results match" +COM_COMPONENTBUILDER_NO_ROWS_WERE_PROCESSED="No rows were processed." COM_COMPONENTBUILDER_NO_SELECTION_DETECTED="No selection detected" COM_COMPONENTBUILDER_NO_SNIPPETS_WERE_SELECTED_PLEASE_MAKE_A_SELECTION_AND_TRY_AGAIN="No snippets were selected, please make a selection and try again!" COM_COMPONENTBUILDER_NO_S_FOUND="No %s Found" diff --git a/admin/src/Controller/CompilerController.php b/admin/src/Controller/CompilerController.php index 0dccab7a0..7917dcdc4 100644 --- a/admin/src/Controller/CompilerController.php +++ b/admin/src/Controller/CompilerController.php @@ -21,6 +21,7 @@ use VDM\Joomla\Componentbuilder\Compiler\Factory as CFactory; use Joomla\CMS\Version; use VDM\Joomla\Componentbuilder\File\Factory as FileFactory; use VDM\Joomla\Componentbuilder\Import\Factory as ImportFactory; +use VDM\Joomla\Abstraction\Console\Import; use VDM\Joomla\Utilities\ArrayHelper as UtilitiesArrayHelper; use VDM\Joomla\Utilities\StringHelper; use Joomla\CMS\Uri\Uri; @@ -76,6 +77,8 @@ class CompilerController extends AdminController * FileFactory * Adding this so that the import factory gets build for Super Powers * ImportFactory + * Adding this so that the import cli gets build for Super Powers + * Import */ /** diff --git a/componentbuilder.xml b/componentbuilder.xml index 0c1ee6f0b..9d5edc842 100644 --- a/componentbuilder.xml +++ b/componentbuilder.xml @@ -1,15 +1,15 @@ COM_COMPONENTBUILDER - 13th October, 2024 + 7th November, 2024 Llewellyn van der Merwe joomla@vdm.io https://dev.vdm.io Copyright (C) 2015 Vast Development Method. All rights reserved. GNU General Public License version 2 or later; see LICENSE.txt - 4.0.4-alpha3 + 4.0.4-beta1 Component Builder (v.4.0.4-alpha3) +

Component Builder (v.4.0.4-beta1)

The Component Builder for [Joomla](https://extensions.joomla.org/extension/component-builder/) is highly advanced tool that is truly able to build extremely complex components in a fraction of the time. diff --git a/componentbuilder_update_server.xml b/componentbuilder_update_server.xml index 11ebe41a7..2f7ce3a5a 100644 --- a/componentbuilder_update_server.xml +++ b/componentbuilder_update_server.xml @@ -113,13 +113,13 @@ pkg_component_builder package site - 4.0.4-alpha3 + 4.0.4-beta1 https://dev.vdm.io - https://git.vdm.dev/api/v1/repos/joomla/pkg-component-builder/archive/v4.0.4-alpha3.zip + https://git.vdm.dev/api/v1/repos/joomla/pkg-component-builder/archive/v4.0.4-beta1.zip - alpha + beta Llewellyn van der Merwe https://dev.vdm.io diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/BaseTable.php b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/BaseTable.php index fe497f6e6..7f48355ad 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/BaseTable.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/BaseTable.php @@ -12,7 +12,7 @@ namespace VDM\Joomla\Abstraction; -use VDM\Joomla\Interfaces\Tableinterface; +use VDM\Joomla\Interfaces\TableInterface; /** @@ -20,7 +20,7 @@ use VDM\Joomla\Interfaces\Tableinterface; * * @since 3.2.0 */ -abstract class BaseTable implements Tableinterface +abstract class BaseTable implements TableInterface { /** * All areas/views/tables with their field details diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Console/Import.php b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Console/Import.php new file mode 100644 index 000000000..6fa74e8e3 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Console/Import.php @@ -0,0 +1,258 @@ + + * @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\Abstraction\Console; + + +use Joomla\CMS\Factory; +use Joomla\Console\Command\AbstractCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use VDM\Joomla\Componentbuilder\Import\Factory as ImportFactory; +use VDM\Joomla\Componentbuilder\Interfaces\Spreadsheet\ImportCliInterface as ImportEngine; +use VDM\Joomla\Data\Items; +use VDM\Joomla\Utilities\Component\Helper; + + +/** + * Console Import + * + * @since 5.0.2 + */ +abstract class Import extends AbstractCommand +{ + /** + * The Items Class. + * + * @var Items + * @since 5.0.2 + */ + protected Items $items; + + /** + * The Import Class. + * + * @var ImportEngine + * @since 5.0.2 + */ + protected ImportEngine $import; + + /** + * The queue table name. + * + * @var string + * @since 5.0.2 + */ + protected string $queueTable; + + /** + * The queue status field + * + * @var string + * @since 5.0.2 + */ + protected string $queueStatusField; + + /** + * The queue awaiting status + * + * @var int + * @since 5.0.2 + */ + protected int $queueWaitState; + + /** + * The queue processing status + * + * @var int + * @since 5.0.2 + */ + protected int $queueProcessingState; + + /** + * The main import target name. + * + * @var string + * @since 5.0.2 + */ + protected string $targetName; + + /** + * The target import class. + * + * @var string + * @since 5.0.2 + */ + protected string $targetImportClass; + + /** + * The default command name. + * + * @var string + * @since 5.0.2 + */ + protected static $defaultName; + + /** + * Constructor. + * + * @param string|null $name The name of the command; if the name is empty and no default is set, a name must be set in the configure() method + * + * @since 5.0.2 + */ + public function __construct(?string $name = null) + { + // make sure we know what component we are working with + Helper::setOption('com_componentbuilder'); + + // Load administrator language file for backend + $lang = Factory::getLanguage(); + $lang->load('com_componentbuilder', JPATH_ADMINISTRATOR); + + $this->items = ImportFactory::_('Data.Items'); + $this->import = ImportFactory::_($this->targetImportClass); + + parent::__construct($name); + } + + /** + * Configures the CLI command, setting up the description and help text. + * + * This command parses the import queue and imports items that are still in the queue. + * It is useful for automatically processing pending item imports in the virtual warehouse. + * + * @return void + * @since 5.0.2 + */ + protected function configure(): void + { + $this->setDescription("Processes the import queue and {$this->targetName} imports all spreadsheets that are still in the queue."); + $this->setHelp( +<<%command.name% command parses the import queue and processes all {$this->targetName} spreadsheets that are still pending import. +This is useful for keeping the system up-to-date with incoming data. + +Usage: +php joomla.php %command.name% +EOF); + } + + /** + * Executes the CLI command, processing each spreadsheet in the import queue. + * + * @param InputInterface $input The input to inject into the command. + * @param OutputInterface $output The output to inject into the command. + * + * @return int The command exit code (0 for success). + * @since 5.0.2 + */ + protected function doExecute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + // Output the title for the task + $io->title("Component Builder: {$this->targetName} import status"); + + // Get all imports in the queue that are in waiting state + if (($queue = $this->items->table($this->queueTable)->get([$this->queueWaitState], $this->queueStatusField)) === null) + { + // Get the current date and time + $timestamp = date('Y-m-d H:i:s'); + + // Output the notice of no imports to be done + $io->info("No {$this->targetName} imports found in the queue. Idle at {$timestamp}."); + + return 0; + } + + // take spreadsheets out of queue + $this->items->table($this->queueTable)->set(array_map(function($item) { + return [ + 'guid' => $item->guid, + $this->queueStatusField => $this->queueProcessingState + ]; + }, $queue)); + + // size of the queue + $numberSteps = count((array) $queue); + + // Output initial task information + $io->info("Initiating import for {$numberSteps} {$this->targetName} spreadsheet(s) in the queue."); + $io->newLine(2); + + // Create a progress bar for the overall import process + $progressBar = $io->createProgressBar($numberSteps); + $progressBar->start(); + + // Track success and failure counts + $successCount = 0; + $failureCount = 0; + + // Import one spreadsheet at a time + foreach ($queue as $spreadsheet) + { + $io->newLine(2); + + // Output the current spreadsheet being processed + $io->section("Processing spreadsheet #{$spreadsheet->guid}..."); + + // Import the data found in the spreadsheet + $this->import->data($spreadsheet); + + // Get the completion message + $completion = $this->import->message(); + + // Track success based on completion message + if ($completion->message_success) + { + $successCount++; + + // Output the success message for this spreadsheet + $io->success($completion->message_success); + } + + // Track failure based on completion message + if ($completion->message_error) + { + $failureCount++; + + // Output the error message for this spreadsheet + $io->error($completion->message_error); + } + + // Advance the main progress bar by one step + sleep(1); + $progressBar->advance(); + $io->newLine(1); + } + + // Finish the main progress bar + $progressBar->finish(); + $io->newLine(2); + + // Calculate the success and failure percentages + $totalProcessed = $successCount + $failureCount; + $successRate = ($totalProcessed > 0) ? round(($successCount / $totalProcessed) * 100) : 0; + $failureRate = ($totalProcessed > 0) ? round(($failureCount / $totalProcessed) * 100) : 0; + + // Get the current date and time + $timestamp = date('Y-m-d H:i:s'); + + // Output the success and failure summary with the timestamp + $io->info("The {$this->targetName} import finished: {$successRate}% success, {$failureRate}% failure. Completed at {$timestamp}."); + + $io->newLine(1); + + return 0; + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Console/index.html b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Console/index.html new file mode 100644 index 000000000..fa6d84e80 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Console/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Database.php b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Database.php index 7e2a21657..60a062ebd 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Database.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Database.php @@ -38,6 +38,14 @@ abstract class Database */ protected string $table; + /** + * Date format to return + * + * @var string + * @since 5.0.2 + */ + protected string $dateFormat = 'Y-m-d H:i:s'; + /** * Constructor * @@ -62,23 +70,32 @@ abstract class Database **/ protected function quote($value) { - if ($value === null) // hmm the null does pose an issue (will keep an eye on this) + if ($value === null) { return 'NULL'; } if (is_numeric($value)) { + // If the value is a numeric string (e.g., "0123"), treat it as a string to preserve the format + if (is_string($value) && ltrim($value, '0') !== $value) + { + return $this->db->quote($value); + } + if (filter_var($value, FILTER_VALIDATE_INT)) { return (int) $value; } - elseif (filter_var($value, FILTER_VALIDATE_FLOAT)) + + if (filter_var($value, FILTER_VALIDATE_FLOAT)) { return (float) $value; } } - elseif (is_bool($value)) // not sure if this will work well (but its correct) + + // Handle boolean values + if (is_bool($value)) { return $value ? 'TRUE' : 'FALSE'; } @@ -86,10 +103,10 @@ abstract class Database // For date and datetime values if ($value instanceof \DateTime) { - return $this->db->quote($value->format('Y-m-d H:i:s')); + return $this->db->quote($value->format($this->getDateFormat())); } - // For other data types, just escape it + // For other types of values, quote as string return $this->db->quote($value); } @@ -110,6 +127,17 @@ abstract class Database } return $table; + } + + /** + * Get the date format to return in the quote + * + * @return string + * @since 5.0.2 + **/ + protected function getDateFormat(): string + { + return $this->dateFormat; } } diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Model.php b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Model.php index ccd4d40b0..d695f6ee3 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Model.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Model.php @@ -14,7 +14,7 @@ namespace VDM\Joomla\Abstraction; use VDM\Joomla\Utilities\StringHelper; use VDM\Joomla\Utilities\ArrayHelper; -use VDM\Joomla\Interfaces\Tableinterface as Table; +use VDM\Joomla\Interfaces\TableInterface as Table; use VDM\Joomla\Interfaces\ModelInterface; @@ -109,7 +109,7 @@ abstract class Model implements ModelInterface /** * Model a value of multiple items - * Example: $this->items(Array, 'value_key', 'table_name'); + * Example: $this->values(Array, 'value_key', 'table_name'); * * @param array|null $items The array of values * @param string $field The field key diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Schema.php b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Schema.php index 18e7e02ce..0eb4281e6 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Schema.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Schema.php @@ -14,7 +14,7 @@ namespace VDM\Joomla\Abstraction; use Joomla\CMS\Factory; use Joomla\CMS\Version; -use VDM\Joomla\Interfaces\Tableinterface as Table; +use VDM\Joomla\Interfaces\TableInterface as Table; use VDM\Joomla\Interfaces\SchemaInterface; diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/SchemaChecker.php b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/SchemaChecker.php index aad47d7d2..6c6aef220 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/SchemaChecker.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/SchemaChecker.php @@ -14,7 +14,7 @@ namespace VDM\Joomla\Abstraction; use Joomla\CMS\Factory; use VDM\Joomla\Interfaces\SchemaInterface as Schema; -use VDM\Joomla\Interfaces\Tableinterface as Table; +use VDM\Joomla\Interfaces\TableInterface as Table; use VDM\Joomla\Utilities\ClassHelper; use VDM\Joomla\Interfaces\SchemaCheckerInterface; diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Assessor.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Assessor.php new file mode 100644 index 000000000..bb986743a --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Assessor.php @@ -0,0 +1,130 @@ + + * @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\Import; + + +use Joomla\CMS\Language\Text; +use VDM\Joomla\Componentbuilder\Import\Data; +use VDM\Joomla\Componentbuilder\Interfaces\ImportStatusInterface as Status; +use VDM\Joomla\Componentbuilder\Interfaces\ImportMessageInterface as Message; +use VDM\Joomla\Componentbuilder\Interfaces\ImportAssessorInterface; + + +/** + * Import Assessor Class + * + * @since 4.0.3 + */ +final class Assessor implements ImportAssessorInterface +{ + /** + * The Data Class. + * + * @var Data + * @since 4.0.3 + */ + protected Data $data; + + /** + * The Import Status Class. + * + * @var Status + * @since 4.0.3 + */ + protected Status $status; + + /** + * The Import Message Class. + * + * @var Message + * @since 4.0.3 + */ + protected Message $message; + + /** + * Constants for defining the success threshold + * Minimum success rate to consider the import successful + * + * @since 4.0.3 + */ + private const SUCCESS_THRESHOLD = 0.80; + + /** + * Constructor. + * + * @param Data $data The Data Class. + * @param Status $status The Import Status Class. + * @param Message $message The Import Message Class. + * + * @since 4.0.3 + */ + public function __construct(Data $data, Status $status, Message $message) + { + $this->data = $data; + $this->status = $status; + $this->message = $message; + } + + /** + * Evaluates the import process and sets the success/error message based on the success rate. + * + * @param int $rowCounter Total number of rows processed. + * @param int $successCounter Number of successfully processed rows. + * @param int $errorCounter Number of rows that failed to process. + * + * @return void + * @since 4.0.3 + */ + public function evaluate(int $rowCounter, int $successCounter, int $errorCounter): void + { + // No rows processed case + if ($rowCounter === 0) + { + $this->message->addError(Text::_('COM_COMPONENTBUILDER_NO_ROWS_WERE_PROCESSED')); + + if (($guid = $this->data->get('import.guid')) !== null) + { + $this->status->set(4, $guid); // Status 4 => completed with errors + } + return; + } + + $successRate = $successCounter / $rowCounter; + $errorRate = (1 - $successRate) * 100; + $successPercentage = $successRate * 100; + + // Determine appropriate message based on success rate + if ($successRate >= self::SUCCESS_THRESHOLD) + { + $this->message->addSuccess(Text::sprintf('COM_COMPONENTBUILDER_D_ROWS_PROCESSED_SUCCESS_RATE_TWOF_IMPORT_SUCCESSFUL', + $rowCounter, + $successPercentage + )); + } + else + { + $this->message->addError(Text::sprintf('COM_COMPONENTBUILDER_IMPORT_FAILED_D_ROWS_PROCESSED_WITH_ONLY_D_SUCCESSES_ERROR_RATE_TWOF', + $rowCounter, + $successCounter, + $errorRate + )); + } + + if (($guid = $this->data->get('import.guid')) !== null) + { + // Update import status based on success rate + $importStatus = ($successPercentage == 100) ? 3 : 4; // 3 => completed, 4 => completed with errors + $this->status->set($importStatus, $guid); + } + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Data.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Data.php new file mode 100644 index 000000000..962de5be4 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Data.php @@ -0,0 +1,26 @@ + + * @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\Import; + + +use VDM\Joomla\Abstraction\Registry; + + +/** + * Import Data Registry + * + * @since 3.2.0 + */ +class Data extends Registry +{ +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Factory.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Factory.php index a2ceb5d8e..95237bcc2 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Factory.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Factory.php @@ -12,10 +12,12 @@ namespace VDM\Joomla\Componentbuilder\Import; +use Joomla\DI\Container; use VDM\Joomla\Service\Table; use VDM\Joomla\Service\Database; use VDM\Joomla\Service\Model; use VDM\Joomla\Service\Data; +use VDM\Joomla\Componentbuilder\Import\Service\Import; use VDM\Joomla\Componentbuilder\File\Service\File; use VDM\Joomla\Componentbuilder\Service\Spreadsheet; use VDM\Joomla\Interfaces\FactoryInterface; @@ -50,6 +52,7 @@ abstract class Factory extends ExtendingFactory implements FactoryInterface ->registerServiceProvider(new Database()) ->registerServiceProvider(new Model()) ->registerServiceProvider(new Data()) + ->registerServiceProvider(new Import()) ->registerServiceProvider(new File()) ->registerServiceProvider(new Spreadsheet()); } diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Item.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Item.php new file mode 100644 index 000000000..b41fbe313 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Item.php @@ -0,0 +1,270 @@ + + * @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\Import; + + +use VDM\Joomla\Interfaces\TableValidatorInterface as Validator; +use VDM\Joomla\Interfaces\Data\ItemInterface as DataItem; +use VDM\Joomla\Componentbuilder\Interfaces\ImportRowInterface as Row; +use VDM\Joomla\Utilities\GuidHelper; +use VDM\Joomla\Componentbuilder\Interfaces\ImportItemInterface; + + +/** + * Import Item Class + * + * @since 4.0.3 + */ +final class Item implements ImportItemInterface +{ + /** + * The Table Validator Class. + * + * @var Validator + * @since 4.0.3 + */ + protected Validator $validator; + + /** + * The Item Class. + * + * @var Item + * @since 4.0.3 + */ + protected DataItem $item; + + /** + * The Import Row Class. + * + * @var Row + * @since 4.0.3 + */ + protected Row $row; + + /** + * Constructor. + * + * @param Validator $validator The Table ValidatorI Class. + * @param DataItem $item The Item Class. + * @param Row $row The Import Row Class. + * + * @since 4.0.3 + */ + public function __construct(Validator $validator, DataItem $item, Row $row) + { + $this->validator = $validator; + $this->item = $item; + $this->row = $row; + } + + /** + * Get the item from the import row values and ensure it is valid + * + * @param string $table The table these columns belongs to. + * @param array $columns The columns to extract. + * + * @return array|null + * @since 4.0.3 + */ + public function get(string $table, array $columns): ?array + { + $item = []; + foreach ($columns as $column => $map) + { + if (($value = $this->row->getValue($column)) !== null && !isset($item[$map['name']])) + { + // get the valid importable value + $item[$map['name']] = $this->getImportValue($value, $map['name'], $table, $map['link'] ?? null); + + // remove value from global row values set + $this->row->unsetValue($column); + } + } + + return $item ?? null; + } + + /** + * Get the correct value needed for the import of the related row (item). + * + * @param mixed $value The value from the row. + * @param string $field The field name where the value is being stored. + * @param string $table The table this field belongs to. + * @param array $link The field link values. + * + * @return mixed + * @since 4.0.3 + */ + private function getImportValue($value, string $field, string $table, ?array $link) + { + // Validate the link array and return the original value if invalid + if (empty($link) || $link['type'] !== 1 || empty($link['table']) || empty($link['key']) || empty($link['value'])) + { + return $this->validImportValue($value, $field, $table); + } + + // Handle GUID key with validation via GuidHelper + if ($link['key'] === 'guid' && GuidHelper::item($value, $link['table'])) + { + return $value; + } + + // Handle numeric ID with validation + if ($link['key'] === 'id' && is_numeric($value) && $this->isValueExists($value, $link)) + { + return (int) $value; + } + + // Attempt to retrieve the local value + $local_value = $this->getLocalValue($value, $link); + + // If no local value exists, create it if necessary + if ($local_value === null) + { + $local_value = $this->setLocalValue($value, $link); + } + + return $this->validImportValue($local_value, $field, $table); + } + + /** + * Make sure we have a valid import value + * + * @param mixed $value The value. + * @param string $field The field name where the value is being stored. + * @param string $table The table this field belongs to. + * + * @return mixed + * @since 4.0.3 + */ + private function validImportValue($value, string $field, string $table) + { + // make sure our value will fit in the database table datatype + return $this->validator->getValid($value, $field, $table); + } + + /** + * Helper function to get the local value from the database table. + * + * @param mixed $value The value to search for. + * @param array $link The field link details. + * + * @return mixed|null The local value or null if not found. + * @since 4.0.3 + */ + private function getLocalValue($value, array $link) + { + // Attempt to retrieve the value based on the link['value'] and link['key'] + $local_value = $this->item->table($link['table'])->value($value, $link['value'], $link['key']); + + // If not found, try retrieving by link['key'] and link['key'] + if ($local_value === null && $this->isValueExists($value, $link)) + { + return $value; + } + + return $local_value; + } + + /** + * Check if the value exists in the table for the given link. + * + * @param mixed $value The value to check. + * @param array $link The field link details. + * + * @return bool True if the value exists, false otherwise. + * @since 4.0.3 + */ + private function isValueExists($value, array $link): bool + { + return $this->item->table($link['table'])->value($value, $link['key'], $link['key']) !== null; + } + + /** + * Create a new value in the database table if it doesn't already exist. + * + * @param mixed $value The value to create. + * @param array $link The field link details. + * + * @return mixed|null The newly created value or null if creation failed. + * @since 4.0.3 + */ + private function setLocalValue($value, array $link) + { + // Handle GUID creation if the provided value is not valid + if ($link['key'] === 'guid') + { + if (!GuidHelper::valid($value)) + { + return $this->insertItemWithGuid($value, $link); + } + return null; + } + + // Handle ID creation + if ($link['key'] === 'id') + { + if (!is_numeric($value)) + { + return $this->insertItemWithId($value, $link); + } + return null; + } + + // could not create local item (we don't have enough details) + return null; + } + + /** + * Insert a new item with a GUID. + * + * @param mixed $value The value to insert. + * @param array $link The field link details. + * + * @return string|null The new GUID or null if insertion failed. + * @since 4.0.3 + */ + private function insertItemWithGuid($value, array $link): ?string + { + $guid = GuidHelper::get(); + $item = (object) [$link['value'] => $value, $link['key'] => $guid]; + + if ($this->item->table($link['table'])->set($item, $link['key'], 'insert')) + { + return $guid; + } + + return null; + } + + /** + * Insert a new item with a non-numeric ID. + * + * @param mixed $value The value to insert. + * @param array $link The field link details. + * + * @return mixed|null The new ID or null if insertion failed. + * @since 4.0.3 + */ + private function insertItemWithId($value, array $link) + { + $item = (object) [$link['key'] => 0, $link['value'] => $value]; + + if ($this->item->table($link['table'])->set($item, $link['key'], 'insert')) + { + return $this->item->table($link['table'])->value($value, $link['value'], $link['key']); + } + + return null; + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Mapper.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Mapper.php new file mode 100644 index 000000000..34626ce49 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Mapper.php @@ -0,0 +1,154 @@ + + * @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\Import; + + +use VDM\Joomla\Interfaces\TableInterface as Table; +use VDM\Joomla\Componentbuilder\Interfaces\ImportMapperInterface; + + +/** + * Import Mapper Class + * + * @since 4.0.3 + */ +final class Mapper implements ImportMapperInterface +{ + /** + * The Table Class. + * + * @var Table + * @since 4.0.3 + */ + protected Table $table; + + /** + * The current parent table map. + * + * @var array + * @since 4.0.3 + */ + private array $parent = []; + + /** + * The current join tables map. + * + * @var array + * @since 4.0.3 + */ + private array $join = []; + + /** + * Constructor. + * + * @param Table $table The Table Class. + * + * @since 4.0.3 + */ + public function __construct(Table $table) + { + $this->table = $table; + } + + /** + * Set the tables mapper + * + * @param object $map The import file map. + * @param string $parentTable The parent table name. + * + * @return void + * @since 4.0.3 + */ + public function set(object $map, string $parentTable): void + { + // always reset these + $this->parent = []; + $this->join = []; + + foreach ($map as $row) + { + $target = $row->target ?? null; + + if (empty($target)) + { + continue; + } + + if (($tm = $this->getTableField($target)) !== null) + { + $field = $this->table->get($tm->table, $tm->field); + if ($tm->table === $parentTable) + { + $this->parent[$row->column] = $field; + } + else + { + $this->join[$tm->table][$row->column] = $field; + } + } + } + } + + /** + * Get the parent table keys + * + * @return array + * @since 4.0.3 + */ + public function getParent(): array + { + return $this->parent; + } + + /** + * Get the join tables keys + * + * @return array + * @since 4.0.3 + */ + public function getJoin(): array + { + return $this->join; + } + + /** + * Get the table and field name + * + * @param string $key The import file key. + * + * @return object|null + * @since 4.0.3 + */ + private function getTableField(string $key): ?object + { + // Find the position of the first dot + $dotPosition = strpos($key, '.'); + + // If no dot is found, return the whole string + if ($dotPosition === false) + { + return null; + } + + // Extract the table (before the dot) and the field (after the dot) + $table = substr($key, 0, $dotPosition); + $field = substr($key, $dotPosition + 1); + + if ($this->table->exist($table ?? '_error', $field)) + { + return (object) ['table' => $table, 'field' => $field]; + } + + return null; + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Message.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Message.php new file mode 100644 index 000000000..43cd7be3f --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Message.php @@ -0,0 +1,307 @@ + + * @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\Import; + + +use VDM\Joomla\Interfaces\Data\UpdateInterface as Update; +use VDM\Joomla\Interfaces\Data\InsertInterface as Insert; +use VDM\Joomla\Utilities\GuidHelper; +use VDM\Joomla\Componentbuilder\Interfaces\ImportMessageInterface; + + +/** + * Import Messages Class + * + * @since 5.0.2 + */ +final class Message implements ImportMessageInterface +{ + /** + * The Update Class. + * + * @var Update + * @since 5.0.2 + */ + protected Update $update; + + /** + * The Insert Class. + * + * @var Insert + * @since 5.0.2 + */ + protected Insert $insert; + + /** + * The success message bus. + * + * @var array + * @since 5.0.2 + */ + private array $success = []; + + /** + * The info message bus. + * + * @var array + * @since 5.0.2 + */ + private array $info = []; + + /** + * The error message bus. + * + * @var array + * @since 5.0.2 + */ + private array $error = []; + + /** + * The entity GUID value. + * + * @var string + * @since 5.0.2 + */ + private ?string $guid = null; + + /** + * The entity type value. + * + * @var string|null + * @since 5.0.2 + */ + private ?string $entity = null; + + /** + * The entity table value. + * + * @var string|null + * @since 5.0.2 + */ + private ?string $table = null; + + /** + * Constructor. + * + * @param Update $update The Update Class. + * @param Insert $insert The Insert Class. + * + * @since 5.0.2 + */ + public function __construct(Update $update, Insert $insert) + { + $this->update = $update; + $this->insert = $insert; + } + + /** + * Load an entity that these message belong to + * + * @param string $guid The entity guid these messages must be linked to. + * @param string $entity The entity type these messages must be linked to. + * @param string $table The messages table where these message must be stored. + * + * @return self + * @throws \InvalidArgumentException if any of the parameters are null or empty. + * @since 5.0.2 + */ + public function load(string $guid, string $entity, string $table): self + { + if (empty($guid) || empty($entity) || empty($table)) + { + throw new \InvalidArgumentException('GUID, entity, and table must not be null or empty.'); + } + + // set entity details + $this->guid = $guid; + $this->entity = $entity; + $this->table = $table; + + return $this; + } + + /** + * Get the messages of the last import event + * + * @return object + * @since 5.0.2 + */ + public function get(): object + { + return (object) [ + 'message_success' => $this->success ?? null, + 'message_info' => $this->info ?? null, + 'message_error' => $this->error ?? null + ]; + } + + /** + * Reset the messages of the last import event + * + * @return void + * @since 5.0.2 + */ + public function reset(): void + { + // clear the message bus + $this->success = []; + $this->info = []; + $this->error = []; + + $this->guid = null; + $this->entity = null; + $this->table = null; + } + + /** + * Archive the messages in the DB of the last import event + * + * @return self + * @throws \InvalidArgumentException if GUID, entity, or table is null. + * @since 5.0.2 + */ + public function archive(): self + { + if (empty($this->guid) || empty($this->entity) || empty($this->table)) + { + throw new \InvalidArgumentException('GUID, entity, and table must not be null or empty.'); + } + + // trash all messages from the past + $this->update->table($this->table)->rows([['entity' => $this->guid, 'published' => -2]], 'entity'); + + return $this; + } + + /** + * Set the messages in the DB of the last import event + * + * @return self + * @throws \InvalidArgumentException if GUID, entity, or table is null. + * @since 5.0.2 + */ + public function set(): self + { + if (empty($this->guid) || empty($this->entity) || empty($this->table)) + { + throw new \InvalidArgumentException('GUID, entity, and table must not be null or empty.'); + } + + // start message bucket + $messages = []; + + // set the success messages + if (!empty($this->success)) + { + foreach ($this->success as $message) + { + $messages[] = [ + 'guid' => GuidHelper::get(), + 'entity' => $this->guid, + 'entity_type' => $this->entity, + 'message' => $message, + 'message_status' => 1 + ]; + } + } + + // set the info messages + if (!empty($this->info)) + { + foreach ($this->info as $message) + { + $messages[] = [ + 'guid' => GuidHelper::get(), + 'entity' => $this->guid, + 'entity_type' => $this->entity, + 'message' => $message, + 'message_status' => 2 + ]; + } + } + + // set the error messages + if (!empty($this->error)) + { + foreach ($this->error as $message) + { + $messages[] = [ + 'guid' => GuidHelper::get(), + 'entity' => $this->guid, + 'entity_type' => $this->entity, + 'message' => $message, + 'message_status' => 3 + ]; + } + } + + $this->insert->table($this->table)->rows($messages); + + return $this; + } + + /** + * Adds a success message to the log. + * + * This method records a success message for the import process. The message provides + * relevant information, such as the number of rows processed and the success rate. + * + * @param string $message The success message to log. + * + * @return self + * @since 5.0.2 + */ + public function addSuccess(string $message): self + { + $this->success[] = $message; + + return $this; + } + + /** + * Adds a info message to the log. + * + * This method records a info message for the import process. The message provides + * relevant information, such as the number of rows processed and the info rate. + * + * @param string $message The info message to log. + * + * @return self + * @since 5.0.2 + */ + public function addInfo(string $message): self + { + $this->info[] = $message; + + return $this; + } + + /** + * Adds an error message to the log. + * + * This method records an error message when the import process encounters issues. + * The message includes details about the failures, such as the number of failed rows + * and the corresponding error rate. + * + * @param string $message The error message to log. + * + * @return self + * @since 5.0.2 + */ + public function addError(string $message): self + { + $this->error[] = $message; + + return $this; + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Row.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Row.php new file mode 100644 index 000000000..f8944344b --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Row.php @@ -0,0 +1,131 @@ + + * @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\Import; + + +use VDM\Joomla\Componentbuilder\Interfaces\ImportRowInterface; + + +/** + * Import Row Class + * + * @since 4.0.3 + */ +final class Row implements ImportRowInterface +{ + /** + * The row array of values. + * + * @var array + * @since 5.0.2 + */ + private array $values; + + /** + * The row index. + * + * @var int + * @since 5.0.2 + */ + private int $index; + + /** + * A flag to track if values and index are set. + * + * @var bool + * @since 5.0.2 + */ + private bool $isSet = false; + + /** + * Set the row details + * + * @param int $index The row index + * @param array $values The values + * + * @return void + * @since 5.0.2 + */ + public function set(int $index, array $values): void + { + $this->index = $index; + $this->values = $values; + $this->isSet = true; + } + + /** + * Clear the row details + * + * @return self + * @since 5.0.2 + */ + public function clear(): self + { + $this->index = 0; + $this->values = []; + $this->isSet = false; + + return $this; + } + + /** + * Get Index + * + * @return int + * @throws \InvalidArgumentException if any of the parameters are null or empty. + * @since 5.0.2 + */ + public function getIndex(): int + { + if (!$this->isSet) + { + throw new \InvalidArgumentException('Index must not be null or empty. Use the set method to first set the index.'); + } + + return $this->index; + } + + /** + * Get Value + * + * @return mixed + * @throws \InvalidArgumentException if any of the parameters are null or empty. + * @since 5.0.2 + */ + public function getValue(string $key) + { + if (!$this->isSet) + { + throw new \InvalidArgumentException('Values must be set before accessing. Use the set method to first set the values.'); + } + + return $this->values[$key] ?? null; + } + + /** + * Unset Value + * + * @return void + * @throws \InvalidArgumentException if any of the parameters are null or empty. + * @since 5.0.2 + */ + public function unsetValue(string $key): void + { + if (!$this->isSet) + { + throw new \InvalidArgumentException('Values must be set before accessing. Use the set method to first set the values.'); + } + + unset($this->values[$key]); + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Service/Import.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Service/Import.php new file mode 100644 index 000000000..3660e7ec9 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Service/Import.php @@ -0,0 +1,171 @@ + + * @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\Import\Service; + + +use Joomla\DI\Container; +use Joomla\DI\ServiceProviderInterface; +use VDM\Joomla\Componentbuilder\Import\Data; +use VDM\Joomla\Componentbuilder\Import\Mapper; +use VDM\Joomla\Componentbuilder\Import\Row; +use VDM\Joomla\Componentbuilder\Import\Item; +use VDM\Joomla\Componentbuilder\Import\Message; +use VDM\Joomla\Componentbuilder\Import\Status; +use VDM\Joomla\Componentbuilder\Import\Assessor; + + +/** + * Import Service Provider + * + * @since 5.0.3 + */ +class Import implements ServiceProviderInterface +{ + /** + * Registers the service provider with a DI container. + * + * @param Container $container The DI container. + * + * @return void + * @since 5.0.3 + */ + public function register(Container $container) + { + $container->alias(Data::class, 'Import.Data') + ->share('Import.Data', [$this, 'getData'], true); + + $container->alias(Mapper::class, 'Import.Mapper') + ->share('Import.Mapper', [$this, 'getMapper'], true); + + $container->alias(Row::class, 'Import.Row') + ->share('Import.Row', [$this, 'getRow'], true); + + $container->alias(Item::class, 'Import.Item') + ->share('Import.Item', [$this, 'getItem'], true); + + $container->alias(Message::class, 'Import.Message') + ->share('Import.Message', [$this, 'getMessage'], true); + + $container->alias(Status::class, 'Import.Status') + ->share('Import.Status', [$this, 'getStatus'], true); + + $container->alias(Assessor::class, 'Import.Assessor') + ->share('Import.Assessor', [$this, 'getAssessor'], true); + } + + /** + * Get The Data Class. + * + * @param Container $container The DI container. + * + * @return Data + * @since 5.0.3 + */ + public function getData(Container $container): Data + { + return new Data(); + } + + /** + * Get The Mapper Class. + * + * @param Container $container The DI container. + * + * @return Mapper + * @since 5.0.3 + */ + public function getMapper(Container $container): Mapper + { + return new Mapper( + $container->get('Table') + ); + } + + /** + * Get The Row Class. + * + * @param Container $container The DI container. + * + * @return Row + * @since 5.0.3 + */ + public function getRow(Container $container): Row + { + return new Row(); + } + + /** + * Get The Item Class. + * + * @param Container $container The DI container. + * + * @return Item + * @since 5.0.3 + */ + public function getItem(Container $container): Item + { + return new Item( + $container->get('Table.Validator'), + $container->get('Data.Item'), + $container->get('Import.Row') + ); + } + + /** + * Get The Message Class. + * + * @param Container $container The DI container. + * + * @return Message + * @since 5.0.3 + */ + public function getMessage(Container $container): Message + { + return new Message( + $container->get('Data.Update'), + $container->get('Data.Insert') + ); + } + + /** + * Get The Status Class. + * + * @param Container $container The DI container. + * + * @return Status + * @since 5.0.3 + */ + public function getStatus(Container $container): Status + { + return new Status( + $container->get('Data.Item') + ); + } + + /** + * Get The Assessor Class. + * + * @param Container $container The DI container. + * + * @return Assessor + * @since 5.0.3 + */ + public function getAssessor(Container $container): Assessor + { + return new Assessor( + $container->get('Import.Data'), + $container->get('Import.Status'), + $container->get('Import.Message') + ); + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Service/index.html b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Service/index.html new file mode 100644 index 000000000..fa6d84e80 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Service/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Status.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Status.php new file mode 100644 index 000000000..fd30dd6f0 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Import/Status.php @@ -0,0 +1,149 @@ + + * @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\Import; + + +use VDM\Joomla\Interfaces\Data\ItemInterface as Item; +use VDM\Joomla\Componentbuilder\Interfaces\ImportStatusInterface; + + +/** + * Import Status Class + * + * @since 5.0.2 + */ +final class Status implements ImportStatusInterface +{ + /** + * The Item Class. + * + * @var Item + * @since 5.0.2 + */ + protected Item $item; + + /** + * Table Name + * + * @var string + * @since 5.0.2 + */ + protected string $table; + + /** + * Status Field Name + * + * @var string + * @since 5.0.2 + */ + protected string $fieldName; + + /** + * Constructor. + * + * @param Item $item The Item Class. + * @param string|null $table The table name + * @param string|null $field The field name. + * + * @since 5.0.2 + */ + public function __construct(Item $item, ?string $table = null, ?string $field = null) + { + $this->item = $item; + + if ($table !== null) + { + $this->table = $table; + } + + if ($field !== null) + { + $this->field = $field; + } + } + + /** + * Updates the status in the database. + * + * This method updates the import status in the database based on the result of the import process. + * Status codes: + * - 2: Being Processed. + * - 3: Import completed successfully. + * - 4: Import completed with errors. + * + * @param int $status The status code to set for the import (2 => processing, 3 => success, 4 => errors). + * @param string $guid The target import GUID + * + * @return void + * @since 5.0.2 + */ + public function set(int $status, string $guid): void + { + $this->item->table($this->getTable())->set((object) [ + 'guid' => $guid, + $this->getField() => $status + ]); + } + + /** + * 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; + } + + /** + * Set the current target status field name + * + * @param string $fieldName The field name where the status is set + * + * @return self + * @since 3.2.2 + */ + public function field(string $fieldName): self + { + $this->fieldName = $fieldName; + + return $this; + } + + /** + * Get the current active table + * + * @return string + * @since 3.2.2 + */ + public function getTable(): string + { + return $this->table; + } + + /** + * Get the current target status field name + * + * @return string + * @since 3.2.2 + */ + public function getField(): string + { + return $this->fieldName; + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportAssessorInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportAssessorInterface.php new file mode 100644 index 000000000..78905daa3 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportAssessorInterface.php @@ -0,0 +1,34 @@ + + * @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\Interfaces; + + +/** + * Import Assessor Interface + * + * @since 3.0.3 + */ +interface ImportAssessorInterface +{ + /** + * Evaluates the import process and sets the success/error message based on the success rate. + * + * @param int $rowCounter Total number of rows processed. + * @param int $successCounter Number of successfully processed rows. + * @param int $errorCounter Number of rows that failed to process. + * + * @return void + * @since 4.0.3 + */ + public function evaluate(int $rowCounter, int $successCounter, int $errorCounter): void; +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportItemInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportItemInterface.php new file mode 100644 index 000000000..28ebff6b5 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportItemInterface.php @@ -0,0 +1,33 @@ + + * @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\Interfaces; + + +/** + * Import Item Interface + * + * @since 3.0.3 + */ +interface ImportItemInterface +{ + /** + * Get the item from the import row values and ensure it is valid + * + * @param string $table The table these columns belongs to. + * @param array $columns The columns to extract. + * + * @return array|null + * @since 4.0.3 + */ + public function get(string $table, array $columns): ?array; +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportMapperInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportMapperInterface.php new file mode 100644 index 000000000..38508c78c --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportMapperInterface.php @@ -0,0 +1,49 @@ + + * @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\Interfaces; + + +/** + * Import Mapper Interface + * + * @since 3.0.3 + */ +interface ImportMapperInterface +{ + /** + * Set the tables mapper + * + * @param object $map The import file map. + * @param string $parentTable The parent table name. + * + * @return void + * @since 4.0.3 + */ + public function set(object $map, string $parentTable): void; + + /** + * Get the parent table keys + * + * @return array + * @since 4.0.3 + */ + public function getParent(): array; + + /** + * Get the join tables keys + * + * @return array + * @since 4.0.3 + */ + public function getJoin(): array; +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportMessageInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportMessageInterface.php new file mode 100644 index 000000000..0fba231b7 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportMessageInterface.php @@ -0,0 +1,109 @@ + + * @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\Interfaces; + + +/** + * Import Message Interface + * + * @since 3.0.2 + */ +interface ImportMessageInterface +{ + /** + * Load an entity that these message belong to + * + * @param string $guid The entity guid these messages must be linked to. + * @param string $entity The entity type these messages must be linked to. + * @param string $table The messages table where these message must be stored. + * + * @return self + * @throws \InvalidArgumentException if any of the parameters are null or empty. + * @since 3.0.2 + */ + public function load(string $guid, string $entity, string $table): self; + + /** + * Get the messages of the last import event + * + * @return object + * @since 3.0.2 + */ + public function get(): object; + + /** + * Reset the messages of the last import event + * + * @return void + * @since 3.0.2 + */ + public function reset(): void; + + /** + * Archive the messages in the DB of the last import event + * + * @return self + * @throws \InvalidArgumentException if GUID, entity, or table is null. + * @since 3.0.2 + */ + public function archive(): self; + + /** + * Set the messages in the DB of the last import event + * + * @return self + * @throws \InvalidArgumentException if GUID, entity, or table is null. + * @since 3.0.2 + */ + public function set(): self; + + /** + * Adds a success message to the log. + * + * This method records a success message for the import process. The message provides + * relevant information, such as the number of rows processed and the success rate. + * + * @param string $message The success message to log. + * + * @return self + * @since 3.0.2 + */ + public function addSuccess(string $message): self; + + /** + * Adds a info message to the log. + * + * This method records a info message for the import process. The message provides + * relevant information, such as the number of rows processed and the info rate. + * + * @param string $message The info message to log. + * + * @return self + * @since 3.0.2 + */ + public function addInfo(string $message): self; + + /** + * Adds an error message to the log. + * + * This method records an error message when the import process encounters issues. + * The message includes details about the failures, such as the number of failed rows + * and the corresponding error rate. + * + * @param string $message The error message to log. + * + * @return self + * @since 3.0.2 + */ + public function addError(string $message): self; +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportRowInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportRowInterface.php new file mode 100644 index 000000000..e611e3626 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportRowInterface.php @@ -0,0 +1,68 @@ + + * @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\Interfaces; + + +/** + * Import Row Interface + * + * @since 3.0.3 + */ +interface ImportRowInterface +{ + /** + * Set the row details + * + * @param int $index The row index + * @param array $values The values + * + * @return void + * @since 3.0.3 + */ + public function set(int $index, array $values): void; + + /** + * Clear the row details + * + * @return self + * @since 3.0.3 + */ + public function clear(): self; + + /** + * Get Index + * + * @return int + * @throws \InvalidArgumentException if any of the parameters are null or empty. + * @since 3.0.3 + */ + public function getIndex(): int; + + /** + * Get Value + * + * @return mixed + * @throws \InvalidArgumentException if any of the parameters are null or empty. + * @since 3.0.3 + */ + public function getValue(string $key); + + /** + * Unset Value + * + * @return void + * @throws \InvalidArgumentException if any of the parameters are null or empty. + * @since 3.0.3 + */ + public function unsetValue(string $key): void; +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportStatusInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportStatusInterface.php new file mode 100644 index 000000000..b09a7dfdc --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/ImportStatusInterface.php @@ -0,0 +1,75 @@ + + * @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\Interfaces; + + +/** + * Import Status Interface + * + * @since 3.2.2 + */ +interface ImportStatusInterface +{ + /** + * Updates the status in the database. + * + * This method updates the import status in the database based on the result of the import process. + * Status codes: + * - 2: Being Processed. + * - 3: Import completed successfully. + * - 4: Import completed with errors. + * + * @param int $status The status code to set for the import (2 => processing, 3 => success, 4 => errors). + * @param string $guid The target import GUID + * + * @return void + * @since 3.2.2 + */ + public function set(int $status, string $guid): void; + + /** + * 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; + + /** + * Set the current target status field name + * + * @param string $fieldName The field name where the status is set + * + * @return self + * @since 3.2.2 + */ + public function field(string $fieldName): self; + + /** + * Get the current active table + * + * @return string + * @since 3.2.2 + */ + public function getTable(): string; + + /** + * Get the current target status field name + * + * @return string + * @since 3.2.2 + */ + public function getField(): string; +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/FileReaderInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/FileReaderInterface.php new file mode 100644 index 000000000..f6f432226 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/FileReaderInterface.php @@ -0,0 +1,34 @@ + + * @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\Interfaces\Spreadsheet; + + +/** + * Spreadsheet File Reader Interface + * + * @since 3.2.2 + */ +interface FileReaderInterface +{ + /** + * Stream rows from a CSV or Excel file one by one using yield. + * + * @param string $filePath The path to the file. + * @param int $startRow The starting row index. + * @param int $chunkSize The number of rows to read per chunk. + * + * @return \Generator A generator that yields each row as an array. + * @since 3.2.0 + */ + public function read(string $filePath, int $startRow, int $chunkSize): \Generator; +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/ImportCliInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/ImportCliInterface.php new file mode 100644 index 000000000..6c8a37e04 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/ImportCliInterface.php @@ -0,0 +1,40 @@ + + * @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\Interfaces\Spreadsheet; + + +/** + * Spreadsheet Import Cli Interface + * + * @since 3.2.2 + */ +interface ImportCliInterface +{ + /** + * The trigger function called from the CLI to start the import on a spreadsheet + * + * @param object $import The spreadsheet data to import. + * + * @return void + * @since 5.0.2 + */ + public function data(object $import): void; + + /** + * The message of the last import event + * + * @return object + * @since 5.0.2 + */ + public function message(): object; +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/RowDataProcessorInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/RowDataProcessorInterface.php new file mode 100644 index 000000000..a1c7da251 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/RowDataProcessorInterface.php @@ -0,0 +1,34 @@ + + * @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\Interfaces\Spreadsheet; + + +use PhpOffice\PhpSpreadsheet\Worksheet\Row; + + +/** + * Spreadsheet Row Data Processor Interface + * + * @since 3.2.2 + */ +interface RowDataProcessorInterface +{ + /** + * Processes the given spreadsheet row and returns it in a specific format. + * + * @param Row $row The row object from the spreadsheet to be processed. + * + * @return mixed Processed row data, could be an array, cell object, or other structures. + */ + public function process(Row $row): mixed; +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/index.html b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/index.html new file mode 100644 index 000000000..fa6d84e80 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Interfaces/Spreadsheet/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Service/Spreadsheet.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Service/Spreadsheet.php index 5086836ee..26b98c936 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Service/Spreadsheet.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Service/Spreadsheet.php @@ -17,6 +17,7 @@ use Joomla\DI\ServiceProviderInterface; use VDM\Joomla\Componentbuilder\Spreadsheet\Header; use VDM\Joomla\Componentbuilder\Spreadsheet\Exporter; use VDM\Joomla\Componentbuilder\Spreadsheet\Importer; +use VDM\Joomla\Componentbuilder\Spreadsheet\FileReader; /** @@ -44,6 +45,9 @@ class Spreadsheet implements ServiceProviderInterface $container->alias(Importer::class, 'Spreadsheet.Importer') ->share('Spreadsheet.Importer', [$this, 'getImporter'], true); + + $container->alias(FileReader::class, 'Spreadsheet.FileReader') + ->share('Spreadsheet.FileReader', [$this, 'getFileReader'], true); } /** @@ -82,7 +86,22 @@ class Spreadsheet implements ServiceProviderInterface */ public function getImporter(Container $container): Importer { - return new Importer(); + return new Importer( + $container->get('Spreadsheet.FileReader') + ); + } + + /** + * Get The FileReader Class. + * + * @param Container $container The DI container. + * + * @return FileReader + * @since 5.0.3 + */ + public function getFileReader(Container $container): FileReader + { + return new FileReader(); } } diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/FileReader.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/FileReader.php new file mode 100644 index 000000000..204504e13 --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/FileReader.php @@ -0,0 +1,109 @@ + + * @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\Spreadsheet; + + +use PhpOffice\PhpSpreadsheet\IOFactory; +use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; +use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException; +use VDM\Joomla\Componentbuilder\Spreadsheet\ChunkReadFilter; +use VDM\Joomla\Componentbuilder\Interfaces\Spreadsheet\FileReaderInterface; + + +/** + * Spreadsheet File Reader Class + * + * @since 3.2.0 + */ +final class FileReader implements FileReaderInterface +{ + /** + * Stream rows from a CSV or Excel file one by one using yield. + * + * @param string $filePath The path to the file. + * @param int $startRow The starting row index. + * @param int $chunkSize The number of rows to read per chunk. + * + * @return \Generator A generator that yields each row as an array. + * @throws \InvalidArgumentException If the file does not exist. + * @throws \OutOfRangeException If the start row is beyond the highest row, no rows can be processed. + * @throws ReaderException If there is an error identifying or reading the file. + * @throws SpreadsheetException If there is an error working with the spreadsheet. + * @since 3.2.0 + */ + public function read(string $filePath, int $startRow, int $chunkSize): \Generator + { + // Check if the file exists + if (!is_file($filePath)) + { + throw new \InvalidArgumentException("File not found: $filePath"); + } + + try { + // Identify file type and create reader + $inputFileType = IOFactory::identify($filePath); + $reader = IOFactory::createReader($inputFileType); + $reader->setReadDataOnly(true); + + // Load the entire spreadsheet to determine the highest row + $spreadsheet = $reader->load($filePath); + $worksheet = $spreadsheet->getActiveSheet(); + $highestRow = $worksheet->getHighestRow(); // Get the highest row number in the sheet + + // Disconnect and free memory after fetching the highest row + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + + // If the start row is beyond the highest row, no rows can be processed + if ($startRow > $highestRow) + { + throw new \OutOfRangeException("Start row ($startRow) is beyond highest row ($highestRow)"); + } + + // Initialize variables for row processing + $totalRows = $startRow; + + do { + // Calculate the last row in the current chunk + $endRow = min($totalRows + $chunkSize - 1, $highestRow); + + // Set up a new chunk filter for the current chunk + $chunkFilter = new ChunkReadFilter($totalRows, $endRow); + $reader->setReadFilter($chunkFilter); + + // Reload the chunk into the spreadsheet + $spreadsheet = $reader->load($filePath); + $worksheet = $spreadsheet->getActiveSheet(); + + // Iterate through the rows in the current chunk + foreach ($worksheet->getRowIterator($totalRows, $endRow) as $row) + { + yield $row; + + // Update the row index for the next chunk + $totalRows = $row->getRowIndex() + 1; + } + + // Disconnect the spreadsheet to free memory + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + + } while ($totalRows <= $highestRow); // Continue reading while within the row limit + + } catch (ReaderException $e) { + throw new ReaderException("Error reading the file: " . $e->getMessage(), $e->getCode(), $e); + } catch (SpreadsheetException $e) { + throw new SpreadsheetException("Error with the spreadsheet: " . $e->getMessage(), $e->getCode(), $e); + } + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/Header.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/Header.php index a97fe41bd..5c84a9562 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/Header.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/Header.php @@ -54,7 +54,7 @@ final class Header if ($row->getRowIndex() === $targetRow) { $cellIterator = $row->getCellIterator(); - $cellIterator->setIterateOnlyExistingCells(false); + $cellIterator->setIterateOnlyExistingCells(true); foreach ($cellIterator as $cell) { $headers[$cell->getColumn()] = $cell->getValue(); diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/Importer.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/Importer.php index 305bfc713..7feb13bfa 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/Importer.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Spreadsheet/Importer.php @@ -12,10 +12,8 @@ namespace VDM\Joomla\Componentbuilder\Spreadsheet; -use PhpOffice\PhpSpreadsheet\IOFactory; -use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; -use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException; -use VDM\Joomla\Componentbuilder\Spreadsheet\ChunkReadFilter; +use VDM\Joomla\Componentbuilder\Interfaces\Spreadsheet\FileReaderInterface as FileReader; +use VDM\Joomla\Componentbuilder\Interfaces\Spreadsheet\RowDataProcessorInterface as RowDataProcessor; /** @@ -25,12 +23,33 @@ use VDM\Joomla\Componentbuilder\Spreadsheet\ChunkReadFilter; */ final class Importer { + /** + * The FileReader Class. + * + * @var FileReader + * @since 3.0.8 + */ + protected FileReader $filereader; + + /** + * Constructor. + * + * @param FileReader $filereader The FileReader Class. + * + * @since 3.0.8 + */ + public function __construct(FileReader $filereader) + { + $this->filereader = $filereader; + } + /** * Stream rows from a CSV or Excel file one by one using yield. * - * @param string $filePath The path to the file. - * @param int $startRow The starting row index (default is 1). - * @param int $chunkSize The number of rows to read per chunk (default is 100). + * @param string $filePath The path to the file. + * @param int $startRow The starting row index. + * @param int $chunkSize The number of rows to read per chunk. + * @param RowDataProcessor $processor The processor used to transform the row data into the desired format. * * @return \Generator A generator that yields each row as an array. * @throws \InvalidArgumentException If the file does not exist. @@ -39,81 +58,11 @@ final class Importer * @throws SpreadsheetException If there is an error working with the spreadsheet. * @since 3.2.0 */ - public function get(string $filePath, int $startRow = 1, int $chunkSize = 100): \Generator + public function read(string $filePath, int $startRow, int $chunkSize, RowDataProcessor $processor): \Generator { - // Check if the file exists - if (!is_file($filePath)) + foreach ($this->filereader->read($filePath, $startRow, $chunkSize) as $row) { - throw new \InvalidArgumentException("File not found: $filePath"); - } - - try { - // Identify file type and create reader - $inputFileType = IOFactory::identify($filePath); - $reader = IOFactory::createReader($inputFileType); - $reader->setReadDataOnly(true); - - // Load the entire spreadsheet to determine the highest row - $spreadsheet = $reader->load($filePath); - $worksheet = $spreadsheet->getActiveSheet(); - $highestRow = $worksheet->getHighestRow(); // Get the highest row number in the sheet - - // Disconnect and free memory after fetching the highest row - $spreadsheet->disconnectWorksheets(); - unset($spreadsheet); - - // If the start row is beyond the highest row, no rows can be processed - if ($startRow > $highestRow) - { - throw new \OutOfRangeException("Start row ($startRow) is beyond highest row ($highestRow)"); - } - - // Initialize variables for row processing - $totalRows = $startRow; - - do { - // Calculate the last row in the current chunk - $endRow = min($totalRows + $chunkSize - 1, $highestRow); - - // Set up a new chunk filter for the current chunk - $chunkFilter = new ChunkReadFilter($totalRows, $endRow); - $reader->setReadFilter($chunkFilter); - - // Reload the chunk into the spreadsheet - $spreadsheet = $reader->load($filePath); - $worksheet = $spreadsheet->getActiveSheet(); - - // Iterate through the rows in the current chunk - foreach ($worksheet->getRowIterator($totalRows, $endRow) as $row) - { - $rowIndex = $row->getRowIndex(); - $rowData = []; - - $cellIterator = $row->getCellIterator(); - $cellIterator->setIterateOnlyExistingCells(false); // Include empty cells - - // Collect all cell data in the row - foreach ($cellIterator as $cell) - { - $rowData[$cell->getColumn()] = $cell->getValue(); - } - - yield $rowData; - - // Update the row index for the next chunk - $totalRows = $rowIndex + 1; - } - - // Disconnect the spreadsheet to free memory - $spreadsheet->disconnectWorksheets(); - unset($spreadsheet); - - } while ($totalRows <= $highestRow); // Continue reading while within the row limit - - } catch (ReaderException $e) { - throw new ReaderException("Error reading the file: " . $e->getMessage(), $e->getCode(), $e); - } catch (SpreadsheetException $e) { - throw new SpreadsheetException("Error with the spreadsheet: " . $e->getMessage(), $e->getCode(), $e); + yield $processor->process($row); } } } diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table.php index 5519d4e4b..6e4222d2c 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table.php @@ -12,7 +12,7 @@ namespace VDM\Joomla\Componentbuilder; -use VDM\Joomla\Interfaces\Tableinterface; +use VDM\Joomla\Interfaces\TableInterface; use VDM\Joomla\Abstraction\BaseTable; @@ -21,7 +21,7 @@ use VDM\Joomla\Abstraction\BaseTable; * * @since 3.2.0 */ -final class Table extends BaseTable implements Tableinterface +final class Table extends BaseTable implements TableInterface { /** * All areas/views/tables with their field details diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table/Validator.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table/Validator.php new file mode 100644 index 000000000..05a553f4b --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table/Validator.php @@ -0,0 +1,426 @@ + + * @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\Table; + + +use VDM\Joomla\Componentbuilder\Table; +use VDM\Joomla\Interfaces\TableValidatorInterface; + + +/** + * Table Value Validator + * + * @since 5.3.0 + */ +final class Validator implements TableValidatorInterface +{ + /** + * The Table Class. + * + * @var Table + * @since 5.3.0 + */ + protected Table $table; + + /** + * A map of MySQL base types to their respective validation methods. + * + * @var array + * @since 5.3.0 + */ + protected array $validators = []; + + /** + * A map of defaults for the respective datatypes. + * + * @var array + * @since 5.3.0 + */ + protected array $defaults = []; + + /** + * Cache of the parsed datatype details + * + * @var array + * @since 5.3.0 + */ + protected array $datatypes = []; + + /** + * Constructor. + * + * @param Table $table The Table Class. + * + * @since 5.3.0 + */ + public function __construct(Table $table) + { + $this->table = $table; + + // Register datatype validators (mapping MySQL types to handlers) + $this->registerValidators(); + + // Register datatype defaults + $this->registerDefaults(); + } + + /** + * Returns the valid value based on datatype definition. + * If the value is valid, return it. If not, return the default value, + * NULL (if allowed), or an empty string if 'EMPTY' is set. + * + * @param mixed $value The value to validate. + * @param string $field The field name. + * @param string $table The table name. + * + * @return mixed Returns the valid value, or the default, NULL, or empty string based on validation. + * @since 5.3.0 + */ + public function getValid($value, string $field, string $table) + { + // Get the database field definition + if (($dbField = $this->getDatabaseField($field, $table)) === null) + { + return null; // not legal field or table + } + + // Check if the value is valid for the field + if ($this->validate($value, $dbField)) + { + return $value; + } + + // If invalid, return default, NULL (if allowed), or empty string + return $this->getDefault($dbField, $value); + } + + /** + * Validate if the given value is valid for the provided database field. + * This is a private method as `getValid()` will handle the actual logic. + * + * @param mixed $value The value to validate. + * @param array $dbField The database field details (type, default, null_switch, etc.). + * + * @return bool Returns true if the value is valid, false otherwise. + * @since 5.3.0 + */ + private function validate($value, array $dbField): bool + { + // Extract datatype and handle the validation + $typeInfo = $this->parseDataType($dbField['type']); + $baseType = $typeInfo['type']; + + // Use the appropriate validator if it exists + if (isset($this->validators[$baseType])) + { + return call_user_func($this->validators[$baseType], $value, $typeInfo); + } + + // If no validator exists, assume invalid + return false; + } + + /** + * Handle returning the default value, null, or empty string if validation fails. + * + * @param array $dbField The database field details. + * @param mixed $value The value to validate. + * + * @return mixed The default value, null, or empty string based on field settings. + * @since 5.3.0 + */ + private function getDefault(array $dbField, $value) + { + // get default value from field db + $db_default = isset($dbField['default']) ? $dbField['default'] : null; + + // If a default value is provided, return it + if ($db_default !== null) + { + return strtoupper($db_default) === 'EMPTY' ? '' : $db_default; + } + + // Check if NULL is allowed + if (isset($dbField['null_switch']) && strtoupper($dbField['null_switch']) === 'NULL') + { + return null; + } + + // Fallback to datatype default + $typeInfo = $this->parseDataType($dbField['type']); + return $this->defaults[$typeInfo['type']] ?? ''; + } + + /** + * Parse the data type from the database field and extract details like type, size, and precision. + * + * @param string $datatype The full MySQL datatype (e.g., VARCHAR(255)). + * + * @return array An array containing 'type', 'size', and other relevant info. + * @since 5.3.0 + */ + private function parseDataType(string $datatype): array + { + if (isset($this->datatypes[$datatype])) + { + return $this->datatypes[$datatype]; + } + + $pattern = '/(?\w+)(\((?\d+)(,\s*(?\d+))?\))?/i'; + preg_match($pattern, $datatype, $matches); + + $result = [ + 'type' => isset($matches['type']) ? strtolower($matches['type']) : strtolower($datatype), + 'size' => $matches['size'] ?? null, + 'precision' => $matches['precision'] ?? null, + ]; + + return $this->datatypes[$datatype] = $result; + } + + /** + * Retrieve the database field structure for the specified field and table. + * In your case, you use `$db = $this->table->get($table, $field, 'db')`. + * + * @param string $field The field name. + * @param string $table The table name. + * + * @return array The database field details, including type, default, null_switch, etc. + * @since 5.3.0 + */ + private function getDatabaseField(string $field, string $table): array + { + // Simulated retrieval of field details. Replace with actual logic. + return $this->table->get($table, $field, 'db'); + } + + /** + * Register validators for MySQL data types. + * + * @return void + * @since 5.3.0 + */ + private function registerValidators(): void + { + $this->validators = [ + 'int' => [$this, 'validateInteger'], + 'tinyint' => [$this, 'validateInteger'], + 'smallint' => [$this, 'validateInteger'], + 'mediumint' => [$this, 'validateInteger'], + 'bigint' => [$this, 'validateInteger'], + 'varchar' => [$this, 'validateString'], + 'char' => [$this, 'validateString'], + 'text' => [$this, 'validateText'], + 'tinytext' => [$this, 'validateText'], + 'mediumtext' => [$this, 'validateText'], + 'longtext' => [$this, 'validateText'], + 'decimal' => [$this, 'validateDecimal'], + 'float' => [$this, 'validateFloat'], + 'double' => [$this, 'validateFloat'], + 'date' => [$this, 'validateDate'], + 'datetime' => [$this, 'validateDate'], + 'timestamp' => [$this, 'validateDate'], + 'time' => [$this, 'validateDate'], + 'json' => [$this, 'validateJson'], + 'blob' => [$this, 'validateBlob'], + 'tinyblob' => [$this, 'validateBlob'], + 'mediumblob' => [$this, 'validateBlob'], + 'longblob' => [$this, 'validateBlob'], + ]; + } + + /** + * Register default values for MySQL data types. + * + * @return void + * @since 5.3.0 + */ + private function registerDefaults(): void + { + $this->defaults = [ + 'int' => 0, + 'tinyint' => 0, + 'smallint' => 0, + 'mediumint' => 0, + 'bigint' => 0, + 'varchar' => '', + 'char' => '', + 'text' => '', + 'tinytext' => '', + 'mediumtext' => '', + 'longtext' => '', + 'decimal' => 0.0, + 'float' => 0.0, + 'double' => 0.0, + 'date' => '0000-00-00', + 'datetime' => '0000-00-00 00:00:00', + 'timestamp' => '0000-00-00 00:00:00', + 'time' => '00:00:00', + 'json' => '{}', + 'blob' => '', + 'tinyblob' => '', + 'mediumblob' => '', + 'longblob' => '', + ]; + } + + // ----------------- Validation Methods ----------------- + + /** + * Validate integer types (including tinyint, smallint, mediumint, etc.). + * + * @param mixed $value The value to validate. + * @param array $typeInfo The parsed data type information. + * + * @return bool True if valid, false otherwise. + * @since 5.3.0 + */ + private function validateInteger($value, array $typeInfo): bool + { + if (!is_numeric($value)) + { + return false; + } + + $value = (int)$value; + if (isset($typeInfo['unsigned']) && $typeInfo['unsigned'] && $value < 0) + { + return false; + } + + return true; + } + + /** + * Validate string types like VARCHAR and CHAR. + * + * @param mixed $value The value to validate. + * @param array $typeInfo The parsed data type information. + * + * @return bool True if valid, false otherwise. + * @since 5.3.0 + */ + private function validateString($value, array $typeInfo): bool + { + if (!is_string($value)) + { + return false; + } + + // Check if the length exceeds the allowed size + if ($typeInfo['size'] !== null && strlen($value) > (int)$typeInfo['size']) + { + return false; + } + + return true; + } + + /** + * Validate text types like TEXT, TINYTEXT, MEDIUMTEXT, LONGTEXT. + * + * @param mixed $value The value to validate. + * @param array $typeInfo The parsed data type information. + * + * @return bool True if valid, false otherwise. + * @since 5.3.0 + */ + private function validateText($value, array $typeInfo): bool + { + return is_string($value); + } + + /** + * Validate float, double, and decimal types. + * + * @param mixed $value The value to validate. + * @param array $typeInfo The parsed data type information. + * + * @return bool True if valid, false otherwise. + * @since 5.3.0 + */ + private function validateFloat($value, array $typeInfo): bool + { + return is_numeric($value); + } + + /** + * Validate decimal types (numeric precision and scale). + * + * @param mixed $value The value to validate. + * @param array $typeInfo The parsed data type information. + * + * @return bool True if valid, false otherwise. + * @since 5.3.0 + */ + private function validateDecimal($value, array $typeInfo): bool + { + return is_numeric($value); + } + + /** + * Validate date, datetime, timestamp, and time types. + * + * @param mixed $value The value to validate. + * @param array $typeInfo The parsed data type information. + * + * @return bool True if valid, false otherwise. + * @since 5.3.0 + */ + private function validateDate($value, array $typeInfo): bool + { + $formats = [ + 'date' => 'Y-m-d', + 'datetime' => 'Y-m-d H:i:s', + 'timestamp' => 'Y-m-d H:i:s', + 'time' => 'H:i:s', + ]; + + if (!isset($formats[$typeInfo['type']])) + { + return false; + } + + $dateTime = \DateTime::createFromFormat($formats[$typeInfo['type']], $value); + return $dateTime && $dateTime->format($formats[$typeInfo['type']]) === $value; + } + + /** + * Validate JSON types. + * + * @param mixed $value The value to validate. + * @param array $typeInfo The parsed data type information. + * + * @return bool True if valid, false otherwise. + * @since 5.3.0 + */ + private function validateJson($value, array $typeInfo): bool + { + json_decode($value); + return json_last_error() === JSON_ERROR_NONE; + } + + /** + * Validate BLOB types (including TINYBLOB, MEDIUMBLOB, LONGBLOB). + * + * @param mixed $value The value to validate. + * @param array $typeInfo The parsed data type information. + * + * @return bool True if valid, false otherwise. + * @since 5.3.0 + */ + private function validateBlob($value, array $typeInfo): bool + { + return is_string($value) || is_resource($value); + } +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Interfaces/Tableinterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Interfaces/TableInterface.php similarity index 95% rename from libraries/vendor_jcb/VDM.Joomla/src/Interfaces/Tableinterface.php rename to libraries/vendor_jcb/VDM.Joomla/src/Interfaces/TableInterface.php index a7cef079e..6f3d7cd88 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Interfaces/Tableinterface.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Interfaces/TableInterface.php @@ -15,7 +15,7 @@ namespace VDM\Joomla\Interfaces; /** * The VDM Core Table Interface */ -interface Tableinterface +interface TableInterface { /** * Get any value from a item/field/column of an area/view/table diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Interfaces/TableValidatorInterface.php b/libraries/vendor_jcb/VDM.Joomla/src/Interfaces/TableValidatorInterface.php new file mode 100644 index 000000000..ca2f5a92d --- /dev/null +++ b/libraries/vendor_jcb/VDM.Joomla/src/Interfaces/TableValidatorInterface.php @@ -0,0 +1,34 @@ + + * @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; + + +/** + * The VDM Core Table Validator Interface + */ +interface TableValidatorInterface +{ + /** + * Returns the valid value based on datatype definition. + * If the value is valid, return it. If not, return the default value, + * NULL (if allowed), or an empty string if 'EMPTY' is set. + * + * @param mixed $value The value to validate. + * @param string $field The field name. + * @param string $table The table name. + * + * @return mixed Returns the valid value, or the default, NULL, or empty string based on validation. + * @since 5.3.0 + */ + public function getValid($value, string $field, string $table); +} + diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Service/Table.php b/libraries/vendor_jcb/VDM.Joomla/src/Service/Table.php index eaf4110de..59745782c 100644 --- a/libraries/vendor_jcb/VDM.Joomla/src/Service/Table.php +++ b/libraries/vendor_jcb/VDM.Joomla/src/Service/Table.php @@ -16,6 +16,7 @@ use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use VDM\Joomla\Componentbuilder\Table as DataTable; use VDM\Joomla\Componentbuilder\Table\Schema; +use VDM\Joomla\Componentbuilder\Table\Validator; /** @@ -40,6 +41,9 @@ class Table implements ServiceProviderInterface $container->alias(Schema::class, 'Table.Schema') ->share('Table.Schema', [$this, 'getSchema'], true); + + $container->alias(Validator::class, 'Table.Validator') + ->share('Table.Validator', [$this, 'getValidator'], true); } /** @@ -68,6 +72,21 @@ class Table implements ServiceProviderInterface return new Schema( $container->get('Table') ); + } + + /** + * Get The Validator Class. + * + * @param Container $container The DI container. + * + * @return Validator + * @since 3.2.2 + */ + public function getValidator(Container $container): Validator + { + return new Validator( + $container->get('Table') + ); } }