You are here

paragraphs_ee.module in Paragraphs Editor Enhancements 8

Main functions for "Paragraphs Editor Enhancements" module.

File

paragraphs_ee.module
View source
<?php

/**
 * @file
 * Main functions for "Paragraphs Editor Enhancements" module.
 */
use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\WidgetInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\paragraphs\ParagraphsTypeInterface;
use Drupal\paragraphs\Entity\ParagraphsType;
use Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget;
use Drupal\paragraphs_ee\Entity\ParagraphsCategory;

/**
 * Implements hook_theme().
 */
function paragraphs_ee_theme() {
  return [
    'paragraphs_add_dialog__categorized' => [
      'render element' => 'element',
      'template' => 'paragraphs-add-dialog--categorized',
      'path' => drupal_get_path('module', 'paragraphs_ee') . '/templates',
    ],
    'input__submit__paragraphs_action__image' => [
      'base hook' => 'input',
      'render element' => 'element',
      'template' => 'input--submit--paragraphs_action--image',
      'path' => drupal_get_path('module', 'paragraphs_ee') . '/templates',
    ],
  ];
}

/**
 * Implements hook_paragraphs_ee_widget_access().
 */
function paragraphs_ee_paragraphs_ee_widget_access(array $elements, FormStateInterface $form_state, array $context) {

  /** @var \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget $widget */
  $widget = $context['widget'];
  if (!$widget instanceof ParagraphsWidget || 'modal' !== $widget
    ->getSetting('add_mode')) {
    return AccessResult::forbidden();
  }
  if (empty($elements['add_more']['#theme']) || 'paragraphs_add_dialog' !== $elements['add_more']['#theme']) {
    return AccessResult::neutral('Do not override default theme implementation for "add-more" element.');
  }
  return AccessResult::allowed();
}

/**
 * Implements hook_field_widget_form_alter().
 */
function paragraphs_ee_field_widget_form_alter(&$element, FormStateInterface &$form_state, $context) {
  if (!$context['widget'] instanceof ParagraphsWidget) {
    return;
  }

  // Add custom library.
  $element['#attached']['library'][] = 'paragraphs_ee/paragraphs_ee.paragraphs';
  if (empty($element['#attached']['drupalSettings']['paragraphs_features'])) {

    // If the widget is not configured to use one of the features provided by
    // the module, this would not exist and cause an error.
    $element['#attached']['drupalSettings']['paragraphs_features'] = [];
  }
}

/**
 * Implements hook_field_widget_multivalue_form_alter().
 */
