You are here

better_formats.module in Better Formats 8

Enhances the core input format system by managing input format defaults and settings.

File

better_formats.module
View source
<?php

/**
 * @file
 * Enhances the core input format system by managing input format defaults and settings.
 */
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;

/**
 * Implements hook_field_widget_form_alter().
 *
 */
function better_formats_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
  $field_definition = $context['items']
    ->getFieldDefinition();
  if (!$field_definition instanceof ThirdPartySettingsInterface) {
    return;
  }
  $betterFormatsSettings = $field_definition
    ->getThirdPartySettings('better_formats');
  if (empty($betterFormatsSettings)) {
    return;
  }
  $element['#better_formats']['settings'] = $betterFormatsSettings;
  $element['#better_formats']['existing_entity'] = FALSE;
  $entity = $context['items']
    ->getEntity();
  if (!empty($entity)) {

    // Determine if this is an existing entity.
    if ($entity
      ->id()) {
      $element['#better_formats']['existing_entity'] = TRUE;
    }
    $element['#better_formats']['entity_type'] = $entity
      ->getEntityTypeId();
    $default_value = $field_definition
      ->getDefaultValue($entity);
    $default_value = array_shift($default_value);
    $element['#better_formats']['default_value'] = isset($default_value['format']) ? $default_value['format'] : NULL;
  }
}

/**
 * Implements hook_element_info_alter().
 */
function better_formats_element_info_alter(array &$types) {

  // Our process callback must run immediately after
  // TextFormat::processFormat().
  if (isset($types['text_format']) && isset($types['text_format']['#process'])) {
    $search_value = [
      'Drupal\\filter\\Element\\TextFormat',
      'processFormat',
    ];
    $key = array_search($search_value, $types['text_format']['#process']);
    if ($key !== FALSE) {
      $key++;
      array_splice($types['text_format']['#process'], $key, 0, 'better_formats_filter_process_format');
    }
    else {
      $types['text_format']['#process'][] = 'better_formats_filter_process_format';
    }
  }
}

/**
 * Process callback for form elements that have a text format selector attached.
 *
 * This callback runs after filter_process_format() and performs additional
 * modifications to the form element.
 *
 * @see \Drupal\filter\Element\TextFormat::processFormat()
 */
