You are here

gutenberg.module in Gutenberg 8

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

Provides integration with the Gutenberg editor.

File

gutenberg.module
View source
<?php

/**
 * @file
 * Provides integration with the Gutenberg editor.
 */
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\NodeInterface;
use Drupal\views\ViewExecutable;
use Symfony\Component\Yaml\Yaml;
use Drupal\Core\Discovery\YamlDiscovery;
use Drupal\image\Entity\ImageStyle;
use Drupal\gutenberg\Controller\UtilsController;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseModalDialogCommand;

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

  // TODO: There's got to be a better way to alter all media forms regardless
  // the type.
  if ($form_id == 'media_video_edit_form' || $form_id == 'media_image_edit_form' || $form_id == 'media_audio_edit_form' || $form_id == 'media_file_edit_form' || $form_id == 'media_remote_video_edit_form') {
    _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 = "<br/><code>[<br/>&nbsp;&nbsp;[\"core/heading\", {}],<br/>&nbsp;&nbsp;[\"core/paragraph\", {\"placeholder\": \"Insert text\"}]<br/>]</code>";
    $form['gutenberg']['gutenberg_template'] = [
      '#type' => 'textarea',
      '#description' => t('JSON structure of blocks. Example:') . $example_code,
      '#title' => t('Template'),
      '#default_value' => $config
        ->get($form['type']['#default_value'] . '_template'),
      '#states' => [
        'visible' => [
          'input[name="enable_gutenberg_experience"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];
    $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 = _gutenberg_get_allowed_blocks();
    $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_node_form_alter().
 */
function gutenberg_form_node_form_alter(&$form, FormStateInterface $form_state) {
  $config = \Drupal::service('config.factory')
    ->getEditable('gutenberg.settings');
  $node = $form_state
    ->getFormObject()
    ->getEntity();
  $node_type = $node->type
    ->getString();
  $gutenberg_enabled = $config
    ->get($node_type . '_enable_full');

  // Leave early if Gutenberg not enabled.
  if (!$gutenberg_enabled) {
    return;
  }

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

  /*
   * DEPRECATED >>>
   */
  $mapped_fields = _gutenberg_get_mapped_fields(json_decode($config
    ->get($node_type . '_template')));
  foreach ($mapped_fields as $item) {
    $form[$item['field']]['#access'] = FALSE;
  }

  /*
   * <<< DEPRECATED
   */
  $mapped_fields = _gutenberg_get_mapping_fields(json_decode($config
    ->get($node_type . '_template')));
  foreach ($mapped_fields as $item) {
    $form[$item['field']]['#access'] = FALSE;
  }
  $module_settings = _gutenberg_get_all_modules_settings();
  foreach ($module_settings as $settings) {
    if (isset($settings['libraries-edit'])) {
      foreach ($settings['libraries-edit'] as $library) {
        $form['#attached']['library'][] = $library;
      }
    }
  }
  $theme_settings = _gutenberg_get_default_theme_settings();
  if (isset($theme_settings['libraries-edit'])) {
    foreach ($theme_settings['libraries-edit'] as $value) {
      $form['#attached']['library'][] = $value;
    }
  }
  if (isset($theme_settings['theme-support'])) {
    $form['#attached']['drupalSettings']['gutenberg']['theme-support'] = $theme_settings['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;
  $text_fields = [];

  // Iterate over all node fields and apply gutenberg text format
  // on first text field found.
  $field_names = array_keys($node
    ->getFields());
  foreach ($field_names as $value) {
    $field_properties = array_keys($node
      ->getFieldDefinition($value)
      ->getFieldStorageDefinition()
      ->getPropertyDefinitions());
    if (in_array('format', $field_properties)) {
      $text_fields[] = $value;
    }
  }

  // No point going forward when no text fields on the form.
  if (count($text_fields) === 0) {
    return;
  }
  $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 ($gutenberg_enabled) {
      if ($text_fields[0] !== $fieldname) {
        $form[$fieldname]['widget']['#after_build'][] = 'gutenberg_form_node_form_after_build';
      }
    }
    else {
      $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 = [];
  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';
  }

  // Check if current theme is Seven (admin)
  $theme = \Drupal::theme()
    ->getActiveTheme();
  if ($theme
    ->getName() === 'seven') {
    $form['#attached']['library'][] = 'gutenberg/seven';
  }
  $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';
  }
}

/**
 * Handle messages.
 */
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 template_preprocess_node().
 */
function gutenberg_preprocess_node(&$variables) {
  $config = \Drupal::service('config.factory')
    ->getEditable('gutenberg.settings');
  $node = $variables['elements']['#node'];
  $node_type = $node->type
    ->getString();
  $gutenberg_enabled = $config
    ->get($node_type . '_enable_full');
  if (!$gutenberg_enabled) {
    return;
  }
  $variables['#attached']['library'][] = 'gutenberg/blocks-view';
  $module_settings = _gutenberg_get_all_modules_settings();
  foreach ($module_settings as $settings) {
    if (isset($settings['libraries-view'])) {
      foreach ($settings['libraries-view'] as $library) {
        $variables['#attached']['library'][] = $library;
      }
    }
  }
  $default_theme = \Drupal::config('system.theme')
    ->get('default');
  if ($default_theme === 'bartik') {
    $variables['#attached']['library'][] = 'gutenberg/bartik';
  }
}

/**
 * Implements hook_entity_presave().
 */
function gutenberg_entity_presave($entity) {
  if (!$entity
    ->getEntityTypeId() === 'node') {
    return;
  }
  $config = \Drupal::service('config.factory')
    ->getEditable('gutenberg.settings');
  $node_type = $entity
    ->bundle();
  $gutenberg_enabled = $config
    ->get($node_type . '_enable_full');
  if (!$gutenberg_enabled) {
    return;
  }

  /*
   * DEPRECATED >>>
   * To be removed on 1.12 or 1.13 (2.0?)
   */

  // Get first text field, that's the one that'll have
  // gutenberg text format
  // TODO: We already have this on node_edit_form_alter,
  // let's move it to a function.
  $value_fields = [
    'title',
  ];
  $text_fields = [];

  // Used for media.
  $file_fields = [];

  // Iterate over all node fields and apply gutenberg text format
  // on first text field found.
  $field_names = array_keys($entity
    ->getFields());
  foreach ($field_names as $value) {
    $definition = $entity
      ->getFieldDefinition($value);
    $field_properties = $definition
      ->getFieldStorageDefinition()
      ->getPropertyDefinitions();
    if (in_array('value', array_keys($field_properties))) {
      $value_fields[] = $value;
    }

    // Check for "rich-text" fields.
    if (in_array('format', array_keys($field_properties))) {
      $text_fields[] = $value;
    }
    if (isset($field_properties['entity'])) {
      $settings = $definition
        ->getSettings();

      // Check for images/files.
      if ($settings['target_type'] === 'file') {
        $file_fields[] = $value;
      }
      if ($settings['target_type'] === 'media') {
        $file_fields[] = $value;
      }
    }
  }
  if (count($text_fields) === 0) {
    return;
  }
  $str = $entity
    ->get($text_fields[0])
    ->getString();

  // Iterate over all mapped fields and set its values.
  $mapped_fields = _gutenberg_get_mapped_fields(json_decode($config
    ->get($node_type . '_template')));
  foreach ($mapped_fields as $item) {
    $re = '/((<!-- .*?\\{.*"mappingField":"' . $item['field'] . '".*} -->)([\\s\\S]*?)(<!-- \\/[\\s\\S]*?-->|\\/-->)|(<!-- .*?\\{.*"mappingField":"' . $item['field'] . '".*} \\/-->))/m';

    // matches:
    // 0: all block
    // 1: opening block comment tag (get attributes from here)
    // 2: content
    // 3: closing block comment tag.
    preg_match_all($re, $str, $matches, PREG_SET_ORDER, 0);

    // Get block attributes.
    $re = '/((<!-- .*?({[\\s\\S]*}).*-->)|(<!-- .*?({[\\s\\S]*}).*\\/-->))/m';
    $tag = $matches[0][2] !== '' ? $matches[0][2] : $matches[0][1];
    preg_match_all($re, $tag, $attrs, PREG_SET_ORDER, 0);
    $attributes = json_decode($attrs[0][3], TRUE);

    // Remove line breaks.
    $re = '/\\r?\\n|\\r/';
    $value = $matches[0][3];
    $value = preg_replace($re, '', $value);
    if (in_array($item['field'], $value_fields)) {
      $entity
        ->set($item['field'], strip_tags($value));
    }

    // Set text fields.
    if (in_array($item['field'], $text_fields)) {
      $entity
        ->set($item['field'], $matches[0][3]);
    }
    if (in_array($item['field'], $file_fields)) {
      $value = is_array($attributes[$item['attribute']]) ? $attributes[$item['attribute']][0] : $attributes[$item['attribute']];
      $entity
        ->set($item['field'], [
        'target_id' => $value,
      ]);
    }
  }

  /*
   * <<< DEPRECATED
   */
  $mapped_fields = _gutenberg_get_mapping_fields(json_decode($config
    ->get($node_type . '_template')));
  $re = '/((<!-- .*?\\{.*"mappingFields":.*} -->)([\\s\\S]*?)(<!-- \\/[\\s\\S]*?-->|\\/-->)|(<!-- .*?\\{.*"mappingFields":.*} \\/-->))/m';
  preg_match_all($re, $str, $blocks, PREG_SET_ORDER, 0);

  // Let's build the field's array of values.
  $fields = [];

  // For each block match.
  foreach ($blocks as $block) {

    // Get block attributes.
    $re = '/((^<!-- .*?({[\\s\\S]*}).*-->$))/m';
    $tag = $block[2] !== '' ? $block[2] : $block[1];
    preg_match_all($re, $tag, $attrs, PREG_SET_ORDER, 0);
    $attributes = json_decode($attrs[0][3], TRUE);

    // Remove line breaks from block's "inner" content.
    // $content is the "inner" content.
    $re = '/\\r?\\n|\\r/';
    $content = $block[3];
    $content = preg_replace($re, '', $content);
    foreach ($attributes['mappingFields'] as $mField) {
      if (!isset($fields[$mField['field']])) {
        $fields[$mField['field']] = [];
      }
      $value = isset($mField['attribute']) ? $attributes[$mField['attribute']] : $content;

      // Value doesn't support array yet.
      if (is_array($value)) {
        $value = $value[0];
      }
      $value = strip_tags($value);
      if (isset($mField['property'])) {
        $fields[$mField['field']][$mField['property']] = $value;
      }
      else {
        $fields[$mField['field']] = $value;
      }
    }
  }
  foreach ($fields as $key => $value) {
    try {
      $entity
        ->set($key, $value);
    } catch (\Exception $e) {
      \Drupal::logger('gutenberg')
        ->error(t('Mapping field: @message', [
        '@message' => $e
          ->getMessage(),
      ]));
    }
  }
}

/**
 * Gets all mapped fields on the template (recursive).
 *
 * @deprecated in gutenberg:8.x-1.11
 * @see https://www.drupal.org/node/3109876
 */
function _gutenberg_get_mapped_fields($template, &$result = []) {
  if (empty($template)) {
    return [];
  }
  foreach ($template as $key => $block) {
    if (isset($block[1]) && isset($block[1]->mappingField)) {
      $item = [];
      $item['field'] = $block[1]->mappingField;
      if (isset($block[1]->mappingAttribute)) {
        $item['attribute'] = $block[1]->mappingAttribute;
      }
      $result[] = $item;
    }
    if (isset($block[2])) {
      _gutenberg_get_mapped_fields($block[2], $result);
    }
  }
  return $result;
}

/**
 * Gets all mapping fields on the template (recursive).
 */
function _gutenberg_get_mapping_fields($template, &$result = []) {
  if (empty($template)) {
    return [];
  }
  foreach ($template as $key => $block) {
    if (isset($block[1]) && isset($block[1]->mappingFields)) {
      foreach ($block[1]->mappingFields as $field) {
        $item = [];
        $item['field'] = $field->field;
        if (isset($field->property)) {
          $item['property'] = $field->property;
        }
        if (isset($field->attribute)) {
          $item['attribute'] = $field->attribute;
        }
        $result[] = $item;
      }
    }
    if (isset($block[2])) {
      _gutenberg_get_mapping_fields($block[2], $result);
    }
  }
  return $result;
}

/**
 * Gets default theme settings.
 */
function _gutenberg_get_default_theme_settings() {
  $settings =& drupal_static(__FUNCTION__);
  if (!isset($settings)) {
    $theme_name = \Drupal::config('system.theme')
      ->get('default');
    $theme_path = drupal_get_path('theme', $theme_name);
    $file_path = DRUPAL_ROOT . '/' . $theme_path . '/' . $theme_name . '.gutenberg.yml';
    if (file_exists($file_path)) {
      $file_contents = file_get_contents($file_path);
      $settings = Yaml::parse($file_contents);
    }
    else {
      $settings = [];
    }
  }
  return $settings;
}

/**
 * Gets settings from all modules.
 */
function _gutenberg_get_all_modules_settings() {
  $settings =& drupal_static(__FUNCTION__);
  if (!isset($settings)) {
    $directories = \Drupal::service('module_handler')
      ->getModuleDirectories();
    $discovery = new YamlDiscovery('gutenberg', $directories);
    $settings = $discovery
      ->findAll();
  }
  return $settings;
}

/**
 * Gets allowed blocks.
 */
function _gutenberg_get_allowed_blocks() {
  $settings =& drupal_static(__FUNCTION__);
  if (!isset($settings)) {
    $module_handler = \Drupal::service('module_handler');
    $path = $module_handler
      ->getModule('gutenberg')
      ->getPath();
    $file_path = DRUPAL_ROOT . '/' . $path . '/' . 'gutenberg.blocks.yml';
    if (file_exists($file_path)) {
      $file_contents = file_get_contents($file_path);
      $settings = Yaml::parse($file_contents);
    }
  }
  return $settings;
}

/**
 * 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_theme().
 */
function gutenberg_theme() {
  return [
    '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_palette' => [
      'variables' => [
        'colors' => [],
      ],
    ],
  ];
}

/**
 * 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) {
  $settings = _gutenberg_get_default_theme_settings();
  if (!isset($settings['theme-support']['colors'])) {
    return;
  }
  $palette = [
    '#theme' => 'gutenberg_palette',
    '#colors' => $settings['theme-support']['colors'],
  ];
  $renderPalette = \Drupal::service('renderer')
    ->renderRoot($palette);

  // Sanitizing the output, like comments etc.
  $renderPalette = strip_tags($renderPalette);
  $renderPalette = trim($renderPalette);
  $page['#attached']['html_head'][] = [
    [
      '#tag' => 'style',
      '#value' => $renderPalette,
    ],
    '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;
}

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_node_form_alter().
gutenberg_form_node_form_details_after_build Called by form after build.
gutenberg_handle_messages Handle messages.
gutenberg_help Implements hook_help().
gutenberg_library_info_alter Implements hook_library_info_alter().
gutenberg_page_attachments Implements hook_page_attachments().
gutenberg_preprocess_node Implements template_preprocess_node().
gutenberg_theme Implements hook_theme().
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_get_allowed_blocks Gets allowed blocks.
_gutenberg_get_all_modules_settings Gets settings from all modules.
_gutenberg_get_default_theme_settings Gets default theme settings.
_gutenberg_get_mapped_fields Deprecated Gets all mapped fields on the template (recursive).
_gutenberg_get_mapping_fields Gets all mapping fields on the template (recursive).
_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.