mercury_editor-2.0.x-dev/mercury_editor.module

mercury_editor.module
<?php

/**
 * @file
 * Define the hook implementations for the Mercury Editor module.
 */

use Drupal\Core\Url;
use Drupal\Core\Render\Element;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\mercury_editor\MercuryEditorEntityTypeInfo;
use Drupal\mercury_editor\Entity\MercuryEditorNodeForm;
use Drupal\mercury_editor\Entity\MercuryEditorTermForm;
use Drupal\mercury_editor\Entity\MercuryEditorBlockContentForm;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

/**
 * @file
 * Mercury editor module.
 */

/**
 * Implements hook_library_info_alter().
 *
 * Attaches our mercury_editor dialog libraries where needed.
 */
function mercury_editor_library_info_alter(&$libraries, $extension) {
  if ($extension == 'core' && isset($libraries['drupal.dialog'])) {
    $libraries['drupal.dialog']['dependencies'][] = 'mercury_editor/dialog.drupal';
  }
  if ($extension == 'entity_browser' && isset($libraries['modal_selection'])) {
    $libraries['modal_selection']['dependencies'][] = 'mercury_editor/entity_browser.modal_selection';
  }
  if ($extension == 'layout_paragraphs' && isset($libraries['builder'])) {
    $libraries['builder']['dependencies'][] = 'mercury_editor/dialog.ajax';
  }
  if ($extension == 'image_radios' && isset($libraries['image_radios'])) {
    $libraries['image_radios']['dependencies'][] = 'mercury_editor/image_radios';
  }

  if ($extension == 'gin' && isset($libraries['media_library'])) {
    // When using the gin_toolbar module, we need to add the gin_base and
    // gin_accent libraries as dependencies to the media_library library as
    // they contain CSS custom property definitions that the media library
    // styles rely on. This should probably be fixed in Gin itself.
    if (\Drupal::moduleHandler()->moduleExists('gin_toolbar')) {
      $libraries['media_library']['dependencies'][] = 'gin/gin_base';
      $libraries['media_library']['dependencies'][] = 'gin/gin_accent';
    }
  }
}

/**
 * Implements hook_ajax_render_alter().
 *
 * Replaces Drupal's ajax Dialog commands with MercuryDialog commands.
 */
function mercury_editor_ajax_render_alter(array &$data): void {
  $route_name = \Drupal::routeMatch()->getRouteName() ?? '';
  if (str_contains($route_name, 'mercury_editor') || \Drupal::request()->query->has('me_id')) {
    foreach ($data as &$command) {
      if ($command['command'] == 'openDialog') {
        $command['command'] = 'openMercuryDialog';
      }
      if ($command['command'] == 'closeDialog') {
        $command['command'] = 'closeMercuryDialog';
      }
    }
  }
}

/**
 * Implements hook_preprocess().
 *
 * @see https://www.drupal.org/project/mercury_editor/issues/3379180
 * @see contextual_preprocess()
 */
function mercury_editor_preprocess(array &$variables, $hook, $info) {
  if (empty($variables['title_suffix']['contextual_links'])) {
    return;
  }
  if (!\Drupal::service('mercury_editor.context')->isPreview()) {
    return;
  }
  // Determine the primary theme function argument.
  if (!empty($info['variables'])) {
    $keys = array_keys($info['variables']);
    $key = $keys[0];
  }
  elseif (!empty($info['render element'])) {
    $key = $info['render element'];
  }
  if (!empty($key) && isset($variables[$key])) {
    $element = $variables[$key];
  }

  if (isset($element) && is_array($element) && !empty($element['#contextual_links'])) {
    // Disable contextual links on the preview route.
    $variables['title_suffix']['#cache']['contexts'][] = 'route.name.is_mercury_editor_preview';
    unset($variables['title_suffix']['contextual_links']);
  }
}

/**
 * Implements hook_preprocess_layout_paragraphs_builder_controls().
 *
 * Set width on component delete modals.
 */
function mercury_editor_preprocess_layout_paragraphs_builder_controls(&$variables) {

  /** @var \Drupal\layout_paragraphs\LayoutParagraphsLayout $layout */
  $layout = $variables['layout_paragraphs_layout'];
  $component = $layout->getComponentByUuid($variables['uuid']);
  $paragraph = $component->getEntity();
  $paragraph_type = $paragraph->bundle();

  if (Drupal::service('mercury_editor.context')->isPreview()) {

    $uuid = $variables['uuid'];
    $layout = $variables['layout_paragraphs_layout'];
    $component = $layout->getComponentByUuid($uuid);
    $type = $component->getEntity()->getParagraphType();

    // Alters the edit link.
    $edit_dialog_options = json_decode($variables['controls']['edit_link']['#attributes']['data-dialog-options']);
    $edit_dialog_options->height = 'max-content';
    $edit_dialog_options->resizable = TRUE;
    $edit_dialog_options = Drupal::service('mercury_editor.dialog')->dialogSettings([
      'layout' => $layout,
      'dialog' => $paragraph_type . '_form',
      'dock' => 'right',
    ]);

    $variables['controls']['edit_link']['#attributes']['data-dialog-options'] = json_encode($edit_dialog_options);
    $variables['controls']['edit_link']['#attributes']['title'] = t('Edit :type', [':type' => $type->label()]);
    _mercury_editor_replace_layout_paragraphs_routes($variables['controls']['edit_link']['#url']);

    // Alters the delete link.
    $delete_dialog_options = Drupal::service('mercury_editor.dialog')->dialogSettings([
      'layout' => $layout,
      'dialog' => 'delete_form',
    ]);
    $variables['controls']['delete_link']['#attributes']['data-dialog-options'] = json_encode($delete_dialog_options);
    $variables['controls']['delete_link']['#attributes']['title'] = t('Delete :type', [':type' => $type->label()]);
    _mercury_editor_replace_layout_paragraphs_routes($variables['controls']['delete_link']['#url']);

    // Alters the duplicate link.
    _mercury_editor_replace_layout_paragraphs_routes($variables['controls']['duplicate_link']['#url']);
  }
}

/**
 * Implements hook_preprocess_layout_paragraphs_insert_component_btn().
 *
 * Alters the "insert component" buttons to use window.postMessage instead
 * of directly invoking an Ajax action.
 */
function mercury_editor_preprocess_layout_paragraphs_insert_component_btn(&$variables) {
  if (!Drupal::service('mercury_editor.context')->isPreview()) {
    return;
  }
  _mercury_editor_replace_layout_paragraphs_routes($variables['url']);
  $old_options = json_decode($variables['attributes']['data-dialog-options'], TRUE);
  $dialog_options = [
    'target' => $old_options['target'],
  ] + Drupal::service('mercury_editor.dialog')->dialogSettings(['dialog' => 'component_menu']);
  // $dialog_options['dock'] = 'right';
  $dialog_options['moveable'] = 'true';
  $variables['attributes']['data-dialog-options'] = json_encode($dialog_options);
}

/**
 * Helper function to replace layout paragraph routes with mercury editor ones.
 *
 * @param \Drupal\Core\Url $url
 *   The url object.
 */
function _mercury_editor_replace_layout_paragraphs_routes(Url &$url) {
  $route_name = $url->getRouteName();
  $parameters = $url->getRouteParameters();
  // Compatibility with layout_paragraphs 2.1.x.
  if (!empty($parameters['paragraph_type_id']) && empty($parameters['paragraph_type'])) {
    $parameters['paragraph_type'] = $parameters['paragraph_type_id'];
  }
  $options = $url->getOptions();
  $route_name = str_replace('layout_paragraphs.', 'mercury_editor.', $route_name);
  $url = Url::fromRoute($route_name, $parameters, $options);
}

/**
 * Implements hook_theme_suggestions_page_alter().
 */