function better_formats_filter_process_format(array &$element, FormStateInterface $form_state, array $complete_form) {

  // Before we make any modifications to the element, record whether or not
  // TextFormat::processFormat() has determined that (for security reasons) the
  // user is not allowed to make any changes to this field. This will happen if
  // the user does not have permission to use the currently-assigned text
  // format.
  $access_denied_for_security = isset($element['format']['format']['#access']) && !$element['format']['format']['#access'];
  if (!empty($element['#better_formats']['settings'])) {
    $betterFormatsSettings = $element['#better_formats']['settings'];
  }
  else {
    return $element;
  }
  if (!empty($element['#better_formats']['entity_type'])) {
    $entity_type = $element['#better_formats']['entity_type'];
  }

  // Whether use the core field module default value to set the default format.
  // See /admin/config/content/formats/settings.
  if (Drupal::config('better_formats.settings')
    ->get('per_field_core')) {
    better_formats_set_default_format($element, $element['#better_formats']['default_value']);
  }

  // Now hide several parts of the element for cosmetic reasons (depending on
  // the permissions of the current user).
  $user = \Drupal::currentUser();
  $user_is_admin = $user
    ->hasPermission('administer filters');

  // The selection should be shown unless proven otherwise.
  $hide_selection = FALSE;

  // If an entity is available then allow Better Formats permission to control
  // visibility.
  if ($entity_type != NULL) {
    $hide_selection = $user
      ->hasPermission('hide format selection for ' . $entity_type);
  }

  // Privileged users should still be able to change the format selection.
  if ($hide_selection && !$user_is_admin) {
    $element['format']['format']['#access'] = FALSE;
  }

  // Allow formats tips to be hidden.
  $hide_tips = $user
    ->hasPermission('hide format tips');
  if ($hide_tips && !$user_is_admin) {
    $element['format']['guidelines']['#access'] = FALSE;
  }

  // Allow format tips link to be hidden.
  $hide_tips_link = $user
    ->hasPermission('hide more format tips link');
  if ($hide_tips_link && !$user_is_admin) {
    $element['format']['help']['#access'] = FALSE;
  }

  // If the element represents a field attached to an entity, we may need to
  // adjust the allowed text format options. However, we don't want to touch
  // this if TextFormat::processFormat() has determined that (for security
  // reasons) the user is not allowed to make any changes; in that case, Drupal
  // core will hide the format selector and force the field to be saved with its
  // current values, and we should not do anything to alter that process.
  if ($entity_type != NULL && !$access_denied_for_security) {

    // Need to only do this on create forms.
    if (!$element['#better_formats']['existing_entity'] && isset($betterFormatsSettings) && !empty($betterFormatsSettings['default_order_toggle']) && !empty($betterFormatsSettings['default_order_wrapper']['formats'])) {
      $order = $betterFormatsSettings['default_order_wrapper']['formats'];
      uasort($order, 'better_formats_text_format_sort');
      $options = [];
      foreach ($order as $id => $weight) {
        if (isset($element['format']['format']['#options'][$id])) {
          $options[$id] = $element['format']['format']['#options'][$id];
        }
      }
      $element['format']['format']['#options'] = $options;
      $options_keys = array_keys($options);
      if (!Drupal::config('better_formats.settings')
        ->get('per_field_core')) {
        better_formats_set_default_format($element, array_shift($options_keys));
      }
    }
    if (isset($betterFormatsSettings) && !empty($betterFormatsSettings['allowed_formats_toggle']) && !empty($betterFormatsSettings['allowed_formats'])) {

      // Filter the list of available formats to those allowed on this field.
      $allowed_fields = array_filter($betterFormatsSettings['allowed_formats']);
      $options =& $element['format']['format']['#options'];
      $options = array_intersect_key($options, $allowed_fields);

      // If there is only one allowed format, deny access to the text format
      // selector for cosmetic reasons, just like filter_process_format() does.
      if (count($options) == 1) {
        $element['format']['format']['#access'] = FALSE;
        $hide_selection = TRUE;
      }

      // If there are no allowed formats, we need to deny access to the entire
      // field, since it doesn't make sense to add or edit content that does
      // not have a text format.
      if (empty($options)) {
        $element['#access'] = FALSE;
      }
      elseif (!isset($options[$element['format']['format']['#default_value']])) {

        // If there is no text in the field, it is safe to automatically assign
        // a new default format. We pick the first available option to be
        // consistent with what filter_default_format() does.
        if (!isset($element['value']['#default_value']) || $element['value']['#default_value'] === '') {
          $formats = array_keys($options);
          better_formats_set_default_format($element, reset($formats));
        }
        else {
          $element['format']['format']['#required'] = TRUE;
          better_formats_set_default_format($element, NULL);

          // Force access to the format selector (it may have been denied
          // previously for cosmetic reasons).
          $element['format']['#access'] = TRUE;
          $element['format']['format']['#access'] = TRUE;
        }
      }
    }
  }

  // If the user is not supposed to see the text format selector, hide all
  // guidelines except those associated with the default format. We need to do
  // this at the end, since the above code may have altered the default format.
  if ($hide_selection && isset($element['format']['format']['#default_value'])) {
    foreach (Element::children($element['format']['guidelines']) as $format) {
      if ($format != $element['format']['format']['#default_value']) {
        $element['format']['guidelines'][$format]['#access'] = FALSE;
      }
    }
  }

  // Keep the format for validation and submit processing but don't sent it to
  // the browser if the user is not supposed to see anything inside of it.
  if ($hide_selection && $hide_tips && $hide_tips_link) {
    unset($element['format']['#type']);
  }
  return $element;
}

/**
 * Determine if text field type uses text formatter.
 *
 * @param string $type
 *   The field type to check.
 *
 * @return bool
 *   TRUE if input field type uses text formatter, FALSE if it does not.
 */
