Release of v5.1.0
Add [AllowDynamicProperties] in the base view class for J5. Move the _prepareDocument above the display call in the base view class. Remove all backward compatibility issues, so JCB will not need the [Backward Compatibility] plugin to run. Added new import powers for custom import of spreadsheets. Move the setDocument and _prepareDocument above the display in the site view and custom admin view. Update the trashhelper layout to work in Joomla 5. Add AllowDynamicProperties (Joomla 4+5) to view class to allow Custom Dynamic Get methods to work without issues. Fix Save failed issue in dynamicGet. #1148. Move all [TEXT, EDITOR, TEXTAREA] fields from [NOT NULL] to [NULL]. Add the DateHelper class and improve the date methods. Add simple SessionHelper class. Add first classes for the new import engine. Improve the [VDM Registry] to be Joomla Registry Compatible. Move all registries to the [VDM Registry] class. Fix Checked Out to be null and not 0. (#1194). Fix created_by, modified_by, checked_out fields in the compiler of the SQL. (#1194). Update all core date fields in table class. (#1188). Update created_by, modified_by, checked_out fields in table class. Implementation of the decentralized Super-Power CORE repository network. (#1190). Fix the noticeboard to display Llewellyn's Joomla Social feed. Started compiling JCB5 on Joomla 5 with PHP 8.2. Add init_defaults option for dynamic form selection setup (to int new items with default values dynamically). Update all JCB 5 tables to utf8mb4_unicode_ci collation if misaligned. Move all internal ID linking to GUID inside of JCB 5. Updated the admin-tab-fields in add-fields view. #1205. Remove Custom Import Tab from admin view. Improved the customcode and placeholder search features.
This commit is contained in:
@@ -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
|
||||
@@ -108,8 +108,7 @@ abstract class BaseTable implements Tableinterface
|
||||
'store' => NULL,
|
||||
'tab_name' => NULL,
|
||||
'db' => [
|
||||
'type' => 'INT(10) unsigned',
|
||||
'default' => '0',
|
||||
'type' => 'INT unsigned',
|
||||
'null_switch' => 'NULL',
|
||||
'key' => true,
|
||||
'key_name' => 'modifiedby'
|
||||
@@ -125,7 +124,6 @@ abstract class BaseTable implements Tableinterface
|
||||
'tab_name' => NULL,
|
||||
'db' => [
|
||||
'type' => 'DATETIME',
|
||||
'default' => '0000-00-00 00:00:00',
|
||||
'null_switch' => 'NULL'
|
||||
]
|
||||
],
|
||||
@@ -138,7 +136,7 @@ abstract class BaseTable implements Tableinterface
|
||||
'store' => NULL,
|
||||
'tab_name' => NULL,
|
||||
'db' => [
|
||||
'type' => 'INT(10) unsigned',
|
||||
'type' => 'INT unsigned',
|
||||
'default' => '0',
|
||||
'null_switch' => 'NULL',
|
||||
'key' => true,
|
||||
@@ -155,7 +153,7 @@ abstract class BaseTable implements Tableinterface
|
||||
'tab_name' => NULL,
|
||||
'db' => [
|
||||
'type' => 'DATETIME',
|
||||
'default' => '0000-00-00 00:00:00',
|
||||
'default' => 'CURRENT_TIMESTAMP',
|
||||
'null_switch' => 'NULL'
|
||||
]
|
||||
],
|
||||
@@ -168,8 +166,7 @@ abstract class BaseTable implements Tableinterface
|
||||
'store' => NULL,
|
||||
'tab_name' => NULL,
|
||||
'db' => [
|
||||
'type' => 'INT(10) unsigned',
|
||||
'default' => '0',
|
||||
'type' => 'INT unsigned',
|
||||
'null_switch' => 'NULL',
|
||||
'key' => true,
|
||||
'key_name' => 'checkout'
|
||||
@@ -185,7 +182,6 @@ abstract class BaseTable implements Tableinterface
|
||||
'tab_name' => NULL,
|
||||
'db' => [
|
||||
'type' => 'DATETIME',
|
||||
'default' => '0000-00-00 00:00:00',
|
||||
'null_switch' => 'NULL'
|
||||
]
|
||||
],
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1 @@
|
||||
<html><body bgcolor="#FFFFFF"></body></html>
|
@@ -12,7 +12,8 @@
|
||||
namespace VDM\Joomla\Abstraction;
|
||||
|
||||
|
||||
use Joomla\CMS\Factory as JoomlaFactory;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use VDM\Joomla\Utilities\Component\Helper;
|
||||
|
||||
|
||||
@@ -38,6 +39,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
|
||||
*
|
||||
@@ -46,7 +55,7 @@ abstract class Database
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = JoomlaFactory::getDbo();
|
||||
$this->db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
// set the component table
|
||||
$this->table = '#__' . Helper::getCode();
|
||||
@@ -62,23 +71,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 +104,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 +128,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -35,14 +35,6 @@ use VDM\Joomla\Interfaces\FactoryInterface;
|
||||
**/
|
||||
abstract class Factory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Global Package Container
|
||||
*
|
||||
* @var Container|null
|
||||
* @since 0.0.0
|
||||
**/
|
||||
protected static ?Container $container = null;
|
||||
|
||||
/**
|
||||
* Get any class from the package container
|
||||
*
|
||||
|
@@ -12,41 +12,17 @@
|
||||
namespace VDM\Joomla\Abstraction;
|
||||
|
||||
|
||||
use Joomla\Registry\Registry as JoomlaRegistry;
|
||||
use VDM\Joomla\Utilities\String\ClassfunctionHelper;
|
||||
use VDM\Joomla\Abstraction\Registry;
|
||||
|
||||
|
||||
/**
|
||||
* Config
|
||||
* A Dynamic Function Registry
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @since 5.0.4
|
||||
*/
|
||||
abstract class BaseConfig extends JoomlaRegistry
|
||||
abstract class FunctionRegistry extends Registry
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Instantiate the internal data object.
|
||||
$this->data = new \stdClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* setting any config value
|
||||
*
|
||||
* @param string $key The value's key/path name
|
||||
* @param mixed $value Optional default value, returned if the internal value is null.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __set($key, $value)
|
||||
{
|
||||
$this->set($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* getting any valid value
|
||||
*
|
||||
@@ -76,7 +52,7 @@ abstract class BaseConfig extends JoomlaRegistry
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function get($path, $default = null)
|
||||
public function get(string $path, $default = null): mixed
|
||||
{
|
||||
// function name with no underscores
|
||||
$method = 'get' . ucfirst((string) ClassfunctionHelper::safe(str_replace('_', '', $path)));
|
||||
@@ -86,7 +62,8 @@ abstract class BaseConfig extends JoomlaRegistry
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
elseif (method_exists($this, $method))
|
||||
// Use the method if it's callable and not excluded
|
||||
elseif ($this->isCallableMethod($method))
|
||||
{
|
||||
$value = $this->{$method}($default);
|
||||
|
||||
@@ -104,19 +81,46 @@ abstract class BaseConfig extends JoomlaRegistry
|
||||
* @param string $path Parent registry Path (e.g. joomla.content.showauthor)
|
||||
* @param mixed $value Value of entry
|
||||
*
|
||||
* @return mixed The value of the that has been set.
|
||||
* @return mixed The values of the path that has been set.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function appendArray(string $path, $value)
|
||||
{
|
||||
// check if it does not exist
|
||||
if (!$this->exists($path))
|
||||
return $this->add($path, $value, true)->get($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a method is callable on this object, excluding certain methods.
|
||||
*
|
||||
* This method checks if a method exists on this object and is callable, but excludes
|
||||
* certain methods to prevent unintended access or recursion. It helps to safely determine
|
||||
* if a dynamic getter method can be invoked without interfering with core methods.
|
||||
*
|
||||
* @param string $method The method name to check.
|
||||
*
|
||||
* @return bool True if the method is callable and not excluded, false otherwise.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
protected function isCallableMethod(string $method): bool
|
||||
{
|
||||
// List of methods to exclude from dynamic access
|
||||
$excludedMethods = [
|
||||
'getActive',
|
||||
'get',
|
||||
'getSeparator',
|
||||
'getIterator',
|
||||
'getName',
|
||||
'getActiveKeys'
|
||||
];
|
||||
|
||||
// Check if the method exists and is not excluded
|
||||
if (method_exists($this, $method) && !in_array($method, $excludedMethods, true))
|
||||
{
|
||||
$this->set($path, []);
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->append($path, $value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,8 @@ use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Filesystem\Folder;
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use VDM\Joomla\Gitea\Repository\Contents;
|
||||
use VDM\Joomla\Interfaces\Git\ApiInterface as Api;
|
||||
use VDM\Joomla\Componentbuilder\Network\Resolve;
|
||||
use VDM\Joomla\Utilities\FileHelper;
|
||||
use VDM\Joomla\Utilities\JsonHelper;
|
||||
use VDM\Joomla\Interfaces\GrepInterface;
|
||||
@@ -50,6 +52,14 @@ abstract class Grep implements GrepInterface
|
||||
**/
|
||||
public ?array $paths;
|
||||
|
||||
/**
|
||||
* The Grep target [network]
|
||||
*
|
||||
* @var string
|
||||
* @since 5.0.4
|
||||
**/
|
||||
protected ?string $target = null;
|
||||
|
||||
/**
|
||||
* Order of global search
|
||||
*
|
||||
@@ -82,6 +92,14 @@ abstract class Grep implements GrepInterface
|
||||
*/
|
||||
protected string $index_path = 'index.json';
|
||||
|
||||
/**
|
||||
* The VDM global API base
|
||||
*
|
||||
* @var string
|
||||
* @since 5.0.4
|
||||
**/
|
||||
protected string $api_base = '//git.vdm.dev/';
|
||||
|
||||
/**
|
||||
* Gitea Repository Contents
|
||||
*
|
||||
@@ -90,6 +108,14 @@ abstract class Grep implements GrepInterface
|
||||
**/
|
||||
protected Contents $contents;
|
||||
|
||||
/**
|
||||
* The Resolve Class.
|
||||
*
|
||||
* @var Resolve
|
||||
* @since 5.0.4
|
||||
*/
|
||||
protected Resolve $resolve;
|
||||
|
||||
/**
|
||||
* Joomla Application object
|
||||
*
|
||||
@@ -101,17 +127,22 @@ abstract class Grep implements GrepInterface
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Contents $contents The Gitea Repository Contents object.
|
||||
* @param array $paths The approved paths
|
||||
* @param string|null $path The local path
|
||||
* @param CMSApplication|null $app The CMS Application object.
|
||||
* @param Contents $contents The Gitea Repository Contents object.
|
||||
* @param Resolve $resolve The Resolve Class.
|
||||
* @param array $paths The approved paths
|
||||
* @param string|null $path The local path
|
||||
* @param CMSApplication|null $app The CMS Application object.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function __construct(Contents $contents, array $paths, ?string $path = null, ?CMSApplication $app = null)
|
||||
public function __construct(
|
||||
Contents $contents, Resolve $resolve,
|
||||
array $paths, ?string $path = null,
|
||||
?CMSApplication $app = null)
|
||||
{
|
||||
$this->contents = $contents;
|
||||
$this->resolve = $resolve;
|
||||
$this->paths = $paths;
|
||||
$this->path = $path;
|
||||
$this->app = $app ?: Factory::getApplication();
|
||||
@@ -264,6 +295,38 @@ abstract class Grep implements GrepInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads API config using the provided base URL and token.
|
||||
*
|
||||
* This method checks if the base URL contains 'https://git.vdm.dev/'.
|
||||
* If it does, it uses the token as is (which may be null).
|
||||
* If not, it ensures the token is not null by defaulting to an empty string.
|
||||
*
|
||||
* @param Api $api The api object with a load_ method.
|
||||
* @param string|null $base The base URL path.
|
||||
* @param string|null $token The token for authentication (can be null).
|
||||
*
|
||||
* @return void
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function loadApi(Api $api, ?string $base, ?string $token): void
|
||||
{
|
||||
// Determine the token to use based on the base URL
|
||||
if ($base && strpos($base, $this->api_base) !== false)
|
||||
{
|
||||
// If base contains $this->api_base = https://git.vdm.dev/, use the token as is
|
||||
$tokenToUse = $token;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, ensure the token is not null (use empty string if null)
|
||||
$tokenToUse = $token ?? '';
|
||||
}
|
||||
|
||||
// Load the content with the determined base and token
|
||||
$api->load_($base, $tokenToUse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set repository messages and errors based on given conditions.
|
||||
*
|
||||
@@ -584,7 +647,7 @@ abstract class Grep implements GrepInterface
|
||||
try
|
||||
{
|
||||
// load the base and token if set
|
||||
$this->contents->load_($path->base ?? null, $path->token ?? null);
|
||||
$this->loadApi($this->contents, $path->base ?? null, $path->token ?? null);
|
||||
$path->index = $this->contents->get($path->organisation, $path->repository, $this->getIndexPath(), $this->getBranchName($path));
|
||||
}
|
||||
catch (\Exception $e)
|
||||
@@ -640,6 +703,12 @@ abstract class Grep implements GrepInterface
|
||||
if (isset($path->organisation) && strlen($path->organisation) > 1 &&
|
||||
isset($path->repository) && strlen($path->repository) > 1)
|
||||
{
|
||||
// resolve API if needed
|
||||
if (!empty($path->base))
|
||||
{
|
||||
$this->resolve->api($this->target ?? $path->repository, $path->base, $path->organisation, $path->repository);
|
||||
}
|
||||
|
||||
// build the path
|
||||
$path->path = trim($path->organisation) . '/' . trim($path->repository);
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -12,6 +12,7 @@
|
||||
namespace VDM\Joomla\Abstraction;
|
||||
|
||||
|
||||
use Joomla\Registry\Factory as FormatFactory;
|
||||
use VDM\Joomla\Interfaces\Registryinterface;
|
||||
use VDM\Joomla\Abstraction\ActiveRegistry;
|
||||
|
||||
@@ -21,18 +22,238 @@ use VDM\Joomla\Abstraction\ActiveRegistry;
|
||||
*
|
||||
* Don't use this beyond 10 dimensional depth for best performance.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
* @since 5.0.4 Joomla Registry Compatible
|
||||
*/
|
||||
abstract class Registry extends ActiveRegistry implements Registryinterface
|
||||
abstract class Registry extends ActiveRegistry implements Registryinterface, \JsonSerializable, \ArrayAccess, \IteratorAggregate, \Countable
|
||||
{
|
||||
/**
|
||||
* Path separator
|
||||
*
|
||||
* @var string|null
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected ?string $separator = '.';
|
||||
|
||||
/**
|
||||
* The name of the registry.
|
||||
*
|
||||
* @var string|null
|
||||
* @since 5.0.4
|
||||
*/
|
||||
protected ?string $name = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Initializes the Registry object with optional data.
|
||||
*
|
||||
* @param mixed $data Optional data to load into the registry.
|
||||
* Can be an array, string, or object.
|
||||
* @param string|null $separator The path separator, and empty string will flatten the registry.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function __construct($data = null, ?string $separator = null)
|
||||
{
|
||||
// we don't allow null on initialization (default is a dot)
|
||||
// so that all class inheritance can override the separator property
|
||||
// use an empty string if you want to flatten the registry
|
||||
if ($separator !== null)
|
||||
{
|
||||
$this->setSeparator($separator);
|
||||
}
|
||||
|
||||
if ($data !== null)
|
||||
{
|
||||
if (is_array($data))
|
||||
{
|
||||
$this->loadArray($data);
|
||||
}
|
||||
elseif (is_string($data))
|
||||
{
|
||||
$this->loadString($data);
|
||||
}
|
||||
elseif (is_object($data))
|
||||
{
|
||||
$this->loadObject($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to get a value from the registry.
|
||||
*
|
||||
* Allows for accessing registry data using object property syntax.
|
||||
*
|
||||
* @param string $name The name of the property to get.
|
||||
*
|
||||
* @return mixed The value of the property, or null if not found.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to set a value in the registry.
|
||||
*
|
||||
* Allows for setting registry data using object property syntax.
|
||||
*
|
||||
* @param string $name The name of the property to set.
|
||||
* @param mixed $value The value to set.
|
||||
*
|
||||
* @return void
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to check if a property is set in the registry.
|
||||
*
|
||||
* Allows for using isset() on registry properties.
|
||||
*
|
||||
* @param string $name The name of the property to check.
|
||||
*
|
||||
* @return bool True if the property is set, false otherwise.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return $this->exists($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to unset a property in the registry.
|
||||
*
|
||||
* Allows for using unset() on registry properties.
|
||||
*
|
||||
* @param string $name The name of the property to unset.
|
||||
*
|
||||
* @return void
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function __unset($name)
|
||||
{
|
||||
$this->remove($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to clone the registry.
|
||||
*
|
||||
* Performs a deep copy of the registry data.
|
||||
*
|
||||
* @return void
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->active = unserialize(serialize($this->active));
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to convert the registry to a string.
|
||||
*
|
||||
* Returns the registry data in JSON format.
|
||||
*
|
||||
* @return string The registry data in JSON format.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data into the registry from a string using Joomla's format classes.
|
||||
*
|
||||
* @param string $data The data string to load.
|
||||
* @param string $format The format of the data string. Supported formats: 'json', 'ini', 'xml', 'php'.
|
||||
* @param array $options Options used by the formatter
|
||||
*
|
||||
* @return self
|
||||
* @throws \InvalidArgumentException If the format is not supported.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function loadString(string $data, string $format = 'JSON', array $options = []): self
|
||||
{
|
||||
// Load a string into the given namespace [or default namespace if not given]
|
||||
$object = FormatFactory::getFormat($format, $options)->stringToObject($data, $options);
|
||||
|
||||
// Merge the object into the registry
|
||||
$this->loadObject($object);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data into the registry from an object.
|
||||
*
|
||||
* @param object $object The data object to load.
|
||||
*
|
||||
* @return self
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function loadObject(object $object): self
|
||||
{
|
||||
// Convert the object to an array
|
||||
$array = $this->objectToArray($object);
|
||||
|
||||
// Merge the array into the registry
|
||||
$this->loadArray($array);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data into the registry from an array.
|
||||
*
|
||||
* The loaded data will be merged into the registry's existing data.
|
||||
*
|
||||
* @param array $array The array of data to load into the registry.
|
||||
*
|
||||
* @return self
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function loadArray(array $array): self
|
||||
{
|
||||
$this->active = $this->arrayMergeRecursive($this->active, $array);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data into the registry from a file.
|
||||
*
|
||||
* @param string $path The path to the file to load.
|
||||
* @param string $format The format of the file. Supported formats: 'json', 'ini', 'xml', 'php'.
|
||||
*
|
||||
* @return self
|
||||
* @throws \InvalidArgumentException If the file does not exist or is not readable.
|
||||
* @throws \RuntimeException If the file cannot be read.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function loadFile(string $path, string $format = 'json'): self
|
||||
{
|
||||
if (!file_exists($path) || !is_readable($path))
|
||||
{
|
||||
throw new \InvalidArgumentException("File does not exist or is not readable: {$path}");
|
||||
}
|
||||
|
||||
$data = file_get_contents($path);
|
||||
|
||||
if ($data === false)
|
||||
{
|
||||
throw new \RuntimeException("Failed to read file: {$path}");
|
||||
}
|
||||
|
||||
$this->loadString($data, $format);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value into the registry using multiple keys.
|
||||
*
|
||||
@@ -41,7 +262,7 @@ abstract class Registry extends ActiveRegistry implements Registryinterface
|
||||
*
|
||||
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
||||
* @return self
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function set(string $path, $value): self
|
||||
{
|
||||
@@ -67,7 +288,7 @@ abstract class Registry extends ActiveRegistry implements Registryinterface
|
||||
*
|
||||
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
||||
* @return self
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function add(string $path, $value, ?bool $asArray = null): self
|
||||
{
|
||||
@@ -89,9 +310,9 @@ abstract class Registry extends ActiveRegistry implements Registryinterface
|
||||
*
|
||||
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
||||
* @return mixed The value or sub-array from the storage. Null if the location doesn't exist.
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function get(string $path, $default = null)
|
||||
public function get(string $path, $default = null): mixed
|
||||
{
|
||||
if (($keys = $this->getActiveKeys($path)) === null)
|
||||
{
|
||||
@@ -108,7 +329,7 @@ abstract class Registry extends ActiveRegistry implements Registryinterface
|
||||
*
|
||||
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
||||
* @return self
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function remove(string $path): self
|
||||
{
|
||||
@@ -129,7 +350,7 @@ abstract class Registry extends ActiveRegistry implements Registryinterface
|
||||
*
|
||||
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
||||
* @return bool True if the location exists, false otherwise.
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function exists(string $path): bool
|
||||
{
|
||||
@@ -141,13 +362,308 @@ abstract class Registry extends ActiveRegistry implements Registryinterface
|
||||
return $this->existsActive(...$keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify data which should be serialized to JSON.
|
||||
*
|
||||
* @return mixed Data which can be serialized by json_encode(),
|
||||
* which is a value of any type other than a resource.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function jsonSerialize(): mixed
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of the registry.
|
||||
*
|
||||
* @return int The number of elements in the registry.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->active);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a given offset exists in the registry.
|
||||
*
|
||||
* @param mixed $offset An offset to check for.
|
||||
*
|
||||
* @return bool True if the offset exists, false otherwise.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
if (!is_string($offset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return $this->exists($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value at a given offset.
|
||||
*
|
||||
* @param mixed $offset The offset to retrieve.
|
||||
*
|
||||
* @return mixed The value at the specified offset.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
if (!is_string($offset))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return $this->get($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value at a given offset.
|
||||
*
|
||||
* @param mixed $offset The offset to assign the value to.
|
||||
* @param mixed $value The value to set.
|
||||
*
|
||||
* @return void
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
if (!is_string($offset))
|
||||
{
|
||||
return;
|
||||
}
|
||||
$this->set($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the value at a given offset.
|
||||
*
|
||||
* @param mixed $offset The offset to unset.
|
||||
*
|
||||
* @return void
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
if (!is_string($offset))
|
||||
{
|
||||
return;
|
||||
}
|
||||
$this->remove($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an external iterator for the registry.
|
||||
*
|
||||
* @return \Traversable An instance of an object implementing Iterator or Traversable.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function getIterator(): \Traversable
|
||||
{
|
||||
return new \ArrayIterator($this->active);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the registry data as an associative array.
|
||||
*
|
||||
* @return array The registry data.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the registry data as an object.
|
||||
*
|
||||
* @return object The registry data converted to an object.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function toObject()
|
||||
{
|
||||
return $this->arrayToObject($this->active);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the registry data to a string in the specified format.
|
||||
*
|
||||
* @param string $format The format to output the string in. Supported formats: 'json', 'ini', 'xml', 'php'.
|
||||
* @param array $options Options used by the formatter.
|
||||
*
|
||||
* @return string The registry data in the specified format.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the format is not supported.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function toString(string $format = 'JSON', array $options = []): string
|
||||
{
|
||||
// Convert the internal array to an object
|
||||
$object = $this->arrayToObject($this->active);
|
||||
|
||||
return FormatFactory::getFormat($format, $options)->objectToString($object, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens the registry data into a one-dimensional array.
|
||||
*
|
||||
* @param string|null $separator The separator for the key names.
|
||||
* @param bool $full True to include the full path as keys.
|
||||
*
|
||||
* @return array The flattened data array.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function flatten(?string $separator = null, bool $full = false): array
|
||||
{
|
||||
// we use default separator
|
||||
if ($separator === null)
|
||||
{
|
||||
$separator = $this->separator;
|
||||
}
|
||||
|
||||
return $this->flattenArray($this->active, $separator, $full);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a default value if not already set.
|
||||
*
|
||||
* @param string $path The registry path (e.g., 'vdm.content.builder').
|
||||
* @param mixed $default The default value to set if the path does not exist.
|
||||
*
|
||||
* @return mixed The value of the path after the method call.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function def(string $path, $default)
|
||||
{
|
||||
if (!$this->exists($path))
|
||||
{
|
||||
$this->set($path, $default);
|
||||
return $default;
|
||||
}
|
||||
return $this->get($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges another registry into this one.
|
||||
*
|
||||
* The data from the source registry will be merged into this registry,
|
||||
* overwriting any existing values with the same keys.
|
||||
*
|
||||
* @param Registryinterface $source The registry to merge with this one.
|
||||
*
|
||||
* @return self
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function merge(Registryinterface $source): self
|
||||
{
|
||||
$this->active = $this->arrayMergeRecursive($this->active, $source->toArray());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all data from the registry.
|
||||
*
|
||||
* @return self
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function clear(): self
|
||||
{
|
||||
$this->active = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a subset of the registry data based on a given path.
|
||||
*
|
||||
* @param string $path The registry path to extract.
|
||||
* @param mixed $default Optional default value, returned if the path does not exist.
|
||||
* @param string|null $separator The path separator.
|
||||
*
|
||||
* @return self A new Registry instance with the extracted data.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function extract(string $path, $default = null, ?string $separator = null): self
|
||||
{
|
||||
$originalSeparator = $this->getSeparator();
|
||||
if ($separator !== null)
|
||||
{
|
||||
$this->setSeparator($separator);
|
||||
}
|
||||
|
||||
$data = $this->get($path, $default);
|
||||
|
||||
if ($separator !== null)
|
||||
{
|
||||
$this->setSeparator($originalSeparator);
|
||||
}
|
||||
|
||||
$newRegistry = new static();
|
||||
|
||||
if ($data !== $default)
|
||||
{
|
||||
if (is_array($data))
|
||||
{
|
||||
$newRegistry->loadArray($data);
|
||||
}
|
||||
else
|
||||
{
|
||||
$newRegistry->set('value', $data);
|
||||
}
|
||||
}
|
||||
|
||||
return $newRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends content into the registry.
|
||||
*
|
||||
* If a key exists, the value will be appended to the existing value.
|
||||
*
|
||||
* @param string $path The registry path (e.g., 'vdm.content.builder').
|
||||
* @param mixed $value The value to append.
|
||||
*
|
||||
* @return self
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function append(string $path, $value): self
|
||||
{
|
||||
return $this->add($path, $value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the registry.
|
||||
*
|
||||
* @return string|null The name of the registry.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the registry.
|
||||
*
|
||||
* @param string|null $name The name to set.
|
||||
*
|
||||
* @return self
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function setName(?string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a separator value
|
||||
*
|
||||
* @param string|null $value The value to set.
|
||||
*
|
||||
* @return self
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function setSeparator(?string $value): self
|
||||
{
|
||||
@@ -156,13 +672,140 @@ abstract class Registry extends ActiveRegistry implements Registryinterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current path separator used in registry paths.
|
||||
*
|
||||
* @return string|null The path separator.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public function getSeparator(): ?string
|
||||
{
|
||||
return $this->separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts an array to an object.
|
||||
*
|
||||
* This method is used to convert the internal array data into an object
|
||||
* structure suitable for serialization or other operations that require objects.
|
||||
*
|
||||
* @param mixed $data The data to convert.
|
||||
*
|
||||
* @return mixed The converted object, or the original data if not an array.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
protected function arrayToObject($data)
|
||||
{
|
||||
if (is_array($data))
|
||||
{
|
||||
$object = new \stdClass();
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
// Handle numeric keys for object properties
|
||||
if (is_numeric($key))
|
||||
{
|
||||
$key = 'item' . $key;
|
||||
}
|
||||
$object->{$key} = $this->arrayToObject($value);
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts an object to an array.
|
||||
*
|
||||
* This method is used to convert data loaded from formats that produce objects
|
||||
* (e.g., JSON, XML) into an array structure for internal storage.
|
||||
*
|
||||
* @param mixed $data The data to convert.
|
||||
*
|
||||
* @return mixed The converted array, or the original data if not an object.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
protected function objectToArray($data)
|
||||
{
|
||||
return json_decode(json_encode($data), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively merges two arrays.
|
||||
*
|
||||
* This method merges the elements of two arrays together so that the values of one
|
||||
* are appended to the end of the previous one. It preserves numeric keys.
|
||||
*
|
||||
* @param array $array1 The array to merge into.
|
||||
* @param array $array2 The array to merge from.
|
||||
*
|
||||
* @return array The merged array.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
protected function arrayMergeRecursive(array $array1, array $array2): array
|
||||
{
|
||||
foreach ($array2 as $key => $value)
|
||||
{
|
||||
// If the value is an array and the key exists in both arrays, merge recursively
|
||||
if (is_array($value) && isset($array1[$key]) && is_array($array1[$key]))
|
||||
{
|
||||
$array1[$key] = $this->arrayMergeRecursive($array1[$key], $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, replace or set the value
|
||||
$array1[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $array1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to recursively flatten the array.
|
||||
*
|
||||
* @param array $array The array to flatten.
|
||||
* @param string $separator The separator for the key names.
|
||||
* @param bool $full True to include the full path as keys.
|
||||
* @param array $flattened The flattened array (used internally for recursion).
|
||||
* @param string $path The current path (used internally for recursion).
|
||||
*
|
||||
* @return array The flattened array.
|
||||
* @since 5.0.4
|
||||
*/
|
||||
protected function flattenArray(array $array, string $separator, bool $full, array $flattened = [], string $path = ''): array
|
||||
{
|
||||
foreach ($array as $key => $value)
|
||||
{
|
||||
if ($full)
|
||||
{
|
||||
$newPath = $path === '' ? $key : $path . $separator . $key;
|
||||
}
|
||||
else
|
||||
{
|
||||
$newPath = $key;
|
||||
}
|
||||
|
||||
if (is_array($value))
|
||||
{
|
||||
$flattened = $this->flattenArray($value, $separator, $full, $flattened, $newPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
$flattened[$newPath] = $value;
|
||||
}
|
||||
}
|
||||
return $flattened;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get that the active keys from a path
|
||||
*
|
||||
* @param string $path The path to determine the location registry.
|
||||
*
|
||||
* @return array|null The valid array of keys
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function getActiveKeys(string $path): ?array
|
||||
{
|
||||
|
@@ -13,11 +13,12 @@ namespace VDM\Joomla\Abstraction\Registry\Traits;
|
||||
|
||||
|
||||
/**
|
||||
* Count Values
|
||||
* Count Values in a Path
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
* @since 5.0.2 name changed to PathCount to avoid collusion in core registry class
|
||||
*/
|
||||
trait Count
|
||||
trait PathCount
|
||||
{
|
||||
/**
|
||||
* Retrieves number of values (or sub-array) from the storage using multiple keys.
|
||||
@@ -28,7 +29,7 @@ trait Count
|
||||
* @return int The number of values
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function count(string $path): int
|
||||
public function pathCount(string $path): int
|
||||
{
|
||||
if (($values = $this->get($path)) === null)
|
||||
{
|
@@ -13,11 +13,12 @@ namespace VDM\Joomla\Abstraction\Registry\Traits;
|
||||
|
||||
|
||||
/**
|
||||
* To String Values
|
||||
* Path To String Values
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
* @since 5.0.2 name changed to PathToString to avoid collusion in core registry class
|
||||
*/
|
||||
trait ToString
|
||||
trait PathToString
|
||||
{
|
||||
/**
|
||||
* Convert an array of values to a string (or return string)
|
||||
@@ -26,9 +27,9 @@ trait ToString
|
||||
* @param string $seperator Return string separator
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function toString(string $path, string $separator = ''): string
|
||||
public function pathToString(string $path, string $separator = ''): string
|
||||
{
|
||||
// Return default value if path is empty
|
||||
if (empty($path))
|
@@ -416,6 +416,8 @@ abstract class Set implements SetInterface
|
||||
|
||||
$settings = $this->mergeIndexSettings($repoGuid, $settings);
|
||||
|
||||
$this->grep->loadApi($this->git, $repo->base ?? null, $repo->token ?? null);
|
||||
|
||||
$this->updateIndexMainFile(
|
||||
$repo,
|
||||
$this->getIndexSettingsPath(),
|
||||
@@ -429,6 +431,8 @@ abstract class Set implements SetInterface
|
||||
$this->mainReadme->get($settings),
|
||||
'Update main readme file'
|
||||
);
|
||||
|
||||
$this->git->reset_();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -580,7 +584,7 @@ abstract class Set implements SetInterface
|
||||
|
||||
$this->setRepoPlaceholders($repo);
|
||||
|
||||
$this->git->load_($repo->base ?? null, $repo->token ?? null);
|
||||
$this->grep->loadApi($this->git, $repo->base ?? null, $repo->token ?? null);
|
||||
|
||||
if (($existing = $this->grep->get($item->guid, ['remote'], $repo)) !== null)
|
||||
{
|
||||
|
@@ -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;
|
||||
|
||||
|
||||
@@ -96,6 +96,22 @@ abstract class Schema implements SchemaInterface
|
||||
**/
|
||||
protected $currentVersion;
|
||||
|
||||
/**
|
||||
* Current DB Version We are IN
|
||||
*
|
||||
* @var string
|
||||
* @since 5.0.4
|
||||
**/
|
||||
protected string $dbVersion;
|
||||
|
||||
/**
|
||||
* Current DB Type We are IN
|
||||
*
|
||||
* @var string
|
||||
* @since 5.0.4
|
||||
**/
|
||||
protected string $dbType;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -112,10 +128,16 @@ abstract class Schema implements SchemaInterface
|
||||
// set the database object
|
||||
$this->db = Factory::getDbo();
|
||||
|
||||
// get current component tables
|
||||
// current DB version
|
||||
$this->dbVersion = $this->db->getVersion();
|
||||
|
||||
// current DB type
|
||||
$this->dbType = $this->db->getServerType();
|
||||
|
||||
// get current website tables
|
||||
$this->tables = $this->db->getTableList();
|
||||
|
||||
// set the component table
|
||||
// set the component table prefix
|
||||
$this->prefix = $this->db->getPrefix() . $this->getCode();
|
||||
|
||||
// set the current version
|
||||
@@ -579,41 +601,57 @@ abstract class Schema implements SchemaInterface
|
||||
*/
|
||||
protected function isDataTypeChangeSignificant(string $currentType, string $expectedType): bool
|
||||
{
|
||||
// Normalize both input types to lowercase for case-insensitive comparison
|
||||
$currentType = strtolower($currentType);
|
||||
$expectedType = strtolower($expectedType);
|
||||
// Normalize both input types to lowercase and remove extra spaces for comparison
|
||||
$currentType = strtolower(trim($currentType));
|
||||
$expectedType = strtolower(trim($expectedType));
|
||||
|
||||
// Regex to extract the base data type and numeric parameters with named groups
|
||||
$typePattern = '/^(?<datatype>\w+)(\((?<params>\d+(,\d+)?)\))?/';
|
||||
// Regex to extract the base data type and numeric parameters (size and precision) with named groups
|
||||
$typePattern = '/^(?<datatype>\w+)(\((?<params>\s*\d+\s*(,\s*\d+\s*)?)\))?/';
|
||||
|
||||
// Match types and parameters
|
||||
preg_match($typePattern, $currentType, $currentMatches);
|
||||
preg_match($typePattern, $expectedType, $expectedMatches);
|
||||
|
||||
// Compare base types
|
||||
// Compare base types (datatype without size/precision)
|
||||
if ($currentMatches['datatype'] !== $expectedMatches['datatype'])
|
||||
{
|
||||
return true; // Base types differ
|
||||
}
|
||||
|
||||
// Define types where size and other modifiers are irrelevant
|
||||
// Define numeric types where display width is irrelevant but precision (for DECIMAL) matters
|
||||
$sizeIrrelevantTypes = [
|
||||
'int', 'tinyint', 'smallint', 'mediumint', 'bigint',
|
||||
'float', 'double', 'decimal', 'numeric' // Numeric types where display width is irrelevant
|
||||
'int', 'tinyint', 'smallint', 'mediumint', 'bigint',
|
||||
'float', 'double' // Numeric types where display width is irrelevant
|
||||
];
|
||||
|
||||
// If the type is not in the size irrelevant list, compare full definitions
|
||||
if (!in_array($currentMatches['datatype'], $sizeIrrelevantTypes))
|
||||
// Handle DECIMAL and NUMERIC types explicitly (precision and scale are relevant)
|
||||
if (in_array($currentMatches['datatype'], ['decimal', 'numeric']))
|
||||
{
|
||||
return $currentType !== $expectedType; // Use full definition for types where size matters
|
||||
// Extract precision and scale (if present)
|
||||
if ($currentMatches['params'] !== $expectedMatches['params'])
|
||||
{
|
||||
return true; // Precision or scale has changed
|
||||
}
|
||||
}
|
||||
|
||||
// For size irrelevant types, only compare base type, ignoring size and unsigned
|
||||
$currentBaseType = preg_replace('/\(\d+(,\d+)?\)|unsigned/', '', $currentType);
|
||||
$expectedBaseType = preg_replace('/\(\d+(,\d+)?\)|unsigned/', '', $expectedType);
|
||||
// Check if the type is in the list of size-irrelevant types
|
||||
if (in_array($currentMatches['datatype'], $sizeIrrelevantTypes))
|
||||
{
|
||||
// Remove irrelevant parts like display width and "unsigned" for size-irrelevant types, including extra spaces
|
||||
$currentBaseType = preg_replace('/\s*\(\s*\d+(\s*,\s*\d+)?\s*\)\s*|\s*unsigned\s*/', '', $currentType);
|
||||
$expectedBaseType = preg_replace('/\s*\(\s*\d+(\s*,\s*\d+)?\s*\)\s*|\s*unsigned\s*/', '', $expectedType);
|
||||
|
||||
// Perform a final comparison for numeric types ignoring size
|
||||
return $currentBaseType !== $expectedBaseType;
|
||||
// Compare base types after normalization
|
||||
return $currentBaseType !== $expectedBaseType;
|
||||
}
|
||||
|
||||
// For types where size is relevant (e.g., VARCHAR, CHAR, etc.), compare the full definitions
|
||||
// Normalize size parameters by removing extra spaces around commas, e.g., "decimal(5 , 2)" -> "decimal(5,2)"
|
||||
$normalizedCurrentType = preg_replace('/\s*,\s*/', ',', $currentType);
|
||||
$normalizedExpectedType = preg_replace('/\s*,\s*/', ',', $expectedType);
|
||||
|
||||
// Perform a full comparison for types where size matters
|
||||
return $normalizedCurrentType !== $normalizedExpectedType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,7 +662,7 @@ abstract class Schema implements SchemaInterface
|
||||
* @param mixed $currentDefault Current default value.
|
||||
* @param mixed $newDefault The new default value to be set.
|
||||
*
|
||||
* @return void
|
||||
* @return bool True if update was successful, false if no update was needed.
|
||||
* @since 3.2.1
|
||||
* @throws \Exception If there is an error updating column defaults.
|
||||
*/
|
||||
@@ -640,13 +678,38 @@ abstract class Schema implements SchemaInterface
|
||||
$updateTable = 'UPDATE ' . $this->db->quoteName($this->getTable($table));
|
||||
$dbField = $this->db->quoteName($column);
|
||||
|
||||
// Update SQL to set new default on existing rows where the default is currently the old default
|
||||
$sql = $updateTable . " SET $dbField = $sqlDefault WHERE $dbField IS NULL OR $dbField = ''";
|
||||
if (isset($this->columns[$column]))
|
||||
{
|
||||
$fieldType = strtoupper($this->columns[$column]->Type);
|
||||
|
||||
// Execute the update
|
||||
$this->db->setQuery($sql);
|
||||
return $this->db->execute();
|
||||
} catch (\Exception $e) {
|
||||
// If the field is numeric, avoid comparing with empty string
|
||||
if (strpos($fieldType, 'INT') !== false ||
|
||||
strpos($fieldType, 'FLOAT') !== false ||
|
||||
strpos($fieldType, 'DOUBLE') !== false ||
|
||||
strpos($fieldType, 'DECIMAL') !== false)
|
||||
{
|
||||
$whereCondition = "$dbField IS NULL OR $dbField = 0";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default condition for non-numeric fields
|
||||
$whereCondition = "$dbField IS NULL OR $dbField = ''";
|
||||
}
|
||||
|
||||
// Update SQL to set new default on existing rows where the default is currently the old default
|
||||
$sql = $updateTable . " SET $dbField = $sqlDefault WHERE $whereCondition";
|
||||
|
||||
// Execute the update
|
||||
$this->db->setQuery($sql);
|
||||
return $this->db->execute();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \Exception("Error: Column $column does not exist in table $table.");
|
||||
}
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
throw new \Exception("Error: failed to update ($column) column defaults in $table table. " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -759,6 +822,7 @@ abstract class Schema implements SchemaInterface
|
||||
*
|
||||
* @return string The SQL fragment to set the default value for a field.
|
||||
* @since 3.2.1
|
||||
* @throws \RuntimeException If the database unsupported
|
||||
*/
|
||||
protected function getDefaultValue(string $type, ?string $defaultValue, bool $pure = false): string
|
||||
{
|
||||
@@ -767,10 +831,31 @@ abstract class Schema implements SchemaInterface
|
||||
return '';
|
||||
}
|
||||
|
||||
// Set default for DATETIME fields in Joomla versions above 3
|
||||
if (strtoupper($type) === 'DATETIME' && $this->currentVersion != 3)
|
||||
// Logic to handle DATETIME default values based on database type
|
||||
if (strtoupper($type) === 'DATETIME')
|
||||
{
|
||||
return $pure ? "CURRENT_TIMESTAMP" : " DEFAULT CURRENT_TIMESTAMP";
|
||||
if ($this->dbType === 'mysql')
|
||||
{
|
||||
// MySQL-specific logic
|
||||
if (version_compare($this->dbVersion, '5.6', '>='))
|
||||
{
|
||||
return $pure ? "CURRENT_TIMESTAMP" : " DEFAULT CURRENT_TIMESTAMP";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $pure ? "'0000-00-00 00:00:00'" : " DEFAULT '0000-00-00 00:00:00'";
|
||||
}
|
||||
}
|
||||
elseif ($this->dbType === 'pgsql')
|
||||
{
|
||||
// PostgreSQL supports CURRENT_TIMESTAMP universally
|
||||
return $pure ? "CURRENT_TIMESTAMP" : " DEFAULT CURRENT_TIMESTAMP";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unsupported database type (at this point... we can grow this area)
|
||||
throw new \RuntimeException("Unsupported database type: {$dbType}");
|
||||
}
|
||||
}
|
||||
|
||||
// Apply and quote the default value
|
||||
|
@@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user