Move all files to 2017/

This commit is contained in:
Oliver Davies 2025-09-29 22:25:17 +01:00
parent ac7370f67f
commit 2875863330
15717 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,130 @@
<?php
/**
* @file
* Callback functions for date, datetime, and time elements.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Datetime\DateHelper;
/**
* Callback for removing abbreviation from datelist.
*
* @param array $element
* The element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param \Drupal\Core\Datetime\DrupalDateTime|null $date
* The date value.
*
* @see \Drupal\Core\Datetime\Element\Datelist::processDatelist
*/
function _webform_datelist_date_date_callback(array &$element, FormStateInterface $form_state, $date) {
$no_abbreviate = (isset($element['#date_abbreviate']) && $element['#date_abbreviate'] === FALSE);
if ($no_abbreviate && isset($element['month']) && isset($element['month']['#options'])) {
// Load translated date part labels from the appropriate calendar plugin.
$date_helper = new DateHelper();
$element['month']['#options'] = $date_helper->monthNames($element['#required']);;
}
}
/**
* Callback for custom datetime date element.
*
* @param array $element
* The element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param \Drupal\Core\Datetime\DrupalDateTime|null $date
* The date value.
*
* @see \Drupal\webform\Plugin\WebformElement\DateTime::prepare
*/
function _webform_datetime_date(array &$element, FormStateInterface $form_state, DrupalDateTime $date = NULL) {
// Make sure the date element is being displayed.
if (!isset($element['date'])) {
return;
}
$type = (isset($element['#date_date_element'])) ? $element['#date_date_element'] : 'date';
switch ($type) {
case 'datepicker':
// Convert #type from datepicker to textfield.
$element['date']['#type'] = 'textfield';
// Must manually set 'data-drupal-date-format' to trigger date picker.
// @see \Drupal\Core\Render\Element\Date::processDate
$element['date']['#attributes']['data-drupal-date-format'] = [$element['date']['#date_date_format']];
break;
}
}
/**
* Callback for custom datetime time element.
*
* @param array $element
* The element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param \Drupal\Core\Datetime\DrupalDateTime|null $date
* The date value.
*
* @see \Drupal\webform\Plugin\WebformElement\DateTime::prepare
*/
function _webform_datetime_time(array &$element, FormStateInterface $form_state, DrupalDateTime $date = NULL) {
// Make sure the time element is being displayed.
if (!isset($element['time'])) {
return;
}
// Apply time specific min/max to the element.
foreach (['min', 'max'] as $property) {
if (!empty($element["#date_time_$property"])) {
$value = $element["#date_time_$property"];
}
elseif (!empty($element["#date_$property"])) {
$value = date('H:i:s', strtotime($element["#date_$property"]));
}
else {
$value = NULL;
}
if ($value) {
$element['time']["#$property"] = $value;
$element['time']['#attributes'][$property] = $value;
}
}
// Apply time step and format to the element.
if (!empty($element['#date_time_step'])) {
$element['time']['#step'] = $element['#date_time_step'];
$element['time']['#attributes']['step'] = $element['#date_time_step'];
}
if (!empty($element['#date_time_format'])) {
$element['time']['#time_format'] = $element['#date_time_format'];
}
// Remove extra attributes for date element.
unset(
$element['time']['#attributes']['data-min-year'],
$element['time']['#attributes']['data-max-year']
);
$type = (isset($element['#date_time_element'])) ? $element['#date_time_element'] : 'time';
switch ($type) {
case 'timepicker':
$element['time']['#type'] = 'webform_time';
$element['time']['#timepicker'] = TRUE;
break;
case 'time':
$element['time']['#type'] = 'webform_time';
break;
case 'text':
$element['time']['#element_validate'][] = ['\Drupal\webform\Element\WebformTime', 'validateWebformTime'];
break;
}
}

View file

