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')
+ );
}
}