function paragraphs_ee_field_widget_multivalue_form_alter(array &$elements, FormStateInterface $form_state, array $context) {

  /** @var \Drupal\paragraphs\Plugin\Field\FieldWidget\ParagraphsWidget $widget */
  $widget = $context['widget'];
  if (!$widget instanceof ParagraphsWidget) {
    return;
  }

  // Check if modifications to widget are allowed.
  $hook_arguments = [
    $elements,
    $form_state,
    $context,
  ];
  $access_results = \Drupal::moduleHandler()
    ->invokeAll('paragraphs_ee_widget_access', $hook_arguments);

  /** @var \Drupal\Core\Access\AccessResultInterface $result */
  $result = AccessResult::neutral();
  if (!empty($access_results)) {
    $result = array_shift($access_results);
    foreach ($access_results as $access_result) {
      $result = $result
        ->orIf($access_result);
    }
  }
  if (!$result
    ->isAllowed()) {
    return;
  }
  if (!isset($elements['add_more'])) {
    return;
  }

  /** @var \Drupal\entity_reference_revisions\EntityReferenceRevisionsFieldItemList $items */
  $items = $context['items'];

  // Add custom library.
  $elements['#attached']['library'][] = 'paragraphs_ee/paragraphs_ee.paragraphs';
  $field_definition_settings = $items
    ->getFieldDefinition()
    ->getSetting('handler_settings');

  // Load all available paragraph types.
  $types_available = ParagraphsType::loadMultiple(array_column($elements['add_more'], '#bundle_machine_name'));
  $buttons_ref = [];

  /** @var \Drupal\paragraphs\Entity\ParagraphsType $type */
  foreach ($types_available as $id => $type) {
    $button_id = "add_more_button_{$id}";
    $button = $elements['add_more'][$button_id];

    // Use custom button layout (and rewrite <input> to <button>).
    $button['#theme_wrappers'] = [
      'input__submit__paragraphs_action__image',
    ];
    $button['#attributes']['class'][] = 'paragraphs-button--add-more';
    $button['#description'] = $type
      ->getDescription();
    $button['#icon_attributes'] = new Attribute();
    $button['#icon_attributes']['aria-hidden'] = 'true';
    $button['#icon_attributes']['class'] = [
      'paragraphs-button--icon',
    ];
    if ($icon_url = $type
      ->getIconUrl()) {

      // Extract icon from button.
      $elements['add_more'][$button_id]['#attributes']['class'][] = 'icon';
      unset($button['#attributes']['style']);
      $button['#icon'] = $icon_url;
    }
    else {
      $button['#icon_attributes']['class'][] = 'image-default';
    }
    $button['#weight'] = 0;
    if (isset($field_definition_settings['target_bundles_drag_drop'][$id]['weight'])) {
      $button['#weight'] = $field_definition_settings['target_bundles_drag_drop'][$id]['weight'];
    }
    $elements['add_more'][$button_id] = $button;
    $settings = array_filter($type
      ->getThirdPartySetting('paragraphs_ee', 'paragraphs_categories', []));
    if (empty($settings)) {

      // Store category for later use.
      $elements['add_more'][$button_id]['#paragraphs_category'] = '_none';

      // Paragraph type is uncategorized so we do not need to process it here.
      continue;
    }

    // We need to call this first because the value is changed on second run
    // only.
    Html::getUniqueId($button_id);
    foreach ($settings as $paragraphs_category) {
      $buttons_ref[$button_id] = empty($buttons_ref[$button_id]) ? 1 : $buttons_ref[$button_id] + 1;
      if (empty($elements['add_more'][$button_id]['#paragraphs_category'])) {

        // Store category for later use.
        $elements['add_more'][$button_id]['#paragraphs_category'] = $paragraphs_category;
        continue;
      }

      // Clone button and change some attributes so AJAX is working.
      $button['#id'] = Html::getUniqueId($button['#id']);
      $button_new_id = strtr(Html::getUniqueId($button_id), [
        '-' => '_',
      ]);
      $button['#name'] .= '__' . $buttons_ref[$button_id];

      // Add clone of button to add_more-element.
      $elements['add_more'][$button_new_id] = $button;

      // Store category for later use.
      $elements['add_more'][$button_new_id]['#paragraphs_category'] = $paragraphs_category;
    }
  }
  uasort($elements['add_more'], 'Drupal\\Component\\Utility\\SortArray::sortByWeightProperty');
  $widget_third_party_settings = $widget
    ->getThirdPartySetting('paragraphs_ee', 'paragraphs_ee');
  $easy_access_buttons = [];
  $easy_access_buttons_max = isset($widget_third_party_settings['easy_access_count']) ? $widget_third_party_settings['easy_access_count'] : 2;

  // Mark the first unique buttons for easy access.
  foreach (Element::children($elements['add_more']) as $child_key) {
    if (empty($elements['add_more'][$child_key]['#bundle_machine_name'])) {
      continue;
    }
    if (count($easy_access_buttons) >= $easy_access_buttons_max) {

      // No need to process more elements as we reached the limit already.
      break;
    }
    if (isset($easy_access_buttons[$elements['add_more'][$child_key]['#bundle_machine_name']])) {

      // Button is already in list and is not added again.
      continue;
    }
    $easy_access_buttons[$elements['add_more'][$child_key]['#bundle_machine_name']] = $elements['add_more'][$child_key]['#weight'];
    $elements['add_more'][$child_key]['#easy_access'] = TRUE;
  }

  // Remove type attribute from "add_more" to prevent a duplicate dialog.
  // @see https://www.drupal.org/i/3153820 for background information.
  unset($elements['add_more']['#type']);

  // Use different theme for modal dialog.
  $elements['add_more']['#theme'] = 'paragraphs_add_dialog__categorized';
  $elements['add_more']['#attributes'] = isset($elements['add_more']['#attributes']) ? $elements['add_more']['#attributes'] : new Attribute();
  $elements['add_more']['#attributes']['class'] = [
    'paragraphs-add-dialog',
    'paragraphs-add-dialog--categorized',
    'js-hide',
  ];
  if (!empty($widget_third_party_settings['dialog_style']) && 'tiles' !== $widget_third_party_settings['dialog_style']) {
    $elements['add_more']['#attributes']['class'][] = 'paragraphs-style-' . $widget_third_party_settings['dialog_style'];
  }
  $elements['add_more']['#attributes']['role'] = 'dialog';
  $elements['add_more']['#attributes']['aria-modal'] = 'true';
  $elements['add_more']['#attributes']['aria-label'] = t('Add @widget_title', [
    '@widget_title' => $widget
      ->getSetting('title'),
  ], [
    'context' => 'Paragraphs Editor Enhancements',
  ]);
  $elements['add_more']['#attributes']['data-widget-title'] = $widget
    ->getSetting('title');
  $elements['add_more']['#attributes']['data-widget-title-plural'] = $widget
    ->getSetting('title_plural');
  $elements['add_more']['#attributes']['data-paragraphs-ee-dialog-wrapper'] = '';
  $elements['add_more']['#attached']['library'][] = 'paragraphs_ee/paragraphs_ee.categories';
  if (!empty($widget_third_party_settings['dialog_off_canvas'])) {

    /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
    $form_display = $form_state
      ->get('form_display');
    $elements['add_more']['#attributes']['data-dialog-off-canvas'] = 'true';
    $elements['add_more']['#attributes']['data-dialog-field-name'] = $items
      ->getFieldDefinition()
      ->getName();
    $browser_params = [
      'entity_type' => $form_display
        ->getTargetEntityTypeId(),
      'bundle' => $form_display
        ->getTargetBundle(),
      'form_mode' => $form_display
        ->getMode(),
      'field_name' => $items
        ->getFieldDefinition()
        ->getName(),
    ];
    $elements['add_more']['#attributes']['data-dialog-browser-url'] = Url::fromRoute('paragraphs_ee.paragraphs_browser', $browser_params)
      ->toString();
  }
  $elements['#attributes']['data-paragraphs-ee'] = '';
  $elements['#attributes']['data-paragraphs-ee-easy-access-max'] = $easy_access_buttons_max;
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function paragraphs_ee_form_paragraphs_type_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  /** @var Drupal\paragraphs\ParagraphsTypeInterface $paragraph */
  $paragraph = $form_state
    ->getFormObject()
    ->getEntity();

  /** @var \Drupal\paragraphs_ee\ParagraphsCategoryInterface[] $categories */
  $categories = \Drupal::entityTypeManager()
    ->getStorage('paragraphs_category')
    ->loadMultiple();

  // Sort the entities using the entity class's sort() method.
  // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
  uasort($categories, [
    ParagraphsCategory::class,
    'sort',
  ]);
  $form['paragraphs_categories'] = [
    '#type' => 'checkboxes',
    '#options' => array_combine(array_column($categories, 'id'), array_column($categories, 'label')),
    '#title' => t('Paragraphs categories'),
    '#description' => t('Select all categories the paragraph applies to.'),
    '#default_value' => $paragraph
      ->getThirdPartySetting('paragraphs_ee', 'paragraphs_categories', []),
  ];
  $form['#entity_builders'][] = 'paragraphs_ee_form_paragraphs_type_form_builder';
}

