Release of v5.0.4-beta1

Add first classes for the new import engine.
This commit is contained in:
Robot 2024-11-07 10:55:30 +02:00
parent 147f2f4cb7
commit 703e8e0133
Signed by untrusted user: Robot
GPG Key ID: 14DECD44E7E1BB95
44 changed files with 2790 additions and 121 deletions

View File

@ -1,9 +1,6 @@
# v5.0.4-alpha3 # v5.0.4-beta1
- Fix Save failed issue in dynamicGet. #1148 - Add first classes for the new import engine.
- Move all [TEXT, EDITOR, TEXTAREA] fields from [NOT NULL] to [NULL]
- Add the DateHelper class and improve the date methods.
- Add simple SessionHelper class.
# v5.0.4-alpha # v5.0.4-alpha
@ -13,7 +10,11 @@
- Added new import powers for custom import of spreadsheets. - 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. - 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. - 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.
# v5.0.3 # v5.0.3

View File

@ -3270,7 +3270,7 @@ class Com_ComponentbuilderInstallerScript implements InstallerScriptInterface
echo '<div style="background-color: #fff;" class="alert alert-info"><a target="_blank" href="https://dev.vdm.io" title="Component Builder"> echo '<div style="background-color: #fff;" class="alert alert-info"><a target="_blank" href="https://dev.vdm.io" title="Component Builder">
<img src="components/com_componentbuilder/assets/images/vdm-component.jpg"/> <img src="components/com_componentbuilder/assets/images/vdm-component.jpg"/>
</a> </a>
<h3>Upgrade to Version 5.0.4-alpha3 Was Successful! Let us know if anything is not working as expected.</h3></div>'; <h3>Upgrade to Version 5.0.4-beta1 Was Successful! Let us know if anything is not working as expected.</h3></div>';
// Add/Update component in the action logs extensions table. // Add/Update component in the action logs extensions table.
$this->setActionLogsExtensions(); $this->setActionLogsExtensions();

View File

