commit
d43eb41d25
@ -0,0 +1,2 @@
|
||||
INSERT INTO `Privileges` (`id`, `name`, `desc`) VALUES (40, 'view_rooms', 'User can view rooms');
|
||||
INSERT INTO `GroupPrivileges` (`id`, `group_id`, `privilege_id`) VALUES (NULL, '-2', '40');
|
@ -1,7 +1,95 @@
|
||||
<?php
|
||||
use Engelsystem\ShiftsFilterRenderer;
|
||||
use Engelsystem\ShiftsFilter;
|
||||
use Engelsystem\ShiftCalendarRenderer;
|
||||
|
||||
/**
|
||||
* Room controllers for managing everything room related.
|
||||
*/
|
||||
|
||||
/**
|
||||
* View a room with its shifts.
|
||||
*/
|
||||
function room_controller() {
|
||||
global $privileges, $user;
|
||||
|
||||
if (! in_array('view_rooms', $privileges)) {
|
||||
redirect(page_link_to());
|
||||
}
|
||||
|
||||
$room = load_room();
|
||||
$all_shifts = Shifts_by_room($room);
|
||||
$days = [];
|
||||
foreach ($all_shifts as $shift) {
|
||||
$day = date("Y-m-d", $shift['start']);
|
||||
if (! in_array($day, $days)) {
|
||||
$days[] = $day;
|
||||
}
|
||||
}
|
||||
|
||||
$shiftsFilter = new ShiftsFilter(true, [
|
||||
$room['RID']
|
||||
], AngelType_ids());
|
||||
$selected_day = date("Y-m-d");
|
||||
if (! empty($days)) {
|
||||
$selected_day = $days[0];
|
||||
}
|
||||
if (isset($_REQUEST['shifts_filter_day'])) {
|
||||
$selected_day = $_REQUEST['shifts_filter_day'];
|
||||
}
|
||||
$shiftsFilter->setStartTime(parse_date("Y-m-d H:i", $selected_day . ' 00:00'));
|
||||
$shiftsFilter->setEndTime(parse_date("Y-m-d H:i", $selected_day . ' 23:59'));
|
||||
|
||||
$shiftsFilterRenderer = new ShiftsFilterRenderer($shiftsFilter);
|
||||
$shiftsFilterRenderer->enableDaySelection($days);
|
||||
|
||||
$shifts = Shifts_by_ShiftsFilter($shiftsFilter, $user);
|
||||
|
||||
return [
|
||||
$room['Name'],
|
||||
Room_view($room, $shiftsFilterRenderer, new ShiftCalendarRenderer($shifts, $shiftsFilter))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch different room actions.
|
||||
*/
|
||||
function rooms_controller() {
|
||||
if (! isset($_REQUEST['action'])) {
|
||||
$_REQUEST['action'] = 'list';
|
||||
}
|
||||
|
||||
switch ($_REQUEST['action']) {
|
||||
default:
|
||||
case 'list':
|
||||
redirect(page_link_to('admin_rooms'));
|
||||
case 'view':
|
||||
return room_controller();
|
||||
}
|
||||
}
|
||||
|
||||
function room_link($room) {
|
||||
return page_link_to('rooms') . '&action=view&room_id=' . $room['RID'];
|
||||
}
|
||||
|
||||
function room_edit_link($room) {
|
||||
return page_link_to('admin_rooms') . '&show=edit&id=' . $room['RID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads room by request param room_id
|
||||
*/
|
||||
function load_room() {
|
||||
if (! test_request_int('room_id')) {
|
||||
redirect(page_link_to());
|
||||
}
|
||||
|
||||
$room = Room($_REQUEST['room_id']);
|
||||
if ($room == null) {
|
||||
redirect(page_link_to());
|
||||
}
|
||||
|
||||
return $room;
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Sign up for a shift.
|
||||
*/
|
||||
function shift_entry_add_controller() {
|
||||
global $privileges, $user;
|
||||
|
||||
if (isset($_REQUEST['shift_id']) && preg_match("/^[0-9]*$/", $_REQUEST['shift_id'])) {
|
||||
$shift_id = $_REQUEST['shift_id'];
|
||||
} else {
|
||||
redirect(page_link_to('user_shifts'));
|
||||
}
|
||||
|
||||
// Locations laden
|
||||
$rooms = sql_select("SELECT * FROM `Room` WHERE `show`='Y' ORDER BY `Name`");
|
||||
$room_array = [];
|
||||
foreach ($rooms as $room) {
|
||||
$room_array[$room['RID']] = $room['Name'];
|
||||
}
|
||||
|
||||
$shift = Shift($shift_id);
|
||||
$shift['Name'] = $room_array[$shift['RID']];
|
||||
if ($shift == null) {
|
||||
redirect(page_link_to('user_shifts'));
|
||||
}
|
||||
|
||||
if (isset($_REQUEST['type_id']) && preg_match("/^[0-9]*$/", $_REQUEST['type_id'])) {
|
||||
$type_id = $_REQUEST['type_id'];
|
||||
} else {
|
||||
redirect(page_link_to('user_shifts'));
|
||||
}
|
||||
|
||||
if (in_array('user_shifts_admin', $privileges)) {
|
||||
$type = sql_select("SELECT * FROM `AngelTypes` WHERE `id`='" . sql_escape($type_id) . "' LIMIT 1");
|
||||
} else {
|
||||
$type = sql_select("SELECT * FROM `UserAngelTypes` JOIN `AngelTypes` ON (`UserAngelTypes`.`angeltype_id` = `AngelTypes`.`id`) WHERE `AngelTypes`.`id` = '" . sql_escape($type_id) . "' AND (`AngelTypes`.`restricted` = 0 OR (`UserAngelTypes`.`user_id` = '" . sql_escape($user['UID']) . "' AND NOT `UserAngelTypes`.`confirm_user_id` IS NULL)) LIMIT 1");
|
||||
}
|
||||
|
||||
if (count($type) == 0) {
|
||||
redirect(page_link_to('user_shifts'));
|
||||
}
|
||||
$type = $type[0];
|
||||
|
||||
if (! Shift_signup_allowed($shift, $type)) {
|
||||
error(_('You are not allowed to sign up for this shift. Maybe shift is full or already running.'));
|
||||
redirect(shift_link($shift));
|
||||
}
|
||||
|
||||
if (isset($_REQUEST['submit'])) {
|
||||
$selected_type_id = $type_id;
|
||||
if (in_array('user_shifts_admin', $privileges)) {
|
||||
if (isset($_REQUEST['user_id']) && preg_match("/^[0-9]*$/", $_REQUEST['user_id'])) {
|
||||
$user_id = $_REQUEST['user_id'];
|
||||
} else {
|
||||
$user_id = $user['UID'];
|
||||
}
|
||||
|
||||
if (sql_num_query("SELECT * FROM `User` WHERE `UID`='" . sql_escape($user_id) . "' LIMIT 1") == 0) {
|
||||
redirect(page_link_to('user_shifts'));
|
||||
}
|
||||
|
||||
if (isset($_REQUEST['angeltype_id']) && test_request_int('angeltype_id') && sql_num_query("SELECT * FROM `AngelTypes` WHERE `id`='" . sql_escape($_REQUEST['angeltype_id']) . "' LIMIT 1") > 0) {
|
||||
$selected_type_id = $_REQUEST['angeltype_id'];
|
||||
}
|
||||
} else {
|
||||
$user_id = $user['UID'];
|
||||
}
|
||||
|
||||
if (sql_num_query("SELECT * FROM `ShiftEntry` WHERE `SID`='" . sql_escape($shift['SID']) . "' AND `UID` = '" . sql_escape($user_id) . "'")) {
|
||||
return error("This angel does already have an entry for this shift.", true);
|
||||
}
|
||||
|
||||
$freeloaded = $shift['freeloaded'];
|
||||
$freeload_comment = $shift['freeload_comment'];
|
||||
if (in_array("user_shifts_admin", $privileges)) {
|
||||
$freeloaded = isset($_REQUEST['freeloaded']);
|
||||
$freeload_comment = strip_request_item_nl('freeload_comment');
|
||||
}
|
||||
|
||||
$comment = strip_request_item_nl('comment');
|
||||
$result = ShiftEntry_create([
|
||||
'SID' => $shift_id,
|
||||
'TID' => $selected_type_id,
|
||||
'UID' => $user_id,
|
||||
'Comment' => $comment,
|
||||
'freeloaded' => $freeloaded,
|
||||
'freeload_comment' => $freeload_comment
|
||||
]);
|
||||
if ($result === false) {
|
||||
engelsystem_error('Unable to create shift entry.');
|
||||
}
|
||||
|
||||
if ($type['restricted'] == 0 && sql_num_query("SELECT * FROM `UserAngelTypes` INNER JOIN `AngelTypes` ON `AngelTypes`.`id` = `UserAngelTypes`.`angeltype_id` WHERE `angeltype_id` = '" . sql_escape($selected_type_id) . "' AND `user_id` = '" . sql_escape($user_id) . "' ") == 0) {
|
||||
sql_query("INSERT INTO `UserAngelTypes` (`user_id`, `angeltype_id`) VALUES ('" . sql_escape($user_id) . "', '" . sql_escape($selected_type_id) . "')");
|
||||
}
|
||||
|
||||
$user_source = User($user_id);
|
||||
engelsystem_log("User " . User_Nick_render($user_source) . " signed up for shift " . $shift['name'] . " from " . date("Y-m-d H:i", $shift['start']) . " to " . date("Y-m-d H:i", $shift['end']));
|
||||
success(_("You are subscribed. Thank you!") . ' <a href="' . page_link_to('user_myshifts') . '">' . _("My shifts") . ' »</a>');
|
||||
redirect(shift_link($shift));
|
||||
}
|
||||
|
||||
if (in_array('user_shifts_admin', $privileges)) {
|
||||
$users = sql_select("SELECT *, (SELECT count(*) FROM `ShiftEntry` WHERE `freeloaded`=1 AND `ShiftEntry`.`UID`=`User`.`UID`) AS `freeloaded` FROM `User` ORDER BY `Nick`");
|
||||
$users_select = [];
|
||||
|
||||
foreach ($users as $usr) {
|
||||
$users_select[$usr['UID']] = $usr['Nick'] . ($usr['freeloaded'] == 0 ? "" : " (" . _("Freeloader") . ")");
|
||||
}
|
||||
$user_text = html_select_key('user_id', 'user_id', $users_select, $user['UID']);
|
||||
|
||||
$angeltypes_source = sql_select("SELECT * FROM `AngelTypes` ORDER BY `name`");
|
||||
$angeltypes = [];
|
||||
foreach ($angeltypes_source as $angeltype) {
|
||||
$angeltypes[$angeltype['id']] = $angeltype['name'];
|
||||
}
|
||||
$angeltype_select = html_select_key('angeltype_id', 'angeltype_id', $angeltypes, $type['id']);
|
||||
} else {
|
||||
$user_text = User_Nick_render($user);
|
||||
$angeltype_select = $type['name'];
|
||||
}
|
||||
|
||||
return ShiftEntry_edit_view($user_text, date("Y-m-d H:i", $shift['start']) . ' – ' . date('Y-m-d H:i', $shift['end']) . ' (' . shift_length($shift) . ')', $shift['Name'], $shift['name'], $angeltype_select, "", false, null, in_array('user_shifts_admin', $privileges));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove somebody from a shift.
|
||||
*/
|
||||
function shift_entry_delete_controller() {
|
||||
global $privileges;
|
||||
|
||||
if (! in_array('user_shifts_admin', $privileges)) {
|
||||
redirect(page_link_to('user_shifts'));
|
||||
}
|
||||
|
||||
if (! isset($_REQUEST['entry_id']) || ! test_request_int('entry_id')) {
|
||||
redirect(page_link_to('user_shifts'));
|
||||
}
|
||||
$entry_id = $_REQUEST['entry_id'];
|
||||
|
||||
$shift_entry_source = sql_select("
|
||||
SELECT `User`.`Nick`, `ShiftEntry`.`Comment`, `ShiftEntry`.`UID`, `ShiftTypes`.`name`, `Shifts`.*, `Room`.`Name`, `AngelTypes`.`name` as `angel_type`
|
||||
FROM `ShiftEntry`
|
||||
JOIN `User` ON (`User`.`UID`=`ShiftEntry`.`UID`)
|
||||
JOIN `AngelTypes` ON (`ShiftEntry`.`TID` = `AngelTypes`.`id`)
|
||||
JOIN `Shifts` ON (`ShiftEntry`.`SID` = `Shifts`.`SID`)
|
||||
JOIN `ShiftTypes` ON (`ShiftTypes`.`id` = `Shifts`.`shifttype_id`)
|
||||
JOIN `Room` ON (`Shifts`.`RID` = `Room`.`RID`)
|
||||
WHERE `ShiftEntry`.`id`='" . sql_escape($entry_id) . "'");
|
||||
if (count($shift_entry_source) > 0) {
|
||||
$shift_entry_source = $shift_entry_source[0];
|
||||
|
||||
$result = ShiftEntry_delete($entry_id);
|
||||
if ($result === false) {
|
||||
engelsystem_error('Unable to delete shift entry.');
|
||||
}
|
||||
|
||||
engelsystem_log("Deleted " . User_Nick_render($shift_entry_source) . "'s shift: " . $shift_entry_source['name'] . " at " . $shift_entry_source['Name'] . " from " . date("Y-m-d H:i", $shift_entry_source['start']) . " to " . date("Y-m-d H:i", $shift_entry_source['end']) . " as " . $shift_entry_source['angel_type']);
|
||||
success(_("Shift entry deleted."));
|
||||
} else {
|
||||
error(_("Entry not found."));
|
||||
}
|
||||
redirect(page_link_to('user_shifts'));
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Engelsystem;
|
||||
|
||||
/**
|
||||
* BO Class that stores all parameters used to filter shifts for users.
|
||||
*
|
||||
* @author msquare
|
||||
*/
|
||||
class ShiftsFilter {
|
||||
|
||||
/**
|
||||
* Shift is completely full.
|
||||
*/
|
||||
const FILLED_FILLED = 1;
|
||||
|
||||
/**
|
||||
* Shift has some free slots.
|
||||
*/
|
||||
const FILLED_FREE = 0;
|
||||
|
||||
/**
|
||||
* Has the user "user shifts admin" privilege?
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $userShiftsAdmin;
|
||||
|
||||
private $filled = [];
|
||||
|
||||
private $rooms = [];
|
||||
|
||||
private $types = [];
|
||||
|
||||
private $startTime = null;
|
||||
|
||||
private $endTime = null;
|
||||
|
||||
public function __construct($user_shifts_admin, $rooms, $types) {
|
||||
$this->user_shifts_admin = $user_shifts_admin;
|
||||
$this->rooms = $rooms;
|
||||
$this->types = $types;
|
||||
|
||||
$this->filled = [
|
||||
ShiftsFilter::FILLED_FREE
|
||||
];
|
||||
|
||||
if ($user_shifts_admin) {
|
||||
$this->filled[] = ShiftsFilter::FILLED_FILLED;
|
||||
}
|
||||
}
|
||||
|
||||
public function getStartTime() {
|
||||
return $this->startTime;
|
||||
}
|
||||
|
||||
public function setStartTime($startTime) {
|
||||
$this->startTime = $startTime;
|
||||
}
|
||||
|
||||
public function getEndTime() {
|
||||
return $this->endTime;
|
||||
}
|
||||
|
||||
public function setEndTime($endTime) {
|
||||
$this->endTime = $endTime;
|
||||
}
|
||||
|
||||
public function getTypes() {
|
||||
if (count($this->types) == 0) {
|
||||
return [
|
||||
0
|
||||
];
|
||||
}
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
public function setTypes($types) {
|
||||
$this->types = $types;
|
||||
}
|
||||
|
||||
public function getRooms() {
|
||||
if (count($this->rooms) == 0) {
|
||||
return [
|
||||
0
|
||||
];
|
||||
}
|
||||
return $this->rooms;
|
||||
}
|
||||
|
||||
public function setRooms($rooms) {
|
||||
$this->rooms = $rooms;
|
||||
}
|
||||
|
||||
public function isUserShiftsAdmin() {
|
||||
return $this->userShiftsAdmin;
|
||||
}
|
||||
|
||||
public function setUserShiftsAdmin($userShiftsAdmin) {
|
||||
$this->userShiftsAdmin = $userShiftsAdmin;
|
||||
}
|
||||
|
||||
public function getFilled() {
|
||||
return $this->filled;
|
||||
}
|
||||
|
||||
public function setFilled($filled) {
|
||||
$this->filled = $filled;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Engelsystem;
|
||||
|
||||
/**
|
||||
* Represents a single lane in a shifts calendar.
|
||||
*/
|
||||
class ShiftCalendarLane {
|
||||
|
||||
private $firstBlockStartTime;
|
||||
|
||||
private $blockCount;
|
||||
|
||||
private $header;
|
||||
|
||||
private $shifts = [];
|
||||
|
||||
public function __construct($header, $firstBlockStartTime, $blockCount) {
|
||||
$this->header = $header;
|
||||
$this->firstBlockStartTime = $firstBlockStartTime;
|
||||
$this->blockCount = $blockCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a shift to the lane, but only if it fits.
|
||||
* Returns true on success.
|
||||
*
|
||||
* @param Shift $shift
|
||||
* The shift to add
|
||||
* @return boolean true on success
|
||||
*/
|
||||
public function addShift($shift) {
|
||||
if ($this->shiftFits($shift)) {
|
||||
$this->shifts[] = $shift;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given shift fits into this lane.
|
||||
*
|
||||
* @param Shift $shift
|
||||
* The shift to fit into this lane
|
||||
*/
|
||||
public function shiftFits($newShift) {
|
||||
foreach ($this->shifts as $laneShift) {
|
||||
if (! ($newShift['start'] >= $laneShift['end'] || $newShift['end'] <= $laneShift['start'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getHeader() {
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
public function getShifts() {
|
||||
return $this->shifts;
|
||||
}
|
||||
}
|
||||
?>
|
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace Engelsystem;
|
||||
|
||||
class ShiftCalendarRenderer {
|
||||
|
||||
/**
|
||||
* 15m * 60s/m = 900s
|
||||
*/
|
||||
const SECONDS_PER_ROW = 900;
|
||||
|
||||
/**
|
||||
* Height of a block in pixel.
|
||||
* Do not change - corresponds with theme/css
|
||||
*/
|
||||
const BLOCK_HEIGHT = 30;
|
||||
|
||||
/**
|
||||
* Distance between two shifts in pixels
|
||||
*/
|
||||
const MARGIN = 5;
|
||||
|
||||
private $lanes;
|
||||
|
||||
private $shiftsFilter;
|
||||
|
||||
private $firstBlockStartTime = null;
|
||||
|
||||
private $blocksPerSlot = null;
|
||||
|
||||
public function __construct($shifts, ShiftsFilter $shiftsFilter) {
|
||||
$this->shiftsFilter = $shiftsFilter;
|
||||
$this->firstBlockStartTime = $this->calcFirstBlockStartTime($shifts);
|
||||
$this->lanes = $this->assignShiftsToLanes($shifts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the shifts to different lanes per room if they collide
|
||||
*
|
||||
* @param Shift[] $shifts
|
||||
* The shifts to assign
|
||||
*
|
||||
* @return Returns an array that assigns a room_id to an array of ShiftCalendarLane containing the shifts
|
||||
*/
|
||||
private function assignShiftsToLanes($shifts) {
|
||||
// array that assigns a room id to a list of lanes (per room)
|
||||
$lanes = [];
|
||||
|
||||
foreach ($shifts as $shift) {
|
||||
$room_id = $shift['RID'];
|
||||
if (! isset($lanes[$room_id])) {
|
||||
// initialize room with one lane
|
||||
$header = Room_name_render([
|
||||
'RID' => $room_id,
|
||||
'Name' => $shift['room_name']
|
||||
]);
|
||||
$lanes[$room_id] = [
|
||||
new ShiftCalendarLane($header, $this->getFirstBlockStartTime(), $this->getBlocksPerSlot())
|
||||
];
|
||||
}
|
||||
// Try to add the shift to the existing lanes for this room
|
||||
$shift_added = false;
|
||||
foreach ($lanes[$room_id] as $lane) {
|
||||
$shift_added = $lane->addShift($shift);
|
||||
if ($shift_added == true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If all lanes for this room are busy, create a new lane and add shift to it
|
||||
if ($shift_added == false) {
|
||||
$newLane = new ShiftCalendarLane("", $this->getFirstBlockStartTime(), $this->getBlocksPerSlot());
|
||||
if (! $newLane->addShift($shift)) {
|
||||
engelsystem_error("Unable to add shift to new lane.");
|
||||
}
|
||||
$lanes[$room_id][] = $newLane;
|
||||
}
|
||||
}
|
||||
|
||||
return $lanes;
|
||||
}
|
||||
|
||||
public function getFirstBlockStartTime() {
|
||||
return $this->firstBlockStartTime;
|
||||
}
|
||||
|
||||
public function getBlocksPerSlot() {
|
||||
if ($this->blocksPerSlot == null) {
|
||||
$this->blocksPerSlot = $this->calcBlocksPerSlot();
|
||||
}
|
||||
return $this->blocksPerSlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the whole calendar
|
||||
*
|
||||
* @return the generated html
|
||||
*/
|
||||
public function render() {
|
||||
return div('shift-calendar', [
|
||||
$this->renderTimeLane(),
|
||||
$this->renderShiftLanes()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the lanes containing the shifts
|
||||
*/
|
||||
private function renderShiftLanes() {
|
||||
$html = "";
|
||||
foreach ($this->lanes as $room_lanes) {
|
||||
foreach ($room_lanes as $lane) {
|
||||
$html .= $this->renderLane($lane);
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single lane
|
||||
*
|
||||
* @param ShiftCalendarLane $lane
|
||||
* The lane to render
|
||||
*/
|
||||
private function renderLane(ShiftCalendarLane $lane) {
|
||||
$shift_renderer = new ShiftCalendarShiftRenderer();
|
||||
$html = "";
|
||||
$rendered_until = $this->getFirstBlockStartTime();
|
||||
foreach ($lane->getShifts() as $shift) {
|
||||
while ($rendered_until + ShiftCalendarRenderer::SECONDS_PER_ROW <= $shift['start']) {
|
||||
$html .= $this->renderTick($rendered_until);
|
||||
$rendered_until += ShiftCalendarRenderer::SECONDS_PER_ROW;
|
||||
}
|
||||
|
||||
list($shift_height, $shift_html) = $shift_renderer->render($shift);
|
||||
$html .= $shift_html;
|
||||
$rendered_until += $shift_height * ShiftCalendarRenderer::SECONDS_PER_ROW;
|
||||
}
|
||||
while ($rendered_until <= $this->shiftsFilter->getEndTime()) {
|
||||
$html .= $this->renderTick($rendered_until);
|
||||
$rendered_until += ShiftCalendarRenderer::SECONDS_PER_ROW;
|
||||
}
|
||||
|
||||
return div('lane', [
|
||||
div('header', $lane->getHeader()),
|
||||
$html
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a tick/block for given time
|
||||
*
|
||||
* @param int $time
|
||||
* unix timestamp
|
||||
* @param boolean $label
|
||||
* Should time labels be generated?
|
||||
* @return rendered tick html
|
||||
*/
|
||||
private function renderTick($time, $label = false) {
|
||||
if ($time % (24 * 60 * 60) == 23 * 60 * 60) {
|
||||
if (! $label) {
|
||||
return div('tick day');
|
||||
}
|
||||
return div('tick day', [
|
||||
date('Y-m-d<b\r />H:i', $time)
|
||||
]);
|
||||
} elseif ($time % (60 * 60) == 0) {
|
||||
if (! $label) {
|
||||
return div('tick hour');
|
||||
}
|
||||
return div('tick hour', [
|
||||
date('H:i', $time)
|
||||
]);
|
||||
}
|
||||
return div('tick');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the left time lane including hour/day ticks
|
||||
*/
|
||||
private function renderTimeLane() {
|
||||
$time_slot = [
|
||||
div('header', [
|
||||
_("Time")
|
||||
])
|
||||
];
|
||||
for ($block = 0; $block < $this->getBlocksPerSlot(); $block ++) {
|
||||
$thistime = $this->getFirstBlockStartTime() + ($block * ShiftCalendarRenderer::SECONDS_PER_ROW);
|
||||
$time_slot[] = $this->renderTick($thistime, true);
|
||||
}
|
||||
return div('lane time', $time_slot);
|
||||
}
|
||||
|
||||
private function calcFirstBlockStartTime($shifts) {
|
||||
$start_time = $this->shiftsFilter->getEndTime();
|
||||
foreach ($shifts as $shift) {
|
||||
if ($shift['start'] < $start_time) {
|
||||
$start_time = $shift['start'];
|
||||
}
|
||||
}
|
||||
return ShiftCalendarRenderer::SECONDS_PER_ROW * floor(($start_time - 60 * 60) / ShiftCalendarRenderer::SECONDS_PER_ROW);
|
||||
}
|
||||
|
||||
private function calcBlocksPerSlot() {
|
||||
return ceil(($this->shiftsFilter->getEndTime() - $this->getFirstBlockStartTime()) / ShiftCalendarRenderer::SECONDS_PER_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace Engelsystem;
|
||||
|
||||
/**
|
||||
* Renders a single shift for the shift calendar
|
||||
*/
|
||||
class ShiftCalendarShiftRenderer {
|
||||
|
||||
/**
|
||||
* Renders a shift
|
||||
*
|
||||
* @param Shift $shift
|
||||
* The shift to render
|
||||
*/
|
||||
public function render($shift) {
|
||||
global $privileges;
|
||||
|
||||
$collides = $this->collides();
|
||||
$info_text = "";
|
||||
if ($shift['title'] != '') {
|
||||
$info_text = glyph('info-sign') . $shift['title'] . '<br>';
|
||||
}
|
||||
list($is_free, $shifts_row) = $this->renderShiftNeededAngeltypes($shift, $collides);
|
||||
|
||||
if (isset($shift['own']) && $shift['own'] && ! in_array('user_shifts_admin', $privileges)) {
|
||||
$class = 'primary';
|
||||
} elseif ($collides && ! in_array('user_shifts_admin', $privileges)) {
|
||||
$class = 'default';
|
||||
} elseif ($is_free) {
|
||||
$class = 'danger';
|
||||
} else {
|
||||
$class = 'success';
|
||||
}
|
||||
|
||||
$blocks = ceil(($shift["end"] - $shift["start"]) / ShiftCalendarRenderer::SECONDS_PER_ROW);
|
||||
$blocks = max(1, $blocks);
|
||||
return [
|
||||
$blocks,
|
||||
'<td class="shift" rowspan="' . $blocks . '">' . div('shift panel panel-' . $class . '" style="height: ' . ($blocks * ShiftCalendarRenderer::BLOCK_HEIGHT - ShiftCalendarRenderer::MARGIN) . 'px"', [
|
||||
$this->renderShiftHead($shift),
|
||||
div('panel-body', [
|
||||
$info_text,
|
||||
Room_name_render([
|
||||
'RID' => $shift['RID'],
|
||||
'Name' => $shift['room_name']
|
||||
])
|
||||
]),
|
||||
$shifts_row,
|
||||
div('shift-spacer')
|
||||
]) . '</td>'
|
||||
];
|
||||
}
|
||||
|
||||
private function renderShiftNeededAngeltypes($shift, $collides) {
|
||||
global $privileges;
|
||||
|
||||
$html = "";
|
||||
$is_free = false;
|
||||
$angeltypes = NeededAngelTypes_by_shift($shift['SID']);
|
||||
foreach ($angeltypes as $angeltype) {
|
||||
list($angeltype_free, $angeltype_html) = $this->renderShiftNeededAngeltype($shift, $angeltype, $collides);
|
||||
$is_free |= $angeltype_free;
|
||||
$html .= $angeltype_html;
|
||||
}
|
||||
if (in_array('user_shifts_admin', $privileges)) {
|
||||
$html .= '<li class="list-group-item">' . button(page_link_to('user_shifts') . '&shift_id=' . $shift['SID'] . '&type_id=' . $angeltype['id'], _("Add more angels"), 'btn-xs') . '</li>';
|
||||
}
|
||||
if ($html != '') {
|
||||
return [
|
||||
$is_free,
|
||||
'<ul class="list-group">' . $html . '</ul>'
|
||||
];
|
||||
}
|
||||
return [
|
||||
$is_free,
|
||||
""
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a list entry containing the needed angels for an angeltype
|
||||
*
|
||||
* @param Shift $shift
|
||||
* The shift which is rendered
|
||||
* @param Angeltype $angeltype
|
||||
* The angeltype, containing informations about needed angeltypes and already signed up angels
|
||||
* @param boolean $collides
|
||||
* true if the shift collides with the users shifts
|
||||
*/
|
||||
private function renderShiftNeededAngeltype($shift, $angeltype, $collides) {
|
||||
global $privileges;
|
||||
|
||||
$is_free = false;
|
||||
$entry_list = [];
|
||||
$freeloader = 0;
|
||||
foreach ($angeltype['shift_entries'] as $entry) {
|
||||
$style = '';
|
||||
if ($entry['freeloaded']) {
|
||||
$freeloader ++;
|
||||
$style = " text-decoration: line-through;";
|
||||
}
|
||||
$entry_list[] = "<span style=\"$style\">" . User_Nick_render(User($entry['UID'])) . "</span>";
|
||||
}
|
||||
if ($angeltype['count'] - count($angeltype['shift_entries']) - $freeloader > 0) {
|
||||
$inner_text = sprintf(ngettext("%d helper needed", "%d helpers needed", $angeltype['count'] - count($angeltype['shift_entries'])), $angeltype['count'] - count($angeltype['shift_entries']));
|
||||
// is the shift still running or alternatively is the user shift admin?
|
||||
$user_may_join_shift = true;
|
||||
|
||||
// you cannot join if user alread joined a parallel or this shift
|
||||
$user_may_join_shift &= ! $collides;
|
||||
|
||||
// you cannot join if user is not of this angel type
|
||||
$user_may_join_shift &= isset($angeltype['user_id']);
|
||||
|
||||
// you cannot join if you are not confirmed
|
||||
if ($angeltype['restricted'] == 1 && isset($angeltype['user_id'])) {
|
||||
$user_may_join_shift &= isset($angeltype['confirm_user_id']);
|
||||
}
|
||||
|
||||
// you can only join if the shift is in future or running
|
||||
$user_may_join_shift &= time() < $shift['start'];
|
||||
|
||||
// User shift admins may join anybody in every shift
|
||||
$user_may_join_shift |= in_array('user_shifts_admin', $privileges);
|
||||
if ($user_may_join_shift) {
|
||||
$entry_list[] = '<a href="' . page_link_to('user_shifts') . '&shift_id=' . $shift['SID'] . '&type_id=' . $angeltype['id'] . '">' . $inner_text . '</a> ' . button(page_link_to('user_shifts') . '&shift_id=' . $shift['SID'] . '&type_id=' . $angeltype['id'], _('Sign up'), 'btn-xs btn-primary');
|
||||
} else {
|
||||
if (time() > $shift['start']) {
|
||||
$entry_list[] = $inner_text . ' (' . _('ended') . ')';
|
||||
} elseif ($angeltype['restricted'] == 1 && isset($angeltype['user_id']) && ! isset($angeltype['confirm_user_id'])) {
|
||||
$entry_list[] = $inner_text . glyph('lock');
|
||||
} elseif ($angeltype['restricted'] == 1) {
|
||||
$entry_list[] = $inner_text;
|
||||
} elseif ($collides) {
|
||||
$entry_list[] = $inner_text;
|
||||
} else {
|
||||
$entry_list[] = $inner_text . '<br />' . button(page_link_to('user_angeltypes') . '&action=add&angeltype_id=' . $angeltype['id'], sprintf(_('Become %s'), $angeltype['name']), 'btn-xs');
|
||||
}
|
||||
}
|
||||
|
||||
unset($inner_text);
|
||||
$is_free = true;
|
||||
}
|
||||
|
||||
$shifts_row = '<li class="list-group-item">';
|
||||
$shifts_row .= '<strong>' . AngelType_name_render($angeltype) . ':</strong> ';
|
||||
$shifts_row .= join(", ", $entry_list);
|
||||
$shifts_row .= '</li>';
|
||||
return [
|
||||
$is_free,
|
||||
$shifts_row
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the shift header
|
||||
*
|
||||
* @param Shift $shift
|
||||
* The shift
|
||||
*/
|
||||
private function renderShiftHead($shift) {
|
||||
global $privileges;
|
||||
|
||||
$header_buttons = "";
|
||||
if (in_array('admin_shifts', $privileges)) {
|
||||
$header_buttons = '<div class="pull-right">' . table_buttons([
|
||||
button(page_link_to('user_shifts') . '&edit_shift=' . $shift['SID'], glyph('edit'), 'btn-xs'),
|
||||
button(page_link_to('user_shifts') . '&delete_shift=' . $shift['SID'], glyph('trash'), 'btn-xs')
|
||||
]) . '</div>';
|
||||
}
|
||||
$shift_heading = date('H:i', $shift['start']) . ' ‐ ' . date('H:i', $shift['end']) . ' — ' . ShiftType($shift['shifttype_id'])['name'];
|
||||
return div('panel-heading', [
|
||||
'<a href="' . shift_link($shift) . '">' . $shift_heading . '</a>',
|
||||
$header_buttons
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the shift collide with the user's shifts
|
||||
*/
|
||||
private function collides() {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Engelsystem;
|
||||
|
||||
class ShiftsFilterRenderer {
|
||||
|
||||
/**
|
||||
* The shiftFilter to render.
|
||||
*
|
||||
* @var ShiftsFilter
|
||||
*/
|
||||
private $shiftsFilter;
|
||||
|
||||
/**
|
||||
* Should the filter display a day selection.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $daySelectionEnabled = false;
|
||||
|
||||
/**
|
||||
* Days that can be selected.
|
||||
* Format Y-m-d
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $days = [];
|
||||
|
||||
public function __construct(ShiftsFilter $shiftsFilter) {
|
||||
$this->shiftsFilter = $shiftsFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the filter.
|
||||
*
|
||||
* @return Generated HTML
|
||||
*/
|
||||
public function render($link_base) {
|
||||
$toolbar = [];
|
||||
if ($this->daySelectionEnabled && ! empty($this->days)) {
|
||||
$selected_day = date("Y-m-d", $this->shiftsFilter->getStartTime());
|
||||
$day_dropdown_items = [];
|
||||
foreach ($this->days as $day) {
|
||||
$day_dropdown_items[] = toolbar_item_link($link_base . '&shifts_filter_day=' . $day, '', $day);
|
||||
}
|
||||
$toolbar[] = toolbar_dropdown('', $selected_day, $day_dropdown_items, 'active');
|
||||
}
|
||||
return div('form-group', [
|
||||
toolbar_pills($toolbar)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the filter display a day selection.
|
||||
*/
|
||||
public function enableDaySelection($days) {
|
||||
$this->daySelectionEnabled = true;
|
||||
$this->days = $days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the filter display a day selection.
|
||||
*/
|
||||
public function isDaySelectionEnabled() {
|
||||
return $this->daySelectionEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,24 @@
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
bootstrap="./includes/engelsystem_provider.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false">
|
||||
<testsuites>
|
||||
<testsuite name="Models">
|
||||
<directory>./test/model/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./include/</directory>
|
||||
<directory>./public/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<php>
|
||||
<const name="PHPUNIT_TESTSUITE" value="true" />
|
||||
</php>
|
||||
</phpunit>
|
@ -1,14 +0,0 @@
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
|
||||
bootstrap="../includes/engelsystem_provider.php" colors="true"
|
||||
convertErrorsToExceptions="true" convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true" forceCoversAnnotation="false">
|
||||
<testsuites>
|
||||
<testsuite name="Models">
|
||||
<directory>model/*</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<php>
|
||||
<const name="PHPUNIT_TESTSUITE" value="true" />
|
||||
</php>
|
||||
</phpunit>
|
Loading…
Reference in New Issue