@ -0,0 +1,267 @@
<?php
/**
* @file
* Webform module editor file upload hooks.
*
* Because Webforms are config entities the editor.module's file uploading
* is not supported.
*
* The below code adds file upload support to Webform config entities and
* 'webform.settings' config.
*
* Below functions are copied from editor.module.
*/
use Drupal\Core\Config\Config;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\webform\WebformInterface;
use Drupal\Component\Utility\Html;
/******************************************************************************/
// Webform entity hooks.
/******************************************************************************/
/**
* Implements hook_webform_insert().
*
* @see editor_entity_insert()
*/
function webform_webform_insert(WebformInterface $webform) {
$uuids = _webform_get_config_entity_file_uuids($webform);
_webform_record_file_usage($uuids, $webform->getEntityTypeId(), $webform->id());
}
/**
* Implements hook_webform_update().
*
* @see editor_entity_update()
*/
function webform_webform_update(WebformInterface $webform) {
$original_uuids = _webform_get_config_entity_file_uuids($webform->original);
$uuids = _webform_get_config_entity_file_uuids($webform);
// Detect file usages that should be incremented.
$added_files = array_diff($uuids, $original_uuids);
_webform_record_file_usage($added_files, $webform->getEntityTypeId(), $webform->id());
// Detect file usages that should be decremented.
$removed_files = array_diff($original_uuids, $uuids);
_webform_delete_file_usage($removed_files, $webform->getEntityTypeId(), $webform->id(), 1);
}
/**
* Implements hook_webform_delete().
*
* @see editor_entity_delete()
*/
function webform_webform_delete(WebformInterface $webform) {
$uuids = _webform_get_config_entity_file_uuids($webform);
_webform_delete_file_usage($uuids, $webform->getEntityTypeId(), $webform->id(), 0);
}
/******************************************************************************/
// Webform config (settings) hooks.
// @see \Drupal\webform\Form\AdminConfig\WebformAdminConfigBaseForm::loadConfig
// @see \Drupal\webform\Form\AdminConfig\WebformAdminConfigBaseForm::saveConfig
/******************************************************************************/
/**
* Update config editor file references.
*
* @param \Drupal\Core\Config\Config $config
* An editable configuration object.
*/
function _webform_config_update(Config $config) {
$original_uuids = _webform_get_array_file_uuids($config->getOriginal());
$uuids = _webform_get_array_file_uuids($config->getRawData());
// Detect file usages that should be incremented.
$added_files = array_diff($uuids, $original_uuids);
_webform_record_file_usage($added_files, 'config', $config->getName());
// Detect file usages that should be decremented.
$removed_files = array_diff($original_uuids, $uuids);
_webform_delete_file_usage($removed_files, 'config', $config->getName(), 1);
}
/**
* Delete config editor file references.
*
* @param \Drupal\Core\Config\Config $config
* An editable configuration object.
*
* @see webform_uninstall()
*/
function _webform_config_delete(Config $config) {
$uuids = _webform_get_array_file_uuids($config->getRawData());
_webform_delete_file_usage($uuids, 'config', $config->getName(), 0);
}
/******************************************************************************/
// Config entity functions.
/******************************************************************************/
/**
* Finds all files referenced (data-entity-uuid) by config entity.
*
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
* An entity whose fields to analyze.
*
* @return array
* An array of file entity UUIDs.
*
* @see _editor_get_file_uuids_by_field()
*/
function _webform_get_config_entity_file_uuids(ConfigEntityInterface $entity) {
return _webform_get_array_file_uuids($entity->toArray());
}
/******************************************************************************/
// Config settings functions.
/******************************************************************************/
/**
* Finds all files referenced (data-entity-uuid) in an associatve array.
*
* @param array $data
* An associative array.
*
* @return array
* An array of file entity UUIDs.
*
* @see _editor_get_file_uuids_by_field()
*/
function _webform_get_array_file_uuids(array $data) {
$text = Yaml::encode($data);
return _webform_parse_file_uuids($text);
}
/******************************************************************************/
// File usage functions.
/******************************************************************************/
/**
* Records file usage of files referenced by formatted text fields.
*
* Every referenced file that does not yet have the FILE_STATUS_PERMANENT state,
* will be given that state.
*
* @param array $uuids
* An array of file entity UUIDs.
* @param string $type
* The type of the object that contains the referenced file.
* @param string $id
* The unique ID of the object containing the referenced file.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @see _editor_record_file_usage()
*/
function _webform_record_file_usage(array $uuids, $type, $id) {
if (empty($uuids) || !\Drupal::moduleHandler()->moduleExists('file')) {
return;
}
/** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
$file_usage = \Drupal::service('file.usage');
foreach ($uuids as $uuid) {
if ($file = \Drupal::entityManager()->loadEntityByUuid('file', $uuid)) {
if ($file->status !== FILE_STATUS_PERMANENT) {
$file->status = FILE_STATUS_PERMANENT;
$file->save();
}
$file_usage->add($file, 'editor', $type, $id);
}
}
}
/**
* Deletes file usage of files referenced by formatted text fields.
*
* @param array $uuids
* An array of file entity UUIDs.
* @param string $type
* The type of the object that contains the referenced file.
* @param string $id
* The unique ID of the object containing the referenced file.
* @param int $count
* The number of references to delete. Should be 1 when deleting a single
* revision and 0 when deleting an entity entirely.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @see \Drupal\file\FileUsage\FileUsageInterface::delete()
* @see _editor_delete_file_usage()
*/
function _webform_delete_file_usage(array $uuids, $type, $id, $count) {
if (empty($uuids) || !\Drupal::moduleHandler()->moduleExists('file')) {
return;
}
/** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
$file_usage = \Drupal::service('file.usage');
$make_unused_managed_files_temporary = \Drupal::config('webform.settings')->get('html_editor.make_unused_managed_files_temporary');
foreach ($uuids as $uuid) {
if ($file = \Drupal::entityManager()->loadEntityByUuid('file', $uuid)) {
$file_usage->delete($file, 'editor', $type, $id, $count);
// Make unused files temporary.
if ($make_unused_managed_files_temporary && empty($file_usage->listUsage($file)) && !$file->isTemporary()) {
$file->setTemporary();
$file->save();
}
}
}
}
/******************************************************************************/
// File parsing functions.
/******************************************************************************/
/**
* Parse an HTML snippet for any linked file with data-entity-uuid attributes.
*
* @param string $text
* The partial (X)HTML snippet to load. Invalid markup will be corrected on
* import.
*
* @return array
* An array of all found UUIDs.
*
* @see _editor_parse_file_uuids()
*/
function _webform_parse_file_uuids($text) {
if (strpos($text, 'data-entity-uuid') === FALSE) {
return [];
}
$uuids = [];
// Get all images using a regex.
if (preg_match_all('/<img[^>]+>/', $text, $matches)) {
foreach ($matches[0] as $img) {
// Cleanup quotes escaped via YAML.
// Please note, calling stripslashes() twice because elements are
// double escaped.
$img = stripslashes(stripslashes($img));
// Create a DomElement so that we can parse the image's attributes.
$dom_node = Html::load($img)->getElementsByTagName('img')->item(0);
// Get the entity type and uuid.
$type = $dom_node->getAttribute('data-entity-type');
$uuid = $dom_node->getAttribute('data-entity-uuid');
// Check the image is a file entity with a uuid.
if ($type === 'file' && $uuid) {
$uuids[] = $uuid;
}
}
}
// Use array_unique() to collect one uuid per uploaded file.
// This prevents cut-n-pasted uploaded files from having multiple usages.
return array_unique($uuids);
}

View file

@ -0,0 +1,366 @@
<?php
/**
* @file
* Webform install helper functions.
*/
use Drupal\Core\Render\Element;
use Drupal\Core\Serialization\Yaml;
use Drupal\system\Entity\Action;
use Drupal\webform\Entity\Webform;
use Drupal\webform\WebformInterface;
/**
* Update admin settings to reflect changes in the default settings.
*
* If you are moving or updating any admin settings this must be explicitly
* done via an update hook.
*
* @param bool $reset
* If set TRUE old admin settings will be completely deleted.
*
* @see drush_webform_repair()
*/
function _webform_update_admin_settings($reset = FALSE) {
$admin_config = \Drupal::configFactory()->getEditable('webform.settings');
$current_settings = $admin_config->getRawData();
$admin_settings = Yaml::decode(file_get_contents(drupal_get_path('module', 'webform') . '/config/install/webform.settings.yml'));
// Note, admin settings are always grouped into associative array,
// except for the langcode.
foreach ($admin_settings as $group => $settings) {
// Handle the rare case the we are adding a new group the admin settings.
if (!isset($current_settings[$group])) {
continue;
}
// Completely copy the format, langcode, and third_party_settings.
if (in_array($group, ['format', 'langcode', 'third_party_settings'])) {
if (isset($current_settings[$group])) {
$admin_settings[$group] = $current_settings[$group];
}
}
elseif ($reset) {
// Copy only group's settings that are defined in admin settings.
// This will cause old settings to be completely deleted.
foreach ($settings as $name => $value) {
if (isset($current_settings[$group][$name])) {
$admin_settings[$group][$name] = $current_settings[$group][$name];
}
}
}
else {
// Loop through the group's settings and apply all existing settings to
// the default admin settings.
foreach ($current_settings[$group] as $name => $value) {
$admin_settings[$group][$name] = $value;
}
}
}
// If not reset, make sure all the current settings are preserved.
if (!$reset) {
$admin_settings += $current_settings;
}
$admin_config->setData($admin_settings)->save();
}
/**
* Update webform settings to reflect changes in the default settings.
*
* This function can be used to apply new webform settings to all existing
* webforms.
*
* @see \Drupal\webform\Entity\Webform::setSettings
*/
function _webform_update_webform_settings() {
$config_factory = \Drupal::configFactory();
foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) {
$webform_config = $config_factory->getEditable($webform_config_name);
$data = $webform_config->getRawData();
$data = _webform_update_webform_setting($data);
$webform_config->setData($data)->save();
}
}
/**
* Update webform setting to reflect changes in the default settings.
*
* @param array $data
* A webform's raw configuration data from webform.webform.*.yml.
*
* @return array
* Updated raw configuration data.
*/
function _webform_update_webform_setting(array $data) {
$default_properties = [
'langcode' => 'en',
'status' => WebformInterface::STATUS_OPEN,
'dependencies' => [],
'open' => NULL,
'close' => NULL,
'weight' => 0,
'uid' => '',
'template' => FALSE,
'archive' => FALSE,
'id' => '',
'title' => '',
'description' => '',
'category' => '',
'elements' => '',
'css' => '',
'javascript' => '',
'settings' => [],
'access' => [],
'handlers' => [],
];
$default_settings = Webform::getDefaultSettings();
// Always apply the default properties.
$properties = $default_properties;
// Now apply defined properties.
foreach ($data as $name => $value) {
$properties[$name] = $value;
}
// Set properties.
$data = $properties;
// Always apply the default settings.
$settings = $default_settings;
// Now apply custom settings.
foreach ($data['settings'] as $name => $value) {
$settings[$name] = $value;
}
// Set settings.
$data['settings'] = $settings;
// Set access.
/** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */
$access_rules_manager = \Drupal::service('webform.access_rules_manager');
$data['access'] += $access_rules_manager->getDefaultAccessRules();
return $data;
}
/**
* Update webform handler settings to reflect changes in a handler's default configuration.
*
* @see \Drupal\webform\Plugin\WebformHandlerInterface
*/
function _webform_update_webform_handler_settings() {
// Issue #2863986: Allow updating modules with new service dependencies.
\Drupal::service('kernel')->rebuildContainer();
// Get the default configuration (aka settings) for all handlers provided
// by the Webform module.
/** @var \Drupal\webform\Plugin\WebformHandlerManagerInterface $handler_manager */
$handler_manager = \Drupal::service('plugin.manager.webform.handler');
$definitions = $handler_manager->getDefinitions();
$default_handler_settings = [];
foreach ($definitions as $plugin_id => $definition) {
if (strpos($definition['provider'], 'webform_test_') === 0 || in_array($definition['provider'], ['webform', 'webform_scheduled_email'])) {
$default_handler_settings[$plugin_id] = $handler_manager->createInstance($plugin_id)->defaultConfiguration();
}
}
$config_factory = \Drupal::configFactory();
// Update 'webform.webform.*' configuration.
foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) {
$webform_config = $config_factory->getEditable($webform_config_name);
// Get data.
$data = $webform_config->getRawData();
// Apply the default handler settings.
$has_handler = FALSE;
foreach ($data['handlers'] as &$handler) {
if (!isset($default_handler_settings[$handler['id']])) {
continue;
}
$settings = $default_handler_settings[$handler['id']];
foreach ($handler['settings'] as $settings_key => $setting_value) {
$settings[$settings_key] = $setting_value;
}
if ($handler['settings'] != $settings) {
$has_handler = TRUE;
$handler['settings'] = $settings;
}
}
if ($has_handler) {
$webform_config->setData($data)->save();
}
}
}
/**
* Update webform options setting to reflect changes in the default settings.
*
* This function can be used to apply new webform options configuration to
* all existing webforms options.
*
* @see \Drupal\webform\Entity\WebformOptions
*/
function _webform_update_options_settings() {
$default_properties = [
'langcode' => 'en',
'status' => TRUE,
'dependencies' => [],
'id' => '',
'label' => '',
'category' => '',
'options' => '',
];
$config_factory = \Drupal::configFactory();
// Update 'webform.webform_options.*' configuration.
foreach ($config_factory->listAll('webform.webform_options.') as $webform_config_name) {
$webform_options_config = $config_factory->getEditable($webform_config_name);
// Get data.
$data = $webform_options_config->getRawData();
// Always apply the default properties.
$properties = $default_properties;
// Now apply defined properties.
foreach ($data as $name => $value) {
$properties[$name] = $value;
}
// Set properties.
$data = $properties;
// Save data.
$webform_options_config->setData($data)->save();
}
}
/**
* Update or install any new system.actions.* config entities.
*/
function _webform_update_actions() {
$files = file_scan_directory(drupal_get_path('module', 'webform') . '/config', '/^system.action..*\.yml$/');
foreach ($files as $path => $file) {
$action_id = str_replace('system.action.', '', $file->name);
$action = Action::load($action_id);
if (!$action) {
// Install new action.
$data = Yaml::decode(file_get_contents($path));
$action = Action::create($data);
$action->trustData()->save();
}
}
}
/**
* Update webform field storage definitions.
*
* @see \Drupal\webform\Plugin\Field\FieldType\WebformEntityReferenceItem::schema
*/
function _webform_update_field_storage_definitions() {
$manager = \Drupal::entityDefinitionUpdateManager();
/** @var \Drupal\field\FieldStorageConfigInterface[] $fields */
$fields = \Drupal::entityTypeManager()
->getStorage('field_storage_config')
->loadByProperties(['type' => 'webform']);
foreach ($fields as $field) {
$field_name = $field->getName();
$entity_type = $field->getTargetEntityTypeId();
$manager->updateFieldStorageDefinition($manager->getFieldStorageDefinition($field_name, $entity_type));
}
}
/**
* Update webform submission storage schema.
*
* @see \Drupal\webform\WebformSubmissionStorageSchema
*/
function _webform_update_webform_submission_storage_schema() {
$manager = \Drupal::entityDefinitionUpdateManager();
$manager->updateEntityType($manager->getEntityType('webform_submission'));
}
/**
* Replace string in webform.settings.yml and webform.webform.*.yml.
*
* @param string $search
* String to be search for.
* @param string $replace
* String to be replace with.
*/
function _webform_update_string_replace($search, $replace) {
$config_factory = \Drupal::configFactory();
// Update 'webform.settings' configuration.
$settings_config = \Drupal::configFactory()->getEditable('webform.settings');
$yaml = Yaml::encode($settings_config->getRawData());
if (strpos($yaml, $search) !== FALSE) {
$yaml = str_replace($search, $replace, $yaml);
$settings_config->setData(Yaml::decode($yaml));
$settings_config->save();
}
// Update 'webform.webform.*' configuration.
foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) {
$webform_config = $config_factory->getEditable($webform_config_name);
$yaml = Yaml::encode($webform_config->getRawData());
if (strpos($yaml, $search) !== FALSE) {
$yaml = str_replace($search, $replace, $yaml);
$webform_config->setData(Yaml::decode($yaml));
$webform_config->save();
}
}
}
/**
* Clear/remove selected webform element properties.
*
* @param array $properties
* An associative array of webform element properties.
*/
function _webform_update_elements_clear_properties(array $properties) {
$pattern = '/(?:' . implode('|', array_keys($properties)) . ')/';
$config_factory = \Drupal::configFactory();
foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) {
$webform_config = $config_factory->getEditable($webform_config_name);
$data = $webform_config->getRawData();
// Make sure elements contains the properties.
if (!preg_match($pattern, $data['elements'])) {
continue;
}
$elements = Yaml::decode($data['elements']);
_webform_update_elements_clear_properties_recursive($elements, $properties);
$data['elements'] = Yaml::encode($elements);
$webform_config->setData($data);
$webform_config->save();
}
}
/**
* Recursively clear/remove selected webform element properties.
*
* @param array $element
* An element.
* @param array $properties
* An associative array of webform element properties.
*/
function _webform_update_elements_clear_properties_recursive(array &$element, array $properties) {
foreach ($properties as $property_name => $property_value) {
if (isset($element[$property_name]) && $element[$property_name] === $property_value) {
unset($element[$property_name]);
}
}
foreach (Element::children($element) as $key) {
if (is_array($element[$key])) {
_webform_update_elements_clear_properties_recursive($element[$key], $properties);
}
}
}

