[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Administrator 5 * @subpackage com_fields 6 * 7 * @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> 8 * @license GNU General Public License version 2 or later; see LICENSE.txt 9 */ 10 11 namespace Joomla\Component\Fields\Administrator\Helper; 12 13 use Joomla\CMS\Factory; 14 use Joomla\CMS\Fields\FieldsServiceInterface; 15 use Joomla\CMS\Form\Form; 16 use Joomla\CMS\Form\FormHelper; 17 use Joomla\CMS\Language\Multilanguage; 18 use Joomla\CMS\Layout\LayoutHelper; 19 use Joomla\CMS\Object\CMSObject; 20 use Joomla\CMS\Plugin\PluginHelper; 21 use Joomla\Component\Fields\Administrator\Model\FieldsModel; 22 use Joomla\Database\ParameterType; 23 24 // phpcs:disable PSR1.Files.SideEffects 25 \defined('_JEXEC') or die; 26 // phpcs:enable PSR1.Files.SideEffects 27 28 /** 29 * FieldsHelper 30 * 31 * @since 3.7.0 32 */ 33 class FieldsHelper 34 { 35 /** 36 * @var FieldsModel 37 */ 38 private static $fieldsCache = null; 39 40 /** 41 * @var FieldsModel 42 */ 43 private static $fieldCache = null; 44 45 /** 46 * Extracts the component and section from the context string which has to 47 * be in the format component.context. 48 * 49 * @param string $contextString contextString 50 * @param object $item optional item object 51 * 52 * @return array|null 53 * 54 * @since 3.7.0 55 */ 56 public static function extract($contextString, $item = null) 57 { 58 $parts = explode('.', $contextString, 2); 59 60 if (count($parts) < 2) { 61 return null; 62 } 63 64 $newSection = ''; 65 66 $component = Factory::getApplication()->bootComponent($parts[0]); 67 68 if ($component instanceof FieldsServiceInterface) { 69 $newSection = $component->validateSection($parts[1], $item); 70 } 71 72 if ($newSection) { 73 $parts[1] = $newSection; 74 } 75 76 return $parts; 77 } 78 79 /** 80 * Returns the fields for the given context. 81 * If the item is an object the returned fields do have an additional field 82 * "value" which represents the value for the given item. If the item has an 83 * assigned_cat_ids field, then additionally fields which belong to that 84 * category will be returned. 85 * Should the value being prepared to be shown in an HTML context then 86 * prepareValue must be set to true. No further escaping needs to be done. 87 * The values of the fields can be overridden by an associative array where the keys 88 * have to be a name and its corresponding value. 89 * 90 * @param string $context The context of the content passed to the helper 91 * @param null $item The item being edited in the form 92 * @param int|bool $prepareValue (if int is display event): 1 - AfterTitle, 2 - BeforeDisplay, 3 - AfterDisplay, 0 - OFF 93 * @param array|null $valuesToOverride The values to override 94 * @param bool $includeSubformFields Should I include fields marked as Only Use In Subform? 95 * 96 * @return array 97 * 98 * @throws \Exception 99 * @since 3.7.0 100 */ 101 public static function getFields( 102 $context, 103 $item = null, 104 $prepareValue = false, 105 array $valuesToOverride = null, 106 bool $includeSubformFields = false 107 ) { 108 if (self::$fieldsCache === null) { 109 // Load the model 110 self::$fieldsCache = Factory::getApplication()->bootComponent('com_fields') 111 ->getMVCFactory()->createModel('Fields', 'Administrator', ['ignore_request' => true]); 112 113 self::$fieldsCache->setState('filter.state', 1); 114 self::$fieldsCache->setState('list.limit', 0); 115 } 116 117 if ($includeSubformFields) { 118 self::$fieldsCache->setState('filter.only_use_in_subform', ''); 119 } else { 120 self::$fieldsCache->setState('filter.only_use_in_subform', 0); 121 } 122 123 if (is_array($item)) { 124 $item = (object) $item; 125 } 126 127 if (Multilanguage::isEnabled() && isset($item->language) && $item->language != '*') { 128 self::$fieldsCache->setState('filter.language', array('*', $item->language)); 129 } 130 131 self::$fieldsCache->setState('filter.context', $context); 132 self::$fieldsCache->setState('filter.assigned_cat_ids', array()); 133 134 /* 135 * If item has assigned_cat_ids parameter display only fields which 136 * belong to the category 137 */ 138 if ($item && (isset($item->catid) || isset($item->fieldscatid))) { 139 $assignedCatIds = $item->catid ?? $item->fieldscatid; 140 141 if (!is_array($assignedCatIds)) { 142 $assignedCatIds = explode(',', $assignedCatIds); 143 } 144 145 // Fields without any category assigned should show as well 146 $assignedCatIds[] = 0; 147 148 self::$fieldsCache->setState('filter.assigned_cat_ids', $assignedCatIds); 149 } 150 151 $fields = self::$fieldsCache->getItems(); 152 153 if ($fields === false) { 154 return array(); 155 } 156 157 if ($item && isset($item->id)) { 158 if (self::$fieldCache === null) { 159 self::$fieldCache = Factory::getApplication()->bootComponent('com_fields') 160 ->getMVCFactory()->createModel('Field', 'Administrator', ['ignore_request' => true]); 161 } 162 163 $fieldIds = array_map( 164 function ($f) { 165 return $f->id; 166 }, 167 $fields 168 ); 169 170 $fieldValues = self::$fieldCache->getFieldValues($fieldIds, $item->id); 171 172 $new = array(); 173 174 foreach ($fields as $key => $original) { 175 /* 176 * Doing a clone, otherwise fields for different items will 177 * always reference to the same object 178 */ 179 $field = clone $original; 180 181 if ($valuesToOverride && array_key_exists($field->name, $valuesToOverride)) { 182 $field->value = $valuesToOverride[$field->name]; 183 } elseif ($valuesToOverride && array_key_exists($field->id, $valuesToOverride)) { 184 $field->value = $valuesToOverride[$field->id]; 185 } elseif (array_key_exists($field->id, $fieldValues)) { 186 $field->value = $fieldValues[$field->id]; 187 } 188 189 if (!isset($field->value) || $field->value === '') { 190 $field->value = $field->default_value; 191 } 192 193 $field->rawvalue = $field->value; 194 195 // If boolean prepare, if int, it is the event type: 1 - After Title, 2 - Before Display Content, 3 - After Display Content, 0 - Do not prepare 196 if ($prepareValue && (is_bool($prepareValue) || $prepareValue === (int) $field->params->get('display', '2'))) { 197 PluginHelper::importPlugin('fields'); 198 199 /* 200 * On before field prepare 201 * Event allow plugins to modify the output of the field before it is prepared 202 */ 203 Factory::getApplication()->triggerEvent('onCustomFieldsBeforePrepareField', array($context, $item, &$field)); 204 205 // Gathering the value for the field 206 $value = Factory::getApplication()->triggerEvent('onCustomFieldsPrepareField', array($context, $item, &$field)); 207 208 if (is_array($value)) { 209 $value = implode(' ', $value); 210 } 211 212 /* 213 * On after field render 214 * Event allows plugins to modify the output of the prepared field 215 */ 216 Factory::getApplication()->triggerEvent('onCustomFieldsAfterPrepareField', array($context, $item, $field, &$value)); 217 218 // Assign the value 219 $field->value = $value; 220 } 221 222 $new[$key] = $field; 223 } 224 225 $fields = $new; 226 } 227 228 return $fields; 229 } 230 231 /** 232 * Renders the layout file and data on the context and does a fall back to 233 * Fields afterwards. 234 * 235 * @param string $context The context of the content passed to the helper 236 * @param string $layoutFile layoutFile 237 * @param array $displayData displayData 238 * 239 * @return NULL|string 240 * 241 * @since 3.7.0 242 */ 243 public static function render($context, $layoutFile, $displayData) 244 { 245 $value = ''; 246 247 /* 248 * Because the layout refreshes the paths before the render function is 249 * called, so there is no way to load the layout overrides in the order 250 * template -> context -> fields. 251 * If there is no override in the context then we need to call the 252 * layout from Fields. 253 */ 254 if ($parts = self::extract($context)) { 255 // Trying to render the layout on the component from the context 256 $value = LayoutHelper::render($layoutFile, $displayData, null, array('component' => $parts[0], 'client' => 0)); 257 } 258 259 if ($value == '') { 260 // Trying to render the layout on Fields itself 261 $value = LayoutHelper::render($layoutFile, $displayData, null, array('component' => 'com_fields','client' => 0)); 262 } 263 264 return $value; 265 } 266 267 /** 268 * PrepareForm 269 * 270 * @param string $context The context of the content passed to the helper 271 * @param Form $form form 272 * @param object $data data. 273 * 274 * @return boolean 275 * 276 * @since 3.7.0 277 */ 278 public static function prepareForm($context, Form $form, $data) 279 { 280 // Extracting the component and section 281 $parts = self::extract($context); 282 283 if (! $parts) { 284 return true; 285 } 286 287 $context = $parts[0] . '.' . $parts[1]; 288 289 // When no fields available return here 290 $fields = self::getFields($parts[0] . '.' . $parts[1], new CMSObject()); 291 292 if (! $fields) { 293 return true; 294 } 295 296 $component = $parts[0]; 297 $section = $parts[1]; 298 299 $assignedCatids = $data->catid ?? $data->fieldscatid ?? $form->getValue('catid'); 300 301 // Account for case that a submitted form has a multi-value category id field (e.g. a filtering form), just use the first category 302 $assignedCatids = is_array($assignedCatids) 303 ? (int) reset($assignedCatids) 304 : (int) $assignedCatids; 305 306 if (!$assignedCatids && $formField = $form->getField('catid')) { 307 $assignedCatids = $formField->getAttribute('default', null); 308 309 if (!$assignedCatids) { 310 // Choose the first category available 311 $catOptions = $formField->options; 312 313 if ($catOptions && !empty($catOptions[0]->value)) { 314 $assignedCatids = (int) $catOptions[0]->value; 315 } 316 } 317 318 $data->fieldscatid = $assignedCatids; 319 } 320 321 /* 322 * If there is a catid field we need to reload the page when the catid 323 * is changed 324 */ 325 if ($form->getField('catid') && $parts[0] != 'com_fields') { 326 /* 327 * Setting some parameters for the category field 328 */ 329 $form->setFieldAttribute('catid', 'refresh-enabled', true); 330 $form->setFieldAttribute('catid', 'refresh-cat-id', $assignedCatids); 331 $form->setFieldAttribute('catid', 'refresh-section', $section); 332 } 333 334 // Getting the fields 335 $fields = self::getFields($parts[0] . '.' . $parts[1], $data); 336 337 if (!$fields) { 338 return true; 339 } 340 341 $fieldTypes = self::getFieldTypes(); 342 343 // Creating the dom 344 $xml = new \DOMDocument('1.0', 'UTF-8'); 345 $fieldsNode = $xml->appendChild(new \DOMElement('form'))->appendChild(new \DOMElement('fields')); 346 $fieldsNode->setAttribute('name', 'com_fields'); 347 348 // Organizing the fields according to their group 349 $fieldsPerGroup = array(0 => array()); 350 351 foreach ($fields as $field) { 352 if (!array_key_exists($field->type, $fieldTypes)) { 353 // Field type is not available 354 continue; 355 } 356 357 if (!array_key_exists($field->group_id, $fieldsPerGroup)) { 358 $fieldsPerGroup[$field->group_id] = array(); 359 } 360 361 if ($path = $fieldTypes[$field->type]['path']) { 362 // Add the lookup path for the field 363 FormHelper::addFieldPath($path); 364 } 365 366 if ($path = $fieldTypes[$field->type]['rules']) { 367 // Add the lookup path for the rule 368 FormHelper::addRulePath($path); 369 } 370 371 $fieldsPerGroup[$field->group_id][] = $field; 372 } 373 374 $model = Factory::getApplication()->bootComponent('com_fields') 375 ->getMVCFactory()->createModel('Groups', 'Administrator', ['ignore_request' => true]); 376 $model->setState('filter.context', $context); 377 378 /** 379 * $model->getItems() would only return existing groups, but we also 380 * have the 'default' group with id 0 which is not in the database, 381 * so we create it virtually here. 382 */ 383 $defaultGroup = new \stdClass(); 384 $defaultGroup->id = 0; 385 $defaultGroup->title = ''; 386 $defaultGroup->description = ''; 387 $iterateGroups = array_merge(array($defaultGroup), $model->getItems()); 388 389 // Looping through the groups 390 foreach ($iterateGroups as $group) { 391 if (empty($fieldsPerGroup[$group->id])) { 392 continue; 393 } 394 395 // Defining the field set 396 /** @var \DOMElement $fieldset */ 397 $fieldset = $fieldsNode->appendChild(new \DOMElement('fieldset')); 398 $fieldset->setAttribute('name', 'fields-' . $group->id); 399 $fieldset->setAttribute('addfieldpath', '/administrator/components/' . $component . '/models/fields'); 400 $fieldset->setAttribute('addrulepath', '/administrator/components/' . $component . '/models/rules'); 401 402 $label = $group->title; 403 $description = $group->description; 404 405 if (!$label) { 406 $key = strtoupper($component . '_FIELDS_' . $section . '_LABEL'); 407 408 if (!Factory::getLanguage()->hasKey($key)) { 409 $key = 'JGLOBAL_FIELDS'; 410 } 411 412 $label = $key; 413 } 414 415 if (!$description) { 416 $key = strtoupper($component . '_FIELDS_' . $section . '_DESC'); 417 418 if (Factory::getLanguage()->hasKey($key)) { 419 $description = $key; 420 } 421 } 422 423 $fieldset->setAttribute('label', $label); 424 $fieldset->setAttribute('description', strip_tags($description)); 425 426 // Looping through the fields for that context 427 foreach ($fieldsPerGroup[$group->id] as $field) { 428 try { 429 Factory::getApplication()->triggerEvent('onCustomFieldsPrepareDom', array($field, $fieldset, $form)); 430 431 /* 432 * If the field belongs to an assigned_cat_id but the assigned_cat_ids in the data 433 * is not known, set the required flag to false on any circumstance. 434 */ 435 if (!$assignedCatids && !empty($field->assigned_cat_ids) && $form->getField($field->name)) { 436 $form->setFieldAttribute($field->name, 'required', 'false'); 437 } 438 } catch (\Exception $e) { 439 Factory::getApplication()->enqueueMessage($e->getMessage(), 'error'); 440 } 441 } 442 443 // When the field set is empty, then remove it 444 if (!$fieldset->hasChildNodes()) { 445 $fieldsNode->removeChild($fieldset); 446 } 447 } 448 449 // Loading the XML fields string into the form 450 $form->load($xml->saveXML()); 451 452 $model = Factory::getApplication()->bootComponent('com_fields') 453 ->getMVCFactory()->createModel('Field', 'Administrator', ['ignore_request' => true]); 454 455 if ( 456 (!isset($data->id) || !$data->id) && Factory::getApplication()->input->getCmd('controller') == 'modules' 457 && Factory::getApplication()->isClient('site') 458 ) { 459 // Modules on front end editing don't have data and an id set 460 $data->id = Factory::getApplication()->input->getInt('id'); 461 } 462 463 // Looping through the fields again to set the value 464 if (!isset($data->id) || !$data->id) { 465 return true; 466 } 467 468 foreach ($fields as $field) { 469 $value = $model->getFieldValue($field->id, $data->id); 470 471 if ($value === null) { 472 continue; 473 } 474 475 if (!is_array($value) && $value !== '') { 476 // Function getField doesn't cache the fields, so we try to do it only when necessary 477 $formField = $form->getField($field->name, 'com_fields'); 478 479 if ($formField && $formField->forceMultiple) { 480 $value = (array) $value; 481 } 482 } 483 484 // Setting the value on the field 485 $form->setValue($field->name, 'com_fields', $value); 486 } 487 488 return true; 489 } 490 491 /** 492 * Return a boolean if the actual logged in user can edit the given field value. 493 * 494 * @param \stdClass $field The field 495 * 496 * @return boolean 497 * 498 * @since 3.7.0 499 */ 500 public static function canEditFieldValue($field) 501 { 502 $parts = self::extract($field->context); 503 504 return Factory::getUser()->authorise('core.edit.value', $parts[0] . '.field.' . (int) $field->id); 505 } 506 507 /** 508 * Return a boolean based on field (and field group) display / show_on settings 509 * 510 * @param \stdClass $field The field 511 * 512 * @return boolean 513 * 514 * @since 3.8.7 515 */ 516 public static function displayFieldOnForm($field) 517 { 518 $app = Factory::getApplication(); 519 520 // Detect if the field should be shown at all 521 if ($field->params->get('show_on') == 1 && $app->isClient('administrator')) { 522 return false; 523 } elseif ($field->params->get('show_on') == 2 && $app->isClient('site')) { 524 return false; 525 } 526 527 if (!self::canEditFieldValue($field)) { 528 $fieldDisplayReadOnly = $field->params->get('display_readonly', '2'); 529 530 if ($fieldDisplayReadOnly == '2') { 531 // Inherit from field group display read-only setting 532 $groupModel = $app->bootComponent('com_fields') 533 ->getMVCFactory()->createModel('Group', 'Administrator', ['ignore_request' => true]); 534 $groupDisplayReadOnly = $groupModel->getItem($field->group_id)->params->get('display_readonly', '1'); 535 $fieldDisplayReadOnly = $groupDisplayReadOnly; 536 } 537 538 if ($fieldDisplayReadOnly == '0') { 539 // Do not display field on form when field is read-only 540 return false; 541 } 542 } 543 544 // Display field on form 545 return true; 546 } 547 548 /** 549 * Gets assigned categories ids for a field 550 * 551 * @param \stdClass[] $fieldId The field ID 552 * 553 * @return array Array with the assigned category ids 554 * 555 * @since 4.0.0 556 */ 557 public static function getAssignedCategoriesIds($fieldId) 558 { 559 $fieldId = (int) $fieldId; 560 561 if (!$fieldId) { 562 return array(); 563 } 564 565 $db = Factory::getDbo(); 566 $query = $db->getQuery(true); 567 568 $query->select($db->quoteName('a.category_id')) 569 ->from($db->quoteName('#__fields_categories', 'a')) 570 ->where('a.field_id = ' . $fieldId); 571 572 $db->setQuery($query); 573 574 return $db->loadColumn(); 575 } 576 577 /** 578 * Gets assigned categories titles for a field 579 * 580 * @param \stdClass[] $fieldId The field ID 581 * 582 * @return array Array with the assigned categories 583 * 584 * @since 3.7.0 585 */ 586 public static function getAssignedCategoriesTitles($fieldId) 587 { 588 $fieldId = (int) $fieldId; 589 590 if (!$fieldId) { 591 return []; 592 } 593 594 $db = Factory::getDbo(); 595 $query = $db->getQuery(true); 596 597 $query->select($db->quoteName('c.title')) 598 ->from($db->quoteName('#__fields_categories', 'a')) 599 ->join('INNER', $db->quoteName('#__categories', 'c') . ' ON a.category_id = c.id') 600 ->where($db->quoteName('field_id') . ' = :fieldid') 601 ->bind(':fieldid', $fieldId, ParameterType::INTEGER); 602 603 $db->setQuery($query); 604 605 return $db->loadColumn(); 606 } 607 608 /** 609 * Gets the fields system plugin extension id. 610 * 611 * @return integer The fields system plugin extension id. 612 * 613 * @since 3.7.0 614 */ 615 public static function getFieldsPluginId() 616 { 617 $db = Factory::getDbo(); 618 $query = $db->getQuery(true) 619 ->select($db->quoteName('extension_id')) 620 ->from($db->quoteName('#__extensions')) 621 ->where($db->quoteName('folder') . ' = ' . $db->quote('system')) 622 ->where($db->quoteName('element') . ' = ' . $db->quote('fields')); 623 $db->setQuery($query); 624 625 try { 626 $result = (int) $db->loadResult(); 627 } catch (\RuntimeException $e) { 628 Factory::getApplication()->enqueueMessage($e->getMessage(), 'error'); 629 $result = 0; 630 } 631 632 return $result; 633 } 634 635 /** 636 * Loads the fields plugins and returns an array of field types from the plugins. 637 * 638 * The returned array contains arrays with the following keys: 639 * - label: The label of the field 640 * - type: The type of the field 641 * - path: The path of the folder where the field can be found 642 * 643 * @return array 644 * 645 * @since 3.7.0 646 */ 647 public static function getFieldTypes() 648 { 649 PluginHelper::importPlugin('fields'); 650 $eventData = Factory::getApplication()->triggerEvent('onCustomFieldsGetTypes'); 651 652 $data = array(); 653 654 foreach ($eventData as $fields) { 655 foreach ($fields as $fieldDescription) { 656 if (!array_key_exists('path', $fieldDescription)) { 657 $fieldDescription['path'] = null; 658 } 659 660 if (!array_key_exists('rules', $fieldDescription)) { 661 $fieldDescription['rules'] = null; 662 } 663 664 $data[$fieldDescription['type']] = $fieldDescription; 665 } 666 } 667 668 return $data; 669 } 670 671 /** 672 * Clears the internal cache for the custom fields. 673 * 674 * @return void 675 * 676 * @since 3.8.0 677 */ 678 public static function clearFieldsCache() 679 { 680 self::$fieldCache = null; 681 self::$fieldsCache = null; 682 } 683 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |