vp-1.0.x-dev/vp.module
vp.module
<?php
/**
* @file
* Primary module hooks for VP module.
*/
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Markup;
use Drupal\Core\Url;
use Drupal\user\UserInterface;
/**
* Prepares variables for virtual patient templates.
*
* Default template: virtual-patient.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the virtual patient
* - information and any fields attached to the entity.
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_virtual_patient(array &$variables) {
$variables['view_mode'] = $variables['elements']['#view_mode'];
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
/**
* Implements hook_user_cancel().
*/
function vp_user_cancel($edit, UserInterface $account, $method) {
switch ($method) {
case 'user_cancel_block_unpublish':
// Unpublish virtual patients.
$storage = \Drupal::entityTypeManager()->getStorage('virtual_patient');
$virtual_patient_ids = $storage->getQuery()
->condition('uid', $account->id())
->condition('status', 1)
->accessCheck(FALSE)
->execute();
foreach ($storage->loadMultiple($virtual_patient_ids) as $virtual_patient) {
$virtual_patient->set('status', FALSE);
$virtual_patient->save();
}
break;
case 'user_cancel_reassign':
// Anonymize virtual patients.
$storage = \Drupal::entityTypeManager()->getStorage('virtual_patient');
$virtual_patient_ids = $storage->getQuery()
->condition('uid', $account->id())
->execute();
foreach ($storage->loadMultiple($virtual_patient_ids) as $virtual_patient) {
$virtual_patient->setOwnerId(0);
$virtual_patient->save();
}
break;
}
}
/**
* Implements hook_ENTITY_TYPE_predelete() for user entities.
*/
function vp_user_predelete(UserInterface $account) {
// Delete virtual patients.
$storage = \Drupal::entityTypeManager()->getStorage('virtual_patient');
$virtual_patient_ids = $storage->getQuery()
->condition('uid', $account->id())
->accessCheck(FALSE)
->execute();
$virtual_patients = $storage->loadMultiple($virtual_patient_ids);
$storage->delete($virtual_patients);
}
/**
* Implements hook_ENTITY_TYPE_predelete().
*/
function vp_virtual_patient_predelete(EntityInterface $entity) {
// Delete virtual patients.
$storage = \Drupal::entityTypeManager()->getStorage('vp_node');
$virtual_patient_node_ids = $storage->getQuery()
->condition('field_parent', $entity->id())
->accessCheck(FALSE)
->execute();
$virtual_patient_nodes = $storage->loadMultiple($virtual_patient_node_ids);
$storage->delete($virtual_patient_nodes);
}
/**
* Implements hook_FORM_ID_alter().
*/
function vp_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$service = \Drupal::service('vp.service');
$language = \Drupal::languageManager()->getCurrentLanguage();
$langcode = $language->getId();
// @todo Use some more general method here
$content_language = $service->getContentLangcode();
if ($form_id == 'vp_node_edit_form') {
$form_object = $form_state->getFormObject();
if ($form_object) {
$node = $form_object->getEntity();
if ($node) {
$original = $node->getUntranslated()->language()->getId();
if ($content_language && $original !== $content_language) {
$form['field_options']['#disabled'] = TRUE;
}
}
}
}
if ($form_id == 'vp_node_add_form' || $form_id == 'vp_node_edit_form') {
$form_object = $form_state->getFormObject();
$entity = $form_object->getEntity();
if (!$entity) {
return;
}
$original = $entity->getUntranslated()->language()->getId();
$form['field_options']['#states'] = [
'disabled' => [
':input[name="field_terminal_node[value]"]' => ['checked' => TRUE],
],
];
if ($content_language && $original !== $content_language) {
$form['field_options']['#attributes']['class'][] = 'input-disabled';
}
$form['langcode']['#disabled'] = TRUE;
$form['#attached']['library'][] = 'vp/vp';
$form['status']['#access'] = FALSE;
$form['created']['#access'] = FALSE;
$form['uid']['#access'] = FALSE;
$form['actions']['delete']['#access'] = FALSE;
$form['actions']['submit']['#submit'][] = '_vp_node_submit_handler';
$form['field_parent']['#access'] = FALSE;
$form['field_parent']['widget'][0]['#attributes'] = ['disabled' => TRUE];
$form['field_root_node']['#access'] = FALSE;
$form['field_subtitle']['widget'][0]['value']['#description'] = t('If subtitle exists, the node page will show this instead of the title.');
$parent = \Drupal::request()->query->get('nid');
if ($parent && $form_id == 'vp_node_add_form') {
$storage = \Drupal::entityTypeManager()->getStorage('virtual_patient');
$parent = $storage->load($parent);
if ($parent) {
$form['info'] = [
'#type' => 'item',
'#markup' => Markup::create('<div class="vp-label"><em><strong>' . $parent->label() . '</strong></em></div>'),
'#weight' => -99,
];
$form['field_parent']['widget'][0]['target_id']['#default_value'] = $parent;
}
}
}
}
/**
* {@inheritdoc}
*/
function _vp_node_submit_handler(array &$form, FormStateInterface $form_state) {
$form_object = $form_state->getFormObject();
if ($form_object) {
$entity = $form_object->getEntity();
/** @var \Drupal\vp\Entity\VirtualPatientNode $entity .*/
$parent = $entity ? $entity->field_parent->entity : NULL;
if ($parent) {
$url = $parent->toUrl('edit-form');
$form_state->setRedirectUrl($url);
}
}
}
/**
* Implements hook_entity_type_insert().
*/
function vp_vp_node_insert(EntityInterface $entity) {
/** @var \Drupal\vp\Entity\VirtualPatientNode $entity */
$parent = $entity->field_parent->entity;
if ($parent) {
$parent->field_vp_nodes[] = ['target_id' => $entity->id()];
$parent->save();
}
}
/**
* Implements hook_FORM_ID_alter().
*/
function vp_form_virtual_patient_add_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$form['#attached']['library'][] = 'vp/vp';
$form['created']['#access'] = FALSE;
$form['uid']['#access'] = FALSE;
$form['actions']['submit']['#value'] = t('Create new virtual patient');
// Disabled this by default.
$form['status']['widget']['value']['#default_value'] = FALSE;
$form['status']['#access'] = FALSE;
}
/**
* Implements hook_FORM_ID_alter().
*/
function vp_form_virtual_patient_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$form['status']['widget']['value']['#title'] = t('Published');
$form['field_description']['#weight'] = -99;
$form['status']['#weight'] = 99;
$form['uid']['#access'] = FALSE;
$form['created']['#access'] = FALSE;
$language = \Drupal::languageManager()->getCurrentLanguage();
$langcode = $language->getId();
$service = \Drupal::service('vp.service');
$form['#attached']['library'][] = 'vp/vp';
$header = [
'id' => t('ID'),
'label' => t('Label'),
'paths' => t('Paths'),
'root' => t('Root'),
'terminal' => t('Terminal'),
'actions' => t('Actions'),
'warnings' => '',
];
$rows = [];
/** @var \Drupal\Core\Entity\EntityForm $form_object */
$form_object = $form_state->getFormObject();
$entity = $form_object ? $form_object->getEntity() : NULL;
if (!$entity) {
return;
}
$entity = $entity->getUntranslated();
$vp_original_langcode = $entity->language()->getId();
$content_language = $service->getContentLangcode();
$nodes = $entity->field_vp_nodes->referencedEntities();
foreach ($nodes as $node) {
$current_path = \Drupal::service('path.current')->getPath();
$original_langcode = $node->getUntranslated()->language()->getId();
$options = ['language' => $language];
$add_translation_url = $node->toUrl('drupal:content-translation-add', $options)
->setRouteParameter('source', $original_langcode)
->setRouteParameter('target', $content_language);
$edit_translation_url = $node->toUrl('drupal:content-translation-edit', $options)
->setRouteParameter('language', $content_language);
$delete_translation_url = $node->toUrl('drupal:content-translation-delete', $options)
->setRouteParameter('language', $content_language);
$paths_count = count($node->field_options->referencedEntities());
$row['id'] = $node->id();
$label_translated = $node->hasTranslation($content_language) ? $node->getTranslation($content_language)->label() : $node->label();
$row['label'] = $label_translated;
$tooltip = '';
if ($paths_count) {
foreach ($node->field_options->referencedEntities() as $option) {
if ($content_language && $option->hasTranslation($content_language)) {
$option = $option->getTranslation($content_language);
}
$ellipsis = strlen($option->label()) > 145 ? '...' : '';
$tooltip .= '• ' . substr($option->label(), 0, 145) . $ellipsis . '<br />';
}
$row['paths'] = Markup::create("<span class='hover-text'><i class='bi bi-shuffle'></i> <strong>{$paths_count}</strong><span class='tooltip-text-options'>{$tooltip}</span></span>");
}
else {
$row['paths'] = '-';
}
$row['root'] = $node->field_root_node->value ? '✅' : '-';
$row['terminal'] = $node->field_terminal_node->value ? '✅' : '-';
$operations = [];
if ($node->access('update') && $node->hasLinkTemplate('edit-form')) {
if ($content_language && $original_langcode != $content_language) {
$current_path = $current_path . "?language_content_entity=" . $content_language;
}
if ($content_language && !$node->hasTranslation($content_language)) {
$operations['translate'] = [
'title' => t('Add translation'),
'weight' => 0,
'attributes' => [
'class' => [
'use-ajax',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 880,
]),
],
'url' => $add_translation_url->setOption('query', [
'language_content_entity' => $content_language,
'nid' => $entity->id(),
'destination' => $current_path,
]),
];
}
else {
$label = t('Edit');
$url = $node->toUrl('edit-form')->setOption('query', [
'language_content_entity' => $content_language,
'nid' => $entity->id(),
'destination' => $current_path,
]);
if ($content_language && $original_langcode !== $content_language) {
$label = t('Edit translation');
$url = $node->toUrl('edit-form')->setOption('query', [
'language_content_entity' => $content_language,
'nid' => $entity->id(),
'destination' => $current_path,
]);
}
$operations['edit'] = [
'title' => $label,
'weight' => 1,
'attributes' => [
'class' => [
'use-ajax',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 880,
]),
],
'url' => $url,
];
}
}
if ($node->access('delete') && $node->hasLinkTemplate('delete-form')) {
$label = t('Delete');
$url = $node->toUrl('delete-form')->setOption('query', [
'nid' => $entity->id(),
'destination' => $current_path,
]);
if ($content_language && $langcode !== $content_language) {
$label = t('Delete translation');
$url = $node->toUrl('delete-form')->setOption('query', [
'destination' => $current_path,
'nid' => $entity->id(),
'language_content_entity' => $content_language,
]);
}
$operations['delete'] = [
'title' => $label,
'weight' => 5,
'attributes' => [
'class' => [
'use-ajax',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 880,
]),
],
'url' => $url,
];
}
$row['actions'] = [
'data' => [
'#type' => 'operations',
'#links' => $operations,
],
];
$terminal_check_pass = (int) $node->field_terminal_node->value === 0 ? $paths_count > 0 : TRUE;
// @todo
$tooltip = t('You need either to have options or define the node as terminal');
$row['warnings'] = $terminal_check_pass ?
Markup::create('<i class="bi bi-check" style="color: darkgreen"></i>') :
Markup::create("<span class='hover-text'><i class='bi bi-exclamation-triangle' style='color: orange'></i><span class='tooltip-text'>{$tooltip}</span></span>");
$rows[] = $row;
}
$form['vp_nodes_root'] = [
'#type' => 'details',
'#title' => t('Root node'),
'#open' => TRUE,
'#weight' => 1,
];
$root_node = $service->getRootNode($entity);
if (!$root_node) {
$warning = "<p style='color:orange'> <i class='bi bi-exclamation-triangle-fill'></i> WARNING: <em>" . t("You don't have a root node defined.") . "</em></p>";
$form['vp_nodes_root']['warning'] = [
'#type' => 'markup',
'#markup' => Markup::create($warning),
];
}
if ($root_node) {
$root_label = $root_node->label();
if ($root_node->hasTranslation($content_language)) {
$translated_root_node = $root_node->getTranslation($content_language);
$root_label = $translated_root_node->label();
}
$form['vp_nodes_root']['title'] = [
'#type' => 'markup',
'#markup' => Markup::create("<p style='margin: 18px'><li> {$root_label} (ID: {$root_node->id()})</li></p>"),
];
}
$add_root['vp_root_add'] = [
'title' => $root_node ? t('Select root node') : t('Add root node'),
'attributes' => [
'class' => [
'use-ajax button button--primary js-form-submit form-submit',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 880,
]),
],
'url' => Url::fromRoute('vp.add.root.node', ['virtual_patient' => $entity->id()]),
];
$form['vp_nodes_root']['vp_nodes_root_add'] = [
'#type' => 'operations',
'#links' => $add_root,
];
$form['vp_nodes'] = [
'#type' => 'details',
'#title' => t('Nodes List'),
'#open' => TRUE,
'#weight' => 2,
];
if ($entity && $vp_original_langcode == $content_language) {
$add['vp'] = [
'title' => t('Create new'),
'attributes' => [
'class' => [
'use-ajax button button--primary js-form-submit form-submit',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 880,
]),
],
'url' => Url::fromRoute('entity.vp_node.add_form')->setOption('query', [
'destination' => $current_path,
'nid' => $entity->id(),
]),
];
$form['vp_nodes']['continue_button'] = [
'#type' => 'operations',
'#links' => $add,
];
}
else {
$form['vp_nodes']['info_vp_nodes'] = [
'#type' => 'markup',
'#markup' => Markup::create(t('<em>You can create nodes in the original language only (@label)</em>', [
'@label' => strtoupper($entity->getUntranslated()->language()->getName()),
])),
];
}
$form['vp_nodes']['vp_list'] = [
'#title' => t('Nodes list'),
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => t('Currently, there are no VP nodes.'),
];
}
/**
* Implements hook_ENTITY_VIEW_alter().
*/
function vp_virtual_patient_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
// Hide vp nodes, we want a clean view.
unset($build['field_vp_nodes']);
/** @var \Drupal\vp\Entity\VirtualPatient $entity */
$build['created']['#access'] = FALSE;
$build['field_description']['#access'] = FALSE;
$build['field_vp_image']['#access'] = FALSE;
$build['uid']['#access'] = FALSE;
$build['#attached']['library'][] = 'vp/preview';
$build['preview'] = [
'#type' => 'markup',
'#markup' => "<div class='vp-preview'><div class='vp-preview-content'>Preview</div></div>",
];
$status = (boolean) $entity->status->value;
$config = \Drupal::service('config.factory')->getEditable('vp.settings');
$player_url = $config->get('player_url');
$uuid = $entity->uuid->value;
$uid = \Drupal::currentUser()->id();
$langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
$content_langcode = \Drupal::service('vp.service')->getContentLangcode();
$preview_url = "{$player_url}/play?uuid={$uuid}&lang={$content_langcode}&uid={$uid}&preview=true";
$translated = $entity->getTranslation($langcode);
$translation_published = $translated->status->value;
if (empty($player_url)) {
$warning_message = Markup::create(t('<div class="warning-message--player"><strong><a href="@link">Add a player URL</a></strong> <br />to preview the virtual patient.</div>', [
'@link' => Url::fromRoute('vp.settings_form')->toString(),
]));
$build['preview'] = [
'#type' => 'markup',
'#markup' => "<div class='vp-preview'><div class='vp-preview-content'>{$warning_message}</div></div>",
];
}
else {
$build['preview'] = [
'#type' => 'inline_template',
'#template' => '<div class="vp-preview"><iframe class="vp-preview-content" src="' . $preview_url . '" frameborder="0"></iframe></div>',
];
}
$build['status'] = [
'#type' => 'details',
'#title' => t('Published status'),
'#open' => TRUE,
'#attributes' => [
'class' => [
'container-right',
],
],
];
$build['status']['info'] = [
'#type' => 'markup',
'#markup' => $status ? Markup::create("<p><i class='bi bi-check-square' style='color: green'></i> This content is <strong>published</strong></p>") : Markup::create("<p>This content is <strong>not published</strong></p>"),
];
$build['status']['translation_info'] = [
'#type' => 'markup',
'#markup' => $translation_published ?
Markup::create("<p><i class='bi bi-check-square' style='color: green'></i> This translation of this content is <strong>published</strong></p>") :
Markup::create("<p>This translation of the content is <strong>not published</strong></p>"),
];
$build['status']['publish'] = [
'#type' => 'link',
'#title' => $status ? t('Unpublish (all languages)') : ('Publish (all languages)'),
'#url' => Url::fromRoute('vp.settings_form'),
'#attributes' => [
'class' => [
'button button--primary js-form-submit form-submit',
],
],
'#ajax' => [
'dialogType' => 'modal',
'dialog' => ['height' => 400, 'width' => 700],
],
];
}
/**
* Implements hook_module_implements_alter().
*/
function vp_module_implements_alter(&$implementations, $hook) {
switch ($hook) {
case 'form_virtual_patient_edit_form_alter':
$group = $implementations['vp'];
unset($implementations['vp']);
$implementations['vp'] = $group;
break;
}
}
/**
* Implements hook_entity_type_translation_delete().
*/
function vp_virtual_patient_translation_delete(EntityInterface $translation) {
$langcode = $translation->language()->getId();
$nodes = $translation->field_vp_nodes->referencedEntities();
foreach ($nodes as $node) {
if ($node->hasTranslation($langcode)) {
$untranslated_node = $node->getUntranslated();
$untranslated_node->removeTranslation($langcode);
$untranslated_node->save();
}
}
}
