Branch 4.0.x-roblib is currently the same as Drupals main branch but with our patches applied.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

681 lines
27 KiB

<?php
namespace Drupal\reserve\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\Core\Render\Markup;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\reserve\Entity\ReserveReservation;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
/**
* Provides route responses for the Example module.
*/
class CalendarController extends ControllerBase {
/**
* Returns a simple page.
*
* Param: $ebundle, e.g. node.room
*
* @return array
* A simple renderable array.
*/
public function calendar($ebundle, $selected_month = null, $selected_day = null) {
$ebundles = reserve_get_reserve_bundles();
if(!in_array($ebundle, $ebundles)) {
throw new NotFoundHttpException();
}
else {
return $this->getBundleCalendar($ebundle, $selected_month, $selected_day);
}
}
public function calendarTitle($ebundle) {
$bundle = ebundle_split($ebundle, 'bundle');
$type = ebundle_split($ebundle, 'type');
$bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($type);
$titlePrefix = isset($bundles[$bundle]) ? $bundles[$bundle]['label'] : $bundle;
return t('@bundle Calendar', array('@bundle' => ucwords($titlePrefix)));
}
private function getBundleCalendar($ebundle, $selected_month = null, $selected_day = null) {
$config = \Drupal::config('reserve.settings');
// likely no need for this as not used anywhere else; but maybe for external modules
global $yyyy_mmdd;
//$entity_type = ebundle_split($ebundle, 'type');
//$bundle = ebundle_split($ebundle, 'bundle');
$category_field = reserve_category_fields($ebundle);
// dates are now keyed by Cat ID; but we only need one of these to build calendar but make sure its the one which starts earliest
$datesbycat = reserve_dates($ebundle, $selected_month, $selected_day);
$categories = reserve_categories($ebundle);
// allow other modules to alter the categories
\Drupal::moduleHandler()->alter('reserve_categories', $categories);
// if no Categories left; we should not bother with the rest of this
if (!count($categories)) {
\Drupal::messenger()->addWarning(t('There are no configured Reserve Categories. Please contact the System Administrator'));
return '';
}
$earliest = $this->reserve_earliest_category($categories);
$dates = $datesbycat[$earliest];
// Determine which day has been selected by the user. If the user has entered a url that specifies a day outside of the
// allowable reservation window, then set the current day as the selected day.
$yyyy_mmdd = $dates[0]['yyyymmdd'];
$day_selected = false;
foreach ($dates as $day) {
if ($day['selected']) {
$yyyy_mmdd = $day['yyyymmdd'];
$day_selected = TRUE;
}
}
if ($yyyy_mmdd == $dates[0]['yyyymmdd']) {
$dates[0]['selected'] = TRUE;
$dates[0]['today'] = TRUE;
}
if(!$day_selected) {
\Drupal::messenger()->addWarning(t('You have chosen a date to far in the '
. 'future. Please choose a date within 14 days of today. The Calendar'
. ' view below has defaulted to todays date.'));
}
// a bit hacky; but we need to store what the calendar is using for its date so we can use this in theme functions later
//$_SESSION['reservations_current_day'] = $yyyy_mmdd;
// If the day being displayed is today, only display time slots that are later than the current time minus two hours.
$today_displayed = FALSE;
foreach ($dates as $day) {
if (($day['selected']) && $day['today']) {
$today_displayed = TRUE;
}
}
if ($today_displayed) {
$hours = reserve_hours('limited');
}
else {
$hours = reserve_hours();
}
// Determine the open hours (display version) for the date selected by the user.
$building_hours_day = reserve_facility_hours($yyyy_mmdd);
$building_hours_display = $building_hours_day['display'];
// For each time slot, determine if the rooms are open or closed.
$int_first_shift_open = intval($building_hours_day['first_shift_open']);
$int_first_shift_close = intval($building_hours_day['first_shift_close']);
$int_second_shift_open = intval($building_hours_day['second_shift_open']);
$int_second_shift_close = intval($building_hours_day['second_shift_close']);
$open_24 = $building_hours_day['open_24_hours'];
$x = 0;
$buffer = $config->get('show_before_after_hours') * 100;
foreach ($hours as $time_slot) {
$int_time_slot_time = intval($time_slot['time']);
if ($building_hours_day['open_24_hours']) {
$time_slot['open'] = TRUE;
}
elseif ((($int_time_slot_time >= $int_first_shift_open) && ($int_time_slot_time < $int_first_shift_close)) ||
(($int_time_slot_time >= $int_second_shift_open) && ($int_time_slot_time < $int_second_shift_close))) {
$time_slot['open'] = TRUE;
}
else {
$time_slot['open'] = FALSE;
}
// if not open ALL day let's limit display to start just before first open shift (or 2nd if only one used)
// this assume you must have 1st shift defined and possible 2nd (i.e. can't define only 2nd shift)
if (!$open_24 && $buffer != 300) {
if ($int_first_shift_open < 9999 && ($hours[$x]['time'] < $int_first_shift_open - $buffer)) {
unset($hours[$x]);
}
else {
$hours[$x] = $time_slot;
}
// and do the same for closing
if (isset($hours[$x])) {
if ($int_second_shift_close < 9999) {
if ($hours[$x]['time'] >= $int_second_shift_close + $buffer) {
unset($hours[$x]);
}
}
elseif ($int_first_shift_close < 9999) {
if ($hours[$x]['time'] >= $int_first_shift_close + $buffer) {
unset($hours[$x]);
}
}
}
}
else {
$hours[$x] = $time_slot;
}
$x++;
}
$times = reserve_times();
$entities = reserve_entities($ebundle);
// Initialize the $reservations array.
$reservations = array();
foreach ($entities as $entity) {
$entity_name = $entity->label();
foreach ($hours as $time_slot) {
$time = $time_slot['time'];
$reservations[$entity->id()][$time] = array(
'time' => $time,
'display' => $time_slot['display'],
'class' => $time_slot['class'],
'id' => '',
'booked' => FALSE,
'start' => FALSE,
'end' => FALSE,
'user' => '',
'name' => '',
);
}
}
$results = array();
$ids = \Drupal::service('entity_type.manager')
->getStorage('reserve_reservation')->getQuery()
->condition('status', TRUE)
->condition('reservation_date', $yyyy_mmdd . '%', 'like')
->condition('reservation_ebundle', $ebundle)
//->sort('reservation_time', 'DESC')
//->sort('reservations_display_order', 'ASC');
->accessCheck(FALSE)
->execute();
$results = \Drupal::entityTypeManager()->getStorage('reserve_reservation')->loadMultiple($ids);
foreach ($results as $data) {
$id = $data->id();
$time = $data->reservation_time->getString();
$rid = $data->reservable_id->getString();
$name = $data->label();
$user = $data->getOwner();
$reservations[$rid][$time]['booked'] = TRUE;
$reservations[$rid][$time]['class'] .= ' booked';
$reservations[$rid][$time]['name'] = $name;
$reservations[$rid][$time]['user_name'] = $user->getDisplayName();
$reservations[$rid][$time]['id'] = $id;
$reservations[$rid][$time]['series_id'] = $data->reservation_series_id->getString() ? $data->reservation_series_id->getString() : '';
$reservations[$rid][$time]['user'] = $user->id();
$reservations[$rid][$time]['blocked'] = $data->reservation_private->getString() ? $data->reservation_private->getString() : FALSE;
// add rest of slots as booked for the length of this reservation
$length = $data->reservation_length->getString();;
$time_slots = $length / 30;
$reservations[$rid][$time]['slots'] = $time_slots;
$remainder = $length % 30;
if ($remainder > 1) {
$time_slots++;
}
$key = array_search($time, $times);
$x = 2;
while ($x <= $time_slots) {
$key++;
$next_time = $times[$key];
$reservations[$rid][$next_time]['booked'] = TRUE;
$reservations[$rid][$next_time]['class'] .= ' booked';
$x++;
// reservation time slots can't go passed midnight; i.e. slot 47
if ($key == 47) {
break;
}
}
// add in pre/post buffer for setup/takedown (rev 7.x-1.3+)
// - if the slot is part of buffer we add "setup" to class
// - if we don't have admin rights; we also mark it as booked so no one can book in these slots
$preslots = 0;
$postslots = 0;
if(isset($entities[$rid])) {
$category = $categories[$entities[$rid]->$category_field->getString()];
$preslots = $category['prebuffer'] / 30;
$postslots = $category['postbuffer'] / 30;
}
$startkey = array_search($reservations[$rid][$time]['time'], $times);
$endkey = $startkey + $time_slots;
$k = $startkey - $preslots;
$bookover = \Drupal::currentUser()->hasPermission('book over reservation buffer');
while ($k < $startkey) {
if (!$reservations[$rid][$times[$k]]['booked']) {
$reservations[$rid][$times[$k]]['class'] .= ' setup';
}
$reservations[$rid][$times[$k]]['booked'] = $bookover ? $reservations[$rid][$times[$k]]['booked'] : true;
$k++;
}
$k = $endkey;
while ($k < $endkey + $postslots) {
if (!$reservations[$rid][$times[$k]]['booked']) {
$reservations[$rid][$times[$k]]['class'] .= ' setup';
}
$reservations[$rid][$times[$k]]['booked'] = $bookover ? $reservations[$rid][$times[$k]]['booked'] : true;
$k++;
}
}
$vars = array(
'ebundle' => $ebundle,
'dates' => $dates,
'categories' => $categories,
'reservations' => $reservations,
'hours' => $hours,
'rooms' => $entities,
'user_reservations' => reserve_user_reservations(),
);
// @todo: should do something here with render array and tpl file
$variables = $this->calenderPage($vars);
$variables['#theme'] = 'calendar_template';
$variables['#attached']['library'] = 'reserve/reserve';
$variables['#building_hours_display'] = $building_hours_display;
return $variables;
}
/**
* Theme the reservation calendar page.
*
* @global object $user
* Drupal user object.
* @global type $base_url
* Application base url.
*
* @param array $dates
* An array containing information about all the possible days for which a
* reservtion can be made.
* @param array $categories
* An array of all the room categories.
* @param array $reservations
* An array representing all the reservations that have been made for the
* day that is being displayed on the calendar.
* @param array $hours
* An array representing every half hour time slot in a single day.
* @param string $building_hours_display
* A user friendly string of the open hours for the day being displayed
* on the calendar.
* @param array $rooms
* An array representing all of the ENTITIES that can be reserved.
* @param array $user_reservations
* An array representing the current user's reservations for the allowable
* reservation period.
*
* @return string
* The html for displaying the page.
*/
private function calenderPage($vars) {
/**
* @var $ebundle
* @var $dates
* @var $categories
* @var $reservations
* @var $hours
* @var $building_hours_display
* @var $rooms
* @var $user_reservations
*/
extract($vars);
if (!count($rooms)) {
$this->messenger()->addError(t('You will need to first add entities which can be reserved.'));
throw new NotFoundHttpException();
}
global $base_url;
$user = \Drupal::currentUser();
$extended = \Drupal::currentUser()->hasPermission('add reservations extended');
$config = \Drupal::config('reserve.settings');
$category_field = reserve_category_fields($ebundle);
$clearimg = '<img src="' . base_path() . drupal_get_path('module', 'reserve') . '/images/clear.png" />';
$modal = ['attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'modal',
'data-dialog-options' => json_encode(['width' => 700])
]];
$entity_type = ebundle_split($ebundle, 'type');
$bundle = ebundle_split($ebundle, 'bundle');
// to support minimum adv booking per category; let's get available dates per cat
$datespercat = reserve_dates($ebundle,null, null, true);
// pass some variables to JS
$format = strtolower($config->get('date_format') ? $config->get('date_format') : 'y/m/d');
$variables['#attached']['drupalSettings']['reserve']['dateFormat'] = $format;
$variables['#attached']['drupalSettings']['reserve']['ebundle'] = $ebundle;
$max_length = $extended ?
($config->get('reservation_max_length_admin') ? $config->get('reservation_max_length_admin') : 120) :
($config->get('reservation_max_length_standard') ? $config->get('reservation_max_length_standard') : 120);
$variables['#attached']['drupalSettings']['reserve']['maxLength'] = $max_length;
// Determine which date has been selected by the user.
foreach ($dates as $day) {
if ($day['selected'] == TRUE) {
$day_of_week = $day['day-of-week'];
$month_number = $day['month-number'];
$month = $day['month'];
$xday = $day['day'];
$year = $day['year'];
$yyyymmdd = $day['yyyymmdd'];
}
}
$variables['#calendar-text'] = $config->get('calendar_text');
$variables['#reserve_room_instructions_text'] = $config->get('reserve_instructions') ? $config->get('reserve_instructions') :
t('To make a reservation, click on the desired time/day in the calendar below. You will be asked to login.');
$variables['#arrow'] = base_path() . drupal_get_path('module', 'reserve') . '/images/arrow-icon.png';
$variables['#date'] = \Drupal::service('date.formatter')->format(strtotime($month . ' ' . $xday . ', ' . $year), 'custom', 'l, F d, Y');
$variables['#date_picker'] = \Drupal::formBuilder()->getForm('Drupal\reserve\Form\CalendarDatePicker');
$field = reserve_category_fields($ebundle);
$fconfig = \Drupal\field\Entity\FieldConfig::loadByName($entity_type, $bundle, $field)->getSettings();
$variables['#calendar_header'] = $fconfig['calendar_header'];
$variables['#reservation_instructions'] = $fconfig['reservation_instructions'];
// Bundle name (or Entity Type name if no bundles)
$bundle_info = \Drupal::service("entity_type.bundle.info")->getAllBundleInfo();
$bundle = $bundle_info[current($rooms)->getEntityTypeId()][current($rooms)->bundle()]['label'];
// Reservation calendar grid:
//
// Each block on the grid is assigned one (or more) of the following classes:
//
// closed - the building is closed;
// booked - the building is open and the time slot has been reserved;
// open - the building is open, the time slot has not been reserved, but the user must login to reserve the time slot;
// reservable - the building is open, the time slot has not been reserved and the user is able to reserve the time slot.
// setup - buffer zones added before/after bookings to allow for setup/takedown (per category)
//
// Panel container.
// If the user is logged in, the class for each unbooked time slot is 'reservable'. If the user is not logged in, the class is 'open'.
// Only logged in users can click a block of time to open the reservation form.
$unbooked_class = ($user->id()) ? 'reservable' : 'open';
// Create a tab for each room category. Each tab contains a grid of time slots and rooms.
$i = 0;
foreach ($categories as $index => $category) {
$table = array();
// Category TABS
$categories[$index]['active'] = ($i == 0) ? 'active' : '';
$categories[$index]['tag'] = strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $category['title']));
// Show the first tab and hide all others.
//// @todo: use JS to set active based on anchor
$categories[$index]['show'] = ($i == 0) ? 'show' : 'hide';
$i++;
$r = 0; $c = 0;
// Date and building hours.
$flipped = $config->get('calendar_flipped');
$orientclass = $flipped ? 'orient-horiz' : 'orient-vert';
$table[$r][$c] = '<li class="room-info">' . $bundle . '</li>' . "\n";
$r++;
// Available hours column.
foreach ($hours as $time_slot) {
$time_display = ($time_slot['class'] == 'odd') ? t($time_slot['display']) : "";
$table[$r][$c] = '<li class="' . $time_slot['class'] . ' timeslot" time="' . $time_slot['time'] . '">' . $time_display . '</li>' . "\n";
$r++;
}
$table[$r][$c] = '<li class="room-info room-info-footer">' . $bundle . '</li>' . "\n";
$r++;
// Column for each room in the category.
$rooms_per_category = 0;
foreach ($rooms as $room) {
$rid = $room->id();
// Count the number of rooms in the selected category.
if ($room->$category_field->getString() == $category['id']) {
$rooms_per_category++;
}
$room_name = $room->label();
$room_link = Link::fromTextAndUrl($room_name, Url::fromUri('internal:/' . $entity_type . '/' . $rid))->toString();
// use qtip if we have it
if (\Drupal::moduleHandler()->moduleExists('qtip')) {
$variables['#room_desc'] = $room->body->getString();
$room_info = '<span class="qtip-link"><span class="qtip-tooltip">' . $room_desc . '</span>' . $room_link . '</span>';
}
else {
$room_info = $room_link;
}
if ($room->$category_field->getString() == $category['id']) {
$c++; $r = 0;
// Room name
$cell = '<li class="room-info">' . $room_info . '</li>';
$r++;
// Populate each time slot.
foreach ($hours as $time_slot) {
$r++;
$time = $time_slot['time'];
$open = $time_slot['open'];
// lets use slot class from reservation if it is set
$slotclass = isset($reservations[$rid][$time_slot['time']]['class']) ? $reservations[$rid][$time_slot['time']]['class'] : $time_slot['class'];
// to support min adv booking per cat; let's simply mark all slots as closed for dates not available to this user for this cat
if (!isset($datespercat[$category['id']][$yyyymmdd])) {
$open = false;
}
// Determine if the building is open or closed for this time slot.
if ($open) {
$booked_class = ($reservations[$rid][$time]['booked']) ? '' : $unbooked_class;
}
else {
$booked_class = 'closed';
}
// The time slot can be reserved by the current user.
$viewable_class = '';
$widthclass = '';
if ($booked_class == 'reservable' && $user->hasPermission('add reservations')) {
$link = Link::fromTextAndUrl(
Markup::create($clearimg),
Url::fromUri('internal:/reserve_reservation/add/' .
$month_number . '/' . $xday . '/' . $time_slot['time'] . '/' . $rid . '/' . $ebundle, $modal))
->toString();
}
// The time slot can be reserved, but the user must login first.
elseif ($booked_class == 'open') {
$link = Link::fromTextAndUrl(
Markup::create($clearimg),
Url::fromUri('internal:/user/login?destination=/reserve_reservation/add/' .
$month_number . '/' . $xday . '/' . $time_slot['time'] . '/' . $rid . '/' . $ebundle, $modal))
->toString();
}
elseif ($booked_class == 'closed') {
$link = '';
}
else {
// if the slot has no ID this means it is the latter slots of the booking
$viewable_class = '';
$link = '';
// The time slot has a reservation that can be edited by the current user.
if ($id = $reservations[$rid][$time]['id']) {
$reservation = \Drupal::entityTypeManager()
->getStorage('reserve_reservation')->load($id);
$viewable_class = $reservation->access('update') ? 'viewable' : '';
if ($viewable_class == 'viewable') {
$options = array_merge_recursive($modal, array(
'attributes' => array(
'title' => $reservations[$rid][$time]['name'],
'class' => 'booking-span'),
'absolute' => TRUE,
));
$link = Link::fromTextAndUrl(
$reservations[$rid][$time]['name'],
Url::fromUri('internal:/reserve_reservation/' . $id . '/edit', $options))
->toString();
}
// The time slot has a reservation that cannot be viewed by the current user. and we are NOT allowed to see the Title
else if (isset($reservations[$rid][$time]['blocked']) && $reservations[$rid][$time]['blocked']) {
$link = t('Booked');
}
// The time slot has a reservation that cannot be edited by the current user. but we are allowed to see the Title
else {
$link = $reservations[$rid][$time]['name'];
}
}
$slots = isset($reservations[$rid][$time]['slots']) ? $reservations[$rid][$time]['slots'] : '';
$widthclass = $slots ? 'colspan' . $reservations[$rid][$time]['slots'] : '';
}
// allow other modules to modify the $link
\Drupal::moduleHandler()->alter('reserve_reservations_link', $link, $reservations[$rid][$time]);
// allow other modules to add a custom class to slots
$custom_class = '';
\Drupal::moduleHandler()->alter('reserve_reservations_custom_class', $custom_class, $reservations[$rid][$time]);
// add div wrapper to display better
$link = $link ? '<div class="booking-span">' . $link . '</div>' : '';
// we used book class to determine if linked or not; which we needed for pre/post slots as well as actual reservation slots
// but we don't want to show booked class now for the slots which are just buffer slots
if (stristr($slotclass, 'setup')) {
$booked_class = '';
}
if ($reservations[$rid][$time]['id']) {
$ingroup = true;
$numslots = $reservations[$rid][$time]['slots'];
if ($reservations[$rid][$time]['series_id']) {
$cell .= '<div class="reserve-group reserve-series">';
}
else {
$cell .= '<div class="reserve-group">';
}
$numslots--;
}
$cell .= '<li class="' . $slotclass . ' ' . $booked_class . ' ' . $custom_class . ' ' .
$viewable_class . ' ' . $widthclass . '">' . $link . '</li>' . "\n";
if (isset($ingroup) && $ingroup) {
if ($numslots) {
$numslots--;
}
else {
$cell .= '</div>';
$ingroup = false;
}
}
}
$table[$r][$c] = $cell;
// Room name and capacity.
$r++;
$table[$r][$c] = '<li class="room-info">' . $room_info . '</li>' . "\n";
}
}
// remove extra table labels based on admin setting
$compressed = false;
if ($config->get('calendar_compressed_labels')) {
$compressed = true;
}
// dump our table contents in std or flipped orientation
if ($flipped) {
$table = $this->reserve_transpose($table);
$m = $r;
$n = $c;
}
else {
$m = $c;
$n = $r;
}
// @todo: this should be done as variables passed to twig
$table_markup = '';
for ($x = 0; $x <= $m; $x++){
if ($flipped && $compressed && ($x == 1 || $x == $m || $x == $m - 1)) continue;
$table_markup .= '<div class="grid-column hours-column">
<ul>
';
for ($y = 0; $y <= $n; $y++) {
if (!$flipped && $compressed && ($y == 1 || $y == $n || $y == $n - 1)) continue;
if (isset($table[$y][$x])) {
$table_markup .= $table[$y][$x];
}
}
$table_markup .= '</ul>
</div>
';
}
if ($rooms_per_category) {
$categories[$index]['table'] = Markup::create($table_markup);
}
else {
$nothing_to_book = t('There are no %bundles to book in this category. You will need to assign this Reserve Category to at least 1 %bundle.',
['%bundles' => $bundle . 's', '%bundle' => $bundle]);
$categories[$index]['table'] = Markup::create('<strong>' . $nothing_to_book . '</strong>' . $table_markup);
}
}
$variables['#categories'] = $categories;
// if we have no bookable entities, lets put out helpful message
// @todo - possibly this could be done much earlier and then skip alot of the code above; but just processing empty arrays so likely not a big deal
return $variables;
}
private function reserve_transpose($array) {
array_unshift($array, null);
return call_user_func_array('array_map', $array);
}
private function reserve_earliest_category($categories) {
$earliest = 1000;
$extended = \Drupal::currentUser()->hasPermission('add reservations extended');
$catmin = $extended ? 'minadvext' : 'minadvstd';
foreach ($categories as $cat) {
if ($cat[$catmin] < $earliest) {
$earliest = $cat[$catmin];
$result = $cat['id'];
}
}
return $result;
}
public function reservationAddModalCallback() {
$response = new jsonResponse();
$entity = ReserveReservation::create();
$editForm = \Drupal::service('entity.form_builder')->getForm($entity, 'default');
$form = \Drupal::service('renderer')->renderPlain($editForm);
$response->setData($form);
return $response;
}
public function accessCalendarPage(AccountInterface $account, $ebundle) {
return AccessResult::allowedIf($account->hasPermission('access calendar for ' . $ebundle));
}
}