"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/modules/filter/src/Element/TextFormat.php" (26 Nov 2020, 10725 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "TextFormat.php" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.0.8_vs_9.1.0-rc1.

    1 <?php
    2 
    3 namespace Drupal\filter\Element;
    4 
    5 use Drupal\Core\Form\FormStateInterface;
    6 use Drupal\Core\Render\Element\RenderElement;
    7 use Drupal\Core\Render\Element;
    8 use Drupal\Core\Url;
    9 
   10 /**
   11  * Provides a text format render element.
   12  *
   13  * Properties:
   14  * - #base_type: The form element #type to use for the 'value' element.
   15  *   'textarea' by default.
   16  * - #format: (optional) The text format ID to preselect. If omitted, the
   17  *   default format for the current user will be used.
   18  * - #allowed_formats: (optional) An array of text format IDs that are available
   19  *   for this element. If omitted, all text formats that the current user has
   20  *   access to will be allowed.
   21  *
   22  * Usage Example:
   23  * @code
   24  * $form['body'] = array(
   25  *   '#type' => 'text_format',
   26  *   '#title' => 'Body',
   27  *   '#format' => 'full_html',
   28  *   '#default_value' => '<p>The quick brown fox jumped over the lazy dog.</p>',
   29  * );
   30  * @endcode
   31  *
   32  * @see \Drupal\Core\Render\Element\Textarea
   33  *
   34  * @RenderElement("text_format")
   35  */
   36 class TextFormat extends RenderElement {
   37 
   38   /**
   39    * {@inheritdoc}
   40    */
   41   public function getInfo() {
   42     $class = get_class($this);
   43     return [
   44       '#process' => [
   45         [$class, 'processFormat'],
   46       ],
   47       '#base_type' => 'textarea',
   48       '#theme_wrappers' => ['text_format_wrapper'],
   49     ];
   50   }
   51 
   52   /**
   53    * Expands an element into a base element with text format selector attached.
   54    *
   55    * The form element will be expanded into two separate form elements, one
   56    * holding the original element, and the other holding the text format
   57    * selector:
   58    * - value: Holds the original element, having its #type changed to the value
   59    *   of #base_type or 'textarea' by default.
   60    * - format: Holds the text format details and the text format selection,
   61    *   using the text format ID specified in #format or the user's default
   62    *   format by default, if NULL.
   63    *
   64    * The resulting value for the element will be an array holding the value and
   65    * the format. For example, the value for the body element will be:
   66    * @code
   67    *   $values = $form_state->getValue('body');
   68    *   $values['value'] = 'foo';
   69    *   $values['format'] = 'foo';
   70    * @endcode
   71    *
   72    * @param array $element
   73    *   The form element to process. See main class documentation for properties.
   74    * @param \Drupal\Core\Form\FormStateInterface $form_state
   75    *   The current state of the form.
   76    * @param array $complete_form
   77    *   The complete form structure.
   78    *
   79    * @return array
   80    *   The form element.
   81    */
   82   public static function processFormat(&$element, FormStateInterface $form_state, &$complete_form) {
   83     $user = static::currentUser();
   84 
   85     // Ensure that children appear as subkeys of this element.
   86     $element['#tree'] = TRUE;
   87     $keys_not_to_copy = [
   88       // Make \Drupal::formBuilder()->doBuildForm() regenerate child properties.
   89       '#parents',
   90       '#id',
   91       '#name',
   92       // Do not copy this #process function to prevent
   93       // \Drupal::formBuilder()->doBuildForm() from recursing infinitely.
   94       '#process',
   95       // Ensure #pre_render functions will be run.
   96       '#pre_render',
   97       // Description is handled by theme_text_format_wrapper().
   98       '#description',
   99       // Ensure proper ordering of children.
  100       '#weight',
  101       // Properties already processed for the parent element.
  102       '#prefix',
  103       '#suffix',
  104       '#attached',
  105       '#processed',
  106       '#theme_wrappers',
  107     ];
  108     // Move this element into sub-element 'value'.
  109     unset($element['value']);
  110     foreach (Element::properties($element) as $key) {
  111       if (!in_array($key, $keys_not_to_copy)) {
  112         $element['value'][$key] = $element[$key];
  113       }
  114     }
  115 
  116     $element['value']['#type'] = $element['#base_type'];
  117     $element['value'] += static::elementInfo()->getInfo($element['#base_type']);
  118     // Make sure the #default_value key is set, so we can use it below.
  119     $element['value'] += ['#default_value' => ''];
  120 
  121     // Turn original element into a text format wrapper.
  122     $element['#attached']['library'][] = 'filter/drupal.filter';
  123 
  124     // Setup child container for the text format widget.
  125     $element['format'] = [
  126       '#type' => 'container',
  127       '#attributes' => ['class' => ['js-filter-wrapper']],
  128     ];
  129 
  130     // Get a list of formats that the current user has access to.
  131     $formats = filter_formats($user);
  132 
  133     // Allow the list of formats to be restricted.
  134     if (isset($element['#allowed_formats'])) {
  135       // We do not add the fallback format here to allow the use-case of forcing
  136       // certain text formats to be used for certain text areas. In case the
  137       // fallback format is supposed to be allowed as well, it must be added to
  138       // $element['#allowed_formats'] explicitly.
  139       $formats = array_intersect_key($formats, array_flip($element['#allowed_formats']));
  140     }
  141 
  142     if (!isset($element['#format']) && !empty($formats)) {
  143       // If no text format was selected, use the allowed format with the highest
  144       // weight. This is equivalent to calling filter_default_format().
  145       $element['#format'] = reset($formats)->id();
  146     }
  147 
  148     // If #allowed_formats is set, the list of formats must not be modified in
  149     // any way. Otherwise, however, if all of the following conditions are true,
  150     // remove the fallback format from the list of formats:
  151     // 1. The 'always_show_fallback_choice' filter setting has not been
  152     //    activated.
  153     // 2. Multiple text formats are available.
  154     // 3. The fallback format is not the default format.
  155     // The 'always_show_fallback_choice' filter setting is a hidden setting that
  156     // has no UI. It defaults to FALSE.
  157     $config = static::configFactory()->get('filter.settings');
  158     if (!isset($element['#allowed_formats']) && !$config->get('always_show_fallback_choice')) {
  159       $fallback_format = $config->get('fallback_format');
  160       if ($element['#format'] !== $fallback_format && count($formats) > 1) {
  161         unset($formats[$fallback_format]);
  162       }
  163     }
  164 
  165     // Prepare text format guidelines.
  166     $element['format']['guidelines'] = [
  167       '#type' => 'container',
  168       '#attributes' => ['class' => ['js-filter-guidelines']],
  169       '#weight' => 20,
  170     ];
  171     $options = [];
  172     foreach ($formats as $format) {
  173       $options[$format->id()] = $format->label();
  174       $element['format']['guidelines'][$format->id()] = [
  175         '#theme' => 'filter_guidelines',
  176         '#format' => $format,
  177       ];
  178     }
  179 
  180     $element['format']['format'] = [
  181       '#type' => 'select',
  182       '#title' => t('Text format'),
  183       '#options' => $options,
  184       '#default_value' => $element['#format'],
  185       '#access' => count($formats) > 1,
  186       '#weight' => 10,
  187       '#attributes' => ['class' => ['js-filter-list']],
  188       '#parents' => array_merge($element['#parents'], ['format']),
  189     ];
  190 
  191     $element['format']['help'] = [
  192       '#type' => 'container',
  193       'about' => [
  194         '#type' => 'link',
  195         '#title' => t('About text formats'),
  196         '#url' => new Url('filter.tips_all'),
  197         '#attributes' => ['target' => '_blank'],
  198       ],
  199       '#weight' => 0,
  200     ];
  201 
  202     $all_formats = filter_formats();
  203     $format_exists = isset($all_formats[$element['#format']]);
  204     $format_allowed = !isset($element['#allowed_formats']) || in_array($element['#format'], $element['#allowed_formats']);
  205     $user_has_access = isset($formats[$element['#format']]);
  206     $user_is_admin = $user->hasPermission('administer filters');
  207 
  208     // If the stored format does not exist or if it is not among the allowed
  209     // formats for this textarea, administrators have to assign a new format.
  210     if ((!$format_exists || !$format_allowed) && $user_is_admin) {
  211       $element['format']['format']['#required'] = TRUE;
  212       $element['format']['format']['#default_value'] = NULL;
  213       // Force access to the format selector (it may have been denied above if
  214       // the user only has access to a single format).
  215       $element['format']['format']['#access'] = TRUE;
  216     }
  217     // Disable this widget, if the user is not allowed to use the stored format,
  218     // or if the stored format does not exist. The 'administer filters'
  219     // permission only grants access to the filter administration, not to all
  220     // formats.
  221     elseif (!$user_has_access || !$format_exists) {
  222       // Overload default values into #value to make them unalterable.
  223       $element['value']['#value'] = $element['value']['#default_value'];
  224       $element['format']['format']['#value'] = $element['format']['format']['#default_value'];
  225 
  226       // Prepend #pre_render callback to replace field value with user notice
  227       // prior to rendering.
  228       $element['value'] += ['#pre_render' => []];
  229       array_unshift($element['value']['#pre_render'], [static::class, 'accessDeniedCallback']);
  230 
  231       // Cosmetic adjustments.
  232       if (isset($element['value']['#rows'])) {
  233         $element['value']['#rows'] = 3;
  234       }
  235       $element['value']['#disabled'] = TRUE;
  236       $element['value']['#resizable'] = 'none';
  237 
  238       // Hide the text format selector and any other child element (such as text
  239       // field's summary).
  240       foreach (Element::children($element) as $key) {
  241         if ($key != 'value') {
  242           $element[$key]['#access'] = FALSE;
  243         }
  244       }
  245     }
  246 
  247     return $element;
  248   }
  249 
  250   /**
  251    * Render API callback: Hides the field value of 'text_format' elements.
  252    *
  253    * To not break form processing and previews if a user does not have access to
  254    * a stored text format, the expanded form elements in
  255    * \Drupal\filter\Element\TextFormat::processFormat() are forced to take over
  256    * the stored #default_values for 'value' and 'format'. However, to prevent
  257    * the unfiltered, original #value from being displayed to the user, we
  258    * replace it with a friendly notice here.
  259    *
  260    * @param array $element
  261    *   The render array to add the access denied message to.
  262    *
  263    * @return array
  264    *   The updated render array.
  265    */
  266   public static function accessDeniedCallback(array $element) {
  267     $element['#value'] = t('This field has been disabled because you do not have sufficient permissions to edit it.');
  268     return $element;
  269   }
  270 
  271   /**
  272    * Wraps the current user.
  273    *
  274    * \Drupal\Core\Session\AccountInterface
  275    */
  276   protected static function currentUser() {
  277     return \Drupal::currentUser();
  278   }
  279 
  280   /**
  281    * Wraps the config factory.
  282    *
  283    * @return \Drupal\Core\Config\ConfigFactoryInterface
  284    */
  285   protected static function configFactory() {
  286     return \Drupal::configFactory();
  287   }
  288 
  289   /**
  290    * Wraps the element info service.
  291    *
  292    * @return \Drupal\Core\Render\ElementInfoManagerInterface
  293    */
  294   protected static function elementInfo() {
  295     return \Drupal::service('element_info');
  296   }
  297 
  298 }