geysir.module in Geysir 8
Geysir module file.
File
geysir.moduleView source
<?php
/**
* @file
* Geysir module file.
*/
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
/**
* Implements hook_theme().
*/
function geysir_theme() {
return [
'geysir_field_paragraph_wrapper' => [
'render element' => 'element',
'file' => 'geysir.theme.inc',
],
];
}
/**
* Implements hook_toolbar().
*/
function geysir_toolbar() {
$items = [];
$items['geysir'] = [
'#cache' => [
'contexts' => [
'user.permissions',
],
],
];
if (!Drupal::currentUser()
->hasPermission('geysir manage paragraphs from front-end')) {
return $items;
}
$items['geysir'] += [
'#type' => 'toolbar_item',
'tab' => [
'#type' => 'html_tag',
'#tag' => 'button',
'#value' => t('Geysir'),
'#attributes' => [
'class' => [
'toolbar-icon',
'toolbar-icon-geysir',
],
'aria-pressed' => 'false',
'type' => 'button',
],
],
'#wrapper_attributes' => [
'class' => [
'geysir-toolbar-tab',
],
],
'#attached' => [
'library' => [
'geysir/geysir-toolbar',
],
],
];
return $items;
}
/**
* Implements hook_help().
*/
function geysir_help($route_name, $route_match) {
$output = '';
switch ($route_name) {
// Main module help for the Geysir module.
case 'help.page.geysir':
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Geysir introduces several user interface optimisations which support content authors in their daily workflow. Focus lies on the page building process.') . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dt>' . t('Inserting new Paragraphs from the front-end') . '</dt>';
$output .= '<dd>' . t('Geysir allows authors to insert new Paragraphs without having to go to the Drupal backend. A button is available to <em>add</em> a new Paragraph between existing Paragraphs, this button opens a modal dialog which allows inserting a new Paragraph. The modal first requires you to select a Paragraph Type to add. Then it contains all the fields that are part of the related Paragraph Bundle.') . '</dd>';
$output .= '<dt>' . t('Editing existing Paragraphs from the front-end') . '</dt>';
$output .= '<dd>' . t('Geysir allows authors to edit Paragraphs without having to go to the Drupal backend. When hovering over an existing Paragraph, it gets highlighted and an <em>edit</em> button appears. This button opens a modal dialog which allows editing that particular Paragraph. The modal contains all the fields that are part of the related Paragraph Bundle.') . '</dd>';
$output .= '<dt>' . t('Deleting existing Paragraphs from the front-end') . '</dt>';
$output .= '<dd>' . t('Next to editing Paragraphs, Geysir also allows deletion of existing paragraphs. A <em>delete</em> button is available which allows fast deletion of a Paragraph in a page.') . '</dd>';
$output .= '<h3>' . t('Future additions') . '</h3>';
$output .= '<p>' . t('We plan to continuously add support for new features in the near future, like the introduction of draft versions to be able to work with a page without publishing every action, reordering of paragraphs from the front-end and the reduction of clutter for authors to provide high-fidelity page previews.') . '</p>';
break;
}
return $output;
}
/**
* Implements hook_entity_type_build().
*/
function geysir_entity_type_build(array &$entity_types) {
/* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
// Add forms for Paragraphs without overriding the default forms.
$entity_types['paragraph']
->setFormClass('geysir_delete', '\\Drupal\\geysir\\Form\\GeysirParagraphDeleteForm');
$entity_types['paragraph']
->setFormClass('geysir_edit', '\\Drupal\\geysir\\Form\\GeysirParagraphForm');
$entity_types['paragraph']
->setFormClass('geysir_modal_add', '\\Drupal\\geysir\\Form\\GeysirModalParagraphAddForm');
$entity_types['paragraph']
->setFormClass('geysir_modal_delete', '\\Drupal\\geysir\\Form\\GeysirModalParagraphDeleteForm');
$entity_types['paragraph']
->setFormClass('geysir_modal_edit', '\\Drupal\\geysir\\Form\\GeysirModalParagraphForm');
}
/**
* Implements hook_preprocess_HOOK().
*
* Using hook_preprocess_paragraph().
*/
function geysir_preprocess_paragraph(&$vars) {
$vars['attributes']['class'][] = 'clearfix';
}
/**
* Gets the first parent that is not a paragraph.
*
* @param Drupal\Core\Entity\EntityInterface $entity
*
* @return Drupal\Core\Entity\EntityInterface $entity
*/
function geysir_get_non_paragraph_parent(EntityInterface $entity) {
if ($entity
->getEntityTypeId() != 'paragraph') {
return $entity;
}
else {
/** @var Drupal\paragraphs\Entity\Paragraph $entity */
return geysir_get_non_paragraph_parent($entity
->getParentEntity());
}
}
/**
* Implements hook_preprocess_HOOK().
*
* Using hook_preprocess_field().
*/
function geysir_preprocess_field(&$vars) {
if (empty($vars['field_type']) || $vars['field_type'] !== 'entity_reference_revisions') {
return;
}
// Do not apply Geysir on Admin routes. This avoids rendering the buttons
// when a paragraph is set to preview in form mode.
// At the same time check if the admin route is not a Geysir route.
$route = \Drupal::routeMatch()
->getRouteName();
if (\Drupal::service('router.admin_context')
->isAdminRoute() && strpos($route, 'geysir.') !== 0) {
return;
}
$element =& $vars['element'];
/** @var Drupal\Core\Entity\FieldableEntityInterface $parent */
$parent = $element['#object'];
// Skip nested paragraphs.
if (!$parent instanceof NodeInterface) {
return;
}
// Check update access for current node + permission to use Geysir.
if (!$parent
->isLatestRevision() || !$parent
->access('update') || !\Drupal::currentUser()
->hasPermission('geysir manage paragraphs from front-end')) {
return;
}
/** @var Drupal\entity_reference_revisions\EntityReferenceRevisionsFieldItemList $field */
$field = $element['#items'];
$field_definition = $field
->getFieldDefinition();
$field_storage_definition = $field_definition
->getFieldStorageDefinition();
if ($field_storage_definition
->getSetting('target_type') !== 'paragraph') {
return;
}
$can_add_more_fields = $field_storage_definition
->getCardinality() == -1 || count($vars['items']) < $field_storage_definition
->getCardinality();
$field_wrapper_id = Html::getUniqueId('geysir--' . $vars['field_name']);
$langcode = Drupal::languageManager()
->getCurrentLanguage()
->getId();
$translated_parent = FALSE;
$non_paragraph_parent = geysir_get_non_paragraph_parent($parent);
if ($non_paragraph_parent
->isTranslatable()) {
$parent_translation = Drupal::service('entity.repository')
->getTranslationFromContext($non_paragraph_parent);
$translated_parent = !$parent_translation
->isDefaultTranslation();
}
$delta = 0;
while (!empty($element[$delta])) {
$links = [];
/** @var \Drupal\paragraphs\Entity\Paragraph $paragraph */
$paragraph = $element[$delta]['#paragraph'];
$paragraph_to_cut = $paragraph;
// Use the parent revision id if available, otherwise the parent id.
$parent_revision_id = $parent
->getRevisionId() ? $parent
->getRevisionId() : $parent
->id();
if (!$translated_parent && $can_add_more_fields) {
// Add link - before.
$links['add_before'] = [
'title' => t('Add before'),
'url' => Url::fromRoute('geysir.modal.add_form', [
'parent_entity_type' => $parent
->getEntityType()
->id(),
'parent_entity_bundle' => $parent
->bundle(),
'parent_entity_revision' => $parent_revision_id,
'field' => $vars['field_name'],
'field_wrapper_id' => $field_wrapper_id,
'delta' => $delta,
'paragraph' => $paragraph
->id(),
'paragraph_revision' => $paragraph
->getRevisionId(),
'position' => 'before',
'js' => 'nojs',
]),
'attributes' => [
'class' => [
'use-ajax',
'geysir-button',
],
'data-dialog-type' => 'modal',
'title' => t('Add before'),
],
];
// Add link - after.
$links['add_after'] = [
'title' => t('Add after'),
'url' => Url::fromRoute('geysir.modal.add_form', [
'parent_entity_type' => $parent
->getEntityType()
->id(),
'parent_entity_bundle' => $parent
->bundle(),
'parent_entity_revision' => $parent_revision_id,
'field' => $vars['field_name'],
'field_wrapper_id' => $field_wrapper_id,
'delta' => $delta,
'paragraph' => $paragraph
->id(),
'paragraph_revision' => $paragraph
->getRevisionId(),
'position' => 'after',
'js' => 'nojs',
]),
'attributes' => [
'class' => [
'use-ajax',
'geysir-button',
],
'data-dialog-type' => 'modal',
'title' => t('Add after'),
],
];
}
// Edit link.
$links['edit'] = [
'title' => t('Edit'),
'url' => Url::fromRoute('geysir.modal.edit_form', [
'parent_entity_type' => $parent
->getEntityType()
->id(),
'parent_entity_bundle' => $parent
->bundle(),
'parent_entity_revision' => $parent_revision_id,
'field' => $vars['field_name'],
'field_wrapper_id' => $field_wrapper_id,
'delta' => $delta,
'paragraph' => $paragraph
->id(),
'paragraph_revision' => $paragraph
->getRevisionId(),
'js' => 'nojs',
]),
'attributes' => [
'class' => [
'use-ajax',
'geysir-button',
],
'data-dialog-type' => 'modal',
'title' => t('Edit'),
],
];
if ($translated_parent) {
if ($paragraph
->isDefaultTranslation()) {
$links['edit'] = [
'title' => t('Translate'),
'url' => Url::fromRoute('geysir.modal.translate_form', [
'parent_entity_type' => $parent
->getEntityType()
->id(),
'parent_entity_bundle' => $parent
->bundle(),
'parent_entity_revision' => $parent_revision_id,
'field' => $vars['field_name'],
'field_wrapper_id' => $field_wrapper_id,
'delta' => $delta,
'paragraph' => $paragraph
->id(),
'paragraph_revision' => $paragraph
->getRevisionId(),
'js' => 'nojs',
]),
'attributes' => [
'class' => [
'use-ajax',
'geysir-button',
],
'data-dialog-type' => 'modal',
'title' => t('Translate'),
],
];
}
if (!$paragraph
->isTranslatable()) {
unset($links['edit']);
}
}
if (!$translated_parent) {
if (count($element['#items']) > 1) {
// Cut link.
$links['cut'] = [
'title' => t('Cut'),
'url' => Url::fromRoute('geysir.cut', [
'parent_entity_type' => $parent
->getEntityType()
->id(),
'parent_entity_bundle' => $parent
->bundle(),
'parent_entity_revision' => $parent_revision_id,
'field' => $vars['field_name'],
'field_wrapper_id' => $field_wrapper_id,
'delta' => $delta,
'paragraph_to_cut' => $paragraph
->id(),
'paragraph_revision' => $paragraph
->getRevisionId(),
'js' => 'nojs',
]),
'attributes' => [
'data-geysir-field-paragraph-field-wrapper' => $field_wrapper_id,
'class' => [
'geysir-button',
],
'data-dialog-type' => 'modal',
'title' => t('Cut'),
],
];
}
// Delete link.
$links['delete'] = [
'title' => t('Delete'),
'url' => Url::fromRoute('geysir.modal.delete_form', [
'parent_entity_type' => $parent
->getEntityType()
->id(),
'parent_entity_bundle' => $parent
->bundle(),
'parent_entity_revision' => $parent_revision_id,
'field' => $vars['field_name'],
'field_wrapper_id' => $field_wrapper_id,
'delta' => $delta,
'paragraph' => $paragraph
->id(),
'paragraph_revision' => $paragraph
->getRevisionId(),
'js' => 'nojs',
]),
'attributes' => [
'class' => [
'use-ajax',
'geysir-button',
],
'data-dialog-type' => 'modal',
'title' => t('Delete'),
],
];
if ($field_definition
->isRequired() && count($element['#items']) == 1) {
$links['delete']['title'] = t('Cannot remove the last item of a required field');
$links['delete']['url'] = Url::fromUserInput('#');
$links['delete']['attributes']['class'][] = 'disabled';
$links['delete']['attributes']['title'] = t('Cannot remove the last item of a required field');
}
// Paste link - before.
$links['paste_before'] = [
'title' => t('Paste'),
'url' => Url::fromRoute('geysir.paste', [
'parent_entity_type' => $parent
->getEntityType()
->id(),
'parent_entity_bundle' => $parent
->bundle(),
'parent_entity_revision' => $parent_revision_id,
'field' => $vars['field_name'],
'field_wrapper_id' => $field_wrapper_id,
'delta' => $delta,
'position' => 'before',
'paragraph_revision' => $paragraph
->getRevisionId(),
'paragraph_to_paste' => $paragraph_to_cut
->id(),
'js' => 'nojs',
]),
'attributes' => [
'data-geysir-field-paragraph-field-wrapper' => $field_wrapper_id,
'class' => [
'use-ajax',
'geysir-button',
'geysir-paste',
],
'data-dialog-type' => 'modal',
'title' => t('Paste'),
],
];
// Paste link - after.
$links['paste_after'] = [
'title' => t('Paste'),
'url' => Url::fromRoute('geysir.paste', [
'parent_entity_type' => $parent
->getEntityType()
->id(),
'parent_entity_bundle' => $parent
->bundle(),
'parent_entity_revision' => $parent_revision_id,
'field' => $vars['field_name'],
'field_wrapper_id' => $field_wrapper_id,
'delta' => $delta,
'position' => 'after',
'paragraph_revision' => $paragraph
->getRevisionId(),
'paragraph_to_paste' => $paragraph_to_cut
->id(),
'js' => 'nojs',
]),
'attributes' => [
'data-geysir-field-paragraph-field-wrapper' => $field_wrapper_id,
'class' => [
'use-ajax',
'geysir-button',
'geysir-paste',
],
'data-dialog-type' => 'modal',
'title' => t('Paste'),
],
];
}
$context = [
'paragraph' => $paragraph,
'parent' => $parent,
'delta' => $delta,
'field_definition' => $field_definition,
];
\Drupal::moduleHandler()
->alter('geysir_paragraph_links', $links, $context);
$links_array = [
'#theme' => 'links',
'#links' => $links,
'#attributes' => [
'class' => [
'geysir-field-paragraph-links',
'links',
],
],
'#attached' => [
'library' => [
'core/drupal.dialog.ajax',
'geysir/geysir',
],
],
];
$vars['items'][$delta]['content']['#theme_wrappers'][] = 'geysir_field_paragraph_wrapper';
$vars['items'][$delta]['content']['#geysir_field_paragraph_links'] = Drupal::service('renderer')
->render($links_array);
/** @var \Drupal\paragraphs\Entity\Paragraph $paragraph */
$paragraph = $vars['items'][$delta]['content']['#paragraph'];
if ($paragraph
->isTranslatable() && $paragraph
->hasTranslation($langcode)) {
$vars['items'][$delta]['content']['#paragraph'] = $paragraph
->getTranslation($langcode);
}
$delta++;
}
// Attach the field wrapper ID in a data-attribute.
$vars['attributes']['data-geysir-field-paragraph-field-wrapper'] = $field_wrapper_id;
}
/**
* Implements hook_preprocess_HOOK().
*
* Using hook_preprocess_node().
*/
function geysir_preprocess_node(&$vars) {
/** @var \Drupal\node\Entity\Node $node */
$node = $vars["node"];
if (empty($node) || !$node
->isLatestRevision() || !$node
->access('update') || !Drupal::currentUser()
->hasPermission('geysir manage paragraphs from front-end') || !$node
->isDefaultTranslation()) {
return;
}
$field_definitions = $node
->getFieldDefinitions();
// Check if multiple paragraph fields.
$paragraph_fields = [];
foreach ($field_definitions as $field_definition) {
/** @var \Drupal\Core\Field\BaseFieldDefinition $field_Definition */
if ($field_definition
->getType() == 'entity_reference_revisions') {
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage_definition */
$field_storage_definition = $field_definition
->getFieldStorageDefinition();
if ($field_storage_definition
->getSetting('target_type') == 'paragraph') {
$paragraph_fields[$field_storage_definition
->get('field_name')] = $field_definition
->getLabel();
}
}
}
if (empty($paragraph_fields)) {
return;
}
foreach ($paragraph_fields as $field_name => $field_label) {
// Check if the paragraph field already has paragraphs added.
if (isset($vars['content'][$field_name]) && empty($vars['content'][$field_name]['#items'])) {
$field_wrapper_id = Html::getUniqueId('geysir--' . $field_name);
$markup = geysir_get_add_first_paragraph_markup($node, $field_name, $field_wrapper_id, $field_label);
$markup = [
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => [
'data-geysir-field-paragraph-field-wrapper' => $field_wrapper_id,
],
'#value' => $markup,
];
$vars['content'][$field_name]['#suffix'] = Drupal::service('renderer')
->render($markup);
}
}
}
/**
* Get the 'Add first paragrah' link.
*
* @param Node $node
* The parent node.
* @param $field_name
* The field name.
* @param $field_wrapper_id
* The wrapper in place for the field.
* @param $field_label
* The field label
*
* @return mixed
* The markup of the link.
*/
function geysir_get_add_first_paragraph_markup(Node $node, $field_name, $field_wrapper_id, $field_label) {
$links['add_before'] = [
'title' => t('Start adding content to %field_label', [
'%field_label' => $field_label,
]),
'url' => Url::fromRoute('geysir.modal.add_form_first', [
'parent_entity_type' => $node
->getEntityType()
->id(),
'parent_entity_bundle' => $node
->bundle(),
'parent_entity_revision' => $node
->getRevisionId() ? $node
->getRevisionId() : $node
->getLoadedRevisionId(),
'field' => $field_name,
'field_wrapper_id' => $field_wrapper_id,
'delta' => 0,
'position' => 'before',
'js' => 'nojs',
]),
'attributes' => [
'class' => [
'use-ajax',
'geysir-button',
],
'data-dialog-type' => 'modal',
'title' => t('Start adding content to %field_label', [
'%field_label' => $field_label,
]),
],
];
$links_array = [
'#theme' => 'links',
'#links' => $links,
'#attributes' => [
'class' => [
'geysir-field-paragraph-links',
'geysir-field-paragraph-add-first',
'links',
],
],
'#attached' => [
'library' => [
'core/drupal.dialog.ajax',
'geysir/geysir',
],
],
];
return Drupal::service('renderer')
->render($links_array);
}
Functions
Name | Description |
---|---|
geysir_entity_type_build | Implements hook_entity_type_build(). |
geysir_get_add_first_paragraph_markup | Get the 'Add first paragrah' link. |
geysir_get_non_paragraph_parent | Gets the first parent that is not a paragraph. |
geysir_help | Implements hook_help(). |
geysir_preprocess_field | Implements hook_preprocess_HOOK(). |
geysir_preprocess_node | Implements hook_preprocess_HOOK(). |
geysir_preprocess_paragraph | Implements hook_preprocess_HOOK(). |
geysir_theme | Implements hook_theme(). |
geysir_toolbar | Implements hook_toolbar(). |