<?php /** * @file * Module file. */ use Drupal\Component\Utility\Html; use Drupal\Core\Access\AccessResult; 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\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\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 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') { // 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); } } } } // If there is a type of media, ensure that we do only have // values in the fields that are selected. if ($entity->hasField('field_uw_type_of_media')) { // Get the type of media. $type_of_media = $entity->field_uw_type_of_media->value; // If it is null then set the hero image to null. if ($type_of_media == NULL) { $entity->set('field_uw_hero_image', NULL); } } } } /** * 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 { // Remove the None option from layout builder styles. unset($form['layout_builder_style']['#empty_option']); // Ensuring that the contained width is selected by default. $form['layout_builder_style']['#default_value'] = $form['layout_builder_style']['#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(). * * 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) { return []; } $render = $plugin_block->build(); $variables['uw_content_moderation_form'] = $render; } } /** * 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-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.*add_form/', $form_id) || preg_match('/node_uw.*edit_form/', $form_id)) { // 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'])) { $form['field_uw_hero_image']['#states'] = [ 'visible' => [ [ 'select[name="field_uw_type_of_media"]' => [ ['value' => 'image'], ], ], ], ]; } // 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]); } } } } } } } } /** * 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; } 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(); $query->condition('webform_id', $webform_id); $query->condition('uid', $uid); $query->condition('in_draft', 0); $query->sort('created', 'ASC'); $entity_ids = $query->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(); } }