1*04fd306cSNickeau<?php 2*04fd306cSNickeau 3*04fd306cSNickeau 4*04fd306cSNickeaunamespace ComboStrap\Meta\Api; 5*04fd306cSNickeau 6*04fd306cSNickeau 7*04fd306cSNickeauuse ComboStrap\DataType; 8*04fd306cSNickeauuse ComboStrap\ExceptionBadArgument; 9*04fd306cSNickeauuse ComboStrap\ExceptionNotFound; 10*04fd306cSNickeauuse ComboStrap\ExceptionRuntime; 11*04fd306cSNickeauuse ComboStrap\ExceptionRuntimeInternal; 12*04fd306cSNickeauuse ComboStrap\LogUtility; 13*04fd306cSNickeauuse ComboStrap\Meta\Field\Aliases; 14*04fd306cSNickeauuse ComboStrap\Path; 15*04fd306cSNickeauuse ComboStrap\References; 16*04fd306cSNickeau 17*04fd306cSNickeau/** 18*04fd306cSNickeau * Class MetadataTabular 19*04fd306cSNickeau * @package ComboStrap 20*04fd306cSNickeau * A list of row represented as a list of column 21*04fd306cSNickeau * ie an entity with a map that has a key 22*04fd306cSNickeau * 23*04fd306cSNickeau * The value of a tabular is an array of row (where a row is an associative array) 24*04fd306cSNickeau */ 25*04fd306cSNickeauabstract class MetadataTabular extends Metadata 26*04fd306cSNickeau{ 27*04fd306cSNickeau 28*04fd306cSNickeau /** 29*04fd306cSNickeau * In the array, the identifier may be the persistent one 30*04fd306cSNickeau * or the identifier one 31*04fd306cSNickeau */ 32*04fd306cSNickeau const PERSISTENT_NAME = "persistent"; 33*04fd306cSNickeau const IDENTIFIER_NAME = "identifier"; 34*04fd306cSNickeau 35*04fd306cSNickeau 36*04fd306cSNickeau /** 37*04fd306cSNickeau * A array of rows where 38*04fd306cSNickeau * - the key is the `identifier value` 39*04fd306cSNickeau * - the value is a list of column metadata 40*04fd306cSNickeau * * the key is the {@link Metadata::getPersistentName()} 41*04fd306cSNickeau * * the value is the metadata 42*04fd306cSNickeau * 43*04fd306cSNickeau * @var Metadata[][] 44*04fd306cSNickeau */ 45*04fd306cSNickeau protected ?array $rows = null; 46*04fd306cSNickeau 47*04fd306cSNickeau 48*04fd306cSNickeau /** 49*04fd306cSNickeau * @return array - the rows in associate array format 50*04fd306cSNickeau * where the identifier value is the key 51*04fd306cSNickeau * @throws ExceptionNotFound 52*04fd306cSNickeau */ 53*04fd306cSNickeau public function getValue(): array 54*04fd306cSNickeau { 55*04fd306cSNickeau $this->buildCheck(); 56*04fd306cSNickeau if ($this->rows === null) { 57*04fd306cSNickeau throw new ExceptionNotFound("No tabular data found."); 58*04fd306cSNickeau } 59*04fd306cSNickeau return $this->rows; 60*04fd306cSNickeau } 61*04fd306cSNickeau 62*04fd306cSNickeau public function setValue($value): Metadata 63*04fd306cSNickeau { 64*04fd306cSNickeau if ($value === null) { 65*04fd306cSNickeau return $this; 66*04fd306cSNickeau } 67*04fd306cSNickeau if (!is_array($value)) { 68*04fd306cSNickeau throw new ExceptionRuntime("The data set is not an array (The tabular data is an array of rows)"); 69*04fd306cSNickeau } 70*04fd306cSNickeau $keys = array_keys($value); 71*04fd306cSNickeau foreach ($keys as $key) { 72*04fd306cSNickeau if (!is_numeric($key)) { 73*04fd306cSNickeau throw new ExceptionRuntime("The element of the array are not rows. The index ($key) should be numeric and is not"); 74*04fd306cSNickeau } 75*04fd306cSNickeau } 76*04fd306cSNickeau $this->rows = $value; 77*04fd306cSNickeau return $this; 78*04fd306cSNickeau 79*04fd306cSNickeau } 80*04fd306cSNickeau 81*04fd306cSNickeau 82*04fd306cSNickeau public function toStoreValue(): ?array 83*04fd306cSNickeau { 84*04fd306cSNickeau $this->buildCheck(); 85*04fd306cSNickeau if ($this->rows === null) { 86*04fd306cSNickeau return null; 87*04fd306cSNickeau } 88*04fd306cSNickeau return $this->rowsToStore($this->rows); 89*04fd306cSNickeau } 90*04fd306cSNickeau 91*04fd306cSNickeau /** 92*04fd306cSNickeau * /** 93*04fd306cSNickeau * A list of rows that contains a list of metadata 94*04fd306cSNickeau * with their value 95*04fd306cSNickeau * Each row has the key has value 96*04fd306cSNickeau * @return Metadata[][] - an array of rows 97*04fd306cSNickeau * @throws ExceptionNotFound 98*04fd306cSNickeau */ 99*04fd306cSNickeau public abstract function getDefaultValue(): array; 100*04fd306cSNickeau 101*04fd306cSNickeau public function toStoreDefaultValue(): ?array 102*04fd306cSNickeau { 103*04fd306cSNickeau try { 104*04fd306cSNickeau $defaultRows = $this->getDefaultValue(); 105*04fd306cSNickeau } catch (ExceptionNotFound $e) { 106*04fd306cSNickeau return null; 107*04fd306cSNickeau } 108*04fd306cSNickeau 109*04fd306cSNickeau return $this->rowsToStore($defaultRows); 110*04fd306cSNickeau } 111*04fd306cSNickeau 112*04fd306cSNickeau /** 113*04fd306cSNickeau * The value is: 114*04fd306cSNickeau * * a string for a unique identifier value 115*04fd306cSNickeau * * an array of columns or an array of rows 116*04fd306cSNickeau */ 117*04fd306cSNickeau public function setFromStoreValueWithoutException($value): Metadata 118*04fd306cSNickeau { 119*04fd306cSNickeau 120*04fd306cSNickeau if ($value === null) { 121*04fd306cSNickeau return $this; 122*04fd306cSNickeau } 123*04fd306cSNickeau 124*04fd306cSNickeau /** 125*04fd306cSNickeau * Value of the metadata id 126*04fd306cSNickeau */ 127*04fd306cSNickeau $identifierMetadataObject = $this->getUidObject(); 128*04fd306cSNickeau $identifierPersistentName = $identifierMetadataObject::getPersistentName(); 129*04fd306cSNickeau 130*04fd306cSNickeau /** 131*04fd306cSNickeau * Single value 132*04fd306cSNickeau */ 133*04fd306cSNickeau if (is_string($value)) { 134*04fd306cSNickeau /** 135*04fd306cSNickeau * @var Metadata $identifierMetadata 136*04fd306cSNickeau */ 137*04fd306cSNickeau $identifierMetadata = (new $identifierMetadataObject()); 138*04fd306cSNickeau $identifierMetadata->setValue($value); 139*04fd306cSNickeau $rowId = $this->getRowId($identifierMetadata); 140*04fd306cSNickeau $this->rows[$rowId] = [$identifierPersistentName => $identifierMetadata]; 141*04fd306cSNickeau return $this; 142*04fd306cSNickeau } 143*04fd306cSNickeau 144*04fd306cSNickeau /** 145*04fd306cSNickeau * Array 146*04fd306cSNickeau */ 147*04fd306cSNickeau if (!is_array($value)) { 148*04fd306cSNickeau LogUtility::msg("The tabular value is nor a string nor an array"); 149*04fd306cSNickeau return $this; 150*04fd306cSNickeau } 151*04fd306cSNickeau 152*04fd306cSNickeau 153*04fd306cSNickeau /** 154*04fd306cSNickeau * List of columns ({@link MetadataFormDataStore form html way} 155*04fd306cSNickeau * 156*04fd306cSNickeau * For example: for {@link Aliases}, a form will have two fields 157*04fd306cSNickeau * alias-path: 158*04fd306cSNickeau * 0: path1 159*04fd306cSNickeau * 1: path2 160*04fd306cSNickeau * alias-type: 161*04fd306cSNickeau * 0: redirect 162*04fd306cSNickeau * 1: redirect 163*04fd306cSNickeau * 164*04fd306cSNickeau * or just a list ({@link References} 165*04fd306cSNickeau * references: 166*04fd306cSNickeau * 0: reference-1 167*04fd306cSNickeau * 1: $colValue-2 168*04fd306cSNickeau */ 169*04fd306cSNickeau $keys = array_keys($value); 170*04fd306cSNickeau $firstElement = array_shift($keys); 171*04fd306cSNickeau if (!is_numeric($firstElement)) { 172*04fd306cSNickeau /** 173*04fd306cSNickeau * Check which kind of key is used 174*04fd306cSNickeau * Resistance to the property key ! 175*04fd306cSNickeau */ 176*04fd306cSNickeau $identifierName = $identifierMetadataObject::getPersistentName(); 177*04fd306cSNickeau $identifierNameType = self::PERSISTENT_NAME; 178*04fd306cSNickeau if (!isset($value[$identifierName])) { 179*04fd306cSNickeau $identifierNameType = self::IDENTIFIER_NAME; 180*04fd306cSNickeau $identifierName = $identifierMetadataObject::getName(); 181*04fd306cSNickeau } 182*04fd306cSNickeau $identifierValues = $value[$identifierName]; 183*04fd306cSNickeau if ($identifierValues === null || $identifierValues === "") { 184*04fd306cSNickeau // No data 185*04fd306cSNickeau return $this; 186*04fd306cSNickeau } 187*04fd306cSNickeau $index = 0; 188*04fd306cSNickeau if (!is_array($identifierValues)) { 189*04fd306cSNickeau // only one value 190*04fd306cSNickeau $identifierValues = [$identifierValues]; 191*04fd306cSNickeau } 192*04fd306cSNickeau foreach ($identifierValues as $identifierValue) { 193*04fd306cSNickeau $row = []; 194*04fd306cSNickeau if ($identifierValue === "") { 195*04fd306cSNickeau // an empty row in the table 196*04fd306cSNickeau continue; 197*04fd306cSNickeau } 198*04fd306cSNickeau $row[$identifierPersistentName] = MetadataSystem::toMetadataObject($identifierMetadataObject, $this) 199*04fd306cSNickeau ->setFromStoreValue($identifierValue); 200*04fd306cSNickeau foreach ($this->getChildrenClass() as $childClass) { 201*04fd306cSNickeau if ($childClass === get_class($identifierMetadataObject)) { 202*04fd306cSNickeau continue; 203*04fd306cSNickeau } 204*04fd306cSNickeau $metadataChildObject = MetadataSystem::toMetadataObject($childClass, $this); 205*04fd306cSNickeau $name = $metadataChildObject::getPersistentName(); 206*04fd306cSNickeau if ($identifierNameType === self::IDENTIFIER_NAME) { 207*04fd306cSNickeau $name = $metadataChildObject::getName(); 208*04fd306cSNickeau } 209*04fd306cSNickeau $childValue = $value[$name]; 210*04fd306cSNickeau if (is_array($childValue)) { 211*04fd306cSNickeau $childValue = $childValue[$index]; 212*04fd306cSNickeau $index++; 213*04fd306cSNickeau } 214*04fd306cSNickeau $metadataChildObject->setFromStoreValue($childValue); 215*04fd306cSNickeau $row[$metadataChildObject::getPersistentName()] = $metadataChildObject; 216*04fd306cSNickeau } 217*04fd306cSNickeau $this->rows[] = $row; 218*04fd306cSNickeau } 219*04fd306cSNickeau 220*04fd306cSNickeau return $this; 221*04fd306cSNickeau } 222*04fd306cSNickeau 223*04fd306cSNickeau /** 224*04fd306cSNickeau * List of row (frontmatter, dokuwiki, ...) 225*04fd306cSNickeau */ 226*04fd306cSNickeau $childClassesByPersistentName = []; 227*04fd306cSNickeau foreach ($this->getChildrenObject() as $childObject) { 228*04fd306cSNickeau $childClassesByPersistentName[$childObject::getPersistentName()] = $childObject; 229*04fd306cSNickeau } 230*04fd306cSNickeau foreach ($value as $item) { 231*04fd306cSNickeau 232*04fd306cSNickeau /** 233*04fd306cSNickeau * By default, the single value is the identifier 234*04fd306cSNickeau * 235*04fd306cSNickeau * (Note that from {@link MetadataFormDataStore}, tabular data 236*04fd306cSNickeau * should be build before via the {@link self::buildFromReadStore()} 237*04fd306cSNickeau * as tabular data is represented as a series of column) 238*04fd306cSNickeau * 239*04fd306cSNickeau */ 240*04fd306cSNickeau if (is_string($item)) { 241*04fd306cSNickeau try { 242*04fd306cSNickeau $identifierMetadata = MetadataSystem::toMetadataObject($identifierMetadataObject, $this)->setFromStoreValue($item); 243*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 244*04fd306cSNickeau throw ExceptionRuntimeInternal::withMessageAndError("The $identifierMetadataObject should be known", $e); 245*04fd306cSNickeau } 246*04fd306cSNickeau $identifierValue = $this->getRowId($identifierMetadata); 247*04fd306cSNickeau $this->rows[$identifierValue] = [$identifierPersistentName => $identifierMetadata]; 248*04fd306cSNickeau continue; 249*04fd306cSNickeau } 250*04fd306cSNickeau if (!is_array($item)) { 251*04fd306cSNickeau LogUtility::msg("The tabular value is not a string nor an array"); 252*04fd306cSNickeau } 253*04fd306cSNickeau $row = []; 254*04fd306cSNickeau $idValue = null; 255*04fd306cSNickeau foreach ($item as $colName => $colValue) { 256*04fd306cSNickeau $childClass = $childClassesByPersistentName[$colName]; 257*04fd306cSNickeau if ($childClass === null) { 258*04fd306cSNickeau LogUtility::msg("The column ($colName) does not have a metadata definition"); 259*04fd306cSNickeau continue; 260*04fd306cSNickeau } 261*04fd306cSNickeau $childObject = MetadataSystem::toMetadataObject($childClass, $this); 262*04fd306cSNickeau $childObject->setFromStoreValueWithoutException($colValue); 263*04fd306cSNickeau $row[$childObject::getPersistentName()] = $childObject; 264*04fd306cSNickeau if ($childObject::getPersistentName() === $identifierPersistentName) { 265*04fd306cSNickeau $idValue = $this->getRowId($childObject); 266*04fd306cSNickeau } 267*04fd306cSNickeau } 268*04fd306cSNickeau if ($idValue === null) { 269*04fd306cSNickeau LogUtility::msg("The value for the identifier ($identifierPersistentName) was not found"); 270*04fd306cSNickeau continue; 271*04fd306cSNickeau } 272*04fd306cSNickeau $this->rows[$idValue] = $row; 273*04fd306cSNickeau 274*04fd306cSNickeau } 275*04fd306cSNickeau return $this; 276*04fd306cSNickeau } 277*04fd306cSNickeau 278*04fd306cSNickeau 279*04fd306cSNickeau public function valueIsNotNull(): bool 280*04fd306cSNickeau { 281*04fd306cSNickeau return $this->rows !== null; 282*04fd306cSNickeau } 283*04fd306cSNickeau 284*04fd306cSNickeau public 285*04fd306cSNickeau function remove(String $identifierValue): MetadataTabular 286*04fd306cSNickeau { 287*04fd306cSNickeau $this->buildCheck(); 288*04fd306cSNickeau if ($this->rows === null) { 289*04fd306cSNickeau return $this; 290*04fd306cSNickeau } 291*04fd306cSNickeau unset($this->rows[$identifierValue]); 292*04fd306cSNickeau return $this; 293*04fd306cSNickeau } 294*04fd306cSNickeau 295*04fd306cSNickeau 296*04fd306cSNickeau public 297*04fd306cSNickeau function has($identifierValue): bool 298*04fd306cSNickeau { 299*04fd306cSNickeau $this->buildCheck(); 300*04fd306cSNickeau if ($this->rows === null) { 301*04fd306cSNickeau return false; 302*04fd306cSNickeau } 303*04fd306cSNickeau return isset($this->rows[$identifierValue]); 304*04fd306cSNickeau } 305*04fd306cSNickeau 306*04fd306cSNickeau public 307*04fd306cSNickeau function getSize(): int 308*04fd306cSNickeau { 309*04fd306cSNickeau $this->buildCheck(); 310*04fd306cSNickeau if ($this->rows === null) { 311*04fd306cSNickeau return 0; 312*04fd306cSNickeau } 313*04fd306cSNickeau return sizeof($this->rows); 314*04fd306cSNickeau } 315*04fd306cSNickeau 316*04fd306cSNickeau public function getRow($id): ?array 317*04fd306cSNickeau { 318*04fd306cSNickeau $this->buildCheck(); 319*04fd306cSNickeau $normalizedValue = $this->getUidObject() 320*04fd306cSNickeau ->setFromStoreValueWithoutException($id) 321*04fd306cSNickeau ->getValue(); 322*04fd306cSNickeau if(is_object($normalizedValue)){ 323*04fd306cSNickeau $normalizedValue = $normalizedValue->__toString(); 324*04fd306cSNickeau } 325*04fd306cSNickeau return $this->rows[$normalizedValue]; 326*04fd306cSNickeau } 327*04fd306cSNickeau 328*04fd306cSNickeau static public function getDataType(): string 329*04fd306cSNickeau { 330*04fd306cSNickeau return DataType::TABULAR_TYPE_VALUE; 331*04fd306cSNickeau } 332*04fd306cSNickeau 333*04fd306cSNickeau /** 334*04fd306cSNickeau * @throws ExceptionNotFound - if the identifier or it's value was not found 335*04fd306cSNickeau * @var Metadata[] $metas 336*04fd306cSNickeau */ 337*04fd306cSNickeau public function addRow(array $metas): MetadataTabular 338*04fd306cSNickeau { 339*04fd306cSNickeau $row = []; 340*04fd306cSNickeau $identifier = null; 341*04fd306cSNickeau foreach ($metas as $meta) { 342*04fd306cSNickeau $row[$meta::getPersistentName()] = $meta; 343*04fd306cSNickeau if (get_class($meta) === $this->getUidClass()) { 344*04fd306cSNickeau $identifier = $meta->getValue(); 345*04fd306cSNickeau } 346*04fd306cSNickeau } 347*04fd306cSNickeau if ($identifier === null) { 348*04fd306cSNickeau throw new ExceptionNotFound("The identifier value was not found in the row"); 349*04fd306cSNickeau } 350*04fd306cSNickeau $this->rows[$identifier] = $row; 351*04fd306cSNickeau return $this; 352*04fd306cSNickeau } 353*04fd306cSNickeau 354*04fd306cSNickeau private function rowsToStore(array $defaultRows): array 355*04fd306cSNickeau { 356*04fd306cSNickeau $rowsArray = []; 357*04fd306cSNickeau ksort($defaultRows); 358*04fd306cSNickeau foreach ($defaultRows as $row) { 359*04fd306cSNickeau $rowArray = []; 360*04fd306cSNickeau foreach ($row as $metadata) { 361*04fd306cSNickeau $toStoreValue = $metadata->toStoreValue(); 362*04fd306cSNickeau $toDefaultStoreValue = $metadata->toStoreDefaultValue(); 363*04fd306cSNickeau if ( 364*04fd306cSNickeau $toStoreValue !== null 365*04fd306cSNickeau && $toStoreValue !== $toDefaultStoreValue 366*04fd306cSNickeau ) { 367*04fd306cSNickeau $rowArray[$metadata::getPersistentName()] = $toStoreValue; 368*04fd306cSNickeau } 369*04fd306cSNickeau } 370*04fd306cSNickeau $rowsArray[] = $rowArray; 371*04fd306cSNickeau } 372*04fd306cSNickeau return $rowsArray; 373*04fd306cSNickeau } 374*04fd306cSNickeau 375*04fd306cSNickeau /** 376*04fd306cSNickeau * @param Metadata $identifierMetadata 377*04fd306cSNickeau * @return mixed 378*04fd306cSNickeau * Due to dokuwiki, the same id may be a markup or media path 379*04fd306cSNickeau * We build the object from the same id but the url is not the same 380*04fd306cSNickeau * This function takes the metadata, get the value and 381*04fd306cSNickeau * return the identifier suitable for an array 382*04fd306cSNickeau */ 383*04fd306cSNickeau private function getRowId(Metadata $identifierMetadata) 384*04fd306cSNickeau { 385*04fd306cSNickeau try { 386*04fd306cSNickeau $identifierValue = $identifierMetadata->getValue(); // normalize if any 387*04fd306cSNickeau } catch (ExceptionNotFound $e) { 388*04fd306cSNickeau throw ExceptionRuntimeInternal::withMessageAndError("The meta identifier ($identifierMetadata) should have a value", $e); 389*04fd306cSNickeau } 390*04fd306cSNickeau if (DataType::isObject($identifierValue)) { 391*04fd306cSNickeau /** 392*04fd306cSNickeau * An object cannot be the key of an array 393*04fd306cSNickeau */ 394*04fd306cSNickeau $identifierValue = $identifierValue->__toString(); 395*04fd306cSNickeau } 396*04fd306cSNickeau return $identifierValue; 397*04fd306cSNickeau } 398*04fd306cSNickeau 399*04fd306cSNickeau} 400