937 lines
34 KiB
Plaintext
937 lines
34 KiB
Plaintext
<?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;
|
|
use Drupal\Core\Render\Element;
|
|
use Drupal\Core\Render\Markup;
|
|
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;
|
|
use Drupal\webform\Utility\WebformElementHelper;
|
|
use Drupal\webform\Utility\WebformDialogHelper;
|
|
use Drupal\webform\Utility\WebformOptionsHelper;
|
|
use Drupal\webform\WebformInterface;
|
|
use Drupal\webform\WebformSubmissionForm;
|
|
|
|
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';
|
|
|
|
/**
|
|
* 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());
|
|
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 <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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 = '<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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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('<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)) {
|
|
_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('<strong>Warning</strong>: 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("<strong>Warning</strong>: 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);
|
|
}
|