[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/administrator/components/com_fields/src/Helper/ -> FieldsHelper.php (source)

   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  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer