You are here

gutenberg.module in Gutenberg 8.2

Same filename and directory in other branches
  1. 8 gutenberg.module

Provides integration with the Gutenberg editor.

File

gutenberg.module
View source
<?php

/**
 * @file
 * Provides integration with the Gutenberg editor.
 */
use Drupal\Component\Utility\Html;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\gutenberg\Controller\UtilsController;
use Drupal\gutenberg\MappingFieldsHelper;
use Drupal\image\Entity\ImageStyle;
use Drupal\media\MediaForm;
use Drupal\node\NodeInterface;
use Drupal\views\ViewExecutable;

// <editor-fold desc="Gutenberg autogenerated code.">
define('GUTENBERG_JS_VERSION', '8.4.0');
define('GUTENBERG_JS_GIT_COMMIT', '83f36e40d7a7ab0b2b9df7ea1036abeb15122f55');

// </editor-fold>

/**
 * Implements hook_theme().
 */
function gutenberg_theme() {
  $templates = [
    'page__node__edit__gutenberg' => [
      'template' => 'page--node--edit--gutenberg',
    ],
    'page__node__add__gutenberg' => [
      'template' => 'page--node--add--gutenberg',
    ],
    'node_edit_form__gutenberg' => [
      'template' => 'node-edit-form--gutenberg',
    ],
    // Gutenberg text field.
    'field_gutenberg_text' => [
      'render element' => 'element',
      'file' => 'gutenberg.theme.inc',
    ],
    'gutenberg_block' => [
      'variables' => [
        'block_name' => NULL,
        'block_attributes' => [],
        'block_content' => NULL,
      ],
      'file' => 'gutenberg.theme.inc',
    ],
  ];

  // Generate theme definitions for each module's dynamic block.

  /*
   * By default, Drupal 8 does not include theme suggestions from inside the
   * module in which they were created, so we must add them manually here.
   */
  $base_hook = 'gutenberg_block';

  /** @var \Drupal\gutenberg\GutenbergLibraryManagerInterface $gutenberg_library_manager */
  $gutenberg_library_manager = \Drupal::service('plugin.manager.gutenberg.library');
  foreach ($gutenberg_library_manager
    ->getModuleDefinitions() as $module => $definition) {
    if (empty($definition['dynamic-blocks'])) {

      // No dynamic blocks to declare.
      continue;
    }
    $template_hooks = drupal_find_theme_templates($templates, '.html.twig', drupal_get_path('module', $module) . '/templates');
    foreach ($definition['dynamic-blocks'] as $block_name => $block_theme_definition) {
      $block_name = str_replace('-', '_', $block_name);
      $block_parts = explode('/', $block_name);
      $hook_names = [
        $block_parts[0],
      ];
      if (count($block_parts) === 2) {

        // namespace/blockname.
        $hook_names[] = $block_parts[0] . '__' . $block_parts[1];
      }
      foreach ($hook_names as $hook_name) {
        $theme_hook_name = $base_hook . '__' . $hook_name;
        if (!isset($templates[$theme_hook_name]) && isset($template_hooks[$theme_hook_name])) {

          // Add the module's theme definition if the template exists.
          $template_hooks[$theme_hook_name]['type'] = 'module';
          $templates[$theme_hook_name] = $block_theme_definition + $template_hooks[$theme_hook_name];
        }
      }
    }
  }
  return $templates;
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function gutenberg_theme_suggestions_gutenberg_block(array $variables) {
  $block_name = str_replace('-', '_', $variables['block_name']);
  $block_parts = explode('/', $block_name);
  $suggestions = [];
  $base_hook = 'gutenberg_block__';
  $suggestions[] = $base_hook . $block_parts[0];
  if (count($block_parts) === 2) {

    // namespace/blockname format.
    $suggestions[] = $base_hook . $block_parts[0] . '__' . $block_parts[1];
  }
  return $suggestions;
}

/**
 * Implements hook_form_alter().
 */
function gutenberg_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  /* TODO: Simplify large alter functions like this using specific classes:
   * https://drupal.stackexchange.com/questions/238341
   * @see content_moderation_form_alter()
   */
  $form_object = $form_state
    ->getFormObject();
  if ($form_object instanceof MediaForm && $form_object
    ->getOperation() === 'edit') {
    _gutenberg_media_form_alter($form, $form_state, $form_id);
  }
  if ($form_id == 'node_type_edit_form' || $form_id == 'node_type_add_form') {
    $config = \Drupal::service('config.factory')
      ->getEditable('gutenberg.settings');
    $form['gutenberg'] = [
      '#type' => 'details',
      '#title' => t('Gutenberg experience'),
      '#description' => '',
      '#group' => 'additional_settings',
      '#weight' => 999,
      'enable_gutenberg_experience' => [
        '#type' => 'checkbox',
        '#title' => t('Enable Gutenberg experience'),
        '#description' => t('Turn the node edit form into a full Gutenberg UI experience. At least one field of long text type is necessary.'),
        '#default_value' => $config
          ->get($form['type']['#default_value'] . '_enable_full'),
      ],
    ];
    $form['gutenberg']['categories'] = [
      '#type' => 'hidden',
      '#default_value' => [],
    ];
    $example_code = '<code>
<pre>[
  ["core/heading", {}],
  ["core/paragraph", {"placeholder": "Insert text"}]
]</pre></code>';
    $form['gutenberg']['gutenberg_template'] = [
      '#type' => 'textarea',
      '#description' => t('JSON structure of blocks. Example: @example_code', [
        '@example_code' => Markup::create($example_code),
      ]),
      '#title' => t('Template'),
      '#default_value' => $config
        ->get($form['type']['#default_value'] . '_template'),
      '#states' => [
        'visible' => [
          'input[name="enable_gutenberg_experience"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];

    /*
     * TODO add validation on this.
     *  Enforcing the "None" lock if the template is empty.
     */
    $form['gutenberg']['gutenberg_template_lock'] = [
      '#type' => 'select',
      '#title' => t('Template lock'),
      '#description' => t('<code>All</code> will fully lock the page template, not able to delete, create or move blocks just edit the content.<br/><code>Insert</code> will allow moving blocks.'),
      '#default_value' => $config
        ->get($form['type']['#default_value'] . '_template_lock'),
      '#options' => [
        'none' => t('None'),
        'insert' => t('Insert'),
        'all' => t('All'),
      ],
      '#states' => [
        'visible' => [
          'input[name="enable_gutenberg_experience"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];
    $form['gutenberg']['allowed_blocks_details'] = [
      '#type' => 'details',
      '#title' => t('Allowed Gutenberg blocks'),
      '#states' => [
        'visible' => [
          'input[name="enable_gutenberg_experience"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];
    $settings = UtilsController::getAllowedBlocks();
    $blocks_settings = UtilsController::getBlocksSettings();
    foreach ($settings['categories'] as $category) {
      $category['reference'] = str_replace('/', '-', $category['reference']);
      $form['gutenberg']['categories']['#default_value'][] = $category['reference'];
      $form['gutenberg']['allowed_blocks_details'][$category['reference']] = [
        '#type' => 'fieldset',
        '#title' => $category['name'],
      ];
      $options = [
        $category['reference'] . '/all' => t('All'),
      ];
      foreach ($category['blocks'] as $block) {
        if (!in_array($block['id'], $blocks_settings['blacklist'])) {
          $options[$block['id']] = $block['name'];
        }
      }
      $default_values = array_combine(array_map(function ($block) {
        return $block['id'];
      }, $category['blocks']), array_map(function ($block) {
        return $block['default'] ? $block['id'] : 0;
      }, $category['blocks']));
      $config_values = $config
        ->get($form['type']['#default_value'] . '_allowed_blocks');
      $form['gutenberg']['allowed_blocks_details'][$category['reference']]['allowed_blocks_' . $category['reference']] = [
        '#type' => 'checkboxes',
        '#options' => $options,
        '#default_value' => array_merge($default_values, $config_values ? $config_values : []),
      ];
    }
    $form['gutenberg']['allowed_drupal_blocks_details'] = [
      '#type' => 'details',
      '#title' => t('Allowed Drupal blocks'),
      '#states' => [
        'visible' => [
          'input[name="enable_gutenberg_experience"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];
    $blockManager = \Drupal::service('plugin.manager.block');
    $contextRepository = \Drupal::service('context.repository');

    // Get blocks definition
    // $definitions = $blockManager->getDefinitionsForContexts($contextRepository->getAvailableContexts());
    $definitions = $blockManager
      ->getFilteredDefinitions('block_ui', $contextRepository
      ->getAvailableContexts());
    $groups = $blockManager
      ->getGroupedDefinitions($definitions);
    $form['gutenberg']['categories_drupal'] = [
      '#type' => 'hidden',
      '#default_value' => [],
    ];
    $default_values = array_fill_keys(array_map(function ($key) {
      return str_replace('drupalblock/', '', $key);
    }, $settings['default_drupal_blocks']), TRUE);
    foreach ($groups as $key => $blocks) {
      $group_reference = preg_replace('@[^a-z0-9-]+@', '_', strtolower($key));
      $options = [];
      $input_default_values = [];
      foreach ($blocks as $key_block => $block) {
        if (!in_array('drupalblock/' . $key_block, $blocks_settings['blacklist'])) {
          $options[$key_block] = $block['admin_label'];
          if (isset($default_values[$key_block]) && $default_values[$key_block]) {
            $input_default_values[$key_block] = $key_block;
          }
        }
      }
      if (count($options) > 0) {
        $form['gutenberg']['categories_drupal']['#default_value'][] = $group_reference;
        $form['gutenberg']['allowed_drupal_blocks_details'][$group_reference] = [
          '#type' => 'fieldset',
          '#title' => $key,
        ];
        $config_values = $config
          ->get($form['type']['#default_value'] . '_allowed_drupal_blocks');
        $form['gutenberg']['allowed_drupal_blocks_details'][$group_reference]['allowed_drupal_blocks_' . $group_reference] = [
          '#type' => 'checkboxes',
          '#options' => $options,
          '#default_value' => array_merge($input_default_values, $config_values ? $config_values : []),
        ];
      }
    }
    $form['#attached']['library'][] = 'gutenberg/admin';
    $form['actions']['submit']['#submit'][] = '_gutenberg_node_type_form_submit';
    if (isset($form['actions']['save_continue']['#submit'])) {
      $form['actions']['save_continue']['#submit'][] = '_gutenberg_node_type_form_submit';
    }
  }
}

/**
 * Alters the node form submit.
 *
 * @param array $form
 *   The form definition array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The current state of the form.
 */
function _gutenberg_node_type_form_submit(array $form, FormStateInterface $form_state) {
  $gutenberg_enabled = $form_state
    ->getValue('enable_gutenberg_experience');
  $template = $form_state
    ->getValue('gutenberg_template');
  $template_lock = $form_state
    ->getValue('gutenberg_template_lock');
  $config = \Drupal::service('config.factory')
    ->getEditable('gutenberg.settings');
  $config
    ->set($form_state
    ->getValue('type') . '_enable_full', $gutenberg_enabled)
    ->save();
  if (!$gutenberg_enabled) {
    $config
      ->clear($form_state
      ->getValue('type') . '_allowed_blocks')
      ->save();
    $config
      ->clear($form_state
      ->getValue('type') . '_allowed_drupal_blocks')
      ->save();
    $config
      ->clear($form_state
      ->getValue('type') . '_gutenberg_template')
      ->save();
    $config
      ->clear($form_state
      ->getValue('type') . '_gutenberg_template_lock')
      ->save();
    return;
  }

  // Save template settings.
  $config
    ->set($form_state
    ->getValue('type') . '_template', $template)
    ->save();
  $config
    ->set($form_state
    ->getValue('type') . '_template_lock', $template_lock)
    ->save();

  // Save Gutenberg core blocks settings.
  $categories = explode(' ', $form_state
    ->getValue('categories'));
  $values = [];
  foreach ($categories as $category) {
    $values = array_merge($values, $form_state
      ->getValue('allowed_blocks_' . $category));
  }
  $config
    ->set($form_state
    ->getValue('type') . '_allowed_blocks', $values)
    ->save();

  // Save Drupal blocks settings.
  $categories = explode(' ', $form_state
    ->getValue('categories_drupal'));
  $values = [];
  foreach ($categories as $category) {
    $values = array_merge($values, $form_state
      ->getValue('allowed_drupal_blocks_' . $category));
  }
  $config
    ->set($form_state
    ->getValue('type') . '_allowed_drupal_blocks', $values)
    ->save();
}

/**
 * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
 *
 * Hides mapping fields.
 */
function gutenberg_form_node_form_alter(&$form, FormStateInterface $form_state) {
  $node = $form_state
    ->getFormObject()
    ->getEntity();
  if (!_gutenberg_is_gutenberg_enabled($node)) {

    // Leave early if Gutenberg is not enabled for this entity.
    return;
  }
  $config = \Drupal::service('config.factory')
    ->get('gutenberg.settings');
  $node_type = $node->type
    ->getString();

  // Set template options to global var.
  $gutenberg_template = json_decode($config
    ->get($node_type . '_template'));
  $form['#attached']['drupalSettings']['gutenberg']['template'] = $gutenberg_template;
  $form['#attached']['drupalSettings']['gutenberg']['template-lock'] = $config
    ->get($node_type . '_template_lock');

  /* @var $mapping_fields \Drupal\gutenberg\MappingFieldsHelper */
  $mapping_fields = \Drupal::service('class_resolver')
    ->getInstanceFromDefinition(MappingFieldsHelper::class);
  $mapped_fields = $mapping_fields
    ->getMappedFields($gutenberg_template);
  foreach ($mapped_fields as $item) {
    $form[$item['field']]['#access'] = FALSE;
  }

  /** @var \Drupal\gutenberg\GutenbergLibraryManagerInterface $gutenberg_library_manager */
  $gutenberg_library_manager = \Drupal::service('plugin.manager.gutenberg.library');
  $module_definitions = $gutenberg_library_manager
    ->getModuleDefinitions();
  foreach ($module_definitions as $module_definition) {
    foreach ($module_definition['libraries-edit'] as $library) {
      $form['#attached']['library'][] = $library;
    }
  }
  $theme_support = [];
  $theme_definitions = $gutenberg_library_manager
    ->getActiveThemeDefinitions();
  foreach ($theme_definitions as $theme_definition) {
    foreach ($theme_definition['libraries-edit'] as $library) {
      $form['#attached']['library'][] = $library;
    }
    if (isset($theme_definition['theme-support'])) {

      // Merge only the top level configuration, with the child theme
      // taking precedence.
      $theme_support = $theme_definition['theme-support'] + $theme_support;
    }
  }
  $form['#attached']['drupalSettings']['gutenberg']['theme-support'] = $theme_support;

  // Set available image sizes for editor.
  $styles = ImageStyle::loadMultiple();
  $sizes = [
    [
      'slug' => 'full',
      'name' => t('Original'),
    ],
  ];
  foreach ($styles as $style) {
    $sizes[] = [
      'slug' => $style
        ->getName(),
      'name' => $style
        ->label(),
    ];
  }
  $form['#attached']['drupalSettings']['gutenberg']['image-sizes'] = $sizes;
  $form['#attached']['drupalSettings']['gutenberg']['is-rtl'] = \Drupal::languageManager()
    ->getCurrentLanguage()
    ->getDirection() === LanguageInterface::DIRECTION_RTL;
  $text_fields = UtilsController::getEntityTextFields($node);
  $form['#attributes']['class'][] = 'metabox-base-form';
  $form[$text_fields[0]]['widget'][0]['#format'] = 'gutenberg';
  $form[$text_fields[0]]['#attributes']['class'][] = 'field--gutenberg';

  // Hide the field label.
  $form[$text_fields[0]]['widget'][0]['#title_display'] = 'hidden';

  // Disable the summary field.
  if (isset($form[$text_fields[0]]['widget'][0]['summary'])) {
    $form[$text_fields[0]]['widget'][0]['summary']['#access'] = FALSE;
  }
  foreach ($text_fields as $fieldname) {

    // For the rest of the text fields call after build to remove
    // Gutenberg from text format options.
    if ($text_fields[0] !== $fieldname) {
      $form[$fieldname]['widget']['#after_build'][] = 'gutenberg_form_node_form_after_build';
    }
  }

  // Let's move the remaining fields to a "special"
  // form group that can be used later by JS to move to
  // Gutenberg's sidebar.
  $form['metabox_fields'] = [
    '#type' => 'details',
    '#access' => TRUE,
    '#title' => t('More settings'),
    '#weight' => 99,
    // Group fallback in case JS fails to move to metaboxes.
    '#group' => 'advanced',
  ];

  // Some other module might have already init this container.
  if (!isset($form['additional_fields'])) {
    $form['additional_fields'] = [
      '#type' => 'container',
      '#title' => 'Additional',
      '#weight' => -100,
    ];
  }

  // Move title to Published/meta pane.
  $form['title']['#group'] = 'meta';

  // Move status to Published/meta pane.
  $form['status']['#group'] = 'meta';

  // Move langcode to Published/meta pane.
  if (isset($form['langcode'])) {
    $form['langcode']['#group'] = 'meta';
  }
  $excluded_fields = [
    'status',
    'title',
    'uid',
    'created',
    'changed',
    'promote',
    'sticky',
    'path',
    'comment',
    'revision_log',
    'langcode',
  ];

  /*
   * Rationale behind this "messy" algo:
   * If there's any details fieldset on the form, add it to a special array
   * and then, on the form after build, add its #id to a JS array.
   * For any other type of fields, group them on the metabox_fields fieldset.
   * This fieldset will also move to metaboxes area.
   */
  $metabox_has_fields = FALSE;
  $fields_with_details = [];
  $field_names = UtilsController::getEntityFieldNames($node);
  foreach ($field_names as $value) {
    if (array_key_exists($value, $form) && $value !== $text_fields[0] && !in_array($value, $excluded_fields)) {
      if (isset($form[$value]['widget']) && isset($form[$value]['widget'][0]) && isset($form[$value]['widget'][0]['#type']) && $form[$value]['widget'][0]['#type'] === 'details') {
        $fields_with_details[] = $value;
      }
      else {
        $form[$value]['#group'] = 'metabox_fields';
        $metabox_has_fields = TRUE;
      }
    }
  }
  $form['#after_build'][] = 'gutenberg_form_node_form_details_after_build';
  $form['#fields_with_details'] = $fields_with_details;
  if (!$metabox_has_fields) {
    unset($form['metabox_fields']);
  }

  // Is Bartik the default theme? Add some custom styles
  // to look even better.
  $default_theme = \Drupal::config('system.theme')
    ->get('default');
  if ($default_theme === 'bartik') {
    $form['#attached']['library'][] = 'gutenberg/bartik';
  }
  $theme = \Drupal::theme()
    ->getActiveTheme();
  $theme_name = $theme
    ->getName();

  // Check if current theme is Seven (admin)
  if ($theme_name === 'seven') {
    $form['#attached']['library'][] = 'gutenberg/seven';
  }

  // Check if current theme is Claro (admin)
  if ($theme_name === 'claro') {
    $form['#attached']['library'][] = 'gutenberg/claro';
  }
  $form['#attached']['drupalSettings']['gutenberg']['metaboxes'][] = 'edit-metabox-fields';

  /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
  $module_handler = \Drupal::service('module_handler');
  $form['#attached']['drupalSettings']['gutenberg']['media-enabled'] = $module_handler
    ->moduleExists('media');
  if ($form['#attached']['drupalSettings']['gutenberg']['media-library-enabled'] = $module_handler
    ->moduleExists('media_library')) {
    $form['#attached']['library'][] = 'media_library/ui';
  }
}

/**
 * Store messages in drupal settings.
 */
function gutenberg_handle_messages(&$form) {
  $messages = \Drupal::messenger()
    ->deleteAll();
  $form['#attached']['drupalSettings']['gutenberg']['messages'] = $messages;
}

/**
 * Called by after build text fields on the form.
 */
function gutenberg_form_node_form_after_build(array $element, FormStateInterface $form_state) {
  unset($element[0]['format']['format']['#options']['gutenberg']);
  return $element;
}

/**
 * Called by form after build.
 */
function gutenberg_form_node_form_details_after_build(array $element, FormStateInterface $form_state) {

  // gutenberg_handle_messages($element);
  foreach ($element['#fields_with_details'] as $value) {
    $element['#attached']['drupalSettings']['gutenberg']['metaboxes'][] = $element[$value]['widget'][0]['#id'];
  }
  return $element;
}

/**
 * Alter media form.
 */
function _gutenberg_media_form_alter(array &$form, FormStateInterface $form_state, string $form_id) {
  $is_gutenberg = !is_null(\Drupal::request()->query
    ->get('gutenberg'));
  if (!$is_gutenberg) {
    return;
  }
  $form['actions']['delete']['#access'] = FALSE;
  unset($form['actions']['delete']);
  $form['#after_build'][] = 'gutenberg_form_media_edit_form_after_build';
  $form['actions']['submit']['#submit'][] = 'gutenberg_form_media_edit_form_submit';
  $form['actions']['submit']['#ajax'] = [
    'callback' => 'gutenberg_form_media_edit_form_submit',
    'event' => 'click',
  ];
  $form['actions']['cancel'] = [
    '#weight' => 99,
    '#type' => 'button',
    '#value' => t('Cancel'),
    '#ajax' => [
      'callback' => 'gutenberg_form_media_edit_form_cancel',
      'event' => 'click',
    ],
  ];
  $form['#submit'][] = 'gutenberg_form_media_edit_form_submit';
}

/**
 * Alter media edit form submit.
 */
function gutenberg_form_media_edit_form_submit(array $form, FormStateInterface $form_state) {
  $form_state
    ->disableRedirect();
  $command = new CloseModalDialogCommand();
  $response = new AjaxResponse();
  $response
    ->addCommand($command);
  return $response;
}

/**
 * Alter media edit form cancel.
 */
function gutenberg_form_media_edit_form_cancel(array $form, FormStateInterface $form_state) {
  $form_state
    ->disableRedirect();
  $command = new CloseModalDialogCommand();
  $response = new AjaxResponse();
  $response
    ->addCommand($command);
  return $response;
}

/**
 * Alter media edit form.
 */
function gutenberg_form_media_edit_form_after_build(array $element, FormStateInterface $form_state) {
  return $element;
}

/**
 * Implements hook_entity_presave().
 */
function gutenberg_entity_presave($entity) {
  if (!_gutenberg_is_gutenberg_enabled($entity)) {

    // Leave early if Gutenberg is not enabled for this entity.
    return;
  }

  /* @var $mapping_fields \Drupal\gutenberg\MappingFieldsHelper */
  $mapping_fields = \Drupal::service('class_resolver')
    ->getInstanceFromDefinition(MappingFieldsHelper::class);
  $mapping_fields
    ->setFieldMappingValues($entity);
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
function gutenberg_theme_suggestions_node_edit_form_alter(array &$suggestions, array $variables) {
  $config = \Drupal::service('config.factory')
    ->getEditable('gutenberg.settings');
  $node = \Drupal::routeMatch()
    ->getParameter('node');
  if (!$node) {
    $route_match = \Drupal::service('current_route_match');
    if (!$route_match
      ->getParameter('node_type')) {
      return;
    }
    $node_type = $route_match
      ->getParameter('node_type')
      ->get('type');
  }
  else {
    $node_type = $node->type
      ->getString();
  }
  $gutenberg_enabled = $config
    ->get($node_type . '_enable_full');
  if (!$gutenberg_enabled) {
    return;
  }
  $suggestions = [
    'node_edit_form__gutenberg',
  ];
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
function gutenberg_theme_suggestions_page_alter(array &$suggestions, array $variables) {
  if (!in_array('page__node__edit', $suggestions) && !in_array('page__node__add', $suggestions)) {
    return;
  }
  $config = \Drupal::service('config.factory')
    ->getEditable('gutenberg.settings');
  $node = \Drupal::routeMatch()
    ->getParameter('node');
  if (!$node) {
    $route_match = \Drupal::service('current_route_match');
    if (!$route_match
      ->getParameter('node_type')) {
      return;
    }
    $node_type = $route_match
      ->getParameter('node_type')
      ->get('type');
  }
  else {
    $node_type = $node->type
      ->getString();
  }
  $gutenberg_enabled = $config
    ->get($node_type . '_enable_full');
  if ($gutenberg_enabled) {
    if (in_array('page__node__edit', $suggestions)) {
      $suggestions = [
        'page__node__edit__gutenberg',
      ];
    }
    if (in_array('page__node__add', $suggestions)) {
      $suggestions = [
        'page__node__add__gutenberg',
      ];
    }
  }
}

/**
 * Implements hook_element_info_alter().
 */
function gutenberg_element_info_alter(array &$info) {
  if (!empty($info['text_format'])) {

    // Add custom processor to eliminate the format if needed.
    $info['text_format']['#process'][] = '_gutenberg_text_format_processor';
  }
}

/**
 * Process the text format element to eliminate the gutenberg format.
 *
 * On the fields that don't belong to content types with enabled gutenberg
 * experience there is no need to have the gutenberg format.
 *
 * @param array $element
 *   Render Element.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state object.
 * @param array $complete_form
 *   Complete form array.
 *
 * @return array
 *   Processed render element.
 */
function _gutenberg_text_format_processor(array $element, FormStateInterface $form_state, array &$complete_form) {

  // Check first if the format is in the list. It might be disabled or the
  // current user has not rights to access it.
  if (!empty($element['format']) && isset($element['format']['format']['#options']['gutenberg'])) {

    // By default let's assume that gutenberg format is not allowed.
    $gutenberg_allowed = FALSE;

    /** @var \Drupal\Core\Entity\ContentEntityForm $form */
    $form = $form_state
      ->getFormObject();

    // Check whether the form that contains the element is an EntityForm.
    if ($form instanceof EntityFormInterface) {

      // Get the entity from the form object for further processing.
      $entity = $form
        ->getEntity();

      // Check whether entity is of node type, because currently only them are
      // supported.
      if ($entity instanceof NodeInterface) {

        // Get the node type to get the Gutenberg experience setting.
        $node_type = $entity
          ->bundle();

        /** @var \Drupal\Core\Config\Config $config */
        $config = \Drupal::service('config.factory')
          ->getEditable('gutenberg.settings');
        $gutenberg_enabled = $config
          ->get($node_type . '_enable_full');
        if (!empty($gutenberg_enabled)) {

          // Gutenberg experience is enabled for current content type and
          // the current user is allowed to use the format.
          $gutenberg_allowed = TRUE;
        }
      }
    }

    // If Gutenberg experience is not enabled for the current form or
    // current user is not allowed to use the format, disable the choice of
    // Gutenberg format for this element.
    if (!$gutenberg_allowed) {
      unset($element['format']['format']['#options']['gutenberg']);
    }
  }
  return $element;
}

/**
 * Implements hook_views_pre_render().
 */
function gutenberg_views_pre_render(ViewExecutable $view) {
  if ($view
    ->id() == "reusable_blocks" && $view->current_display == 'page_1') {

    // Attached Gutenberg's basic style to reusable blocks view.
    $view->element['#attached']['library'][] = 'gutenberg/admin';
    $view->element['#attached']['library'][] = 'gutenberg/blocks-view';
  }
}

/**
 * Implements hook_page_attachments().
 */
function gutenberg_page_attachments(array &$page) {

  /*
   * FIXME: Recommending removing the generated CSS functionality since
   *  Gutenberg core doesn't support it, and causes the styles to be embedded
   *  on all pages, even those without Gutenberg on it.
   *  @see https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes
   *  If the functionality is truly needed, it could be added as a third
   *  party/custom module.
   */

  /** @var \Drupal\gutenberg\GutenbergLibraryManagerInterface $gutenberg_library_manager */
  $gutenberg_library_manager = \Drupal::service('plugin.manager.gutenberg.library');
  $theme_definition = $gutenberg_library_manager
    ->getActiveThemeMergedDefinition();
  if (empty($theme_definition['theme-support']['colors']) || !empty($theme_definition['theme-includes-colors'])) {

    // Color not available or the styles are already declared in the theme.
    return;
  }
  $css_markup = '';

  // Generate minified palette styles.
  foreach ($theme_definition['theme-support']['colors'] as $color) {
    $color_name = Html::getClass($color['name']);
    $color_value = $color['color'];
    $css_markup .= ":root .has-{$color_name}-color{color:{$color_value}}";
    $css_markup .= ":root .has-{$color_name}-background-color{background-color:{$color_value}}";
  }
  $page['#attached']['html_head'][] = [
    [
      '#tag' => 'style',
      '#attributes' => [
        'id' => Html::getUniqueId('gutenberg-palette'),
      ],
      '#value' => $css_markup,
    ],
    'gutenberg_palette',
  ];
}

/**
 * Implements hook_library_info_alter().
 */
function gutenberg_library_info_alter(&$libraries, $extension) {
  if ($extension === 'gutenberg') {
    $moduleHandler = \Drupal::moduleHandler();
    $js_files_edit = [];
    $css_files_edit = [];
    $css_files_view = [];
    $moduleHandler
      ->alter('gutenberg_blocks', $js_files_edit, $css_files_edit, $css_files_view);
    foreach ($js_files_edit as $file) {
      $libraries['blocks-edit']['js'][$file] = [];
    }
    foreach ($css_files_edit as $file) {
      $libraries['blocks-edit']['css']['base'][$file] = [];
    }
    foreach ($css_files_view as $file) {
      $libraries['blocks-edit']['css']['base'][$file] = [];
      $libraries['blocks-view']['css']['base'][$file] = [];
    }
  }
}

/**
 * Implements hook_help().
 */
function gutenberg_help($route_name, RouteMatchInterface $route_match) {
  if ($route_name === 'help.page.gutenberg') {
    $readme_file = file_exists(__DIR__ . '/README.md') ? __DIR__ . '/README.md' : __DIR__ . '/README.txt';
    if (!file_exists($readme_file)) {
      return NULL;
    }
    $text = file_get_contents($readme_file);
    if (!\Drupal::moduleHandler()
      ->moduleExists('markdown')) {
      return '<pre>' . $text . '</pre>';
    }
    else {

      // Use the Markdown filter to render the README.
      $filter_manager = \Drupal::service('plugin.manager.filter');
      $settings = \Drupal::configFactory()
        ->get('markdown.settings')
        ->getRawData();
      $config = [
        'settings' => $settings,
      ];
      $filter = $filter_manager
        ->createInstance('markdown', $config);
      return $filter
        ->process($text, 'en');
    }
  }
  return NULL;
}

/**
 * Implements hook_themes_installed().
 */
function gutenberg_themes_installed($theme_list) {
  \Drupal::service('plugin.manager.gutenberg.library')
    ->clearCachedDefinitions();
}

/**
 * Implements hook_themes_uninstalled().
 */
function gutenberg_themes_uninstalled(array $themes) {
  \Drupal::service('plugin.manager.gutenberg.library')
    ->clearCachedDefinitions();
}

/**
 * Checks whether Gutenberg is enabled for an entity.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity to check.
 *
 * @return bool
 *   Whether the entity is supported.
 */
function _gutenberg_is_gutenberg_enabled(EntityInterface $entity = NULL) {
  if (!$entity) {
    return FALSE;
  }
  if ($entity
    ->getEntityTypeId() !== 'node') {
    return FALSE;
  }

  /*
   * TODO read from the entity type's third_party_settings instead of a global.
   *  @see menu_ui_form_node_form_alter()
   *  and https://www.sitepoint.com/drupal-8-third-party-settings-and-pseudo-fields/
   */
  $config = \Drupal::service('config.factory')
    ->get('gutenberg.settings');
  $node_type = $entity
    ->bundle();
  return (bool) $config
    ->get($node_type . '_enable_full');
}

Functions

Namesort descending Description
gutenberg_element_info_alter Implements hook_element_info_alter().
gutenberg_entity_presave Implements hook_entity_presave().
gutenberg_form_alter Implements hook_form_alter().
gutenberg_form_media_edit_form_after_build Alter media edit form.
gutenberg_form_media_edit_form_cancel Alter media edit form cancel.
gutenberg_form_media_edit_form_submit Alter media edit form submit.
gutenberg_form_node_form_after_build Called by after build text fields on the form.
gutenberg_form_node_form_alter Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
gutenberg_form_node_form_details_after_build Called by form after build.
gutenberg_handle_messages Store messages in drupal settings.
gutenberg_help Implements hook_help().
gutenberg_library_info_alter Implements hook_library_info_alter().
gutenberg_page_attachments Implements hook_page_attachments().
gutenberg_theme Implements hook_theme().
gutenberg_themes_installed Implements hook_themes_installed().
gutenberg_themes_uninstalled Implements hook_themes_uninstalled().
gutenberg_theme_suggestions_gutenberg_block Implements hook_theme_suggestions_HOOK().
gutenberg_theme_suggestions_node_edit_form_alter Implements hook_theme_suggestions_HOOK_alter().
gutenberg_theme_suggestions_page_alter Implements hook_theme_suggestions_HOOK_alter().
gutenberg_views_pre_render Implements hook_views_pre_render().
_gutenberg_is_gutenberg_enabled Checks whether Gutenberg is enabled for an entity.
_gutenberg_media_form_alter Alter media form.
_gutenberg_node_type_form_submit Alters the node form submit.
_gutenberg_text_format_processor Process the text format element to eliminate the gutenberg format.

Constants