Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / modules / filter / src / Element / TextFormat.php
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     $blacklist = [
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, $blacklist)) {
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' => ['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 activated.
152     // 2. Multiple text formats are available.
153     // 3. The fallback format is not the default format.
154     // The 'always_show_fallback_choice' filter setting is a hidden setting that
155     // has no UI. It defaults to FALSE.
156     $config = static::configFactory()->get('filter.settings');
157     if (!isset($element['#allowed_formats']) && !$config->get('always_show_fallback_choice')) {
158       $fallback_format = $config->get('fallback_format');
159       if ($element['#format'] !== $fallback_format && count($formats) > 1) {
160         unset($formats[$fallback_format]);
161       }
162     }
163
164     // Prepare text format guidelines.
165     $element['format']['guidelines'] = [
166       '#type' => 'container',
167       '#attributes' => ['class' => ['filter-guidelines']],
168       '#weight' => 20,
169     ];
170     $options = [];
171     foreach ($formats as $format) {
172       $options[$format->id()] = $format->label();
173       $element['format']['guidelines'][$format->id()] = [
174         '#theme' => 'filter_guidelines',
175         '#format' => $format,
176       ];
177     }
178
179     $element['format']['format'] = [
180       '#type' => 'select',
181       '#title' => t('Text format'),
182       '#options' => $options,
183       '#default_value' => $element['#format'],
184       '#access' => count($formats) > 1,
185       '#weight' => 10,
186       '#attributes' => ['class' => ['filter-list']],
187       '#parents' => array_merge($element['#parents'], ['format']),
188     ];
189
190     $element['format']['help'] = [
191       '#type' => 'container',
192       'about' => [
193         '#type' => 'link',
194         '#title' => t('About text formats'),
195         '#url' => new Url('filter.tips_all'),
196         '#attributes' => ['target' => '_blank'],
197       ],
198       '#attributes' => ['class' => ['filter-help']],
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'], 'filter_form_access_denied');
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    * Wraps the current user.
252    *
253    * \Drupal\Core\Session\AccountInterface
254    */
255   protected static function currentUser() {
256     return \Drupal::currentUser();
257   }
258
259   /**
260    * Wraps the config factory.
261    *
262    * @return \Drupal\Core\Config\ConfigFactoryInterface
263    */
264   protected static function configFactory() {
265     return \Drupal::configFactory();
266   }
267
268   /**
269    * Wraps the element info service.
270    *
271    * @return \Drupal\Core\Render\ElementInfoManagerInterface
272    */
273   protected static function elementInfo() {
274     return \Drupal::service('element_info');
275   }
276
277 }