function better_formats_is_text_format($type) {
  if (in_array($type, [
    'text',
    'text_long',
    'text_with_summary',
  ], TRUE)) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Set the default format for the element.
 *
 * @param array $element
 *    The form element to set the default format on.
 * @param string $default_value
 *    The id for the format to set as default.
 */
function better_formats_set_default_format(array &$element, $default_value) {
  $element['#format'] = $default_value;
  $element['format']['format']['#default_value'] = $default_value;
}

/**
 * Sort text formats by weight.
 *
 * @param array $a
 *   Array containing weight value to compare.
 * @param array $b
 *   Array containing weight value to compare.
 *
 * @return bool
 *   TRUE if the weight of $a is greater than $b, FALSE if weight of $b is
 *   greater than $a or equal to $a.
 */
function better_formats_text_format_sort($a, $b) {
  return $a['weight'] > $b['weight'];
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function better_formats_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $entity = $form_state
    ->getFormObject()
    ->getEntity();

  // Only alter fields with text processing and if admin has chosen.
  $text_processing = better_formats_is_text_format($entity
    ->getType());
  $config = Drupal::config('better_formats.settings');
  if ($text_processing && $config
    ->get('per_field_core')) {

    // Add a submit handler to save default values on empty fields.
    $form['actions']['submit']['#submit'][] = 'better_formats_field_config_edit_form_submit';
  }

  // If the field is a format-using text field, allow the admin to configure
  // which formats are allowed here.
  if ($text_processing) {

    // We have to set an explicit weight here so that we can put the allowed
    // formats list after it.
    $betterFormatsSettings = $entity
      ->getThirdPartySettings('better_formats') != NULL ? $entity
      ->getThirdPartySettings('better_formats') : [];

    // Add in the better formats table.
    $form['third_party_settings'] += better_formats_field_settings_form($betterFormatsSettings);
    $form['third_party_settings']['#weight'] = -4;
  }
}

/**
 * Build the settings form for Field API fields.
 *
 * @param array $bf_form
 *   The existing better formats settings form from the form element.
 *
 * @return array
 *   The array of better formats form items.
 */
function better_formats_field_settings_form($bf_form = []) {
  $formats = filter_formats();

  // Plain Text Format should not be an option, that is a separate field type.
  unset($formats['plain_text']);
  $form = [];
  $form['better_formats'] = [
    '#tree' => TRUE,
    '#type' => 'fieldset',
    '#title' => t('Text Formats'),
    '#weight' => 0,
  ];
  $allowed_options = [];
  foreach ($formats as $format) {
    $allowed_options[$format
      ->id()] = $format
      ->label();
  }
  $allowed_toggle_default = isset($bf_form['allowed_formats_toggle']) ? $bf_form['allowed_formats_toggle'] : FALSE;
  $allowed_defaults = isset($bf_form['allowed_formats']) ? $bf_form['allowed_formats'] : [];
  if (empty($allowed_defaults)) {
    $allowed_defaults = array_keys($allowed_options);
  }
  $form['better_formats']['allowed_formats_toggle'] = [
    '#type' => 'checkbox',
    '#title' => t('Limit allowed text formats'),
    '#description' => t('Check the allowed formats below. If checked available text formats can be chosen.'),
    '#weight' => 1,
    '#default_value' => $allowed_toggle_default,
  ];
  $form['better_formats']['allowed_formats'] = [
    '#type' => 'checkboxes',
    '#title' => t('Allowed formats'),
    '#options' => $allowed_options,
    '#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.'),
    '#weight' => 2,
    '#default_value' => $allowed_defaults,
    '#states' => [
      'visible' => [
        ':input[name="third_party_settings[better_formats][allowed_formats_toggle]"]' => [
          'checked' => TRUE,
        ],
      ],
    ],
  ];
  $order_toggle_default = isset($bf_form['default_order_toggle']) ? $bf_form['default_order_toggle'] : FALSE;
  $form['better_formats']['default_order_toggle'] = [
    '#type' => 'checkbox',
    '#title' => t('Override default order'),
    '#description' => t('Override the global order that will determine the default text format a user will get <strong>only on entity creation</strong>.'),
    '#weight' => 3,
    '#default_value' => $order_toggle_default,
  ];
  $form['better_formats']['default_order_wrapper'] = [
    '#tree' => TRUE,
    '#type' => 'container',
    '#weight' => 4,
    '#states' => [
      'visible' => [
        ':input[name="third_party_settings[better_formats][default_order_toggle]"]' => [
          'checked' => TRUE,
        ],
      ],
    ],
  ];

  // Formats ordering table.
  $form['better_formats']['default_order_wrapper']['formats'] = [
    '#type' => 'table',
    '#header' => [
      t('Format'),
      t('Weight'),
    ],
    '#empty' => t('There are no items yet.'),
    '#tabledrag' => [
      [
        'action' => 'order',
        'relationship' => 'sibling',
        'group' => 'format-order-weight',
      ],
    ],
  ];
  foreach ($formats as $id => $format) {
    $default = isset($bf_form['default_order_wrapper']['formats'][$id]) ? $bf_form['default_order_wrapper']['formats'][$id] : NULL;
    $weight = isset($default['weight']) ? $default['weight'] : $format
      ->get('weight');

    // TableDrag: Mark the table row as draggable.
    $form['better_formats']['default_order_wrapper']['formats'][$id]['#attributes']['class'][] = 'draggable';

    // TableDrag: Sort the table row according to its existing/configured weight.
    $form['better_formats']['default_order_wrapper']['formats'][$id]['#weight'] = $weight;

    // Some table columns containing raw markup.
    $form['better_formats']['default_order_wrapper']['formats'][$id]['label'] = [
      '#markup' => $format
        ->label(),
    ];

    // TableDrag: Weight column element.
    $form['better_formats']['default_order_wrapper']['formats'][$id]['weight'] = [
      '#type' => 'weight',
      '#title' => t('Weight for @title', [
        '@title' => $format
          ->label(),
      ]),
      '#title_display' => 'invisible',
      '#default_value' => $weight,
      // Classify the weight element for #tabledrag.
      '#attributes' => [
        'class' => [
          'format-order-weight',
        ],
      ],
    ];
  }

  // Sort formats according to weight.
  Element::children($form['better_formats']['default_order_wrapper']['formats'], TRUE);
  return $form;
}

