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.
567 lines
15 KiB
PHP
567 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* @package Joomla.Component.Builder
|
|
*
|
|
* @created 3rd September, 2020
|
|
* @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\Data;
|
|
|
|
|
|
use VDM\Joomla\Interfaces\Data\SubformInterface as Subform;
|
|
use VDM\Joomla\Interfaces\Data\MultiSubformInterface;
|
|
|
|
|
|
/**
|
|
* CRUD the data of multi subform to another views (tables)
|
|
*
|
|
* @since 3.2.2
|
|
*/
|
|
final class MultiSubform implements MultiSubformInterface
|
|
{
|
|
/**
|
|
* The Subform Class.
|
|
*
|
|
* @var Subform
|
|
* @since 3.2.2
|
|
*/
|
|
protected Subform $subform;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param Subform $subform The Subform Class.
|
|
*
|
|
* @since 3.2.2
|
|
*/
|
|
public function __construct(Subform $subform)
|
|
{
|
|
$this->subform = $subform;
|
|
}
|
|
|
|
/**
|
|
* Get a subform items
|
|
*
|
|
* @param array $getMap The map to get the subfrom data
|
|
*
|
|
* Example:
|
|
* $getMap = [
|
|
* '_core' => [
|
|
* 'table' =>'data',
|
|
* 'linkValue' => $item->guid ?? '',
|
|
* 'linkKey' => 'look',
|
|
* 'field' => 'data',
|
|
* 'get' => ['guid','email','image','mobile_phone','website','dateofbirth']
|
|
* ],
|
|
* 'countries' => [
|
|
* 'table' =>'data_country',
|
|
* 'linkValue' => 'data:guid', // coretable:fieldname
|
|
* 'linkKey' => 'data',
|
|
* 'get' => ['guid','country','currency']
|
|
* ]
|
|
* ];
|
|
*
|
|
* @return array|null The subform
|
|
* @since 3.2.2
|
|
*/
|
|
public function get(array $getMap): ?array
|
|
{
|
|
// Validate the core map presence and structure
|
|
if (!isset($getMap['_core']) || !is_array($getMap['_core']) || !$this->validGetMap($getMap['_core']))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Initialize the core data
|
|
$coreData = $this->getSubformData($getMap['_core']);
|
|
|
|
// Return null if fetching core data fails
|
|
if (null === $coreData)
|
|
{
|
|
return null;
|
|
}
|
|
$table = $getMap['_core']['table'];
|
|
unset($getMap['_core']);
|
|
|
|
// Recursively get data for all nested subforms
|
|
return $this->getNestedSubforms($getMap, $coreData, $table);
|
|
}
|
|
|
|
/**
|
|
* Set a subform items
|
|
*
|
|
* @param mixed $items The list of items from the subform to set
|
|
* @param array $setMap The map to set the subfrom data
|
|
*
|
|
* Example:
|
|
* $items,
|
|
* $setMap = [
|
|
* '_core' => [
|
|
* 'table' =>'data',
|
|
* 'indexKey' => 'guid',
|
|
* 'linkKey' => 'look',
|
|
* 'linkValue' => $data['guid'] ?? ''
|
|
* ],
|
|
* 'countries' => [
|
|
* 'table' =>'data_country',
|
|
* 'indexKey' => 'guid',
|
|
* 'linkKey' => 'data',
|
|
* 'linkValue' => 'data:guid' // coretable:fieldname
|
|
* ]
|
|
* ];
|
|
*
|
|
* @return bool
|
|
* @since 3.2.2
|
|
*/
|
|
public function set(mixed $items, array $setMap): bool
|
|
{
|
|
// Validate the core map presence and structure
|
|
if (!isset($setMap['_core']) || !is_array($setMap['_core']) || !$this->validSetMap($setMap['_core']))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// catch an empty set
|
|
if (!is_array($items))
|
|
{
|
|
$items = []; // will delete all existing linked items :( not ideal, but real
|
|
}
|
|
else
|
|
{
|
|
// make sure the sub-subform:linkValue[data:guid]
|
|
// is set with the needed key if possible
|
|
// this ensures that new sub-subform data is correctly linked
|
|
$this->prepLinkValue($items, $setMap);
|
|
}
|
|
|
|
// Save the core data
|
|
if (!$this->setSubformData($items, $setMap['_core']))
|
|
{
|
|
return false;
|
|
}
|
|
$table = $setMap['_core']['table'];
|
|
unset($setMap['_core']);
|
|
|
|
// Recursively set data for all nested subforms
|
|
return $this->setNestedSubforms($setMap, $items, $table);
|
|
}
|
|
|
|
/**
|
|
* Fetch data based on provided map configuration.
|
|
*
|
|
* @param array $map Map configuration
|
|
* @param array|null $coreData The core data to be appended with subform data
|
|
*
|
|
* @return array|null Fetched data or null on failure
|
|
* @since 3.2.2
|
|
*/
|
|
private function getSubformData(array $map, ?array $coreData = null): ?array
|
|
{
|
|
$map['linkValue'] = $this->setLinkValue($map['linkValue'], $coreData);
|
|
|
|
if (empty($map['linkValue']) || strpos($map['linkValue'], ':') !== false)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return $this->subform->table($map['table'])->get(
|
|
$map['linkValue'],
|
|
$map['linkKey'],
|
|
$map['field'],
|
|
$map['get']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Set data based on provided map configuration.
|
|
*
|
|
* @param array $items The list of items from the subform to set
|
|
* @param array $map The map to set the subfrom data
|
|
* @param array|null $coreData The core data to be appended with subform data
|
|
*
|
|
* @return bool
|
|
* @since 3.2.2
|
|
*/
|
|
private function setSubformData(array $items, array $map, ?array $coreData = null): bool
|
|
{
|
|
$map['linkValue'] = $this->setLinkValue($map['linkValue'], $coreData);
|
|
|
|
if (empty($map['linkValue']) || strpos($map['linkValue'], ':') !== false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return $this->subform->table($map['table'])->set(
|
|
$items,
|
|
$map['indexKey'],
|
|
$map['linkKey'],
|
|
$map['linkValue']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Set the linked value if needed, and posible.
|
|
*
|
|
* @param string $linkValue The current linkValue
|
|
* @param array|null $data The already found data as table => dataSet[field] => value
|
|
*
|
|
* @return string|null The actual linkValue
|
|
* @since 3.2.2
|
|
*/
|
|
private function setLinkValue(string $linkValue, ?array $data = null): ?string
|
|
{
|
|
if ($data !== null && strpos($linkValue, ':') !== false)
|
|
{
|
|
[$table, $field] = explode(':', $linkValue);
|
|
$linkValue = $data[$table][$field] ?? null;
|
|
}
|
|
|
|
return $linkValue;
|
|
}
|
|
|
|
/**
|
|
* Recursively process additional subform data.
|
|
*
|
|
* @param array $getMap The nested map of data to process
|
|
* @param array $subformData The core subform data
|
|
* @param string $table The core table
|
|
*
|
|
* @return array The core data with nested subforms included
|
|
* @since 3.2.2
|
|
*/
|
|
private function getNestedSubforms(array $getMap, array $subformData, string $table): array
|
|
{
|
|
foreach ($subformData as &$subform)
|
|
{
|
|
$subform = $this->processGetSubform($getMap, $subform, $table);
|
|
}
|
|
|
|
return $subformData;
|
|
}
|
|
|
|
/**
|
|
* Recursively process additional subform data.
|
|
*
|
|
* @param array $setMap The nested map of data to process
|
|
* @param array $subformData The core subform data
|
|
* @param string $table The core table
|
|
*
|
|
* @return bool
|
|
* @since 3.2.2
|
|
*/
|
|
private function setNestedSubforms(array $setMap, array $subformData, string $table): bool
|
|
{
|
|
$status = true;
|
|
foreach ($subformData as $subform)
|
|
{
|
|
if (!$this->processSetSubform($setMap, $subform, $table))
|
|
{
|
|
$status = false;
|
|
}
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* Process each subform entry based on the map.
|
|
*
|
|
* @param array $getMap Mapping data for processing subforms
|
|
* @param array $subform A single subform entry
|
|
* @param string $table The table name used for linking values
|
|
*
|
|
* @return array Updated subform
|
|
* @since 3.2.2
|
|
*/
|
|
private function processGetSubform(array $getMap, array $subform, string $table): array
|
|
{
|
|
foreach ($getMap as $key => $map)
|
|
{
|
|
if (!is_array($map) || isset($subform[$key]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$this->processGetMap($subform, $map, $key, $table);
|
|
}
|
|
|
|
return $subform;
|
|
}
|
|
|
|
/**
|
|
* Process each subform entry based on the map.
|
|
*
|
|
* @param array $setMap Mapping data for processing subforms
|
|
* @param array $subform A single subform entry
|
|
* @param string $table The table name used for linking values
|
|
*
|
|
* @return bool
|
|
* @since 3.2.2
|
|
*/
|
|
private function processSetSubform(array $setMap, array $subform, string $table): bool
|
|
{
|
|
$status = true;
|
|
foreach ($setMap as $key => $map)
|
|
{
|
|
if (!is_array($map) || !isset($subform[$key]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!$this->processSetMap($subform, $map, $key, $table))
|
|
{
|
|
$status = false;
|
|
}
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* Process a given map by either fetching nested subforms or handling them directly.
|
|
*
|
|
* @param array &$subform Reference to subform data
|
|
* @param array $map Map configuration for subform processing
|
|
* @param string $key Key associated with the map
|
|
* @param string $table Core table name for linking values
|
|
*
|
|
* @return void
|
|
* @since 3.2.2
|
|
*/
|
|
private function processGetMap(array &$subform, array $map, string $key, string $table): void
|
|
{
|
|
if (isset($map['_core']))
|
|
{
|
|
$this->handleCoreGetMap($subform, $map, $key, $table);
|
|
}
|
|
else
|
|
{
|
|
$this->handleRegularGetMap($subform, $map, $key, $table);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a given map by either setting nested subforms or handling them directly.
|
|
*
|
|
* @param array $subform Subform data
|
|
* @param array $map Map configuration for subform processing
|
|
* @param string $key Key associated with the map
|
|
* @param string $table Core table name for linking values
|
|
*
|
|
* @return bool
|
|
* @since 3.2.2
|
|
*/
|
|
private function processSetMap(array $subform, array $map, string $key, string $table): bool
|
|
{
|
|
if (isset($map['_core']))
|
|
{
|
|
return $this->handleCoreSetMap($subform, $map, $key, $table);
|
|
}
|
|
|
|
return $this->handleRegularSetMap($subform, $map, $key, $table);
|
|
}
|
|
|
|
/**
|
|
* Handle the processing of '_core' maps in a subform.
|
|
*
|
|
* @param array &$subform Reference to subform data
|
|
* @param array $map Map configuration for core subform processing
|
|
* @param string $key Key associated with the map
|
|
* @param string $table Core table name for linking values
|
|
*
|
|
* @return void
|
|
* @since 3.2.2
|
|
*/
|
|
private function handleCoreGetMap(array &$subform, array $map, string $key, string $table): void
|
|
{
|
|
if (is_array($map['_core']) && $this->validGetMap($map['_core']))
|
|
{
|
|
$map['_core']['linkValue'] = $this->setLinkValue($map['_core']['linkValue'], [$table => $subform]);
|
|
|
|
$subCoreData = $this->get($map);
|
|
if ($subCoreData !== null)
|
|
{
|
|
$subform[$key] = $subCoreData;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle the processing of '_core' maps in a subform.
|
|
*
|
|
* @param array $subform Subform data
|
|
* @param array $map Map configuration for core subform processing
|
|
* @param string $key Key associated with the map
|
|
* @param string $table Core table name for linking values
|
|
*
|
|
* @return bool
|
|
* @since 3.2.2
|
|
*/
|
|
private function handleCoreSetMap(array $subform, array $map, string $key, string $table): bool
|
|
{
|
|
if (is_array($map['_core']) && $this->validGetMap($map['_core']))
|
|
{
|
|
$map['_core']['linkValue'] = $this->setLinkValue($map['_core']['linkValue'], [$table => $subform]);
|
|
|
|
return $this->set($subform[$key], $map);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Handle the processing of regular maps in a subform.
|
|
*
|
|
* @param array &$subform Reference to subform data
|
|
* @param array $map Map configuration for regular subform processing
|
|
* @param string $key Key associated with the map
|
|
* @param string $table Core table name for linking values
|
|
*
|
|
* @return void
|
|
* @since 3.2.2
|
|
*/
|
|
private function handleRegularGetMap(array &$subform, array $map, string $key, string $table): void
|
|
{
|
|
$map['field'] = $key;
|
|
if ($this->validGetMap($map))
|
|
{
|
|
$subformData = $this->getSubformData($map, [$table => $subform]);
|
|
if ($subformData !== null)
|
|
{
|
|
$subform[$key] = $subformData;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle the processing of regular maps in a subform.
|
|
*
|
|
* @param array $subform Subform data
|
|
* @param array $map Map configuration for regular subform processing
|
|
* @param string $key Key associated with the map
|
|
* @param string $table Core table name for linking values
|
|
*
|
|
* @return bool
|
|
* @since 3.2.2
|
|
*/
|
|
private function handleRegularSetMap(array $subform, array $map, string $key, string $table): bool
|
|
{
|
|
if ($this->validSetMap($map))
|
|
{
|
|
// will delete all existing linked items [IF EMPTY] :( not ideal, but real
|
|
$data = (empty($subform[$key]) || !is_array($subform[$key])) ? [] : $subform[$key];
|
|
|
|
return $this->setSubformData($data, $map, [$table => $subform]);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Validate the get map configuration for fetching subform data.
|
|
* Ensures all required keys are present and have valid values.
|
|
*
|
|
* @param array $map The map configuration to validate.
|
|
*
|
|
* @return bool Returns true if the map is valid, false otherwise.
|
|
* @since 3.2.2
|
|
*/
|
|
private function validGetMap(array $map): bool
|
|
{
|
|
// List of required keys with their expected types or validation functions
|
|
$requiredKeys = [
|
|
'table' => 'is_string',
|
|
'linkValue' => 'is_string',
|
|
'linkKey' => 'is_string',
|
|
'field' => 'is_string',
|
|
'get' => 'is_array'
|
|
];
|
|
|
|
// Iterate through each required key and validate
|
|
foreach ($requiredKeys as $key => $validator)
|
|
{
|
|
if (empty($map[$key]) || !$validator($map[$key]))
|
|
{
|
|
return false; // Key missing or validation failed
|
|
}
|
|
}
|
|
|
|
return true; // All checks passed
|
|
}
|
|
|
|
/**
|
|
* Validate the set map configuration for fetching subform data.
|
|
* Ensures all required keys are present and have valid values.
|
|
*
|
|
* @param array $map The map configuration to validate.
|
|
*
|
|
* @return bool Returns true if the map is valid, false otherwise.
|
|
* @since 3.2.2
|
|
*/
|
|
private function validSetMap(array $map): bool
|
|
{
|
|
// List of required keys with their expected types or validation functions
|
|
$requiredKeys = [
|
|
'table' => 'is_string',
|
|
'indexKey' => 'is_string',
|
|
'linkKey' => 'is_string',
|
|
'linkValue' => 'is_string'
|
|
];
|
|
|
|
// Iterate through each required key and validate
|
|
foreach ($requiredKeys as $key => $validator)
|
|
{
|
|
if (empty($map[$key]) || !$validator($map[$key]))
|
|
{
|
|
return false; // Key missing or validation failed
|
|
}
|
|
}
|
|
|
|
return true; // All checks passed
|
|
}
|
|
|
|
/**
|
|
* Prepare the linkValue needed by the sub-subform
|
|
*
|
|
* @param array $subform The subform data
|
|
* @param array $setMap Mapping data for processing subforms
|
|
*
|
|
* @return void
|
|
* @since 5.0.3
|
|
*/
|
|
private function prepLinkValue(array &$subform, array $setMap): void
|
|
{
|
|
$code_table = null;
|
|
foreach ($setMap as $key => $map)
|
|
{
|
|
if ($key === '_core')
|
|
{
|
|
$code_table = $map['table'] ?? null;
|
|
continue;
|
|
}
|
|
|
|
if (strpos($map['linkValue'], ':') !== false)
|
|
{
|
|
[$table, $field] = explode(':', $map['linkValue']);
|
|
if ($code_table !== null &&
|
|
'guid' === $field &&
|
|
$table === $code_table)
|
|
{
|
|
foreach ($subform as &$row)
|
|
{
|
|
if (empty($row['guid']))
|
|
{
|
|
$row['guid'] = $this->subform->table($table)->getGuid($field);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|