diff options
Diffstat (limited to 'lib/models/resources/Resource.class.php')
| -rw-r--r-- | lib/models/resources/Resource.class.php | 2970 |
1 files changed, 0 insertions, 2970 deletions
diff --git a/lib/models/resources/Resource.class.php b/lib/models/resources/Resource.class.php deleted file mode 100644 index 531bc4c..0000000 --- a/lib/models/resources/Resource.class.php +++ /dev/null @@ -1,2970 +0,0 @@ -<?php - -/** - * Resource.class.php - model class for a resource - * - * The Resource class is the base class of the new - * Room and Resource management system in Stud.IP. - * It provides core functionality for handling general resources - * and can be derived for handling special resources. - * - * 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 - * - * @property string $id database column - * @property string $parent_id database column - * @property string $category_id database column - * @property int|null $level database column - * @property string $name database column - * @property I18NString|null $description database column - * @property int $requestable database column - * @property int $lockable database column - * @property int $mkdate database column - * @property int $chdate database column - * @property int $sort_position database column - * @property SimpleORMapCollection|ResourceProperty[] $properties has_many ResourceProperty - * @property SimpleORMapCollection|ResourcePermission[] $permissions has_many ResourcePermission - * @property SimpleORMapCollection|ResourceRequest[] $requests has_many ResourceRequest - * @property SimpleORMapCollection|ResourceBooking[] $bookings has_many ResourceBooking - * @property SimpleORMapCollection|Resource[] $children has_many Resource - * @property ResourceCategory $category belongs_to ResourceCategory - * @property Resource $parent belongs_to Resource - * @property mixed $class_name additional field - */ -class Resource extends SimpleORMap implements StudipItem -{ - protected static function configure($config = []) - { - $config['db_table'] = 'resources'; - - $config['belongs_to']['category'] = [ - 'class_name' => ResourceCategory::class, - 'foreign_key' => 'category_id', - 'assoc_func' => 'find' - ]; - - $config['has_many']['properties'] = [ - 'class_name' => ResourceProperty::class, - 'assoc_foreign_key' => 'resource_id', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - - $config['has_many']['permissions'] = [ - 'class_name' => ResourcePermission::class, - 'assoc_foreign_key' => 'resource_id', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - - $config['has_many']['requests'] = [ - 'class_name' => ResourceRequest::class, - 'assoc_foreign_key' => 'resource_id', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - - $config['has_many']['bookings'] = [ - 'class_name' => ResourceBooking::class, - 'assoc_foreign_key' => 'resource_id', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - - $config['has_many']['children'] = [ - 'class_name' => Resource::class, - 'assoc_func' => 'findChildren', - 'on_delete' => 'delete', - 'on_store' => 'store' - ]; - - $config['belongs_to']['parent'] = [ - 'class_name' => Resource::class, - 'foreign_key' => 'parent_id' - ]; - - $config['i18n_fields']['description'] = true; - - $config['additional_fields']['class_name'] = ['category', 'class_name']; - $config['registered_callbacks']['before_store'][] = 'cbValidate'; - - parent::configure($config); - } - - /** - * This is a cache for permissions that users have on resources. - * It is meant to reduce the database requests in cases where the - * same permission is queried a lot of times. - */ - protected static $permission_cache; - - /** - * Returns the children of a resource. - * The children are converted to an instance of the derived class, - * if they are not instances of the default Resource class. - */ - public static function findChildren($resource_id) - { - $children = self::findBySql( - 'parent_id = :parent_id ORDER BY name ASC', - ['parent_id' => $resource_id] - ); - - if (!$children) { - return []; - } - - foreach ($children as &$child) { - $child = $child->getDerivedClassInstance(); - } - return $children; - } - - /** - * Returns a translation of the resource class name. - * The translated name can be singular or plural, depending - * on the value of the parameter $item_count. - * - * @param int $item_count The amount of items the translation shall be - * made for. This is only used to determine, if a singular or a - * plural form shall be returned. - * - * @return string The translated form of the class name, either in - * singular or plural. - * - */ - public static function getTranslatedClassName($item_count = 1) - { - return ngettext( - 'Ressource', - 'Ressourcen', - $item_count - ); - } - - /** - * Retrieves all resources which don't have a parent resource assigned. - * Such resources are called root resources since they are roots of - * a resource hierarchy (or a resource tree). - * - * @return Resource[] An array of Resource objects - * which are root resources. - */ - public static function getRootResources() - { - return self::findBySql("parent_id = '' ORDER BY name"); - } - - /** - * A method for overloaded classes so that they can define properties - * that are required for that resource class. - * - * @return string[] An array with the names of the required properties. - * Example: The properties with the names "foo", "bar" and "baz" - * are required properties. The array would have the following content: - * [ - * 'foo', - * 'bar', - * 'baz' - * ] - */ - public static function getRequiredProperties() - { - return []; - } - - - /** - * Returns the part of the URL for getLink and getURL which will be - * placed inside the calls to URLHelper::getLink and URLHelper::getURL - * in these methods. - * - * @param string $action The action for the resource. - * @param string $id The ID of the resource. - * - * @return string The URL path for the specified action. - * @throws InvalidArgumentException If $resource_id is empty. - * - */ - protected static function buildPathForAction($action = 'show', $id = null) - { - $actions_without_id = ['add']; - if (!$id && !in_array($action, $actions_without_id)) { - throw new InvalidArgumentException( - _('Zur Erstellung der URL fehlt eine Ressourcen-ID!') - ); - } - - switch ($action) { - case 'show': - return 'dispatch.php/resources/resource/index/' . $id; - case 'add': - return 'dispatch.php/resources/resource/add'; - case 'edit': - return 'dispatch.php/resources/resource/edit/' . $id; - case 'files': - return 'dispatch.php/resources/resource/files/' . $id . '/'; - case 'permissions': - return 'dispatch.php/resources/resource/permissions/' . $id; - case 'temporary_permissions': - return 'dispatch.php/resources/resource/temporary_permissions/' . $id; - case 'booking_plan': - return 'dispatch.php/resources/room_planning/booking_plan/' . $id; - case 'request_plan': - return 'dispatch.php/resources/room_planning/request_plan/' . $id; - case 'semester_plan': - return 'dispatch.php/resources/room_planning/semester_plan/' . $id; - case 'assign-undecided': - return 'dispatch.php/resources/booking/add/' . $id; - case 'assign': - return 'dispatch.php/resources/booking/add/' . $id . '/0'; - case 'reserve': - return 'dispatch.php/resources/booking/add/' . $id . '/1'; - case 'lock': - return 'dispatch.php/resources/booking/add/' . $id . '/2'; - case 'delete_bookings': - return 'dispatch.php/resources/resource/delete_bookings/' . $id; - case 'export_bookings': - return 'dispatch.php/resources/export/resource_bookings/' . $id; - case 'delete': - return 'dispatch.php/resources/resource/delete/' . $id; - default: - return 'dispatch.php/resources/resource/show/' . $id; - } - } - - /** - * Returns the appropriate link for the resource action that shall be - * executed on a resource. - * - * @param string $action The action which shall be executed. - * For default Resources the actions 'show', 'add', 'edit' and 'delete' - * are defined. - * @param string $id The ID of the resource on which the specified - * action shall be executed. - * @param array $link_parameters Optional parameters for the link. - * - * @return string The Link for the resource action. - * @throws InvalidArgumentException If $resource_id is empty. - * - */ - public static function getLinkForAction( - $action = 'show', - $id = null, - $link_parameters = [] - ) - { - return URLHelper::getLink( - self::buildPathForAction($action, $id), - $link_parameters - ); - } - - /** - * Returns the appropriate URL for the resource action that shall be - * executed on a resource. - * - * @param string $action The action which shall be executed. - * For default Resources the actions 'show', 'add', 'edit' and 'delete' - * are defined. - * @param string $id The ID of the resource on which the specified - * action shall be executed. - * @param array $url_parameters Optional parameters for the URL. - * - * @return string The URL for the resource action. - * @throws InvalidArgumentException If $resource_id is empty. - * - */ - public static function getURLForAction( - $action = 'show', - $id = null, - $url_parameters = [] - ) - { - return URLHelper::getURL( - self::buildPathForAction($action, $id), - $url_parameters - ); - } - - /** - * The SORM store method is overloaded to assure that the right level - * attribute is stored. - */ - public function store() - { - //Set the level attribute according to the parent's - //level attribute. If no parents are defined - //set the level to zero. - if ($this->parent_id && $this->parent) { - $this->level = $this->parent->level + 1; - } else { - $this->level = 0; - } - - //Store the folder, if it hasn't been stored before: - - $folder = $this->getFolder(); - if ($folder) { - $folder->store(); - } - - return parent::store(); - } - - public function delete() - { - //Delete the folder: - - $folder = $this->getFolder(false); - if ($folder) { - $folder->delete(); - } - - return parent::delete(); - } - - public function cbValidate() - { - if (!$this->category_id) { - throw new InvalidResourceException( - sprintf( - _('Die Ressource %s ist keiner Ressourcenkategorie zugeordnet!'), - $this->name - ) - ); - } - return true; - } - - - /** - * @see StudipItem::__toString - */ - public function __toString() - { - return $this->getFullName(); - } - - - /** - * Retrieves the folder for this resource. - * - * @param bool $create_if_missing Whether to create a folder (true) or - * not (false) in case no folder exists for this resource. - * Defaults to true. - * - * @returns ResourceFolder|null Either a ResourceFolder instance or null - * in case no such instance can be retrieved or created. - */ - public function getFolder($create_if_missing = true) - { - $folder = Folder::findOneByRange_id($this->id); - - if ($folder) { - $folder = $folder->getTypedFolder(); - - if ($folder instanceof ResourceFolder) { - //Only return ResourceFolder instances. - return $folder; - } - } elseif ($create_if_missing) { - $folder = $this->createFolder(); - if ($folder instanceof ResourceFolder) { - return $folder; - } - } - //In all other cases return null: - return null; - } - - public function setFolder(ResourceFolder $folder) - { - if ($this->isNew()) { - $this->store(); - } - - $folder->range_id = $this->id; - $folder->range_type = 'Resource'; - - return $folder->store(); - } - - public function createFolder() - { - if ($this->isNew()) { - $this->id = $this->getNewId(); - } - - $folder = Folder::createTopFolder( - $this->id, - 'Resource', - 'ResourceFolder' - ); - - if ($folder) { - $folder = $folder->getTypedFolder(); - if ($folder) { - $folder->store(); - return $folder; - } - } - - return null; - } - - /** - * Returns a list of property names that are required - * for the resource class. - * - * @return string[] An array with the property names. - */ - public function getRequiredPropertyNames() - { - return []; - } - - - /** - * This is a simplified version of the createBooking method. - * @param User $user - * @param DateTime $begin - * @param DateTime $end - * @param int $preparation_time - * @param string $description - * @param string $internal_comment - * @param int $booking_type - * @return ResourceBooking - * @see Resource::createBooking - */ - public function createSimpleBooking( - User $user, - DateTime $begin, - DateTime $end, - $preparation_time = 0, - $description = '', - $internal_comment = '', - $booking_type = ResourceBooking::TYPE_NORMAL - ) - { - return $this->createBooking( - $user, - $user->id, - [ - [ - 'begin' => $begin, - 'end' => $end - ] - ], - null, - 0, - null, - $preparation_time, - $description, - $internal_comment, - $booking_type - ); - } - - /** - * Creates bookings from a request. - * @param User $user - * @param ResourceRequest $request The request from which - * a resource booking shall be built. - * @param int $preparation_time - * @param string $description - * @param string $internal_comment - * @param int $booking_type - * @param bool $prepend_preparation_time . If this is set to true, - * the preparation time will end before the start of the - * requested time. If $prepend_preparation_time is set to false - * (the default) the preparation time starts with the start of the - * requested time. - * @param bool $notify_lecturers - * @return ResourceBooking[] A list of resource bookings - * matching the request. - * @throws ResourceRequestException if the request could not be marked - * as resolved. - * - * @throws ResourceUnavailableException if the resource cannot be assigned - * in at least one of the time ranges specified by the resource request. - */ - public function createBookingFromRequest( - User $user, - ResourceRequest $request, - $preparation_time = 0, - $description = '', - $internal_comment = '', - $booking_type = ResourceBooking::TYPE_NORMAL, - $prepend_preparation_time = false, - $notify_lecturers = false - ) - { - $course_dates = $request->getAffectedDates(); - - $bookings = []; - if ($course_dates) { - foreach ($course_dates as $course_date) { - $booking = $this->createBooking( - $user, - $course_date->id, - [ - [ - 'begin' => ( - $prepend_preparation_time - ? $course_date->date - $preparation_time - : $course_date->date - ), - 'end' => $course_date->end_time - ] - ], - null, - 0, - $course_date->end_time, - $preparation_time, - $description, - $internal_comment, - $booking_type - ); - - if ($booking instanceof ResourceBooking) { - $bookings[] = $booking; - } - } - } elseif (count($request->appointments)) { - //It is a request for multiple single dates. - //Such requests are resolved into multiple bookings. - foreach ($request->appointments as $appointment) { - $begin = ( - $prepend_preparation_time - ? $appointment->appointment->date - $preparation_time - : $appointment->appointment->date - ); - $end = $appointment->appointment->end_time; - - $booking = $this->createBooking( - $user, - $appointment->appointment_id, - [ - [ - 'begin' => $begin, - 'end' => $end - ] - ], - null, - 0, - $end, - $preparation_time, - $description, - $internal_comment, - $booking_type - ); - - if ($booking instanceof ResourceBooking) { - $bookings[] = $booking; - } - } - } else { - //No date objects for the request. - //It is a simple request: - $booking = $this->createBooking( - $user, - $request->user->id, - [ - [ - 'begin' => ( - $prepend_preparation_time - ? $request->begin - $preparation_time - : $request->begin - ), - 'end' => $request->end - ] - ], - null, - 0, - $request->end, - $preparation_time, - $description, - $internal_comment, - $booking_type - ); - - if ($booking instanceof ResourceBooking) { - $bookings[] = $booking; - } - } - - if (!$request->closeRequest($notify_lecturers)) { - throw new ResourceRequestException( - _('Die Anfrage konnte nicht als bearbeitet markiert werden!') - ); - } - - return $bookings; - } - - /** - * A factory method for creating a ResourceBooking object - * for this resource. - * - * @param User $user The user who wishes to create a resource booking. - * @param string $range_id The ID of the user (or the Stud.IP object) - * which owns the ResourceBooking. - * @param array[][] $time_ranges The time ranges for the booking. - * At least one time range has to be specified using unix timestamps - * or DateTime objects. - * This array has the following structure: - * [ - * [ - * 'begin' => The begin timestamp or DateTime object. - * 'end' => The end timestamp or DateTime object. - * ] - * ] - * @param DateInterval|null $repetition_interval The repetition interval - * for the new booking. This must be a DateInterval object if - * repetitions shall be stored. - * Otherwise this parameter must be set to null. - * @param int $repeat_amount The amount of repetitions. - * This parameter is only regarded if $repetition_interval contains - * a DateInterval object. - * In case repetitions are specified by their end date set this - * parameter to 0. - * @param DateTime|string|null $repetition_end_date The end date of the - * repetition. This can either be an unix timestamp or a DateTime object - * and will only be regarded if $repetition_interval contains a - * DateInterval object. - * In case repetitions are specified by their amount set this - * parameter to null. - * @param int $repetition_amount (obsolete, has no effect) - * @param int $preparation_time The preparation time which is needed before - * the real start time. This will be substracted - * from the begin timestamp and stored in an extra column of the - * resource_bookings table. - * @param string $description An optional description for the booking. - * This fields was previously known as "user_free_name". - * @param string $internal_comment An optional comment for the - * booking which is intended to be used internally - * in the room and resource administration staff. - * @param int $booking_type The booking type. - * 0 = normal booking - * 1 = reservation - * 2 = lock booking - * @param bool $force_booking If this parameter is set to true, - * overlapping bookings are removed before storing this booking. - * - * @return ResourceBooking object. - * @throws InvalidArgumentException If no time ranges are specified - * or if there is an error regarding the time ranges. - * @throws ResourceBookingRangeException If $range_id is not set. - * @throws ResourceBookingOverlapException If the booking overlaps - * with another booking or a resource lock. - * @throws ResourcePermissionException If the specified user does not - * have sufficient permissions to create a resource booking. - * @throws ResourceBookingException If the repetition interval - * is invalid or if the resource booking cannot be stored. - * - */ - public function createBooking( - User $user, - $range_id = null, - $time_ranges = [], - $repetition_interval = null, - $repetition_amount = 0, - $repetition_end_date = null, - $preparation_time = 0, - $description = '', - $internal_comment = '', - $booking_type = ResourceBooking::TYPE_NORMAL, - $force_booking = false - ) - { - if (!is_array($time_ranges)) { - throw new InvalidArgumentException( - _('Es wurden keine Zeitbereiche für die Buchung angegeben!') - ); - } - - $booking_begin = null; - $booking_end = null; - - //Check if each entry of the $time_intervals array is in the right - //format and if it contains either timestamps or DateTime objects. - //After that the time ranges are checked for validity (begin > end) - //and if there are locks or bookings in one of the time ranges. - //Furthermore all reservations that are affected by this booking - //are collected so that the persons who made the reservations - //can be informed about the new booking. - $affected_reservations = []; - foreach ($time_ranges as $index => $time_range) { - $begin = $time_range['begin']; - $end = $time_range['end']; - - if ($begin === null || $end === null) { - throw new InvalidArgumentException( - _('Mindestens eines der Zeitintervalls ist im falschen Format!') - ); - } - - if (!($begin instanceof DateTime)) { - $b = new DateTime(); - $b->setTimestamp($begin); - $begin = $b; - } - if (!($end instanceof DateTime)) { - $e = new DateTime(); - $e->setTimestamp($end); - $end = $e; - } - - $real_begin = clone $begin; - if ($preparation_time > 0) { - $real_begin = $real_begin->sub( - new DateInterval('PT' . $preparation_time . 'S') - ); - } - - if ($real_begin > $end) { - throw new InvalidArgumentException( - _('Der Startzeitpunkt darf nicht hinter dem Endzeitpunkt liegen!') - ); - } - - $duration = $end->getTimestamp() - $begin->getTimestamp(); - $min_duration = Config::get()->RESOURCES_MIN_BOOKING_TIME; - if ($min_duration && ($duration < ($min_duration * 60))) { - throw new InvalidArgumentException( - sprintf( - _('Die minimale Buchungsdauer von %1$d Minuten wurde unterschritten!'), - $min_duration - ) - ); - } - - if ($index == array_keys($time_ranges)[0]) { - $booking_begin = clone $begin; - $booking_end = clone $end; - } - - if ($repetition_interval instanceof DateInterval) { - //We must calculate the end of the repetition interval - //by using $repetition_amount or $repetition_end_date. - $repetition_end = null; - if ($repetition_end_date instanceof DateTime) { - $repetition_end = $repetition_end_date; - } elseif ($repetition_end_date) { - //convert $repetition_end_date to a DateTime object: - $red = new DateTime(); - $red->setTimestamp($repetition_end_date); - $repetition_end = $red; - } else { - //$repetition_end_date is not set: Use $repetition_amount. - //Add the repetition interval $repetition_amount times - //to the $real_begin DateTime object to get the end date - //of the repetition: - $repetition = clone $real_begin; - for ($i = 0; $i < $repetition_amount; $i++) { - $repetition = $repetition->add($repetition_interval); - } - $repetition_end = $repetition; - } - - $current_date = clone $real_begin; - - //Check for each repetition if the resource is available - //or locked: - while ($current_date <= $repetition_end) { - $current_begin = clone $current_date; - $current_end = clone $current_date; - $current_end->setTime( - intval($end->format('H')), - intval($end->format('i')), - intval($end->format('s')) - ); - - if ($current_begin < $current_end) { - $affected_reservations = array_merge( - ResourceBooking::findByResourceAndTimeRanges( - $this, - [ - [ - 'begin' => $current_begin->getTimestamp(), - 'end' => $current_end->getTimestamp(), - ] - ], - [1, 3] - ), - $affected_reservations - ); - } - - $current_date = $current_date->add($repetition_interval); - } - } else { - $affected_reservations = array_merge( - ResourceBooking::findByResourceAndTimeRanges( - $this, - [ - [ - 'begin' => $real_begin->getTimestamp(), - 'end' => $end->getTimestamp(), - ] - ], - [1, 3] - ), - $affected_reservations - ); - } - } - - $booking = new ResourceBooking(); - $booking->resource_id = $this->id; - $booking->booking_user_id = $user->id; - $booking->range_id = $range_id; - $booking->description = $description; - $booking->begin = $booking_begin->getTimestamp(); - $booking->end = $booking_end->getTimestamp(); - - if ($repetition_interval instanceof DateInterval) { - if ($repetition_end_date) { - if ($repetition_end_date instanceof DateTime) { - $booking->repeat_end = $repetition_end_date->getTimestamp(); - } else { - $booking->repeat_end = $repetition_end_date; - } - } - - $booking->repetition_interval = $repetition_interval->format('P%YY%MM%DD'); - } - - if ($preparation_time) { - $booking->preparation_time = $preparation_time; - } else { - $booking->preparation_time = '0'; - } - $booking->internal_comment = $internal_comment; - $booking->booking_type = (int)$booking_type; - - //We can finally store the new booking. - - try { - $booking->store($force_booking); - } catch (ResourceBookingOverlapException $e) { - if ($begin->format('Ymd') == $end->format('Ymd')) { - throw new ResourceBookingException( - sprintf( - _('%1$s: Die Buchung vom %2$s bis %3$s konnte wegen Überlappungen nicht gespeichert werden: %4$s'), - $this->getFullName(), - $begin->format('d.m.Y H:i'), - $end->format('H:i'), - $e->getMessage() - ) - ); - } else { - throw new ResourceBookingException( - sprintf( - _('%1$s: Die Buchung vom %2$s bis %3$s konnte wegen Überlappungen nicht gespeichert werden: %4$s'), - $this->getFullName(), - $begin->format('d.m.Y H:i'), - $end->format('d.m.Y H:i'), - $e->getMessage() - ) - ); - } - } catch (Exception $e) { - if ($begin->format('Ymd') == $end->format('Ymd')) { - throw new ResourceBookingException( - sprintf( - _('%1$s: Die Buchung vom %2$s bis %3$s konnte aus folgendem Grund nicht gespeichert werden: %4$s'), - $this->getFullName(), - $begin->format('d.m.Y H:i'), - $end->format('H:i'), - $e->getMessage() - ) - ); - } else { - throw new ResourceBookingException( - sprintf( - _('%1$s: Die Buchung vom %2$s bis %3$s konnte aus folgendem Grund nicht gespeichert werden: %4$s'), - $this->getFullName(), - $begin->format('d.m.Y H:i'), - $end->format('d.m.Y H:i'), - $e->getMessage() - ) - ); - } - } - - return $booking; - } - - /** - * This method creates a simple request for this resource. - * A simple request is not bound to a date, metadate - * or course object and its time ranges. Instead the time - * range is specified directly. - * Note that simple resource requests do not support recurrence. - * - * @param User $user The user who wishes to create a simple request. - * @param DateTime $begin The begin timestamp of the request. - * @param DateTime $end The end timestamp of the request. - * @param string $comment A comment for the resource request. - * @param int $preparation_time The requested preparation time before - * the begin of the requested time range. This parameter must be - * specified in seconds. Only positive values are accepted. - * - * @return ResourceRequest A resource request object. - * @throws AccessDeniedException If the user is not permitted - * to request this resource. - * @throws InvalidArgumentException If the the timestamps provided by - * $begin and $end are invalid or if $begin is greater than or equal - * to $end which results in an invalid time range. - * @throws ResourceUnavailableException If the resource is not available - * in the selected time range. - * @throws ResourceRequestException If the resource request - * cannot be stored. - * - */ - public function createSimpleRequest( - User $user, - DateTime $begin, - DateTime $end, - $comment = '', - $preparation_time = 0 - ) - { - //All users are permitted to create a request, - //if the resource is requestable. - - if (!$this->requestable) { - throw new InvalidArgumentException( - _('Diese Ressource kann nicht angefragt werden!') - ); - } - - if ($begin > $end) { - throw new InvalidArgumentException( - _('Der Startzeitpunkt darf nicht hinter dem Endzeitpunkt liegen!') - ); - } elseif ($begin == $end) { - throw new InvalidArgumentException( - _('Startzeitpunkt und Endzeitpunkt sind identisch!') - ); - } - - if (!$this->isAvailable($begin, $end)) { - throw new ResourceUnavailableException( - sprintf( - _('Die Ressource %1$s ist im Zeitraum von %2$s bis %3$s nicht verfügbar!'), - $this->name, - $begin->format('d.m.Y H:i'), - $end->format('d.m.Y H:i') - ) - ); - } - - $request = new ResourceRequest(); - $request->resource_id = $this->id; - $request->category_id = $this->category_id; - $request->user_id = $user->id; - - $request->begin = $begin->getTimestamp(); - $request->end = $end->getTimestamp(); - $request->preparation_time = ( - $preparation_time > 0 - ? $preparation_time - : 0 - ); - - $request->closed = '0'; - $request->comment = $comment; - - if (!$request->store()) { - throw new ResourceRequestException( - sprintf( - _('Die Anfrage zur Ressource %s konnte nicht gespeichert werden!'), - $this->name - ) - ); - } - - return $request; - } - - - /** - * This method creates a resource request for this resource. - * - * @param User $user The user who wishes to create a request. - * @param string|array $date_range_ids One or more IDs of Stud.IP objects - * which can provide at least one time range. - * Objects which fulfill this requirement are - * course dates (CourseDate objects), - * cycle dates (SeminarCycleDate objects) - * and courses (Course objects). - * If only one ID is provided it can be passed as string. - * If multiple IDs are provided they have to be passed as array. - * @param string $comment A comment for the resource request. - * @param mixed[] $properties The wishable properties - * for the resource request. The format of the array is as follows: - * [ - * 'property name' => 'property state' - * ] - * @param int $preparation_time The requested preparation time before - * the begin of the requested time range. This parameter must be - * specified in seconds. Only positive values are accepted. - * - * @return ResourceRequest A resource request object. - * @throws InvalidArgumentException If $date_range_id is not set. - * or no object which can provide at least one time range - * can be found with the specified ID. - * @throws ResourceNoTimeRangeException If no time range can be found - * by looking at the object, specified by its ID in $date_range_id. - * @throws ResourceUnavailableException If the resource is not available - * in the selected time range. - * @throws ResourceRequestException If the resource request - * cannot be stored. - * - */ - public function createRequest( - User $user, - $date_range_ids = null, - $comment = '', - $properties = [], - $preparation_time = 0 - ) - { - if (!$date_range_ids) { - throw new InvalidArgumentException( - _('Es wurde keine ID eines Objektes angegeben, welches Zeiträume für eine Ressourcenanfrage liefern kann!') - ); - } - - if (!$this->requestable) { - throw new InvalidArgumentException( - _('Diese Ressource kann nicht angefragt werden!') - ); - } - - //We must get the date ranges by looking at $date_range_id - //and the object which lies behind that ID. - - if (!is_array($date_range_ids)) { - $date_range_ids = [$date_range_ids]; - } - - $time_ranges = []; - foreach ($date_range_ids as $date_range_id) { - $time_ranges = array_merge( - $time_ranges, - ResourceManager::getTimeRangesFromRangeId( - $date_range_id - ) - ); - } - - if (!$time_ranges) { - //We couldn't find any time range. - throw new ResourceNoTimeRangeException( - sprintf( - _('Es konnte kein Zeitbereich für die Anfrage der Ressource %s gefunden werden.'), - $this->name - ) - ); - } - - //Default resource request handling: - //Check if the resource is available in all requested time ranges. - - foreach ($time_ranges as $time_range) { - if (!$this->isAvailable($time_range[0], $time_range[1])) { - throw new ResourceUnavailableException( - sprintf( - _('Die Ressource %1$s ist im Zeitraum vom %2$s bis %3$s nicht verfügbar!'), - $this->name, - $time_range[0]->format('d.m.Y H:i'), - $time_range[1]->format('d.m.Y H:i') - ) - ); - } - } - - //We must check, if all the properties exist: - if ($properties and is_array($properties)) { - foreach ($properties as $property_name => $property_state) { - $property_object = ResourcePropertyDefinition::findByName( - $property_name - ); - - if (!$property_object) { - throw new ResourcePropertyException( - sprintf( - _('Die Ressourceneigenschaft %s ist nicht definiert!'), - $property_name - ) - ); - } elseif (count($property_object) > 1) { - throw new ResourcePropertyException( - sprintf( - _('Es gibt mehrere Ressourceneigenschaften mit dem Namen %s!'), - $property_name - ) - ); - } - - //$property_object is an array of ResourcePropertyDefinition objects: - $property_data[] = [ - 'object' => $property_object[0], - 'state' => $property_state - ]; - } - } - - $request = new ResourceRequest(); - $request->resource_id = $this->id; - $request->category_id = $this->category_id; - $request->user_id = $user->id; - $request->comment = $comment; - $request->preparation_time = ( - $preparation_time > 0 - ? $preparation_time - : 0 - ); - $request->closed = '0'; - - //Resolve the date range ID and set the - //appropriate field in the request object: - if (count($date_range_ids) <= 1) { - $course_date = CourseDate::find($date_range_ids[0]); - if ($course_date) { - $request->termin_id = $course_date->id; - } else { - $cycle_date = SeminarCycleDate::find($date_range_ids[0]); - if ($cycle_date) { - $request->metadate_id = $cycle_date->id; - } else { - $course = Course::find($date_range_ids[0]); - if ($course) { - $request->course_id = $course->id; - } - } - } - - if (!$request->store()) { - throw new ResourceRequestException( - sprintf( - _('Die Anfrage zur Ressource %s konnte nicht gespeichert werden!'), - $this->name - ) - ); - } - } else { - if (!$request->store()) { - throw new ResourceRequestException( - sprintf( - _('Die Anfrage zur Ressource %s konnte nicht gespeichert werden!'), - $this->name - ) - ); - } - - //More than one entry: - //We must use ResourceBookingAppointment objects. - foreach ($date_range_ids as $date_range_id) { - $appointment_id = null; - $course_date = CourseDate::find($date_range_id); - if ($course_date) { - $appointment_id = $course_date->id; - } else { - $cycle_date = SeminarCycleDate::find($date_range_id); - if ($cycle_date) { - $appointment_id = $cycle_date->id; - } else { - $course = Course::find($date_range_id); - if ($course) { - $appointment_id = $course->id; - } - } - } - - if ($appointment_id) { - $rra = new ResourceRequestAppointment(); - $rra->request_id = $request->id; - $rra->appointment_id = $appointment_id; - if (!$rra->store()) { - throw new ResourceRequestException( - _('Die Terminzuordnungen zur Anfrage konnten nicht gespeichert werden!') - ); - } - } - } - } - - //The request has been created: Now we need to link the properties: - if(!empty($property_data)) { - foreach ($property_data as $property) { - $rrp = new ResourceRequestProperty(); - $rrp->request_id = $request->id; - $rrp->property_id = $property['object']->id; - $rrp->state = intval($property['state']); - if (!$rrp->store()) { - throw new InvalidResourceRequestException( - sprintf( - _('%1$s: Die Eigenschaft %2$s zur Anfrage konnte nicht gespeichert werden!'), - $this->getFullName(), - $property['object']->name - ) - ); - } - } - } - return $request; - } - - /** - * Creates a lock booking for this resource. - * - * @param User $user The user who wishes to create a lock booking. - * @param DateTime $begin The begin of the lock time range. - * @param DateTime $end The end of the lock time range. - * @param string $internal_comment An optional comment for the - * lock booking which is intended to be used internally - * in the room and resource administration staff. - * - * @return ResourceBooking A ResourceBooking object. - * @throws ResourceUnavailableException If a lock booking already - * exists in the specified time range. - * - * @throws AccessDeniedException If the user does not have sufficient - * permissions to lock this resource. - */ - public function createLock( - User $user, - DateTime $begin, - DateTime $end, - $internal_comment = '' - ) - { - if (!$this->userHasPermission($user, 'admin', [$begin, $end])) { - throw new AccessDeniedException( - sprintf( - _('%s: Unzureichende Berechtigungen zum Erstellen einer Sperrbuchung!'), - $this->getFullName() - ) - ); - } - - if ($this->isLocked($begin, $end)) { - throw new ResourceUnavailableException( - sprintf( - _('%1$s: Im Zeitbereich von %2$s bis %3$s gibt es bereits Sperrbuchungen!'), - $this->getFullName(), - $begin->format('d.m.Y H:i'), - $end->format('d.m.Y H:i') - ) - ); - } - - $lock = new ResourceBooking(); - $lock->booking_type = ResourceBooking::TYPE_LOCK; - $lock->range_id = $user->id; - $lock->resource_id = $this->id; - $lock->begin = $begin->getTimestamp(); - $lock->end = $end->getTimestamp(); - $lock->internal_comment = $internal_comment; - - if (!$lock->store()) { - throw new ResourceBookingException( - sprintf( - _('%1$s: Fehler beim Speichern der Sperrbuchung für den Zeitbereich von %2$s bis %3$s!'), - $begin->format('d.m.Y H:i'), - $end->format('d.m.Y H:i') - ) - ); - } - - return $lock; - } - - /** - * Retrieves the properties grouped by their property groups - * and in the order specified in that group. - * - * @param string[] excluded_properties An array with the names - * of the properties that shall be excluded from the result set. - * - * @return array An array with the group names as keys and the properties - * in the second array dimension. The structure of the array - * is as follows: - * [ - * group1 name => [ - * property1, - * property2, - * ... - * ], - * group2 name => [ - * ... - * ] - * ] - */ - public function getGroupedProperties($excluded_properties = []) - { - if (is_array($excluded_properties) && count($excluded_properties)) { - $properties = ResourceProperty::findBySql( - "INNER JOIN resource_property_definitions rpd - USING (property_id) - LEFT JOIN resource_property_groups rpg - ON rpd.property_group_id = rpg.id - WHERE - resource_properties.resource_id = :resource_id - AND - rpd.name NOT IN ( :excluded_properties ) - ORDER BY - rpg.position ASC, rpg.name ASC, - rpd.property_group_pos ASC, rpd.name ASC", - [ - 'resource_id' => $this->id, - 'excluded_properties' => $excluded_properties - ] - ); - } else { - $properties = ResourceProperty::findBySql( - "INNER JOIN resource_property_definitions rpd - USING (property_id) - LEFT JOIN resource_property_groups rpg - ON rpd.property_group_id = rpg.id - WHERE - resource_properties.resource_id = :resource_id - ORDER BY - rpg.position ASC, rpg.name ASC, - rpd.property_group_pos ASC, rpd.name ASC", - [ - 'resource_id' => $this->id - ] - ); - } - - if (!$properties) { - return []; - } - - $property_groups = []; - foreach ($properties as $property) { - if (!$property->state) { - continue; - } - $group_name = ''; - if (!empty($property->definition->group->name)) { - $group_name = $property->definition->group->name; - } - if (empty($property_groups[$group_name]) || !is_array($property_groups[$group_name])) { - $property_groups[$group_name] = []; - } - $property_groups[$group_name][] = $property; - } - - return $property_groups; - } - - - /** - * Determines wheter this resource has a property - * with the specified name. - * - * @param string $name The name of the resource property. - * - * @return bool True, if this resource has a property with - * the specified name, false otherwise. - */ - public function propertyExists($name = '') - { - if (!$name) { - return false; - } - - $db = DBManager::get(); - - $exists_stmt = $db->prepare( - "SELECT TRUE FROM resource_properties - INNER JOIN resource_property_definitions rpd - ON resource_properties.property_id = rpd.property_id - WHERE resource_properties.resource_id = :resource_id - AND rpd.name = :name"); - - $exists_stmt->execute( - [ - 'resource_id' => $this->id, - 'name' => $name - ] - ); - - $exists = $exists_stmt->fetchColumn(0); - - return (bool)$exists; - } - - /** - * Retrieves a ResourceProperty object for a property of this resource - * which has the specified name. If the property has not been set for this - * resource, but is defined for this resource's category, a new - * ResourceProperty object will be created, stored and returned. - * - * @param string $name The name of the resource property. - * - * @return ResourceProperty|null Either a ResourceProperty object for - * the resource property matching the specified name or null, - * if no resource property with the specified name can be found. - * @throws InvalidResourceCategoryException If this resource category - * doesn't match the category of the resource object. - * - */ - public function getPropertyObject(string $name) - { - if (!$this->propertyExists($name)) { - if ($name === 'geo_coordinates') { - return null; - } - //A property with the name $name does not exist for this - //resource object. If it is a defined property - //we can still try to create it: - - if ($this->category->hasProperty($name)) { - $property = $this->category->createDefinedResourceProperty( - $this, - $name - ); - - $property->store(); - return $property; - } else { - return null; - } - } - return ResourceProperty::findOneBySql( - "INNER JOIN resource_property_definitions rpd - ON resource_properties.property_id = rpd.property_id - WHERE resource_properties.resource_id = :resource_id - AND rpd.name = :name", - [ - 'resource_id' => $this->id, - 'name' => $name - ] - ); - } - - /** - * Returns all info-label properties - * - * @return SimpleCollection - */ - public function getInfolabelProperties() - { - return SimpleCollection::createFromArray( - ResourceProperty::findBySQL('INNER JOIN `resource_property_definitions` USING (`property_id`) - WHERE `info_label` = 1 AND `state` != "" AND `resource_id` = ?', [$this->id] - ) - ); - } - - /** - * Returns the state of the property specified by $name. - * If the property has not been set for this resource, but is defined - * for this resource's category, a new ResourceProperty object - * will be created, stored and its state will be returned. - * - * @param string $name The name of the resource property. - * - * @return string|null The state of the specified property or null - * if the propery can't be found. - */ - public function getProperty(string $name) - { - if (!$this->propertyExists($name)) { - //A property with the name $name does not exist for this - //resource object. If it is a defined property - //we can still try to create it: - - if ($this->category->hasProperty($name)) { - $property = $this->category->createDefinedResourceProperty( - $this, - $name, - '' - ); - - $property->store(); - return $property->state; - } else { - return null; - } - } - - $db = DBManager::get(); - - $value_stmt = $db->prepare( - "SELECT resource_properties.state FROM resource_properties - INNER JOIN resource_property_definitions rpd - ON resource_properties.property_id = rpd.property_id - WHERE resource_properties.resource_id = :resource_id - AND rpd.name = :name"); - - $value_stmt->execute( - [ - 'resource_id' => $this->id, - 'name' => $name - ] - ); - - $value = $value_stmt->fetchColumn(0); - - if (!$value) { - return null; - } - - return $value; - } - - /** - * Retrieves an object by the state of a property of this resource, - * specified by the property's name. - * This method is useful for properties of type user, institute - * or fileref. Those properties store IDs of User, Institute - * or FileRef objects. Therefore the IDs can be resolved directly - * to get the corresponding User, Institute or FileRef object directly. - * - * @param string $name The name of the resource property. - * - * @return SimpleORMap|null A SimpleORMap-based object or null, - * if no such object can be retrieved from the property's state. - */ - public function getPropertyRelatedObject(string $name) - { - //Get the property state first: - $property = $this->getPropertyObject($name); - - //Now we return the object which is referenced by the property's state: - - if ($property) { - switch ($property->definition->type) { - case 'user': - return User::find($property->state); - case 'institute': - return Institute::find($property->state); - case 'fileref' : - return FileRef::find($property->state); - default: - //For all other property types where we cannot create an object - //we return the raw state value: - return $property->state; - } - } - return null; - } - - /** - * Sets a specified property of this resource to the specified state. - * If the property has not been set for this resource, but is defined - * for this resource's category, a new ResourceProperty object - * will be created, stored and its state will be returned. - * - * @param string $name The name of the resource property. - * @param mixed $state The state of the resource property. - * @param User|null $user The user who wishes to set the property. - * - * @return bool True, if the property state could be set, false otherwise. - */ - public function setProperty(string $name, $state = '', $user = null) - { - if (!($user instanceof User)) { - $user = User::findCurrent(); - if (!$user) { - //We cannot continue without a user object! - return false; - } - } - - //Get the minimum permission level required for modifying the property: - - if (!$this->userHasPermission($user, 'admin')) { - throw new AccessDeniedException( - sprintf( - _('Unzureichende Berechtigungen zum Ändern der Ressource %s!'), - $this->name - ) - ); - } - if (!$this->category->userHasPropertyWritePermissions($name, $user, $this)) { - throw new AccessDeniedException( - sprintf( - _('Unzureichende Berechtigungen zum Ändern der Eigenschaft %s!'), - $name - ) - ); - } - - if (!$this->propertyExists($name)) { - //A property with the name $name does not exist for this - //resource object. If it is a defined property - //we can still try to create it: - - if ($this->category->hasProperty($name)) { - $property = $this->category->createDefinedResourceProperty( - $this, - $name, - $state - ); - return $property->store(); - } else { - return false; - } - } - - $property = $this->getPropertyObject($name); - - if ($property) { - $property->state = $state; - if ($property->isDirty()) { - return $property->store(); - } - return true; - } - - return false; - } - - /** - * Sets the properties (specified by their names) to the specified values. - * - * @param array $properties The properties array in the format "key-value". - * The array keys must contain the property name while the - * items of the array contain the values. - * Example: - * ['bar' => 'foo']: Sets the value 'foo' for the property - * with the name 'bar'. - * - * @param User|null $user The user who wishes to set the properties. - * If this is left empty, the current user will be used. - * - * @return array If properties cannot be set, their names (as key) and the - * error messages (if any) are returned. - * The array has the following structure: - * [ - * (property name) => (error message or empty string) - * ] - */ - public function setPropertiesByName(array $properties, User $user) - { - $failed_properties = []; - - if (!($user instanceof User)) { - $user = User::findCurrent(); - if (!$user) { - //No property can be set. - foreach ($properties as $name => $state) { - $failed_properties[$name] = ''; - } - return $failed_properties; - } - } - - foreach ($properties as $name => $state) { - try { - $this->setProperty($name, $state, $user); - } catch (Exception $e) { - $this->failed_properties[$name] = $e->getMessage(); - } - } - - return $failed_properties; - } - - /** - * Sets the properties (specified by their IDs) to the specified values. - * - * @param array $properties The properties array in the format "key-value". - * The array keys must contain the property-ID while the - * items of the array contain the values. - * Example: - * ['1' => 'foo']: Sets the value 'foo' for the property - * with the ID '1'. - * - * @param User|null $user The user who wishes to set the properties. - * If this is left empty, the current user will be used. - * - * @return array If properties cannot be set, their ids (as key) and the - * error messages (if any) are returned. - * The array has the following structure: - * [ - * (property-ID) => (error message or empty string) - * ] - */ - public function setPropertiesById(array $properties, User $user = null) - { - $failed_properties = []; - - if (!($user instanceof User)) { - $user = User::findCurrent(); - if (!$user) { - //No property can be set. - foreach ($properties as $id => $state) { - $failed_properties[$id] = ''; - } - return $failed_properties; - } - } - - foreach ($properties as $id => $state) { - $property = ResourcePropertyDefinition::find($id); - if (!$property) { - //Invalid property: - $this->failed_properties[$id] = - _('Die Eigenschaft wurde nicht gefunden!'); - continue; - } - try { - $this->setProperty($property->name, $state, $user); - } catch (Exception $e) { - $failed_properties[$id] = $e->getMessage(); - } - } - - return $failed_properties; - } - - /** - * Determines if the specified user has sufficient permissions to edit - * the property specified by its name. - * - * @param string $name The name of the resource property. - * @param user $user The user whose edit permissions shall be checked. - * - * @return bool True, if the user has edit permissions for the property, - * false otherwise. - */ - public function isPropertyEditable(string $name, User $user) - { - return $this->category->userHasPropertyWritePermissions($name, $user, $this); - } - - /** - * Sets the state of a property by its definition_id rather than its name. - * - * @param string $property_definition_id The definition-ID of the property. - * @param string $state The state of the property. - * - * @return bool True, if the property state can be stored, false otherwise. - * @throws ResourcePropertyStateException If the provided state is invalid - * for the specified resource property. - * - */ - public function setPropertyByDefinitionId($property_definition_id = null, $state = null) - { - if (!$property_definition_id and !$state) { - return false; - } - - //Get property definition: - $definition = ResourcePropertyDefinition::find($property_definition_id); - if (!$definition) { - return false; - } - - //Check if the state matches the property definition's rules: - $definition->validateState($state); - - //Check if the property for this resource already exists. - //If so, update it. Otherwise create it. - - $property = ResourceProperty::findOneBySql( - '(property_id = :property_id) AND (resource_id = :resource_id)', - [ - 'property_id' => $definition->id, - 'resource_id' => $this->id - ] - ); - - if (!$property) { - $property = new ResourceProperty(); - $property->property_id = $definition->id; - $property->resource_id = $this->id; - } - - $property->state = $state; - return $property->store(); - } - - /** - * Sets the property state by specifying an SimpleORMap object. - * This method is meant for resource properties of type user, - * institute or fileref. - * - * @param string $name The name of the resource property. - * @param SimpleORMap $object The object for the resource property. - * - * @return bool True, if the property has been saved, false otherwise. - */ - public function setPropertyRelatedObject(string $name, SimpleORMap $object) - { - //Get the property state first: - $property = $this->getPropertyObject($name); - - if (!$property) { - return false; - } - - //Now we return the object which is referenced by the property's state: - - switch ($property->definition->type) { - case 'user': - if (!($object instanceof User)) { - throw new ResourcePropertyException( - _("Eine Ressourceneigenschaft vom Typ 'user' benötigt ein Nutzer-Objekt zur Wertzuweisung!") - ); - } - break; - case 'institute': - if (!($object instanceof Institute)) { - throw new ResourcePropertyException( - _("Eine Ressourceneigenschaft vom Typ 'institute' benötigt ein Institut-Objekt zur Wertzuweisung!") - ); - } - break; - case 'fileref': - if (!($object instanceof FileRef)) { - throw new ResourcePropertyException( - _("Eine Ressourceneigenschaft vom Typ 'fileref' benötigt ein FileRef-Objekt zur Wertzuweisung!") - ); - } - break; - default: - break; - } - - //When no exception is thrown above we can set the object's ID - //as the property's state: - $property->state = $object->id; - - return $property->store(); - } - - /** - * Deletes a property for a resource. - * - * @param string $name The name of the property to be deleted. - * - * @param User $user The user who wishes to delete the property. - * @return number - */ - public function deleteProperty(string $name, User $user) - { - //Get the user object and the minimum permission level - //required for modifying the property: - - if (!$this->userHasPermission($user, 'admin')) { - throw new AccessDeniedException( - sprintf( - _('Unzureichende Berechtigungen zum Ändern der Ressource %s!'), - $this->name - ) - ); - } - if (!$this->category->userHasPropertyWritePermissions($name, $user)) { - throw new AccessDeniedException( - sprintf( - _('Unzureichende Berechtigungen zum Löschen der Eigenschaft %s!'), - $name - ) - ); - } - - return ResourceProperty::deleteBySql( - "INNER JOIN resource_property_definitions rpd - ON resource_properties.property_id = rpd.property_id - WHERE - rpd.name = :name AND resource_properties.resource_id = :resource_id", - [ - 'name' => $name, - 'resource_id' => $this->id - ] - ); - } - - /** - * Returns the path for the resource's image. - * If the resource has no image the path for a general - * resource icon will be returned. - * - * Classes derived from the Resource class should only re-implement - * this method if they have an alternative storage method for - * resource pictures than the Stud.IP file system. - * - * @return string The URL to the resource picture. - */ - public function getPictureUrl() - { - return ''; - } - - /** - * Returns the default picture for the resource class. - * - * Classes derived from Resource should re-implement this method - * if they want to get a different default picture than the resource icon. - * The call to getPictureUrl will call the getDefaultPictureUrl method - * from the derived class. - * - * @return string The URL to the picture. - */ - public function getDefaultPictureUrl() - { - return $this->getIcon()->asImagePath(); - } - - /** - * Returns the Icon for the resource class. - * - * Classes derived from Resource should re-implement this method - * if they want to get a different icon than the resource icon. - * @param string $role - * @return Icon The icon for the resource. - */ - public function getIcon($role = Icon::ROLE_INFO) - { - return Icon::create('resources', $role); - } - - /** - * Returns all properties in a two-dimensional array with the following - * property data inside of the second dimension: - * [ - * 'name' => (the property's name) - * 'display_name' => (the display name of the property) - * 'type' => (the property's type) - * 'state' => (the property's state) - * 'requestable' => (if the property is requestable or not (true or false)) - * ] - * - * @param bool $only_requestable_properties If only requestable properties - * shall be returned set this to true. If all properties shall be - * returned, set this to false. - * - * @return array[] A two-dimensional array containing property data. - */ - public function getPropertyArray($only_requestable_properties = false) - { - $property_array = []; - - if ($this->properties) { - foreach ($this->properties as $property) { - if ($only_requestable_properties) { - $category_property = ResourceCategoryProperty::findByNameAndCategoryId( - $property->name, - $this->category_id - ); - - if ($category_property) { - if ($category_property->requestable) { - $property_array[] = [ - 'name' => $property->name, - 'display_name' => $property->display_name, - 'type' => $property->type, - 'state' => $property->state, - 'requestable' => $property->isRequestable() - ]; - } - } - } else { - $property_array[] = [ - 'name' => $property->name, - 'display_name' => $property->display_name, - 'type' => $property->type, - 'state' => $property->state, - 'requestable' => $property->isRequestable() - ]; - } - } - } - return $property_array; - } - - /** - * Shortcut method for ResourceBooking::countByResourceAndTimeRanges. - * Determines whether normal resource bookings exist - * in the specified time range. - * - * @param DateTime $begin Time range start timestamp. - * - * @param DateTime $end Time range end timestamp. - * - * @param array $excluded_booking_ids The IDs of bookings that shall - * be excluded from the determination of the "assigned" status. - * - * @return bool True, if the resource is assigned in the specified - * time range, false otherwise. - */ - public function isAssigned( - DateTime $begin, - DateTime $end, - $excluded_booking_ids = [] - ) - { - return ResourceBooking::countByResourceAndTimeRanges( - $this, - [ - [ - 'begin' => $begin->getTimestamp(), - 'end' => $end->getTimestamp() - ] - ], - [0], - $excluded_booking_ids - ) > 0; - } - - /** - * Shortcut method for ResourceBooking::countByResourceAndTimeRanges. - * Determines whether resource reservations exist - * in the specified time range. - * - * @param DateTime $begin Time range start timestamp. - * - * @param DateTime $end Time range end timestamp. - * - * @param array $excluded_reservation_ids The IDs of reservation bookings that shall - * be excluded from the determination of the "reserved" status. - * - * @return bool True, if the resource is reserved in the specified - * time range, false otherwise. - */ - public function isReserved( - DateTime $begin, - DateTime $end, - $excluded_reservation_ids = [] - ) - { - //One second is added to the begin timestamp to avoid - //getting "false" overlaps where another booking ends on exactly - //the begin timestamp. - return ResourceBooking::countByResourceAndTimeRanges( - $this, - [ - [ - 'begin' => $begin->getTimestamp(), - 'end' => $end->getTimestamp() - ] - ], - [1, 3], - $excluded_reservation_ids - ) > 0; - } - - /** - * Shortcut method for ResourceBooking::countByResourceAndTimeRanges. - * Determines whether resource locks exist - * in the specified time range. - * - * @param DateTime $begin Time range start timestamp. - * - * @param DateTime $end Time range end timestamp. - * - * @param array $excluded_lock_ids The IDs of lock bookings that shall - * be excluded from the determination of the "locked" status. - * - * @return bool True, if the resource is locked in the specified - * time range, false otherwise. - */ - public function isLocked( - DateTime $begin, - DateTime $end, - $excluded_lock_ids = [] - ) - { - //One second is added to the begin timestamp to avoid - //getting "false" overlaps where another booking ends on exactly - //the begin timestamp. - return ResourceBooking::countByResourceAndTimeRanges( - $this, - [ - [ - 'begin' => $begin->getTimestamp(), - 'end' => $end->getTimestamp() - ] - ], - [2], - $excluded_lock_ids - ) > 0; - } - - /** - * Determines, if the resource is available (not assigned or locked) - * in a specified time range. - * - * @param DateTime $begin Time range start timestamp. - * @param DateTime $end Time range end timestamp. - * - * @param array $excluded_booking_ids The IDs of available bookings that shall - * be excluded from the determination of the "available" status. - * - * @return bool True, if the resource is available in the specified - * time range, false otherwise. - */ - public function isAvailable( - DateTime $begin, - DateTime $end, - $excluded_booking_ids = [] - ) - { - return ResourceBooking::countByResourceAndTimeRanges( - $this, - [ - [ - 'begin' => $begin->getTimestamp(), - 'end' => $end->getTimestamp() - ] - ], - [0, 2], - $excluded_booking_ids - ) == 0; - } - - /** - * Determines, if the resource is available (not assigned or locked) - * in the time ranges specified by a resource request. - * - * @param ResourceRequest $request A resource request object. - * - * @return bool True, if the resource is available in the - * time ranges of the resource request, false otherwise. - */ - public function isAvailableForRequest(ResourceRequest $request) - { - $time_intervals = $request->getTimeIntervals(true); - if (!$time_intervals) { - //Without a single time interval we cannot check - //if the resource is available. - return false; - } - foreach ($time_intervals as $time_interval) { - $begin = new DateTime(); - $end = new DateTime(); - $begin->setTimestamp($time_interval['begin']); - $end->setTimestamp($time_interval['end']); - - if (!$this->isAvailable($begin, $end)) { - //The resource is not available in the time interval. - //We can stop here and return false. - return false; - } - - //If code execution reaches this point the resource is - //available in all time intervals of the resource request: - return true; - } - } - - /** - * Returns the full (localised) name of the resource. - * - * @return string The full name of the resource. - */ - public function getFullName() - { - return sprintf( - _('Ressource %s'), - $this->name - ); - } - - /** - * Sets the permission for one user for this resource. - * - * @param User $user The user whose permission shall be set. - * @param string $perm The permission level for the specified user. - * The levels 'user', 'autor', 'tutor' and 'admin' are allowed. - * - * @return bool True, if the permission has been stored successfully, - * false otherwise. - */ - public function setUserPermission(User $user, $perm = 'autor') - { - if (!in_array($perm, ['user', 'autor', 'tutor', 'admin'])) { - return false; - } - - $perm_object = ResourcePermission::findOneBySql( - '(user_id = :user_id) AND (resource_id = :resource_id)', - [ - 'user_id' => $user->id, - 'resource_id' => $this->id - ] - ); - - if (!$perm_object) { - $perm_object = new ResourcePermission(); - $perm_object->user_id = $user->id; - $perm_object->resource_id = $this->id; - } - - $perm_object->perms = $perm; - $stored = (bool)$perm_object->store(); - if ($stored) { - if (!isset(self::$permission_cache[$this->id])) { - self::$permission_cache[$this->id] = []; - } - //Update the permission cache. - self::$permission_cache[$this->id][$user->id] = $perm; - } - return $stored; - } - - /** - * Deletes the permission a specified user has on this resource. - * - * @param User $user The user whose permission shall be deleted. - * - * @return bool True - */ - public function deleteUserPermission(User $user) - { - $deleted = ResourcePermission::deleteBySql( - '(user_id = :user_id) AND (resource_id = :resource_id)', - [ - 'user_id' => $user->id, - 'resource_id' => $this->id - ] - ); - - if ($deleted && is_array(self::$permission_cache[$this->id])) { - //Update the permission cache. - self::$permission_cache[$this->id][$user->id] = null; - } - - return true; - } - - /** - * Deletes all permissions of all users for this resource. - * - * @return bool True - */ - public function deleteAllPermissions() - { - ResourcePermission::deleteBySql( - 'resource_id = :resource_id', - [ - 'resource_id' => $this->id - ] - ); - - //Update the permission cache: - self::$permission_cache[$this->id] = []; - - return true; - } - - /** - * Retrieves the permission level a specified user - * has on this resource. - * - * Setting the optional $time_range parameter will also enable checks for - * temporary global permissions. - * - * @param User $user The user whose permission shall be retrieved. - * - * @param array $time_range (DateTime) This is an optional parameter that can - * be used to pass two DateTime objects to this method. The first object - * will be treated as the begin timestamp and the second one as the - * end timestamp. - * - * @param bool $permanent_only Whether to retrieve only permanent permissions - * (true) or permanent and temporary permissions (false). - * Defaults to false. - * - * @return string The permission level, expressed as string. - * The level can be 'user', 'autor', 'tutor' or 'admin'. - */ - public function getUserPermission(User $user, $time_range = [], $permanent_only = false) - { - if (ResourceManager::getGlobalResourcePermission($user) === 'admin') { - return 'admin'; - } - - //Check for a temporary permission first: - - $perm_string = ''; - $temp_perm = null; - - $begin = time(); - $end = $begin; - - //If $time range is set and contains two DateTime objects - //we can include that in the search for temporary permissions. - if ($time_range) { - if ($time_range[0] instanceof DateTime) { - $begin = $time_range[0]->getTimestamp(); - } else { - $begin = $time_range[0]; - } - if ($time_range[1] instanceof DateTime) { - $end = $time_range[1]->getTimestamp(); - } else { - $end = $time_range[1]; - } - } - - if (!$permanent_only) { - $temp_perm = ResourceTemporaryPermission::findOneBySql( - '(resource_id = :resource_id) AND (user_id = :user_id) - AND (begin <= :begin) AND (end >= :end)', - [ - 'resource_id' => $this->id, - 'user_id' => $user->id, - 'begin' => $begin, - 'end' => $end - ] - ); - } - - if ($temp_perm) { - $perm_string = $temp_perm->perms; - } else { - //No temporary permission exist or has been retrieved. - //Check for a "normal" permission. - $cached_perms = self::$permission_cache[$this->id][$user->id] ?? null; - if ($cached_perms === null) { - //The permission of the specified user is not in the - //permission cache. Load it from the database and store - //it in the permission cache before returning it. - $perms = ResourcePermission::findOneBySql( - '(resource_id = :resource_id) AND (user_id = :user_id)', - [ - 'resource_id' => $this->id, - 'user_id' => $user->id - ] - ); - if ($perms) { - if (!isset(self::$permission_cache[$this->id])) { - self::$permission_cache[$this->id] = []; - } - self::$permission_cache[$this->id][$user->id] = $perms->perms; - $perm_string = $perms->perms; - } - } else { - $perm_string = $cached_perms; - } - } - - if (!$perm_string) { - //A user which doesn't have special permissions for this resource - //can have global resource permissions: - $global_perm = ResourceManager::getGlobalResourcePermission($user); - if ($global_perm) { - //Set the permission cache: - if (!isset(self::$permission_cache[$this->id])) { - self::$permission_cache[$this->id] = []; - } - self::$permission_cache[$this->id][$user->id] = $global_perm; - } - $perm_string = $global_perm; - } - //Now we must check for global resource locks: - - if ($perm_string && $time_range && $this->lockable) { - if (GlobalResourceLock::isLocked($begin, $end)) { - //A permission level exists for the user. - //The user gets "user" permissions in case - //a global lock is active. - $perm_string = 'user'; - } - } - - //No global resource lock exists. We must return - //the permission string if it is set: - if ($perm_string) { - return $perm_string; - } - - return ''; - } - - /** - * Determines if a user has the specified permission. - * - * @param ?User $user The user whose permissions shall be checked on this - * resource object. May be null. - * @param string $permission The permission level. - * @param $time_range @TODO - * - * @return bool True, if the specified user has the specified permission, - * false otherwise. - */ - public function userHasPermission( - ?User $user, - string $permission = 'user', - array $time_range = [] - ) - { - if (!in_array($permission, ['user', 'autor', 'tutor', 'admin']) || $user === null) { - return false; - } - - - if (ResourceManager::getGlobalResourcePermission($user) === 'admin') { - return true; - } - - $perm_level = $this->getUserPermission($user, $time_range); - - if ($permission === 'user') { - //No check for global resource locks here: - //If only user permissions are requested we can safely grant them - //since 'user' users may only perform reading actions but - //no writing actions. - if (in_array($perm_level, ['user', 'autor', 'tutor', 'admin'])) { - return true; - } else { - return false; - } - } elseif ($permission === 'autor') { - if (in_array($perm_level, ['autor', 'tutor', 'admin'])) { - return true; - } else { - return false; - } - } elseif ($permission === 'tutor') { - if (in_array($perm_level, ['tutor', 'admin'])) { - return true; - } else { - return false; - } - } elseif ($permission === 'admin') { - if ($perm_level == 'admin') { - return true; - } else { - return false; - } - } - //Code execution should be finished at this point. - //If this point is reached the user has no permissions for the - //resource management system at all. - return false; - } - - /** - * Determines whether the user may create a child resource - * on this resource. - * - * @param User $user The user whose permission to create a child - * resource shall be checked. - * - * @return bool True, if the user may create a child resource - * on this resource, false otherwise. - */ - public function userMayCreateChild(User $user) - { - return $this->userHasPermission($user, 'admin'); - } - - /** - * Checks if the specified user has sufficient permissions to make resource - * requests, according to the setting RESOURCES_MIN_REQUEST_PERMISSION. - * This permission check is only relevant for creating requests that are not - * bound to a course. - * - * @param User $user The user whose request permissions shall be checked. - * - * @return bool True, if the user has request permissions, false otherwise. - */ - public function userHasRequestRights(User $user) - { - if (!Config::get()->RESOURCES_ALLOW_ROOM_REQUESTS) { - return false; - } - $min_perm = Config::get()->RESOURCES_MIN_REQUEST_PERMISSION; - if (!in_array($min_perm, ['', 'user', 'autor', 'tutor', 'admin'])) { - //Invalid permission level! - return false; - } - if (!$min_perm) { - //No minimum permission set: Every logged-in user - //can create requests. - return true; - } - return $this->userHasPermission($user, $min_perm); - } - - /** - * Determines whether the user may book the resource or not. - * An optional time range can be set to check the user's - * temporary permissions on another date than the current date. - * - * @param User $user The user whose booking permissions shall be checked. - * - * @param int|string|DateTime $begin The begin timestamp of the - * optional time range. - * - * @param int|string|DateTime $end The end timestamp of the - * optional time range. - * - * @return bool True, if the user may book the resource, false otherwise. - */ - public function userHasBookingRights( - User $user, - $begin = null, - $end = null - ) - { - if ($begin && $end) { - $time_range = [$begin, $end]; - } else { - $time_range = []; - } - - //Check the permissions on this resource and the global permissions: - return $this->userHasPermission($user, 'autor', $time_range); - } - - /** - * Determines if the booking plan of the resource is visible for a - * specified user. - * - * @param ?User $user The user whose permission to view the booking plan - * shall be determined. May be null. - * - * @param DateTime[] $time_range An optional time range for the - * permission check. - * @return bool True, if the user can see the resource booking plan, - * false otherwise. - * @see Resource::getUserPermission - * - */ - public function bookingPlanVisibleForUser(?User $user, $time_range = []) - { - return $this->userHasPermission($user, 'user', $time_range); - } - - /** - * Retrieves a parent resource object that matches the specified - * class name. The search stops when either a parent resource - * with the class name is found or when the root resource object - * is reached. - * - * @param string $class_name The class name of the parent. - * - * @return Resource|null Either a resource object or null - * in case a matching parent resource cannot be found. - */ - public function findParentByClassName($class_name = 'Resource') - { - $resource_ids = [$this->id]; - $resource = $this->parent; - - while ($resource) { - //We should check for circular hierarchies first - //to avoid an endless while loop: - if (in_array($resource->id, $resource_ids)) { - //We have a circular hierarchy: this resource is - //the parent of itself which is an invalid state! - throw new InvalidResourceException( - sprintf( - _('Zirkuläre Hierarchie: Die Ressource %1$s ist ein Elternknoten von sich selbst!'), - $resource->name - ) - ); - } - if (is_a($resource->class_name, $class_name, true)) { - //We have found a parent node which has the - //specified class name: return that parent. - return $resource; - } - //The current parent was not the one we were looking for. - //Therefore we must go one layer up in the resource - //hierarchy and continue search: - $resource_ids[] = $resource->id; - $resource = $resource->parent; - } - //The search was not successful: - //We have reached the root resource (whose parent_id field - //is set to an equivalend of NULL) and we haven't found a - //resource matching the specified class name. - return null; - } - - /** - * This method searches the hierarchy below this resource - * to find resources matching the specified class name. - * Via the optional parameter $depth the search can be limited - * to a specific amount of layers. - * - * @param string $class_name The name of the resource class - * where resources shall be found to. - * @param int $depth The (optional) maximum depth below this resource - * which shall be searched. - * @param bool $convert_objects True, if objects shall be converted to - * $class_name (default), false otherwise. - * @param bool $order_by_name Order the children by name. - * Defaults to true. - * - * @return Resource[] An array of resource objects or an empty array - * if no matching resources can be found. - */ - public function findChildrenByClassName( - $class_name = 'Resource', - $depth = 0, - $convert_objects = true, - $order_by_name = true - ) - { - $result = []; - if ($this->children) { - //this resource has children: iterate over them and - //check if they match the search criteria. - foreach ($this->children as $child) { - if (is_a($child->class_name, $class_name, true)) { - if ($convert_objects) { - $result[] = $child->getDerivedClassInstance(); - } else { - $result[] = $child; - } - } - if (($depth > 1) || ($depth == 0)) { - //Search the child and lower depth by one when calling this - //method on the child. - $result = array_merge( - $result, - $child->findChildrenByClassName( - $class_name, - (($depth > 1) ? $depth - 1 : 0), - $convert_objects - ) - ); - } - } - if ($order_by_name) { - usort( - $result, - function ($a, $b) { - if ($a->name == $b->name) { - return 0; - } elseif ($a->name < $b->name) { - return -1; - } else { - return 1; - } - } - ); - } - } - return $result; - } - - /** - * Adds a resource as child resource to this resource. - * - * @param Resource $resource The child resource. - * - * @return bool True on success, false on failure. - */ - public function addChild(Resource $resource) - { - $old_parent = $resource->parent; - $old_parent_id = $resource->parent_id; - - $resource->parent = $this; - $resource->parent_id = $this->id; - - if (!$resource->checkHierarchy()) { - //We must revert the parent fields since $resource - //may be used in other code pieces afterwards. - $resource->parent = $old_parent; - $resource->parent_id = $old_parent_id; - throw new InvalidArgumentException( - sprintf( - _('Die Ressource %1$s (Typ %2$s) kann nicht unterhalb der Ressource %3$s (Typ %4$s) platziert werden!'), - $resource->name, - $resource->class_name, - $this->name, - $this->class_name - ) - ); - } - if ($resource->isDirty()) { - //Only store the resource object if setting the parent_id field - //did change it: - return $resource->store(); - } - //The resource object hasn't changed by setting the parent_id field: - //We can return true. - return true; - } - - /** - * Get all resource requests for the resource in a given timeframe. - * - * @param DateTime $begin Begin of timeframe. - * @param DateTime $end End of timeframe. - * - * @return ResourceRequest[] An array of ResourceRequest objects. - */ - public function getOpenResourceRequests(DateTime $begin, DateTime $end) - { - //We must get all requests that either have a start and end date - //set or that have a start date, repeate end, repeat interval and - //repeat quantity set. - - return ResourceRequest::findByResourceAndTimeRanges( - $this, - [ - [ - 'begin' => $begin->getTimestamp(), - 'end' => $end->getTimestamp() - ] - ], - 0 - ); - } - - /** - * Get all resource bookings for the resource in a given timeframe. - * - * @param DateTime $begin Begin of timeframe. - * @param DateTime $end End of timeframe. - * @param array $booking_types - * - * @return ResourceBooking[] An array of ResourceBooking objects. - */ - public function getResourceBookings(DateTime $begin, DateTime $end, array $booking_types = [0]) - { - return ResourceBooking::findByResourceAndTimeRanges( - $this, - [ - [ - 'begin' => $begin->getTimestamp(), - 'end' => $end->getTimestamp() - ] - ], - $booking_types - ); - } - - - /** - * Get all resource locks for the resource in a given timeframe. - * - * @param DateTime $begin Begin of timeframe. - * @param DateTime $end End of timeframe. - * - * @return ResourceBooking[] An array of ResourceBooking objects. - */ - public function getResourceLocks(DateTime $begin, DateTime $end) - { - return ResourceBooking::findByResourceAndTimeRanges( - $this, - [ - [ - $begin->getTimestamp(), - $end->getTimestamp() - ] - ], - [2] - ); - } - - - /** - * Determines if files are attached to this resource. - * If a folder exists for this resource its files are counted. - * Depending on whether the folder has files in it or not - * this method returns true or false. - * - * @return bool True, if there are files attached to this resource, - * false otherwise. - */ - public function hasFiles() - { - $folder = Folder::findOneBySql( - 'range_id = :range_id', - [ - 'range_id' => $this->id - ] - ); - - if (!$folder) { - return false; - } - - //Since files from resources shall always be stored in the - //Stud.IP file system we can skip the conversion from Folder - //to FolderType and count the FileRef-objects for this resource - //directly in the database. Since resource folders do not - //have subfolders we will count any file of the resource: - return FileRef::countBySql( - 'folder_id = :folder_id', - [ - 'folder_id' => $folder->id - ] - ) > 0; - } - - /** - * Converts a Resource object to an object of a specialised resource class. - * - * @return Resource|other An object of a specialised resource class - * or a Resource object, if the resource is a standard resource - * with the class_name 'Resource' in its resource category. - * If the derived resource class is not available, an instance of - * BrokenResource is returned. - */ - public function getDerivedClassInstance() - { - $class_name = $this->class_name; - - if ($class_name == 'Resource') { - //It is a standard resource which is managed by this class. - return $this; - } - - if (is_subclass_of($class_name, 'Resource')) { - $converted_resource = $class_name::buildExisting( - $this->toRawArray() - ); - return $converted_resource; - } else { - //$class_name does not contain the name of a subclass - //of Resource. That's an error! - $broken_resource = BrokenResource::buildExisting( - $this->toRawArray() - ); - return $broken_resource; - } - } - - /** - * Checks if the place in the resource hierarchy (resource tree) - * is correct for this resource. - * This method has no function in this class but can be filled - * with logic in one of the classes derived from Resource. - * - * @return bool True, if this resource is correctly placed, - * false otherwise. - * @throws NoResourceClassException - * if the class name of this resource is not a derived class - * of the Resource class. - * - */ - public function checkHierarchy() - { - if ($this->class_name == 'Resource') { - //Objects of the Resource class are always in the right - //place of the resource hierarchy. - return true; - } - - //The object does not use the Resource class name and uses - //a derived class instead. We must check the hierarchy - //using the checkHierarchy method of the derived class. - - $converted_resource = $this->getDerivedClassInstance(); - return $converted_resource->checkHierarchy(); - } - - /** - * Returns the link for an action for this resource. - * This is the non-static variant of Resource::getLinkForAction. - * - * @param string $action The action which shall be executed. - * For default Resources the actions 'show', 'add', 'edit' and 'delete' - * are defined. - * @param array $link_parameters Optional parameters for the link. - * @return string @TODO - */ - public function getActionLink($action = 'show', $link_parameters = []) - { - //We must check the class name and call the appropriate - //getLinkForAction method for derived classes: - - $class_name = $this->class_name; - if (is_subclass_of($class_name, 'Resource')) { - return $class_name::getLinkForAction( - $action, - $this->id, - $link_parameters - ); - } else { - return self::getLinkForAction( - $action, - $this->id, - $link_parameters - ); - } - } - - /** - * Returns the URL for an action for this resource. - * This is the non-static variant of Resource::getURLForAction. - * - * @param string $action The action which shall be executed. - * For default Resources the actions 'show', 'add', 'edit' and 'delete' - * are defined. - * @param array $url_parameters Optional parameters for the URL. - * @return string @TODO - */ - public function getActionURL($action = 'show', $url_parameters = []) - { - //We must check the class name and call the appropriate - //getURLForAction method for derived classes: - - $class_name = $this->class_name; - if (is_subclass_of($class_name, 'Resource')) { - return $class_name::getURLForAction( - $action, - $this->id, - $url_parameters - ); - } else { - return self::getURLForAction( - $action, - $this->id, - $url_parameters - ); - } - } - - public function getItemName($long_format = true) - { - if ($long_format) { - //In some cases the general Resource class may be used - //when the resource objects are in fact instances - //of derived classes. To make sure that the correct prefix - //is always displayed, we retrieve the derived class first - //before returning the name: - $derived_class = $this->getDerivedClassInstance(); - return $derived_class->getFullName(); - } else { - return $this->name; - } - } - - public function getItemURL() - { - return $this->getActionURL('show'); - } - - public function getItemAvatarURL() - { - return Icon::create('resources', Icon::ROLE_INFO)->asImagePath(); - } - - - public function getLink() : StudipLink - { - return new StudipLink($this->getActionURL(), $this->name, Icon::create('resources')); - } -} |