/**
 * Submit handler for field instance edit form.
 *
 * Copied and slightly modifed from FieldConfigEditForm::submitForm().
 *
 * @see \Drupal\field_ui\Form\FieldConfigEditForm::submitForm()
 */
function better_formats_field_config_edit_form_submit(array &$form, FormStateInterface $form_state) {
  $entity = $form_state
    ->getFormObject()
    ->getEntity();
  $text_processing = better_formats_is_text_format($entity
    ->getType());

  // Only act on fields that have text processing enabled.
  if ($text_processing) {

    // Handle the default value.
    $default_value = [];
    $default_input_value = $form_state
      ->getValue([
      'default_value_input',
      $entity
        ->getName(),
    ]);
    if ($default_input_value != NULL) {
      $default_value = $default_input_value;
    }
    $entity
      ->setDefaultValue($default_value)
      ->save();
  }
}

Functions

Namesort descending Description
better_formats_element_info_alter Implements hook_element_info_alter().
better_formats_field_config_edit_form_submit Submit handler for field instance edit form.
better_formats_field_settings_form Build the settings form for Field API fields.
better_formats_field_widget_form_alter Implements hook_field_widget_form_alter().
better_formats_filter_process_format Process callback for form elements that have a text format selector attached.
better_formats_form_field_config_edit_form_alter Implements hook_form_FORM_ID_alter().
better_formats_is_text_format Determine if text field type uses text formatter.
better_formats_set_default_format Set the default format for the element.
better_formats_text_format_sort Sort text formats by weight.