@ -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! 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 (5.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 (5.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) > 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) + *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io)
+ *Name*: [Component Builder](https://git.vdm.dev/joomla/Component-Builder) + *Name*: [Component Builder](https://git.vdm.dev/joomla/Component-Builder)
+ *First Build*: 30th April, 2015 + *First Build*: 30th April, 2015
+ *Last Build*: 13th October, 2024 + *Last Build*: 7th November, 2024
+ *Version*: 5.0.4-alpha3 + *Version*: 5.0.4-beta1
+ *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved. + *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved.
+ *License*: GNU General Public License version 2 or later; see LICENSE.txt + *License*: GNU General Public License version 2 or later; see LICENSE.txt
+ *Line count*: **871857** + *Line count*: **890506**
+ *Field count*: **2104** + *Field count*: **2104**
+ *File count*: **6028** + *File count*: **6228**
+ *Folder count*: **633** + *Folder count*: **636**
> This **component** was build with a [Joomla](https://extensions.joomla.org/extension/component-builder/) [Automated Component Builder](https://www.joomlacomponentbuilder.com). > 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) > Developed by [Llewellyn van der Merwe](mailto:joomla@vdm.io)

View File

@ -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! 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 (5.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 (5.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) > 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) + *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io)
+ *Name*: [Component Builder](https://git.vdm.dev/joomla/Component-Builder) + *Name*: [Component Builder](https://git.vdm.dev/joomla/Component-Builder)
+ *First Build*: 30th April, 2015 + *First Build*: 30th April, 2015
+ *Last Build*: 13th October, 2024 + *Last Build*: 7th November, 2024
+ *Version*: 5.0.4-alpha3 + *Version*: 5.0.4-beta1
+ *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved. + *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved.
+ *License*: GNU General Public License version 2 or later; see LICENSE.txt + *License*: GNU General Public License version 2 or later; see LICENSE.txt
+ *Line count*: **871857** + *Line count*: **890506**
+ *Field count*: **2104** + *Field count*: **2104**
+ *File count*: **6028** + *File count*: **6228**
+ *Folder count*: **633** + *Folder count*: **636**
> This **component** was build with a [Joomla](https://extensions.joomla.org/extension/component-builder/) [Automated Component Builder](https://www.joomlacomponentbuilder.com). > 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) > Developed by [Llewellyn van der Merwe](mailto:joomla@vdm.io)

View File

@ -4570,6 +4570,7 @@ COM_COMPONENTBUILDER_DYNAMIC_GET_YES="Yes"
COM_COMPONENTBUILDER_DYNAMIC_GET_YY="yy" COM_COMPONENTBUILDER_DYNAMIC_GET_YY="yy"
COM_COMPONENTBUILDER_DYNAMIC_GET_Z="z" COM_COMPONENTBUILDER_DYNAMIC_GET_Z="z"
COM_COMPONENTBUILDER_DYNAMIC_GET_ZZ="zz" 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_EDIT="Edit"
COM_COMPONENTBUILDER_EDITCREATE_SITE_VIEW="Edit/Create Site View" COM_COMPONENTBUILDER_EDITCREATE_SITE_VIEW="Edit/Create Site View"
COM_COMPONENTBUILDER_EDITING="Editing" 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_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="<i>Email:</i> <b>%s</b>" COM_COMPONENTBUILDER_IEMAILI_BSB="<i>Email:</i> <b>%s</b>"
COM_COMPONENTBUILDER_IMPORT_BY_GUID_ONLY="Import by GUID only!" 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_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_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." 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_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_PATHS_FOUND="No Paths Found"
COM_COMPONENTBUILDER_NO_RESULTS_MATCH="No results match" 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_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_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" COM_COMPONENTBUILDER_NO_S_FOUND="No %s Found"

View File

@ -21,6 +21,7 @@ use VDM\Joomla\Componentbuilder\Compiler\Factory as CFactory;
use Joomla\CMS\Version; use Joomla\CMS\Version;
use VDM\Joomla\Componentbuilder\File\Factory as FileFactory; use VDM\Joomla\Componentbuilder\File\Factory as FileFactory;
use VDM\Joomla\Componentbuilder\Import\Factory as ImportFactory; 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\ArrayHelper as UtilitiesArrayHelper;
use VDM\Joomla\Utilities\StringHelper; use VDM\Joomla\Utilities\StringHelper;
use Joomla\CMS\Uri\Uri; use Joomla\CMS\Uri\Uri;
@ -76,6 +77,8 @@ class CompilerController extends AdminController
* FileFactory * FileFactory
* Adding this so that the import factory gets build for Super Powers * Adding this so that the import factory gets build for Super Powers
* ImportFactory * ImportFactory
* Adding this so that the import cli gets build for Super Powers
* Import
*/ */
/** /**

View File

@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="5.0" method="upgrade"> <extension type="component" version="5.0" method="upgrade">
<name>COM_COMPONENTBUILDER</name> <name>COM_COMPONENTBUILDER</name>
<creationDate>13th October, 2024</creationDate> <creationDate>7th November, 2024</creationDate>
<author>Llewellyn van der Merwe</author> <author>Llewellyn van der Merwe</author>
<authorEmail>joomla@vdm.io</authorEmail> <authorEmail>joomla@vdm.io</authorEmail>
<authorUrl>https://dev.vdm.io</authorUrl> <authorUrl>https://dev.vdm.io</authorUrl>
<copyright>Copyright (C) 2015 Vast Development Method. All rights reserved.</copyright> <copyright>Copyright (C) 2015 Vast Development Method. All rights reserved.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license> <license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<version>5.0.4-alpha3</version> <version>5.0.4-beta1</version>
<description><![CDATA[ <description><![CDATA[
<h1>Component Builder (v.5.0.4-alpha3)</h1> <h1>Component Builder (v.5.0.4-beta1)</h1>
<div style="clear: both;"></div> <div style="clear: both;"></div>
<p>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. <p>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.

View File

@ -131,13 +131,13 @@
<element>pkg_component_builder</element> <element>pkg_component_builder</element>
<type>package</type> <type>package</type>
<client>site</client> <client>site</client>
<version>5.0.4-alpha3</version> <version>5.0.4-beta1</version>
<infourl title="Component Builder!">https://dev.vdm.io</infourl> <infourl title="Component Builder!">https://dev.vdm.io</infourl>
<downloads> <downloads>
<downloadurl type="full" format="zip">https://git.vdm.dev/api/v1/repos/joomla/pkg-component-builder/archive/v5.0.4-alpha3.zip</downloadurl> <downloadurl type="full" format="zip">https://git.vdm.dev/api/v1/repos/joomla/pkg-component-builder/archive/v5.0.4-beta1.zip</downloadurl>
</downloads> </downloads>
<tags> <tags>
<tag>alpha</tag> <tag>beta</tag>
</tags> </tags>
<maintainer>Llewellyn van der Merwe</maintainer> <maintainer>Llewellyn van der Merwe</maintainer>
<maintainerurl>https://dev.vdm.io</maintainerurl> <maintainerurl>https://dev.vdm.io</maintainerurl>

View File

@ -12,7 +12,7 @@
namespace VDM\Joomla\Abstraction; 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 * @since 3.2.0
*/ */
abstract class BaseTable implements Tableinterface abstract class BaseTable implements TableInterface
{ {
/** /**
* All areas/views/tables with their field details * All areas/views/tables with their field details

View File

@ -0,0 +1,258 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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(
<<<EOF
The <info>%command.name%</info> 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:
<info>php joomla.php %command.name%</info>
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;
}
}

View File

@ -0,0 +1 @@
<html><body bgcolor="#FFFFFF"></body></html>

View File

@ -38,6 +38,14 @@ abstract class Database
*/ */
protected string $table; protected string $table;
/**
* Date format to return
*
* @var string
* @since 5.0.2
*/
protected string $dateFormat = 'Y-m-d H:i:s';
/** /**
* Constructor * Constructor
* *
@ -62,23 +70,32 @@ abstract class Database
**/ **/
protected function quote($value) 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'; return 'NULL';
} }
if (is_numeric($value)) 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)) if (filter_var($value, FILTER_VALIDATE_INT))
{ {
return (int) $value; return (int) $value;
} }
elseif (filter_var($value, FILTER_VALIDATE_FLOAT))
if (filter_var($value, FILTER_VALIDATE_FLOAT))
{ {
return (float) $value; 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'; return $value ? 'TRUE' : 'FALSE';
} }
@ -86,10 +103,10 @@ abstract class Database
// For date and datetime values // For date and datetime values
if ($value instanceof \DateTime) 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); return $this->db->quote($value);
} }
@ -110,6 +127,17 @@ abstract class Database
} }
return $table; return $table;
}
/**
* Get the date format to return in the quote
*
* @return string
* @since 5.0.2
**/
protected function getDateFormat(): string
{
return $this->dateFormat;
} }
} }

