modules/stanford_decoupled/stanford_decoupled.module
<?php
/**
* @file
* stanford_decoupled.module
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\graphql\GraphQL\Execution\FieldContext;
use Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeFieldTypeInterface;
use Drupal\paragraphs\ParagraphInterface;
use Drupal\stanford_decoupled\Config\DecoupledConfigOverrides;
use Drupal\views\ViewExecutable;
/**
* Implements hook_next_revalidator_info_alter().
*/
function stanford_decoupled_next_revalidator_info_alter(&$plugins) {
$plugins['path']['class'] = 'Drupal\stanford_decoupled\Plugin\Next\Revalidator\Path';
}
/**
* Implements hook_graphql_compose_field_type_alter().
*/
function stanford_decoupled_graphql_compose_field_type_alter(array &$field_types) {
$field_types['image']['class'] = 'Drupal\stanford_decoupled\Plugin\GraphQLCompose\FieldType\ImageItem';
}
/**
* Implements hook_graphql_compose_graphql_type_alter().
*/
function stanford_decoupled_graphql_compose_graphql_type_alter(array &$entity_types) {
$entity_types['Image']['class'] = 'Drupal\stanford_decoupled\Plugin\GraphQLCompose\SchemaType\ImageType';
}
/**
* Implements hook_library_info_alter().
*/
function stanford_decoupled_library_info_alter(&$libraries, $extension) {
if ($extension == 'editoria11y' && DecoupledConfigOverrides::isDecoupled()) {
// Disable Editoria11y library if the site is decoupled to avoid confusion.
$libraries = [];
}
}
/**
* Implements hook_ENTITY_TYPE_access().
*/
function stanford_decoupled_layout_access(EntityInterface $entity, $operation, AccountInterface $account) {
// Only allow access if the site is decoupled. Otherwise, don't change access.
return AccessResult::allowedIf($operation == 'view' && DecoupledConfigOverrides::isDecoupled());
}
/**
* Implements hook_token_info().
*/
function stanford_decoupled_token_info() {
// Drupal 11.1.0 already has the UUID token. This can be removed at that time.
if (version_compare(\Drupal::VERSION, '11.1', '>=')) {
return [];
}
$entity_types = \Drupal::entityTypeManager()->getDefinitions();
$info = [];
foreach ($entity_types as $entity_id => $entity_type) {
if ($entity_type->getGroup() == 'content') {
$info['tokens'][$entity_id]['uuid'] = [
'name' => t('@entity_id UUID', ['@entity_id' => $entity_type->getLabel()]),
'description' => t('The Universal Unique Identifier of @entity_id', ['@entity_id' => $entity_type->getLabel()]),
];
}
}
return $info;
}
/**
* Implements hook_tokens().
*/
function stanford_decoupled_tokens($type, $tokens, array $data = [], array $options = []) {
$replacements = [];
if (!empty($data[$type]) && $data[$type] instanceof ContentEntityInterface) {
$entity = $data[$type];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'uuid':
$replacements[$original] = $entity->uuid();
break;
}
}
}
return $replacements;
}
/**
* Implements hook_cron().
*/
function stanford_decoupled_cron() {
if (!DecoupledConfigOverrides::isDecoupled()) {
return;
}
$last_run = \Drupal::state()->get('stanford-decoupled-last-ran', 0);
$now = time();
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$query = $node_storage->getQuery()
->accessCheck(FALSE)
->condition('status', TRUE);
$conditions = $query->orConditionGroup();
$start_conditions = $query->andConditionGroup();
$end_conditions = $query->andConditionGroup();
$start_conditions->condition('su_event_date_time.value', $last_run, '>=');
$start_conditions->condition('su_event_date_time.value', $now, '<=');
$end_conditions->condition('su_event_date_time.end_value', $last_run, '>=');
$end_conditions->condition('su_event_date_time.end_value', $now, '<=');
$conditions->condition($start_conditions);
$conditions->condition($end_conditions);
$query->condition($conditions);
$results = $query->execute();
foreach ($node_storage->loadMultiple($results) as $node) {
next_entity_update($node);
}
\Drupal::state()->set('stanford-decoupled-last-ran', $now);
}
/**
* Implements hook_entity_insert().
*/
function stanford_decoupled_entity_insert(EntityInterface $entity) {
_stanford_decoupled_entity_changes($entity);
}
/**
* Implements hook_entity_update().
*/
function stanford_decoupled_entity_update(EntityInterface $entity) {
_stanford_decoupled_entity_changes($entity);
}
/**
* Implement hook_entity_delete().
*/
function stanford_decoupled_entity_delete(EntityInterface $entity) {
_stanford_decoupled_entity_changes($entity);
}
/**
* Trigger Next module for on-demand invalidation for referencing entities.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Inserted, updated, or deleted entity.
*/
function _stanford_decoupled_entity_changes(EntityInterface $entity) {
$tracked_types = \Drupal::config('stanford_decoupled.settings')
->get('referenced_invalidated_types') ?: ['taxonomy_term', 'media'];
if (
!in_array($entity->getEntityTypeId(), $tracked_types) ||
!$entity instanceof ContentEntityInterface ||
!DecoupledConfigOverrides::isDecoupled()
) {
return;
}
$entity_type_manager = \Drupal::entityTypeManager();
$field_storage = $entity_type_manager->getStorage('field_storage_config');
/** @var \Drupal\Core\Queue\QueueFactoryInterface $queue_factory */
$queue_factory = \Drupal::service('queue');
$queue = $queue_factory->get('decoupled_referenced_invalidator');
// Loop through the fields that use the changed entity as a possible target.
// We'll then query to find entities that do reference the entity, and call
// the Next functions to trigger any invalidations that are configured.
/** @var \Drupal\field\FieldStorageConfigInterface[] $reference_fields */
$reference_fields = $field_storage->loadByProperties(['settings' => ['target_type' => $entity->getEntityTypeId()]]);
foreach ($reference_fields as $field) {
$entity_storage = $entity_type_manager->getStorage($field->getTargetEntityTypeId());
$referencing_ids = $entity_storage->getQuery()
->accessCheck(FALSE)
->condition($field->getName(), $entity->id())
->execute();
foreach ($referencing_ids as $referencing_id) {
$queue->createItem([
'entity_type' => $field->getTargetEntityTypeId(),
'id' => $referencing_id,
]);
}
}
}
/**
* Implements hook_graphql_compose_field_results_alter().
*/
function stanford_decoupled_graphql_compose_field_results_alter(array &$results, $entity, GraphQLComposeFieldTypeInterface $plugin, FieldContext $context) {
$field_definition = $plugin->getFieldDefinition();
if ($field_definition->getName() == 'layout_selection') {
foreach ($results as &$result) {
$result = ['id' => $result->id(), 'label' => $result->label()];
}
}
foreach ($results as $result) {
if ($result instanceof ParagraphInterface) {
$behaviors = $result->getAllBehaviorSettings();
$result->set('behavior_settings', $behaviors ? json_encode($behaviors) : NULL);
}
}
}
/**
* Implements hook_graphql_compose_entity_base_fields_alter().
*/
function stanford_decoupled_graphql_compose_entity_base_fields_alter(array &$fields, string $entity_type_id) {
if ($entity_type_id == 'paragraph') {
$fields['behavior_settings'] = [
'field_type' => 'string',
'name_sdl' => 'behaviors',
'required' => FALSE,
'description' => t('Paragraph Behavior Settings.'),
];
}
}
/**
* Implements hook_ENTITY_TYPE_insert().
*
* When a new Next site is created, create all Next entity type configs.
*/
function stanford_decoupled_next_site_insert(EntityInterface $entity) {
Cache::invalidateTags(['library_info']);
$next_storage = \Drupal::entityTypeManager()
->getStorage('next_entity_type_config');
$node_types = \Drupal::entityTypeManager()
->getStorage('node_type')
->loadMultiple();
// Create each of the node type bundle configs.
foreach (array_keys($node_types) as $node_bundle) {
// Make sure one doesn't already exist.
if (!$next_storage->load("node.$node_bundle")) {
$next_storage->create([
'id' => "node.$node_bundle",
'site_resolver' => 'site_selector',
'revalidator' => 'path',
'configuration' => [
'sites' => [$entity->id() => $entity->id()],
],
'revalidator_configuration' => [
'revalidate_page' => TRUE,
'additional_paths' => "/tags/views:all/views:$node_bundle",
],
])->save();
}
}
if (!$next_storage->load('redirect.redirect')) {
$next_storage->create([
'id' => 'redirect.redirect',
'site_resolver' => 'site_selector',
'revalidator' => 'redirect_path',
'configuration' => ['sites' => [$entity->id() => $entity->id()]],
'revalidator_configuration' => [],
])->save();
}
if (!$next_storage->load('menu_link_content.menu_link_content')) {
$next_storage->create([
'id' => 'menu_link_content.menu_link_content',
'site_resolver' => 'site_selector',
'revalidator' => 'path',
'configuration' => ['sites' => [$entity->id() => $entity->id()]],
'revalidator_configuration' => [
'revalidate_page' => FALSE,
'additional_paths' => '/tags/menu:main',
],
])->save();
}
$config_page_types = [
'lockup_settings',
'stanford_global_message',
'stanford_local_footer',
'stanford_basic_site_settings',
'stanford_super_footer',
];
// Create each of the node type bundle configs.
foreach ($config_page_types as $config_page_type) {
// Make sure one doesn't already exist.
if (!$next_storage->load("config_pages.$config_page_type")) {
$next_storage->create([
'id' => "config_pages.$config_page_type",
'site_resolver' => 'site_selector',
'revalidator' => 'path',
'configuration' => [
'sites' => [$entity->id() => $entity->id()],
],
'revalidator_configuration' => [
'revalidate_page' => FALSE,
'additional_paths' => "/tags/config-pages",
],
])->save();
}
}
}
/**
* Implements hook_ENTITY_TYPE_access().
*/
function stanford_decoupled_redirect_access(EntityInterface $entity, $operation, AccountInterface $account) {
if ($operation == 'view' && DecoupledConfigOverrides::isDecoupled()) {
// Allowing viewing redirecting from JSON API.
return AccessResult::allowed();
}
return AccessResult::neutral();
}
/**
* Implements hook_next_site_preview_alter().
*/
function stanford_decoupled_next_site_preview_alter(array &$preview, array $context) {
// Only use the preview for nodes. Prevent the preview from any other entity
// type that might have a revalidation configured, like redirects.
if ($context['entity']->getEntityTypeid() != 'node') {
$preview = $context['original_build'][0]['content'];
}
if (isset($preview['toolbar']['links']['#links']['live_link']['url'])) {
/** @var \Drupal\Core\Url $url */
$url = $preview['toolbar']['links']['#links']['live_link']['url'];
$options = $url->getOptions();
// No need for all the other parameters.
if (isset($options['query']['slug'])) {
$options['query'] = [
'slug' => $options['query']['slug'],
'secret' => $options['query']['secret'],
];
$url->setOptions($options);
}
}
}
/**
* Implements hook_preprocess_HOOK().
*/
function stanford_decoupled_preprocess_image(&$variables) {
$decoupled = DecoupledConfigOverrides::isDecoupled();
if ($decoupled && (empty($variables['width']) || empty($variables['height']))) {
$path = str_starts_with($variables['uri'], '/') ? DRUPAL_ROOT . $variables['uri'] : $variables['uri'];
$path = preg_replace('/\?.*$/', '', $path);
if ($size = @getimagesize($path)) {
$variables['attributes']['data-width'] = $size[0];
$variables['attributes']['data-height'] = $size[1];
}
}
}
/**
* Implements hook_views_pre_execute().
*/
function stanford_decoupled_views_pre_execute(ViewExecutable $view) {
$route = \Drupal::routeMatch()->getRouteName();
if (!($route == 'entity.node.canonical' && DecoupledConfigOverrides::isDecoupled())) {
return;
}
$current_limit = $view->query->getLimit();
if ($current_limit <= 0 || $current_limit > 5) {
$view->query->setLimit(30);
}
}
/**
* Implements hook_preprocess_HOOK().
*/
function stanford_decoupled_preprocess_file_link(&$variables) {
if (DecoupledConfigOverrides::isDecoupled()) {
$variables['link']['#url']->setAbsolute();
}
}