stanford_profile_helper.module
<?php
/**
* @file
* stanford_profile_helper_helper.module
*/
use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\Element;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Drupal\search_api\IndexInterface;
use Drupal\stanford_profile_helper\StanfordProfileHelper;
use Drupal\taxonomy\TermInterface;
use Drupal\taxonomy_menu\Plugin\Menu\TaxonomyMenuMenuLink;
use Drupal\user\RoleInterface;
use Drupal\views\ViewEntityInterface;
/**
* Implements hook_preprocess_HOOK().
*/
function stanford_profile_helper_preprocess_html(&$variables) {
$module_handler = \Drupal::moduleHandler();
$environment = 'sws-other';
if ($module_handler->moduleExists('acquia_purge')) {
$environment = 'sws-acquia';
}
if ($module_handler->moduleExists('acsf')) {
$environment = 'sws-acsf';
}
// Add a class to the body tag to identify sws applications.
$variables['attributes']['class'][] = $environment;
}
/**
* Implements hook_module_implements_alter().
*/
function stanford_profile_helper_module_implements_alter(&$implementations, $hook) {
// Only for test scenarios, disable all hooks in the next module since they
// cause errors with the containers in unit tests. It's just easier to remove
// the hooks than to work around the errors.
if (Settings::get('STANFORD_PROFILE_HELPER_DISABLE_NEXT')) {
unset($implementations['next']);
}
if ($hook === 'plugin_filter_block__layout_builder_alter') {
unset($implementations['menu_block']);
}
}
/**
* Implements hook_plugin_filter_TYPE__CONSUMER_alter().
*
* Curate the blocks available in the Layout Builder "Add Block" UI.
*/
function stanford_profile_helper_plugin_filter_block__layout_builder_alter(array &$definitions, array $extra) {
foreach ($definitions as &$definition) {
if ($definition['provider'] == 'menu_block') {
// Change the category for blocks provided by the menu block module so it
// is separate from the "system" menus.
$definition['category'] = t('Menu Block');
}
}
}
/**
* Implements hook_entity_delete().
*
*/
/**
* Implements hook_ENTITY_TYPE_update().
*/
function stanford_profile_helper_node_update(NodeInterface $node) {
if (!$node->isPublished() && $node->original?->isPublished() && \Drupal::hasService('search_api_algolia.helper')) {
// Trigger algolia to delete the record.
\Drupal::service('search_api_algolia.helper')->entityDelete($node);
}
}
/**
* Implements hook_cron().
*/
function stanford_profile_helper_cron() {
if (!\Drupal::hasService('config_pages.loader')) {
return;
}
/** @var \Drupal\config_pages\ConfigPagesLoaderServiceInterface $config_pages */
$config_pages = \Drupal::service('config_pages.loader');
$ally = $config_pages->getValue('stanford_basic_site_settings', 'su_site_a11y_contact', [], 'value');
$canonical_url = $config_pages->getValue('stanford_basic_site_settings', 'su_site_url', 0, 'uri');
$created = (int) $config_pages->getValue('stanford_basic_site_settings', 'su_site_created', 0, 'value');
$org_ids = $config_pages->getValue('stanford_basic_site_settings', 'su_site_org', [], 'target_id');
$owners = $config_pages->getValue('stanford_basic_site_settings', 'su_site_owner_contact', [], 'value');
$renewal_date = $config_pages->getValue('stanford_basic_site_settings', 'su_site_renewal_due', 0, 'value');
$site_managers = $config_pages->getValue('stanford_basic_site_settings', 'su_site_tech_contact', [], 'value');
$site_type = $config_pages->getValue('stanford_basic_site_settings', 'su_site_type', 0, 'value');
$orgs = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadMultiple($org_ids);
foreach ($orgs as &$org) {
$org = $org->label();
}
$site_information = [
'accessibility' => $ally ?: [],
'canonicalUrl' => $canonical_url,
'created' => $created,
'organizations' => array_values($orgs) ?: [],
'owners' => $owners ?: [],
'renewalDate' => $renewal_date,
'siteManagers' => $site_managers ?: [],
'siteName' => \Drupal::config('system.site')->get('name'),
'siteType' => $site_type,
'theme' => \Drupal::config('system.theme')->get('default'),
];
$uri = 'private://stanford';
$directory = \Drupal::service('stream_wrapper_manager')->normalizeUri($uri);
$file_system = \Drupal::service('file_system');
$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
$file_system->saveData(json_encode($site_information, JSON_PRETTY_PRINT), "$directory/site-info.json", FileExists::Replace);
}
/**
* Implements hook_entity_type_alter().
*/
function stanford_profile_helper_entity_type_alter(array &$entity_types) {
if (isset($entity_types['menu_link_content'])) {
$entity_types['menu_link_content']->addConstraint('menu_link_item_url_constraint');
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function stanford_profile_helper_form_taxonomy_overview_terms_alter(&$form, FormStateInterface $form_state) {
if ($form_state->get('taxonomy')['vocabulary']->id() == 'stanford_publication_topics') {
$url = Url::fromUri('https://userguide.sites.stanford.edu/tour/publications#publications-list-page');
$link = Link::fromTextAndUrl(t('default Publications List Page'), $url)
->toString();
$form['citation_format']['#title'] = t('Citation Style');
$form['citation_format']['#description'] = t('Select citation format for the %link. *<strong>CAUTION</strong>: The default Publication list page uses Chicago as the citation style. If you select a different citation format here, you should also update the citation format on the default Publications List Page that uses a "filter by topics" menu.', ['%link' => $link]);
}
}
/**
* Implements hook_ENTITY_TYPE_presave().
*/
function stanford_profile_helper_redirect_presave(EntityInterface $redirect) {
// Purge everything for the source url so that it can redirect without any
// intervention.
if (\Drupal::moduleHandler()->moduleExists('purge_processor_lateruntime')) {
$source = $redirect->get('redirect_source')->getString();
_stanford_profile_helper_purge_path($source);
}
}
/**
* Purges a relative path using the generated absolute url.
*
* @param string $path
* Drupal site relative path.
*
* @throws \Drupal\purge\Plugin\Purge\Invalidation\Exception\InvalidExpressionException
* @throws \Drupal\purge\Plugin\Purge\Invalidation\Exception\MissingExpressionException
* @throws \Drupal\purge\Plugin\Purge\Invalidation\Exception\TypeUnsupportedException
*/
function _stanford_profile_helper_purge_path($path) {
$url = Url::fromUserInput('/' . trim($path, '/'), ['absolute' => TRUE])
->toString(TRUE)->getGeneratedUrl();
$purgeInvalidationFactory = \Drupal::service('purge.invalidation.factory');
$purgeProcessors = \Drupal::service('purge.processors');
$purgePurgers = \Drupal::service('purge.purgers');
$processor = $purgeProcessors->get('lateruntime');
$invalidations = [$purgeInvalidationFactory->get('url', $url)];
try {
$purgePurgers->invalidate($processor, $invalidations);
}
catch (\Exception $e) {
\Drupal::logger('stanford_profile_helper')->error($e->getMessage());
}
}
/**
* Implements hook_page_attachments().
*/
function stanford_profile_helper_page_attachments(array &$attachments) {
$env = getenv('AH_SITE_ENVIRONMENT');
// Add SiteImprove analytics for anonymous users on prod sites.
// ACE prod is 'prod'; ACSF can be '01live', '02live', ...
if (
\Drupal::currentUser()->isAnonymous() &&
($env === 'prod' || preg_match('/^\d*live$/', $env))
) {
$attachments['#attached']['library'][] = 'stanford_profile_helper/siteimprove.analytics';
}
}
/**
* Implements hook_filter_info_alter().
*/
function stanford_profile_helper_filter_info_alter(&$info) {
if (
isset($info['filter_mathjax']) &&
\Drupal::moduleHandler()->moduleExists('mathjax')
) {
$info['filter_mathjax']['class'] = 'Drupal\stanford_profile_helper\Plugin\Filter\Mathjax';
}
}
/**
* Implements hook_theme().
*/
function stanford_profile_helper_theme($existing, $type, $theme, $path) {
$themes['block__stanford_basic_search'] = [
'template' => 'block--stanford-basic-search',
'original hook' => 'block',
];
$themes['rabbit_hole_message'] = [
'variables' => ['destination' => NULL],
];
return $themes;
}
/**
* Implements hook_library_info_alter().
*/
function stanford_profile_helper_library_info_alter(&$libraries, $extension) {
if ($extension == 'views') {
$libraries['views.ajax']['dependencies'][] = 'stanford_profile_helper/ajax_views';
}
if ($extension == 'mathjax') {
$libraries['source']['dependencies'][] = 'stanford_profile_helper/mathjax';
unset($libraries['setup'], $libraries['config']);
}
// Rely on the fontawesome module to provide the library.
if (
$extension == 'stanford_basic' &&
\Drupal::moduleHandler()->moduleExists('fontawesome')
) {
unset($libraries['fontawesome']);
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function stanford_profile_helper_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
unset($form['actions']['unlock'], $form['scheduler_settings']);
$node = $form_state->getBuildInfo()['callback_object']->getEntity();
$system_pages = \Drupal::config('system.site')->get('page');
$access = !in_array('/node/' . $node->id(), $system_pages);
$scheduler_increment = \Drupal::state()
->get('stanford_profile_helper.scheduler_increment', 60 * 60 * 4);
$hours = (int) floor($scheduler_increment / 3600);
$mins = (int) floor($scheduler_increment / 60 % 60);
$scheduler_increment = $hours ? "$hours hour(s)" : "$mins minute(s)";
$example_start = new DateTime('today 8:00 AM');
$example_end = clone $example_start;
if ($hours > 0) {
$example_end->modify("+$hours hours");
}
$example_end->modify("+$mins minutes");
$help_text = [
t('Select a date and time* to publish this content in the future.'),
t('After scheduling the publish, it will automatically publish to your site on the scheduled date within @times of the selected time.', [
'@times' => $scheduler_increment,
]),
t('For example, if you select @start as the publish time, the content will be published between @start and @end.', [
'@start' => $example_start->format('H:i'),
'@end' => $example_end->format('H:i'),
]),
t('<p><strong>*Note</strong>: You must select a time that is increments of @times, starting with 12AM.</p>', [
'@times' => $scheduler_increment,
]),
];
$form['scheduling'] = [
'#type' => 'container',
'#group' => 'revision_information',
'#access' => (isset($form['unpublish_on']) || isset($form['unpublish_on'])) && !in_array('/node/' . $node->id(), $system_pages),
'help' => ['#markup' => implode(' ', $help_text)],
'#weight' => 999,
];
if (isset($form['unpublish_on'])) {
$form['unpublish_on']['#group'] = 'revision_information';
$form['unpublish_on']['#weight'] = 55;
$form['scheduling']['unpublish_on'] = $form['unpublish_on'];
}
if (isset($form['publish_on'])) {
$form['publish_on']['#group'] = 'revision_information';
$form['publish_on']['#weight'] = 50;
$form['unpublish_on']['#access'] = $access;
$status_element = &$form['status']['widget']['value'];
$status_element['#states'] = [
'disabled' => [':input[name="publish_on[0][value][time]"]' => ['filled' => TRUE]],
];
$form['scheduling']['publish_on'] = $form['publish_on'];
}
unset($form['publish_on'], $form['unpublish_on']);
}
/**
* Implements hook_form_alter().
*/
function stanford_profile_helper_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if (strpos($form_id, 'views_form_') === 0) {
// Remove the select all since it selects every node, not just the ones
// from the active filters.
// @link https://www.drupal.org/project/views_bulk_operations/issues/3055770#comment-13116724
unset($form['header']['views_bulk_operations_bulk_form']['select_all']);
// Sort the action menu options alphabetically.
if (!empty($form['header']['views_bulk_operations_bulk_form']['action']['#options'])) {
$actions_array = $form['header']['views_bulk_operations_bulk_form']['action']['#options'];
uasort($actions_array, function ($a, $b) {
return strcasecmp((string) $a, (string) $b);
});
$form['header']['views_bulk_operations_bulk_form']['action']['#options'] = $actions_array;
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function stanford_profile_helper_form_views_bulk_operations_configure_action_alter(&$form, FormStateInterface $form_state, $form_id) {
if (!empty($form['node']['stanford_event']['su_event_date_time']['widget'])) {
$form['node']['stanford_event']['su_event_date_time']['widget'][0]['time_wrapper']['value']['#required'] = FALSE;
$form['node']['stanford_event']['su_event_date_time']['widget'][0]['time_wrapper']['end_value']['#required'] = FALSE;
}
}
/**
* Implements hook_preprocess_ds_entity_view().
*/
function stanford_profile_helper_preprocess_ds_entity_view(&$variables) {
$variables['content']['#pre_render'][] = [
'Drupal\stanford_profile_helper\StanfordProfileHelper',
'preRenderDsEntity',
];
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function stanford_profile_helper_form_taxonomy_term_form_alter(&$form, FormStateInterface $form_state, $form_id) {
/** @var \Drupal\taxonomy\VocabularyInterface $vocabulary */
$vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
$flat_taxonomy = $vocabulary->getThirdPartySetting('flat_taxonomy', 'flat');
// Tweak the taxonomy term add/edit form.
if (!empty($form['relations']['parent']) && !$flat_taxonomy) {
$form['relations']['#open'] = TRUE;
$form['relations']['parent']['#multiple'] = FALSE;
$form['relations']['parent']['#title'] = t('Parent term');
$form['relations']['parent']['#description'] = t('Select the appropriate parent item for this term.');
$form['relations']['parent']['#element_validate'][] = '_stanford_profile_helper_term_form_validate';
}
}
/**
* Tweak the taxonomy term parent form value after submitting.
*
* Because we are changing the form to not allow multiple parents, the form
* value needs to be changed into an array so the TermForm can still manage
* it correctly.
*
* @param array $element
* Form element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Current form state object.
* @param array $form
* Complete form.
*
* @see stanford_profile_helper_form_taxonomy_term_form_alter()
*/
function _stanford_profile_helper_term_form_validate(array $element, FormStateInterface $form_state, array $form) {
$form_state->setValueForElement($element, [$element['#value']]);
}
/**
* Implements hook_menu_links_discovered_alter().
*/
function stanford_profile_helper_menu_links_discovered_alter(&$links) {
if (isset($links['admin_toolbar_tools.extra_links:media_page'])) {
// Alter the "Media" link for /admin/content/media path.
$links['admin_toolbar_tools.extra_links:media_page']['title'] = t('All Media');
}
if (isset($links['system.admin_content'])) {
// Change the node list page for the /admin/content path.
$links['system.admin_content']['title'] = t('All Content');
}
}
/**
* Implements hook_preprocess_HOOK().
*/
function stanford_profile_helper_preprocess_block__help(&$variables) {
if (\Drupal::routeMatch()->getRouteName() == 'help.main') {
// Removes the help text from core help module. Its not helpful, and we're
// going to provide our own help text.
// @see help_help()
unset($variables['content']);
}
}
/**
* Implements hook_help_section_info_alter().
*/
function stanford_profile_helper_help_section_info_alter(array &$info) {
// Change "Module overviews" header.
$info['hook_help']['title'] = t('For Developers');
}
/**
* Implements hook_entity_field_access().
*/
function stanford_profile_helper_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
if (
$operation != 'view' &&
$field_definition->getName() == 'status' &&
$field_definition->getTargetEntityTypeId() == 'node' &&
$items &&
$items->getEntity()->id()
) {
// Prevent unpublishing the home, 404 and 403 pages.
$node = $items->getEntity();
$site_config = \Drupal::config('system.site');
$node_urls = [
$node->toUrl()->toString(TRUE)->getGeneratedUrl(),
"/node/{$node->id()}",
];
// If the node is configured to be the home page, 404, or 403, prevent the
// user from deleting. Unfortunately this only works for roles without the
// "Bypass content access control" permission.
if (array_intersect($node_urls, $site_config->get('page'))) {
return AccessResult::forbidden();
}
}
if (
$field_definition->getType() == 'entity_reference' &&
$field_definition->getSetting('handler') == 'layout_library' &&
$operation == 'edit'
) {
$entity_type = $field_definition->getTargetEntityTypeId();
$bundle = $field_definition->getTargetBundle();
if (!$account->hasPermission("choose layout for $entity_type $bundle")) {
return AccessResult::forbidden();
}
}
$route_match = \Drupal::routeMatch();
// When the page title banner is in use on the page, disable the node title
// field access because the field title will be used in the banner.
if (
$operation == 'view' &&
$field_definition->getName() == 'title' &&
$items?->getEntity()->getEntityTypeId() == 'node' &&
$items->getEntity()->bundle() == 'stanford_page' &&
$items->getEntity()->get('su_page_banner')->count() &&
$route_match->getRouteName() == 'entity.node.canonical' &&
$route_match->getParameter('node')->id() == $items->getEntity()->id()
) {
// Now we know we are on the node page and the node has a banner paragraph
// of some sort. If the banner paragraph is the correct type, we can prevent
// the original node title from displaying.
/** @var \Drupal\paragraphs\ParagraphInterface $banner_paragraph */
$banner_paragraph = $items->getEntity()->get('su_page_banner')->get(0)->entity;
return AccessResult::forbiddenIf($banner_paragraph->bundle() == 'stanford_page_title_banner');
}
return AccessResult::neutral();
}
/**
* Implements hook_field_widget_WIDGET_TYPE_form_alter().
*/
function stanford_profile_helper_field_widget_options_select_form_alter(&$element, FormStateInterface $form_state, $context) {
if ($context['items']->getFieldDefinition()
->getName() == 'layout_selection') {
$element['#description'] = t('Choose a layout to display the page as a whole. Choose "- None -" to keep the default layout setting.');
}
}
/**
* Implements hook_preprocess_toolbar().
*/
function stanford_profile_helper_preprocess_toolbar(&$variables) {
array_walk($variables['tabs'], function (&$tab, $key) {
if (isset($tab['attributes'])) {
$tab['attributes']->addClass(Html::cleanCssIdentifier("$key-tab"));
}
});
}
/**
* Implements hook_contextual_links_alter().
*/
function stanford_profile_helper_contextual_links_alter(array &$links, $group, array $route_parameters) {
if ($group == 'paragraph') {
// Paragraphs edit module clone link does not function correctly. Remove it
// from available links. Also remove delete to avoid unwanted delete.
unset($links['paragraphs_edit.delete_form']);
unset($links['paragraphs_edit.clone_form']);
}
}
/**
* Implements hook_node_access().
*/
function stanford_profile_helper_node_access(NodeInterface $node, $op, AccountInterface $account) {
if ($op == 'delete') {
$site_config = \Drupal::config('system.site');
$node_urls = [$node->toUrl()->toString(TRUE)->getGeneratedUrl(), "/node/{$node->id()}"];
// If the node is configured to be the home page, 404, or 403, prevent the
// user from deleting. Unfortunately this only works for roles without the
// "Bypass content access control" permission.
if (array_intersect($node_urls, $site_config->get('page'))) {
return AccessResult::forbidden();
}
}
$locked_node_ids = \Drupal::state()->get('stanford_profile_helper.locked_admin_nodes', []);
if (in_array($node->id(), $locked_node_ids)) {
return $op === 'view' ? AccessResult::forbiddenIf($account->isAnonymous()) : AccessResult::forbidden();
}
return AccessResult::neutral();
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function stanford_profile_helper_form_menu_edit_form_alter(array &$form, FormStateInterface $form_state) {
$read_only = Settings::get('config_readonly', FALSE);
if (!$read_only) {
return;
}
// If the form is locked, hide the config you cannot change from users without
// the know how.
$access = \Drupal::currentUser()
->hasPermission('Administer menus and menu items');
$form['label']['#access'] = $access;
$form['description']['#access'] = $access;
$form['id']['#access'] = $access;
// Remove the warning message if the user does not have access.
if (!$access) {
\Drupal::messenger()->deleteByType("warning");
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function stanford_profile_helper_form_config_pages_stanford_basic_site_settings_form_alter(array &$form, FormStateInterface $form_state) {
$form['#validate'][] = 'stanford_profile_helper_config_pages_stanford_basic_site_settings_form_validate';
}
/**
* Validates form values.
*
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state interface object.
*/
function stanford_profile_helper_config_pages_stanford_basic_site_settings_form_validate(array $form, FormStateInterface $form_state) {
$element = $form_state->getValue('su_site_url');
$uri = $element['0']['uri'];
if (!empty($uri)) {
// Test if the site url submmitted is equal to current domain.
$host = \Drupal::request()->getSchemeAndHttpHost();
if ($host != $uri) {
$form_state->setErrorByName('su_site_url', t('This URL does not match your domain.'));
}
}
}
/**
* Alter the data of a sitemap link before the link is saved.
*
* @param array $link
* An array with the data of the sitemap link.
* @param array $context
* An optional context array containing data related to the link.
*/
function stanford_profile_helper_xmlsitemap_link_alter(array &$link, array $context) {
// Get node/[:id] from loc.
$node_id = $link['loc'];
// Get 403 page path.
$stanford_profile_helper_403_page = \Drupal::config('system.site')
->get('page.403');
// Get 404 page path.
$stanford_profile_helper_404_page = \Drupal::config('system.site')
->get('page.404');
// If node id matches 403 or 404 pages, remove it from sitemap.
switch ($node_id) {
case $stanford_profile_helper_403_page:
case $stanford_profile_helper_404_page:
// Status is set to zero to exclude the item in the sitemap.
$link['status'] = 0;
}
}
/**
* Implements hook_config_readonly_whitelist_patterns().
*/
function stanford_profile_helper_config_readonly_whitelist_patterns() {
$default_theme = \Drupal::config('system.theme')->get('default');
// Allow the theme settings to be changed in the UI.
$patterns = ["$default_theme.settings", 'next.next_site.*'];
// Allow the form to be submitted in the UI for specific routes that don't
// alter the configuration, such as resetting the order of taxonomy terms.
$routes_to_config = [
'entity.taxonomy_vocabulary.reset_form' => ['taxonomy.vocabulary.*'],
'entity.search_api_index.rebuild_tracker' => ['search_api.index.*'],
'entity.search_api_index.clear' => ['search_api.index.*'],
'entity.search_api_index.reindex' => ['search_api.index.*'],
'xmlsitemap.admin_rebuild' => ['xmlsitemap.settings'],
];
$route_name = \Drupal::routeMatch()->getRouteName();
if (isset($routes_to_config[$route_name])) {
$patterns = [...$patterns, ...$routes_to_config[$route_name]];
}
return $patterns;
}
/**
* Implements field_group_form_process_build_alter().
*/
function stanford_profile_helper_field_group_form_process_build_alter(&$element) {
// Hide / Show the field groups based on the enabled checkbox.
if (isset($element['group_lockup_options'])) {
$element['group_lockup_options']['#states'] = [
'visible' => [
':input[name="su_lockup_enabled[value]"]' => [
'checked' => FALSE,
],
],
];
$element['group_logo_image']['#states'] = [
'visible' => [
':input[name="su_lockup_enabled[value]"]' => [
'checked' => FALSE,
],
],
];
}
}
/**
* Creates a states array.
*
* @param array $opts
* Allowed values.
* @param string $input
* Field selector.
*
* @return array
* State array.
*/
function _stanford_profile_helper_get_lockup_states(array $opts, $input) {
$ret = [];
foreach ($opts as $val) {
$ret[] = [
$input => ['value' => $val],
];
}
return $ret;
}
/**
* Implements hook_entity_type_update().
*/
function stanford_profile_helper_taxonomy_term_update(TermInterface $entity) {
// https://www.drupal.org/project/taxonomy_menu/issues/2867626
$original_parent = $entity->original->get('parent')->getString();
if ($original_parent == $entity->get('parent')->getString()) {
return;
}
$database = \Drupal::database();
$menu_link_exists = $database->select('menu_tree', 'm')->fields('m')
->condition('id', 'taxonomy_menu.menu_link%', 'LIKE')
->condition('route_param_key', 'taxonomy_term=' . $entity->id())
->countQuery()
->execute()
->fetchField();
if ($menu_link_exists > 0) {
$database->delete('menu_tree')
->condition('id', 'taxonomy_menu.menu_link%', 'LIKE')
->condition('route_param_key', 'taxonomy_term=' . $entity->id())
->execute();
\Drupal::service('router.builder')->rebuild();
}
}
/**
* Implements hook_preprocess_pattern_NAME().
*/
function stanford_profile_helper_preprocess_pattern_alert(&$variables) {
$entity_type = $variables['context']->getProperty('entity_type');
$bundle = $variables['context']->getProperty('bundle');
$entity = $variables['context']->getProperty('entity');
// Global Messages!
if ($entity_type == "config_pages" && $bundle == "stanford_global_message") {
// Validate that the entity has the field we need so we don't 500 the site.
if (!$entity->hasField('su_global_msg_type')) {
\Drupal::logger('stanford_profile_helper')
->error("Global Messages Config Block is missing the field su_global_msg_type");
return;
}
$color = $entity->get('su_global_msg_type')->getString();
$variables['attributes']->addClass("su-alert--" . $color);
$dark_bgs = ['error', 'info', 'success'];
if (in_array($color, $dark_bgs)) {
$variables['attributes']->addClass("su-alert--text-light");
}
}
}
/**
* Implements hook_preprocess_pattern_NAME().
*/
function stanford_profile_helper_preprocess_pattern_localfooter(&$variables) {
$entity_type = $variables['context']->getProperty('entity_type');
$bundle = $variables['context']->getProperty('bundle');
$entity = $variables['context']->getProperty('entity');
// If the pattern has already been rendered and the 2nd cell is already a
// markup object or other, we can't manipulate it.
if (!is_array($variables['cell2'])) {
return;
}
$second_content = $variables['cell2']['su_local_foot_se_co'] ?? [];
$third_content = $variables['cell2']['su_local_foot_tr2_co'] ?? [];
// The local footer pattern from Decanter doesn't have equal columns for each
// of the 3 content blocks. To avoid having to completely rewrite the
// template, we can use some special markup to wrap a couple fields with the
// necessary classes to simulate the 4 equal columns. If the user populates
// 1, 2, and 4 content blocks, then the 2nd block will stretch to fill take
// the area of the missing 3rd block.
if (\Drupal::service('renderer')->render($third_content)) {
$variables['cell2'] = [
'#type' => 'container',
'#attributes' => ['class' => ['flex-container']],
'second' => [
'#type' => 'container',
'#attributes' => ['class' => ['flex-md-6-of-12', 'su-margin-bottom-1']],
'contents' => $second_content,
],
'third' => [
'#type' => 'container',
'#attributes' => ['class' => ['flex-md-6-of-12']],
'contents' => $variables['cell2']['su_local_foot_tr2_co'],
],
];
}
// Local Footer!
if ($entity_type == "config_pages" && $bundle == "stanford_local_footer") {
// If the lockup updates are not enabled just end.
if (
!$entity->hasField('su_local_foot_use_loc')
|| $entity->get('su_local_foot_use_loc')->getString() === "1"
) {
return;
}
// Enable custom lockup.
$variables['custom_lockup'] = TRUE;
// Lockup customizations are enabled.
$variables['line1'] = $entity->get('su_local_foot_line_1')->getString();
$variables['line2'] = $entity->get('su_local_foot_line_2')->getString();
$variables['line3'] = $entity->get('su_local_foot_line_3')->getString();
$variables['line4'] = $entity->get('su_local_foot_line_4')->getString();
$variables['line5'] = $entity->get('su_local_foot_line_5')->getString();
$variables['use_logo'] = $entity->get('su_local_foot_use_logo')
->getString();
$file_field = $entity->get('su_local_foot_loc_img');
// Check if there is a file.
if (isset($file_field->entity)) {
$file_uri = $file_field->entity->getFileUri();
$variables['site_logo'] = \Drupal::service('file_url_generator')->generateAbsoluteString($file_uri);
}
else {
$variables['use_logo'] = "1";
}
// Check if there is a link and patch it through.
$link = $entity->get('su_local_foot_loc_link')->getString();
if ($link) {
$variables['lockup_link'] = [
'#markup' => URL::fromUri($link)->toString(TRUE)->getGeneratedUrl(),
];
}
// Pass through the lockup option.
$option = $entity->get('su_local_foot_loc_op')->getString();
$variables['lockup_option'] = 'su-lockup--option-' . $option;
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function stanford_profile_helper_form_media_library_add_form_embeddable_alter(array &$form, FormStateInterface $form_state) {
$source_field = $form_state->get('source_field');
$embed_code_field = $form_state->get('unstructured_field_name');
$user = \Drupal::currentUser();
$authorized = $user->hasPermission('create field_media_embeddable_code')
|| $user->hasPermission('edit field_media_embeddable_code');
if (isset($form['container'][$embed_code_field])) {
$form['container'][$embed_code_field]['#access'] = $authorized;
}
if (isset($form['container'][$source_field])) {
if (!$authorized) {
$new_desc = 'Allowed providers: @providers. For custom embeds, please <a href="@snow_form">request support.</a>';
$args = $form['container'][$source_field]['#description']->getArguments();
$args['@snow_form'] = 'https://stanford.service-now.com/it_services?id=sc_cat_item&sys_id=83daed294f4143009a9a97411310c70a';
$form['container'][$source_field]['#description'] = t($new_desc, $args);
}
$form['container'][$source_field]['#title'] = t('oEmbed URL');
}
}
/**
* Check the access for certain admin menu items and remove them if needed.
*
* @param array $menu_items
* Keyed array of menu item from preprocess_menu.
*/
function stanford_profile_helper_check_admin_menu_access(array &$menu_items): void {
$current_user = \Drupal::currentUser();
foreach ($menu_items as $key => &$item) {
/** @var \Drupal\Core\Url $url */
$url = $item['url'];
$vid = $url->getRouteParameters()['taxonomy_vocabulary'] ?? FALSE;
if (
$vid &&
!$current_user->hasPermission('administer taxonomy') &&
!$current_user->hasPermission("create terms in $vid") &&
!$current_user->hasPermission("delete terms in $vid") &&
!$current_user->hasPermission("edit terms in $vid")
) {
unset($menu_items[$key]);
continue;
}
stanford_profile_helper_check_admin_menu_access($item['below']);
}
}
/**
* Implements hook_preprocess_HOOK().
*/
function stanford_profile_helper_preprocess_menu(&$variables) {
if ($variables['menu_name'] == 'admin') {
stanford_profile_helper_check_admin_menu_access($variables['items']);
}
$cache_tags = $variables['#cache']['tags'] ?? [];
foreach ($variables['items'] as &$item) {
// Taxonomy menu link items use the description from the term as the title
// attribute. The description can be very long and could contain HTML. To
// Make things easiest, just remove the title attribute.
if ($item['original_link'] instanceof TaxonomyMenuMenuLink) {
$attributes = $item['url']->getOption('attributes');
unset($attributes['title']);
$item['url']->setOption('attributes', $attributes);
$term = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->load($item['url']->getRouteParameters()['taxonomy_term']);
if ($term) {
$cache_tags[] = 'taxonomy_term_list:' . $term->bundle();
$cache_tags = array_merge($cache_tags, $term->getCacheTags());
}
}
}
$variables['#cache']['tags'] = array_unique($cache_tags);
}
/**
* Implements hook_field_widget_form_alter().
*/
function stanford_profile_helper_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
if ($context['items']->getName() == 'su_page_components') {
// Push pages to only allow 3 items per row but don't break any existing
// pages that have 4 per row.
$element['container']['value']['#attached']['drupalSettings']['reactParagraphs'][0]['itemsPerRow'] = 3;
}
if ($context['items']->getName() == 'field_media_embeddable_oembed') {
$user = \Drupal::currentUser();
$authorized = $user->hasPermission('create field_media_embeddable_code')
|| $user->hasPermission('edit field_media_embeddable_code');
if (!$authorized) {
$args = $element['value']['#description']['#items'][1]->getArguments();
$args['@snow_form'] = 'https://stanford.service-now.com/it_services?id=sc_cat_item&sys_id=83daed294f4143009a9a97411310c70a';
$new_desc = 'Allowed providers: @providers. For custom embeds, please <a href="@snow_form">request support.</a>';
$element['value']['#description'] = t($new_desc, $args);
}
}
}
/**
* Implements hook_field_widget_WIDGET_TYPE_form_alter().
*/
function stanford_profile_helper_field_widget_datetime_timestamp_no_default_form_alter(&$element, FormStateInterface $form_state, $context) {
// Set the date increment for scheduler settings.
$state = \Drupal::state();
$element['value']['#date_increment'] = $state->get('stanford_profile_helper.scheduler_increment', 60 * 60 * 4);
}
/**
* Implements hook_entity_bundle_field_info_alter().
*/
function stanford_profile_helper_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
if (
$bundle == 'stanford_global_message' &&
!empty($fields['su_global_msg_enabled'])
) {
$fields['su_global_msg_enabled']->addConstraint('global_message_constraint', []);
}
}
/**
* Get available roles, limited if the role_delegation module is enabled.
*
* @return array
* Keyed array of role id and role label.
*/
function _stanford_profile_helper_get_assignable_roles(): array {
if (\Drupal::moduleHandler()->moduleExists('role_delegation')) {
/** @var \Drupal\role_delegation\DelegatableRolesInterface $role_delegation */
$role_delegation = \Drupal::service('delegatable_roles');
return $role_delegation->getAssignableRoles(\Drupal::currentUser());
}
$roles = \Drupal::entityTypeManager()
->getStorage('user_role')
->loadMultiple();
unset($roles[RoleInterface::ANONYMOUS_ID]);
return array_map(fn($role) => $role->label(), $roles);
}
/**
* Implements hook_component_info_alter().
*/
function stanford_profile_helper_component_info_alter(&$components) {
foreach ($components as $id => $component) {
// Check if the provider of the PDB component is enabled.
if (!_stanford_profile_helper_extension_enabled($component)) {
unset($components[$id]);
}
}
}
/**
* Traverse the PDB extension to see if it's module/theme/profile is enabled.
*
* @param \Drupal\Core\Extension\Extension $extension
* Discovered PDB extension object.
*
* @return bool
* If the PDB extension's provider is enabled.
*/
function _stanford_profile_helper_extension_enabled(Extension $extension) {
$path = $extension->getPath();
// Traverse down the path of the extension to find a module/theme/profile
// that can be checked for existance.
while ($path) {
// An info.yml file exists in the current path, check if it's enabled as
// a theme, profile, or module.
if ($info_files = glob("$path/*.info.yml")) {
$info_file_path = $info_files[0];
$name = basename($info_file_path, '.info.yml');
$info_file = Yaml::decode(file_get_contents($info_file_path));
if (isset($info_file['type'])) {
switch ($info_file['type']) {
case 'theme':
return \Drupal::service('theme_handler')->themeExists($name);
case 'module':
case 'profile':
return \Drupal::moduleHandler()->moduleExists($name);
}
}
}
// Pop off the last part of the path to go one level higher.
$path = explode('/', $path);
array_pop($path);
$path = implode('/', $path);
}
return FALSE;
}
/**
* Implements hook_block_build_alter().
*/
function stanford_profile_helper_block_build_alter(array &$build, BlockPluginInterface $block) {
if ($block->getBaseId() == 'system_menu_block') {
$build['#cache']['tags'][] = 'stanford_profile_helper:menu_links';
StanfordProfileHelper::removeCacheTags($build, [
'^node:*',
'^config:system.menu.*',
]);
}
}
/**
* Implements hook_preprocess_HOOK().
*/
function stanford_profile_helper_preprocess_block__system_main_block(&$variables) {
$variables['content']['#cache']['tags'][] = 'stanford_profile_helper:menu_links';
// Remove node cache tags since we'll use our own cache tag above.
StanfordProfileHelper::removeCacheTags($variables['content'], ['^config:system.menu.*']);
}
/**
* Implements hook_preprocess_HOOK().
*/
function stanford_profile_helper_preprocess_block__system_menu_block(&$variables) {
$variables['content']['#cache']['tags'][] = 'stanford_profile_helper:menu_links';
// Remove node cache tags since we'll use our own cache tag above.
StanfordProfileHelper::removeCacheTags($variables['content'], [
'^node:*',
'^config:system.menu.*',
]);
}
/**
* Implements hook_entity_view().
*
* Modifies entity render arrays for fields that are of a certain type. The DS
* module provides a limit option, but it doesn't work for all field formatters
* because of the way the render array is structured. We'll move around the
* field items, call the DS module function, and then correct the render array
* back so that it still functions as expected.
*/
function stanford_profile_helper_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
$list_types = [
'entity_reference_list_label_class',
'link_list_class',
'list_string_list_class',
'string_list_class',
];
$components = $display->getComponents();
$list_render_arrays = [];
// First, move the items from the list render array into the build of the
// field. Store the list render array for re-use later.
foreach ($components as $field => $component) {
if (
isset($component['type']) &&
in_array($component['type'], $list_types) &&
!empty($component['third_party_settings']['ds']['ds_limit']) &&
!empty($build[$field][0]['#items'])
) {
$list_render_arrays[$field] = $build[$field][0];
// Pull out the items to be placed higher in the build array.
$items = $list_render_arrays[$field]['#items'];
unset($list_render_arrays[$field]['#items']);
foreach ($items as $delta => $item) {
$build[$field][$delta] = $item;
}
}
}
// We must have modified some stuff, so call the DS module function.
if ($list_render_arrays) {
ds_entity_view_alter($build, $entity, $display, $view_mode);
}
foreach ($list_render_arrays as $field => $render_array) {
$deltas = Element::children($build[$field]);
$items = [];
// Pull the field items back out that have been limited by DS module and
// put those back into the render array from earlier.
foreach ($deltas as $delta) {
$items[$delta] = $build[$field][$delta];
unset($build[$field][$delta]);
}
$render_array['#items'] = $items;
$build[$field][0] = $render_array;
}
}
/**
* Implements hook_search_api_processor_info_alter().
*/
function stanford_profile_helper_search_api_processor_info_alter(array &$processors) {
$processors['custom_value']['class'] = '\Drupal\stanford_profile_helper\Plugin\search_api\processor\CustomValue';
}
/**
* Implements hook_entity_view_display_alter().
*/
function stanford_profile_helper_entity_view_display_alter(EntityViewDisplayInterface $display, array $context) {
if (str_contains($context['view_mode'], 'search_indexing') && $context['entity_type'] == 'node') {
// The title is already in the template, it's not needed in the display.
$display->removeComponent('title');
}
}
/**
* Implements hook_ENTITY_TYPE_access().
*/
function stanford_profile_helper_filter_format_access(EntityInterface $entity, $operation, AccountInterface $account) {
return AccessResult::forbiddenIf($entity->id() == 'administrative_html');
}
/**
* Implements hook_search_api_algolia_objects_alter().
*/
function stanford_profile_helper_search_api_algolia_objects_alter(array &$objects, IndexInterface $index, array $items) {
/** @var \Drupal\config_pages\ConfigPagesLoaderServiceInterface $config_page_loader */
$config_page_loader = \Drupal::service('config_pages.loader');
// If the canonical url is set, use that to adjust the urls.
$site_domain = $config_page_loader->getValue('stanford_basic_site_settings', 'su_site_url', 0, 'uri');
$current_host = \Drupal::request()->getSchemeAndHttpHost();
foreach ($objects as &$item) {
// Remove fields that aren't necessary.
unset($item['search_api_datasource'], $item['status']);
foreach ($item as $name => &$field) {
// Data that is being sent as the taxonomy term names should always be
// sent as an array of strings. When the node is only configured with one
// term in the field, it tries to send it as a string. So we force to be
// an array.
$property_path = $index->getField($name)?->getPropertyPath() ?: '';
if (is_string($field) && str_contains($property_path, ':entity:name')) {
$field = [$field];
}
// Either the canonical url hasn't been set, or it matches the current
// request. It would match the current request when the event is happening
// in the UI. If cron is running, the current host won't match the canonical
// url.
if (
$site_domain &&
$site_domain != $current_host &&
is_string($field) &&
str_contains($field, $current_host)
) {
// Change the urls from the current host to the canonical url.
$field = str_replace($current_host, $site_domain, $field);
}
}
}
}
/**
* Implements hook_ENTITY_TYPE_load().
*/
function stanford_profile_helper_xmlsitemap_load($entities) {
/** @var \Drupal\xmlsitemap\XmlSitemapInterface $xml_entity */
foreach ($entities as $xml_entity) {
// When loading the XML sitemaps, set the uri to include the base url. This
// will fix cron google submissions as well as decoupled site scenarios.
$uri = $xml_entity->get('uri');
$base_url = \Drupal::state()->get('xmlsitemap_base_url');
$uri['path'] = $base_url . $uri['path'];
$xml_entity->set('uri', $uri);
}
}