recogito_integration-1.0.x-dev/recogito_integration.module
recogito_integration.module
<?php
/**
* @file
* Contains hooks and functions for the Recogito Integration module.
*
* This module integrates the Recogito annotation tool with Drupal. It provides
* functionality to attach Recogito to certain node types and manage permissions
* for viewing, creating, editing, and deleting annotations.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\NodeInterface;
use Drupal\taxonomy\TermInterface;
/**
* Implements hook_page_attachments().
*/
function recogito_integration_page_attachments(array &$attachments) {
$routeName = \Drupal::routeMatch()->getRouteName();
if (!in_array($routeName, ['entity.node.canonical', 'recogito_annotate_node'])) {
return;
}
$currentUser = \Drupal::currentUser();
$viewPerm = $currentUser->hasPermission('recogito view annotations');
if (!$viewPerm) {
return;
}
$createPerm = $currentUser->hasPermission('recogito create annotations');
$config = \Drupal::config('recogito_integration.settings');
$annotatables = $config->get('recogito_integration.annotatables') ?? [];
$node = \Drupal::routeMatch()->getParameter('node');
if (!isset($annotatables[$node->getType()])) {
return;
}
if (!$annotatables[$node->getType()]['enabled']) {
return;
}
$attached = [];
if ($routeName === 'recogito_annotate_node' && $createPerm) {
$perms = [
'view' => $viewPerm,
'create' => $createPerm,
'edit' => $currentUser->hasPermission('recogito edit annotations'),
'delete' => $currentUser->hasPermission('recogito delete annotations'),
'edit-own' => $currentUser->hasPermission('recogito edit own annotations'),
'delete-own' => $currentUser->hasPermission('recogito delete own annotations'),
];
$attached = [
'permissions' => $perms,
'userData' => [
'id' => $currentUser->id(),
'displayName' => $currentUser->getDisplayName(),
],
'admin' => $currentUser->isAuthenticated(),
'nodeId' => $node->id(),
];
$vocab = $config->get('recogito_integration.vocabulary_name');
$termList = [];
if ($vocab) {
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('taxonomy_term', $vocab);
$style_field = '';
if ($config->get('recogito_integration.preview_tag_selector')) {
foreach ($field_definitions as $field_name => $field_definition) {
if ($field_definition->getType() === 'annotation_profile') {
$style_field = $field_name;
break;
}
}
}
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vocab);
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$termList = [];
$termStyleList = [];
foreach ($terms as $term) {
$termList[] = $term->name;
$result = ['name' => $term->name, 'hasStyle' => 0];
$term = $term_storage->load($term->tid);
if (!$style_field) {
$termStyleList[] = $result;
continue;
}
$style = $term->get($style_field)->getValue();
if (empty($style) || !$style[0]['styling_choice']) {
$termStyleList[] = $result;
continue;
}
$result['hasStyle'] = 1;
$result['style'] = [
'text_color' => $style[0]['text_color'],
'background_color' => $style[0]['background_color'],
'underline_style' => $style[0]['underline_style'],
'underline_stroke' => $style[0]['underline_stroke'],
'underline_color' => $style[0]['underline_color'],
'background_transparency' => $style[0]['background_transparency'],
];
$result['style'] = recogito_integration_validate_style($result['style']);
$termStyleList[] = $result;
}
}
$defaultTagIds = $config->get('recogito_integration.default_tag') ?? [];
$defaultTags = [];
if ($defaultTagIds) {
$tags = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadMultiple($defaultTagIds);
$defaultTags = array_map(function ($term) {
return $term->getName();
}, $tags);
$defaultTags = array_values($defaultTags);
}
$attached['tagOptions'] = [
'defaultTags' => $defaultTags,
'tagList' => $termList,
'tagStyleList' => $termStyleList,
'selector' => $config->get('recogito_integration.tag_selector') ?? 0,
'textInput' => $config->get('recogito_integration.tag_text_input') ?? 1,
'createNewTag' => $config->get('recogito_integration.create_nonexistent_tag') ?? 1,
];
}
else {
$attached = [
'permissions' => ['view' => $viewPerm],
'nodeId' => $node->id(),
];
}
$attached['annotatable_fields'] = $annotatables[$node->getType()]['fields'];
if ($config->get('recogito_integration.custom_annotations')) {
$attached['custom_elements'] = preg_split("/\r?\n/", $config->get('recogito_integration.custom_annotations_elements') ?? '');
}
else {
$attached['custom_elements'] = [];
}
$attachments['#attached']['drupalSettings']['recogito_integration'] = $attached;
switch ($routeName) {
case 'entity.node.canonical':
$attachments['#attached']['library'][] = 'recogito_integration/recogito_user';
break;
case 'recogito_annotate_node':
if ($createPerm && $currentUser->isAuthenticated()) {
$attachments['#attached']['library'][] = 'recogito_integration/recogito_admin';
}
break;
}
}
/**
* Implements hook_node_predelete().
*/
function recogito_integration_node_predelete(NodeInterface $node) {
if ($node->getType() == 'annotation') {
$textualbodies = $node->get('field_annotation_textualbody')->referencedEntities();
foreach ($textualbodies as $textualbody) {
$textualbody->delete();
}
return;
}
$annotations = recogito_integration_get_annotations_for_node($node->id());
foreach ($annotations as $annotation) {
$annotation->delete();
}
}
/**
* Implements hook_taxonomy_term_predelete().
*/
function recogito_integration_taxonomy_term_predelete(TermInterface $term) {
$vocabularyId = $term->bundle();
$config = \Drupal::config('recogito_integration.settings');
$selectedVocab = $config->get('recogito_integration.vocabulary_name') ?? '';
if ($vocabularyId !== $selectedVocab) {
return;
}
$textualBodies = recogito_integration_get_tag_textualbody($term->id());
foreach ($textualBodies as $textualBody) {
$textualBody->delete();
}
}
/**
* Implements hook_form_alter().
*/
function recogito_integration_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
if (preg_match('/^node_.*_(edit|delete)_form$/', $form_id, $matches)) {
$form_type = $matches[1];
$nid = $form_state->getFormObject()->getEntity()->id();
if (!$nid) {
return;
}
$node = $form_state->getFormObject()->getEntity();
if (!$node instanceof NodeInterface) {
return;
}
if ($node->getType() == 'annotation') {
return;
}
if (recogito_integration_get_annotations_for_node($nid)) {
switch ($form_type) {
case 'edit':
$msg = 'IMPORTANT INFORMATION:\n\nThis page contains annotations. Updating this node may affect '
. 'the positioning of existing annotations.\n\nPlease double check the existing annotations after '
. 'updating this page.';
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => 'Save',
'#attributes' => [
'onclick' => 'if(!confirm("' . $msg . '")){return false;}',
],
'#submit' => [
'::submitForm',
'::save',
'menu_ui_form_node_form_submit',
],
'#access' => TRUE,
'#button_type' => 'primary',
'#weight' => 5,
];
break;
case 'delete':
$form['description']['#markup'] = '<strong><u>Warning:</u></strong><br>
This page contains annotations. Deleting this page will also delete all
associated annotations.<br>
<strong>This action cannot be undone!</strong>';
break;
}
}
}
elseif (preg_match('/^taxonomy_term_(.*)_delete_form$/', $form_id, $matches)) {
$config = \Drupal::config('recogito_integration.settings');
$vocab = $config->get('recogito_integration.vocabulary_name') ?? '';
if ($vocab && $matches[1] === $vocab) {
$term = $form_state->getFormObject()->getEntity();
if (recogito_integration_get_tag_textualbody($term->id())) {
$form['description']['#markup'] = '<strong><u>Warning:</u></strong><br>
This term is used for tagging annotations. Deleting this term will
remove the tag from annotations.<br>
<strong>This action cannot be undone!</strong>';
}
}
}
}
/**
* Implements hook_preprocess_field().
*/
function recogito_integration_preprocess_field(array &$variables) {
$config = \Drupal::config('recogito_integration.settings');
$annotatables = $config->get('recogito_integration.annotatables') ?? [];
$contentType = $variables['element']['#bundle'];
if (!isset($annotatables[$contentType]) || !$annotatables[$contentType]['enabled']) {
return;
}
$fieldsAnnotate = $annotatables[$contentType]['fields'] ?? [];
if (in_array($variables['element']['#field_name'], $fieldsAnnotate)) {
$variables['attributes']['annotatable-field-id'] = $variables['element']['#field_name'];
$variables['attributes']['annotatable-type'] = 'field';
}
}
/**
* Implements hook_preprocess_views_view_field().
*/
function recogito_integration_preprocess_views_view_field(array &$variables) {
$field = $variables['field'];
$config = \Drupal::config('recogito_integration.settings');
$annotatables = $config->get('recogito_integration.annotatables') ?? [];
$node = $variables['row']->_entity;
if (!$node instanceof NodeInterface) {
return;
}
$contentType = $node->getType();
if (!isset($annotatables[$contentType]) || !$annotatables[$contentType]['enabled']) {
return;
}
$fieldsAnnotate = $annotatables[$contentType]['fields'] ?? [];
if (in_array($field->field, $fieldsAnnotate)) {
$variables['output'] = [
'#prefix' => '<div annotatable-field-id="' . $field->field . '" annotatable-type="view_field">',
'#suffix' => '</div>',
'#markup' => $variables['output'],
];
}
}
/**
* Validate the style for an annotation.
*
* @param array $style
* The style to validate.
*
* @return array
* The validated style.
*/
function recogito_integration_validate_style(array $style) {
$colorRegex = '/^#(?:[0-9a-fA-F]{3}){1,2}$/';
$lineStyles = ['dotted', 'dashed', 'double', 'solid', 'groove', 'ridge', 'inset', 'outset', 'none'];
$config = \Drupal::config('recogito_integration.settings');
$style['text_color'] = preg_match($colorRegex, $style['text_color']) ? $style['text_color'] : ($config->get('recogito_integration.text_color') ?? '#000000');
$style['background_color'] = preg_match($colorRegex, $style['background_color']) ? $style['background_color'] : ($config->get('recogito_integration.background_color') ?? '#000000');
$style['underline_color'] = preg_match($colorRegex, $style['underline_color']) ? $style['underline_color'] : ($config->get('recogito_integration.underline_color') ?? '#000000');
$style['underline_style'] = in_array($style['underline_style'], $lineStyles) ? $style['underline_style'] : ($config->get('recogito_integration.underline_style') ?? 'none');
$style['underline_stroke'] = is_numeric($style['underline_stroke']) ? $style['underline_stroke'] : ($config->get('recogito_integration.underline_stroke') ?? '0');
$style['background_transparency'] = is_numeric($style['background_transparency']) ? $style['background_transparency'] : ($config->get('recogito_integration.background_transparency') ?? '0');
return $style;
}
/**
* Query Annotation Collection based on URL.
*
* @param int $nodeId
* The node ID.
*/
function recogito_integration_get_annotations_for_node(int $nodeId) {
return \Drupal::entityTypeManager()
->getStorage('node')
->loadByProperties([
'type' => 'annotation',
'field_annotation_node_reference' => $nodeId,
'status' => 1,
]);
}
/**
* Query Textualbody Tags by Taxonomy Term.
*
* @param int $termId
* The term name.
*/
function recogito_integration_get_tag_textualbody(int $termId) {
return \Drupal::entityTypeManager()
->getStorage('paragraph')
->loadByProperties([
'type' => 'annotation_textualbody',
'field_annotation_tag_reference' => $termId,
'field_annotation_purpose' => 'tagging',
]);
}
