Further modules included.
[yaffs-website] / web / modules / contrib / better_formats / better_formats.module
1 <?php
2
3 /**
4  * @file
5  * Enhances the core input format system by managing input format defaults and settings.
6  */
7
8 use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
9 use Drupal\Core\Form\FormStateInterface;
10 use Drupal\Core\Render\Element;
11
12 /**
13  * Implements hook_element_info_alter().
14  */
15 function better_formats_element_info_alter(array &$types) {
16   // Our process callback must run immediately after
17   // TextFormat::processFormat().
18   if (isset($types['text_format']) && isset($types['text_format']['#process'])) {
19     $search_value = ['Drupal\filter\Element\TextFormat', 'processFormat'];
20     $key = array_search($search_value, $types['text_format']['#process']);
21
22     if ($key !== FALSE) {
23       $key++;
24       array_splice($types['text_format']['#process'], $key, 0, 'better_formats_filter_process_format');
25     }
26     else {
27       $types['text_format']['#process'][] = 'better_formats_filter_process_format';
28     }
29   }
30 }
31
32 /**
33  * Process callback for form elements that have a text format selector attached.
34  *
35  * This callback runs after filter_process_format() and performs additional
36  * modifications to the form element.
37  *
38  * @see \Drupal\filter\Element\TextFormat::processFormat()
39  */
40 function better_formats_filter_process_format(array &$element, FormStateInterface $form_state, array $complete_form) {
41   // Before we make any modifications to the element, record whether or not
42   // TextFormat::processFormat() has determined that (for security reasons) the
43   // user is not allowed to make any changes to this field. This will happen if
44   // the user does not have permission to use the currently-assigned text
45   // format.
46   $access_denied_for_security = isset($element['format']['format']['#access']) && !$element['format']['format']['#access'];
47
48   $form_object = $form_state->getFormObject();
49
50   if ($form_object instanceof Drupal\Core\Entity\ContentEntityForm) {
51     $entity = $form_state->getFormObject()->getEntity();
52     $entity_type = $entity->getEntityTypeId();
53     $field_name = reset($element['#parents']);
54     $field_definition = $entity->getFieldDefinition($field_name);
55
56     if ($field_definition instanceof ThirdPartySettingsInterface) {
57       $bf = $field_definition->getThirdPartySettings('better_formats');
58
59       if (Drupal::config('better_formats.settings')->get('per_field_core')) {
60         $field_defaults = $field_definition->getDefaultValue($entity);
61         $default_value = array_shift($field_defaults);
62         better_formats_set_default_format($element, $default_value['format']);
63       }
64     }
65   }
66   elseif ($form_object instanceof Drupal\field_ui\Form\FieldConfigEditForm) {
67     $entity = $form_state->getFormObject()->getEntity();
68     $entity_type = $entity->getTargetEntityTypeId();
69     $bf = $entity->getThirdPartySettings('better_formats');
70   }
71   else {
72     return $element;
73   }
74
75   // Now hide several parts of the element for cosmetic reasons (depending on
76   // the permissions of the current user).
77   $user = \Drupal::currentUser();
78   $user_is_admin = $user->hasPermission('administer filters');
79
80   // The selection should be shown unless proven otherwise.
81   $hide_selection = FALSE;
82
83   // If an entity is available then allow Better Formats permission to control
84   // visibility.
85   if ($entity_type != NULL) {
86     $hide_selection = $user->hasPermission('hide format selection for ' . $entity_type);
87   }
88
89   // Privileged users should still be able to change the format selection.
90   if ($hide_selection && !$user_is_admin) {
91     $element['format']['format']['#access'] = FALSE;
92   }
93
94   // Allow formats tips to be hidden.
95   $hide_tips = $user->hasPermission('hide format tips');
96
97   if ($hide_tips && !$user_is_admin) {
98     $element['format']['guidelines']['#access'] = FALSE;
99   }
100
101   // Allow format tips link to be hidden.
102   $hide_tips_link = $user->hasPermission('hide more format tips link');
103
104   if ($hide_tips_link && !$user_is_admin) {
105     $element['format']['help']['#access'] = FALSE;
106   }
107
108   // If the element represents a field attached to an entity, we may need to
109   // adjust the allowed text format options. However, we don't want to touch
110   // this if TextFormat::processFormat() has determined that (for security
111   // reasons) the user is not allowed to make any changes; in that case, Drupal
112   // core will hide the format selector and force the field to be saved with its
113   // current values, and we should not do anything to alter that process.
114   if ($entity_type != NULL && !$access_denied_for_security) {
115     $eid = $entity->id();
116
117     // Need to only do this on create forms.
118     if (empty($eid) && isset($bf) && !empty($bf['default_order_toggle']) && !empty($bf['default_order_wrapper']['formats'])) {
119       $order = $bf['default_order_wrapper']['formats'];
120       uasort($order, 'better_formats_text_format_sort');
121
122       $options = [];
123
124       foreach ($order as $id => $weight) {
125         if (isset($element['format']['format']['#options'][$id])) {
126           $options[$id] = $element['format']['format']['#options'][$id];
127         }
128       }
129
130       $element['format']['format']['#options'] = $options;
131       $options_keys = array_keys($options);
132
133       if (!Drupal::config('better_formats.settings')->get('per_field_core')) {
134         better_formats_set_default_format($element, array_shift($options_keys));
135       }
136     }
137     if (isset($bf) && !empty($bf['allowed_formats_toggle']) && !empty($bf['allowed_formats'])) {
138       // Filter the list of available formats to those allowed on this field.
139       $allowed_fields = array_filter($bf['allowed_formats']);
140       $options = &$element['format']['format']['#options'];
141       $options = array_intersect_key($options, $allowed_fields);
142
143       // If there is only one allowed format, deny access to the text format
144       // selector for cosmetic reasons, just like filter_process_format() does.
145       if (count($options) == 1) {
146         $element['format']['format']['#access'] = FALSE;
147         $hide_selection = TRUE;
148       }
149
150       // If there are no allowed formats, we need to deny access to the entire
151       // field, since it doesn't make sense to add or edit content that does
152       // not have a text format.
153       if (empty($options)) {
154         $element['#access'] = FALSE;
155       }
156       // Otherwise, if the current default format is no longer one of the
157       // allowed options, a new default format must be assigned.
158       elseif (!isset($options[$element['format']['format']['#default_value']])) {
159         // If there is no text in the field, it is safe to automatically assign
160         // a new default format. We pick the first available option to be
161         // consistent with what filter_default_format() does.
162         if (!isset($element['value']['#default_value']) || $element['value']['#default_value'] === '') {
163           $formats = array_keys($options);
164           better_formats_set_default_format($element, reset($formats));
165         }
166         // Otherwise, it is unsafe to automatically assign a new default format
167         // (since this will display the content in a way that was not
168         // originally intended and might be dangerous, e.g. if the content
169         // contains an attempted XSS attack). A human must explicitly decide
170         // which new format to assign, so we force the field to be required but
171         // with no default value, similar to what filter_process_format() does.
172         // Although filter_process_format() limits this functionality to users
173         // with the 'administer filters' permission, we can allow it for any
174         // user here since we know that the user already has permission to use
175         // the current format; thus, there is no danger of exposing unformatted
176         // text (for example, raw PHP code) that they are otherwise not allowed
177         // to see.
178         else {
179           $element['format']['format']['#required'] = TRUE;
180           better_formats_set_default_format($element, NULL);
181           // Force access to the format selector (it may have been denied
182           // previously for cosmetic reasons).
183           $element['format']['#access'] = TRUE;
184           $element['format']['format']['#access'] = TRUE;
185         }
186       }
187     }
188   }
189
190   // If the user is not supposed to see the text format selector, hide all
191   // guidelines except those associated with the default format. We need to do
192   // this at the end, since the above code may have altered the default format.
193   if ($hide_selection && isset($element['format']['format']['#default_value'])) {
194     foreach (Element::children($element['format']['guidelines']) as $format) {
195       if ($format != $element['format']['format']['#default_value']) {
196         $element['format']['guidelines'][$format]['#access'] = FALSE;
197       }
198     }
199   }
200
201   // Keep the format for validation and submit processing but don't sent it to
202   // the browser if the user is not supposed to see anything inside of it.
203   if ($hide_selection && $hide_tips && $hide_tips_link) {
204     unset($element['format']['#type']);
205   }
206
207   return $element;
208 }
209
210 /**
211  * Determine if text field type uses text formatter.
212  *
213  * @param string $type
214  *   The field type to check.
215  *
216  * @return bool
217  *   TRUE if input field type uses text formatter, FALSE if it does not.
218  */
219 function better_formats_is_text_format($type) {
220   if (in_array($type, ['text', 'text_long', 'text_with_summary'], TRUE)) {
221     return TRUE;
222   }
223   else {
224     return FALSE;
225   }
226 }
227
228 /**
229  * Set the default format for the element.
230  *
231  * @param array $element
232  *    The form element to set the default format on.
233  * @param string $default_value
234  *    The id for the format to set as default.
235  */
236 function better_formats_set_default_format(array &$element, $default_value) {
237   $element['#format'] = $default_value;
238   $element['format']['format']['#default_value'] = $default_value;
239 }
240
241 /**
242  * Sort text formats by weight.
243  *
244  * @param array $a
245  *   Array containing weight value to compare.
246  * @param array $b
247  *   Array containing weight value to compare.
248  *
249  * @return bool
250  *   TRUE if the weight of $a is greater than $b, FALSE if weight of $b is
251  *   greater than $a or equal to $a.
252  */
253 function better_formats_text_format_sort($a, $b) {
254   return $a['weight'] > $b['weight'];
255 }
256
257 /**
258  * Implements hook_form_FORM_ID_alter().
259  */
260 function better_formats_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
261   $entity = $form_state->getFormObject()->getEntity();
262
263   // Only alter fields with text processing and if admin has chosen.
264   $text_processing = better_formats_is_text_format($entity->getType());
265   $config = Drupal::config('better_formats.settings');
266
267   if ($text_processing && $config->get('per_field_core')) {
268     // Add a submit handler to save default values on empty fields.
269     $form['actions']['submit']['#submit'][] = 'better_formats_field_config_edit_form_submit';
270   }
271
272   // If the field is a format-using text field, allow the admin to configure
273   // which formats are allowed here.
274   if ($text_processing) {
275     // We have to set an explicit weight here so that we can put the allowed
276     // formats list after it.
277     $bf_settings = $entity->getThirdPartySettings('better_formats') != NULL ? $entity->getThirdPartySettings('better_formats') : [];
278
279     // Add in the better formats table.
280     $form['third_party_settings'] += better_formats_field_settings_form($bf_settings);
281     $form['third_party_settings']['#weight'] = -4;
282   }
283 }
284
285 /**
286  * Build the settings form for Field API fields.
287  *
288  * @param array $bf_form
289  *   The existing better formats settings form from the form element.
290  *
291  * @return array
292  *   The array of better formats form items.
293  */
294 function better_formats_field_settings_form($bf_form = []) {
295   $formats = filter_formats();
296   // Plain Text Format should not be an option, that is a separate field type.
297   unset($formats['plain_text']);
298
299   $form = [];
300   $form['better_formats'] = [
301     '#tree' => TRUE,
302     '#type' => 'fieldset',
303     '#title' => t('Text Formats'),
304     '#weight' => 0,
305   ];
306
307   $allowed_options = [];
308
309   foreach ($formats as $format) {
310     $allowed_options[$format->id()] = $format->label();
311   }
312
313   $allowed_toggle_default = isset($bf_form['allowed_formats_toggle']) ? $bf_form['allowed_formats_toggle'] : FALSE;
314   $allowed_defaults = isset($bf_form['allowed_formats']) ? $bf_form['allowed_formats'] : [];
315
316   if (empty($allowed_defaults)) {
317     $allowed_defaults = array_keys($allowed_options);
318   }
319
320   $form['better_formats']['allowed_formats_toggle'] = [
321     '#type' => 'checkbox',
322     '#title' => t('Limit allowed text formats'),
323     '#description' => t('Check the allowed formats below. If checked available text formats can be chosen.'),
324     '#weight' => 1,
325     '#default_value' => $allowed_toggle_default,
326   ];
327   $form['better_formats']['allowed_formats'] = [
328     '#type' => 'checkboxes',
329     '#title' => t('Allowed formats'),
330     '#options' => $allowed_options,
331     '#description' => t('Select the text formats allowed for this field. Note that not all of these may appear on the form if a user does not have permission to use them. <strong>Warning:</strong> This affects existing content which may leave you unable to edit some fields. If that happens you must allow the format that field was saved in here.'),
332     '#weight' => 2,
333     '#default_value' => $allowed_defaults,
334     '#states' => [
335       'visible' => [
336         ':input[name="third_party_settings[better_formats][allowed_formats_toggle]"]' => ['checked' => TRUE],
337       ],
338     ],
339   ];
340
341   $order_toggle_default = isset($bf_form['default_order_toggle']) ? $bf_form['default_order_toggle'] : FALSE;
342
343   $form['better_formats']['default_order_toggle'] = [
344     '#type' => 'checkbox',
345     '#title' => t('Override default order'),
346     '#description' => t('Override the gloabl order that will determine the default text format a user will get <strong>only on entity creation</strong>.'),
347     '#weight' => 3,
348     '#default_value' => $order_toggle_default,
349   ];
350   $form['better_formats']['default_order_wrapper'] = [
351     '#tree' => TRUE,
352     '#type' => 'container',
353     '#weight' => 4,
354     '#states' => [
355       'visible' => [
356         ':input[name="third_party_settings[better_formats][default_order_toggle]"]' => ['checked' => TRUE],
357       ],
358     ],
359   ];
360
361   // Formats ordering table.
362   $form['better_formats']['default_order_wrapper']['formats'] = [
363     '#type' => 'table',
364     '#header' => [t('Format'), t('Weight')],
365     '#empty' => t('There are no items yet.'),
366     '#tabledrag' => [
367       [
368         'action' => 'order',
369         'relationship' => 'sibling',
370         'group' => 'format-order-weight',
371       ],
372     ],
373   ];
374
375   foreach ($formats as $id => $format) {
376     $default = isset($bf_form['default_order_wrapper']['formats'][$id]) ? $bf_form['default_order_wrapper']['formats'][$id] : NULL;
377     $weight = isset($default['weight']) ? $default['weight'] : $format->get('weight');
378
379     // TableDrag: Mark the table row as draggable.
380     $form['better_formats']['default_order_wrapper']['formats'][$id]['#attributes']['class'][] = 'draggable';
381     // TableDrag: Sort the table row according to its existing/configured weight.
382     $form['better_formats']['default_order_wrapper']['formats'][$id]['#weight'] = $weight;
383
384     // Some table columns containing raw markup.
385     $form['better_formats']['default_order_wrapper']['formats'][$id]['label'] = [
386       '#markup' => $format->label(),
387     ];
388
389     // TableDrag: Weight column element.
390     $form['better_formats']['default_order_wrapper']['formats'][$id]['weight'] = [
391       '#type' => 'weight',
392       '#title' => t('Weight for @title', ['@title' => $format->label()]),
393       '#title_display' => 'invisible',
394       '#default_value' => $weight,
395       // Classify the weight element for #tabledrag.
396       '#attributes' => ['class' => ['format-order-weight']],
397     ];
398   }
399
400   // Sort formats according to weight.
401   Element::children($form['better_formats']['default_order_wrapper']['formats'], TRUE);
402
403   return $form;
404 }
405
406 /**
407  * Submit handler for field instance edit form.
408  *
409  * Copied and slightly modifed from FieldConfigEditForm::submitForm().
410  *
411  * @see \Drupal\field_ui\Form\FieldConfigEditForm::submitForm()
412  */
413 function better_formats_field_config_edit_form_submit(array &$form, FormStateInterface $form_state) {
414   $entity = $form_state->getFormObject()->getEntity();
415   $text_processing = better_formats_is_text_format($entity->getType());
416
417   // Only act on fields that have text processing enabled.
418   if ($text_processing) {
419     // Handle the default value.
420     $default_value = [];
421     $default_input_value = $form_state->getValue(['default_value_input', $entity->getName()]);
422
423     if ($default_input_value != NULL) {
424       $default_value = $default_input_value;
425     }
426
427     $entity->setDefaultValue($default_value)->save();
428   }
429 }