You are here

webform_group.module in Webform 6.x

Same filename and directory in other branches
  1. 8.5 modules/webform_group/webform_group.module

Provides a Webform integration with the Group module.

File

modules/webform_group/webform_group.module
View source
<?php

/**
 * @file
 * Provides a Webform integration with the Group module.
 */
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\webform\Element\WebformMessage;
use Drupal\webform\WebformInterface;
use Drupal\webform\Plugin\WebformHandler\EmailWebformHandler;
use Drupal\webform\WebformSubmissionInterface;

/******************************************************************************/

// Schema definitions hook.

/******************************************************************************/

/**
 * Implements hook_config_schema_info_alter().
 */
function webform_group_config_schema_info_alter(&$definitions) {

  // Append group roles to webform access schema.
  $mapping =& $definitions['webform.webform.*']['mapping']['access']['sequence']['mapping'];
  $mapping['group_roles'] = [
    'type' => 'sequence',
    'label' => 'Group roles',
    'sequence' => [
      'type' => 'string',
      'label' => 'Group role',
    ],
  ];
}

/******************************************************************************/

// Form and element UI alter hooks.

/******************************************************************************/

/**
 * Implements hook_form_FORM_ID_alter() for webform handler form.
 *
 * Adds group role token options to email handler elements.
 */
function webform_group_form_webform_handler_form_alter(&$form, FormStateInterface $form_state) {

  /** @var \Drupal\webform\Form\WebformHandlerFormBase $form_object */
  $form_object = $form_state
    ->getFormObject();
  $webform_handler = $form_object
    ->getWebformHandler();
  if (!$webform_handler instanceof EmailWebformHandler) {
    return;
  }

  /** @var \Drupal\webform_group\WebformGroupManagerInterface $webform_group_manager */
  $webform_group_manager = \Drupal::service('webform_group.manager');

  // Get available group tokens as options.
  // @see webform_group_token_info()
  $group_role_options = [];

  /** @var \Drupal\group\Entity\GroupTypeInterface[] $group_types */
  $group_types = \Drupal::entityTypeManager()
    ->getStorage('group_type')
    ->loadMultiple();
  $group_role_names = [];
  foreach ($group_types as $group_type_id => $group_type) {
    $group_roles = $group_type
      ->getRoles();
    foreach ($group_roles as $group_role_id => $group_role) {
      if ($group_role
        ->isInternal() && $group_role_id !== "{$group_type_id}-member" || !$group_role
        ->inPermissionsUI() || $group_role
        ->isAnonymous()) {
        continue;
      }

      // Make sure the group role is allowed to be used by email handlers.
      // @see webform_group_form_webform_admin_config_handlers_form_alter()
      if ($webform_group_manager
        ->isGroupRoleTokenEnabled($group_role_id)) {
        $t_args = [
          '@group_type' => $group_type
            ->label(),
          '@group_role' => $group_role
            ->label(),
        ];
        $group_role_options["[webform_group:role:{$group_role_id}]"] = t('@group_type: @group_role', $t_args);
      }
      $group_role_name = preg_replace("/^{$group_type_id}-/", "", $group_role_id);

      // Make sure the group name is allowed to be used by email handlers.
      // @see webform_group_form_webform_admin_config_handlers_form_alter()
      if ($webform_group_manager
        ->isGroupRoleTokenEnabled($group_role_name)) {
        $group_role_names[$group_role_name] = $group_role
          ->label();
      }
    }
  }
  foreach ($group_role_names as $group_role_name => $group_role_label) {
    $group_role_options["[webform_group:role:{$group_role_name}]"] = $group_role_label;
  }
  if ($webform_group_manager
    ->isGroupOwnerTokenEnable()) {
    $group_role_options['[webform_group:owner:mail]'] = t('Owner');
  }
  if ($group_role_options) {
    _webform_group_form_webform_handler_form_alter_email_element_recursive($form, $group_role_options);
  }
  if (empty($group_role_options) && \Drupal::currentUser()
    ->hasPermission('administer webform')) {
    $route_name = 'webform.config.handlers';
    $route_destination = Url::fromRoute('entity.webform.handlers', [
      'webform' => $webform_handler
        ->getWebform()
        ->id(),
    ])
      ->toString();
    $route_options = [
      'query' => [
        'destination' => $route_destination,
      ],
    ];
    $t_args = [
      ':href' => Url::fromRoute($route_name, [], $route_options)
        ->toString(),
    ];
    $form['settings']['to']['group_roles_message'] = [
      '#type' => 'webform_message',
      '#message_type' => 'warning',
      '#message_message' => t('Please note: You can select which <strong>group roles</strong> are available to receive webform emails by going to the Webform module\'s <a href=":href">admin settings</a> form.', $t_args),
      '#message_close' => TRUE,
      '#message_id' => 'webform_email_group_roles_message',
      '#message_storage' => WebformMessage::STORAGE_USER,
    ];
  }
}

/**
 * Add group role token options to email handler elements.
 *
 * @param array $form
 *   A form.
 * @param array $options
 *   Group role token options.
 */
function _webform_group_form_webform_handler_form_alter_email_element_recursive(array &$form, array $options) {
  foreach ($form as $element_key => &$element) {
    if (!Element::child($element_key) || !is_array($element)) {
      continue;
    }
    if (isset($element['#type']) && $element['#type'] === 'webform_select_other' && isset($element['#other__type']) && $element['#other__type'] === 'webform_email_multiple') {
      $group_optgroup = (string) t('Group roles');
      $other_optgroup = (string) t('Other');
      $other_options = $element['#options'][$other_optgroup];
      unset($element['#options'][$other_optgroup]);
      $element['#options'][$group_optgroup] = $options;
      $element['#options'][$other_optgroup] = $other_options;
    }
    _webform_group_form_webform_handler_form_alter_email_element_recursive($element, $options);
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for webform settings access form.
 */
function webform_group_form_webform_settings_access_form_alter(&$form, FormStateInterface $form_state) {

  /** @var \Drupal\webform_group\WebformGroupManagerInterface $webform_group_manager */
  $webform_group_manager = \Drupal::service('webform_group.manager');

  /** @var Drupal\webform\EntitySettings\WebformEntitySettingsAccessForm $entity_form */
  $entity_form = $form_state
    ->getFormObject();

  /** @var \Drupal\webform\WebformInterface $webform */
  $webform = $entity_form
    ->getEntity();
  $weight = 0;
  $access_rules = $webform_group_manager
    ->getAccessRules($webform);
  foreach ($access_rules as $permission => $access_rule) {
    if ($permission === 'administer') {

      // Create dedicated 'Administer submission' details.
      $form['access']['administer_submissions'] = [
        '#type' => 'details',
        '#title' => t('Administer submissions (Groups only)'),
        '#open' => FALSE,
        '#weight' => $weight++,
      ];
      $form['access']['administer_submissions']['group_roles'] = [
        '#type' => 'webform_group_roles',
        '#title' => t('Group (node) roles'),
        '#default_value' => $access_rules[$permission]['group_roles'],
      ];
    }
    else {
      $form['access'][$permission]['group_roles'] = [
        '#type' => 'webform_group_roles',
        '#title' => t('Group (node) roles'),
        '#default_value' => $access_rules[$permission]['group_roles'],
      ];
      $form['access'][$permission]['group_roles_message'] = [
        '#type' => 'webform_message',
        '#message_message' => t('Anonymous and authenticated users are able to access this webform, which will result in group roles being ignored.'),
        '#message_type' => 'warning',
        '#message_close' => TRUE,
        '#message_storage' => WebformMessage::STORAGE_SESSION,
        '#states' => [
          'visible' => [
            [
              ':input[name="access[' . $permission . '][roles][anonymous]"]' => [
                'checked' => TRUE,
              ],
            ],
            'or',
            [
              ':input[name="access[' . $permission . '][roles][authenticated]"]' => [
                'checked' => TRUE,
              ],
            ],
          ],
        ],
      ];
    }
    $form['access'][$permission]['#weight'] = $weight++;
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for webform ui element form.
 */
function webform_group_form_webform_ui_element_form_alter(&$form, FormStateInterface $form_state) {
  $default_properties = $form_state
    ->get('default_properties');
  $custom_default_value =& $form['properties']['custom']['properties']['#default_value'];
  $operations = [
    'create',
    'update',
    'view',
  ];
  foreach ($operations as $operation) {
    $user_roles_property = 'access_' . $operation . '_roles';
    $group_roles_property = 'access_' . $operation . '_group_roles';

    // Get default value from custom properties.
    if (isset($custom_default_value[$group_roles_property])) {
      $default_value = $custom_default_value[$group_roles_property];
      unset($custom_default_value[$group_roles_property]);
    }
    else {
      $default_value = [];
    }
    $form['properties']['access']['access_' . $operation][$group_roles_property] = [
      '#type' => 'webform_group_roles',
      '#title' => t('Group roles'),
      '#parents' => [
        'properties',
        $group_roles_property,
      ],
      '#default_value' => $default_value,
    ];
    $form['properties']['access']['access_' . $operation][$group_roles_property . '_message'] = [
      '#type' => 'webform_message',
      '#message_message' => t('Anonymous or authenticated users are able to access this element, which will result in group roles being ignored.'),
      '#message_type' => 'warning',
      '#message_close' => TRUE,
      '#message_storage' => WebformMessage::STORAGE_SESSION,
      '#states' => [
        'visible' => [
          [
            ':input[name="properties[' . $user_roles_property . '][anonymous]"]' => [
              'checked' => TRUE,
            ],
          ],
          'or',
          [
            ':input[name="properties[' . $user_roles_property . '][authenticated]"]' => [
              'checked' => TRUE,
            ],
          ],
        ],
      ],
    ];

    // Set default property so that these custom properties are processed.
    $default_properties[$group_roles_property] = [];
  }
  $form_state
    ->set('default_properties', $default_properties);
}

/**
 * Implements hook_form_FORM_ID_alter() for webform admin config handlers form.
 */
function webform_group_form_webform_admin_config_handlers_form_alter(&$form, FormStateInterface $form_state) {
  $form['mail']['roles']['#weight'] = 0;
  $form['mail']['group_roles'] = [
    '#type' => 'webform_group_roles',
    '#title' => t('Recipient group roles'),
    '#description' => t("Select group roles that can be assigned to receive a webform's email. <em>Please note: Selected group roles will be available to all webforms.</em>"),
    '#include_anonymous' => FALSE,
    '#include_outsider' => FALSE,
    '#default_value' => \Drupal::config('webform_group.settings')
      ->get('mail.group_roles'),
    '#parents' => [
      'webform_group_mail',
      'group_roles',
    ],
    '#weight' => 0,
  ];
  $form['mail']['group_owner'] = [
    '#type' => 'checkbox',
    '#title' => t('Allow group owner to receive emails'),
    '#return_value' => TRUE,
    '#default_value' => \Drupal::config('webform_group.settings')
      ->get('mail.group_owner'),
    '#parents' => [
      'webform_group_mail',
      'group_owner',
    ],
    '#weight' => 0,
  ];
  $form['#submit'][] = '_webform_group_form_webform_admin_config_handlers_form_submit';
}

/**
 * Submit handler for handlers configuration form.
 */
function _webform_group_form_webform_admin_config_handlers_form_submit(&$form, FormStateInterface $form_state) {
  \Drupal::configFactory()
    ->getEditable('webform_group.settings')
    ->set('mail', $form_state
    ->getValue('webform_group_mail'))
    ->save();
  \Drupal::token()
    ->resetInfo();
  if (function_exists('token_clear_cache')) {
    token_clear_cache();
  }
}

/******************************************************************************/

// Access controls.

/******************************************************************************/

/**
 * Implements hook_ENTITY_TYPE_access() for webform entities.
 */
function webform_group_webform_access(WebformInterface $webform, $operation, AccountInterface $account) {

  // Prevent recursion when a webform is being passed as the source entity
  // via the URL.
  // @see \Drupal\webform\Plugin\WebformSourceEntity\QueryStringWebformSourceEntity::getSourceEntity
  if (\Drupal::request()->query
    ->get('source_entity_type') === 'webform') {
    return AccessResult::neutral();
  }

  /** @var \Drupal\webform_group\WebformGroupManagerInterface $webform_group_manager */
  $webform_group_manager = \Drupal::service('webform_group.manager');

  // Get the current user's group roles for the current group content.
  $current_user_group_roles = $webform_group_manager
    ->getCurrentUserGroupRoles();

  // Get webform's access rules.
  $access_rules = $webform_group_manager
    ->getAccessRules($webform);

  // Get access rules permission name.
  $permission = str_replace('submission_', '', $operation);

  // Make sure the permission exists.
  if (!isset($access_rules[$permission])) {
    return AccessResult::neutral();
  }

  // Compare the current user group roles with the admin and permission
  // access rules' group roles.
  $is_admin = array_intersect($access_rules['administer']['group_roles'], $current_user_group_roles);
  $has_permission = array_intersect($access_rules[$permission]['group_roles'], $current_user_group_roles);
  return AccessResult::allowedIf($is_admin || $has_permission)
    ->cachePerUser()
    ->addCacheableDependency($webform);
}

/**
 * Implements hook_ENTITY_TYPE_access() for webform_submission entities.
 */
function webform_group_webform_submission_access(WebformSubmissionInterface $webform_submission, $operation, AccountInterface $account) {
  if (!in_array($operation, [
    'view',
    'update',
    'delete',
  ])) {
    return AccessResult::neutral();
  }

  /** @var \Drupal\webform_group\WebformGroupManagerInterface $webform_group_manager */
  $webform_group_manager = \Drupal::service('webform_group.manager');

  // During testing we need to only look at the current users group roles.
  // @todo Rework webform_group_webform_submission_query_access_alter().
  // @see \Drupal\Tests\webform_group\Functional\WebformGroupSubmissionAccessTest
  if (drupal_valid_test_ua()) {

    // Get the current user's group roles for the current group content.
    $user_group_roles = $webform_group_manager
      ->getCurrentUserGroupRoles();
  }
  else {

    // Get the user's group roles for the current group content.
    $user_group_roles = $webform_group_manager
      ->getWebformSubmissionUserGroupRoles($webform_submission, $account);
  }

  // Get webform access rules.
  $webform = $webform_submission
    ->getWebform();
  $access_rules = $webform_group_manager
    ->getAccessRules($webform);

  // Compare the current user group roles with the admin and permission
  // access rules' group roles.
  if (array_intersect($access_rules['administer']['group_roles'], $user_group_roles) || array_intersect($access_rules[$operation . '_any']['group_roles'], $user_group_roles) || array_intersect($access_rules[$operation . '_own']['group_roles'], $user_group_roles) && (int) $webform_submission
    ->getOwnerId() === (int) $account
    ->id()) {
    return AccessResult::allowed()
      ->cachePerUser()
      ->addCacheableDependency($webform)
      ->addCacheableDependency($webform_submission);
  }

  // No opinion.
  return AccessResult::neutral();
}

/**
 * Implements hook_webform_element_access().
 */
function webform_group_webform_element_access($operation, array $element, AccountInterface $account = NULL) {

  // Set default access rules.
  $element += [
    '#access_' . $operation . '_roles' => [
      'anonymous',
      'authenticated',
    ],
    '#access_' . $operation . '_group_roles' => [],
  ];

  // Get user and group roles.
  $user_roles = $element['#access_' . $operation . '_roles'];
  $group_roles = $element['#access_' . $operation . '_group_roles'];

  // If group roles are empty and current user is assigned Drupal's
  // anonymous/authenticated roles, then allow access.
  if (empty($group_roles)) {
    $current_user_default_role = $account
      ->isAnonymous() ? 'anonymous' : 'authenticated';
    if (in_array($current_user_default_role, $user_roles)) {
      return AccessResult::allowed();
    }
  }

  /** @var \Drupal\webform_group\WebformGroupManagerInterface $webform_group_manager */
  $webform_group_manager = \Drupal::service('webform_group.manager');
  $current_user_group_roles = $webform_group_manager
    ->getCurrentUserGroupRoles() ?: [];

  // If the group or current user roles are empty return no opinion.
  if (empty($group_roles) || empty($current_user_group_roles)) {
    return AccessResult::neutral();
  }
  return AccessResult::allowedIf(array_intersect($group_roles, $current_user_group_roles));
}

/**
 * Implements hook_webform_submission_query_access_alter().
 */
function webform_group_webform_submission_query_access_alter(AlterableInterface $query, array $webform_submission_tables) {

  /** @var \Drupal\Core\Database\Query\SelectInterface $query */
  $operation = $query
    ->getMetaData('op') ?: 'view';
  $account = $query
    ->getMetaData('account') ?: \Drupal::currentUser();

  /** @var \Drupal\webform_group\WebformGroupManagerInterface $webform_group_manager */
  $webform_group_manager = \Drupal::service('webform_group.manager');

  // Get the current group webform.
  $webform = $webform_group_manager
    ->getCurrentGroupWebform();
  if (!$webform) {
    return;
  }

  // Get the current group content (source) entity.
  $group_content = $webform_group_manager
    ->getCurrentGroupContent();
  $source_entity = $group_content
    ->getEntity();

  // Get the current user's group roles for the current group content.
  $current_user_group_roles = $webform_group_manager
    ->getCurrentUserGroupRoles();

  // Get webform's access rules.
  $access_rules = $webform_group_manager
    ->getAccessRules($webform);
  $has_administer_access = array_intersect($access_rules['administer']['group_roles'], $current_user_group_roles);
  $has_any_access = array_intersect($access_rules[$operation . '_any']['group_roles'], $current_user_group_roles);

  // Only check own access if user can administer or access any submissions.
  if (!$has_administer_access && !$has_any_access) {
    $check_own_access = array_intersect($access_rules[$operation . '_own']['group_roles'], $current_user_group_roles);
  }
  else {
    $check_own_access = FALSE;
  }
  if ($has_administer_access || $has_any_access || $check_own_access) {
    foreach ($webform_submission_tables as $table) {

      /** @var \Drupal\Core\Database\Query\SelectInterface $query */
      $and_condition = $query
        ->andConditionGroup();
      $and_condition
        ->condition($table['alias'] . '.webform_id', $webform
        ->id());
      $and_condition
        ->condition($table['alias'] . '.entity_type', $source_entity
        ->getEntityTypeId());
      $and_condition
        ->condition($table['alias'] . '.entity_id', $source_entity
        ->id());
      if ($check_own_access) {
        $and_condition
          ->condition($table['alias'] . '.uid', $account
          ->id());
      }
      $table['condition']
        ->condition($and_condition);
    }
  }
}