Skip to content
Snippets Groups Projects
Resource.php 102 KiB
Newer Older
 * Resource.php - model class for a resource
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
 * 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
 *
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
 * @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'] = [
            'assoc_func' => 'findChildren',
            'on_delete'  => 'delete',
            'on_store'   => 'store'
        ];

        $config['belongs_to']['parent'] = [
            '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;
    }

Moritz Strohm's avatar
Moritz Strohm committed

    /**
     * @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 = '',
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        $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 = '',
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        $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 = '',
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        $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;
    }