View file

@ -0,0 +1,170 @@
<?php
/**
* @file
* Webform requirements.
*/
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Url;
use Drupal\webform\Plugin\WebformElement\ManagedFile;
/**
* Implements hook_requirements().
*/
function webform_requirements($phase) {
if ($phase != 'runtime') {
return [];
}
$requirements = [];
/****************************************************************************/
// Check HTML email handling.
/****************************************************************************/
/** @var \Drupal\webform\WebformEmailProviderInterface $email_provider */
$email_provider = \Drupal::service('webform.email_provider');
$mail_module_name = $email_provider->getModuleName();
$mail_plugin_id = $email_provider->getMailPluginId();
$mail_plugin_definition = $email_provider->getMailPluginDefinition();
if ($mail_module_name || $mail_plugin_id) {
$t_args = [
'@module' => $mail_module_name,
'@plugin_id' => $mail_plugin_id,
'@plugin_label' => $mail_plugin_definition['label'],
'@plugin_description' => $mail_plugin_definition['description'],
];
$requirements['webform_email'] = [
'title' => t('Webform: HTML email support'),
'value' => ($mail_module_name) ? t('Provided by the @module module.', $t_args) : t('Provided by @plugin_id mail plugin.', $t_args),
'description' => new FormattableMarkup('@plugin_label: @plugin_description', $t_args),
'severity' => REQUIREMENT_OK,
];
}
else {
$requirements['webform_email'] = [
'title' => t('Webform: HTML email support'),
'value' => t('Unable to determine email module and/or provider'),
'severity' => REQUIREMENT_ERROR,
];
}
/****************************************************************************/
// Check private file upload.
/****************************************************************************/
$scheme_options = ManagedFile::getVisibleStreamWrappers();
if (isset($scheme_options['private'])) {
$requirements['webform_file_private'] = [
'title' => t('Webform: Private files'),
'value' => t('Private file system is set.'),
];
}
else {
$requirements['webform_file_private'] = [
'title' => t('Webform: Private files'),
'value' => t('Private file system is not set.'),
'description' => t('This must be changed in <a href="https://www.drupal.org/documentation/modules/file">settings.php</a>. For more information see: <a href="https://www.drupal.org/psa-2016-003">DRUPAL-PSA-2016-003</a>'),
'severity' => REQUIREMENT_WARNING,
];
}
/****************************************************************************/
// Check external libraries.
/****************************************************************************/
/** @var \Drupal\webform\WebformLibrariesManagerInterface $libraries_manager */
$libraries_manager = \Drupal::service('webform.libraries_manager');
$requirements += $libraries_manager->requirements();
/****************************************************************************/
// Check Bootstrap theme.
/****************************************************************************/
if (\Drupal::config('webform.settings')->get('requirements.bootstrap')) {
$spam_protection = FALSE;
$themes = \Drupal::service('theme_handler')->listInfo();
foreach ($themes as $theme) {
if ((isset($theme->base_themes) && isset($theme->base_themes['bootstrap'])) || $theme == 'bootstrap') {
$spam_protection = TRUE;
}
}
if ($spam_protection) {
if (\Drupal::moduleHandler()->moduleExists('webform_bootstrap')) {
$requirements['webform_bootstrap'] = [
'title' => t('Webform: Bootstrap integration'),
'value' => t('Webform Bootstrap module installed.'),
];
}
else {
$requirements['webform_bootstrap'] = [
'title' => t('Webform: Bootstrap integration'),
'value' => t('Webform Bootstrap Integration module not installed.'),
'description' => t('The Webform Bootstrap module helps support Webform to Bootstrap integration. <a href=":href">Disable Webform Bootstrap Integration warning</a>', [':href' => Url::fromRoute('webform.config.advanced')->toString()]),
'severity' => REQUIREMENT_WARNING,
];
}
}
}
/****************************************************************************/
// Check SPAM protection.
/****************************************************************************/
if (\Drupal::config('webform.settings')->get('requirements.spam')) {
$spam_protection = FALSE;
$installed_projects = [
'#prefix' => '<p><hr/></p><dl>',
'#suffix' => '</dl>',
];
$available_projects = [
'#prefix' => '<p><hr/></p><dl>',
'#suffix' => '</dl>',
];
/** @var \Drupal\webform\WebformAddonsManagerInterface $addons_manager */
$addons_manager = \Drupal::service('webform.addons_manager');
$projects = $addons_manager->getProjects('spam');
foreach ($projects as $project_name => $project) {
$available_projects[$project_name] = [
'title' => [
'#type' => 'link',
'#title' => $project['title'],
'#url' => $project['url'],
'#prefix' => '<dt><strong>',
'#suffix' => '</strong></dt>',
],
'description' => [
'#markup' => $project['description'],
'#prefix' => '<dd>',
'#suffix' => '</dd>',
],
];
if (\Drupal::moduleHandler()->moduleExists($project_name)) {
$spam_protection = TRUE;
$installed_projects[$project_name] = $available_projects[$project_name];
}
}
if ($spam_protection) {
$requirements['webform_spam'] = [
'title' => t('Webform: SPAM protection'),
'value' => t('Webform SPAM protection module installed.'),
'description' => \Drupal::service('renderer')->renderPlain($installed_projects),
];
}
else {
$requirements['webform_spam'] = [
'title' => t('Webform: SPAM protection'),
'value' => t('Webform <a href=":href">SPAM protection module</a> missing. Please install one of the below modules.', [':href' => 'https://www.drupal.org/node/206787']),
'description' => \Drupal::service('renderer')->renderPlain($available_projects),
'severity' => REQUIREMENT_WARNING,
];
}
}
// Sort all requirements alphabetically.
ksort($requirements);
return $requirements;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,124 @@
<?php
/**
* @file
* Webform libraries.
*/
/**
* Implements hook_library_info_alter().
*/
function webform_library_info_alter(&$libraries, $extension) {
// Only alter modules that declare webform libraries.
// @see hook_webform_libraries_info()
$webform_libraries_modules = \Drupal::moduleHandler()->getImplementations('webform_libraries_info');
$webform_libraries_modules[] = 'webform';
if (!in_array($extension, $webform_libraries_modules)) {
return;
}
// If chosen_lib.module is installed, then update the dependency.
if (\Drupal::moduleHandler()->moduleExists('chosen_lib')) {
if (isset($libraries['webform.element.chosen'])) {
$dependencies =& $libraries['webform.element.chosen']['dependencies'];
foreach ($dependencies as $index => $dependency) {
if ($dependency === 'webform/libraries.jquery.chosen') {
$dependencies[$index] = 'chosen_lib/chosen';
$dependencies[] = 'chosen_lib/chosen.css';
break;
}
}
}
}
// If select2.module is installed, then update the dependency.
if (\Drupal::moduleHandler()->moduleExists('select2')) {
if (isset($libraries['webform.element.select2'])) {
$dependencies =& $libraries['webform.element.select2']['dependencies'];
foreach ($dependencies as $index => $dependency) {
if ($dependency === 'webform/libraries.jquery.select2') {
$dependencies[$index] = 'select2/select2';
break;
}
}
}
}
/** @var \Drupal\webform\WebformLibrariesManagerInterface $libraries_manager */
$libraries_manager = \Drupal::service('webform.libraries_manager');
// Map /library/* paths to CDN.
// @see webform.libraries.yml.
foreach ($libraries as $library_name => &$library) {
// Remove excluded libraries.
if ($libraries_manager->isExcluded($library_name)) {
unset($libraries[$library_name]);
continue;
}
// Skip libraries installed by other modules.
if (isset($library['module'])) {
continue;
}
if (!empty($library['dependencies'])) {
// Remove excluded libraries from dependencies.
foreach ($library['dependencies'] as $dependency_index => $dependency_name) {
if ($libraries_manager->isExcluded($dependency_name)) {
$library['dependencies'][$dependency_index] = NULL;
$library['dependencies'] = array_filter($library['dependencies']);
}
}
}
// Check CDN setting exists.
if (!isset($library['cdn'])) {
continue;
}
// Check if the CDN's source /library/* path exists.
reset($library['cdn']);
if (file_exists(DRUPAL_ROOT . key($library['cdn']))) {
continue;
}
_webform_library_info_alter_recursive($library, $library['cdn']);
}
}
/**
* Recursive through a webform library.
*
* @param array $library
* A webform library defined in webform.libraries.yml.
* @param array $cdn
* A associative array of library paths mapped to CDN URL.
*/
function _webform_library_info_alter_recursive(array &$library, array $cdn) {
foreach ($library as $key => &$value) {
// CSS and JS files and listed in associative arrays keyed via string.
if (!is_string($key) || !is_array($value)) {
continue;
}
// Ignore the CDN's associative array.
if ($key == 'cdn') {
continue;
}
// Replace the CDN sources (i.e. /library/*) with the CDN URL destination
// (https://cdnjs.cloudflare.com/ajax/libs/*).
foreach ($cdn as $source => $destination) {
if (strpos($key, $source) === 0) {
$uri = str_replace($source, $destination, $key);
$library[$uri] = $value;
$library[$uri]['type'] = 'external';
unset($library[$key]);
break;
}
}
// Recurse downward to find nested libraries.
_webform_library_info_alter_recursive($value, $cdn);
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Options alter hooks.
*/
use Drupal\webform\Utility\WebformOptionsHelper;
use Drupal\Core\Locale\CountryManager;
use Drupal\Core\Language\LanguageManager;
/**
* Implements hook_webform_options_WEBFORM_OPTIONS_ID_alter().
*
* @see config/install/webform.webform.example_options.yml
*/
function webform_webform_options_range_alter(array &$options, array $element = []) {
$element += [
'#min' => 1,
'#max' => 100,
'#step' => 1,
'#pad_length' => NULL,
'#pad_str' => 0,
];
$options = WebformOptionsHelper::range(
$element['#min'],
$element['#max'],
$element['#step'],
$element['#pad_length'],
$element['#pad_str']
);
}
/**
* Implements hook_webform_options_WEBFORM_OPTIONS_ID_alter().
*/
function webform_webform_options_time_zones_alter(array &$options, array $element = []) {
if (empty($options)) {
$options = system_time_zones();
}
}
/**
* Implements hook_webform_options_WEBFORM_OPTIONS_ID_alter().
*/
function webform_webform_options_country_codes_alter(array &$options, array $element = []) {
if (empty($options)) {
$options = CountryManager::getStandardList();
}
}
/**
* Implements hook_webform_options_WEBFORM_OPTIONS_ID_alter().
*/
function webform_webform_options_country_names_alter(array &$options, array $element = []) {
if (empty($options)) {
$countries = CountryManager::getStandardList();
$options = array_combine($countries, $countries);
}
}
/**
* Implements hook_webform_options_WEBFORM_OPTIONS_ID_alter().
*/
function webform_webform_options_languages_alter(array &$options, array $element = []) {
if (empty($options)) {
$languages = LanguageManager::getStandardLanguageList();
unset($languages['en-x-simple']);
$options = [];
foreach ($languages as $language) {
$options[$language[0]] = $language[0];
}
}
}
/**
* Implements hook_webform_options_WEBFORM_OPTIONS_ID_alter().
*/
function webform_webform_options_translations_alter(array &$options, array $element = []) {
if (empty($options)) {
$languages = \Drupal::languageManager()->getLanguages();
$options = [];
foreach ($languages as $language) {
$options[$language->getId()] = $language->getName();
}
}
}

View file

@ -0,0 +1,997 @@
<?php
/**
* @file
* Theme hooks, preprocessor, and suggestions.
*/
use Drupal\file\Entity\File;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Template\Attribute;
use Drupal\webform\Utility\WebformAccessibilityHelper;
use Drupal\webform\Utility\WebformElementHelper;
/******************************************************************************/
// Theme hooks.
/******************************************************************************/
/**
* Implements hook_theme().
*/
function webform_theme() {
$info = [
'webform_help' => [
'variables' => ['info' => []],
],
'webform_help_video_youtube' => [
'variables' => ['youtube_id' => NULL, 'autoplay' => TRUE],
],
'webform_contribute' => [
'variables' => [
'account' => [],
'membership' => [],
'contribution' => [],
],
],
'webform' => [
'render element' => 'element',
],
'webform_actions' => [
'render element' => 'element',
],
'webform_handler_action_summary' => [
'variables' => ['settings' => NULL, 'handler' => NULL],
],
'webform_handler_debug_summary' => [
'variables' => ['settings' => NULL, 'handler' => NULL],
],
'webform_handler_email_summary' => [
'variables' => ['settings' => NULL, 'handler' => NULL],
],
'webform_handler_remote_post_summary' => [
'variables' => ['settings' => NULL, 'handler' => NULL],
],
'webform_handler_settings_summary' => [
'variables' => ['settings' => NULL, 'handler' => NULL],
],
'webform_confirmation' => [
'variables' => ['webform' => NULL, 'source_entity' => NULL, 'webform_submission' => NULL],
],
'webform_required' => [
'variables' => ['label' => NULL],
],
'webform_submission' => [
'render element' => 'elements',
],
'webform_submission_form' => [
'render element' => 'form',
],
'webform_submission_navigation' => [
'variables' => ['webform_submission' => NULL],
],
'webform_submission_information' => [
'variables' => ['webform_submission' => NULL, 'source_entity' => NULL, 'open' => TRUE],
],
'webform_element_base_html' => [
'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => []],
],
'webform_element_base_text' => [
'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => []],
],
'webform_container_base_html' => [
'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => []],
],
'webform_container_base_text' => [
'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => []],
],
'webform_element_help' => [
'variables' => ['help' => NULL, 'help_title' => '', 'attributes' => []],
],
'webform_element_more' => [
'variables' => ['more' => NULL, 'more_title' => '', 'attributes' => []],
],
'webform_element_managed_file' => [
'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => [], 'file' => NULL],
],
'webform_element_audio_file' => [
'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => [], 'file' => NULL],
],
'webform_element_document_file' => [
'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => [], 'file' => NULL],
],
'webform_element_image_file' => [
'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => [], 'file' => NULL, 'style_name' => NULL, 'format' => NULL],
],
'webform_element_video_file' => [
'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => [], 'file' => NULL],
],
'webform_email_html' => [
'variables' => ['subject' => '', 'body' => '', 'webform_submission' => NULL, 'handler' => NULL],
],
'webform_email_message_html' => [
'variables' => ['message' => '', 'webform_submission' => NULL, 'handler' => NULL],
],
'webform_email_message_text' => [
'variables' => ['message' => '', 'webform_submission' => NULL, 'handler' => NULL],
],
'webform_horizontal_rule' => [
'render element' => 'element',
],
'webform_message' => [
'render element' => 'element',
],
'webform_section' => [
'render element' => 'element',
],
'webform_composite_address' => [
'render element' => 'element',
],
'webform_composite_contact' => [
'render element' => 'element',
],
'webform_composite_location' => [
'render element' => 'element',
],
'webform_composite_link' => [
'render element' => 'element',
],
'webform_composite_name' => [
'render element' => 'element',
],
'webform_composite_telephone' => [
'render element' => 'element',
],
'webform_codemirror' => [
'variables' => ['code' => NULL, 'type' => 'text'],
],
'webform_progress' => [
'variables' => [
'webform' => NULL,
'current_page' => NULL,
'operation' => NULL,
],
],
'webform_progress_bar' => [
'variables' => [
'webform' => NULL,
'current_page' => NULL,
'operation' => NULL,
'max_pages' => 10,
],
],
'webform_progress_tracker' => [
'variables' => [
'webform' => NULL,
'current_page' => NULL,
'operation' => NULL,
'max_pages' => 10,
],
],
];
// Since any rendering of a webform is going to require 'webform.theme.inc'
// we are going to just add it to every template.
foreach ($info as &$template) {
$template['file'] = 'includes/webform.theme.template.inc';
}
return $info;
}
/**
* Implements hook_theme_registry_alter().
*/
function webform_theme_registry_alter(&$theme_registry) {
// Allow attributes to be defined for status messages so that #states
// can be added to messages.
// @see \Drupal\webform\Element\WebformMessage
if (!isset($theme_registry['status_messages']['variables']['attributes'])) {
$theme_registry['status_messages']['variables']['attributes'] = [];
}
}
/******************************************************************************/
// Preprocessors.
/******************************************************************************/
/**
* Prepares variables for single local action link templates.
*
* Applies custom link attributes to local actions.
* Custom attributes are used to open Webform UI modals.
*
* @see template_preprocess_menu_local_action();
* @see \Drupal\webform\WebformEntityHandlersForm
* @see \Drupal\webform_ui\WebformUiEntityEditForm
* @see https://www.drupal.org/node/2897396
*/
function webform_preprocess_menu_local_action(&$variables) {
$link = $variables['element']['#link'];
// Only need to update local actions with link attributes.
if (!isset($link['attributes'])) {
return;
}
$link += [
'localized_options' => [],
];
$link['localized_options']['attributes'] = $link['attributes'];
$link['localized_options']['attributes']['class'][] = 'button';
$link['localized_options']['attributes']['class'][] = 'button-action';
$link['localized_options']['set_active_class'] = TRUE;
$variables['link'] = [
'#type' => 'link',
'#title' => $link['title'],
'#options' => $link['localized_options'],
'#url' => $link['url'],
];
}
/**
* Prepares variables for checkboxes templates.
*
* @see \Drupal\webform\Plugin\WebformElement\OptionsBase
*/
function webform_preprocess_checkboxes(&$variables) {
if (!WebformElementHelper::isWebformElement($variables['element'])) {
return;
}
_webform_preprocess_options($variables);
}
/**
* Prepares variables for radios templates.
*
* @see \Drupal\webform\Plugin\WebformElement\OptionsBase
*/
function webform_preprocess_radios(&$variables) {
if (!WebformElementHelper::isWebformElement($variables['element'])) {
return;
}
_webform_preprocess_options($variables);
}
/******************************************************************************/
// Preprocess tables.
/******************************************************************************/
/**
* Prepares variables for table templates.
*/
function webform_preprocess_table(&$variables) {
// Add links to 'Translate' webform tab.
if (\Drupal::routeMatch()->getRouteName() === 'entity.webform.config_translation_overview') {
/** @var \Drupal\webform\WebformInterface $webform */
$webform = \Drupal::routeMatch()->getParameter('webform');
foreach ($variables['rows'] as &$row) {
// Check first cell.
if (!isset($row['cells'][0]['content'])
|| !is_array($row['cells'][0]['content'])
|| !isset($row['cells'][0]['content']['#markup'])) {
continue;
}
// Check last cell edit link.
if (!isset($row['cells'][1]['content'])
|| !is_array($row['cells'][1]['content'])
|| !isset($row['cells'][1]['content']['#links'])
|| !is_array($row['cells'][1]['content']['#links'])
|| !isset($row['cells'][1]['content']['#links']['edit'])) {
continue;
}
// Get language from edit link.
$route_parameters = $row['cells'][1]['content']['#links']['edit']['url']->getRouteParameters();
$langcode = (isset($route_parameters['langcode'])) ? $route_parameters['langcode'] : NULL;
$language = \Drupal::languageManager()->getLanguage($langcode);
// Convert first to link.
$row['cells'][0]['content'] = [
'#type' => 'link',
'#url' => $webform->toUrl('canonical', ['language' => $language]),
'#title' => $row['cells'][0]['content'],
];
}
}
}
/******************************************************************************/
// Preprocess containers.
/******************************************************************************/
/**
* Prepares variables for datetime form element templates.
*/
function webform_preprocess_datetime_form(&$variables) {
if (!WebformElementHelper::isWebformElement($variables['element'])) {
return;
}
// Add .container-inline to datetime form wrapper which is missing from the
// stable base theme.
// @see core/themes/classy/templates/form/datetime-form.html.twig
// @see core/themes/stable/templates/form/datetime-form.html.twig
$variables['attributes']['class'][] = 'container-inline';
}
/**
* Prepares variables for details element templates.
*/
function webform_preprocess_details(&$variables) {
if (!WebformElementHelper::isWebformElement($variables['element'])) {
return;
}
// Setup description, help, and more.
_webform_preprocess_element($variables);
$element = &$variables['element'];
// Hide details title.
if (isset($element['#title_display']) && $element['#title_display'] === 'invisible') {
$variables['title'] = WebformAccessibilityHelper::buildVisuallyHidden($variables['title']);
}
// Remove invalid 'required' and 'aria-required' attributes from details.
if (isset($element['#webform_key'])) {
unset(
$variables['attributes']['required'],
$variables['attributes']['aria-required']
);
}
}
/**
* Prepares variables for fieldset element templates.
*/
function webform_preprocess_fieldset(&$variables) {
if (!WebformElementHelper::isWebformElement($variables['element'])) {
return;
}
// Setup description, help, and more.
_webform_preprocess_element($variables, ['legend', 'title']);
$element = &$variables['element'];
// If the description is displayed 'before' we need to move it to the
// fieldset's prefix.
// @see fieldset.html.twig
if (isset($element['#description_display']) && $element['#description_display'] === 'before' && !empty($variables['description']['content'])) {
if (isset($variables['prefix'])) {
if (is_array($variables['prefix'])) {
$variables['prefix'] = ['prefix' => $variables['prefix']];
}
else {
$variables['prefix'] = ['prefix' => ['#markup' => $variables['prefix']]];
}
}
else {
$variables['prefix'] = [];
}
$variables['prefix']['description'] = $variables['description']['content'];
unset($variables['description']['content']);
}
// Apply inline title defined by radios, checkboxes, and buttons.
// @see \Drupal\webform\Plugin\WebformElement\OptionsBase::prepare
if (isset($element['#_title_display'])) {
$variables['attributes']['class'][] = 'webform-fieldset--title-inline';
}
// Add .js-webform-form-type-* class to be used JavaScript and #states API.
// @see js/webform.element.location.geocomplete.js
// @see js/webform.states.js
if (isset($element['#type'])) {
$variables['attributes']['class'][] = 'js-webform-type-' . Html::getClass($element['#type']);
$variables['attributes']['class'][] = 'webform-type-' . Html::getClass($element['#type']);
}
// Remove invalid 'required' and 'aria-required' attributes from fieldset.
if (isset($element['#webform_key'])) {
unset(
$variables['attributes']['required'],
$variables['attributes']['aria-required']
);
}
}
/******************************************************************************/
// Preprocess form element.
/******************************************************************************/
/**
* Prepares variables for form element templates.
*/
function webform_preprocess_form_element(&$variables) {
if (!WebformElementHelper::isWebformElement($variables['element'])) {
return;
}
// Setup description, help, and more.
_webform_preprocess_element($variables);
$element = &$variables['element'];
// Add #help, #help_attributes, and #_title_display to label.
// Note: #_title_display is used to track inline titles.
// @see \Drupal\webform\Plugin\WebformElementBase::prepare
$variables['label'] += array_intersect_key($element, array_flip(['#help', '#help_title', '#help_attributes', '#_title_display']));
}
/**
* Prepares variables for form label templates.
*/
function webform_preprocess_form_element_label(&$variables) {
$element = &$variables['element'];
// Restructure the label's title to include #help.
_webform_preprocess_help_title($variables['title'], $element);
}
/******************************************************************************/
// Preprocess file/image elements.
/******************************************************************************/
/**
* Prepares variables for file managed file templates.
*
* @see https://stackoverflow.com/questions/21842274/cross-browser-custom-styling-for-file-upload-button
* @see template_preprocess_file_managed_file()
*/
function webform_preprocess_file_managed_file(&$variables) {
if (!WebformElementHelper::isWebformElement($variables['element'])) {
return;
}
$element = &$variables['element'];
if (empty($element['#button'])) {
return;
}
// Don't alter hidden file upload input.
if (isset($element['upload']['#access']) && $element['upload']['#access'] === FALSE) {
return;
}
// Create an unique id for the file upload input and label.
$button_id = Html::getUniqueId($variables['element']['upload']['#id'] . '-button');
// Create a label that is styled like an action button.
$label = [
'#type' => 'html_tag',
'#tag' => 'label',
'#value' => (isset($element['#button__title'])) ? $element['#button__title'] : (empty($element['#multiple']) ? t('Choose file') : t('Choose files')),
'#attributes' => (isset($element['#button__attributes'])) ? $element['#button__attributes'] : [],
];
// Add 'for' attribute.
$label['#attributes']['for'] = $button_id;
// Add default button classes.
if (empty($label['#attributes']['class'])) {
$label['#attributes']['class'][] = 'button';
$label['#attributes']['class'][] = 'button-action';
}
// Add .webform-file-button.
$label['#attributes']['class'][] = 'webform-file-button';
// Make sure the label is first.
$element = ['label' => $label] + $element;
// Set the custom button ID for file upload input.
$element['upload']['#attributes']['id'] = $button_id;
// Hide the file upload.
$element['upload']['#attributes']['class'][] = 'webform-file-button-input';
// Attach library.
$element['#attached']['library'][] = 'webform/webform.element.file.button';
}
/**
* Prepares variables for file upload help text templates.
*/
function webform_preprocess_file_upload_help(&$variables) {
$upload_validators = $variables['upload_validators'];
if (isset($upload_validators['webform_file_limit']) && $upload_validators['webform_file_limit'][0]) {
$variables['descriptions'][] = t('@size limit per form.', ['@size' => format_size($upload_validators['webform_file_limit'][0])]);
}
}
/**
* Prepares variables for file link templates.
*
* @see webform_file_access
*/
function webform_preprocess_file_link(&$variables) {
/** @var \Drupal\file\FileInterface $file */
$file = $variables['file'];
$file = ($file instanceof File) ? $file : File::load($file->fid);
// Remove link to temporary anonymous private file uploads.
if ($file->isTemporary() && $file->getOwner()->isAnonymous() && strpos($file->getFileUri(), 'private://webform/') === 0) {
$variables['link'] = $file->getFilename();
}
}
/**
* Prepares variables for image.
*
* Make sure the image src for the 'webform_image_file' src is an absolute URL.
*/
function webform_preprocess_image(&$variables) {
global $base_url;
if (isset($variables['attributes']['class']) && in_array('webform-image-file', (array) $variables['attributes']['class'])) {
$variables['attributes']['src'] = $base_url . preg_replace('/^' . preg_quote(base_path(), '/') . '/', '/', $variables['attributes']['src']);
}
}
/******************************************************************************/
// Preprocess webform specific elements.
/******************************************************************************/
/**
* Prepares variables for webform section element templates.
*/
function webform_preprocess_webform_section(&$variables) {
// Setup description, help, and more.
_webform_preprocess_element($variables);
}
/******************************************************************************/
// Preprocess helpers.
/******************************************************************************/
/**
* Prepares variables for checkboxes and radios options templates.
*
* Below code must be called by template_preprocess_(radios|checkboxes) which
* reset the element's 'attributes';
*/
function _webform_preprocess_options(array &$variables) {
$element =& $variables['element'];
$variables['attributes']['class'][] = Html::getClass('js-webform-' . $element['#type']);
if (!empty($element['#options_display'])) {
$variables['attributes']['class'][] = Html::getClass('webform-options-display-' . $element['#options_display']);
$variables['#attached']['library'][] = 'webform/webform.element.options';
}
}
/**
* Prepares webform element description, help, and more templates.
*
* @see template_preprocess_form_element()
* @see core/modules/system/templates/form-element.html.twig
* @see template_preprocess_details()
* @see /core/modules/system/templates/details.html.twig
* @see template_preprocess_fieldset()
* @see /core/modules/system/templates/fieldset.html.twig
* @see template_preprocess_webform_section()
* @see /webform/templates/webform-section.html.twig
*/
function _webform_preprocess_element(array &$variables, $title_parents = ['title']) {
$element =& $variables['element'];
$type = $element['#type'];
// Fix details 'description' property which does not have description.content.
// @see template_preprocess_details
// @see Issue #2896169: Details elements have incorrect aria-describedby attributes
if (!empty($element['#description'])) {
// Convert the current description to simple #markup.
if (is_array($element['#description'])) {
// Make a copy of the element's #description so that it can be rendered
// without affecting the element's #description.
$element_description = $element['#description'];
$description = ['#markup' => \Drupal::service('renderer')->render($element_description)];
}
else {
$description = ['#markup' => $element['#description']];
}
if ($type === 'details') {
$description_attributes = [];
if (!empty($element['#id'])) {
$description_attributes['id'] = $element['#id'] . '--description';
}
$variables['description'] = [];
$variables['description']['content'] = [
'#type' => 'container',
'#attributes' => new Attribute($description_attributes),
] + $description;
}
else {
// Wrap description in a container.
$variables['description']['content'] = [
'#type' => 'container',
'#attributes' => $variables['description']['attributes'],
] + $description;
$variables['description']['attributes'] = new Attribute();
}
$variables['description']['content']['#attributes']->addClass('webform-element-description');
// Handle invisible descriptions.
if (isset($element['#description_display']) && $element['#description_display'] === 'invisible') {
$variables['description']['content']['#attributes']->addClass('visually-hidden');
$variables['description_display'] = 'after';
}
// Nest description content so that we can a more link
// below the description.
$variables['description']['content'] = [
'description' => $variables['description']['content'],
];
}
$title =& NestedArray::getValue($variables, $title_parents);
// Move #description to #help for webform admin routes.
_webform_preprocess_description_help($variables);
// Add (read) more to #description.
_webform_preprocess_form_element_description_more($variables);
// Add help to title (aka label).
_webform_preprocess_help_title($title, $element);
}
/**
* Prepares #description and #help properties for form element templates.
*/
function _webform_preprocess_description_help(array &$variables) {
$element = &$variables['element'];
// Move #description to #help for webform admin routes.
if (\Drupal::config('webform.settings')->get('ui.description_help')
&& \Drupal::service('webform.request')->isWebformAdminRoute()
&& \Drupal::routeMatch()->getRouteName() != 'webform.contribute.settings'
&& !isset($element['#help'])
&& !empty($element['#title']) && (empty($element['#title_display']) || !in_array($element['#title_display'], ['attribute', 'invisible']))
&& !empty($element['#description']) && (empty($element['#description_display']) || !in_array($element['#description_display'], ['invisible']))
) {
// Render the description.
$description = (is_array($element['#description'])) ? \Drupal::service('renderer')->render($element['#description']) : $element['#description'];
// Replace breaks in admin tooltips with horizontal rules.
$description = str_replace('<br /><br />', '<hr />', $description);
$element['#help'] = ['#markup' => $description];
// We must still render the description as visually hidden because the input
// has an 'aria-describedby' attribute pointing to the description's id.
$variables['description_display'] = 'after';
$variables['description']['content']['description']['#attributes']->addClass('visually-hidden');
// Remove all links from the #description since it will be .visually-hidden
// and unreachable via tabbing.
if (isset($variables['description']['content']['description']['#markup'])) {
$variables['description']['content']['description']['#markup'] = strip_tags($variables['description']['content']['description']['#markup']);
}
}
}
/**
* Append #help to title variable.
*/
function _webform_preprocess_help_title(&$variables, array &$element) {
if (empty($variables) || empty($element['#help'])) {
return;
}
// Default #help_title to element's #title.
if (empty($element['#help_title']) && !empty($element['#title'])) {
$element['#help_title'] = $element['#title'];
}
$variables = [
'title' => (is_array($variables)) ? $variables : ['#markup' => $variables],
'help' => [
'#type' => 'webform_help',
] + array_intersect_key($element, array_flip(['#help', '#help_title'])),
];
// Add help attributes.
if (isset($element['#help_attributes'])) {
$variables['help']['#attributes'] = $element['#help_attributes'];
}
// Get #title_display and move help before title for 'inline' titles.
if (isset($element['#_title_display'])) {
// #_title_display is set via WebformElementBase::prepare.
// @see \Drupal\webform\Plugin\WebformElementBase::prepare.
$title_display = $element['#_title_display'];
}
elseif (isset($element['#title_display'])) {
$title_display = $element['#title_display'];
}
else {
$title_display = NULL;
}
if ($title_display === 'inline') {
$variables['title']['#weight'] = 0;
$variables['help']['#weight'] = -1;
}
}
/**
* Prepares #more property for form element template.
*
* @see template_preprocess_form_element()
* @see form-element.html.twig
* @see template_preprocess_datetime_wrapper()
* @see datetime-wrapper.html.twig
*/
function _webform_preprocess_form_element_description_more(array &$variables) {
$element = &$variables['element'];
if (empty($element['#more'])) {
return;
}
// Make sure description is displayed.
if (!isset($variables['description_display'])) {
$variables['description_display'] = 'after';
}
// Add more element.
$variables['description']['content']['more'] = [
'#type' => 'webform_more',
'#attributes' => (!empty($element['#id'])) ? ['id' => $element['#id'] . '--more'] : [],
] + array_intersect_key($element, array_flip(['#more', '#more_title']));
}
/******************************************************************************/
// Theme suggestions.
/******************************************************************************/
/**
* Provides alternate named suggestions for a specific theme hook.
*
* @param array $variables
* An array of variables passed to the theme hook.
* @param string $hook
* The base hook name.
*
* @return array
* An array of theme suggestions.
*/
function _webform_theme_suggestions(array $variables, $hook) {
$suggestions = [];
if ($hook == 'webform' && isset($variables['element']) && isset($variables['element']['#webform_id'])) {
$suggestions[] = $hook . '__' . $variables['element']['#webform_id'];
}
elseif ($hook == 'webform_submission_form' && isset($variables['form']) && isset($variables['form']['#webform_id'])) {
$suggestions[] = $hook . '__' . $variables['form']['#webform_id'];
}
elseif (strpos($hook, 'webform_element_base_') === 0 || strpos($hook, 'webform_container_base_') === 0) {
$element = $variables['element'];
if (!empty($element['#type'])) {
$type = $element['#type'];
$name = $element['#webform_key'];
$suggestions[] = $hook . '__' . $type;
$suggestions[] = $hook . '__' . $type . '__' . $name;
}
}
else {
$webform = NULL;
$webform_submission = NULL;
$sanitized_view_mode = NULL;
if (isset($variables['elements']) && isset($variables['elements']['#webform_submission'])) {
/** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
$webform_submission = $variables['elements']['#webform_submission'];
$webform = $webform_submission->getWebform();
$sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
}
elseif (isset($variables['webform_submission'])) {
/** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
$webform_submission = $variables['webform_submission'];
$webform = $webform_submission->getWebform();
}
elseif (isset($variables['webform'])) {
/** @var \Drupal\webform\WebformInterface $webform */
$webform = $variables['webform'];
}
if ($webform) {
$suggestions[] = $hook . '__' . $webform->id();
if (isset($variables['handler'])) {
/** @var \Drupal\webform\Plugin\WebformHandlerInterface $handler */
$handler = $variables['handler'];
$suggestions[] = $hook . '__' . $webform->id() . '__' . $handler->getPluginId();
$suggestions[] = $hook . '__' . $webform->id() . '__' . $handler->getPluginId() . '__' . $handler->getHandlerId();
}
if ($sanitized_view_mode) {
$suggestions[] = $hook . '__' . $webform->id() . '__' . $sanitized_view_mode;
$suggestions[] = $hook . '__' . $sanitized_view_mode;
}
}
}
return $suggestions;
}
/**
* Helper function used to generate hook_theme_suggestions_HOOK().
*/
function _webform_devel_hook_theme_suggestions_generate() {
$theme = webform_theme();
print '<pre>';
foreach ($theme as $hook => $info) {
$suggestion = FALSE;
if ($hook == 'webform') {
$suggestion = TRUE;
}
elseif (strpos($hook, 'webform_element_base_') === 0 || strpos($hook, 'webform_container_base_') === 0) {
$suggestion = TRUE;
}
elseif (isset($info['variables'])
&& !array_key_exists('element', $info['variables'])
&& (array_key_exists('webform_submission', $info['variables']) || array_key_exists('webform', $info['variables']))) {
$suggestion = TRUE;
}
if ($suggestion) {
print "/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_$hook(array \$variables) {
return _webform_theme_suggestions(\$variables, '$hook');
}
";
}
}
print '</pre>';
exit;
}
/******************************************************************************/
// Theme suggestions.
// Generate using _webform_devel_hook_theme_suggestions_generate();
/******************************************************************************/
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform(array $variables) {
return _webform_theme_suggestions($variables, 'webform');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_confirmation(array $variables) {
return _webform_theme_suggestions($variables, 'webform_confirmation');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_preview(array $variables) {
return _webform_theme_suggestions($variables, 'webform_preview');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_submission_navigation(array $variables) {
return _webform_theme_suggestions($variables, 'webform_submission_navigation');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_submission(array $variables) {
return _webform_theme_suggestions($variables, 'webform_submission');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_submission_form(array $variables) {
return _webform_theme_suggestions($variables, 'webform_submission_form');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_submission_information(array $variables) {
return _webform_theme_suggestions($variables, 'webform_submission_information');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_element_base_html(array $variables) {
return _webform_theme_suggestions($variables, 'webform_element_base_html');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_element_base_text(array $variables) {
return _webform_theme_suggestions($variables, 'webform_element_base_text');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_container_base_html(array $variables) {
return _webform_theme_suggestions($variables, 'webform_container_base_html');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_container_base_text(array $variables) {
return _webform_theme_suggestions($variables, 'webform_container_base_text');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_email_html(array $variables) {
return _webform_theme_suggestions($variables, 'webform_email_html');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_email_message_html(array $variables) {
return _webform_theme_suggestions($variables, 'webform_email_message_html');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_email_message_text(array $variables) {
return _webform_theme_suggestions($variables, 'webform_email_message_text');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_progress(array $variables) {
return _webform_theme_suggestions($variables, 'webform_progress');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_progress_bar(array $variables) {
return _webform_theme_suggestions($variables, 'webform_progress_bar');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function webform_theme_suggestions_webform_progress_tracker(array $variables) {
return _webform_theme_suggestions($variables, 'webform_progress_tracker');
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,296 @@
<?php
/**
* @file
* Webform module translation hooks.
*
* @see webform_preprocess_table()
*/
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\webform\Entity\Webform;
use Drupal\webform\Utility\WebformYaml;
use Drupal\Core\Serialization\Yaml;
use Drupal\webform\Utility\WebformElementHelper;
/**
* Implements hook_form_FORM_ID_alter().
*/
function webform_form_locale_translate_edit_form_alter(&$form, FormStateInterface $form_state) {
// Don't allow YAML to be validated using locale string translation.
foreach (Element::children($form['strings']) as $key) {
$element =& $form['strings'][$key];
if ($element['original']
&& !empty($element['original']['#plain_text'])
&& preg_match("/'#[^']+':/", $element['original']['#plain_text'])
&& WebformYaml::isValid($element['original']['#plain_text'])) {
$element['original'] = [
'#theme' => 'webform_codemirror',
'#code' => $element['original']['#plain_text'],
'#type' => 'yaml',
];
$element['translations'] = [
'#type' => 'webform_message',
'#message_type' => 'warning',
'#message_message' => t("Webforms can only be translated via the Webform's (Configuration) Translate tab."),
];
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function webform_form_config_translation_add_form_alter(&$form, FormStateInterface $form_state, $is_new = TRUE) {
// Manually apply YAML editor to text field that store YAML data.
foreach ($form['config_names'] as $config_name => &$config_element) {
if ($config_name == 'webform.settings') {
_webform_form_config_translate_add_form_alter_yaml_element($config_element['test']['types']);
_webform_form_config_translate_add_form_alter_yaml_element($config_element['test']['names']);
}
elseif (strpos($config_name, 'webform.webform_options.') === 0) {
_webform_form_config_translate_add_form_alter_yaml_element($config_element['options']);
}
elseif (strpos($config_name, 'webform.webform.') === 0) {
$webform_id = str_replace('webform.webform.', '', $config_name);
$webform = Webform::load($webform_id);
/** @var \Drupal\webform\WebformTranslationManagerInterface $translation_manager */
$translation_manager = \Drupal::service('webform.translation_manager');
$translation_langcode = $form_state->get('config_translation_language')->getId();;
$source_elements = $translation_manager->getSourceElements($webform);
$translation_elements = $translation_manager->getTranslationElements($webform, $translation_langcode);
$source_value = WebformYaml::encode($source_elements);
$translation_value = WebformYaml::encode($translation_elements);
_webform_form_config_translate_add_form_alter_yaml_element($config_element['elements'], $source_value, $translation_value);
$config_element['elements']['translation']['#description'] = t('Please note: Custom properties will be automatically removed.');
$form_state->set('webform_config_name', $config_name);
$form_state->set('webform_source_elements', $source_elements);
$form['#validate'][] = '_webform_form_config_translate_add_form_validate';
}
}
}
/**
* Validate callback; Validates and cleanups webform elements.
*/
function _webform_form_config_translate_add_form_validate(&$form, FormStateInterface $form_state) {
if ($form_state::hasAnyErrors()) {
return;
}
$values = $form_state->getValues();
$config_name = $form_state->get('webform_config_name');
$source_elements = $form_state->get('webform_source_elements');
$submitted_translation_elements = Yaml::decode($values['translation']['config_names'][$config_name]['elements']);
$translation_elements = $source_elements;
// Remove all custom translation properties.
WebformElementHelper::merge($translation_elements, $submitted_translation_elements);
// Remove any translation property that has not been translated.
_webform_form_config_translate_add_form_filter_elements($translation_elements, $source_elements);
// Update webform value.
$values['translation']['config_names'][$config_name]['elements'] = ($translation_elements) ? Yaml::encode($translation_elements) : '';
$form_state->setValues($values);
}
/**
* Merge element properties.
*
* @param array $translation_elements
* An array of elements.
* @param array $source_elements
* An array of elements to be merged.
*/
function _webform_form_config_translate_add_form_filter_elements(array &$translation_elements, array $source_elements) {
foreach ($translation_elements as $key => &$translation_element) {
if (!isset($source_elements[$key])) {
continue;
}
$source_element = $source_elements[$key];
if ($translation_element == $source_element) {
unset($translation_elements[$key]);
}
elseif (is_array($translation_element)) {
_webform_form_config_translate_add_form_filter_elements($translation_element, $source_element);
if (empty($translation_element)) {
unset($translation_elements[$key]);
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function webform_form_config_translation_edit_form_alter(&$form, FormStateInterface $form_state) {
webform_form_config_translation_add_form_alter($form, $form_state, FALSE);
}
/**
* Alter translated config entity property.
*
* @param array $element
* A webform element containing 'source' and 'translation'.
* @param string $source_value
* (optional) The custom config source value.
* @param string $translation_value
* (optional) The custom config translation value.
*/
function _webform_form_config_translate_add_form_alter_yaml_element(array &$element, $source_value = NULL, $translation_value = NULL) {
// Source.
$element['source']['#wrapper_attributes']['class'][] = 'webform-translation-source';
$element['source']['value'] = [
'#type' => 'webform_codemirror',
'#mode' => 'yaml',
'#value' => WebformYaml::tidy($source_value ?: trim(strip_tags($element['source']['#markup']))),
'#disabled' => TRUE,
'#attributes' => ['readonly' => TRUE],
];
unset($element['source']['#markup']);
// Translation.
$element['translation']['#type'] = 'webform_codemirror';
$element['translation']['#mode'] = 'yaml';
if ($translation_value) {
$element['translation']['#default_value'] = WebformYaml::tidy($translation_value);
}
$element['translation']['#default_value'] = trim($element['translation']['#default_value']);
$element['#attached']['library'][] = 'webform/webform.admin.translation';
}
/******************************************************************************/
// Lingotek integration.
/******************************************************************************/
/**
* Implements hook_lingotek_config_entity_document_upload().
*/
function webform_lingotek_config_entity_document_upload(array &$source_data, ConfigEntityInterface &$entity, &$url) {
switch ($entity->getEntityTypeId()) {
case 'webform';
/** @var \Drupal\webform\WebformTranslationManagerInterface $translation_manager */
$translation_manager = \Drupal::service('webform.translation_manager');
// Replace elements with just the translatable properties
// (i.e. #title, #description, #options, etc…) so that Lingotek's
// translation services can correctly translate each element.
$translation_elements = $translation_manager->getTranslationElements($entity, $entity->language()->getId());
$source_data['elements'] = $translation_elements;
_webform_lingotek_decode_tokens($source_data);
break;
case 'webform_options';
// Convert options YAML string to an associative array.
$source_data['options'] = Yaml::decode($source_data['options']);
break;
}
}
/**
* Implements hook_lingotek_config_entity_translation_presave().
*/
function webform_lingotek_config_entity_translation_presave(ConfigEntityInterface &$translation, $langcode, &$data) {
switch ($translation->getEntityTypeId()) {
case 'webform';
_webform_lingotek_decode_tokens($data);
/** @var \Drupal\webform\WebformInterface $translation */
$translation->setElements($data['elements']);
$data['elements'] = Yaml::encode($data['elements']);
break;
case 'webform_options';
/** @var \Drupal\webform\WebformOptionsInterface $translation */
// Convert options associative array back to YAML string.
$translation->setOptions($data['options']);
$data['options'] = Yaml::encode($data['options']);
break;
}
}
/**
* Implements hook_lingotek_config_object_document_upload()
*/
function webform_lingotek_config_object_document_upload(array &$data, $config_name) {
if ($config_name !== 'webform.settings') {
return;
}
$data['webform.settings']['test.types'] = Yaml::decode($data['webform.settings']['test.types']);
$data['webform.settings']['test.names'] = Yaml::decode($data['webform.settings']['test.names']);
_webform_lingotek_encode_tokens($data);
}
/**
* Implements hook_lingotek_config_object_translation_presave()
*/
function webform_lingotek_config_object_translation_presave(array &$data, $config_name) {
if ($config_name !== 'webform.settings') {
return;
}
_webform_lingotek_decode_tokens($data);
$data['webform.settings']['test.types'] = Yaml::encode($data['webform.settings']['test.types']);
$data['webform.settings']['test.names'] = Yaml::encode($data['webform.settings']['test.names']);
}
/******************************************************************************/
// Lingotek decode/encode token functions.
/******************************************************************************/
/**
* Encode all tokens so that they won't be translated.
*
* @param array $data
* An array of data.
*/
function _webform_lingotek_encode_tokens(array &$data) {
$yaml = Yaml::encode($data);
$yaml = preg_replace_callback(
'/\[([a-z][^]]+)\]/',
function ($matches) {
// Encode all token characters to HTML entities.
// @see https://stackoverflow.com/questions/6720826/php-convert-all-characters-to-html-entities.
$replacement = mb_encode_numericentity($matches[1], array(0x000000, 0x10ffff, 0, 0xffffff), 'UTF-8');
return "[$replacement]";
},
$yaml
);
$data = Yaml::decode($yaml);
}
/**
* Decode all tokens after string have been translated.
*
* @param array $data
* An array of data.
*/
function _webform_lingotek_decode_tokens(array &$data) {
$yaml = Yaml::encode($data);
$yaml = preg_replace_callback(
'/\[([^]]+?)\]/',
function ($matches) {
// Decode token HTML entities to characters.
// @see https://stackoverflow.com/questions/6720826/php-convert-all-characters-to-html-entities.
$token = mb_decode_numericentity($matches[1], array(0x000000, 0x10ffff, 0, 0xffffff), 'UTF-8');
return "[$token]";
},
$yaml
);
$data = Yaml::decode($yaml);
}