/**
 * Entity builder for the menu configuration entity.
 */
function paragraphs_ee_form_paragraphs_type_form_builder($entity_type, ParagraphsTypeInterface $paragraph, &$form, FormStateInterface $form_state) {
  if ($form_state
    ->getValue('paragraphs_categories')) {
    $paragraph
      ->setThirdPartySetting('paragraphs_ee', 'paragraphs_categories', array_filter($form_state
      ->getValue('paragraphs_categories')));
    return;
  }

  // Remove setting.
  $paragraph
    ->unsetThirdPartySetting('paragraphs_ee', 'paragraphs_categories');
}

/**
 * Prepare variables used in paragraphs-add-dialog--categorized.html.twig.
 */
function template_preprocess_paragraphs_add_dialog__categorized(&$vars) {

  // Define variables for the template.
  $vars += [
    'buttons' => [],
  ];
  $vars['add_mode'] = isset($vars['element']['#add_mode']) ? $vars['element']['#add_mode'] : 'modal';
  if (isset($vars['element']['add_modal_form_area'])) {
    $vars['add'] = $vars['element']['add_modal_form_area'];
  }

  /** @var \Drupal\paragraphs_ee\ParagraphsCategoryInterface[] $categories */
  $paragraphs_categories = \Drupal::entityTypeManager()
    ->getStorage('paragraphs_category')
    ->loadMultiple();

  // Sort the entities using the entity class's sort() method.
  // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
  uasort($paragraphs_categories, [
    ParagraphsCategory::class,
    'sort',
  ]);
  $category_names = array_keys($paragraphs_categories);
  $grouped = array_combine($category_names, array_fill(0, count($category_names), []));
  foreach ($paragraphs_categories as $key => $category) {
    $conditional_id = isset($vars['element']['#id']) ? $vars['element']['#id'] . '-category-' . $key : 'category-' . $key;
    $vars['categories'][$key] = [
      'title' => $category
        ->label(),
      'description' => $category
        ->getDescription(),
      'id' => Html::getUniqueId($conditional_id),
    ];
  }
  $vars['categories']['_none'] = [
    'title' => t('Uncategorized'),
    'description' => '',
  ];

  // Add category for uncategorized items.
  $grouped['_none'] = [];
  foreach (Element::children($vars['element']) as $element_key) {
    if (empty($vars['element'][$element_key]['#paragraphs_category'])) {
      continue;
    }

    // Add button to category.
    $grouped[$vars['element'][$element_key]['#paragraphs_category']][] = $vars['element'][$element_key];
  }
  $vars['groups'] = array_filter($grouped);
  $placeholder_value = 'Paragraphs';
  if (isset($vars['element']['#attributes']['data-widget-title-plural'])) {
    $placeholder_value = $vars['element']['#attributes']['data-widget-title-plural'];
  }
  $vars['filter_placeholder'] = t('Find @placeholder_value', [
    '@placeholder_value' => $placeholder_value,
  ], [
    'context' => 'Paragraphs Editor Enhancements',
  ]);
}