function mercury_editor_theme_suggestions_page_alter(array &$suggestions, array $variables) {
  $request = \Drupal::requestStack()->getCurrentRequest();
  $exception = $request->attributes->get('exception');

  // If this request is an exception response, remove ME-specific suggestions.
  if ($exception instanceof HttpExceptionInterface) {
    // Prune all suggestions starting with "page__mercury_editor".
    $suggestions = array_filter($suggestions, function ($suggestion) {
      return !str_starts_with($suggestion, 'page__mercury_editor');
    });
  }
  // Adds template suggestions for the mercury editor preview screen.
  // This allows themers to create a specific page template for the preview
  // if needed.
  if (\Drupal::service('mercury_editor.context')->isPreview()) {
    $mercury_editor_entity = \Drupal::service('mercury_editor.context')->getEntity();
    $suggestions[] = 'page__' . $mercury_editor_entity->bundle();
  }
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 *
 * Use the mercury_editor theme suggestion for the component menu.
 */
function mercury_editor_theme_suggestions_layout_paragraphs_builder_component_menu_alter(array &$suggestions, array $variables) {
  $route_name = \Drupal::routeMatch()->getRouteName();
  if ($route_name === 'mercury_editor.builder.choose_component') {
    $suggestions[] = 'layout_paragraphs_builder_component_menu__mercury_editor';
  }
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 *
 * Since node forms are set to use a specialized template in the node module,
 * we need to set the template suggestion here.
 */
function mercury_editor_theme_suggestions_node_edit_form_alter(array &$suggestions, array $variables) {
  if (strpos($variables['form']['#form_id'], '_mercury_editor_form') !== FALSE) {
    $suggestions[] = 'mercury_editor_entity_form';
  }
}

/**
 * Implements hook_entity_build_defaults_alter().
 *
 * Add cache context for mercury editor preview screen.
 */
function mercury_editor_entity_build_defaults_alter(array &$build, EntityInterface $entity, $view_mode) {
  $me_entity_types = \Drupal::config('mercury_editor.settings')->get('bundles');
  if (!empty($me_entity_types[$entity->getEntityTypeId()])) {
    $build['#cache']['contexts'][] = 'route.name.is_mercury_editor_preview';
  }
}

/**
 * Implements hook_block_build_alter().
 *
 * Add cache context for mercury editor preview screen.
 */
function mercury_editor_block_build_alter(array &$build, BlockPluginInterface $block) {
  $me_entity_types = \Drupal::config('mercury_editor.settings')->get('bundles');
  if (empty($me_entity_types['block_content'])) {
    return;
  }
  $build['#cache']['contexts'][] = 'route.name.is_mercury_editor_preview';
}

/**
 * Implements hook_build_alter().
 */
function mercury_editor_entity_display_build_alter(&$build, $context) {

  if (empty($context['entity'])) {
    return;
  }
  if (!\Drupal::service('mercury_editor.context')->isPreview()) {
    return;
  }
  $mercury_editor_entity = \Drupal::service('mercury_editor.context')->getEntity();
  if (empty($mercury_editor_entity)) {
    return;
  }
  if ($context['entity']->uuid() != $mercury_editor_entity->uuid()) {
    return;
  }

  // Adds a data attribute to the entity wrapper for Mercury Editor.
  $build['#attributes']['data-me-edit-screen-key'] = $mercury_editor_entity->uuid();
  $build['#attributes']['class'][] = 'is-mercury-edit-mode';

  // Turns on Layout Paragraphs builder for Mercury Editor LP fields.
  $layout_paragraphs_field_ids = \Drupal::service('mercury_editor.tempstore_repository')
    ->getLayoutParagraphsFieldIds($mercury_editor_entity);
  foreach ($layout_paragraphs_field_ids as $field_name => $lp_key) {
    if (isset($build[$field_name])) {
      $layout = \Drupal::service('layout_paragraphs.tempstore_repository')->getWithStorageKey($lp_key);
      $build[$field_name] = [
        '#type' => 'layout_paragraphs_builder',
        '#layout_paragraphs_layout' => $layout,
        '#is_translating' => $layout->getSetting('is_translating'),
      ];
    }
  }

}

/**
 * Implements hook_theme().
 */
function mercury_editor_theme() {
  return [
    'page__mercury_editor' => [
      'base hook' => 'page',
    ],
    'mercury_editor_entity_form' => [
      'render element' => 'form',
    ],
    'layout_paragraphs_builder_component_menu__mercury_editor' => [
      'base hook' => 'layout_paragraphs_builder_component_menu',
    ],
  ];
}

/**
 * Implements hook_block_alter().
 *
 * Swap out the Field Block class with our own to detect when in Mercury Editor
 * preview mode.
 */
function mercury_editor_block_alter(&$definitions) {
  $matches = array_filter($definitions, function ($definition) {
    return !empty($definition['default_formatter']) && $definition['default_formatter'] == 'entity_reference_revisions_entity_view';
  });
  foreach (array_keys($matches) as $block_id) {
    $definitions[$block_id]['default_formatter'] = 'mercury_editor_entity_reference_revisions_entity_view';
    $definitions[$block_id]['class'] = 'Drupal\mercury_editor\MEFieldBlock';
  }
}

/**
 * Implements hook_preprocess_html().
 *
 * Removes admin toolbar for Mercury Editor edit screens.
 */
function mercury_editor_preprocess_html(&$variables) {
  if (\Drupal::routeMatch()->getRouteObject()->getOption('_hide_admin_toolbar')) {
    // Check if toolbar is enabled.
    // Remove toolbar from the page.
    unset($variables['page_top']['toolbar']);

    // Remove toolbar classes from the body.
    $variables['attributes']['class'] = array_filter($variables['attributes']['class'] ?? [], function ($value) {
      return !empty($value) && strpos($value, 'toolbar-') !== 0;
    });
    // Check if gin_toolbar is enabled.
    if (\Drupal::moduleHandler()->moduleExists('gin_toolbar')) {
      // Remove toolbar classes from the body.
      $variables['attributes']['class'] = array_filter($variables['attributes']['class'] ?? [], function ($value) {
        return !empty($value) && strpos($value, 'gin--') !== 0;
      });
      // Remove toolbar attributes from the body.
      $variables['attributes'] = array_filter($variables['attributes'] ?? [], function ($value) {
        return strpos($value, 'data-gin') !== 0;
      }, ARRAY_FILTER_USE_KEY);
    }
    // Check if adminimal_admin_toolbar is enabled.
    if (\Drupal::moduleHandler()->moduleExists('adminimal_admin_toolbar')) {
      // Remove toolbar classes from the body.
      $variables['attributes']['class'] = array_filter($variables['attributes']['class'] ?? [], function ($value) {
        return strpos($value, 'adminimal-admin-toolbar') !== 0;
      });
    }

    // Check if navigation is enabled.
    if (\Drupal::moduleHandler()->moduleExists('navigation')) {
      // Remove navigation from the page.
      unset($variables['page_top']['navigation']);
    }
  }
}

/**
 * Implements hook_preprocess_node().
 */
function mercury_editor_preprocess_node(&$variables) {
  $mercury_editor_entity = \Drupal::service('mercury_editor.context')->getEntity();
  if ($mercury_editor_entity && $variables['node']->uuid() == $mercury_editor_entity->uuid()) {
    $variables['page'] = TRUE;
  }
}

/**
 * Implements hook_preprocess_page().
 *
 * Adds a select list to the page for choosing from a list of mobile presets.
 */
function mercury_editor_preprocess_page__mercury_editor(&$variables) {
  // Get mobile presets from the Mercury Editor settings.
  $mobile_presets = \Drupal::config('mercury_editor.settings')->get('mobile_presets') ?? [];
  if ($mobile_presets) {
    // Parse mobile presets into an array.
    $mobile_presets_names = array_map(function ($preset) {
      return $preset['width'] . 'x' . $preset['height'];
    }, $mobile_presets);
    $mobile_presets_values = array_map(function ($preset) {
      return $preset['name'] . ' (' . $preset['width'] . 'x' . $preset['height'] . ')';
    }, $mobile_presets);
    $mobile_presets_options = array_combine($mobile_presets_names, $mobile_presets_values);
    $variables['mobile_presets'] = [
      '#type' => 'select',
      '#options' => $mobile_presets_options,
      '#attributes' => [
        'class' => ['me-mobile-presets'],
        'style' => 'display: none',
      ],
    ];
  }
  // Get uuid from the route.
  $mercury_editor_entity = \Drupal::service('mercury_editor.context')->getEntity();
  if ($mercury_editor_entity) {
    $variables['preview_url'] = Url::fromRoute('entity.' . $mercury_editor_entity->getEntityTypeId() . '.mercury_editor_preview', [
      $mercury_editor_entity->getEntityTypeId() => $mercury_editor_entity->uuid(),
    ])->toString();
    $variables['component_outline_url'] = Url::fromRoute('mercury_editor.component_outline', [
      'mercury_editor_entity' => $mercury_editor_entity->uuid(),
    ])->toString();
    // Get the entity's canonical url.
    $variables['uuid'] = $mercury_editor_entity->uuid();
    $variables['entity_type'] = $mercury_editor_entity->getEntityTypeId();
    $destination = \Drupal::request()->query->get('destination');
    $variables['exit_url'] = Url::fromRoute('mercury_editor.exit', [
      'mercury_editor_entity' => $mercury_editor_entity->uuid(),
      $destination ? ['destination' => $destination] : NULL,
    ]);
  }
  $variables['#attached']['library'][] = 'core/drupal.dialog';

  // Set the default tray width.
  $tray_width = \Drupal::config('mercury_editor.settings')->get('dialog_tray_width');
  if ($tray_width) {
    $variables['#attached']['drupalSettings']['mercuryEditor']['defaultWidth'] = $tray_width;
  }
}

/**
 * Implements hook_preprocess_page().
 */
function mercury_editor_preprocess_page(&$variables) {
  // Add the rollover padding settings.
  $rollover_padding_block = \Drupal::config('mercury_editor.settings')->get('rollover_padding_block');
  if ($rollover_padding_block) {
    $variables['#attached']['drupalSettings']['mercuryEditor']['rolloverPaddingBlock'] = $rollover_padding_block;
  }
  $rollover_padding_inline = \Drupal::config('mercury_editor.settings')->get('rollover_padding_inline');
  if ($rollover_padding_inline) {
    $variables['#attached']['drupalSettings']['mercuryEditor']['rolloverPaddingInline'] = $rollover_padding_inline;
  }
}

/**
 * Implements hook_preprocess_taxonomy_term().
 */
function mercury_editor_preprocess_taxonomy_term(&$variables) {
  $mercury_editor_entity = \Drupal::service('mercury_editor.context')->getEntity();
  if ($mercury_editor_entity && $variables['term']->uuid() == $mercury_editor_entity->uuid()) {
    $variables['page'] = TRUE;
  }
}

/**
 * Implements hook_theme_registry_alter().
 *
 * Make sure this module's hook_preprocess_html implementation runs last.
 */
function mercury_editor_theme_registry_alter(&$theme_registry) {
  $index = array_search('mercury_editor_preprocess_html', $theme_registry['html']['preprocess functions']);
  unset($theme_registry['html']['preprocess functions'][$index]);
  $theme_registry['html']['preprocess functions'][50] = 'mercury_editor_preprocess_html';
}

/**
 * Implements hook_page_attachments().
 */
function mercury_editor_page_attachments(&$attachments) {
  if (\Drupal::service('mercury_editor.context')->isPreview()) {
    $attachments['#attached']['library'][] = 'mercury_editor/preview_screen';
  }
  _mercury_editor_content_translation_page_attachments($attachments);
}

/**
 * Copies and modifies the content_translation_page_attachments function.
 *
 * It is modified to check entities for an id before trying to create a link to
 * them.
 */
function _mercury_editor_content_translation_page_attachments(&$page) {
  $cache = CacheableMetadata::createFromRenderArray($page);
  $route_match = \Drupal::routeMatch();

  // If the current route has no parameters, return.
  if (!($route = $route_match->getRouteObject()) || !($parameters = $route->getOption('parameters'))) {
    return;
  }
  $is_front = \Drupal::service('path.matcher')->isFrontPage();

  // Determine if the current route represents an entity.
  foreach ($parameters as $name => $options) {
    if (!isset($options['type']) || !str_starts_with($options['type'], 'entity:')) {
      continue;
    }

    $entity = $route_match->getParameter($name);
    if ($entity instanceof ContentEntityInterface && $entity->id() && $entity->hasLinkTemplate('canonical')) {
      // Current route represents a content entity. Build hreflang links.
      foreach ($entity->getTranslationLanguages() as $language) {
        // Skip any translation that cannot be viewed.
        $translation = $entity->getTranslation($language->getId());
        $access = $translation->access('view', NULL, TRUE);
        $cache->addCacheableDependency($access);
        if (!$access->isAllowed()) {
          continue;
        }
        if ($is_front) {
          // If the current page is front page, do not create hreflang links
          // from the entity route, just add the languages to root path.
          $url = Url::fromRoute('<front>', [], [
            'absolute' => TRUE,
            'language' => $language,
          ])->toString();
        }
        // Create links for the entity path.
        else {
          $url = $entity->toUrl('canonical')
            ->setOption('language', $language)
            ->setAbsolute()
            ->toString();
        }
        $page['#attached']['html_head_link'][] = [
          [
            'rel' => 'alternate',
            'hreflang' => $language->getId(),
            'href' => $url,
          ],
        ];
      }
    }
    // Since entity was found, no need to iterate further.
    break;
  }
  // Apply updated caching information.
  $cache->applyTo($page);
}

/**
 * Implements hook_page_attachments_alter().
 */
function mercury_editor_page_attachments_alter(array &$attachments) {
  if (
    \Drupal::service('mercury_editor.context')->isPreview() ||
    \Drupal::routeMatch()->getRouteObject()->getOption('_mercury_editor_route')
  ) {
    $theme = \Drupal::theme()->getActiveTheme()->getName();
    if ($theme !== 'gin') {
      // Remove all gin assets.
      $attachments['#attached']['library'] = array_filter($attachments['#attached']['library'], function ($library) {
        return strpos($library, 'gin/') !== 0;
      });
    }
    // Remove Gin Toolbar assets.
    if (in_array('gin/gin_toolbar', $attachments['#attached']['library'])) {
      $index = array_search('gin/gin_toolbar', $attachments['#attached']['library']);
      unset($attachments['#attached']['library'][$index]);
    }
  }
}

/**
 * Implements hook_element_info_alter().
 */
function mercury_editor_element_info_alter(array &$types) {
  // Attach layout_select overrides.
  if (isset($types['layout_select'])) {
    $types['layout_select']['#attached']['library'][] = 'mercury_editor/layout_select';
  }
}

/**
 * Implements hook_preprocess_layout_paragraphs_component_menu().
 */
function mercury_editor_preprocess_layout_paragraphs_builder_component_menu(&$variables) {

  $route_name = \Drupal::routeMatch()->getRouteName();
  if ($route_name !== 'mercury_editor.builder.choose_component') {
    return;
  }

  foreach (['layout', 'content'] as $category) {
    if (isset($variables['types'][$category])) {
      foreach ($variables['types'][$category] as $key => $type) {
        if (!empty($variables['types'][$category][$key]['url_object'])) {
          _mercury_editor_replace_layout_paragraphs_routes($variables['types'][$category][$key]['url_object']);
          $variables['types'][$category][$key]['url'] = $variables['types'][$category][$key]['url_object']->toString();
        }
      }
    }
  }

  $groups = Drupal::config('mercury_editor.menu.settings')->get('groups');
  $types = $variables['types']['content'] + $variables['types']['layout'];
  $variables['count'] = count($types);

  if (!empty($groups)) {
    $groups_array = Yaml::decode($groups);
    $variables['groups'] = [];
    foreach ($groups_array as $name => &$group) {
      $variables['groups'][$name] = [
        'items' => array_filter(array_map(function ($component) use ($types) {
            return $types[$component] ?? FALSE;
        }, array_combine($group['components'], $group['components']))),
        'label' => $group['label'],
      ];
    }
    $default_group = key(array_filter($groups_array, function ($group) {
      return !empty($group['default']);
    })) ?? 'default';

    $variables['groups'] = array_filter($variables['groups'], function ($group, $id) use ($default_group) {
      return count($group['items']) || $id == $default_group;
    }, ARRAY_FILTER_USE_BOTH);

    $orphaned_types = array_filter(array_keys($types), function ($type) use ($variables) {
      foreach ($variables['groups'] as $group) {
        if (!empty($group['items'][$type])) {
          return FALSE;
        }
      }
      return TRUE;
    });
    foreach ($orphaned_types as $type) {
      $variables['groups'][$default_group]['items'][$type] = $types[$type];
    }
  }
  else {
    $variables['groups'] = [
      'layout' => [
        'items' => $variables['types']['layout'],
        'label' => t('Layout'),
      ],
      'content' => [
        'items' => $variables['types']['content'],
        'label' => t('Content'),
      ],
    ];
  }

  $template_components = array_filter($types, function ($group, $id) {
    return strpos($id, 'me_template_') === 0;
  }, ARRAY_FILTER_USE_BOTH);
  if (!empty($template_components)) {
    $variables['groups']['templates'] = [
      'items' => $template_components,
      'label' => t('Templates'),
    ];

    // Remove template components from the other groups.
    foreach ($variables['groups'] as $key => &$group) {
      if ($key == 'templates') {
        continue;
      }
      $variables['groups'][$key]['items'] = array_filter($variables['groups'][$key]['items'], function ($component) use ($template_components) {
        return !in_array($component, $template_components);
      });
    }
  }

  $variables['#attached']['library'][] = 'mercury_editor/menu';
  $variables['#attached']['library'][] = 'mercury_editor/lpb_component_list';
}

/**
 * Implements hook_preprocess_layout_paragraphs_builder().
 */
function mercury_editor_preprocess_layout_paragraphs_builder(&$variables) {
  $variables['#attached']['library'][] = 'mercury_editor/mercury_editor';
}

/**
 * Implements hook_toolbar_alter().
 */
function mercury_editor_toolbar_alter(&$items) {
  if (isset($items['primary_tasks'])) {
    $items['primary_tasks']['#weight'] = -1000;
    $classes =& $items['primary_tasks']['tray']['toolbar_actions']['#attributes']['class'];
    $classes[] = 'toolbar-menu';
    if (($key = array_search('toolbar-menu-administration', $classes)) !== FALSE) {
      unset($classes[$key]);
    }
  }
}

/**
 * Implements hook_preprocess_field().
 */
function mercury_editor_preprocess_field(&$variables) {
  // If in the mercury editor preview route.
  if (!\Drupal::service('mercury_editor.context')->isPreview()) {
    return;
  }
  if (isset($variables['entity_type']) && isset($variables['element']['#object'])) {
    $sync_changes_value = $variables['entity_type'] . '/' .
      $variables['element']['#object']->uuid() . '/' . $variables['field_name'];
    $variables['attributes']['data-sync-changes'] = $sync_changes_value;

    // Add a specific class for title fields to make them easier to target in AJAX updates.
    $entity = $variables['element']['#object'];
    $field_name = $variables['field_name'];
    $entity_type = $entity->getEntityType();

    // Check if this is a title field.
    $title_fields = ['title', 'name', 'label'];
    $is_title_field = in_array($field_name, $title_fields);

    // Also check if it's the entity type's defined label field.
    if (!$is_title_field && $entity_type->hasKey('label')) {
      $label_field = $entity_type->getKey('label');
      $is_title_field = ($field_name === $label_field);
    }

    if ($is_title_field) {
      $variables['attributes']['class'][] = 'me-entity-title';
      $variables['attributes']['data-me-entity-title'] = $entity->uuid();
    }
  }
}

/**
 * Implements hook_entity_type_build().
 */
function mercury_editor_entity_type_build(array &$entity_types) {
  $entity_forms = [
    'node' => MercuryEditorNodeForm::class,
    'taxonomy_term' => MercuryEditorTermForm::class,
    'block_content' => MercuryEditorBlockContentForm::class,
  ];

  foreach ($entity_forms as $entity_type => $form_class) {
    if (isset($entity_types[$entity_type]) && $entity_types[$entity_type] instanceof EntityTypeInterface) {
      $entity_types[$entity_type]->setFormClass('mercury_editor', $form_class);
    }
  }
}

/**
 * Implements hook_entity_prepare_form().
 */
function mercury_editor_entity_prepare_form(EntityInterface $entity, $operation, FormStateInterface $form_state) {
  // If content moderation module is enabled.
  if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
    \Drupal::service('class_resolver')
      ->getInstanceFromDefinition(MercuryEditorEntityTypeInfo::class)
      ->entityPrepareForm($entity, $operation, $form_state);
  }
}

/**
 * Implements hook_form_alter().
 */
function mercury_editor_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
    \Drupal::service('class_resolver')
      ->getInstanceFromDefinition(MercuryEditorEntityTypeInfo::class)
      ->formAlter($form, $form_state, $form_id);
  }
}

