1<?php 2 3 4namespace ComboStrap\Meta\Form; 5 6 7use ComboStrap\Canonical; 8use ComboStrap\DataType; 9use ComboStrap\ExceptionBadArgument; 10use ComboStrap\ExceptionNotFound; 11use ComboStrap\ExceptionRuntimeInternal; 12use ComboStrap\Html; 13use ComboStrap\LogUtility; 14use ComboStrap\Meta\Api\Metadata; 15use ComboStrap\Meta\Api\MetadataSystem; 16use ComboStrap\Meta\Api\MetadataMultiple; 17use ComboStrap\Meta\Api\MetadataTabular; 18use ComboStrap\PageDescription; 19use ComboStrap\PluginUtility; 20use ComboStrap\ResourceName; 21use ComboStrap\Web\Url; 22 23/** 24 * Class FormField 25 * @package ComboStrap 26 * 27 * A class that represents a tree of form field. 28 * 29 * Each field can be a scalar, a list or 30 * tabular by adding child fields. 31 * 32 * 33 */ 34class FormMetaField 35{ 36 37 38 /** 39 * The JSON attribute 40 */ 41 public const TAB_ATTRIBUTE = "tab"; 42 public const LABEL_ATTRIBUTE = "label"; 43 public const URL_ATTRIBUTE = "url"; 44 public const MUTABLE_ATTRIBUTE = "mutable"; 45 /** 46 * A value may be a scalar or an array 47 */ 48 public const VALUE_ATTRIBUTE = "value"; 49 public const DEFAULT_VALUE_ATTRIBUTE = "default"; 50 51 public const DOMAIN_VALUES_ATTRIBUTE = "domain-values"; 52 public const WIDTH_ATTRIBUTE = "width"; 53 public const CHILDREN_ATTRIBUTE = "children"; 54 const DESCRIPTION_ATTRIBUTE = PageDescription::PROPERTY_NAME; 55 const NAME_ATTRIBUTE = ResourceName::PROPERTY_NAME; 56 const MULTIPLE_ATTRIBUTE = "multiple"; 57 const TYPE_ATTRIBUTE = DataType::PROPERTY_NAME; 58 59 60 private $name; 61 /** 62 * @var bool 63 */ 64 private $mutable; 65 /** 66 * @var string 67 */ 68 private $tab; 69 /** 70 * @var string 71 */ 72 private $label; 73 private $description; 74 /** 75 * If canonical is set, an url is also send 76 */ 77 private string $canonical; 78 private array $values = []; 79 private array $defaults = []; 80 /** 81 * @var string 82 */ 83 private $type; 84 /** 85 * @var array 86 */ 87 private $domainValues; 88 /** 89 * @var FormMetaField[] 90 */ 91 private $children; 92 /** 93 * @var int 94 */ 95 private $width; 96 /** 97 * Multiple value can be chosen 98 * @var bool 99 */ 100 private $multiple = false; 101 102 103 /** 104 * FormField constructor. 105 * The name is mandatory 106 * and the type to be able to control the values 107 */ 108 public function __construct($name, $type) 109 { 110 $this->name = $name; 111 $this->label = ucfirst($name); 112 $this->description = $name; 113 if (!in_array($type, DataType::TYPES)) { 114 throw new ExceptionRuntimeInternal("The type ($type) is not a known field type"); 115 } 116 $this->type = $type; 117 $this->mutable = true; 118 } 119 120 public static function create(string $name, string $type): FormMetaField 121 { 122 return new FormMetaField($name, $type); 123 } 124 125 /** 126 * Almost because a form does not allow hierarchical data 127 * We send an error in this case 128 * @param Metadata $metadata 129 * @return FormMetaField 130 */ 131 public static function createFromMetadata(Metadata $metadata): FormMetaField 132 { 133 $field = FormMetaField::create($metadata->getName(), $metadata->getDataType()); 134 135 self::setCommonDataToFieldFromMetadata($field, $metadata); 136 137 $childrenMetadata = $metadata->getChildrenClass(); 138 139 try { 140 $metadata->getParent(); 141 } catch (ExceptionNotFound $e) { 142 /** 143 * Only the top field have a tab value 144 */ 145 $field->setTab($metadata->getTab()); 146 } 147 148 149 /** 150 * No children 151 */ 152 if (count($childrenMetadata) === 0) { 153 154 static::setLeafDataToFieldFromMetadata($field, $metadata); 155 156 /** 157 * When tabular, the value comes from the parent 158 */ 159 if ($metadata->isScalar()) { 160 $value = $metadata->toStoreValue(); 161 $defaultValue = $metadata->toStoreDefaultValue(); 162 $field->addValue($value, $defaultValue); 163 } 164 165 } else { 166 167 if ($metadata instanceof MetadataTabular) { 168 169 $childFields = []; 170 foreach ($metadata->getChildrenClass() as $childMetadataClass) { 171 172 try { 173 $childMetadata = MetadataSystem::toMetadataObject($childMetadataClass, $metadata); 174 } catch (ExceptionBadArgument $e) { 175 // should happen only internally 176 LogUtility::internalError("The metadata class/object ($childMetadataClass) is not a metadata class"); 177 continue; 178 } 179 $childField = FormMetaField::createFromMetadata($childMetadata); 180 static::setCommonDataToFieldFromMetadata($childField, $childMetadata); 181 static::setLeafDataToFieldFromMetadata($childField, $childMetadata); 182 $field->addColumn($childField); 183 $childFields[$childMetadata::getPersistentName()] = $childField; 184 } 185 try { 186 $rows = $metadata->getValue(); 187 } catch (ExceptionNotFound $e) { 188 $rows = null; 189 } 190 if ($rows !== null) { 191 $defaultRow = null; 192 try { 193 $defaultRows = $metadata->getDefaultValue(); 194 $defaultRow = $defaultRows[0]; 195 } catch (ExceptionNotFound $e) { 196 // no default row 197 } 198 foreach ($rows as $row) { 199 foreach ($childFields as $childName => $childField) { 200 $colValue = $row[$childName] ?? null; 201 if ($colValue === null) { 202 if ($defaultRow === null) { 203 continue; 204 } 205 $colValue = $defaultRow[$childName]; 206 if ($colValue === null) { 207 continue; 208 } 209 } 210 $storeValue = $colValue->toStoreValue(); 211 $defaultStoreValue = $colValue->toStoreDefaultValue(); 212 $childField->addValue($storeValue, $defaultStoreValue); 213 } 214 215 } 216 217 // Add an extra empty row to allow adding an image 218 if ($defaultRow !== null) { 219 foreach ($defaultRow as $colName => $colMetadata) { 220 if ($colName === null) { 221 LogUtility::internalError("The persistence name (column name) should be set as key for the default rows"); 222 continue; 223 } 224 $defaultColValue = null; 225 if ($colMetadata !== null) { 226 $defaultColValue = $colMetadata->toStoreDefaultValue(); 227 } 228 $childField = $childFields[$colName]; 229 $childField->addValue(null, $defaultColValue); 230 } 231 } 232 } else { 233 234 235 // No rows, show the default rows 236 try { 237 $rows = $metadata->getDefaultValue(); 238 } catch (ExceptionNotFound $e) { 239 // no default row ok 240 $rows = []; 241 } 242 foreach ($rows as $row) { 243 foreach ($row as $colName => $colValue) { 244 if ($colValue === null) { 245 continue; 246 } 247 $childField = $childFields[$colName]; 248 $childField->addValue(null, $colValue->toStoreValue()); 249 } 250 } 251 252 } 253 254 255 } else { 256 257 LogUtility::msg("Hierarchical data is not supported in a form. Metadata ($metadata) has children and is not tabular"); 258 } 259 } 260 return $field; 261 262 } 263 264 265 public 266 function toAssociativeArray(): array 267 { 268 /** 269 * Mandatory attributes 270 */ 271 $associative = [ 272 self::NAME_ATTRIBUTE => $this->name, 273 self::LABEL_ATTRIBUTE => $this->label, 274 self::TYPE_ATTRIBUTE => $this->type 275 ]; 276 277 try { 278 $associative[self::URL_ATTRIBUTE] = $this->getUrl()->toString(); 279 } catch (ExceptionNotFound $e) { 280 // ok 281 } 282 283 if ($this->description != null) { 284 $associative[self::DESCRIPTION_ATTRIBUTE] = $this->description; 285 } 286 /** 287 * For child form field (ie column), there is no tab 288 */ 289 if ($this->tab != null) { 290 $associative[self::TAB_ATTRIBUTE] = $this->tab; 291 } 292 293 294 if ($this->width !== null) { 295 $associative[self::WIDTH_ATTRIBUTE] = $this->width; 296 } 297 if ($this->children !== null) { 298 foreach ($this->children as $column) { 299 $associative[self::CHILDREN_ATTRIBUTE][] = $column->toAssociativeArray(); 300 } 301 } else { 302 303 /** 304 * Only valid for leaf field 305 */ 306 if ($this->getValue() !== null && $this->getValue() !== "") { 307 $associative[self::VALUE_ATTRIBUTE] = $this->getValue(); 308 } 309 310 if ($this->getDefaultValue() !== null) { 311 $associative[self::DEFAULT_VALUE_ATTRIBUTE] = $this->getDefaultValue(); 312 } 313 314 if ($this->domainValues !== null) { 315 $associative[self::DOMAIN_VALUES_ATTRIBUTE] = $this->domainValues; 316 if ($this->multiple) { 317 $associative[self::MULTIPLE_ATTRIBUTE] = $this->multiple; 318 } 319 } 320 321 $associative[self::MUTABLE_ATTRIBUTE] = $this->mutable; 322 323 324 } 325 326 327 return $associative; 328 } 329 330 public 331 function setMutable(bool $bool): FormMetaField 332 { 333 $this->mutable = $bool; 334 return $this; 335 } 336 337 338 public 339 function setTab(string $tabName): FormMetaField 340 { 341 Html::validNameGuard($tabName); 342 $this->tab = $tabName; 343 return $this; 344 } 345 346 public 347 function setLabel(string $label): FormMetaField 348 { 349 $this->label = $label; 350 return $this; 351 } 352 353 /** 354 * @throws ExceptionNotFound 355 */ 356 public 357 function getUrl(): Url 358 { 359 if (!isset($this->canonical)) { 360 throw new ExceptionNotFound(); 361 } 362 363 return Canonical::createFromValue($this->canonical) 364 ->getComboStrapUrlForDocumentation(); 365 366 } 367 368 public 369 function setCanonical(string $canonical): FormMetaField 370 { 371 $this->canonical = $canonical; 372 return $this; 373 } 374 375 public 376 function setDescription(string $string): FormMetaField 377 { 378 $this->description = $string; 379 return $this; 380 } 381 382 /** 383 * @param boolean|string|null $value 384 * @param boolean|string|null $defaultValuePlaceholderOrReturned - the value set as placeholder or return value for a checked checkbox 385 * @return $this 386 */ 387 public 388 function addValue($value, $defaultValuePlaceholderOrReturned = null): FormMetaField 389 { 390 if ($this->getType() === DataType::BOOLEAN_TYPE_VALUE) { 391 if ($value != null && !DataType::isBoolean($value)) { 392 throw new ExceptionRuntimeInternal("The value ($value) is not a boolean"); 393 } 394 if ($defaultValuePlaceholderOrReturned != null && !DataType::isBoolean($defaultValuePlaceholderOrReturned)) { 395 throw new ExceptionRuntimeInternal("The default value ($defaultValuePlaceholderOrReturned) is not a boolean"); 396 } 397 } 398 $this->values[] = $value; 399 $this->defaults[] = $defaultValuePlaceholderOrReturned; 400 return $this; 401 } 402 403 404 public 405 function setDomainValues(array $domainValues): FormMetaField 406 { 407 $this->domainValues = $domainValues; 408 return $this; 409 } 410 411 public 412 function addColumn(FormMetaField $formField): FormMetaField 413 { 414 $this->type = DataType::TABULAR_TYPE_VALUE; 415 // A parent node is not mutable 416 $this->mutable = false; 417 $this->children[] = $formField; 418 return $this; 419 } 420 421 public 422 function setWidth(int $int): FormMetaField 423 { 424 $this->width = $int; 425 return $this; 426 } 427 428 public 429 function getTab(): string 430 { 431 return $this->tab; 432 } 433 434 public 435 function getName() 436 { 437 return $this->name; 438 } 439 440 public 441 function getValue() 442 { 443 switch (sizeof($this->values)) { 444 case 0: 445 return null; 446 case 1: 447 $value = $this->values[0]; 448 if ($value !== null) { 449 return $this->values[0]; 450 } 451 return null; 452 default: 453 return $this->values; 454 } 455 } 456 457 public 458 function isMutable(): bool 459 { 460 return $this->mutable; 461 } 462 463 public 464 function getChildren(): ?array 465 { 466 return $this->children; 467 } 468 469 public 470 function getType(): string 471 { 472 return $this->type; 473 } 474 475 public 476 function getDefaultValue() 477 { 478 switch (sizeof($this->defaults)) { 479 case 0: 480 return null; 481 case 1: 482 $value = $this->defaults[0]; 483 if ($value !== null) { 484 return $value; 485 } 486 return null; 487 default: 488 return $this->defaults; 489 } 490 } 491 492 public 493 function setMultiple(bool $bool): FormMetaField 494 { 495 $this->multiple = $bool; 496 return $this; 497 } 498 499 public 500 function isMultiple(): bool 501 { 502 return $this->multiple; 503 } 504 505 /** 506 * If this is a scalar value, you can set/overwrite the value 507 * with this function 508 * @param $value 509 * @param null $default 510 * @return $this 511 */ 512 public 513 function setValue($value, $default = null): FormMetaField 514 { 515 $this->values = []; 516 $this->defaults = []; 517 return $this->addValue($value, $default); 518 519 } 520 521 /** 522 * Common metadata to all field from a leaf to a tabular 523 * @param FormMetaField $field 524 * @param Metadata $metadata 525 */ 526 private 527 static 528 function setCommonDataToFieldFromMetadata(FormMetaField $field, Metadata $metadata) 529 { 530 $field 531 ->setCanonical($metadata->getCanonical()) 532 ->setLabel($metadata->getLabel()) 533 ->setDescription($metadata->getDescription()); 534 } 535 536 /** 537 * @param FormMetaField $field 538 * @param Metadata $metadata 539 * Add the field metadata that are only available for leaf metadata 540 */ 541 private 542 static 543 function setLeafDataToFieldFromMetadata(FormMetaField $field, Metadata $metadata) 544 { 545 $field->setMutable($metadata->isMutable()); 546 547 $formControlWidth = $metadata->getFormControlWidth(); 548 if ($formControlWidth !== null) { 549 $field->setWidth($formControlWidth); 550 } 551 $possibleValues = $metadata->getPossibleValues(); 552 if ($possibleValues !== null) { 553 $field->setDomainValues($possibleValues); 554 if ($metadata instanceof MetadataMultiple) { 555 $field->setMultiple(true); 556 } 557 } 558 559 } 560 561 public function __toString() 562 { 563 return $this->getName(); 564 } 565 566 567} 568