View File

@ -14,7 +14,7 @@ namespace VDM\Joomla\Abstraction;
use VDM\Joomla\Utilities\StringHelper; use VDM\Joomla\Utilities\StringHelper;
use VDM\Joomla\Utilities\ArrayHelper; use VDM\Joomla\Utilities\ArrayHelper;
use VDM\Joomla\Interfaces\Tableinterface as Table; use VDM\Joomla\Interfaces\TableInterface as Table;
use VDM\Joomla\Interfaces\ModelInterface; use VDM\Joomla\Interfaces\ModelInterface;
@ -109,7 +109,7 @@ abstract class Model implements ModelInterface
/** /**
* Model a value of multiple items * 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 array|null $items The array of values
* @param string $field The field key * @param string $field The field key

View File

@ -14,7 +14,7 @@ namespace VDM\Joomla\Abstraction;
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use Joomla\CMS\Version; use Joomla\CMS\Version;
use VDM\Joomla\Interfaces\Tableinterface as Table; use VDM\Joomla\Interfaces\TableInterface as Table;
use VDM\Joomla\Interfaces\SchemaInterface; use VDM\Joomla\Interfaces\SchemaInterface;

View File

@ -14,7 +14,7 @@ namespace VDM\Joomla\Abstraction;
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use VDM\Joomla\Interfaces\SchemaInterface as Schema; 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\Utilities\ClassHelper;
use VDM\Joomla\Interfaces\SchemaCheckerInterface; use VDM\Joomla\Interfaces\SchemaCheckerInterface;

View File

@ -0,0 +1,130 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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);
}
}
}

View File

@ -0,0 +1,26 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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
{
}

View File

@ -12,10 +12,12 @@
namespace VDM\Joomla\Componentbuilder\Import; namespace VDM\Joomla\Componentbuilder\Import;
use Joomla\DI\Container;
use VDM\Joomla\Service\Table; use VDM\Joomla\Service\Table;
use VDM\Joomla\Service\Database; use VDM\Joomla\Service\Database;
use VDM\Joomla\Service\Model; use VDM\Joomla\Service\Model;
use VDM\Joomla\Service\Data; use VDM\Joomla\Service\Data;
use VDM\Joomla\Componentbuilder\Import\Service\Import;
use VDM\Joomla\Componentbuilder\File\Service\File; use VDM\Joomla\Componentbuilder\File\Service\File;
use VDM\Joomla\Componentbuilder\Service\Spreadsheet; use VDM\Joomla\Componentbuilder\Service\Spreadsheet;
use VDM\Joomla\Interfaces\FactoryInterface; use VDM\Joomla\Interfaces\FactoryInterface;
@ -50,6 +52,7 @@ abstract class Factory extends ExtendingFactory implements FactoryInterface
->registerServiceProvider(new Database()) ->registerServiceProvider(new Database())
->registerServiceProvider(new Model()) ->registerServiceProvider(new Model())
->registerServiceProvider(new Data()) ->registerServiceProvider(new Data())
->registerServiceProvider(new Import())
->registerServiceProvider(new File()) ->registerServiceProvider(new File())
->registerServiceProvider(new Spreadsheet()); ->registerServiceProvider(new Spreadsheet());
} }

View File

@ -0,0 +1,270 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}
}

View File

@ -0,0 +1,154 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}
}

View File

@ -0,0 +1,307 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}
}

View File

@ -0,0 +1,131 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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]);
}
}

View File

@ -0,0 +1,171 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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')
);
}
}

View File

@ -0,0 +1 @@
<html><body bgcolor="#FFFFFF"></body></html>

View File

@ -0,0 +1,149 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}

View File

@ -0,0 +1,33 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}

View File

@ -0,0 +1,49 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}

View File

@ -0,0 +1,109 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}

View File

@ -0,0 +1,68 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}

View File

@ -0,0 +1,75 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}

View File

@ -0,0 +1,40 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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;
}

View File

@ -0,0 +1 @@
<html><body bgcolor="#FFFFFF"></body></html>

View File

@ -17,6 +17,7 @@ use Joomla\DI\ServiceProviderInterface;
use VDM\Joomla\Componentbuilder\Spreadsheet\Header; use VDM\Joomla\Componentbuilder\Spreadsheet\Header;
use VDM\Joomla\Componentbuilder\Spreadsheet\Exporter; use VDM\Joomla\Componentbuilder\Spreadsheet\Exporter;
use VDM\Joomla\Componentbuilder\Spreadsheet\Importer; 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') $container->alias(Importer::class, 'Spreadsheet.Importer')
->share('Spreadsheet.Importer', [$this, 'getImporter'], true); ->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 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();
} }
} }

View File

@ -0,0 +1,109 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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);
}
}
}

View File

@ -54,7 +54,7 @@ final class Header
if ($row->getRowIndex() === $targetRow) if ($row->getRowIndex() === $targetRow)
{ {
$cellIterator = $row->getCellIterator(); $cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(false); $cellIterator->setIterateOnlyExistingCells(true);
foreach ($cellIterator as $cell) foreach ($cellIterator as $cell)
{ {
$headers[$cell->getColumn()] = $cell->getValue(); $headers[$cell->getColumn()] = $cell->getValue();

View File

@ -12,10 +12,8 @@
namespace VDM\Joomla\Componentbuilder\Spreadsheet; namespace VDM\Joomla\Componentbuilder\Spreadsheet;
use PhpOffice\PhpSpreadsheet\IOFactory; use VDM\Joomla\Componentbuilder\Interfaces\Spreadsheet\FileReaderInterface as FileReader;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use VDM\Joomla\Componentbuilder\Interfaces\Spreadsheet\RowDataProcessorInterface as RowDataProcessor;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use VDM\Joomla\Componentbuilder\Spreadsheet\ChunkReadFilter;
/** /**
@ -25,12 +23,33 @@ use VDM\Joomla\Componentbuilder\Spreadsheet\ChunkReadFilter;
*/ */
final class Importer 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. * Stream rows from a CSV or Excel file one by one using yield.
* *
* @param string $filePath The path to the file. * @param string $filePath The path to the file.
* @param int $startRow The starting row index (default is 1). * @param int $startRow The starting row index.
* @param int $chunkSize The number of rows to read per chunk (default is 100). * @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. * @return \Generator A generator that yields each row as an array.
* @throws \InvalidArgumentException If the file does not exist. * @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. * @throws SpreadsheetException If there is an error working with the spreadsheet.
* @since 3.2.0 * @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 foreach ($this->filereader->read($filePath, $startRow, $chunkSize) as $row)
if (!is_file($filePath))
{ {
throw new \InvalidArgumentException("File not found: $filePath"); yield $processor->process($row);
}
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);
} }
} }
} }