/**
 * Implements hook_module_implements_alter().
 *
 * Removes content_moderation's hook_form_alter and hook_entity_prepare_form so
 * they can be replaced with Mercury Editor's implementations, which check for
 * the "mercury_editor" form operation.
 *
 * Removes content_translation's hook_page_attachments implementation to prevent
 * throwing and error when an entity doesn't yet have an id (i.e. when a new
 * entity is rendered in the Mercury Editor preview).
 *
 * @see content_translation_page_attachments()
 * @see content_moderation_form_alter()
 * @see content_moderation_entity_prepare_form()
 */
function mercury_editor_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'page_attachments') {
    if (isset($implementations['content_translation'])) {
      unset($implementations['content_translation']);
    }
  }
  if ($hook == 'form_alter' || $hook == 'entity_prepare_form') {
    if (isset($implementations['content_moderation'])) {
      unset($implementations['content_moderation']);
    }
  }
}

/**
 * Implements hook_preprocess_layout_paragraphs_builder_formatter().
 */
function mercury_editor_preprocess_layout_paragraphs_builder_formatter(&$variables) {
  $variables['#attached']['library'][] = 'mercury_editor/mercury_editor';
  $variables['#attached']['library'][] = 'mercury_editor/field_formatter';
}

/**
 * Implements hook_form_layout_paragraphs_builder_form_alter().
 */
