Same name and namespace in other branches
  1. 8.9.x core/modules/filter/src/Element/TextFormat.php \Drupal\filter\Element\TextFormat::processFormat()
  2. 9 core/modules/filter/src/Element/TextFormat.php \Drupal\filter\Element\TextFormat::processFormat()

Expands an element into a base element with text format selector attached.

The form element will be expanded into two separate form elements, one holding the original element, and the other holding the text format selector:

  • value: Holds the original element, having its #type changed to the value of #base_type or 'textarea' by default.
  • format: Holds the text format details and the text format selection, using the text format ID specified in #format or the user's default format by default, if NULL.

The resulting value for the element will be an array holding the value and the format. For example, the value for the body element will be:

$values = $form_state
  ->getValue('body');
$values['value'] = 'foo';
$values['format'] = 'foo';

Parameters

array $element: The form element to process. See main class documentation for properties.

\Drupal\Core\Form\FormStateInterface $form_state: The current state of the form.

array $complete_form: The complete form structure.

Return value

array The form element.

File

core/modules/filter/src/Element/TextFormat.php, line 82

Class

TextFormat

Namespace

Drupal\filter\Element

Code

public static function processFormat(&$element, FormStateInterface $form_state, &$complete_form) {
  $user = static::currentUser();

  // Ensure that children appear as subkeys of this element.
  $element['#tree'] = TRUE;
  $keys_not_to_copy = [
    // Make \Drupal::formBuilder()->doBuildForm() regenerate child properties.
    '#parents',
    '#id',
    '#name',
    // Do not copy this #process function to prevent
    // \Drupal::formBuilder()->doBuildForm() from recursing infinitely.
    '#process',
    // Ensure #pre_render functions will be run.
    '#pre_render',
    // Description is handled by theme_text_format_wrapper().
    '#description',
    // Ensure proper ordering of children.
    '#weight',
    // Properties already processed for the parent element.
    '#prefix',
    '#suffix',
    '#attached',
    '#processed',
    '#theme_wrappers',
  ];

  // Move this element into sub-element 'value'.
  unset($element['value']);
  foreach (Element::properties($element) as $key) {
    if (!in_array($key, $keys_not_to_copy)) {
      $element['value'][$key] = $element[$key];
    }
  }
  $element['value']['#type'] = $element['#base_type'];
  $element['value'] += static::elementInfo()
    ->getInfo($element['#base_type']);

  // Make sure the #default_value key is set, so we can use it below.
  $element['value'] += [
    '#default_value' => '',
  ];

  // Turn original element into a text format wrapper.
  $element['#attached']['library'][] = 'filter/drupal.filter';

  // Setup child container for the text format widget.
  $element['format'] = [
    '#type' => 'container',
    '#theme_wrappers' => [
      'container__text_format_filter_wrapper',
    ],
    '#attributes' => [
      'class' => [
        'js-filter-wrapper',
      ],
    ],
  ];

  // Get a list of formats that the current user has access to.
  $formats = filter_formats($user);

  // Allow the list of formats to be restricted.
  if (isset($element['#allowed_formats'])) {

    // We do not add the fallback format here to allow the use-case of forcing
    // certain text formats to be used for certain text areas. In case the
    // fallback format is supposed to be allowed as well, it must be added to
    // $element['#allowed_formats'] explicitly.
    $formats = array_intersect_key($formats, array_flip($element['#allowed_formats']));
  }
  if (!isset($element['#format']) && !empty($formats)) {

    // If no text format was selected, use the allowed format with the highest
    // weight. This is equivalent to calling filter_default_format().
    $element['#format'] = reset($formats)
      ->id();
  }

  // If #allowed_formats is set, the list of formats must not be modified in
  // any way. Otherwise, however, if all of the following conditions are true,
  // remove the fallback format from the list of formats:
  // 1. The 'always_show_fallback_choice' filter setting has not been
  //    activated.
  // 2. Multiple text formats are available.
  // 3. The fallback format is not the default format.
  // The 'always_show_fallback_choice' filter setting is a hidden setting that
  // has no UI. It defaults to FALSE.
  $config = static::configFactory()
    ->get('filter.settings');
  if (!isset($element['#allowed_formats']) && !$config
    ->get('always_show_fallback_choice')) {
    $fallback_format = $config
      ->get('fallback_format');
    if ($element['#format'] !== $fallback_format && count($formats) > 1) {
      unset($formats[$fallback_format]);
    }
  }

  // If the value element has #states set, copy it to the format element.
  if (isset($element['value']['#states'])) {
    $element['format']['#states'] = $element['value']['#states'];
  }

  // Prepare text format guidelines.
  $element['format']['guidelines'] = [
    '#type' => 'container',
    '#attributes' => [
      'class' => [
        'js-filter-guidelines',
      ],
    ],
    '#theme_wrappers' => [
      'container__text_format_filter_guidelines',
    ],
    '#weight' => 20,
  ];
  $options = [];
  foreach ($formats as $format) {
    $options[$format
      ->id()] = $format
      ->label();
    $element['format']['guidelines'][$format
      ->id()] = [
      '#theme' => 'filter_guidelines',
      '#format' => $format,
    ];
  }
  $element['format']['format'] = [
    '#type' => 'select',
    '#title' => t('Text format'),
    '#options' => $options,
    '#default_value' => $element['#format'],
    '#access' => count($formats) > 1,
    '#weight' => 10,
    '#attributes' => [
      'class' => [
        'js-filter-list',
      ],
    ],
    '#parents' => array_merge($element['#parents'], [
      'format',
    ]),
  ];
  $element['format']['help'] = [
    '#type' => 'container',
    '#theme_wrappers' => [
      'container__text_format_filter_help',
    ],
    'about' => [
      '#type' => 'link',
      '#title' => t('About text formats'),
      '#url' => new Url('filter.tips_all'),
      '#attributes' => [
        'target' => '_blank',
      ],
    ],
    '#weight' => 0,
  ];
  $all_formats = filter_formats();
  $format_exists = isset($all_formats[$element['#format']]);
  $format_allowed = !isset($element['#allowed_formats']) || in_array($element['#format'], $element['#allowed_formats']);
  $user_has_access = isset($formats[$element['#format']]);
  $user_is_admin = $user
    ->hasPermission('administer filters');

  // If the stored format does not exist or if it is not among the allowed
  // formats for this textarea, administrators have to assign a new format.
  if ((!$format_exists || !$format_allowed) && $user_is_admin) {
    $element['format']['format']['#required'] = TRUE;
    $element['format']['format']['#default_value'] = NULL;

    // Force access to the format selector (it may have been denied above if
    // the user only has access to a single format).
    $element['format']['format']['#access'] = TRUE;
  }
  elseif (!$user_has_access || !$format_exists) {

    // Overload default values into #value to make them unalterable.
    $element['value']['#value'] = $element['value']['#default_value'];
    $element['format']['format']['#value'] = $element['format']['format']['#default_value'];

    // Prepend #pre_render callback to replace field value with user notice
    // prior to rendering.
    $element['value'] += [
      '#pre_render' => [],
    ];
    array_unshift($element['value']['#pre_render'], [
      static::class,
      'accessDeniedCallback',
    ]);

    // Cosmetic adjustments.
    if (isset($element['value']['#rows'])) {
      $element['value']['#rows'] = 3;
    }
    $element['value']['#disabled'] = TRUE;
    $element['value']['#resizable'] = 'none';

    // Hide the text format selector and any other child element (such as text
    // field's summary).
    foreach (Element::children($element) as $key) {
      if ($key != 'value') {
        $element[$key]['#access'] = FALSE;
      }
    }
  }
  return $element;
}