Something went wrong on our end
uw_cfg_common.module 65.35 KiB
<?php
/**
* @file
* Module file.
*/
use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\fillpdf\Controller\HandlePdfController;
use Drupal\fillpdf\Form\FillPdfFormForm;
use Drupal\media_library\MediaLibraryState;
use Drupal\user\UserInterface;
use Drupal\uw_cfg_common\Service\UWService;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ViewExecutable;
use Drupal\webform\WebformInterface;
use Drupal\webform\WebformSubmissionStorageInterface;
/**
* Implements hook_preprocess_form_element().
*
* Allows for use of label_class in form elements and will add
* any class in label_classes to the label.
*/
function uw_cfg_common_preprocess_form_element(&$variables) {
if (isset($variables['element']['#label_classes'])) {
$variables['label']['#attributes']['class'] = $variables['element']['#label_classes'];
}
}
/**
* Implements template_preprocess_form_element_label().
*/
function uw_cfg_common_preprocess_form_element_label(&$variables) {
// Check if we need to add the form required to the label.
// Conditions are not the blank summary checkbox,
// the id of the label contains edit-field-uw and has
// either summary or position in the id.
if (
isset($variables['element']['#id']) &&
$variables['element']['#id'] !== 'edit-field-uw-blank-summary-value' &&
str_contains($variables['element']['#id'], 'edit-field-uw-') &&
(
str_contains($variables['element']['#id'], 'summary') ||
str_contains($variables['element']['#id'], 'position')
)
) {
// Try and get the node type, by replacing the id of the label.
$node_type = $variables['element']['#id'];
$node_type = str_replace('edit-field-uw-', '', $node_type);
$node_type = str_replace('-summary-0-value', '', $node_type);
$node_type = str_replace('-position-0-value', '', $node_type);
// The node types to place the form required on the label.
$blank_summary_node_types = [
'blog',
'event',
'news',
'opportunity',
'profile',
'project',
];
// If we are on a node that needs a form required
// on the label add the class.
if (in_array($node_type, $blank_summary_node_types)) {
$variables['attributes']['class'][] = 'form-required';
}
}
}
/**
* Implements hook_sendgrid_integration_categories_alter().
*
* Set the category for all sendmail as WCMS.
*/
function uw_cfg_common_sendgrid_integration_categories_alter($message, $categories) {
$categories = ['WCMS'];
return $categories;
}
/**
* Implements hook_metatags_attachments_alter().
*/
function uw_cfg_common_metatags_attachments_alter(array &$metatag_attachments) {
// Check if the image and og:image fields are empty.
// We do this here instead of in hook_metatags_alter() because we want to
// check if they're empty after tokens have been applied.
// We also want to support the use case where they don't upload an image,
// but do manually specify an image.
// Because of where the information is stored, we have to loop to find it.
$found_image_src = FALSE;
$found_og_image = FALSE;
foreach ($metatag_attachments['#attached']['html_head'] as $attachment) {
if ($attachment[1] == 'image_src') {
$found_image_src = TRUE;
}
elseif ($attachment[1] == 'og_image_0') {
$found_og_image = TRUE;
}
}
// Define what the fallback image is, so we only need to change it once.
$fallback_image = 'https://uwaterloo.ca/university-of-waterloo-logo-152.png';
// If the image_src field is missing, create one with the UWaterloo logo.
if (!$found_image_src) {
$metatag_attachments['#attached']['html_head'][] = [
0 => [
'#tag' => 'link',
'#attributes' => [
'rel' => "image_src",
'href' => $fallback_image,
],
],
1 => 'image_src',
];
}
// If the og:image field is missing, create one with the UWaterloo logo.
if (!$found_og_image) {
$metatag_attachments['#attached']['html_head'][] = [
0 => [
'#tag' => 'meta',
'#attributes' => [
'property' => "og:image",
'content' => $fallback_image,
],
],
1 => 'og_image_0',
];
}
}
/**
* Implements hook_entity_presave().
*/
function uw_cfg_common_entity_presave(EntityInterface $entity) {
// Check if we are on a menu link.
if ($entity->getEntityTypeId() == 'menu_link_content') {
// Check that we are on a Information For (audience) link.
if ($entity->menu_name->value == 'uw-menu-audience-menu') {
// Invalid all the menu caching.
\Drupal::cache('menu')->invalidateAll();
// Rebuild all the menus.
\Drupal::service('plugin.manager.menu.link')->rebuild();
}
}
// On a node entity save, check if the responsive
// image has created the derivatives so that things
// like hero images will load when no image has yet
// been rendered. If we do not do this, most hero
// images will not work.
if ($entity->getEntityTypeId() == 'node') {
// ISTWCMS-5846: if the leave summary blank is checked
// on the node add/edit page, set the summary to NULL.
// Check that the node has the field leave summary blank.
if ($entity->hasField('field_uw_blank_summary')) {
// If the leave summary blank is checked, set summary to NULL.
if ($entity->field_uw_blank_summary->value) {
// Get the node type from the entity.
$node_type = $entity->getType();
// Since all the summary fields are field_uw_ct_<node_type>_summary,
// we need to get the node_type from the getType. We need this
// because the node_type has uw_ct_ in the name, so simply replacing
// the uw_ct_ with nothing will give us the node_type.
// the uw_ct_ with nothing will give us the node_type.
$node_type = str_replace('uw_ct_', '', $node_type);
// Since news has the content type with news_item, we need
// to remove the _item to get the correct field name.
$node_type = str_replace('_item', '', $node_type);
// Set the field name using the notation from above.
$field_name = 'field_uw_' . $node_type . '_summary';
// Now set the summary to NULL using the field_name.
$entity->$field_name = NULL;
}
}
// If there is a hero image (media), continue to process.
if (
$entity->hasField('field_uw_hero_image') &&
$media = $entity->field_uw_hero_image->entity
) {
// Hero media exists, get file entity from media.
if ($file = $media->field_media_image->entity) {
// Load the image styles that are needed for the hero.
$uw_styles = \Drupal::service('uw_cfg_common.uw_service')->uwGetResponsiveImageStyles();
// Step through each of the image styles and ensure that
// the derivative is created.
foreach ($uw_styles as $uw_style) {
// Load the image style.
$style = \Drupal::entityTypeManager()
->getStorage('image_style')
->load($uw_style);
// Get the styled image derivative.
$destination = $style->buildUri($file->getFileUri());
// If the derivative doesn't exist yet (as the image style may
// have been added post launch), create it.
if (!file_exists($destination)) {
$style->createDerivative($file->getFileUri(), $destination);
}
}
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Remove the None option from layout builder styles.
*/
function uw_cfg_common_form_layout_builder_configure_section_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// Column separator group should not appear one column.
if (isset($form['layout_builder_style_column_separator'])) {
// Ensuring that none is selected for column separator by default.
if (!isset($form['layout_builder_style_column_separator']['#default_value'])) {
$form['layout_builder_style_column_separator']['#default_value'] = 'uw_lbs_column_separator_none';
}
}
// Ensuring that none is selected for section separator by default.
if (!isset($form['layout_builder_style_section_separator']['#default_value'])) {
$form['layout_builder_style_section_separator']['#default_value'] = 'uw_lbs_section_separator_none';
}
// Ensuring that default is selected for section spacing by default.
if (!isset($form['layout_builder_style_section_spacing']['#default_value'])) {
$form['layout_builder_style_section_spacing']['#default_value'] = 'uw_lbs_section_spacing_default';
}
// Ensuring that the contained width is selected by default.
if (!isset($form['layout_builder_style_default']['#default_value'])) {
$form['layout_builder_style_default']['#default_value'] = 'uw_lbs_contained_width';
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Configure admin/structure/webform/config/submissions.
*/
function uw_cfg_common_form_webform_admin_config_submissions_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// Remove undesired features.
$form['views_settings']['#access'] = FALSE;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Remove all ability to set CSS classes, CSS styles and custom attributes
* in admin/structure/webform/manage/{webform_id}.
*/
function uw_cfg_common_form_webform_ui_element_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// Advanced -> Wrapper Attributes.
$form['properties']['wrapper_attributes']['#access'] = FALSE;
// Advanced -> Element Attributes.
$form['properties']['element_attributes']['#access'] = FALSE;
// Advanced -> Label Attributes.
$form['properties']['label_attributes']['#access'] = FALSE;
// Advanced -> Submission Display -> Display Wrapper Attributes.
$form['properties']['display']['format_attributes']['#access'] = FALSE;
// Advanced -> Summary Attributes.
$form['properties']['summary_attributes']['#access'] = FALSE;
// Advanced -> Title Attributes.
$form['properties']['title_attributes']['#access'] = FALSE;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Configure admin/structure/webform/manage/WEBFORM_ID/access.
*/
function uw_cfg_common_form_webform_settings_access_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
/** @var \Drupal\webform\WebformInterface $webform */
$webform = $form_state->getFormObject()->getEntity();
// Choose access control method.
$form['access']['create']['uw_access_control_method'] = [
'#type' => 'radios',
'#title' => t('Choose who can view and submit the form'),
'#options' => [
'all' => t('Everyone'),
'auth' => t('Users who are logged in'),
'group' => t('Users specified by Active Directory groups'),
'user' => t('Users specified below'),
'anon' => t('Users who are logged out (for anonymous submission)'),
],
'#default_value' => $webform->getThirdPartySetting('uw_cfg_common', 'access_control_method') ?: 'all',
'#required' => TRUE,
'#weight' => -50,
];
// Access by Active Directory group.
$form['access']['create']['uw_ad_access'] = [
'#type' => 'details',
'#title' => t('Access control by Active Directory group'),
'#required' => TRUE,
'#open' => TRUE,
'#states' => [
'visible' => [
'input[name="access[create][uw_access_control_method]"]' => ['value' => 'group'],
],
],
];
$form['access']['create']['uw_ad_access']['ad_require_groups'] = [
'#type' => 'textarea',
'#title' => t('Limit form submission to these Active Directory groups'),
'#description' => t('Put one Active Directory group per line. To complete the form, the user must be in at least one of these groups. Leave blank to allow everyone.'),
'#default_value' => implode("\r\n", $webform->getThirdPartySetting('uw_cfg_common', 'ad_require_groups') ?: []),
];
$form['access']['create']['uw_ad_access']['ad_deny_groups'] = [
'#type' => 'textarea',
'#title' => t('Prevent form submission for these Active Directory groups'),
'#description' => t('Put one Active Directory group per line. To complete the form, the user must not be in any of these groups. Leave blank to allow everyone.'),
'#default_value' => implode("\r\n", $webform->getThirdPartySetting('uw_cfg_common', 'ad_deny_groups') ?: []),
];
// Validate and submit handler to save UW settings.
$form['actions']['submit']['#validate'][] = '_uw_cfg_common_form_webform_settings_access_form_validate';
$form['actions']['submit']['#submit'][] = '_uw_cfg_common_form_webform_settings_access_form_submit';
// Users control is hidden or required, the latter when authz by user.
$access_rule = [
'input[name="access[create][uw_access_control_method]"]' => ['value' => 'user'],
];
$form['access']['create']['users']['#states']['required'][] = $access_rule;
$form['access']['create']['users']['#states']['visible'][] = $access_rule;
// Remove sections for access control that should not be available.
$sections_to_remove = [
'update_any',
'update_own',
'delete_own',
'administer',
'configuration',
];
foreach ($sections_to_remove as $section) {
$form['access'][$section]['#access'] = FALSE;
}
// Remove all but user-based access for submissions and test.
$permissions_to_edit = [
'create',
'view_any',
'delete_any',
'purge_any',
'view_own',
'test',
];
$access_types_to_remove = [
'roles',
'permissions',
];
foreach ($permissions_to_edit as $permission) {
foreach ($access_types_to_remove as $type) {
$form['access'][$permission][$type]['#access'] = FALSE;
}
}
}
/**
* Form validate handler.
*/
function _uw_cfg_common_form_webform_settings_access_form_validate(array $form, FormStateInterface $form_state): void {
// Validate UW settings.
$access_control_method = [
'access',
'create',
'uw_access_control_method',
];
$access_control_method = $form_state->getValue($access_control_method);
switch ($access_control_method) {
// Validate AD groups.
case 'group':
$fields = [
'ad_require_groups' => NULL,
'ad_deny_groups' => NULL,
];
foreach (array_keys($fields) as $field) {
// Get groups.
$setting = [
'access',
'create',
'uw_ad_access',
$field,
];
$fields[$field] = uw_cfg_common_array_split_clean($form_state->getValue($setting));
$form_state->setValue($setting, $fields[$field]);
// Raise error for invalid groups.
foreach ($fields[$field] as $group) {
if (!preg_match('/^[A-Za-z0-9_& -]+$/', $group)) {
$form_state->setError(
$form['access']['create']['uw_ad_access'][$field],
t(
'Invalid group: %group.',
['%group' => substr($group, 0, 100)]
)
);
break;
}
}
}
// Raise error if no groups are entered.
if (!array_filter($fields)) {
$form_state->setError(
$form['access']['create']['uw_ad_access'],
t('Provide at least one group to use for access control.')
);
}
// Fall-through.
case 'all':
case 'auth':
case 'anon':
// Except for case 'user', ensure no user access constraint is set.
$form_state->setValue(['access', 'create', 'users'], NULL);
break;
}
}
/**
* Form submit handler.
*/
function _uw_cfg_common_form_webform_settings_access_form_submit(array $form, FormStateInterface $form_state): void {
// Save UW settings.
if ($webform = $form_state->getFormObject()->getEntity()) {
// Access control method.
$access_control_method = [
'access',
'create',
'uw_access_control_method',
];
$access_control_method = $form_state->getValue($access_control_method);
$webform->setThirdPartySetting('uw_cfg_common', 'access_control_method', $access_control_method);
// Only save groups if that is the access method. Otherwise, they would be
// saved without having been validated.
if ($access_control_method === 'group') {
// AD group access.
foreach (['ad_require_groups', 'ad_deny_groups'] as $field) {
$setting = [
'access',
'create',
'uw_ad_access',
$field,
];
$setting = $form_state->getValue($setting);
$webform->setThirdPartySetting('uw_cfg_common', $field, $setting);
}
}
$webform->save();
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Configure admin/structure/webform/manage/WEBFORM_ID/settings/confirmation.
*/
function uw_cfg_common_form_webform_settings_confirmation_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// Remove undesirable Webform submission confirmation types.
// The 'modal' type is just a different way to display the message. Disable
// for consistency.
unset($form['confirmation_type']['confirmation_type']['#options']['modal']);
// The 'none' type is only useful along with a custom handler which provides
// the confirmation message.
unset($form['confirmation_type']['confirmation_type']['#options']['none']);
// Remove undesired features.
$form['confirmation_attributes_container']['#access'] = FALSE;
$form['back']['back_container']['confirmation_back_attributes_container']['#access'] = FALSE;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Configure admin/structure/webform/manage/WEBFORM_ID/settings.
*/
function uw_cfg_common_form_webform_settings_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// Remove undesired features.
$form['ajax_settings']['#access'] = FALSE;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Configure admin/structure/webform/manage/WEBFORM_ID/handlers.
*/
function uw_cfg_common_form_webform_handler_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// Add help text to 'SEND FROM (WEBSITE/DOMAIN)' in webform email handler.
if ($form['#webform_handler_plugin_id'] === 'email') {
$form['settings']['from']['#description'] = t('This must be an <strong>@uwaterloo.ca</strong> email address for sending to succeed. Please consider using the reply-to email option instead when emails are not limited to campus accounts.');
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Configure admin/structure/webform/manage/WEBFORM_ID/settings/form.
*/
function uw_cfg_common_form_webform_settings_form_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// Unset the source entity settings in webforms.
$form['form_behaviors']['form_prepopulate_source_entity']['#access'] = FALSE;
$form['form_behaviors']['form_prepopulate_source_entity_required']['#access'] = FALSE;
$form['form_behaviors']['form_prepopulate_source_entity_type']['#access'] = FALSE;
// Remove undesired features.
$form['access_denied']['#access'] = FALSE;
$form['custom_settings']['#access'] = FALSE;
$form['form_behaviors']['form_autofocus']['#access'] = FALSE;
$form['form_behaviors']['form_disable_back']['#access'] = FALSE;
$form['form_behaviors']['form_novalidate']['#access'] = FALSE;
$form['form_behaviors']['form_required']['#access'] = FALSE;
$form['form_behaviors']['form_reset']['#access'] = FALSE;
$form['form_behaviors']['form_submit_back']['#access'] = FALSE;
$form['form_settings']['form_attributes']['#access'] = FALSE;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Configure admin/structure/webform/manage/WEBFORM_ID/settings/submissions.
*/
function uw_cfg_common_form_webform_settings_submissions_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// Remove undesired features.
$form['access_denied']['#access'] = FALSE;
$form['submission_behaviors']['form_convert_anonymous']['#access'] = FALSE;
$form['submission_behaviors']['submission_log']['#access'] = FALSE;
$form['submission_behaviors']['token_update']['#access'] = FALSE;
$form['views_settings']['#access'] = FALSE;
}
/**
* Implements hook_ENTITY_TYPE_create().
*/
function uw_cfg_common_webform_create(WebformInterface $webform) {
// Submission purge settings. Set the default to purge drafts after 28 days.
$webform->setSetting('purge', WebformSubmissionStorageInterface::PURGE_DRAFT);
$webform->setSetting('purge_days', 28);
// On admin/structure/webform/manage/FORM/settings/confirmation, default
// "Confirmation type" to "inline".
$webform->setSetting('confirmation_type', WebformInterface::CONFIRMATION_INLINE);
// Set so that uw_cfg_common_webform_build_access_denied_alter() will run.
// This value is tested for in Webform::preRenderWebformElement().
// This value appears on
// admin/structure/webform/manage/WEBFORM_ID/settings/form.
$webform->setSetting('form_access_denied', WebformInterface::ACCESS_DENIED_PAGE);
}
/**
* Implements hook_toolbar_alter().
*
* Remove the Manage link from the toolbar for authenticated users.
*/
function uw_cfg_common_toolbar_alter(&$items) {
// Get the current user.
$current_user = \Drupal::currentUser();
// Remove the "manage" people for non-admin users.
if (!$current_user->hasPermission('access manage toolbar item')) {
// Remove "Manage" toolbar item.
unset($items['administration']);
}
// Add "people" and "reports" links to "Workbench".
// Note: 'dashboards' is renamed in
// uw_dashboard_toolbar_alter().
$links = [
'entity.user.collection' => t('People'),
'system.admin_reports' => t('Reports'),
];
foreach ($links as $route => $title) {
$url = Url::fromRoute($route);
if ($url->access()) {
$items['dashboards']['tray']['dashboards']['#items'][] = [
'#type' => 'link',
'#title' => $title,
'#url' => $url,
];
}
}
}
/**
* Implements hook_preprocess_HOOK().
*/
function uw_cfg_common_preprocess_node(&$variables) {
// Get the current path.
$path = explode('/', \Drupal::service('path.current')->getPath());
// The paths to place the content moderation block on. Made this
// an array to future proof, if there are more pages later.
$paths_for_content_moderation = ['latest'];
// ISTWCMS-4493: adding class if section has full width.
// If there is a sidebar on the node, check all sections for full width.
if (isset($variables['sidebar'])) {
// Get the layouts from the node.
$layouts = $variables['node']->layout_builder__layout->getValue();
// Step through each of the layouts and check for full width.
foreach ($layouts as $layout) {
// Get the layout settings from the section.
$settings = $layout['section']->getLayoutSettings();
// If the layout builder style is set to full width, then set
// the classes variable for the node and exit the loop.
if (isset($settings['layout_builder_styles_style']) &&
$settings['layout_builder_styles_style'] == "uw_lbs_full_width"
) {
// Add a class to the node for full width on a section.
$variables['attributes']['class'][] = 'uw-section-has-full-width';
// Break out of the loop to save computational time.
break;
}
}
}
// Check if we are to add the content moderation place.
if (in_array(end($path), $paths_for_content_moderation)) {
// Add the content moderation block.
$variables['uw_content_moderation_form'] = \Drupal::formBuilder()->getForm('Drupal\content_moderation\Form\EntityModerationForm', $variables['node']);
}
else {
$block_manager = \Drupal::service('plugin.manager.block');
$plugin_block = $block_manager->createInstance('uw_cbl_content_moderation', []);
$access_result = $plugin_block->access(\Drupal::currentUser());
// Return empty render array if user doesn't have access.
// $access_result can be boolean or an AccessResult class.
if (
is_object($access_result) &&
$access_result->isForbidden() ||
is_bool($access_result)
&& $access_result
) {
$variables['uw_content_moderation_form'] = $plugin_block->build();
}
}
// Set the media flags for the node.
$variables['media_flags'] = \Drupal::service('uw_cfg_common.uw_service')->uwGetMediaFlags($variables['node'], $variables['view_mode']);
}
/**
* Get the layout builder styles as options.
*
* @param \Drupal\field\Entity\FieldStorageConfig $definition
* The field definition.
* @param \Drupal\Core\Entity\ContentEntityInterface|null $entity
* The entity being created if applicable.
* @param bool $cacheable
* Boolean indicating if the results are cacheable.
*
* @return array
* An array of possible key and value options.
*
* @see options_allowed_values()
*/
function _uw_cfg_common_get_layout_builder_styles(
FieldStorageConfig $definition,
ContentEntityInterface $entity = NULL,
$cacheable
) {
// Get all the styles for sections.
$all_styles = _layout_builder_styles_retrieve_by_type('section');
// Array of options.
$options = [];
// Step through each style and get the info.
foreach ($all_styles as $style) {
// Only load styles from the "default" group,
// which contains the section widths.
// Needed for when other section styles ship.
if ($style->getGroup() == 'default') {
$options[$style->id()] = $style->label();
}
}
return $options;
}
/**
* Implements hook_page_attachments().
*/
function uw_cfg_common_page_attachments(array &$page) {
$page['#attached']['library'][] = 'uw_cfg_common/uw_mathjax';
// Load uw_cfg_common module analytics configuration.
$config = \Drupal::config('uw_cfg_common.google_settings');
if ($config && $gso = $config->get('uw_cfg_common_google_site_ownership')) {
$data = [
'#tag' => 'meta',
'#attributes' => [
'name' => 'google-site-verification',
'content' => $gso,
],
];
// Attach tag to HEAD section.
$page['#attached']['html_head'][] = [$data, 'uw-google-site-verification'];
}
$admin_page = \Drupal::service('uw_cfg_common.uw_analytics')->administrationPage();
// Get the code from config and inject to the page.
if (!$admin_page && !empty($config->get('uw_cfg_common_ga_account'))) {
$code = Html::escape($config->get('uw_cfg_common_ga_account'));
$firstLetter = strtolower(substr($code, 0, 1));
$snippet = _uw_cfg_common_google_analytics_snippet($firstLetter, $code);
if ($firstLetter === 'g') {
$external_script_data = [
'#tag' => 'script',
'#attributes' => [
'async' => TRUE,
'src' => 'https://www.googletagmanager.com/gtag/js?id=' . $code,
],
];
$page['#attached']['html_head'][] = [
$external_script_data,
'uw-google-tag-manager',
];
}
$analytics = [
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => $snippet,
];
$page['#attached']['html_head'][] = [
$analytics,
'uw-google-analytics',
];
}
}
/**
* Returns Google Analytics snippet code.
*
* @param string $firstLetter
* First letter of the code in lowercase.
* @param string $code
* Analytics code, could be G-9999999999 or UA-99999999-99.
* @param int $code_id
* Code order number.
*
* @return string
* Code snippet to be injected to html page in script tag.
*/
function _uw_cfg_common_google_analytics_snippet($firstLetter, $code, $code_id = 0): string {
$snippet['u'] = "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '@gtag', 'auto', {'name': 'tracker@code_id'});
ga('tracker@code_id.send', 'pageview');";
$snippet['g'] = "window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '@gtag');";
$output = str_replace('@gtag', $code, $snippet[$firstLetter]);
if ($firstLetter === 'u') {
$output = str_replace('@code_id', $code_id, $output);
}
return $output;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Set the default of preview mode disabled.
*/
function uw_cfg_common_form_node_type_add_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$form['submission']['preview_mode']['#default_value'] = 0;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Node edit form: node/NID/edit.
*
* Prevent certain changes to the home page.
*/
function uw_cfg_common_form_node_uw_ct_web_page_edit_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// No changes for those with access.
if (\Drupal::currentUser()->hasPermission('bypass home page protection')) {
return;
}
// Do not allow the home page to be parent of any item.
unset($form['menu']['link']['menu_parent']['#options']['main:uw_base_profile.front_page']);
// Early return if not editing home page.
$nid = (int) \Drupal::routeMatch()->getRawParameter('node');
if (!UWService::nodeIsHomePage($nid)) {
return;
}
// Remove access to certain controls.
$form['path']['#access'] = FALSE;
$form['promote']['#access'] = FALSE;
$form['sticky']['#access'] = FALSE;
// For 'menu', setting #access did not work for non-admins. So, also hide the
// sub-components and make it a container so that nothing appears on the page.
$form['menu']['#access'] = FALSE;
$form['menu']['#type'] = 'container';
$form['menu']['enabled']['#access'] = FALSE;
$form['menu']['link']['#access'] = FALSE;
// Hide delete link if no access. This should happen by itself, but does not.
if (!$form['actions']['delete']['#url']->access()) {
$form['actions']['delete']['#access'] = FALSE;
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Menu edit form: admin/structure/menu/manage/{menu}.
*/
function uw_cfg_common_form_menu_edit_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// Hide links to menu edit and delete for non-admin.
if (!\Drupal::currentUser()->hasPermission('administer menu')) {
foreach (Element::children($form['links']['links']) as $element_key) {
$form['links']['links'][$element_key]['operations']['#access'] = FALSE;
}
}
// Prevent certain changes to the home page.
//
// No changes for those with access.
if (\Drupal::currentUser()->hasPermission('bypass home page protection')) {
return;
}
// Return early if not editing "Main navigation" menu.
if (!isset($form['links']['links']['menu_plugin_id:uw_base_profile.front_page'])) {
return;
}
// Remove access to home page controls.
$form['links']['links']['menu_plugin_id:uw_base_profile.front_page']['enabled']['#access'] = FALSE;
$form['links']['links']['menu_plugin_id:uw_base_profile.front_page']['operations']['#access'] = FALSE;
$form['links']['links']['menu_plugin_id:uw_base_profile.front_page']['weight']['#access'] = FALSE;
// Make home page not draggable.
$key = array_search('draggable', $form['links']['links']['menu_plugin_id:uw_base_profile.front_page']['#attributes']['class'], TRUE);
unset($form['links']['links']['menu_plugin_id:uw_base_profile.front_page']['#attributes']['class'][$key]);
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Menu link edit form: admin/structure/menu/item/ID/edit.
*
* Do not allow the home page to be parent of any item.
*/
function uw_cfg_common_form_menu_link_content_menu_link_content_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// No changes for those with access.
if (\Drupal::currentUser()->hasPermission('bypass home page protection')) {
return;
}
// Do not allow the home page to be parent of any item.
unset($form['menu_parent']['#options']['main:uw_base_profile.front_page']);
}
/**
* Implements hook_preprocess_HOOK().
*/
function uw_cfg_common_preprocess_responsive_image(&$variables) {
// Get the current path.
$current_path = \Drupal::service('path.current')->getPath();
// Explode the current path so we can check where we are.
$current_path_parts = explode('/', $current_path);
// Get the media library parameters, we will use this
// if we are on a media library page/modal.
$media_lib_parameters = \Drupal::request()->query->get('media_library_opener_parameters');
// If the current path has a node or media library params,
// we need to alter the image styles.
if (
$current_path_parts[1] == 'node' ||
$media_lib_parameters
) {
// If we are on a contact image, remove all styles
// but those for portraits.
if (
isset($media_lib_parameters['bundle']) &&
$media_lib_parameters['bundle'] == 'uw_ct_contact' ||
end($current_path_parts) == 'uw_ct_contact'
) {
// Get the styles used for portraits.
$uw_styles = \Drupal::service('uw_cfg_common.uw_service')->getCropImageStyles('portrait');
}
else {
// Get the styles used for responsive.
$uw_styles = \Drupal::service('uw_cfg_common.uw_service')->getCropImageStyles('responsive');
}
// Step through each of the sources and see if we are.
// to use it.
foreach ($variables['sources'] as $index => $source) {
// Get the srcset.
$srcset = $source->storage()['srcset']->render();
// Break into parts so that we can check for image styles.
$srcset_parts = explode('/', $srcset);
// Step through each of the srcset parts.
foreach ($srcset_parts as $sp) {
// Ensure that we are on an image style.
if (strpos($sp, 'uw_is') !== FALSE) {
// If not in the list of image styles, remove
// it from the sources.
if (!in_array($sp, $uw_styles)) {
unset($variables['sources'][$index]);
}
}
}
}
}
}
/**
* Implements hook_form_alter().
*
* Hide/disable metatag information on our nodes.
*/
function uw_cfg_common_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// ISTWCMS-5846: if we are on a UW content type which is
// the notation of node_uw_ct_<node_type>_form of the
// form_id, then we need to add the status to the
// summary field.
if (preg_match("/node_uw_ct_.*form/", $form_id)) {
// Only add states if we have the blank summary field.
if (isset($form['field_uw_blank_summary'])) {
// Since we are unable to simply get the node type in format that
// we need to use it (i.e. event, news, blog, etc.), we need to
// take the form_id and manipulate it to get us the node type.
// First is the remove the node_uw_ct_, then the _edit and
// finally the _form.
$node_type = str_replace('node_uw_ct_', '', $form_id);
$node_type = str_replace('_edit', '', $node_type);
$node_type = str_replace('_form', '', $node_type);
// We need to add this because news has the name news_item in its
// content type, so we need to remove that to just get news
// to be used in the field name.
$node_type = str_replace('_item', '', $node_type);
// If the node type is an opportunity, we have to give
// the specific field name.
if ($node_type == 'opportunity') {
$field_name = 'field_uw_opportunity_position';
}
// Now since all the rest of the field names
// are the same, field_uw_<node_type>_summary,
// we can get the proper field name.
else {
$field_name = 'field_uw_' . $node_type . '_summary';
}
// Set the states of the summary, required and visible.
$form[$field_name]['widget'][0]['#states'] = [
'visible' => [
':input[name="field_uw_blank_summary[value]"]' => ['checked' => FALSE],
],
];
// Add custom validation for blank summaries.
$form['#validate'][] = '_uw_cfg_common_blank_summaries_validation';
}
}
// ISTWCMS-4648: removing revisions from layout builder page.
if (\Drupal::routeMatch()->getRouteName() == 'layout_builder.overrides.node.view') {
$form['revision']['#access'] = FALSE;
$form['advanced']['#access'] = FALSE;
}
// Ensure that we are on a UW content type node.
if (preg_match('/^node_uw_ct_.*_form$/', $form_id)) {
// Add custom validation for alias.
$form['#validate'][] = '_uw_cfg_common_alias_validate';
// Ensure that the node has metatag information.
if (isset($form['field_uw_meta_tags'])) {
// Hide elements under advanced settings.
$form['field_uw_meta_tags']['widget'][0]['advanced']['canonical_url']['#access'] = FALSE;
$form['field_uw_meta_tags']['widget'][0]['advanced']['shortlink_url']['#access'] = FALSE;
// Hide elements under open graph.
$form['field_uw_meta_tags']['widget'][0]['open_graph']['og_type']['#access'] = FALSE;
$form['field_uw_meta_tags']['widget'][0]['open_graph']['og_url']['#access'] = FALSE;
$form['field_uw_meta_tags']['widget'][0]['open_graph']['og_updated_time']['#access'] = FALSE;
$form['field_uw_meta_tags']['widget'][0]['open_graph']['og_locale']['#access'] = FALSE;
// Hide elements under Twitter settings.
$form['field_uw_meta_tags']['widget'][0]['twitter_cards']['twitter_cards_page_url']['#access'] = FALSE;
$form['field_uw_meta_tags']['widget'][0]['twitter_cards']['twitter_cards_page_url']['#access'] = FALSE;
}
}
// If there is a type of media field, it means that we are
// on a node add/edit page, so add the states for the
// actual media types in the hero section.
if (isset($form['field_uw_type_of_media'])) {
// Get the node type from the form id.
$node_type = str_replace('node_uw_ct_', '', $form_id);
$node_type = str_replace('_edit', '', $node_type);
$node_type = str_replace('_form', '', $node_type);
$node_type = str_replace('_item', '', $node_type);
// Content types to remove the hero image from media.
$remove_hero_image_nodes = [
'web_page',
'catalog',
'contact',
'opportunity',
'profile',
'project',
'service',
];
// If the node type can not have hero image, remove the option.
if (in_array($node_type, $remove_hero_image_nodes)) {
unset($form['field_uw_type_of_media']['widget']['#options']['image']);
}
// Set the states for the hero image.
$form['field_uw_hero_image']['#states'] = [
'visible' => [
[
'select[name="field_uw_type_of_media"]' => [
['value' => 'image'],
],
],
],
];
// Set the states for the banner.
$form['group_banner']['#states'] = [
'visible' => [
[
'select[name="field_uw_type_of_media"]' => [
['value' => 'banner'],
],
],
],
];
// Set the states for the banner settings.
$form['field_uw_media_width']['#states'] = [
'visible' => [
[
'select[name="field_uw_type_of_media"]' => [
['value' => 'banner'],
],
'select[name="field_uw_text_overlay_style"]' => [
['value' => 'full-width'],
],
],
],
];
// Add our custom validation for banners.
$form['#validate'][] = '_uw_cfg_common_banner_validate';
}
// If we are on the media upload form, we want to restrict
// what crops are available.
if ($form_id == 'media_library_add_form_upload') {
// If the crop widget is on the form, unset certain crops.
if (isset($form['media'][0]['fields']['field_media_image']['widget'][0]['#crop_list'])) {
// Get the parameters from the request, this was the only
// way to get out what the bundle was. Since this is a new
// form call from media_library, we could not use get current
// path or uri, since it would only return /media_library.
// The media library parameters has everything listed for the
// node and the node types.
$media_lib_parameters = \Drupal::request()->query->get('media_library_opener_parameters');
// If there are media lib parameters, process them.
if ($media_lib_parameters) {
// If there is a bundle on the parameters, continue
// to process.
if (isset($media_lib_parameters['bundle'])) {
// If this is a contact, remove all the responsive crops.
// If anything else, remove the portrait crop.
if ($media_lib_parameters['bundle'] == 'uw_ct_contact') {
foreach ($form['media'][0]['fields']['field_media_image']['widget'][0]['#crop_list'] as $index => $crop) {
if ($crop !== 'uw_crop_portrait') {
unset($form['media'][0]['fields']['field_media_image']['widget'][0]['#crop_list'][$index]);
}
}
}
else {
foreach ($form['media'][0]['fields']['field_media_image']['widget'][0]['#crop_list'] as $index => $crop) {
if ($crop == 'uw_crop_portrait') {
unset($form['media'][0]['fields']['field_media_image']['widget'][0]['#crop_list'][$index]);
}
}
}
}
}
}
}
}
/**
* Validates submission values for alias on nodes.
*/
function _uw_cfg_common_alias_validate(array &$form, FormStateInterface $form_state): void {
// Get the values from the form state.
$values = $form_state->getValues();
if (isset($values['path'][0]['alias'])) {
// List of urls which should not be an alias.
$urls = [
'blog',
'events',
'news',
'projects',
'profile',
'profiles',
'contacts',
'service',
'opportunities',
'user',
'users',
];
// Get the alias from the form state values.
$alias = $values['path'][0]['alias'];
$orig_alias = $alias;
// Trim any surrounding slashes from the alias to
// ensure that we are getting exact matches for the
// predefined alias from above. Some users will add
// slashes before and after the alias, so just
// easier to check without slashes.
$alias = trim($alias, '/');
// Check if the alias exists if yes, sets error.
// We are checking three cases, the first is if
// the alias is in the predefined list. The second
// is if the alias is just /, or just a series of slashes,
// we have to check here because we removed all the
// slashes with the alias variable. The last is to check
// if the alias has any form of strictly admin, so /admin/,
// admin, admin/, and admin/something are not allowed,
// but something like admin-meeting would be.
if (
in_array($alias, $urls) ||
preg_match('/^\/+$/', $orig_alias) ||
preg_match('/^admin(?:\/.*)?$/', $alias)
) {
// Set an error message if alias exists.
$form_state->setError($form['path']['widget'][0]['alias'], t('The alias "@url" is a reserved path that cannot be used.', ['@url' => $orig_alias]));
}
}
}
/**
* Validates submission values for banners on nodes.
*/
function _uw_cfg_common_blank_summaries_validation(array &$form, FormStateInterface $form_state) {
// Get the values from the form state.
$values = $form_state->getValues();
// Get the node type.
$node_type = str_replace('node_uw_ct_', '', $form['#form_id']);
$node_type = str_replace('_edit', '', $node_type);
$node_type = str_replace('_form', '', $node_type);
$node_type = str_replace('_item', '', $node_type);
// If the node type is an opportunity, we have to give
// the specific field name, if not just use the field name.
if ($node_type == 'opportunity') {
$field_name = 'field_uw_opportunity_position';
}
else {
$field_name = 'field_uw_' . $node_type . '_summary';
}
// If the blank summary is not checked and the summary
// is null, then set an error.
if (!$values['field_uw_blank_summary']['value']) {
if ($values[$field_name][0]['value'] == '') {
$form_state->setError($form[$field_name]['widget'][0], t('Summary/position field is required.'));
}
}
}
/**
* Validates submission values for banners on nodes.
*/
function _uw_cfg_common_banner_validate(array &$form, FormStateInterface $form_state) {
// Get the values of the form state.
$values = $form_state->getValues();
// If there is a type of media set and it is banners,
// then perform the validation.
if (
isset($values['field_uw_type_of_media']) &&
!empty($values['field_uw_type_of_media']) &&
$values['field_uw_type_of_media'][0]['value'] == 'banner'
) {
// Since the add more button is in the values, and there is
// a mix of integers and strings as keys, we need to count
// the array keys and if it is less than or equal to one
// means that the user has not entered a banner at all.
if (count(array_keys($values['field_uw_banner'])) <= 1) {
$form_state->setError($form['field_uw_banner'], t('You must add at least one banner.'));
}
// At least one banner, now check that there is an
// image in each banner.
else {
// Step through all the values of banners and check for an image.
foreach ($values['field_uw_banner'] as $key => $value) {
// Ensure that the key is an integer, since there is an add_more
// in the values. We also need to check if there is "top" in the
// array, as this is what paragraphs uses for closed items. These
// closed items have already been validated so if there is a closed
// item we can just ignore it, only open items, meaning it has values
// in the fields needs to be validated.
if (is_int($key) && !$value['top']) {
// If there is no selection on the media, then there is no image,
// so set the error.
// @todo fix so that inline errors show.
if (!isset($value['subform']['field_uw_ban_image']['selection'])) {
// Set the error that an image is missing.
$form_state->setError($form['field_uw_banner']['widget'][$key]['subform']['field_uw_ban_image'], t('Banner image field is required.'));
}
}
}
}
}
}
/**
* Implements hook_field_widget_WIDGET_TYPE_form_alter().
*/
function uw_cfg_common_field_widget_entity_reference_paragraphs_form_alter(&$element, &$form_state, $context) {
// If this is a banner image, add the required manually.
if ($element['#paragraph_type'] == 'uw_para_image_banner') {
// If this is not a block, add the class for required
// on the image, we need to do this so that when using
// banners with media, the required does not work properly
// when banners are not selected.
// If it is a block, just add the required to the element.
if (!isset($context['form']['#block'])) {
// If there already is classes set, then add to the array.
// If no classes yet, add as an array.
if (isset($element['subform']['field_uw_ban_image']['widget']['#attributes']['class'])) {
$element['subform']['field_uw_ban_image']['widget']['#attributes']['class'][] = 'form-required';
}
else {
$element['subform']['field_uw_ban_image']['widget']['#attributes']['class'] = ['form-required'];
}
}
else {
$element['subform']['field_uw_ban_image']['widget']['#required'] = TRUE;
}
}
}
/**
* Implements hook_field_widget_WIDGET_TYPE_form_alter().
*/
function uw_cfg_common_field_widget_media_library_widget_form_alter(array &$element, FormStateInterface $form_state, array $context): void {
/** @var \Drupal\Core\Routing\RouteMatchInterface $route_match */
$route_match = \Drupal::routeMatch();
if ($route_match->getRouteName() === 'layout_builder.add_block') {
/** @var \Drupal\media_library\MediaLibraryState $state */
$state = $element['open_button']['#media_library_state'];
$openerParameters = $state->getOpenerParameters();
$openerParameters['plugin_id'] = $route_match->getParameters()->get('plugin_id');
$new_state = MediaLibraryState::create($state->getOpenerId(), $state->getAllowedTypeIds(), $state->getSelectedTypeId(), $state->getAvailableSlots(), $openerParameters);
$element['open_button']['#media_library_state'] = $new_state;
}
}
/**
* Implements hook_ENTITY_TYPE_create_access().
*/
function uw_cfg_common_block_content_create_access(AccountInterface $account, array $context, string $entity_bundle): AccessResult {
$route_name = \Drupal::routeMatch()->getRouteName();
if ($route_name === 'media_library.ui') {
/** @var \Drupal\media_library\MediaLibraryState $state */
$state = MediaLibraryState::fromRequest(\Drupal::request());
$openerParameters = $state->getOpenerParameters();
// If the plugin ID exists within the opener parameters, we know
// the media library is being used on the layout builder form.
if (isset($openerParameters['plugin_id']) && substr($openerParameters['plugin_id'], 0, 12) === 'inline_block') {
if ($account->hasPermission('create and edit custom blocks')) {
return AccessResult::allowed();
}
}
}
// No opinion.
return AccessResult::neutral();
}
/**
* Implements hook_ENTITY_TYPE_access() for webform entities.
*/
function uw_cfg_common_webform_access(WebformInterface $webform, string $operation, AccountInterface $account): AccessResult {
// Always allow access for Form editor so they can see the forms they create.
if ($account->hasPermission('create webform')) {
return AccessResult::neutral();
}
// Allow access to submissions for Form results access.
if ($account->hasPermission('view any webform submission') && $operation === 'submission_view_any') {
return AccessResult::neutral();
}
switch ($webform->getThirdPartySetting('uw_cfg_common', 'access_control_method')) {
case 'anon':
if (!$account->isAnonymous()) {
return AccessResult::forbidden();
}
break;
case 'auth':
if (!$account->isAuthenticated()) {
return AccessResult::forbidden();
}
break;
case 'group':
// Must be authenticated for group auth.
if (!$account->isAuthenticated()) {
return AccessResult::forbidden();
}
// Access control by Active Directory group.
// Convert all groups to lowercase for case-insensitive matching.
$user_ad_groups = uw_cfg_common_get_user_ad_groups() ?: [];
$user_ad_groups = array_map('mb_strtolower', $user_ad_groups);
// Required group. If at least one is provided, the user must be in it.
$ad_require_groups = $webform->getThirdPartySetting('uw_cfg_common', 'ad_require_groups');
$ad_require_groups = array_map('mb_strtolower', $ad_require_groups);
if ($ad_require_groups && !array_intersect($ad_require_groups, $user_ad_groups)) {
return AccessResult::forbidden();
}
// Deny group. If at least one is provided, the user must not be in it.
$ad_deny_groups = $webform->getThirdPartySetting('uw_cfg_common', 'ad_deny_groups');
$ad_deny_groups = array_map('mb_strtolower', $ad_deny_groups);
if ($ad_deny_groups && array_intersect($ad_deny_groups, $user_ad_groups)) {
return AccessResult::forbidden();
}
break;
case 'user':
// Must be authenticated for group auth.
if (!$account->isAuthenticated()) {
return AccessResult::forbidden();
}
// Get all users when selecting 'Users specified below' under
// admin/structure/webform/manage/WEBFORM_ID/access.
$create_user_ids = $webform->getAccessRules()['create']['users'];
// If the logged in user is not a specified user, get access denied.
if (!in_array($account->id(), $create_user_ids)) {
return AccessResult::forbidden();
}
break;
}
return AccessResult::neutral();
}
/**
* Implements hook_views_plugins_field_alter().
*/
function uw_cfg_common_views_plugins_field_alter(array &$plugins): void {
// Replace Drupal\views\Plugin\views\field\Dropbutton with UW version.
$plugins['dropbutton']['class'] = 'Drupal\uw_cfg_common\Plugin\views\field\UWDropbutton';
}
/**
* Split text on EOL, trim, remove blanks and duplicates, and return as array.
*
* @param string|null $data
* The data to act on.
*
* @return string[]
* The data as an array.
*/
function uw_cfg_common_array_split_clean(?string $data): array {
$data = preg_split('/[\r\n]+/', $data);
$data = array_map('trim', $data);
$data = array_filter($data);
$data = array_unique($data);
return $data;
}
/**
* Return the Active Directory groups that the current user is part of.
*
* @return null|string[]
* An array of Active Directory group names or NULL if unable to determine.
*/
function uw_cfg_common_get_user_ad_groups(): ?array {
$attributes = \Drupal::service('simplesamlphp_auth.manager')->getAttributes();
return $attributes['http://schemas.xmlsoap.org/claims/Group'] ?? NULL;
}
/**
* Implements hook_preprocess_HOOK().
*
* Custom access denied messages with login/logout links.
*/
function uw_cfg_common_preprocess_webform_access_denied(array &$variables): void {
$webform = $variables['webform'];
$message = NULL;
switch ($webform->getThirdPartySetting('uw_cfg_common', 'access_control_method')) {
case 'auth':
case 'group':
case 'user':
// If authenticated access and anonymous user, login.
if (\Drupal::currentUser()->isAnonymous()) {
$route = 'user.login';
$message = 'You must <a href="@url">login to view this form</a>.';
}
break;
case 'anon':
// If anonymous access and authenticated user, logout.
if (\Drupal::currentUser()->isAuthenticated()) {
$route = 'user.logout';
$message = 'This form must be completed anonymously. You must <a href="@url">logout to view this form</a>.';
}
break;
}
// Set a custom message only if a message has been chosen above.
if ($message) {
$options = ['query' => \Drupal::destination()->getAsArray()];
$url = Url::fromRoute($route, [], $options);
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
$message = '<p>' . t($message, ['@url' => $url->toString()]) . '</p>';
$variables['message']['#markup'] = $message;
}
}
/**
* Implements hook_entity_type_alter().
*/
function uw_cfg_common_entity_type_alter(array &$entity_types) {
// Add validation constraint to the node entity.
$entity_types['node']->addConstraint('UwMedia');
}
/**
* Implements hook_simplesamlphp_auth_user_attributes().
*
* Add role expiry records for all roles populated automatically from
* simpleSAMLphp attributes by simplesamlphp_auth module.
*/
function uw_cfg_common_simplesamlphp_auth_user_attributes(UserInterface $account, array $attributes): bool {
// Get the SimplesamlphpDrupalAuth service.
$drupalauth = \Drupal::service('simplesamlphp_auth.drupalauth');
$roles_to_expire = $drupalauth->getMatchingRoles();
// Get the role_expire API.
$role_expire = \Drupal::service('role_expire.api');
$expiry_timestamp = time() + 24 * 60 * 60;
foreach ($roles_to_expire as $rid) {
// Add role_expiry for the account.
$role_expire->writeRecord($account->id(), $rid, $expiry_timestamp);
}
// This implementation does not alter $account, so return is always FALSE.
return FALSE;
}
/**
* Return the most recent completed Webform submission for a user on a form.
*
* @param string $webform_id
* The entity ID of the Webform.
* @param int $uid
* The user ID of the user.
*
* @return int|null
* The submission ID or NULL if there are none.
*/
function uw_cfg_common_get_most_recent_webform_submission(string $webform_id, int $uid): ?int {
// Load submission IDs from webform_submission storage.
$webform_submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission');
$query = $webform_submission_storage->getQuery();
$entity_ids = $query
->condition('webform_id', $webform_id)
->condition('uid', $uid)
->condition('in_draft', 0)
->sort('created', 'ASC')
->accessCheck(FALSE)
->execute();
// If there is at least one, return the last as integer, otherwise NULL.
return $entity_ids ? (int) end($entity_ids) : NULL;
}
/**
* Implements hook_fillpdf_populate_pdf_context_alter().
*/
function uw_cfg_common_fillpdf_populate_pdf_context_alter(array &$context): void {
// If there are no webform_submission entities but there is at least one
// webform entity, add the most recent submission for each webform.
// Only do this for authenticated users.
$current_uid = (int) \Drupal::currentUser()->id();
if ($current_uid && empty($context['entity_ids']['webform_submission']) && !empty($context['entity_ids']['webform'])) {
foreach ($context['entity_ids']['webform'] as $webform_id) {
$entity_id = uw_cfg_common_get_most_recent_webform_submission($webform_id, $current_uid);
if ($entity_id) {
$context['entity_ids']['webform_submission'][$entity_id] = $entity_id;
}
}
}
}
/**
* Implements hook_ENTITY_TYPE_view().
*
* Provide download links to associated PDFs on Layout Builder pages containing
* a Webform block. Only the first such block is used.
*/
function uw_cfg_common_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, string $view_mode): void {
// Do nothing unless view_mode full.
if ($view_mode !== 'full') {
return;
}
// Do nothing unless user is authenticated.
$current_user = \Drupal::currentUser();
if (!$current_user->id()) {
return;
}
// Find the first Layout Builder block that contains a Webform.
$webform = NULL;
foreach (Element::children($build['_layout_builder']) as $child) {
foreach (Element::children($build['_layout_builder'][$child]) as $grand_child) {
foreach (Element::children($build['_layout_builder'][$child][$grand_child]) as $great_grand_child) {
$element = $build['_layout_builder'][$child][$grand_child][$great_grand_child];
if (isset($element['content']['#webform']) && $element['content']['#webform'] instanceof WebformInterface) {
$webform = $element['content']['#webform'];
break 3;
}
}
}
}
// Do nothing unless:
// Node is Webform.
// Webform "Show the notification about previous submissions" is enabled.
if (!$webform || !$webform->getSetting('form_previous_submissions')) {
return;
}
// Do nothing if fillpdf_form entity type does not exist.
$fillpdf_form_exists = \Drupal::entityTypeManager()->hasDefinition('fillpdf_form');
if (!$fillpdf_form_exists) {
return;
}
// Load the FillPDF form for this Webform.
$fillpdf_form_storage = \Drupal::entityTypeManager()->getStorage('fillpdf_form');
$query = $fillpdf_form_storage->getQuery();
$query->condition('default_entity_type', 'webform');
$query->condition('default_entity_id', $webform->id());
$fillpdf_forms = $query->execute();
$fillpdf_forms = $fillpdf_form_storage->loadMultiple($fillpdf_forms);
// Do nothing unless there are PDFs associated with this Webform.
if (!$fillpdf_forms) {
return;
}
// Do nothing unless there is a Webform submission by this user.
// Find the most recent submission to this Webform by the current user.
$webform_submission_entity_id = uw_cfg_common_get_most_recent_webform_submission($webform->id(), $current_user->id());
if (!$webform_submission_entity_id) {
return;
}
// For each FillPDF form, check access and generate a link with filename.
$pdf_links = [];
$fillpdf_link_manipulator = \Drupal::service('fillpdf.link_manipulator');
$fillpdf_access_helper = \Drupal::service('fillpdf.access_helper');
$handle_pdf_controller = HandlePdfController::create(\Drupal::getContainer());
foreach ($fillpdf_forms as $fid => $fillpdf_form) {
$parameters = ['fid' => $fid];
$pdf_link = $fillpdf_link_manipulator->generateLink($parameters);
$context = $fillpdf_link_manipulator->parseLink($pdf_link);
$has_generate_pdf_access = $fillpdf_access_helper->canGeneratePdfFromContext($context, $current_user);
if ($has_generate_pdf_access) {
$handle_pdf_controller->alterContext($context);
$filename = $handle_pdf_controller->getFilename($context);
$pdf_links[] = Link::fromTextAndUrl($filename, $pdf_link);
}
}
// Do nothing if no links to display.
if (!$pdf_links) {
return;
}
// Create status message.
$message = [
'#theme' => 'item_list',
'#items' => $pdf_links,
'#prefix' => t('Download PDF version of your form:'),
];
$message = \Drupal::service('renderer')->render($message);
\Drupal::messenger()->addStatus($message);
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Configure admin/structure/fillpdf/FID.
*/
function uw_cfg_common_form_fillpdf_form_edit_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// We only use FillPDF with Webform, so hide the entity type selector and
// change the title of the entity selector.
$form['default_entity_type']['#access'] = FALSE;
$form['default_entity_id']['#title'] = t('Default Webform');
}
/**
* Implements hook_fillpdf_form_form_pre_form_build_alter().
*/
function uw_cfg_common_fillpdf_form_form_pre_form_build_alter(FillPdfFormForm $fillpdf_form_form): void {
// We only use FillPDF with Webform, so set the default entity type for any
// FillPDF form that does not have one to 'webform'.
$fillpdf_form = $fillpdf_form_form->getEntity();
$default_entity_type = $fillpdf_form->getDefaultEntityType();
if (!$default_entity_type) {
$fillpdf_form->set('default_entity_type', 'webform')->save();
}
}
/**
* Implements template_preprocess_html().
*/
function uw_cfg_common_preprocess_html(&$variables): void {
// Setting the meta tag for web forms getting indexed
// if not used any content.
if ($variables['root_path'] === 'webform') {
$noindex = [
'#tag' => 'meta',
'#attributes' => [
'name' => 'robots',
'content' => 'noindex',
],
];
$variables['page']['#attached']['html_head'][] = [$noindex, 'noindex'];
}
}
/**
* Set dynamic allowed values for the type of media field.
*
* @param \Drupal\field\Entity\FieldStorageConfig $definition
* The field definition.
* @param \Drupal\Core\Entity\ContentEntityInterface|null $entity
* The entity being created if applicable.
* @param bool $cacheable
* Boolean indicating if the results are cacheable.
*
* @return array
* An array of possible key and value options.
*
* @see options_allowed_values()
*/
function _uw_cfg_common_allowed_media_types(
FieldStorageConfig $definition,
ContentEntityInterface $entity = NULL,
$cacheable
) {
return [
'banner' => 'Banner',
'image' => 'Image',
];
}
/**
* Implements hook_views_query_alter().
*/
function uw_cfg_common_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
// Redirect page default sort updated, added redirect id to sort, on top of
// created datetime sort.
if ($view->id() === 'redirect') {
$query->addOrderBy('redirect', 'rid');
}
}
/**
* Implements hook_field_widget_single_element_WIDGET_TYPE_form_alter().
*/
function uw_cfg_common_field_widget_single_element_link_default_form_alter(array &$element, FormStateInterface $form_state, array $context) {
// Get field information from context.
$field_definition = $context['items']->getFieldDefinition();
// Set custom description only for event map field.
if ($field_definition->getName() == 'field_uw_event_map') {
$element['uri']['#description'] = t('Optional: provide a link to a map with the event location (e.g. https://uwaterloo.ca/map/). This must be an external URL such as https://example.com.');
}
else {
// Set custom description for all link fields except event map.
$element['uri']['#description'] = t('Start typing the title of a piece of content to select it. You can also enter an internal path such as /blog or an external URL such as https://example.com. Enter <front> to link to the front page.');
// Set custom description only for event host field.
if ($field_definition->getName() == 'field_uw_event_host') {
$element['uri']['#description'] .= ' ' . t('Enter <nolink> to display link text only.');
}
// Set custom description only for timeline link field.
if ($field_definition->getName() == 'field_uw_timeline_link') {
$element['uri']['#description'] = t('Links the entire content to a URL. If entered, do not use links inside the content itself.') . ' ' . $element['uri']['#description'];
}
// Set custom description only for banner link field.
if ($field_definition->getName() == 'field_uw_ban_link') {
$element['uri']['#description'] = t('Provide an optional link for this banner.') . ' ' . $element['uri']['#description'];
}
}
// Add link uri field element validation function.
$element['uri']['#element_validate'][] = '_uw_cfg_common_uw_link_validator';
}
/**
* Link uri field validation function.
*/
function _uw_cfg_common_uw_link_validator($element, &$form_state, $form) {
$uri = $element['#value'];
// Get the field name and set it up in the format used by setErrorByName.
$fieldname = rtrim($element['#name'], ']');
$pos = strpos($fieldname, '[');
$fieldname = substr_replace($fieldname, '][', $pos, 1);
// Buttons are just not allowed.
if ($uri == '<button>') {
$form_state->setErrorByName($fieldname, t('The <button> value is not supported for this field.'));
}
if ($uri == '<nolink>') {
// Don't allow blank link text when <nolink> is used.
if ($fieldname == 'field_uw_event_host][0][uri') {
$link_text = $form_state->getValue('field_uw_event_host')[0]['title'] ?? '';
if ($link_text == '') {
$form_state->setErrorByName('field_uw_event_host][0][title', t('You must provide link text when using <nolink> as the URL.'));
}
}
else {
// Nolink is not allowed, with one exception (event host field).
$form_state->setErrorByName($fieldname, t('The <nolink> value is not supported for this field.'));
}
}
}