Same name in this branch
  1. 10 core/modules/config_translation/src/FormElement/TextFormat.php \Drupal\config_translation\FormElement\TextFormat
  2. 10 core/modules/filter/src/Element/TextFormat.php \Drupal\filter\Element\TextFormat
Same name and namespace in other branches
  1. 8.9.x core/modules/filter/src/Element/TextFormat.php \Drupal\filter\Element\TextFormat
  2. 9 core/modules/filter/src/Element/TextFormat.php \Drupal\filter\Element\TextFormat

Hierarchy

Expanded class hierarchy of TextFormat

File

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

Namespace

Drupal\filter\Element
View source
class TextFormat extends RenderElementBase {

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = static::class;
    return [
      '#process' => [
        [
          $class,
          'processFormat',
        ],
      ],
      '#base_type' => 'textarea',
      '#theme_wrappers' => [
        'text_format_wrapper',
      ],
    ];
  }

  /**
   * 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:
   * @code
   *   $values = $form_state->getValue('body');
   *   $values['value'] = 'foo';
   *   $values['format'] = 'foo';
   * @endcode
   *
   * @param array $element
   *   The form element to process. See main class documentation for properties.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   *
   * @return array
   *   The form element.
   */
  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;
  }

  /**
   * Render API callback: Hides the field value of 'text_format' elements.
   *
   * To not break form processing and previews if a user does not have access to
   * a stored text format, the expanded form elements in
   * \Drupal\filter\Element\TextFormat::processFormat() are forced to take over
   * the stored #default_values for 'value' and 'format'. However, to prevent
   * the unfiltered, original #value from being displayed to the user, we
   * replace it with a friendly notice here.
   *
   * @param array $element
   *   The render array to add the access denied message to.
   *
   * @return array
   *   The updated render array.
   */
  public static function accessDeniedCallback(array $element) {
    $element['#value'] = t('This field has been disabled because you do not have sufficient permissions to edit it.');
    return $element;
  }

  /**
   * Wraps the current user.
   *
   * \Drupal\Core\Session\AccountInterface
   */
  protected static function currentUser() {
    return \Drupal::currentUser();
  }

  /**
   * Wraps the config factory.
   *
   * @return \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected static function configFactory() {
    return \Drupal::configFactory();
  }

  /**
   * Wraps the element info service.
   *
   * @return \Drupal\Core\Render\ElementInfoManagerInterface
   */
  protected static function elementInfo() {
    return \Drupal::service('element_info');
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
MessengerTrait::$messenger protected property The messenger. 10
MessengerTrait::messenger public function Gets the messenger. 10
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 2
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginBase::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. 38
RenderElement::preRenderAjaxForm public static function Adds Ajax information about an element to communicate with JavaScript.
RenderElement::preRenderGroup public static function Adds members of this group as actual elements for rendering.
RenderElement::processAjaxForm public static function Form element processing handler for the #ajax form property. 1
RenderElement::processGroup public static function Arranges elements into groups.
RenderElement::setAttributes public static function Sets a form element's class attribute. Overrides ElementInterface::setAttributes
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 1
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
TextFormat::accessDeniedCallback public static function Render API callback: Hides the field value of 'text_format' elements.
TextFormat::configFactory protected static function Wraps the config factory.
TextFormat::currentUser protected static function Wraps the current user.
TextFormat::elementInfo protected static function Wraps the element info service.
TextFormat::getInfo public function Returns the element properties for this element. Overrides ElementInterface::getInfo
TextFormat::processFormat public static function Expands an element into a base element with text format selector attached.