2017-03-16 15:29:07 +00:00
<?php
/**
* @file
* Enables the creation of webforms and questionnaires.
*/
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Database\Query\AlterableInterface;
2018-11-23 12:29:20 +00:00
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Markup;
2017-03-16 15:29:07 +00:00
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\file\FileInterface;
use Drupal\webform\Entity\Webform;
use Drupal\webform\Entity\WebformSubmission;
use Drupal\webform\Element\WebformMessage;
use Drupal\webform\Plugin\WebformElement\ManagedFile;
use Drupal\webform\Utility\WebformArrayHelper;
2018-11-23 12:29:20 +00:00
use Drupal\webform\Utility\WebformElementHelper;
use Drupal\webform\Utility\WebformDialogHelper;
use Drupal\webform\Utility\WebformOptionsHelper;
2017-03-16 15:29:07 +00:00
use Drupal\webform\WebformInterface;
use Drupal\webform\WebformSubmissionForm;
2018-11-23 12:29:20 +00:00
require_once __DIR__ . '/includes/webform.date.inc';
require_once __DIR__ . '/includes/webform.libraries.inc';
require_once __DIR__ . '/includes/webform.options.inc';
require_once __DIR__ . '/includes/webform.theme.inc';
require_once __DIR__ . '/includes/webform.translation.inc';
require_once __DIR__ . '/includes/webform.editor.inc';
2017-03-16 15:29:07 +00:00
/**
* Implements hook_help().
*/
function webform_help($route_name, RouteMatchInterface $route_match) {
// Get path from route match.
$path = preg_replace('/^' . preg_quote(base_path(), '/') . '/', '/', Url::fromRouteMatch($route_match)->setAbsolute(FALSE)->toString());
2018-11-23 12:29:20 +00:00
if (!in_array($route_name, ['system.modules_list', 'update.status']) && strpos($route_name, 'webform') === FALSE && strpos($path, '/webform') === FALSE) {
2017-03-16 15:29:07 +00:00
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;
}
}
2018-11-23 12:29:20 +00:00
/**
* 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);
}
}
2017-03-16 15:29:07 +00:00
/**
* 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');
2018-11-23 12:29:20 +00:00
$has_third_party_settings = FALSE;
2017-03-16 15:29:07 +00:00
foreach ($modules as $module) {
2018-11-23 12:29:20 +00:00
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();
2017-03-16 15:29:07 +00:00
}
// 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();
}
2018-11-23 12:29:20 +00:00
/**
* Implements hook_user_login().
*/
function webform_user_login($account) {
// Notify the storage of this log in.
\Drupal::entityTypeManager()->getStorage('webform_submission')->userLogin($account);
}
2017-03-16 15:29:07 +00:00
/**
* Implements hook_cron().
*/
function webform_cron() {
$config = \Drupal::config('webform.settings');
2018-11-23 12:29:20 +00:00
\Drupal::entityTypeManager()->getStorage('webform_submission')->purge($config->get('purge.cron_size'));
2017-03-16 15:29:07 +00:00
}
/**
* Implements hook_local_tasks_alter().
*/
function webform_local_tasks_alter(&$local_tasks) {
2018-11-23 12:29:20 +00:00
// Change config translation local task hierarchy.
2017-03-16 15:29:07 +00:00
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';
}
2018-11-23 12:29:20 +00:00
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']);
}
2017-03-16 15:29:07 +00:00
}
/**
* Implements hook_menu_local_tasks_alter().
*/
function webform_menu_local_tasks_alter(&$data, $route_name) {
2018-11-23 12:29:20 +00:00
// 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');
2017-03-16 15:29:07 +00:00
}
// 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());
}
}
}
}
}
2018-11-23 12:29:20 +00:00
/**
* 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;
}
}
2017-03-16 15:29:07 +00:00
/**
* 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;
}
2018-11-23 12:29:20 +00:00
// 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';
}
2017-03-16 15:29:07 +00:00
// 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,
];
}
}
2018-11-23 12:29:20 +00:00
// Add details 'toggle all' to all webforms (except submission forms).
if (!($form_object instanceof WebformSubmissionForm)) {
2017-03-16 15:29:07 +00:00
$form['#attributes']['class'][] = 'js-webform-details-toggle';
$form['#attributes']['class'][] = 'webform-details-toggle';
$form['#attached']['library'][] = 'webform/webform.element.details.toggle';
2018-11-23 12:29:20 +00:00
return;
2017-03-16 15:29:07 +00:00
}
}
/**
* Alter webform after build.
*/
2018-11-23 12:29:20 +00:00
function _webform_form_webform_submission_form_after_build($form, FormStateInterface $form_state) {
2017-03-16 15:29:07 +00:00
$form_object = $form_state->getFormObject();
2018-11-23 12:29:20 +00:00
/** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
$webform_submission = $form_object->getEntity();
$webform = $webform_submission->getWebform();
2017-03-16 15:29:07 +00:00
// 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'] = [
2018-11-23 12:29:20 +00:00
'route_parameters' => ['webform' => $webform->id()],
2017-03-16 15:29:07 +00:00
];
$form['#theme_wrappers'] = ['webform'];
return $form;
}
2018-11-23 12:29:20 +00:00
/**
* 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 <a href=":href_manual">manually</a> or by using <a href=":href_drush">Drush</a>.', $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();
}
}
2017-03-16 15:29:07 +00:00
/**
* 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();
2018-11-23 12:29:20 +00:00
if (strpos($path, '/admin/structure/webform/config/') !== FALSE) {
2017-03-16 15:29:07 +00:00
$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.
2018-11-23 12:29:20 +00:00
if ($route_match->getRouteName() == 'webform.help.video') {
2017-03-16 15:29:07 +00:00
$links = $breadcrumb->getLinks();
$link = end($links);
$link->setText(t('Webforms'));
}
}
2018-11-23 12:29:20 +00:00
/**
* 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 = '<br/>' . t('Append the below suffixes to alter the returned value.') .
'<ul>' .
'<li>' . t('<code>:clear</code> to removes token when not replaced.') . '</li>' .
'<li>' . t('<code>:urlencode</code> URL encodes returned value.') . '</li>' .
'<li>' . t('<code>:htmldecode</code> decodes HTML enities in returned value.') . '<br/><b>' . t('This suffix has security implications.') . '</b><br/>' . t('Use <code>:htmldecode</code> with <code>:striptags</code>.') . '</li>' .
'<li>' . t('<code>:striptags</code> removes all HTML tags from returned value.') . '</li>' .
'</ul>';
foreach ($data['types'] as $type => &$info) {
if (strpos($type, 'webform') === 0) {
$info['description'] = Markup::create($info['description'] . ' ' . $token_suffixes);
}
}
}
2017-03-16 15:29:07 +00:00
/**
* Implements hook_entity_delete().
*/
function webform_entity_delete(EntityInterface $entity) {
2018-11-23 12:29:20 +00:00
/** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */
$entity_reference_manager = \Drupal::service('webform.entity_reference_manager');
2017-03-16 15:29:07 +00:00
// Delete saved export settings for a webform or source entity with the
// webform field.
2018-11-23 12:29:20 +00:00
if (($entity instanceof WebformInterface) || $entity_reference_manager->hasField($entity)) {
2017-03-16 15:29:07 +00:00
$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;
}
2018-11-23 12:29:20 +00:00
// Set default parameters.
$params += [
'from_mail' => '',
'from_name' => '',
'cc_mail' => '',
'bcc_mail' => '',
'reply_to' => '',
'return_path' => '',
'sender_mail' => '',
'sender_name' => '',
];
2017-03-16 15:29:07 +00:00
$message['subject'] = $params['subject'];
$message['body'][] = $params['body'];
2018-11-23 12:29:20 +00:00
// 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.
2017-03-16 15:29:07 +00:00
if (!empty($params['from_mail'])) {
2018-11-23 12:29:20 +00:00
$message['from'] = $message['headers']['From'] = (!empty($params['from_name'])) ? Unicode::mimeHeaderEncode($params['from_name'], TRUE) . ' <' . $params['from_mail'] . '>' : $params['from_mail'];
2017-03-16 15:29:07 +00:00
}
2018-11-23 12:29:20 +00:00
// Set header 'Cc'.
2017-03-16 15:29:07 +00:00
if (!empty($params['cc_mail'])) {
$message['headers']['Cc'] = $params['cc_mail'];
}
2018-11-23 12:29:20 +00:00
// Set header 'Bcc'.
2017-03-16 15:29:07 +00:00
if (!empty($params['bcc_mail'])) {
$message['headers']['Bcc'] = $params['bcc_mail'];
}
2018-11-23 12:29:20 +00:00
// 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;
}
2017-03-16 15:29:07 +00:00
}
/**
* 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().
*/
2018-11-23 12:29:20 +00:00
function webform_page_attachments(array &$attachments) {
$route_name = \Drupal::routeMatch()->getRouteName();
2017-03-16 15:29:07 +00:00
$url = Url::fromRoute('<current>')->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)) {
2018-11-23 12:29:20 +00:00
_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();
2017-03-16 15:29:07 +00:00
}
2018-11-23 12:29:20 +00:00
}
}
2017-03-16 15:29:07 +00:00
2018-11-23 12:29:20 +00:00
/**
* 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";
2017-03-16 15:29:07 +00:00
}
}
2018-11-23 12:29:20 +00:00
// 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();
}
}
2017-03-16 15:29:07 +00:00
}
}
2018-11-23 12:29:20 +00:00
/**
* 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;
}
2017-03-16 15:29:07 +00:00
/**
* Implements hook_css_alter().
*/
function webform_css_alter(&$css, AttachedAssetsInterface $assets) {
2018-11-23 12:29:20 +00:00
// 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');
2017-03-16 15:29:07 +00:00
}
/**
* Implements hook_js_alter().
*/
function webform_js_alter(&$javascript, AttachedAssetsInterface $assets) {
2018-11-23 12:29:20 +00:00
// 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');
2017-03-16 15:29:07 +00:00
}
/**
2018-11-23 12:29:20 +00:00
* Alter Webform CSS or JavaScript assets and make sure they appear last.
2017-03-16 15:29:07 +00:00
*
* @param array $items
* An array of all CSS or JavaScript being presented on the page.
* @param string $type
* The type of asset being attached.
2018-11-23 12:29:20 +00:00
*
* @see hook_library_info_build()
2017-03-16 15:29:07 +00:00
*/
2018-11-23 12:29:20 +00:00
function _webform_asset_alter(array &$items, $type) {
foreach ($items as $key => &$item) {
if (strpos($key, "webform/$type/") === 0) {
$item['weight'] = 1000;
$item['group'] = 1000;
2017-03-16 15:29:07 +00:00
}
}
}
/**
* 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();
}
2018-11-23 12:29:20 +00:00
return AccessResult::neutral();
2017-03-16 15:29:07 +00:00
}
/**
* Implements hook_file_download().
*/
function webform_file_download($uri) {
return ManagedFile::accessFileDownload($uri);
}
/**
2018-11-23 12:29:20 +00:00
* 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
2017-03-16 15:29:07 +00:00
*/
2018-11-23 12:29:20 +00:00
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';
}
}
}
2017-03-16 15:29:07 +00:00
2018-11-23 12:29:20 +00:00
/**
* Implements hook_webform_access_rules().
*/
function webform_webform_access_rules() {
return [
'create' => [
'title' => t('Create submissions'),
'roles' => [
'anonymous',
'authenticated',
],
2017-03-16 15:29:07 +00:00
],
2018-11-23 12:29:20 +00:00
'view_any' => [
'title' => t('View any submissions'),
2017-03-16 15:29:07 +00:00
],
2018-11-23 12:29:20 +00:00
'update_any' => [
'title' => t('Update any submissions'),
2017-03-16 15:29:07 +00:00
],
2018-11-23 12:29:20 +00:00
'delete_any' => [
'title' => t('Delete any submissions'),
2017-03-16 15:29:07 +00:00
],
2018-11-23 12:29:20 +00:00
'purge_any' => [
'title' => t('Purge any submissions'),
2017-03-16 15:29:07 +00:00
],
2018-11-23 12:29:20 +00:00
'view_own' => [
'title' => t('View own submissions'),
2017-03-16 15:29:07 +00:00
],
2018-11-23 12:29:20 +00:00
'update_own' => [
'title' => t('Update own submissions'),
2017-03-16 15:29:07 +00:00
],
2018-11-23 12:29:20 +00:00
'delete_own' => [
'title' => t('Delete own submissions'),
2017-03-16 15:29:07 +00:00
],
2018-11-23 12:29:20 +00:00
'administer' => [
'title' => t('Administer webform & submissions'),
'description' => [
'#type' => 'webform_message',
'#message_type' => 'warning',
'#message_message' => t('<strong>Warning</strong>: The below settings give users, permissions, and roles full access to this webform and its submissions.'),
2017-03-16 15:29:07 +00:00
],
],
2018-11-23 12:29:20 +00:00
'test' => [
'title' => t('Test webform'),
],
'configuration' => [
'title' => t('Access webform configuration'),
'description' => [
'#type' => 'webform_message',
'#message_type' => 'warning',
'#message_message' => t("<strong>Warning</strong>: The below settings give users, permissions, and roles full access to this webform's configuration via API requests."),
2017-03-16 15:29:07 +00:00
],
],
];
}
/**
* 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';
}
}
2018-11-23 12:29:20 +00:00
/******************************************************************************/
// 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;
}
2017-03-16 15:29:07 +00:00
/******************************************************************************/
// 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 {
2018-11-23 12:29:20 +00:00
_drupal_error_handler($error_level, $message, $filename, $line, $context);
2017-03-16 15:29:07 +00:00
}
}
2018-11-23 12:29:20 +00:00
/**
* 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;
}
2017-03-16 15:29:07 +00:00
/**
* 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);
}