Select Git revision
bootstrap-api.php
Forked from
Stud.IP / Stud.IP
Source project has a limited visibility.
-
Jan-Hendrik Willms authoredJan-Hendrik Willms authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ResourceRequest.php 85.05 KiB
<?php
/**
* ResourceRequest.php - Contains a model class for resource requests.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* @author Moritz Strohm <strohm@data-quest.de>
* @copyright 2017-2019
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
* @category Stud.IP
* @package resources
* @since 4.5
*
* The attributes begin and end are only used in simple resource requests.
* The "traditional" resource requests use either course_id, metadate_id
* or termin_id to store the time ranges connected to the request.
*
* @property string $id database column
* @property string $course_id database column
* @property string $termin_id database column
* @property string $metadate_id database column
* @property string $user_id database column
* @property string $last_modified_by database column
* @property string $resource_id database column
* @property string|null $category_id database column
* @property string|null $comment database column
* @property string|null $reply_comment database column
* @property string $reply_recipients database column
* @property int $closed database column
* @property int|null $mkdate database column
* @property int|null $chdate database column
* @property int $begin database column
* @property int $end database column
* @property int $preparation_time database column
* @property int $marked database column
* @property SimpleORMapCollection|ResourceRequestProperty[] $properties has_many ResourceRequestProperty
* @property SimpleORMapCollection|ResourceRequestAppointment[] $appointments has_many ResourceRequestAppointment
* @property Resource $resource belongs_to Resource
* @property ResourceCategory|null $category belongs_to ResourceCategory
* @property User $user belongs_to User
* @property User $last_modifier belongs_to User
* @property Course $course belongs_to Course
* @property SeminarCycleDate $cycle belongs_to SeminarCycleDate
* @property CourseDate $date belongs_to CourseDate
*/
class ResourceRequest extends SimpleORMap implements PrivacyObject, Studip\Calendar\EventSource
{
const MARK_NONE = 0;
const MARK_RED = 1;
const MARK_YELLOW = 2;
const MARK_GREEN = 3;
const REPLY_REQUESTER = 'requester';
const REPLY_LECTURER = 'lecturer';
const STATE_OPEN = 0; // room-request is open
const STATE_PENDING = 1; // room-request has been processed, but no confirmation has been sent
const STATE_CLOSED = 2; // room-request has been processed and a confirmation has been sent
const STATE_DECLINED = 3; // room-request has been declined
/**
* The amount of defined marking states.
*/
const MARKING_STATES = 4;
protected static function configure($config = [])
{
$config['db_table'] = 'resource_requests';
$config['belongs_to']['resource'] = [
'class_name' => Resource::class,
'foreign_key' => 'resource_id',
'assoc_func' => 'find'
];
$config['belongs_to']['category'] = [
'class_name' => ResourceCategory::class,
'foreign_key' => 'category_id',
'assoc_func' => 'find'
];
$config['belongs_to']['user'] = [
'class_name' => User::class,
'foreign_key' => 'user_id',
'assoc_func' => 'find'
];
$config['belongs_to']['last_modifier'] = [
'class_name' => User::class,
'foreign_key' => 'last_modified_by',
'assoc_func' => 'find'
];
$config['belongs_to']['course'] = [
'class_name' => Course::class,
'foreign_key' => 'course_id',
'assoc_func' => 'find'
];
$config['belongs_to']['cycle'] = [
'class_name' => SeminarCycleDate::class,
'foreign_key' => 'metadate_id'
];
$config['belongs_to']['date'] = [
'class_name' => CourseDate::class,
'foreign_key' => 'termin_id'
];
$config['has_many']['properties'] = [
'class_name' => ResourceRequestProperty::class,
'foreign_key' => 'id',
'assoc_foreign_key' => 'request_id',
'on_store' => 'store',
'on_delete' => 'delete'
];
$config['has_many']['appointments'] = [
'class_name' => ResourceRequestAppointment::class,
'foreign_key' => 'id',
'assoc_foreign_key' => 'request_id',
'on_store' => 'store',
'on_delete' => 'delete'
];
//In regard to TIC 6460:
//As long as TIC 6460 is not implemented, we must add the validate
//method as a callback before storing the object.
if (!method_exists('SimpleORMap', 'validate')) {
$config['registered_callbacks']['before_store'][] = 'validate';
}
$config['registered_callbacks']['after_create'][] = 'cbLogNewRequest';
$config['registered_callbacks']['before_store'][] = 'cbBeforeStore';
$config['registered_callbacks']['after_store'][] = 'cbAfterStore';
$config['registered_callbacks']['after_delete'][] = 'cbAfterDelete';
parent::configure($config);
}
/**
* @inheritDoc
*/
public static function exportUserdata(StoredUserData $storage)
{
$user = User::find($storage->user_id);
$requests = self::findBySql(
'user_id = :user_id ORDER BY mkdate',
[
'user_id' => $storage->user_id
]
);
$request_rows = [];
foreach ($requests as $request) {
$request_rows[] = $request->toRawArray();
}
$storage->addTabularData(
_('Ressourcenanfragen'),
'resource_requests',
$request_rows,
$user
);
}
/**
* Retrieves all resource requests from the database.
*
* @return ResourceRequest[] An array of ResourceRequests objects
* or an empty array, if no resource requests are stored
* in the database.
*/
public static function findAll()
{
return self::findBySql('TRUE ORDER BY mkdate ASC');
}
/**
* Retrieves all open resource requests from the database.
*
* @return ResourceRequest[] An array of ResourceRequests objects
* or an empty array, if no open resource requests are stored
* in the database.
*/
public static function findOpen()
{
return self::findBySql(
'closed = ? ORDER BY mkdate ASC',
[self::STATE_OPEN]
);
}
/**
* Internal method that generated the SQL query used in
* findByResourceAndTimeRanges and countByResourceAndTimeRanges.
*
* @see findByResourceAndTimeRanges
* @inheritDoc
*/
protected static function buildResourceAndTimeRangesSqlQuery(
Resource $resource,
$time_ranges = [],
$closed_status = null,
$excluded_request_ids = [],
$additional_conditions = '',
$additional_parameters = []
)
{
if (!is_array($time_ranges)) {
throw new InvalidArgumentException(
_('Es wurde keine Liste mit Zeiträumen angegeben!')
);
}
//Check the array:
foreach ($time_ranges as $time_range) {
if ($time_range['begin'] > $time_range['end']) {
throw new InvalidArgumentException(
_('Der Startzeitpunkt darf nicht hinter dem Endzeitpunkt liegen!')
);
}
if ($time_range['begin'] == $time_range['end']) {
throw new InvalidArgumentException(
_('Startzeitpunkt und Endzeitpunkt dürfen nicht identisch sein!')
);
}
}
$sql_params = [
'resource_id' => $resource->id
];
//First we build the SQL snippet for the case that the $closed_status
//variable is set to something different than null.
$closed_status_sql = '';
if ($closed_status !== null) {
$closed_status_sql = ' AND (resource_requests.closed = :status) ';
$sql_params['status'] = strval($closed_status);
}
//Then we build the snipped for excluded request IDs, if specified.
$excluded_request_ids_sql = '';
if (is_array($excluded_request_ids) && count($excluded_request_ids)) {
$excluded_request_ids_sql = ' AND resource_requests.id NOT IN ( :excluded_ids ) ';
$sql_params['excluded_ids'] = $excluded_request_ids;
}
//Now we build the SQL snippet for the time intervals.
//These are repeated four times in the query below.
//BEGIN and END are replaced below since the columns for
//BEGIN and END are different in the four cases where we
//repeat the SQL snippet for the time intervals.
$time_sql = '';
if ($time_ranges) {
$time_sql = 'AND (';
$i = 1;
foreach ($time_ranges as $time_range) {
if ($i > 1) {
$time_sql .= ' OR ';
}
$time_sql .= sprintf('BEGIN < :end%d AND END > :begin%d ', $i, $i);
$sql_params[('begin' . $i)] = $time_range['begin'];
$sql_params[('end' . $i)] = $time_range['end'];
$i++;
}
$time_sql .= ') ';
}
//Check if the request has a start and end timestamp set or if it belongs
//to a date, a metadate or a course.
//This is done in the rest of the SQL query:
// FIXME this subselect looks unnecessarily complex
$whole_sql = '
SELECT id FROM resource_requests
WHERE
resource_id = :resource_id
'
. str_replace(
['BEGIN', 'END'],
['(CAST(begin AS SIGNED) - preparation_time)', 'end'],
$time_sql
)
. $closed_status_sql
. '
UNION
SELECT id FROM resource_requests
INNER JOIN termine USING (termin_id)
WHERE
resource_id = :resource_id
'
. str_replace(
['BEGIN', 'END'],
[
'(CAST(termine.date AS SIGNED) - resource_requests.preparation_time)',
'termine.end_time'
],
$time_sql
)
. $closed_status_sql
. '
UNION
SELECT id FROM resource_requests
INNER JOIN termine USING (metadate_id)
WHERE
resource_id = :resource_id
'
. str_replace(
['BEGIN', 'END'],
[
'(CAST(termine.date AS SIGNED) - resource_requests.preparation_time)',
'termine.end_time'
],
$time_sql
)
. $closed_status_sql
. '
UNION
SELECT id FROM resource_requests
INNER JOIN termine
ON resource_requests.course_id = termine.range_id
WHERE
resource_id = :resource_id
'
. str_replace(
['BEGIN', 'END'],
[
'(CAST(termine.date AS SIGNED) - resource_requests.preparation_time)',
'termine.end_time'
],
$time_sql
)
. $closed_status_sql
. '
GROUP BY id
'
. $excluded_request_ids_sql;
$request_ids = DBManager::get()->fetchFirst($whole_sql, $sql_params);
$whole_sql = "resource_requests.id IN(:request_ids)";
$sql_params = ['request_ids' => $request_ids];
if ($additional_conditions) {
$whole_sql .= ' AND ' . $additional_conditions;
if ($additional_parameters) {
$sql_params = array_merge($sql_params, $additional_parameters);
}
}
$whole_sql .= ' ORDER BY mkdate ASC';
return [
'sql' => $whole_sql,
'params' => $sql_params
];
}
/**
* Retrieves all resource requests for the given resource and
* time range. By default, all requests are returned.
* To get only open or closed requests set the $closed_status parameter.
*
* @param Resource $resource The resource whose requests shall be retrieved.
* @param array $time_ranges An array with time ranges as DateTime objects.
* The array has the following structure:
* [
* [
* 'begin' => begin timestamp,
* 'end' => end timestamp
* ],
* ...
* ]
* @param mixed $closed_status An optional status for the closed column in the
* database. By default this is set to null which means that
* resource requests are not filtered by the status column field.
* A value of 0 means only open requests are retrived.
* A value of 1 means only closed requests are retrieved.
*
* @param array $excluded_request_ids An array of strings representing
* resource request IDs. IDs specified in this array are excluded from
* the search.
* @return ResourceRequest[] An array of ResourceRequest objects.
* If no requests can be found, the array is empty.
*
* @throws InvalidArgumentException, if the time ranges are either not in an
* array matching the format description from above or if one of the
* following conditions is met in one of the time ranges:
* - begin > end
* - begin == end
*/
public static function findByResourceAndTimeRanges(
Resource $resource,
$time_ranges = [],
$closed_status = null,
$excluded_request_ids = [],
$additional_conditions = '',
$additional_parameters = []
)
{
//Build the SQL query and the parameter array.
$sql_data = self::buildResourceAndTimeRangesSqlQuery(
$resource,
$time_ranges,
$closed_status,
$excluded_request_ids,
$additional_conditions,
$additional_parameters
);
//Call findBySql:
return self::findBySql($sql_data['sql'], $sql_data['params']);
}
public static function countByResourceAndTimeRanges(
Resource $resource,
$time_ranges = [],
$closed_status = null,
$excluded_request_ids = [],
$additional_conditions = '',
$additional_parameters = []
)
{
$sql_data = self::buildResourceAndTimeRangesSqlQuery(
$resource,
$time_ranges,
$closed_status,
$excluded_request_ids,
$additional_conditions,
$additional_parameters
);
return self::countBySql($sql_data['sql'], $sql_data['params']);
}
public static function findByCourse($course_id)
{
return self::findOneBySql(
"termin_id = '' AND metadate_id = '' AND course_id = :course_id",
[
'course_id' => $course_id
]
);
}
public static function findByDate($date_id)
{
return self::findOneBySql(
'termin_id = :date_id',
[
'date_id' => $date_id
]
);
}
public static function findByMetadate($metadate_id)
{
return self::findOneBySql(
'metadate_id = :metadate_id',
[
'metadate_id' => $metadate_id
]
);
}
public static function existsByCourse($course_id, $request_is_open = false)
{
$parameters = [':course_id' => $course_id];
$sql = '';
if ($request_is_open) {
$sql .= "closed = :closed_state AND ";
$parameters[':closed_state'] = self::STATE_OPEN;
}
$request = self::findOneBySql(
$sql . "termin_id = '' AND metadate_id = '' AND course_id = :course_id",
$parameters
);
if ($request) {
return $request->id;
} else {
return false;
}
}
public static function existsByDate($date_id, $request_is_open = false)
{
$parameters = [':date_id' => $date_id];
$sql = '';
if ($request_is_open) {
$sql .= "closed = :closed_state AND ";
$parameters[':closed_state'] = self::STATE_OPEN;
}
$request = self::findOneBySql(
$sql . "termin_id = :date_id",
$parameters
);
if ($request) {
return $request->id;
} else {
return false;
}
}
public static function existsByMetadate($metadate_id, $request_is_open = false)
{
$parameters = [':metadate_id' => $metadate_id];
$sql = '';
if ($request_is_open) {
$sql .= "closed = :closed_state AND ";
$parameters[':closed_state'] = self::STATE_OPEN;
}
$request = self::findOneBySql(
$sql . "metadate_id = :metadate_id",
$parameters
);
if ($request) {
return $request->id;
} else {
return false;
}
}
/**
* A callback method that creates a Stud.IP log entry
* when a new request has been made.
*/
public function cbLogNewRequest()
{
$this->sendNewRequestMail();
StudipLog::log('RES_REQUEST_NEW', $this->course_id, $this->resource_id, $this->getLoggingInfoText());
}
/**
* A callback method that sets the users connection before store.
*/
public function cbBeforeStore()
{
if ($this->isNew() && !$this->user_id) {
$this->user_id = User::findCurrent()->id;
}
$this->last_modified_by = User::findCurrent()->id;
}
/**
* A callback method that send a mail
* when a new request has been udpated.
*/
public function cbAfterStore()
{
if ($this->isFieldDirty('closed')) {
if ($this->closed == self::STATE_DECLINED) {
$this->sendRequestDeniedMail();
StudipLog::log('RES_REQUEST_DENY', $this->course_id, $this->resource_id, $this->getLoggingInfoText());
} elseif ($this->closed == self::STATE_PENDING || $this->closed == self::STATE_CLOSED) {
StudipLog::log('RES_REQUEST_RESOLVE', $this->course_id, $this->resource_id, $this->getLoggingInfoText());
}
} else {
StudipLog::log('RES_REQUEST_UPDATE', $this->course_id, $this->resource_id, $this->getLoggingInfoText());
}
}
public function cbAfterDelete()
{
StudipLog::log('RES_REQUEST_DEL', $this->course_id, $this->resource_id, $this->getLoggingInfoText());
}
/**
* This validation method is called before storing an object.
*/
public function validate()
{
if (!$this->resource_id && !$this->category_id) {
throw new Exception(
_('Eine Anfrage muss einer konkreten Ressource oder deren Kategorie zugewiesen sein!')
);
}
}
public function getDerivedClassInstance()
{
if (!$this->resource) {
//We cannot determine a derived class.
return $this;
}
$class_name = $this->resource->class_name;
if ($class_name === 'Resource') {
//This is already the correct class.
return $this;
}
if (is_subclass_of($class_name, 'Resource')) {
//Now we append 'Request' to the class name:
$class_name = $class_name . 'Request';
return $class_name::buildExisting(
$this->toRawArray()
);
} else {
//$class_name does not contain the name of a subclass
//of Resource. That's an error!
throw new NoResourceClassException(
sprintf(
_('Die Klasse %1$s ist keine Spezialisierung der Ressourcen-Kernklasse!'),
$class_name
)
);
}
}
/**
* Sets the range fields (termin_id, metadate_id, course_id)
* or the ResourceRequestAppointment objects related to this request
* according to the range type and its range-IDs specified as parameters
* for this method. The ResourceRequest object is not stored after
* setting the fields / related objects.
*
* @param string $range_type The range type for this request. One of
* the following: 'date', 'cycle', 'course' or 'date-multiple'.
*
* @param array $range_ids An array of range-IDs to be set for the
* specified range type. This is mostly an array of size one
* since the fields termin_id, metadate_id and course_id only
* accept one ID. The range type 'date-multiple' accepts multiple
* IDs.
*
* @return void No return value.
*/
public function setRangeFields($range_type = '', $range_ids = [])
{
if ($range_type === 'date') {
$this->termin_id = $range_ids[0];
$this->metadate_id = '';
} elseif ($range_type === 'cycle') {
$this->termin_id = '';
$this->metadate_id = $range_ids[0];
} elseif ($range_type === 'date-multiple') {
$this->termin_id = '';
$this->metadate_id = '';
$appointments = [];
foreach ($range_ids as $range_id) {
$app = new ResourceRequestAppointment();
$app->appointment_id = $range_id;
$appointments[] = $app;
}
$this->appointments = $appointments;
} elseif ($range_type === 'course') {
$this->termin_id = '';
$this->metadate_id = '';
$this->course_id = $range_ids[0];
}
}
/**
* Closes the requests and sends out notification mails.
* If the request is closed and a resource has been booked,
* it can be passed as parameter to be included in the notification mails.
*
* @param bool $notify_lecturers Whether to notify lecturers of a course
* (true) or not (false). Defaults to false. Note that this parameter
* is only useful in case the request is bound to a course, either
* directly or via a course date or a course cycle date.
*
* @param ResourceBooking $bookings The resource bookings that have been
* created from this request.
* @return bool @TODO
*/
public function closeRequest($notify_lecturers = false, $bookings = [])
{
if (
$this->closed == self::STATE_CLOSED
|| $this->closed == self::STATE_DECLINED
) {
//The request has already been closed.
return true;
}
$this->closed = self::STATE_PENDING;
if ($this->isDirty()) {
$this->store();
}
//Now we send the confirmation mail to the requester:
$this->sendCloseRequestMailToRequester($bookings);
if ($notify_lecturers) {
$this->sendCloseRequestMailToLecturers($bookings);
}
//Sending successful: The request is closed.
$this->closed = self::STATE_CLOSED;
if ($this->isDirty()) {
return $this->store();
}
return true;
}
/**
* Returns the resource requests whose time ranges overlap
* with those of this resource request.
*
* @return ResourceRequest[] An array of ResourceRequest objects.
*/
public function getOverlappingRequests()
{
if ($this->resource) {
return self::findByResourceAndTimeRanges(
$this->resource,
$this->getTimeIntervals(true),
self::STATE_OPEN,
[$this->id]
);
}
return [];
}
/**
* Counts the resource requests whose time ranges overlap
* with those of this resource request.
*
* @return int The amount of overlapping resource requests.
*/
public function countOverlappingRequests()
{
if ($this->resource) {
return self::countByResourceAndTimeRanges(
$this->resource,
$this->getTimeIntervals(true),
self::STATE_OPEN,
[$this->id]
);
}
return 0;
}
/**
* Returns the resource bookings whose time ranges overlap
* with those of this resource request.
*
* @return ResourceBooking[] An array of ResourceBooking objects.
*/
public function getOverlappingBookings()
{
if ($this->resource) {
return ResourceBooking::findByResourceAndTimeRanges(
$this->resource,
$this->getTimeIntervals(true),
[ResourceBooking::TYPE_NORMAL, ResourceBooking::TYPE_LOCK]
);
}
return [];
}
/**
* Counts the resource bookings whose time ranges overlap
* with those of this resource request.
*
* @return int The amount of overlapping resource bookings.
*/
public function countOverlappingBookings()
{
if ($this->resource) {
return ResourceBooking::countByResourceAndTimeRanges(
$this->resource,
$this->getTimeIntervals(true),
[ResourceBooking::TYPE_NORMAL, ResourceBooking::TYPE_LOCK]
);
}
return 0;
}
/**
* Returns the repetion interval if regular appointments are used
* for this request.
*
* @return DateInterval|null In case regular appointments are used
* for this request a DateInterval is returned.
* Otherwise null is returned.
*/
public function getRepetitionInterval()
{
if ($this->metadate_id) {
//It is a set of regular appointments.
//We just have to compute the time difference between the first
//two appointments to get the interval.
$first_date = $this->cycle->dates[0];
$second_date = $this->cycle->dates[1];
if (!$first_date || !$second_date) {
//Either only one date is in the set of regular appointments
//or there is a database error. We cannot continue.
return null;
}
$first_datetime = new DateTime();
$first_datetime->setTimestamp($first_date->date);
$second_datetime = new DateTime();
$second_datetime->setTimestamp($second_date->date);
return $first_datetime->diff($second_datetime);
}
return null;
}
public function getStartDate()
{
$start_date = new DateTime();
if (count($this->appointments) > 0) {
$start_date->setTimestamp($this->appointments->first()->appointment->date);
return $start_date;
}
if ($this->termin_id) {
$start_date->setTimestamp($this->date->date);
return $start_date;
}
if (isset($this->cycle) && count($this->cycle->dates) > 0) {
$first_date = $this->cycle->dates->first();
if ($this->metadate_id && isset($first_date->date)) {
$start_date->setTimestamp($first_date->date);
return $start_date;
}
if ($this->course_id && isset($first_date->date)) {
$start_date->setTimestamp($first_date->date);
return $start_date;
}
}
if ($this->begin) {
$start_date->setTimestamp($this->begin);
return $start_date;
}
return null;
}
public function getEndDate()
{
$end_date = new DateTime();
if (count($this->appointments) > 0) {
$end_date->setTimestamp($this->appointments->last()->appointment->end_time);
return $end_date;
}
if ($this->termin_id) {
$end_date->setTimestamp($this->date->end_time);
return $end_date;
}
if ($this->metadate_id) {
$date = $this->cycle->dates->last();
if (!isset($date)) {
return null;
}
$end_date->setTimestamp($this->cycle->dates->last()->end_time);
return $end_date;
}
if ($this->course_id) {
$date = $this->course->dates->last();
if (!isset($date)) {
return null;
}
$end_date->setTimestamp($this->course->dates->last()->end_time);
return $end_date;
}
if ($this->end) {
$end_date->setTimestamp($this->end);
return $end_date;
}
return null;
}
public function getStartSemester()
{
$start_date = $this->getStartDate();
if ($start_date instanceof DateTime) {
return Semester::findByTimestamp($start_date->getTimestamp());
}
return null;
}
public function getEndSemester()
{
$end_date = $this->getEndDate();
if ($end_date instanceof DateTime) {
return Semester::findByTimestamp($end_date->getTimestamp());
}
return null;
}
public function getRepetitionEndDate()
{
$repetition_interval = $this->getRepetitionInterval();
if (!$repetition_interval) {
//There is no repetition.
return null;
}
return $this->getEndDate();
}
/**
* Retrieves the time intervals by looking at metadate objects
* and other time interval sources and returns them grouped by metadate.
* @param bool $with_preparation_time @TODO
* @return mixed[][][] A three-dimensional array with
* the following structure:
* - The first dimension has the metadate-id as index. For single dates
* an empty string is used as index.
* - The second dimension contains two elements:
* - 'metadate' => The metadate object. This is only set, if the
* request is for a metadate.
* - 'intervals' => The time intervals.
* - The third dimension contains a time interval
* in the following format:
* [
* 'begin' => The begin timestamp
* 'end' => The end timestamp
* 'range' => The name of the range class that provides the range_id.
* This is usually the name of the SORM class.
* 'range_id' => The ID of the single date or ResourceRequestAppointment.
* ]
*/
public function getGroupedTimeIntervals($with_preparation_time = false, $with_past_intervals = true)
{
$now = time();
if (count($this->appointments)) {
$time_intervals = [
'' => [
'metadate' => null,
'intervals' => []
]
];
foreach ($this->appointments as $appointment) {
if (!$with_past_intervals && $appointment->appointment->end_time < $now) {
continue;
}
if ($with_preparation_time) {
$interval = [
'begin' => $appointment->appointment->date - $this->preparation_time,
'end' => $appointment->appointment->end_time
];
} else {
$interval = [
'begin' => $appointment->appointment->date,
'end' => $appointment->appointment->end_time
];
}
$date = CourseDate::find($appointment->appointment_id);
$interval['range'] = 'CourseDate';
$interval['range_id'] = $appointment->appointment_id;
$interval['booked_room'] = $date->room_booking->resource_id ?? '';
$interval['booking_id'] = $date->room_booking->id ?? '';
$time_intervals['']['intervals'][] = $interval;
}
if (empty($time_intervals['']['intervals'])) {
return [];
} else {
return $time_intervals;
}
} elseif ($this->termin_id) {
if (!$with_past_intervals && $this->date->end_time < $now) {
return [];
}
if ($with_preparation_time) {
$interval = [
'begin' => $this->date->date - $this->preparation_time,
'end' => $this->date->end_time
];
} else {
$interval = [
'begin' => $this->date->date,
'end' => $this->date->end_time
];
}
$date = CourseDate::find($this->termin_id);
$interval['range'] = 'CourseDate';
$interval['range_id'] = $this->termin_id;
$interval['booked_room'] = $date->room_booking->resource_id ?? null;
$interval['booking_id'] = $date->room_booking->id ?? null;
if (!empty($interval)) {
return [
'' => [
'metadate' => null,
'intervals' => [$interval]
]
];
} else {
return [];
}
} elseif ($this->metadate_id) {
$time_intervals = [
$this->metadate_id => [
'metadate' => $this->cycle,
'intervals' => []
]
];
foreach ($this->cycle->dates as $date) {
if (!$with_past_intervals && $date->end_time < $now) {
continue;
}
if ($with_preparation_time) {
$interval = [
'begin' => $date->date - $this->preparation_time,
'end' => $date->end_time
];
} else {
$interval = [
'begin' => $date->date,
'end' => $date->end_time
];
}
$interval['range'] = 'CourseDate';
$interval['range_id'] = $date->id;
$interval['booked_room'] = $date->room_booking->resource_id ?? null;
$interval['booking_id'] = $date->room_booking->id ?? null;
$time_intervals[$this->metadate_id]['intervals'][] = $interval;
}
return $time_intervals;
} elseif ($this->course_id) {
$time_intervals = [];
if ($this->course->cycles) {
foreach ($this->course->cycles as $cycle) {
$time_intervals[$cycle->id] = [
'metadate' => $cycle,
'intervals' => []
];
if ($cycle->dates) {
foreach ($cycle->dates as $date) {
if (!$with_past_intervals && $date->end_time < $now) {
continue;
}
if ($with_preparation_time) {
$interval = [
'begin' => $date->date - $this->preparation_time,
'end' => $date->end_time
];
} else {
$interval = [
'begin' => $date->date,
'end' => $date->end_time
];
}
$interval['range'] = CourseDate::class;
$interval['range_id'] = $date->id;
$interval['booked_room'] = $date->room_booking->resource_id ?? null;
$interval['booking_id'] = $date->room_booking->id ?? null;
$time_intervals[$cycle->id]['intervals'][] = $interval;
}
}
}
}
if ($this->course->dates) {
$time_intervals[''] = [
'metadate' => null,
'intervals' => []
];
foreach ($this->course->dates as $date) {
if (!$with_past_intervals && $date->end_time < $now) {
continue;
}
if ($date->cycle instanceof SeminarCycleDate) {
//Metadates are already handled above.
continue;
}
if ($with_preparation_time) {
$interval = [
'begin' => $date->date - $this->preparation_time,
'end' => $date->end_time
];
} else {
$interval = [
'begin' => $date->date,
'end' => $date->end_time
];
}
$interval['range'] = 'CourseDate';
$interval['range_id'] = $date->id;
$interval['booked_room'] = $date->room_booking->resource_id ?? null;
$interval['booking_id'] = $date->room_booking->id ?? null;
$time_intervals['']['intervals'][] = $interval;
}
if (empty($time_intervals['']['intervals'])) {
unset($time_intervals['']);
}
}
return $time_intervals;
} elseif ($this->begin && $this->end) {
if (!$with_past_intervals && $this->end < $now) {
return [];
}
if ($with_preparation_time) {
$interval = [
'begin' => $this->begin - $this->preparation_time,
'end' => $this->end
];
} else {
$interval = [
'begin' => $this->begin,
'end' => $this->end
];
}
$interval['range'] = 'User';
$interval['range_id'] = $this->user_id;
return [
'' => [
'metadate' => null,
'intervals' => [$interval]
]
];
} else {
return [];
}
}
/**
* Retrieves the time intervals for this request.
*
* @param bool $with_preparation_time Whether the preparation time
* of the request shall be prepended to the begin timestamp (true)
* or whether it should not be included at all (false).
* Defaults to false.
*
* @param bool $with_range Whether to include data of the Stud.IP range
* and its corresponding ID to the request (true) or not (false).
* Defaults to false.
*
* @param bool $with_past_intervals Whether to include past intervals (true)
* or only include intervals from the current time and the future (false).
* Defaults to true.
*
* @return string[][] A two-dimensional array of unix timestamps.
* The first dimension contains one entry for each date,
* the second dimension contains the start and end timestamp
* for the date.
* The second dimension uses the array keys 'begin' and 'end'
* for start and end date.
* If the @with_range parameter is set to true, the second array
* dimension also contains the key 'range' for specifying the
* range type and 'range_id' for specifying the ID of the
* range object.
* The range can be "CourseDate", "ResourceRequestAppointment"
* or "User". The last two can only be present for simple requests
* that are not bound to a course. The range "CourseDate"
* can only occur on course-bound requests.
*/
public function getTimeIntervals($with_preparation_time = false, $with_range = false, $with_past_intervals = true)
{
$now = time();
if (count($this->appointments)) {
$time_intervals = [];
foreach ($this->appointments as $appointment) {
if (!$with_past_intervals && $appointment->appointment->end_time < $now) {
continue;
}
if ($with_preparation_time) {
$interval = [
'begin' => $appointment->appointment->date - $this->preparation_time,
'end' => $appointment->appointment->end_time
];
} else {
$interval = [
'begin' => $appointment->appointment->date,
'end' => $appointment->appointment->end_time
];
}
if ($with_range) {
$date = CourseDate::find($appointment->appointment_id);
$interval['range'] = ResourceRequestAppointment::class;
$interval['range_id'] = $appointment->appointment_id;
$interval['booked_room'] = $date->room_booking->resource_id ?? null;
$interval['booking_id'] = $date->room_booking->id ?? null;
}
$time_intervals[] = $interval;
}
return $time_intervals;
} elseif ($this->termin_id) {
if (!$with_past_intervals && $this->date->end_time < $now) {
return [];
}
if ($with_preparation_time) {
$interval = [
'begin' => $this->date->date - $this->preparation_time,
'end' => $this->date->end_time
];
} else {
$interval = [
'begin' => $this->date->date,
'end' => $this->date->end_time
];
}
if ($with_range) {
$interval['range'] = CourseDate::class;
$interval['range_id'] = $this->termin_id;
$interval['booked_room'] = $this->date->room_booking->resource_id ?? null;
$interval['booking_id'] = $this->date->room_booking->id ?? null;
}
return [$interval];
} elseif ($this->metadate_id) {
$time_intervals = [];
foreach ($this->cycle->dates as $date) {
if (!$with_past_intervals && $date->end_time < $now) {
continue;
}
if ($with_preparation_time) {
$interval = [
'begin' => $date->date - $this->preparation_time,
'end' => $date->end_time
];
} else {
$interval = [
'begin' => $date->date,
'end' => $date->end_time
];
}
if ($with_range) {
$interval['range'] = CourseDate::class;
$interval['range_id'] = $date->id;
$interval['booked_room'] = $date->room_booking->resource_id ?? null;
$interval['booking_id'] = $date->room_booking->id ?? null;
}
$time_intervals[] = $interval;
}
return $time_intervals;
} elseif ($this->course_id) {
$time_intervals = [];
if ($this->course->dates) {
foreach ($this->course->dates as $date) {
if (!$with_past_intervals && $date->end_time < $now) {
continue;
}
if ($with_preparation_time) {
$interval = [
'begin' => $date->date - $this->preparation_time,
'end' => $date->end_time
];
} else {
$interval = [
'begin' => $date->date,
'end' => $date->end_time
];
}
if ($with_range) {
$interval['range'] = CourseDate::class;
$interval['range_id'] = $date->id;
$interval['booked_room'] = $date->room_booking->resource_id ?? null;
$interval['booking_id'] = $date->room_booking->id ?? null;
}
$time_intervals[] = $interval;
}
}
return $time_intervals;
} elseif ($this->begin && $this->end) {
if (!$with_past_intervals && $this->end < $now) {
return [];
}
if ($with_preparation_time) {
$interval = [
'begin' => $this->begin - $this->preparation_time,
'end' => $this->end
];
} else {
$interval = [
'begin' => $this->begin,
'end' => $this->end
];
}
if ($with_range) {
$interval['range'] = 'User';
$interval['range_id'] = $this->user_id;
}
return [$interval];
} else {
return [];
}
}
/**
* Returns a string representation of the time intervals for this request.
*/
public function getTimeIntervalStrings()
{
$strings = [];
$intervals = $this->getTimeIntervals(false, true);
foreach ($intervals as $interval) {
$room = '';
if ($interval['range'] === 'CourseDate') {
$date = call_user_func([$interval['range'], 'find'], $interval['range_id']);
if ($date->room_booking) {
$room_obj = Room::find($date->room_booking->resource_id);
if ($room_obj) {
$room = $room_obj->name;
}
}
}
$same_day = date('Ymd', $interval['begin']) === date('Ymd', $interval['end']);
if ($same_day) {
$strings[] = strftime('%a. %x %R', $interval['begin'])
. ' - ' . strftime('%R', $interval['end'])
. ($room ? ', '. $room : '');
} else {
$strings[] = strftime('%a. %x %R', $interval['begin'])
. ' - ' . strftime('%a %x %R', $interval['end'])
. ($room ? ', '. $room : '');
}
}
return $strings;
}
/**
* Filters the time intervals for this request
* by a specified time range.
*
* @see ResourceRequest::getTimeIntervals for the return format.
*/
public function getTimeIntervalsInTimeRange(DateTime $begin, DateTime $end)
{
$all_time_intervals = $this->getTimeIntervals();
$included_intervals = [];
foreach ($all_time_intervals as $interval) {
$interval_in_range = (
(
$interval['begin'] >= $begin->getTimestamp()
&&
$interval['begin'] <= $end->getTimestamp()
)
||
(
$interval['end'] >= $begin->getTimestamp()
&&
$interval['end'] <= $end->getTimestamp()
)
);
if ($interval_in_range) {
$included_intervals[] = $interval;
}
}
return $included_intervals;
}
/**
* Returns a string representation of the ResourceRequest's type.
*/
public function getType()
{
if (count($this->appointments)) {
return 'appointments';
} elseif ($this->termin_id) {
return 'date';
} elseif ($this->metadate_id) {
return 'cycle';
} elseif ($this->course_id) {
return 'course';
}
return null;
}
/**
* Returns a string representation of the status of the ResourceRequest.
*/
public function getStatus()
{
switch ($this->closed) {
case self::STATE_OPEN:
return 'open';
case self::STATE_PENDING:
return 'pending';
case self::STATE_CLOSED:
return 'closed';
case self::STATE_DECLINED:
return 'declined';
default:
return '';
}
}
/**
* Returns a textual representation of the status of the ResourceRequest.
*/
public function getStatusText()
{
if ($this->isNew()) {
return _('Diese Anfrage wurde noch nicht gespeichert.');
}
if ($this->closed == self::STATE_OPEN) {
return _('Die Anfrage wurde noch nicht bearbeitet.');
} else if ($this->closed == self::STATE_DECLINED) {
return _('Die Anfrage wurde bearbeitet und abgelehnt.');
} else {
return _('Die Anfrage wurde bearbeitet.');
}
}
/**
* Returns a textual representation of the dates for which the request
* has been created.
*
* @param bool $as_array True, if an array with a string for each date
* (single or cycle date) shall be returned, false otherwise.
*
* @returns string|array Depending on the parameter $as_array, the text
* is returned as one string or as an array of strings for each date
* (single or cycle date).
*/
public function getDateString($as_array = false, $with_past_intervals = true)
{
$now = time();
$strings = [];
$resource_name = '';
if (count($this->appointments) > 0) {
// Filter invalid/not matching items
$appointments = $this->appointments->filter(
function (ResourceRequestAppointment $appointment) use ($with_past_intervals, $now): bool {
if (!$appointment->appointment) {
return false;
}
return $with_past_intervals
|| $appointment->appointment->end_time > $now;
}
);
// Sort by appointment date
$appointments->uasort(
function (ResourceRequestAppointment $a, ResourceRequestAppointment $b): int {
return $a->appointment->date - $b->appointment->date;
}
);
// Get readable string for each appointment
$strings = $appointments->map(
function (ResourceRequestAppointment $appointment): string {
return $appointment->appointment->getFullName('include-room');
},
);
} elseif ($this->termin_id) {
if ($this->date) {
if ($with_past_intervals || $this->date->end_time >= $now) {
$strings[] = $this->date->getFullName('include-room');
}
}
} elseif ($this->metadate_id) {
if ($this->cycle) {
$strings = $this->cycle->dates->filter(function ($date) use($with_past_intervals, $now) {
return $with_past_intervals || $date->end_time >= $now;
})->map(function($date) {
return $date->getFullName('include-room');
});
}
} elseif ($this->course_id) {
$strings = $this->course->getAllDatesInSemester()->toStringArray();
} elseif ($this->begin && $this->end) {
$begin_date = date('Ymd', $this->begin);
$end_date = date('Ymd', $this->end);
if($this->resource) {
$resource_name = htmlReady($this->resource->getFullName());
}
if ($begin_date == $end_date) {
$strings[] = strftime('%a., %x, %R', $this->begin) . ' - '
. strftime('%R', $this->end) . ' ' . $resource_name;
} else {
//Begin and end are on differnt dates
$strings[] = strftime('%a., %x, %R', $this->begin) . ' - '
. strftime('%a., %x, %R', $this->end) . ' ' . $resource_name;
}
}
if ($as_array) {
return $strings;
} else {
return implode(';', $strings);
}
}
/**
* Returns a human-readable string describing the type of the request.
*
* @param bool $short If this parameter is set to true, only the
* type of the request is returned without any information about the
* appointments. Otherwise, appointment information like the
* date or the repetition are appended. Defaults to false.
* @return string
*/
public function getTypeString($short = false)
{
if (count($this->appointments) > 1) {
if ($short) {
return _('Einzeltermine');
} else {
return sprintf(_('Einzeltermine (%sx)'), count($this->appointments));
}
} elseif (count($this->appointments) === 1) {
$date = $this->appointments[0]->appointment;
if ($short || !$date) {
return _('Einzeltermin');
} else {
return sprintf(_('Einzeltermin (%s)'), $date->getFullName());
}
} elseif ($this->date) {
if ($short) {
return _('Einzeltermin');
} else {
return sprintf(_('Einzeltermin (%s)'), $this->date->getFullName());
}
} elseif ($this->cycle) {
if ($short) {
return _('Regelmäßige Termine');
} else {
return sprintf(
_('Regelmäßige Termine (%s)'),
$this->cycle->toString('full')
);
}
} elseif ($this->course) {
if ($short) {
return _('Alle Termine der Veranstaltung');
} else {
return sprintf(
_('Alle Termine der Veranstaltung (%sx)'),
count($this->course->dates)
);
}
} else {
return _('Einfache Anfrage');
}
}
/**
* Returns an array of date objects which are affected
* by this ResourceRequest.
*/
public function getAffectedDates()
{
$dates = [];
switch ($this->getType()) {
case 'date':
$dates[] = $this->date;
break;
case 'cycle':
$dates = $this->cycle->dates->getArrayCopy();
break;
case 'course':
$dates = $this->course->dates->getArrayCopy();
break;
}
return $dates;
}
/**
* @param array $excluded_property_names
* Returns all resource property definitions for all properties
* which can be applied for this ResourceRequest by looking at the
* Resource category. If no resource category ID is set for the request
* an empty array is returned.
*/
public function getAvailableProperties($excluded_property_names = [])
{
if (!$this->category_id) {
//Without a category-ID we cannot find any property!
return [];
}
if (count($excluded_property_names)) {
return ResourcePropertyDefinition::findBySql(
"INNER JOIN resource_category_properties
USING (property_id)
WHERE requestable = '1' AND category_id = :category_id
AND name NOT IN ( :excluded_property_names )",
[
'category_id' => $this->category_id,
'excluded_property_names' => $excluded_property_names
]
);
} else {
return ResourcePropertyDefinition::findBySql(
"INNER JOIN resource_category_properties
USING (property_id)
WHERE requestable = '1' AND category_id = :category_id",
[
'category_id' => $this->category_id
]
);
}
}
/**
* Returns a "compressed" array of resource request properties.
* @param array $excluded_property_names
* @return array An associative array where the keys represent the
* property names and the values represent the property states.
* Note that the value can be an array in case of range properties.
*/
public function getPropertyData($excluded_property_names = [])
{
$data = [];
foreach ($this->properties as $property) {
if ($property->definition->range_search) {
//Assume that a minimum value is requested:
$data[$property->name] = [$property->state];
} else {
$data[$property->name] = $property->state;
}
}
return $data;
}
/**
* @param $name
* @return bool
*/
public function propertyExists($name)
{
$db = DBManager::get();
$exists_stmt = $db->prepare(
"SELECT TRUE FROM resource_request_properties
INNER JOIN resource_property_definitions rpd
ON resource_request_properties.property_id = rpd.property_id
WHERE resource_request_properties.request_id = :request_id
AND rpd.name = :name");
$exists_stmt->execute(
[
'request_id' => $this->id,
'name' => $name
]
);
$exists = $exists_stmt->fetchColumn(0);
return (bool)$exists;
}
/**
* @param $name
* Returns the state of the property specified by $name.
*/
public function getProperty($name)
{
if (!$this->propertyExists($name)) {
//A property with the name $name does not exist for this
//resource request object.
//In that case we can only return null, since resource requests
//store only those properties which are requested:
return null;
}
$db = DBManager::get();
$value_stmt = $db->prepare(
"SELECT resource_request_properties.state FROM resource_request_properties
INNER JOIN resource_property_definitions rpd
ON resource_request_properties.property_id = rpd.property_id
WHERE resource_request_properties.request_id = :request_id
AND rpd.name = :name");
$value_stmt->execute(
[
'request_id' => $this->id,
'name' => $name
]
);
$value = $value_stmt->fetchColumn(0);
if (!$value) {
return null;
}
return $value;
}
/**
* @param $name
* @return ResourceRequestProperty
* @throws InvalidResourceCategoryException If this resource category
* doesn't match the category of the resource request object.
* @throws ResourcePropertyException If the name of the
* resource request property is not defined for this resource category.
*/
public function getPropertyObject($name)
{
if (!$this->propertyExists($name)) {
//A property with the name $name does not exist for this
//resource object. If it is a mandatory property
//we can still try to create it:
$property = $this->category->createDefinedResourceRequestProperty(
$this,
$name
);
$property->store();
return $property;
}
return ResourceRequestProperty::findOneBySql(
"INNER JOIN resource_property_definitions rpd
ON resource_request_properties.property_id = rpd.property_id
WHERE resource_request_properties.request_id = :request_id
AND rpd.name = :name",
[
'request_id' => $this->id,
'name' => $name
]
);
}
/**
* @param string $name
* @param string $state
* @return bool True, if the property state could be set, false otherwise.
*/
public function setProperty($name, $state = '')
{
if (!$this->propertyExists($name)) {
//A property with the name $name does not exist for this
//resource object. If it is a mandatory property
//we can still try to create it:
if ($this->category) {
$property = $this->category->createDefinedResourceRequestProperty(
$this,
$name,
$state
);
return $property->store();
}
return false;
}
$property = $this->getPropertyObject($name);
if ($property) {
$property->state = $state;
if ($property->isDirty()) {
return $property->store();
}
return true;
}
}
/**
* Sets or unsets the properties for this resource request.
*
* @param array $property_list The properties which shall be set
* or unset. The array has the following structure:
* [
* property_name => property_value
* ]
*
* @param bool $accept_null_values True, if a value of null
* shall be used when setting the property.
* If $accept_null_values is set to false all properties
* with a value equal to null will be deleted.
*
* @return null
*/
public function updateProperties($property_list = [], $accept_null_values = false)
{
//Delete all properties first then re-create them
//from the $property_list array:
$this->properties->delete();
if (is_array($property_list)) {
foreach ($property_list as $name => $state) {
if ($state or $accept_null_values) {
//State is set or null values are allowed:
//create/update the property
$this->setProperty($name, $state);
}
}
}
$this->resetRelation('properties');
}
public function deletePropertyIfExists($name = '')
{
if (!$this->propertyExists($name)) {
return true;
} else {
$property = $this->getPropertyObject($name);
return $property->delete();
}
}
public function getRangeName()
{
if ($this->getRangeType() === 'course') {
$name = $this->getRangeObject()->getFullName();
$name .= ' (' . implode(',', $this->getRangeObject()->getMembersWithStatus('dozent', true)->limit(3)->getValue('nachname')) . ')';
} else {
$range_object = $this->getRangeObject();
if ($range_object instanceof User) {
if (get_visibility_by_id($range_object->id)) {
$name = $range_object->getFullName();
} else if ($this->user_id === $GLOBALS['user']->id) {
$name = $range_object->getFullName();
} else {
$current_user = User::findCurrent();
if ($current_user instanceof User) {
//If the current user has at least autor permissions
//(which are required to see all requests), they can
//see the name of the requester.
if ($this->resource_id && ($this->resource instanceof Resource)
&& $this->resource->userHasPermission($current_user, 'autor')) {
$name = $range_object->getFullName();
} else if (ResourceManager::userHasGlobalPermission($current_user, 'autor')) {
$name = $range_object->getFullName();
} else {
return '';
}
} else {
return '';
}
}
} else {
$name = $range_object->getFullName();
}
if ($this->comment) {
$name .= " \n" . $this->comment;
}
}
return $name;
}
public function isSimpleRequest()
{
return !$this->course_id && !$this->metadate_id && !$this->termin_id;
}
public function getRangeId()
{
//Check if the request belongs to a course:
if ($this->termin_id) {
return $this->date->range_id;
} elseif ($this->metadate_id) {
return $this->cycle->seminar_id;
} elseif ($this->course_id) {
return $this->course_id;
}
//The request does not belong to a course and therefore
//belongs to a user:
return $this->user_id;
}
public function getRangeType()
{
if ($this->course_id || $this->termin_id || $this->metadate_id) {
return 'course';
}
return 'user';
}
public function getRangeObject()
{
if ($this->course_id) {
return $this->course;
}
if ($this->termin_id) {
return $this->date->course;
}
if ($this->metadate_id) {
return $this->cycle->course;
}
return $this->user;
}
/**
* This method sends a notification mail to all room administrators
* that informs them of this new request.
*/
public function sendNewRequestMail()
{
//First we must get all users who have admin permissions in the
//resource management system. Depending wheter a resource_id is set
//for this resource request either all admins of a resource or
//all admins of the resource management system must be informed.
$now = time();
if ($this->resource_id) {
//The resource-ID is set for this request:
//Get all admins of the resource and the resource management system.
$admin_users = User::findBySql(
"user_id IN (
SELECT user_id FROM resource_permissions
WHERE (
resource_id = :resource_id
OR resource_id = 'global'
)
AND perms = 'admin'
UNION
SELECT user_id FROM resource_temporary_permissions
WHERE resource_id = :resource_id
AND perms = 'admin'
AND begin <= :now AND end >= :now
)",
[
'resource_id' => $this->resource_id,
'now' => $now
]
);
} else {
//Get all admins of the resource management system.
$admin_users = User::findBySql(
"user_id IN (
SELECT user_id FROM resource_permissions
WHERE resource_id = 'global'
AND perms = 'admin'
UNION
SELECT user_id FROM resource_temporary_permissions
WHERE resource_id = 'global'
AND perms = 'admin'
AND begin <= :now AND end >= :now
GROUP BY user_id
)",
[
'now' => $now
]
);
}
if (!$admin_users) {
return;
}
$factory = new Flexi\Factory(
$GLOBALS['STUDIP_BASE_PATH'] . '/locale/'
);
foreach ($admin_users as $user) {
$user_lang_path = getUserLanguagePath($user->id);
$template = $factory->open(
$user_lang_path . '/LC_MAILS/new_resource_request.php'
);
$template->set_attribute('request', $this);
if ($this->resource instanceof Resource) {
$resource = $this->resource->getDerivedClassInstance();
if ($resource instanceof Room) {
$template->set_attribute('requested_room', $resource->name);
} else {
$template->set_attribute('requested_resource', $resource->name);
}
}
$mail_text = $template->render();
setLocaleEnv($user->preferred_language);
if ($this->resource) {
$resource = $this->resource->getDerivedClassInstance();
$template->set_attribute('derived_resource', $resource);
$mail_title = sprintf(
_('%1$s: Neue Anfrage in der Raumverwaltung'),
$resource->getFullName()
);
} else {
$mail_title = sprintf(
_('Neue Anfrage in der Raumverwaltung')
);
}
Message::send(
User::findCurrent()->id,
$user->username,
$mail_title,
$mail_text
);
restoreLanguage();
}
}
/**
* @param array $bookings
* This method sends a mail to inform the requester that
* the request has been closed.
*/
public function sendCloseRequestMailToRequester($bookings = [])
{
$factory = new Flexi\Factory(
$GLOBALS['STUDIP_BASE_PATH'] . '/locale/'
);
$requester_lang = $this->user->preferred_language;
$requester_lang_path = getUserLanguagePath($this->user->id);
setLocaleEnv($requester_lang);
$template = $factory->open(
$requester_lang_path . '/LC_MAILS/close_resource_request.php'
);
$template->set_attribute('request', $this);
if ($this->course) {
$lecturers = CourseMember::findByCourseAndStatus(
$this->course->id,
'dozent'
);
$lecturer_names = [];
foreach ($lecturers as $lecturer) {
if ($lecturer->user instanceof User) {
$lecturer_names[] = $lecturer->user->getFullName();
}
}
$lecturer_names = implode(', ', $lecturer_names);
$template->set_attribute('lecturer_names', $lecturer_names);
}
if (is_array($bookings)) {
$booked_rooms = [];
$booked_time_intervals = [];
$metadates = [];
$single_dates = [];
foreach ($bookings as $booking) {
if (!($booking instanceof ResourceBooking)) {
continue;
}
$booked_rooms[] = $booking->resource->name;
if ($booking->assigned_course_date instanceof CourseDate) {
$single_date = $booking->assigned_course_date;
$metadate = $single_date->cycle;
if ($metadate instanceof SeminarCycleDate) {
$metadates[$metadate->id] = $metadate;
} else {
$single_dates[$single_date->id] = $single_date;
}
} else {
$time_intervals = $booking->getTimeIntervals();
foreach ($time_intervals as $time_interval) {
$booked_time_intervals[] = $time_interval->__toString();
}
}
}
$booked_rooms = array_unique($booked_rooms);
sort($booked_rooms);
$template->set_attribute('booked_rooms', implode(', ', $booked_rooms));
$template->set_attribute('metadates', $metadates);
$template->set_attribute('single_dates', $single_dates);
$template->set_attribute('booked_time_intervals', $booked_time_intervals);
}
$mail_title = _('Ihre Anfrage wurde bearbeitet!');
$mail_text = $template->render();
Message::send(
User::findCurrent()->id,
$this->user->username,
$mail_title,
$mail_text
);
restoreLanguage();
}
/**
* @param array $bookings
* This method sends mails to the lecurers of the course (if any)
* where this request has been assigned to. The sent mail informs them
* about the closing of the request.
*/
public function sendCloseRequestMailToLecturers($bookings = [])
{
//Notify each lecturer of the course:
if ($this->course) {
$lecturers = CourseMember::findByCourseAndStatus(
$this->course->id,
'dozent'
);
if ($lecturers) {
$factory = new Flexi\Factory(
$GLOBALS['STUDIP_BASE_PATH'] . '/locale/'
);
$lecturer_names = [];
foreach ($lecturers as $lecturer) {
if ($lecturer->user instanceof User) {
$lecturer_names[] = $lecturer->user->getFullName();
}
}
$lecturer_names = implode(', ', $lecturer_names);
$booked_rooms = [];
$booked_time_intervals = [];
$metadates = [];
$single_dates = [];
if (is_array($bookings)) {
foreach ($bookings as $booking) {
if (!($booking instanceof ResourceBooking)) {
continue;
}
$booked_rooms[] = $booking->resource->name;
if ($booking->assigned_course_date instanceof CourseDate) {
$single_date = $booking->assigned_course_date;
$metadate = $single_date->cycle;
if ($metadate instanceof SeminarCycleDate) {
$metadates[$metadate->id] = $metadate;
} else {
$single_dates[$single_date->id] = $single_date;
}
} else {
$time_intervals = $booking->getTimeIntervals();
foreach ($time_intervals as $time_interval) {
$booked_time_intervals[] = $time_interval->__toString();
}
}
}
}
$booked_rooms = array_unique($booked_rooms);
sort($booked_rooms);
$booked_rooms = implode(', ', $booked_rooms);
foreach ($lecturers as $lecturer) {
$lec_lang = $lecturer->user->preferred_language;
$lec_lang_path = getUserLanguagePath($lecturer->user->id);
setLocaleEnv($lec_lang);
$template = $factory->open(
$lec_lang_path . '/LC_MAILS/close_resource_request.php'
);
$template->set_attribute('request', $this);
$template->set_attribute('lecturer_names', $lecturer_names);
$template->set_attribute('booked_rooms', $booked_rooms);
$template->set_attribute('metadates', $metadates);
$template->set_attribute('single_dates', $single_dates);
$template->set_attribute('booked_time_intervals', $booked_time_intervals);
$mail_title = _('Bearbeitung einer Anfrage!');
$mail_text = $template->render();
Message::send(
User::findCurrent()->id,
$lecturer->user->username,
$mail_title,
$mail_text
);
restoreLanguage();
}
}
}
}
/**
* This method sends a mail to inform the requester
* about the denial of the request.
*/
public function sendRequestDeniedMail()
{
//Get the user who made the request:
$user = $this->user;
if (!($user instanceof User)) {
//No mail to send.
return;
}
//Load the mail template:
$factory = new Flexi\Factory(
$GLOBALS['STUDIP_BASE_PATH'] . '/locale/'
);
$user_lang_path = getUserLanguagePath($user->id);
$template = $factory->open(
$user_lang_path . '/LC_MAILS/request_denied_mail.inc.php'
);
$range_object = $this->getRangeObject();
$mail_title = _('Raumanfrage wurde abgelehnt');
if($range_object instanceof Course) {
$mail_title .= ': ' . $range_object->getFullName();
}
$mail_text = $template->render(
[
'request' => $this,
'range_object' => $range_object
]
);
//Send the mail:
Message::send(
User::findCurrent()->id,
$user->username,
$mail_title,
$mail_text
);
}
public function isReadOnlyForUser(User $user)
{
$resource = $this->resource;
if (!$resource) {
//We cannot continue with the permission check.
return false;
}
$resource = $resource->getDerivedClassInstance();
return !$resource->userHasPermission($user, 'autor')
&& ($this->user_id != $user->id);
}
protected function convertToEventData(array $time_intervals, User $user)
{
$booking_plan_request_bg = ColourValue::find('Resources.BookingPlan.Request.Bg');
$booking_plan_request_fg = ColourValue::find('Resources.BookingPlan.Request.Fg');
$booking_plan_preparation_bg = ColourValue::find('Resources.BookingPlan.PreparationTime.Bg');
$booking_plan_preparation_fg = ColourValue::find('Resources.BookingPlan.PreparationTime.Fg');
$user_is_resource_autor = false;
if ($this->resource_id && $this->resource instanceof Resource) {
$user_is_resource_autor = $this->resource->userHasPermission(
$user,
'autor'
);
}
$request_is_editable = $user_is_resource_autor || ($user->id == $this->user_id);
$request_api_urls = [];
$request_view_urls = [];
if ($request_is_editable) {
$request_api_urls = [
'resize' => URLHelper::getURL(
'dispatch.php/resources/ajax/move_request/'. $this->id,
['quiet' => true]
),
'move' => URLHelper::getURL(
'dispatch.php/resources/ajax/move_request/'. $this->id,
['quiet' => true]
)
];
$request_view_urls = [
'edit' => URLHelper::getURL(
'dispatch.php/resources/room_request/edit/'
. $this->id
)
];
if (
$this->resource_id
&& $this->resource instanceof Resource
&& $this->resource->userHasBookingRights($user)
) {
$request_view_urls['edit'] = URLHelper::getURL(
'dispatch.php/resources/room_request/resolve/'. $this->id
);
}
}
$events = [];
foreach ($time_intervals as $interval) {
$real_begin = $interval['begin'];
if ($this->preparation_time) {
$real_begin += (int)$this->preparation_time;
$begin = new DateTime();
$begin->setTimestamp($interval['begin']);
$end = new DateTime();
$end->setTimestamp($real_begin);
$events[] = new Studip\Calendar\EventData(
$begin,
$end,
_('Rüstzeit'),
['preparation-time'],
$booking_plan_preparation_fg->__toString(),
$booking_plan_preparation_bg->__toString(),
$request_is_editable,
'',
'',
ResourceRequest::class,
$this->id,
Resource::class,
$this->resource_id,
$request_view_urls,
$request_api_urls
);
}
$begin = new DateTime();
$begin->setTimestamp($real_begin);
$end = new DateTime();
$end->setTimestamp($interval['end']);
$events[] = new Studip\Calendar\EventData(
$begin,
$end,
$this->getRangeName(),
['resource-request'],
$booking_plan_request_fg->__toString(),
$booking_plan_request_bg->__toString(),
$request_is_editable,
ResourceRequest::class,
$this->id,
Resource::class,
$this->resource_id,
Resource::class,
$this->resource_id,
$request_view_urls,
$request_api_urls
);
}
return $events;
}
public function getAllEventData()
{
return $this->convertToEventData(
$this->getTimeIntervals(true),
User::findCurrent()
);
}
public function getEventDataForTimeRange(DateTime $begin, DateTime $end)
{
$intervals = $this->getTimeIntervals(true);
$time_intervals = [];
$begin_timestamp = $begin->getTimestamp();
$end_timestamp = $end->getTimestamp();
foreach ($intervals as $interval) {
if ((($interval['begin'] >= $begin_timestamp)
&& ($interval['begin'] <= $end_timestamp)) ||
(($interval['end'] >= $begin_timestamp)
&& ($interval['end'] <= $end_timestamp)) ||
(($interval['begin'] < $begin_timestamp)
&& ($interval['end'] > $end_timestamp))
) {
$time_intervals[] = $interval;
}
}
return $this->convertToEventData($time_intervals, User::findCurrent());
}
public function getFilteredEventData(
$user_id = null,
$range_id = null,
$range_type = null,
$begin = null,
$end = null
)
{
$intervals = $this->getTimeIntervals(true);
$time_intervals = [];
if ($begin && $end) {
$begin_timestamp = $begin;
$end_timestamp = $end;
if ($begin instanceof DateTime) {
$begin_timestamp = $begin->getTimestamp();
}
if ($end instanceof DateTime) {
$end_timestamp = $end->getTimestamp();
}
foreach ($intervals as $interval) {
if ((($interval['begin'] >= $begin_timestamp)
&& ($interval['begin'] <= $end_timestamp)) ||
(($interval['end'] >= $begin_timestamp)
&& ($interval['end'] <= $end_timestamp)) ||
(($interval['begin'] < $begin_timestamp)
&& ($interval['end'] > $end_timestamp))
) {
$time_intervals[] = $interval;
}
}
} else {
$time_intervals = $intervals;
}
if ($user_id) {
$user = User::find($user_id);
} else {
$user = User::findCurrent();
}
return $this->convertToEventData($time_intervals, $user);
}
public function getPriority()
{
$result = $this->getTimeIntervals();
if (count($result) === 0) {
return null;
}
$first = $result[0];
return round(($first['begin'] - time()) / 86400);
}
public function getLoggingInfoText()
{
$props = '';
foreach ($this->getPropertyData() as $name => $state) {
$props .= $name . '=' . json_encode($state) . ' ';
}
$info['Anfrage'] = $this->getType();
$info['Status'] = $this->getStatus();
if ($this->category) {
$info['Raumtyp'] = $this->category->name;
}
if ($this->termin_id) {
$info['Termin'] = $this->termin_id;
}
if ($this->metadate_id) {
$info['Metadate'] = $this->metadate_id;
}
if ($props) {
$info['Eigenschaften'] = $props;
}
if ($this->comment) {
$info['Kommentar'] = $this->comment;
}
$txt = '';
foreach ($info as $n => $m) {
$txt .= $n . ': ' . $m . ', ';
}
return trim($txt, ' ,');
}
}