View File

@ -12,7 +12,7 @@
namespace VDM\Joomla\Componentbuilder; namespace VDM\Joomla\Componentbuilder;
use VDM\Joomla\Interfaces\Tableinterface; use VDM\Joomla\Interfaces\TableInterface;
use VDM\Joomla\Abstraction\BaseTable; use VDM\Joomla\Abstraction\BaseTable;
@ -21,7 +21,7 @@ use VDM\Joomla\Abstraction\BaseTable;
* *
* @since 3.2.0 * @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 * All areas/views/tables with their field details

View File

@ -0,0 +1,426 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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 = '/(?<type>\w+)(\((?<size>\d+)(,\s*(?<precision>\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);
}
}

View File

@ -15,7 +15,7 @@ namespace VDM\Joomla\Interfaces;
/** /**
* The VDM Core Table Interface * The VDM Core Table Interface
*/ */
interface Tableinterface interface TableInterface
{ {
/** /**
* Get any value from a item/field/column of an area/view/table * Get any value from a item/field/column of an area/view/table

View File

@ -0,0 +1,34 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/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);
}

View File

@ -16,6 +16,7 @@ use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface; use Joomla\DI\ServiceProviderInterface;
use VDM\Joomla\Componentbuilder\Table as DataTable; use VDM\Joomla\Componentbuilder\Table as DataTable;
use VDM\Joomla\Componentbuilder\Table\Schema; 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') $container->alias(Schema::class, 'Table.Schema')
->share('Table.Schema', [$this, 'getSchema'], true); ->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( return new Schema(
$container->get('Table') $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')
);
} }
} }