function mercury_editor_form_layout_paragraphs_builder_form_alter(&$form, $form_state) {
  if (empty($form['#attached']['library'])) {
    $form['#attached']['library'] = [];
  }
  $form['#attached']['library'][] = 'mercury_editor/mercury_editor';
}

/**
 * Implements hook_layout_paragraphs_component_form_alter().
 */
function mercury_editor_form_layout_paragraphs_component_form_alter(&$form, $form_state) {

  $form['uuid'] = [
    '#type' => 'hidden',
    '#value' => $form['#paragraph']->uuid(),
  ];

  $form['actions']['#attributes']['class'][] = 'me-form-actions';
  $form['#attached']['library'][] = 'mercury_editor/mercury_editor';
  $form['#attached']['library'][] = 'mercury_editor/component_form';
  $form['tabs'] = [
    '#type' => 'radios',
    '#options' => [],
    '#weight' => -1000,
    '#wrapper_attributes' => [
      'class' => ['me-horizontal-tab-radios', 'me-tabs'],
    ],
    '#after_build' => ['mercury_editor_after_build_radios'],
  ];
  if (isset($form['layout_paragraphs'])) {
    $form['layout_paragraphs']['#process'][] = 'mercury_editor_layout_paragraphs_form_process';
  }
  $form['#after_build'][] = 'mercury_editor_after_build_form';

  // Add tab group to custom fields.
  /** @var Drupal\mercury_editor\Form\EditComponentForm $form_object */
  $form_object = $form_state->getFormObject();
  $paragraph = $form_object->getParagraph();

  /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
  $entity_field_manager = \Drupal::service('entity_field.manager');
  $field_definitions = $entity_field_manager->getFieldDefinitions($paragraph->getEntityTypeId(), $paragraph->bundle());

  $field_keys = array_filter(
    Element::children($form),
    function ($key) use ($field_definitions, $form) {
      return isset($field_definitions[$key]) && Element::isVisibleElement($form[$key]);
    });
  if (count($field_keys)) {
    $form['tabs']['#options']['content'] = t('Content');
    $form['tabs']['#default_value'] = 'content';
    foreach ($field_keys as $key) {
      $form[$key]['#attributes']['class'][] = 'me-tab-group';
      $form[$key]['#attributes']['class'][] = 'me-tab-group--content';
    }
  }

  // Add tab class to field group elements too.
  if (isset($form['#fieldgroups'])) {
    foreach ($form['#fieldgroups'] as $field_group) {
      $field_group->format_settings["classes"] = "me-tab-group me-tab-group--content";
    }
  }

  // @todo Make this work for all behaviors, not just styles.
  if (isset($form['behavior_plugins'])) {
    $form['tabs']['#options']['styles'] = t('Styles');
    $form['behavior_plugins']['#type'] = 'container';
    $form['behavior_plugins']['#attributes']['class'][] = 'me-tab-group';
    $form['behavior_plugins']['#attributes']['class'][] = 'me-tab-group--styles';
  }
}

