setAbsolute(FALSE)->toString()); if (!in_array($route_name, ['system.modules_list', 'update.status']) && strpos($route_name, 'webform') === FALSE && strpos($path, '/webform') === FALSE) { return NULL; } /** @var \Drupal\webform\WebformHelpManagerInterface $help_manager */ $help_manager = \Drupal::service('webform.help_manager'); if ($route_name == 'help.page.webform') { $build = $help_manager->buildIndex(); } else { $build = $help_manager->buildHelp($route_name, $route_match); } if ($build) { $renderer = \Drupal::service('renderer'); $config = \Drupal::config('webform.settings'); $renderer->addCacheableDependency($build, $config); return $build; } else { return NULL; } } /** * Implements hook_webform_message_custom(). */ function webform_webform_message_custom($operation, $id) { if (strpos($id, 'webform_help_notification__') === 0 && $operation === 'close') { $id = str_replace('webform_help_notification__', '', $id); /** @var \Drupal\webform\WebformHelpManagerInterface $help_manager */ $help_manager = \Drupal::service('webform.help_manager'); $help_manager->deleteNotification($id); } } /** * Implements hook_modules_installed(). */ function webform_modules_installed($modules) { // Add webform paths when the path.module is being installed. if (in_array('path', $modules)) { /** @var \Drupal\webform\WebformInterface[] $webforms */ $webforms = Webform::loadMultiple(); foreach ($webforms as $webform) { $webform->updatePaths(); } } // Check HTML email provider support as modules are installed. /** @var \Drupal\webform\WebformEmailProviderInterface $email_provider */ $email_provider = \Drupal::service('webform.email_provider'); $email_provider->check(); } /** * Implements hook_modules_uninstalled(). */ function webform_modules_uninstalled($modules) { // Remove uninstalled module's third party settings from admin settings. $config = \Drupal::configFactory()->getEditable('webform.settings'); $third_party_settings = $config->get('third_party_settings'); $has_third_party_settings = FALSE; foreach ($modules as $module) { if (isset($third_party_settings[$module])) { $has_third_party_settings = TRUE; unset($third_party_settings[$module]); } } if ($has_third_party_settings) { $config->set('third_party_settings', $third_party_settings); $config->save(); } // Check HTML email provider support as modules are ininstalled. /** @var \Drupal\webform\WebformEmailProviderInterface $email_provider */ $email_provider = \Drupal::service('webform.email_provider'); $email_provider->check(); } /** * Implements hook_user_login(). */ function webform_user_login($account) { // Notify the storage of this log in. \Drupal::entityTypeManager()->getStorage('webform_submission')->userLogin($account); } /** * Implements hook_cron(). */ function webform_cron() { $config = \Drupal::config('webform.settings'); \Drupal::entityTypeManager()->getStorage('webform_submission')->purge($config->get('purge.cron_size')); } /** * Implements hook_local_tasks_alter(). */ function webform_local_tasks_alter(&$local_tasks) { // Change config translation local task hierarchy. if (isset($local_tasks['config_translation.local_tasks:entity.webform.config_translation_overview'])) { $local_tasks['config_translation.local_tasks:entity.webform.config_translation_overview']['base_route'] = 'entity.webform.canonical'; } if (isset($local_tasks['config_translation.local_tasks:config_translation.item.overview.webform.config'])) { $local_tasks['config_translation.local_tasks:config_translation.item.overview.webform.config']['parent_id'] = 'webform.config'; } // Disable 'Contribute' tab if explicitly disabled or the Contribute module // is installed. if (\Drupal::config('webform.settings')->get('ui.contribute_disabled') || \Drupal::moduleHandler()->moduleExists('contribute')) { unset($local_tasks['webform.contribute']); } } /** * Implements hook_menu_local_tasks_alter(). */ function webform_menu_local_tasks_alter(&$data, $route_name) { // Change 'Translate *' tab to be just label 'Translate'. if (isset($data['tabs'][0]['config_translation.local_tasks:entity.webform.config_translation_overview']['#link']['title'])) { $data['tabs'][0]['config_translation.local_tasks:entity.webform.config_translation_overview']['#link']['title'] = t('Translate'); } if (isset($data['tabs'][1]['config_translation.local_tasks:config_translation.item.overview.webform.config'])) { $data['tabs'][1]['config_translation.local_tasks:config_translation.item.overview.webform.config']['#link']['title'] = t('Translate'); } // ISSUE: // Devel routes do not use 'webform' parameter which throws the below error. // Some mandatory parameters are missing ("webform") to generate a URL for // route "entity.webform_submission.canonical" // // WORKAROUND: // Make sure webform parameter is set for all routes. if (strpos($route_name, 'entity.webform_submission.devel_') === 0 || $route_name === 'entity.webform_submission.token_devel') { foreach ($data['tabs'] as $tab_level) { foreach ($tab_level as $tab) { /** @var Drupal\Core\Url $url */ $url = $tab['#link']['url']; $tab_route_name = $url->getRouteName(); $tab_route_parameters = $url->getRouteParameters(); if (strpos($tab_route_name, 'entity.webform_submission.devel_') !== 0) { $webform_submission = WebformSubmission::load($tab_route_parameters['webform_submission']); $url->setRouteParameter('webform', $webform_submission->getWebform()->id()); } } } } } /** * Implements hook_module_implements_alter(). */ function webform_module_implements_alter(&$implementations, $hook) { if ($hook == 'form_alter') { $implementation = $implementations['webform']; unset($implementations['webform']); $implementations['webform'] = $implementation; } } /** * Implements hook_form_alter(). */ function webform_form_alter(&$form, FormStateInterface $form_state, $form_id) { if (strpos($form_id, 'webform_') === FALSE || strpos($form_id, 'node_') === 0) { return; } // Get form object. $form_object = $form_state->getFormObject(); // Alter the webform submission form. if (strpos($form_id, 'webform_submission') === 0 && $form_object instanceof WebformSubmissionForm) { // Make sure webform libraries are always attached to submission form. _webform_page_attachments($form); // After build. $form['#after_build'][] = '_webform_form_webform_submission_form_after_build'; } // Display editing original language warning. if (\Drupal::moduleHandler()->moduleExists('config_translation') && preg_match('/^entity.webform.(?:edit|settings|assets|access|handlers|third_party_settings)_form$/', \Drupal::routeMatch()->getRouteName())) { /** @var \Drupal\webform\WebformInterface $webform */ $webform = \Drupal::routeMatch()->getParameter('webform'); /** @var \Drupal\Core\Language\LanguageManagerInterface $language_manager */ $language_manager = \Drupal::service('language_manager'); // If current webform is translated, load the base (default) webform and apply // the translation to the elements. if ($webform->getLangcode() != $language_manager->getCurrentLanguage()->getId()) { $original_language = $language_manager->getLanguage($webform->getLangcode()); $form['langcode_message'] = [ '#type' => 'webform_message', '#message_type' => 'warning', '#message_message' => t('You are editing the original %language language for this webform.', ['%language' => $original_language->getName()]), '#message_close' => TRUE, '#message_storage' => WebformMessage::STORAGE_LOCAL, '#message_id' => $webform->id() . '.original_language', '#weight' => -100, ]; } } // Add details 'toggle all' to all webforms (except submission forms). if (!($form_object instanceof WebformSubmissionForm)) { $form['#attributes']['class'][] = 'js-webform-details-toggle'; $form['#attributes']['class'][] = 'webform-details-toggle'; $form['#attached']['library'][] = 'webform/webform.element.details.toggle'; return; } } /** * Alter webform after build. */ function _webform_form_webform_submission_form_after_build($form, FormStateInterface $form_state) { $form_object = $form_state->getFormObject(); /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ $webform_submission = $form_object->getEntity(); $webform = $webform_submission->getWebform(); // Add contextual links and change theme wrapper to webform.html.twig // which includes 'title_prefix' and 'title_suffix' variables needed for // contextual links to appear. $form['#contextual_links']['webform'] = [ 'route_parameters' => ['webform' => $webform->id()], ]; $form['#theme_wrappers'] = ['webform']; return $form; } /** * Implements hook_form_FORM_ID_alter(). * * Add warnings when attempting to update the Webform module using * the 'Update manager'. * * @see https://www.drupal.org/project/webform/issues/2930116 * @see https://www.drupal.org/project/webform/issues/2920095 */ function webform_form_update_manager_update_form_alter(&$form, FormStateInterface $form_state) { if (!isset($form['projects']) || !isset($form['projects']['#options']['webform'])) { return; } // Display dismissible warning at the top of the page. $t_args = [ ':href_manual' => 'https://www.drupal.org/docs/user_guide/en/extend-manual-install.html', ':href_drush' => 'https://www.drupal.org/docs/user_guide/en/security-update-module.html', ]; $form['webform_update_manager_warning'] = [ '#type' => 'webform_message', '#message_type' => 'warning', '#message_message' => t('The Webform module may not update properly using this administrative interface. It is strongly recommended that you update the Webform module manually or by using Drush.', $t_args), '#message_close' => TRUE, '#message_storage' => WebformMessage::STORAGE_SESSION, '#weight' => -10, ]; // Display warning to backup site when webform is checked. $form['projects']['#options']['webform']['title']['data'] = [ 'title' => $form['projects']['#options']['webform']['title']['data'], 'container' => [ '#type' => 'container', '#states' => ['visible' => [':input[name="projects[webform]"]' => ['checked' => TRUE]]], '#attributes' => ['class' => ['js-form-wrapper'], 'style' => 'display:none'], 'message' => [ '#type' => 'webform_message', '#message_type' => 'warning', '#message_message' => t('Please make sure to backup your website before updating the Webform module.'), ], ], ]; } /** * Implements hook_form_FORM_ID_alter(). */ function webform_form_views_exposed_form_alter(&$form, FormStateInterface $form_state, $form_id) { /** @var \Drupal\views\ViewExecutable $view */ $view = $form_state->get('view'); // Check if this a is webform submission view. // @see \Drupal\webform\WebformSubmissionListBuilder::buildSubmissionViews if (isset($view->webform_submission_view)) { $form['#action'] = Url::fromRoute(\Drupal::routeMatch()->getRouteName(), \Drupal::routeMatch()->getRawParameters()->all())->toString(); } } /** * Implements hook_system_breadcrumb_alter(). */ function webform_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context) { // Remove 'Webforms' prefix from breadcrumb links generated path breadcrumbs. // @see \Drupal\system\PathBasedBreadcrumbBuilder $path = Url::fromRouteMatch($route_match)->toString(); if (strpos($path, '/admin/structure/webform/config/') !== FALSE) { $links = $breadcrumb->getLinks(); foreach ($links as $link) { $text = $link->getText(); if (strpos($text, ((string) t('Webforms')) . ' ') == 0) { $text = str_replace(((string) t('Webforms')) . ': ', '', $text); $link->setText(Unicode::ucfirst($text)); } } } // Fix 'Help' breadcrumb text. if ($route_match->getRouteName() == 'webform.help.video') { $links = $breadcrumb->getLinks(); $link = end($links); $link->setText(t('Webforms')); } } /** * Implements hook_token_info_alter(). */ function webform_token_info_alter(&$data) { // Append token suffixes to all webform token descriptions. // @see \Drupal\webform\WebformTokenManager::replace $token_suffixes = '
' . t('Append the below suffixes to alter the returned value.') . ''; foreach ($data['types'] as $type => &$info) { if (strpos($type, 'webform') === 0) { $info['description'] = Markup::create($info['description'] . ' ' . $token_suffixes); } } } /** * Implements hook_entity_delete(). */ function webform_entity_delete(EntityInterface $entity) { /** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */ $entity_reference_manager = \Drupal::service('webform.entity_reference_manager'); // Delete saved export settings for a webform or source entity with the // webform field. if (($entity instanceof WebformInterface) || $entity_reference_manager->hasField($entity)) { $name = 'webform.export.' . $entity->getEntityTypeId() . '.' . $entity->id(); \Drupal::state()->delete($name); } } /** * Implements hook_mail(). */ function webform_mail($key, &$message, $params) { // Never send emails when using devel generate to create 1000's of // submissions. if (\Drupal::moduleHandler()->moduleExists('devel_generate') && \Drupal\webform\Plugin\DevelGenerate\WebformSubmissionDevelGenerate::isGeneratingSubmissions()) { $message['send'] = FALSE; } // Set default parameters. $params += [ 'from_mail' => '', 'from_name' => '', 'cc_mail' => '', 'bcc_mail' => '', 'reply_to' => '', 'return_path' => '', 'sender_mail' => '', 'sender_name' => '', ]; $message['subject'] = $params['subject']; $message['body'][] = $params['body']; // Set the header 'From'. // Usingthe 'from_mail' so that the webform's email from value is used // instead of site's email address. // @see: \Drupal\Core\Mail\MailManager::mail. if (!empty($params['from_mail'])) { $message['from'] = $message['headers']['From'] = (!empty($params['from_name'])) ? Unicode::mimeHeaderEncode($params['from_name'], TRUE) . ' <' . $params['from_mail'] . '>' : $params['from_mail']; } // Set header 'Cc'. if (!empty($params['cc_mail'])) { $message['headers']['Cc'] = $params['cc_mail']; } // Set header 'Bcc'. if (!empty($params['bcc_mail'])) { $message['headers']['Bcc'] = $params['bcc_mail']; } // Set header 'Reply-to'. $reply_to = $params['reply_to'] ?: ''; if (empty($reply_to) && !empty($params['from_mail'])) { // @todo Determine if the 'reply-to' must only be the to 'from mail' if 'from mail' has the same domain as the 'site mail'. $reply_to = $message['from']; } if ($reply_to) { $message['reply-to'] = $message['headers']['Reply-to'] = $reply_to; } // Set header 'Return-Path'. $return_path = $params['return_path'] ?: $params['from_mail'] ?: ''; if ($return_path) { $message['headers']['Sender'] = $message['headers']['Return-Path'] = $return_path; } // Set header 'Sender'. $sender_mail = $params['sender_mail'] ?: ''; $sender_name = $params['sender_name'] ?: $params['from_name'] ?: ''; if ($sender_mail) { $message['headers']['Sender'] = ($sender_name) ? Unicode::mimeHeaderEncode($sender_name, TRUE) . ' <' . $sender_mail . '>' : $sender_mail; } } /** * Implements hook_mail_alter(). */ function webform_mail_alter(&$message) { // Drupal hardcodes all mail header as 'text/plain' so we need to set the // header's 'Content-type' to HTML if the EmailWebformHandler's // 'html' flag has been set. // @see \Drupal\Core\Mail\MailManager::mail() // @see \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler::getMessage(). if (strpos($message['id'], 'webform') === 0) { if (isset($message['params']['html']) && $message['params']['html']) { $message['headers']['Content-Type'] = 'text/html; charset=UTF-8; format=flowed'; } } } /** * Implements hook_page_attachments(). */ function webform_page_attachments(array &$attachments) { $route_name = \Drupal::routeMatch()->getRouteName(); $url = Url::fromRoute('')->toString(); // Attach global libraries only to webform specific pages. if (preg_match('/^(webform\.|^entity\.([^.]+\.)?webform)/', $route_name) || preg_match('#(/node/add/webform|/admin/help/webform)#', $url)) { _webform_page_attachments($attachments); } // Attach codemirror and select2 library to block admin to ensure that the // library is loaded by the webform block is placed using Ajax. if (strpos($route_name, 'block.admin_display') === 0) { $attachments['#attached']['library'][] = 'webform/webform.block'; } // Attach 'Contribute' section style. if (\Drupal::routeMatch()->getRouteName() === 'webform.contribute') { /** @var \Drupal\webform\WebformContributeManagerInterface $contribute_manager */ $contribute_manager = \Drupal::service('webform.contribute_manager'); $attachments['#attached']['html_head'][] = [ [ '#type' => 'html_tag', '#tag' => 'style', '#value' => $contribute_manager->getStyle(), ], 'webform_contribute', ]; } // Attach webform dialog library and options to every page. if (\Drupal::config('webform.settings')->get('settings.dialog')) { $attachments['#attached']['library'][] = 'webform/webform.dialog'; $attachments['#attached']['drupalSettings']['webform']['dialog']['options'] = \Drupal::config('webform.settings')->get('settings.dialog_options'); /** @var \Drupal\webform\WebformRequestInterface $request_handler */ $request_handler = \Drupal::service('webform.request'); if ($source_entity = $request_handler->getCurrentSourceEntity()) { $attachments['#attached']['drupalSettings']['webform']['dialog']['entity_type'] = $source_entity->getEntityTypeId(); $attachments['#attached']['drupalSettings']['webform']['dialog']['entity_id'] = $source_entity->id(); } } } /** * Add webform libraries to page attachments. * * @param array $attachments * An array of page attachments. */ function _webform_page_attachments(array &$attachments) { /** @var \Drupal\webform\WebformThemeManagerInterface $theme_manager */ $theme_manager = \Drupal::service('webform.theme_manager'); $active_theme_names = $theme_manager->getActiveThemeNames(); foreach ($active_theme_names as $active_theme_name) { if (file_exists(drupal_get_path('module', 'webform') . "/css/webform.theme.$active_theme_name.css")) { $attachments['#attached']['library'][] = "webform/webform.theme.$active_theme_name"; } } // Attach webform contextual link helper. if (\Drupal::currentUser()->hasPermission('access contextual links')) { $attachments['#attached']['library'][] = 'webform/webform.contextual'; } // Attach details element save open/close library. // This ensures pages without a webform will still be able to save the // details element state. if (\Drupal::config('webform.settings')->get('ui.details_save')) { $attachments['#attached']['library'][] = 'webform/webform.element.details.save'; } // Assets: Add custom shared and webform specific CSS and JS. // @see webform_library_info_build() /** @var \Drupal\webform\WebformRequestInterface $request_handler */ $request_handler = \Drupal::service('webform.request'); if ($webform = $request_handler->getCurrentWebform()) { $assets = $webform->getAssets(); foreach ($assets as $type => $value) { if ($value) { $attachments['#attached']['library'][] = 'webform/webform.' . $type . '.' . $webform->id(); } } } } /** * Implements hook_library_info_build(). */ function webform_library_info_build() { $base_path = base_path(); $default_query_string = \Drupal::state()->get('system.css_js_query_string') ?: '0'; /** @var \Drupal\webform\WebformInterface[] $webforms */ $webforms = Webform::loadMultiple(); $libraries = []; foreach ($webforms as $webform_id => $webform) { $assets = array_filter($webform->getAssets()); foreach ($assets as $type => $value) { // Note: // Set 'type' to 'external' and manually build the CSS/JS file path // to prevent JS from being parsed by locale_js_alter() // @see locale_js_alter() // @see https://www.drupal.org/node/1803330 $settings = ['type' => 'external', 'preprocess' => FALSE, 'minified' => FALSE]; if ($type === 'css') { $libraries["webform.css.$webform_id"] = [ 'css' => ['theme' => ["{$base_path}webform/css/{$webform_id}?{$default_query_string}" => $settings]], ]; } else { $libraries["webform.javascript.$webform_id"] = [ 'js' => ["{$base_path}webform/javascript/{$webform_id}?{$default_query_string}" => $settings], ]; } } } return $libraries; } /** * Implements hook_css_alter(). */ function webform_css_alter(&$css, AttachedAssetsInterface $assets) { // Remove the stable.theme's off-canvas CSS reset for webform admin routes. // @see https://www.drupal.org/project/drupal/issues/2826722 // // NOTE: Most admin themes correct style jQuery UI dialogs because Drupal's // admin UI, especially Views, relies on them. $use_off_canvas = WebformDialogHelper::useOffCanvas(); $is_admin_route = \Drupal::service('router.admin_context')->isAdminRoute(); $is_webform_route = preg_match('/(^webform|^entity\.webform|^entity\.node\.webform)/', \Drupal::routeMatch()->getRouteName()); if ($use_off_canvas && $is_admin_route && $is_webform_route) { foreach ($css as $key => $item) { if (strpos($key, 'core/themes/stable/css/core/dialog/off-canvas') === 0) { unset($css[$key]); } } } _webform_asset_alter($css, 'css'); } /** * Implements hook_js_alter(). */ function webform_js_alter(&$javascript, AttachedAssetsInterface $assets) { // Add Google API key required by webform/libraries.jquery.geocomplete // which is dependency for webform/webform.element.location.geocomplete. // // @see \Drupal\webform\Element\WebformLocationGeocomplete::processWebformComposite // @see webform.libraries.yml // @see webform/webform.element.location.geocomplete // @see webform/libraries.jquery.geocomplete $settings = $assets->getSettings(); if (!empty($settings['webform']['location']['geocomplete']['api_key']) && isset($javascript['https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places'])) { $api_key = $settings['webform']['location']['geocomplete']['api_key']; $javascript['https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places']['data'] = "https://maps.googleapis.com/maps/api/js?key=$api_key&libraries=places"; unset($settings['webform']['location']['geocomplete']['api_key']); $assets->setSettings($settings); } _webform_asset_alter($javascript, 'javascript'); } /** * Alter Webform CSS or JavaScript assets and make sure they appear last. * * @param array $items * An array of all CSS or JavaScript being presented on the page. * @param string $type * The type of asset being attached. * * @see hook_library_info_build() */ function _webform_asset_alter(array &$items, $type) { foreach ($items as $key => &$item) { if (strpos($key, "webform/$type/") === 0) { $item['weight'] = 1000; $item['group'] = 1000; } } } /** * Implements hook_file_access(). * * @see file_file_download() * @see webform_preprocess_file_link() */ function webform_file_access(FileInterface $file, $operation, AccountInterface $account) { // Block access to temporary anonymous private file uploads. if ($operation == 'download' && $file->isTemporary() && $file->getOwnerId() == 0 && strpos($file->getFileUri(), 'private://webform/') === 0) { return AccessResult::forbidden(); } return AccessResult::neutral(); } /** * Implements hook_file_download(). */ function webform_file_download($uri) { return ManagedFile::accessFileDownload($uri); } /** * Implements hook_contextual_links_view_alter(). * * Add .webform-contextual class to all webform context links. * * @see webform.links.contextual.yml * @see js/webform.contextual.js */ function webform_contextual_links_view_alter(&$element, $items) { $links = [ 'entitywebformtest-form', 'entitywebformresults-submissions', 'entitywebformedit-form', 'entitywebformsettings', ]; foreach ($links as $link) { if (isset($element['#links'][$link])) { $element['#links'][$link]['attributes']['class'][] = 'webform-contextual'; } } } /** * Implements hook_webform_access_rules(). */ function webform_webform_access_rules() { return [ 'create' => [ 'title' => t('Create submissions'), 'roles' => [ 'anonymous', 'authenticated', ], ], 'view_any' => [ 'title' => t('View any submissions'), ], 'update_any' => [ 'title' => t('Update any submissions'), ], 'delete_any' => [ 'title' => t('Delete any submissions'), ], 'purge_any' => [ 'title' => t('Purge any submissions'), ], 'view_own' => [ 'title' => t('View own submissions'), ], 'update_own' => [ 'title' => t('Update own submissions'), ], 'delete_own' => [ 'title' => t('Delete own submissions'), ], 'administer' => [ 'title' => t('Administer webform & submissions'), 'description' => [ '#type' => 'webform_message', '#message_type' => 'warning', '#message_message' => t('Warning: The below settings give users, permissions, and roles full access to this webform and its submissions.'), ], ], 'test' => [ 'title' => t('Test webform'), ], 'configuration' => [ 'title' => t('Access webform configuration'), 'description' => [ '#type' => 'webform_message', '#message_type' => 'warning', '#message_message' => t("Warning: The below settings give users, permissions, and roles full access to this webform's configuration via API requests."), ], ], ]; } /** * Adds JavaScript to change the state of an element based on another element. * * @param array $elements * A renderable array element having a #states property as described above. * @param string $key * The element property to add the states attribute to. * * @see drupal_process_states() */ function webform_process_states(array &$elements, $key = '#attributes') { if (empty($elements['#states'])) { return; } $elements['#attached']['library'][] = 'core/drupal.states'; $elements[$key]['data-drupal-states'] = Json::encode($elements['#states']); // Make sure to include target class for this container. if (empty($elements[$key]['class']) || !WebformArrayHelper::inArray(['js-form-item', 'js-form-submit', 'js-form-wrapper'], $elements[$key]['class'])) { $elements[$key]['class'][] = 'js-form-item'; } } /******************************************************************************/ // Element info hooks. /******************************************************************************/ /** * Implements hook_element_info_alter(). */ function webform_element_info_alter(array &$info) { $info['checkboxes']['#process'][] = 'webform_process_options'; $info['radios']['#process'][] = 'webform_process_options'; } /** * Process radios or checkboxes descriptions. * * @param array $element * An associative array containing the properties and children of the * radios or checkboxes element. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete webform structure. * * @return array * The processed element. */ function webform_process_options(&$element, FormStateInterface $form_state, &$complete_form) { if (!WebformElementHelper::isWebformElement($element)) { return $element; } if (!empty($element['#options_description_display'])) { $description_property_name = ($element['#options_description_display'] == 'help') ? '#help' : '#description'; foreach (Element::children($element) as $key) { $title = (string) $element[$key]['#title']; // Check for -- delimiter. if (strpos($title, WebformOptionsHelper::DESCRIPTION_DELIMITER) === FALSE) { continue; } list($title, $description) = explode(WebformOptionsHelper::DESCRIPTION_DELIMITER, $title); $element[$key]['#title'] = $title; $element[$key]['#webform_element'] = TRUE; $element[$key][$description_property_name] = $description; } } // Issue #2839344: Some aria-describedby refers to not existing element ID. // @see https://www.drupal.org/project/drupal/issues/2839344 if (!empty($element['#attributes']['aria-describedby'])) { foreach (Element::children($element) as $key) { if (empty($element[$key]['#attributes']['aria-describedby']) && $element['#attributes']['aria-describedby'] === $element[$key]['#attributes']['aria-describedby']) { unset($element[$key]['#attributes']['aria-describedby']); } } } return $element; } /******************************************************************************/ // Private functions. /******************************************************************************/ /** * Provides custom PHP error handling when webform rendering is validated. * * Converts E_RECOVERABLE_ERROR to WARNING so that an exceptions can be thrown * and caught by * \Drupal\webform\WebformEntityElementsValidator::validateRendering(). * * @param int $error_level * The level of the error raised. * @param string $message * The error message. * @param string $filename * The filename that the error was raised in. * @param int $line * The line number the error was raised at. * @param array $context * An array that points to the active symbol table at the point the error * occurred. * * @throws \ErrorException * Throw ErrorException for E_RECOVERABLE_ERROR errors. * * @see \Drupal\webform\WebformEntityElementsValidator::validateRendering() */ function _webform_entity_element_validate_rendering_error_handler($error_level, $message, $filename, $line, array $context) { // From: http://stackoverflow.com/questions/15461611/php-try-catch-not-catching-all-exceptions if (E_RECOVERABLE_ERROR === $error_level) { // Allow Drupal to still log the error but convert it to a warning. _drupal_error_handler(E_WARNING, $message, $filename, $line, $context); throw new ErrorException($message, $error_level, 0, $filename, $line); } else { _drupal_error_handler($error_level, $message, $filename, $line, $context); } } /** * Provides custom PHP exception handling when webform rendering is validated. * * @param \Exception|\Throwable $exception * The exception object that was thrown. * * @throws \Exception * Throw the exception back to * WebformEntityElementsValidator::validateRendering(). * * @see \Drupal\webform\WebformEntityElementsValidator::validateRendering() */ function _webform_entity_element_validate_rendering_exception_handler($exception) { throw $exception; } /** * Implements hook_query_alter(). * * Append EAV sort to webform_submission entity query. * * @see http://stackoverflow.com/questions/12893314/sorting-eav-database * @see \Drupal\webform\WebformSubmissionListBuilder::getEntityIds */ function webform_query_alter(AlterableInterface $query) { /** @var \Drupal\Core\Database\Query\SelectInterface $query */ $name = $query->getMetaData('webform_submission_element_name'); if (!$name) { return; } $direction = $query->getMetaData('webform_submission_element_direction'); $property_name = $query->getMetaData('webform_submission_element_property_name'); $query->distinct(); $query->addJoin('INNER', 'webform_submission_data', NULL, 'base_table.sid = webform_submission_data.sid'); $query->addField('webform_submission_data', 'value', 'value'); $query->condition('name', $name); if ($property_name) { $query->condition('property', $property_name); } $query->orderBy('value', $direction); }