/**
 * Prepare variables used in input--submit--paragraphs_action--image.html.twig.
 */
function paragraphs_ee_preprocess_input__submit__paragraphs_action__image(&$vars) {
  $element = $vars['element'];

  // Add title and description as custom element.
  $vars['title'] = $element['#value'];
  $vars['description'] = $element['#description'];
  $vars['description_id'] = empty($vars['attributes']['aria-describedby']) ? NULL : $vars['attributes']['aria-describedby'];
  $icon_attributes = $element['#icon_attributes'];
  if (!empty($element['#icon'])) {
    $icon_attributes['style'] = 'background-image: url("' . $element['#icon'] . '");';
  }
  $vars['icon_attributes'] = $icon_attributes;
}

/**
 * Implements hook_field_widget_settings_summary_alter().
 */
function paragraphs_ee_field_widget_settings_summary_alter(&$summary, $context) {
  if (!$context['widget'] instanceof ParagraphsWidget) {
    return;
  }
  if ($context['widget']
    ->getSetting('add_mode') !== 'modal') {
    return;
  }
  $settings = $context['widget']
    ->getThirdPartySettings('paragraphs_ee');
  if (!empty($settings['paragraphs_ee']['dialog_off_canvas'])) {
    $summary[] = t('Use off-canvas dialog');
  }
  $styles = [
    'tiles' => t('Tiles', [], [
      'context' => 'Paragraphs Editor Enhancements',
    ]),
    'list' => t('List', [], [
      'context' => 'Paragraphs Editor Enhancements',
    ]),
  ];
  if (!empty($settings['paragraphs_ee']['dialog_style']) && isset($styles[$settings['paragraphs_ee']['dialog_style']])) {
    $summary[] = t('Display paragraphs in dialog as: %style', [
      '%style' => $styles[$settings['paragraphs_ee']['dialog_style']],
    ], [
      'context' => 'Paragraphs Editor Enhancements',
    ]);
  }
  $easy_access_count = isset($settings['paragraphs_ee']['easy_access_count']) ? $settings['paragraphs_ee']['easy_access_count'] : 2;
  $summary[] = t('Number of easy-access-buttons: @count', [
    '@count' => $easy_access_count,
  ], [
    'context' => 'Paragraphs Editor Enhancements',
  ]);
}

/**
 * Implements hook_field_widget_third_party_settings_form().
 */
function paragraphs_ee_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) {
  $elements = [];
  if (!$plugin instanceof ParagraphsWidget) {
    return $elements;
  }
  $settings_defaults = [
    'dialog_off_canvas' => FALSE,
    'dialog_style' => 'tiles',
    'easy_access_count' => 2,
  ];
  $settings = $plugin
    ->getThirdPartySetting('paragraphs_ee', 'paragraphs_ee', $settings_defaults);

  // Define rule for enabling/disabling options that depend on modal add mode.
  $modal_related_options_rule = [
    ':input[name="fields[' . $field_definition
      ->getName() . '][settings_edit_form][settings][add_mode]"]' => [
      'value' => 'modal',
    ],
  ];
  $elements['paragraphs_ee'] = [
    '#type' => 'fieldgroup',
    '#title' => t('Paragraphs Editor Enhancements'),
    '#attributes' => [
      'class' => [
        'fieldgroup',
        'form-composite',
      ],
    ],
  ];
  $elements['paragraphs_ee']['dialog_off_canvas'] = [
    '#type' => 'checkbox',
    '#title' => t('Use off-canvas instead of modal dialog'),
    '#default_value' => $settings['dialog_off_canvas'] ?? FALSE,
    '#attributes' => [
      'class' => [
        'paragraphs-ee__dialog-off-canvas__option',
      ],
    ],
    '#states' => [
      'enabled' => $modal_related_options_rule,
      'visible' => $modal_related_options_rule,
    ],
  ];
  $elements['paragraphs_ee']['dialog_style'] = [
    '#type' => 'select',
    '#title' => t('Display Paragraphs in dialog as'),
    '#default_value' => $settings['dialog_style'] ?? 'tiles',
    '#attributes' => [
      'class' => [
        'paragraphs-ee__dialog-style__option',
      ],
    ],
    '#options' => [
      'tiles' => t('Tiles', [], [
        'context' => 'Paragraphs Editor Enhancements',
      ]),
      'list' => t('List', [], [
        'context' => 'Paragraphs Editor Enhancements',
      ]),
    ],
    '#states' => [
      'enabled' => $modal_related_options_rule,
      'visible' => $modal_related_options_rule,
    ],
  ];
  $elements['paragraphs_ee']['easy_access_count'] = [
    '#type' => 'number',
    '#title' => t('Number of easy-access-buttons', [], [
      'context' => 'Paragraphs Editor Enhancements',
    ]),
    '#default_value' => isset($settings['easy_access_count']) ? $settings['easy_access_count'] : 2,
    '#min' => 0,
    '#description' => t('Set the number of buttons available to directly add a paragraph type without using the modal/off-canvas.', [], [
      'context' => 'Paragraphs Editor Enhancements',
    ]),
    '#attributes' => [
      'class' => [
        'paragraphs-ee__easy-access-count__option',
      ],
    ],
    '#states' => [
      'enabled' => $modal_related_options_rule,
      'visible' => $modal_related_options_rule,
    ],
  ];
  return $elements;
}