/**
 * Processes the layout paragraphs form element.
 *
 * @param array $element
 *   The form element.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state.
 *
 * @return array
 *   The processed element.
 */
function mercury_editor_layout_paragraphs_form_process(array $element, FormStateInterface $form_state, &$form) {
  if (!empty($element['layout'])) {
    if (count($element['layout']['#options']) === 1) {
      $element['layout']['#access'] = FALSE;
      $element['layout']['#default_value'] = array_key_first($element['layout']['#options']);
    }
    else {
      $form['tabs']['#options']['layout'] = t('Layout');
      $form['tabs']['#default_value'] = 'layout';
      $element['layout']['#prefix'] = '<div class="me-tab-group me-tab-group--layout">';
      $element['layout']['#suffix'] = '</div>';
    }
  }

  if (!empty($element['config'])) {
    // @todo See if there is a better way to update the weight of this element
    // to come after the layout tab, rather than unsetting and re-adding it.
    unset($form['tabs']['#options']['styles']);
    $form['tabs']['#options']['styles'] = t('Styles');
    $element['config']['#type'] = 'container';
    $element['config']['#prefix'] = '<div class="me-tab-group me-tab-group--styles ">';
    $element['config']['#suffix'] = '</div>';
  }
  return $element;
}

/**
 * After build callback for adding classes to radio tabs.
 */
function mercury_editor_after_build_radios($element, $form_state) {
  foreach (Element::children($element) as $key) {
    $element[$key]['#wrapper_attributes']['class'][] = 'horizontal-tab--' . $key;
  }
  return $element;
}

/**
 * After callback for attaching the horizontal tabs library.
 */
function mercury_editor_after_build_form($form, $form_state) {
  if (isset($form['tabs']) && count($form['tabs']['#options']) < 2) {
    $form['tabs']['#access'] = FALSE;
  }
  else {
    $form['#attached']['library'][] = 'mercury_editor/horizontal_tabs';
  }
  return $form;
}

/**
 * Implements hook_layout_paragraphs_delete_component_form_alter().
 */
function mercury_editor_form_layout_paragraphs_delete_component_form_alter(&$form, $form_state) {
  $form['actions']['#attributes']['class'][] = 'me-form-actions';
  $form['#attached']['library'][] = 'mercury_editor/lpb_component_delete_form';
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc