bat.module in Booking and Availability Management Tools for Drupal 8
Same filename and directory in other branches
Contains bat.module..
File
bat.moduleView source
<?php
/**
* @file
* Contains bat.module..
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\Core\Render\Element;
use Drupal\bat\Entity\TypeGroup;
use Drupal\bat\Entity\TypeGroupBundle;
/**
* Implements hook_help().
*/
function bat_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the bat module.
case 'help.page.bat':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('A generalized Booking and Availability Management Framework') . '</p>';
return $output;
default:
}
}
/**
* Implements hook_theme().
*/
function bat_theme() {
return [
'bat_type_group_add_list' => [
'variables' => [
'content' => NULL,
],
],
'bat_entity_edit_form' => [
'render element' => 'form',
],
];
}
/**
* Implements hook_toolbar().
*/
function bat_toolbar() {
$items = [];
$items['bat'] = [
'#type' => 'toolbar_item',
'#attached' => [
'library' => [
'bat/drupal.bat.toolbar',
],
],
];
return $items;
}
/**
* Prepares variables for list of available type group bundles templates.
*
* Default template: bat-type-group-add-list.html.twig.
*
* @param array $variables
* An associative array containing:
* - content: An array of type group bundles.
*/
function template_preprocess_bat_type_group_add_list(&$variables) {
$variables['types'] = [];
if (!empty($variables['content'])) {
foreach ($variables['content'] as $type) {
$variables['types'][$type
->id()] = [
'type' => $type
->id(),
'add_link' => Link::fromTextAndUrl($type
->label(), new Url('entity.bat_type_group.add_form', [
'type_group_bundle' => $type
->id(),
])),
];
}
}
}
/**
* Implements hook_entity_access().
*/
function bat_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
$rights =& drupal_static(__FUNCTION__, []);
$entity_type = $entity
->getEntityType()
->id();
if (in_array($entity_type, [
'bat_type_group',
'bat_unit',
'bat_unit_type',
'bat_event',
'bat_event_series',
'bat_booking',
])) {
$entity_info = \Drupal::entityTypeManager()
->getDefinition($entity_type);
$cid = $entity
->id();
// If we are creating a new entity make sure we set the type
// so permissions get applied.
if ($operation == 'create' && $cid == '') {
$cid = $entity
->getEntityType()
->id();
}
// If we've already checked access for this entity, user and op, return the
// cached result.
if (isset($rights[$account
->id()][$cid][$operation])) {
if ($rights[$account
->id()][$cid][$operation]) {
return AccessResult::allowed();
}
else {
return AccessResult::forbidden();
}
}
// Grant generic administrator level access.
if ($account
->hasPermission('bypass ' . $entity_type . ' entities access')) {
$rights[$account
->id()][$cid][$operation] = TRUE;
return AccessResult::allowed();
}
if ($operation == 'view') {
// When trying to figure out access to an entity, query the base table
// using our access control tag.
// TODO remove incorrect hook test in summer 2020.
if (Drupal::moduleHandler()
->getImplementations('query_' . $entity_type . '_alter') || Drupal::moduleHandler()
->getImplementations('query_' . $entity_type . '_access_alter')) {
// Trigger an error if the deprecated hook is implemented.
// TODO remove in summer 2020.
if (Drupal::moduleHandler()
->getImplementations('query_' . $entity_type . '_alter')) {
$message = "The 'query_{$entity_type}_alter' hook has been ";
$message .= "deprecated, and will be removed in a future release. ";
$message .= "Please use the 'query_{$entity_type}_access_alter'";
$message .= " hook instead.";
// phpcs:ignore
trigger_error($message, E_USER_DEPRECATED);
}
$query = \Drupal::database()
->select($entity_info
->getBaseTable());
$query
->addExpression('1');
$result = (bool) $query
->addTag($entity_type)
->addTag($entity_type . '_access')
->addMetaData('account', $account)
->condition($entity_info
->getKey('id'), $entity
->id())
->range(0, 1)
->execute()
->fetchField();
$rights[$account
->id()][$cid][$operation] = $result;
if ($result) {
return AccessResult::allowed();
}
else {
return AccessResult::forbidden();
}
}
else {
$rights[$account
->id()][$cid][$operation] = TRUE;
return AccessResult::allowed();
}
}
else {
// Non-view operations.
// First grant access to the entity for the specified operation if no other
// module denies it and at least one other module says to grant access.
$access_results = \Drupal::moduleHandler()
->invokeAll('bat_entity_access', [
$entity,
$operation,
$account,
]);
if (in_array(FALSE, $access_results, TRUE)) {
$rights[$account
->id()][$cid][$operation] = FALSE;
return AccessResult::forbidden();
}
elseif (in_array(TRUE, $access_results, TRUE)) {
$rights[$account
->id()][$cid][$operation] = TRUE;
return AccessResult::allowed();
}
// Grant access based on entity type and bundle specific permissions with
// special handling for the create operation since the entity passed in will
// be initialized without ownership.
if ($operation == 'create') {
$access = $account
->hasPermission('create ' . $entity_type . ' entities') || $account
->hasPermission('create ' . $entity_type . ' entities of bundle ' . $entity
->bundle());
$rights[$account
->id()][$cid][$operation] = $access;
if ($access) {
return AccessResult::allowed();
}
else {
return AccessResult::forbidden();
}
}
else {
// Finally perform checks for the rest of operations. Begin by
// extracting the bundle name from the entity if available.
$bundle_name = $entity
->bundle();
// For the update and delete operations, first perform the entity type and
// bundle-level access check for any entity.
if ($account
->hasPermission($operation . ' any ' . $entity_type . ' entity') || $account
->hasPermission($operation . ' any ' . $entity_type . ' entity of bundle ' . $bundle_name)) {
$rights[$account
->id()][$cid][$operation] = TRUE;
return AccessResult::allowed();
}
// Then check an authenticated user's access to delete own entities.
if (method_exists($entity, 'getOwnerId')) {
if ($account
->id() && $entity
->getOwnerId() && $entity
->getOwnerId() === $account
->id()) {
if ($account
->hasPermission($operation . ' own ' . $entity_type . ' entities') || $account
->hasPermission($operation . ' own ' . $entity_type . ' entities of bundle ' . $bundle_name)) {
$rights[$account
->id()][$cid][$operation] = TRUE;
return AccessResult::allowed();
}
}
}
}
}
return AccessResult::forbidden();
}
}
/**
* Return permission names for a given entity type.
*/
function bat_entity_access_permissions($entity_type) {
$entity_info = \Drupal::entityTypeManager()
->getDefinition($entity_type);
$label = $entity_info
->getLabel()
->__toString();
$permissions = [];
// General 'bypass' permission.
$permissions['bypass ' . $entity_type . ' entities access'] = [
'title' => t('Bypass access to @entity_type', [
'@entity_type' => $label,
]),
'description' => t('Allows users to perform any action on @entity_type.', [
'@entity_type' => $label,
]),
'restrict access' => TRUE,
];
// Generic create and edit permissions.
$permissions['create ' . $entity_type . ' entities'] = [
'title' => t('Create @entity_type of any type', [
'@entity_type' => $label,
]),
];
if ($entity_info
->getKey('uid') !== FALSE) {
$permissions['view own ' . $entity_type . ' entities'] = [
'title' => t('View own @entity_type of any type', [
'@entity_type' => $label,
]),
];
}
$permissions['view any ' . $entity_type . ' entity'] = [
'title' => t('View any @entity_type of any type', [
'@entity_type' => $label,
]),
'restrict access' => TRUE,
];
if ($entity_info
->getKey('uid') !== FALSE) {
$permissions['update own ' . $entity_type . ' entities'] = [
'title' => t('Edit own @entity_type of any type', [
'@entity_type' => $label,
]),
];
}
$permissions['update any ' . $entity_type . ' entity'] = [
'title' => t('Edit any @entity_type of any type', [
'@entity_type' => $label,
]),
'restrict access' => TRUE,
];
if ($entity_info
->getKey('uid') !== FALSE) {
$permissions['delete own ' . $entity_type . ' entities'] = [
'title' => t('Delete own @entity_type of any type', [
'@entity_type' => $label,
]),
];
}
$permissions['delete any ' . $entity_type . ' entity'] = [
'title' => t('Delete any @entity_type of any type', [
'@entity_type' => $label,
]),
'restrict access' => TRUE,
];
// Per-bundle create and edit permissions.
foreach (\Drupal::service('entity_type.bundle.info')
->getBundleInfo($entity_type) as $bundle_name => $bundle_info) {
$permissions['create ' . $entity_type . ' entities of bundle ' . $bundle_name] = [
'title' => t('Create %bundle @entity_type', [
'@entity_type' => $label,
'%bundle' => $bundle_info['label'],
]),
];
if ($entity_info
->getKey('uid') !== FALSE) {
$permissions['view own ' . $entity_type . ' entities of bundle ' . $bundle_name] = [
'title' => t('View own %bundle @entity_type', [
'@entity_type' => $label,
'%bundle' => $bundle_info['label'],
]),
];
}
$permissions['view any ' . $entity_type . ' entity of bundle ' . $bundle_name] = [
'title' => t('View any %bundle @entity_type', [
'@entity_type' => $label,
'%bundle' => $bundle_info['label'],
]),
'restrict access' => TRUE,
];
if ($entity_info
->getKey('uid') !== FALSE) {
$permissions['update own ' . $entity_type . ' entities of bundle ' . $bundle_name] = [
'title' => t('Edit own %bundle @entity_type', [
'@entity_type' => $label,
'%bundle' => $bundle_info['label'],
]),
];
}
$permissions['update any ' . $entity_type . ' entity of bundle ' . $bundle_name] = [
'title' => t('Edit any %bundle @entity_type', [
'@entity_type' => $label,
'%bundle' => $bundle_info['label'],
]),
'restrict access' => TRUE,
];
if ($entity_info
->getKey('uid') !== FALSE) {
$permissions['delete own ' . $entity_type . ' entities of bundle ' . $bundle_name] = [
'title' => t('Delete own %bundle @entity_type', [
'@entity_type' => $label,
'%bundle' => $bundle_info['label'],
]),
];
}
$permissions['delete any ' . $entity_type . ' entity of bundle ' . $bundle_name] = [
'title' => t('Delete any %bundle @entity_type', [
'@entity_type' => $label,
'%bundle' => $bundle_info['label'],
]),
'restrict access' => TRUE,
];
}
return $permissions;
}
/**
* Implements hook_query_alter().
*
* Enforces access control for bat units during database queries.
*/
function bat_entity_access_query_alter($query, $entity_type, $base_table = NULL, $account = NULL, $op = 'view') {
// Get the Drupal user account from the query if available, or
// default to the logged in user if not.
if (!isset($account) && !($account = $query
->getMetaData('account'))) {
$account = \Drupal::currentUser();
}
// Do not apply any conditions for users with administrative view permissions.
if ($account
->hasPermission('bypass ' . $entity_type . ' entities access') || $account
->hasPermission($op . ' any ' . $entity_type . ' entity')) {
return;
}
// Get the entity type info array for the current access check and prepare a
// conditions object.
$entity_info = \Drupal::entityTypeManager()
->getDefinition($entity_type);
// Prepare an OR container for conditions. Conditions will be added that seek
// to grant access, meaning any particular type of permission check may grant
// access even if none of the others apply. At the end of this function, if no
// conditions have been added to the array, a condition will be added that
// always returns FALSE (1 = 0).
$conditions = new Condition('OR');
// Loop over every possible bundle for the given entity type.
foreach (\Drupal::service('entity_type.bundle.info')
->getBundleInfo($entity_type) as $bundle_name => $bundle_info) {
// If the user has access to operation entities of the current bundle...
if ($account
->hasPermission($op . ' any ' . $entity_type . ' entity of bundle ' . $bundle_name)) {
// Add a condition granting access if the entity specified by the view
// query is of the same bundle.
$conditions
->condition($base_table . '.' . $entity_info
->getKey('bundle'), $bundle_name);
}
elseif ($account
->id() && $entity_info
->getKey('uid') !== FALSE && $account
->hasPermission($op . ' own ' . $entity_type . ' entities of bundle ' . $bundle_name)) {
// Add an AND condition group that grants access if the entity specified
// by the view query matches the same bundle and belongs to the user.
$c_and = new Condition('AND');
$conditions
->condition($c_and
->condition($base_table . '.' . $entity_info
->getKey('bundle'), $bundle_name)
->condition($base_table . '.' . $entity_info
->getKey('uid'), $account
->id()));
}
}
// Perform 'operation own' access control for the entity in the query if the
// user is authenticated.
if ($account
->id() && $account
->hasPermission($op . ' own ' . $entity_type . ' entities')) {
$conditions
->condition($base_table . '.' . $entity_info
->getKey('uid'), $account
->id());
}
// Prepare an array of condition alter hooks to invoke and an array of context
// data for the current query.
$hooks = [
'bat_entity_access_' . $op . '_condition_' . $entity_type,
'bat_entity_access_' . $op . '_condition',
];
$context = [
'account' => $account,
'entity_type' => $entity_type,
'base_table' => $base_table,
];
// Allow other modules to add conditions to the array as necessary.
\Drupal::moduleHandler()
->alter($hooks, $conditions, $context);
// If we have more than one condition based on the entity access permissions
// and any hook implementations...
if (count($conditions)) {
// Add the conditions to the query.
$query
->condition($conditions);
}
else {
// Otherwise, since we don't have any possible conditions to match against,
// we falsify this query. View checks are access grants, not access denials.
$query
->where('1 = 0');
}
}
/**
* Utility function to create two related datepickers.
*
* We have a few forms that need a start and end date field
* and we need to apply the same javascript to these forms in order to enforce
* specific consistent behaviours and group the form elements and javascript
* injection in one place.
*
* @param int $year
* @param int $month
*
* @return array
* The array holding the field definitions
*/
function bat_date_range_fields($year = NULL, $month = NULL, $granularity = 'bat_hourly') {
$date_range_fields = [];
$config = \Drupal::config('bat.settings');
$date = new DateTime();
if ($year && $month) {
// Calculate min and max dates of the specified year/month.
$date
->setDate($year, $month, 01);
$min_date = $date
->format($config
->get('bat_daily_date_format'));
$date
->modify('last day of this month');
$max_date = $date
->format($config
->get('bat_daily_date_format'));
$extra_attributes = [
'min' => $min_date,
'max' => $max_date,
'bat-min' => $min_date,
'bat-max' => $max_date,
];
}
else {
$date
->modify('+' . $config
->get('bat_event_start_date') . ' days');
$extra_attributes = [
'min' => $date
->format($config
->get('bat_daily_date_format')),
'bat-min' => $date
->format($config
->get('bat_daily_date_format')),
];
}
$date_range_fields['bat_start_date'] = [
'#prefix' => '<div class="form-wrapper bat-date-range"><div class="start-date">',
'#suffix' => '</div>',
'#type' => 'date',
'#title' => t('Event Start'),
'#date_date_format' => $config
->get('bat_daily_date_format'),
'#attributes' => $extra_attributes + [
'type' => 'date',
'class' => [
'bat_start_date',
],
],
'#required' => TRUE,
];
$date_range_fields['bat_end_date'] = [
'#prefix' => '<div class="end-date">',
'#suffix' => '</div></div>',
'#type' => 'date',
'#title' => t('Event End'),
'#date_date_format' => $config
->get('bat_daily_date_format'),
'#attributes' => $extra_attributes + [
'type' => 'date',
'class' => [
'bat_end_date',
],
],
'#required' => TRUE,
'#attached' => [
'library' => [
'bat/bat_date_range',
],
],
];
return $date_range_fields;
}
/**
* Prepares variables for Type Group templates.
*
* Default template: bat-type-group.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the user information and any
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_bat_type_group(array &$variables) {
// Fetch Type Group Entity Object.
$type_group = $variables['elements']['#bat_type_group'];
// Helpful $content variable for templates.
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
/**
* Fetches a type group object.
*
* @param int $group_id
* Integer specifying the group id.
* @param bool $reset
* A boolean indicating whether the internal cache should be reset.
*
* @return \Drupal\bat\Entity\TypeGroup
* A fully-loaded $type_group object or FALSE if it cannot be loaded.
*
* @see bat_type_group_load_multiple()
*/
function bat_type_group_load($group_id, $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()
->getStorage('bat_type_group')
->resetCache();
}
return TypeGroup::load($group_id);
}
/**
* Loads multiple units based on certain conditions.
*
* @param array $group_ids
* An array of group IDs.
* @param array $conditions
* An array of conditions to match against the {type_group} table.
* @param bool $reset
* A boolean indicating that the internal cache should be reset.
*
* @return array
* An array of type group objects, indexed by group_id.
*
* @see bat_type_group_load()
*/
function bat_type_group_load_multiple($group_ids = [], $conditions = [], $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()
->getStorage('bat_type_group')
->resetCache();
}
if (!empty($conditions)) {
$query = \Drupal::entityQuery('bat_type_group');
if (!empty($group_ids)) {
$query
->condition('id', $group_ids, 'IN');
}
foreach ($conditions as $key => $value) {
$query
->condition($key, $value);
}
$group_ids = $query
->execute();
}
return TypeGroup::loadMultiple($group_ids);
}
/**
* Saves a type group to the database.
*
* @param \Drupal\bat\Entity\TypeGroup $group
* The TypeGroup object.
*/
function bat_type_group_save(TypeGroup $group) {
return $group
->save();
}
/**
* Deletes a type group.
*
* @param \Drupal\bat\Entity\TypeGroup $group
* The TypeGroup object that represents the group to delete.
*/
function bat_type_group_delete(TypeGroup $group) {
$group
->delete();
}
/**
* Deletes multiple type groups.
*
* @param array $group_ids
* An array of group IDs.
*/
function bat_type_group_delete_multiple(array $group_ids) {
$groups = TypeGroup::loadMultiple($group_ids);
foreach ($groups as $group) {
$group
->delete();
}
}
/**
* Gets an array of all type group bundles, keyed by the bundle name.
*
* @param string $bundle_name
* If set, the bundle with the given name is returned.
* @param bool $reset
* A boolean indicating that the internal cache should be reset.
*
* @return \Drupal\bat\Entity\TypeGroupBundle[]
* Depending whether $bundle isset, an array of type group bundles or a single one.
*/
function bat_type_group_get_bundles($bundle_name = NULL, $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()
->getStorage('bat_type_group_bundle')
->resetCache();
}
$types = TypeGroupBundle::loadMultiple();
return isset($bundle_name) ? $types[$bundle_name] : $types;
}
/**
* Creates a type group object.
*
* @param array $values
* The properties for the new type group bundle.
*/
function bat_type_group_create($values = []) {
return TypeGroup::create($values);
}
/**
* Menu argument loader; Load a type group bundle by string.
*
* @param string $bundle
* The machine-readable name of a type group bundle to load.
* @param bool $reset
* A boolean indicating whether the internal cache should be reset.
*
* @return \Drupal\bat\Entity\TypeGroupBundle
* A type group bundle array or FALSE if $bundle does not exist.
*/
function bat_type_group_bundle_load($bundle, $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()
->getStorage('bat_type_group_bundle')
->resetCache([
$bundle,
]);
}
return TypeGroupBundle::load($bundle);
}
Functions
Name | Description |
---|---|
bat_date_range_fields | Utility function to create two related datepickers. |
bat_entity_access | Implements hook_entity_access(). |
bat_entity_access_permissions | Return permission names for a given entity type. |
bat_entity_access_query_alter | Implements hook_query_alter(). |
bat_help | Implements hook_help(). |
bat_theme | Implements hook_theme(). |
bat_toolbar | Implements hook_toolbar(). |
bat_type_group_bundle_load | Menu argument loader; Load a type group bundle by string. |
bat_type_group_create | Creates a type group object. |
bat_type_group_delete | Deletes a type group. |
bat_type_group_delete_multiple | Deletes multiple type groups. |
bat_type_group_get_bundles | Gets an array of all type group bundles, keyed by the bundle name. |
bat_type_group_load | Fetches a type group object. |
bat_type_group_load_multiple | Loads multiple units based on certain conditions. |
bat_type_group_save | Saves a type group to the database. |
template_preprocess_bat_type_group | Prepares variables for Type Group templates. |
template_preprocess_bat_type_group_add_list | Prepares variables for list of available type group bundles templates. |