Skip to content
Snippets Groups Projects
Commit 00b4e32c authored by Jan-Hendrik Willms's avatar Jan-Hendrik Willms
Browse files

add consultation routes

Closes #1149

Merge request studip/studip!686
parent f345a34d
No related branches found
No related tags found
No related merge requests found
Showing
with 501 additions and 136 deletions
......@@ -115,7 +115,7 @@ class RouteMap
$group->patch('/config-values/{id}', Routes\ConfigValues\ConfigValuesUpdate::class);
$this->addAuthenticatedBlubberRoutes($group);
// $this->addAuthenticatedConsultationRoutes($group);
$this->addAuthenticatedConsultationRoutes($group);
$this->addAuthenticatedContactsRoutes($group);
$this->addAuthenticatedCoursesRoutes($group);
$this->addAuthenticatedCoursewareRoutes($group);
......@@ -180,7 +180,7 @@ class RouteMap
private function addAuthenticatedConsultationRoutes(RouteCollectorProxy $group): void
{
$group->get('/users/{id}/consultations', Routes\Consultations\BlocksByUserIndex::class);
$group->get('/{type:courses|institutes|users}/{id}/consultations', Routes\Consultations\BlocksByRangeIndex::class);
$group->get('/consultation-blocks/{id}', Routes\Consultations\BlockShow::class);
$group->get('/consultation-blocks/{id}/slots', Routes\Consultations\SlotsByBlockIndex::class);
......@@ -189,6 +189,7 @@ class RouteMap
$group->get('/consultation-slots/{id}/bookings', Routes\Consultations\BookingsBySlotIndex::class);
$group->post('/consultation-slots/{id}/bookings', Routes\Consultations\BookingsCreate::class);
$group->post('/consultation-bookings', Routes\Consultations\BookingsCreate::class);
$group->get('/consultation-bookings/{id}', Routes\Consultations\BookingsShow::class);
$group->delete('/consultation-bookings/{id}', Routes\Consultations\BookingsDelete::class);
}
......
......@@ -2,55 +2,57 @@
namespace JsonApi\Routes\Consultations;
use ConsultationBlock;
use ConsultationBooking;
use ConsultationSlot;
use JsonApi\Schemas\ConsultationBooking;
class Authority
final class Authority
{
// TODO
public static function canShowBlubberThread(User $user, BlubberThread $resource)
public static function canShowRange(\User $user, \Range $range): bool
{
return self::userIsAuthor($user) && $resource->isReadable($user->id);
return $range->isAccessibleToUser($user->id);
}
public static function canCreatePrivateBlubberThread(User $user)
public static function canEditRange(\User $user, \Range $range): bool
{
return self::userIsAuthor($user);
return $range->isEditableByUser($user->id);
}
public static function canCreateComment(User $user, BlubberThread $resource)
public static function canShowBlock(\User $user, \ConsultationBlock $block): bool
{
return self::userIsAuthor($user) && $resource->isCommentable($user->id);
return self::canShowRange($user, $block->range);
}
public static function canDeleteComment(User $user, BlubberComment $resource)
public static function canEditBlock(\User $user, \ConsultationBlock $block): bool
{
return self::canEditComment($user, $resource);
return self::canEditRange($user, $block->range);
}
public static function canEditComment(User $user, BlubberComment $resource)
public static function canShowSlot(\User $user, \ConsultationSlot $slot): bool
{
return self::userIsAuthor($user) && $resource->isWritable($user->id);
return self::canShowBlock($user, $slot->block);
}
public static function canIndexComments(User $user, BlubberThread $resource = null)
public static function canEditSlot(\User $user, \ConsultationSlot $slot): bool
{
return isset($resource)
? self::canShowBlubberThread($user, $resource)
: self::userIsAuthor($user);
return self::canEditBlock($user, $slot->block);
}
public static function canShowComment(User $user, BlubberComment $resource)
public static function canBookSlot(\User $user, \ConsultationSlot $slot): bool
{
return self::canShowBlubberThread($user, $resource->thread);
return \ConsultationBooking::userMayCreateBookingForRange(
$slot->block->range,
$user
);
}
/**
* @SuppressWarnings(PHPMD.Superglobals)
*/
private static function userIsAuthor(User $user)
public static function canShowBooking(\User $user, \ConsultationBooking $booking): bool
{
return $GLOBALS['perm']->have_perm('autor', $user->id);
return self::canShowSlot($user, $booking->slot)
|| $booking->user_id === $user->id;
}
public static function canEditBooking(\User $user, \ConsultationBooking $booking): bool
{
return self::canEditSlot($user, $booking->slot)
|| $booking->user_id === $user->id;
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use JsonApi\Schemas\ConsultationBlock;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class BlockShow extends JsonApiController
{
protected $allowedIncludePaths = [
ConsultationBlock::REL_SLOTS,
ConsultationBlock::REL_RANGE,
];
public function __invoke(Request $request, Response $response, $args)
{
$block = \ConsultationBlock::find($args['id']);
if (!$block) {
throw new RecordNotFoundException();
}
if (!Authority::canShowBlock($this->getUser($request), $block)) {
throw new AuthorizationFailedException();
}
return $this->getContentResponse($block);
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use JsonApi\Schemas\ConsultationBlock;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
/**
* Displays all consultation blocks of a range
*/
class BlocksByRangeIndex extends JsonApiController
{
use FilterTrait;
protected $allowedIncludePaths = [
ConsultationBlock::REL_SLOTS,
ConsultationBlock::REL_RANGE,
];
protected $allowedPagingParameters = ['offset', 'limit'];
protected $allowedFilteringParameters = ['current', 'expired'];
public function __invoke(Request $request, Response $response, $args)
{
$this->validateFilters();
$range_id = $args['id'];
$range_type = substr($args['type'], 0, -1); // Strips trailing plural s
$range = \RangeFactory::createRange($range_type, $range_id);
if ($range->isNew()) {
throw new RecordNotFoundException();
}
if (!Authority::canShowRange($this->getUser($request), $range)) {
throw new AuthorizationFailedException();
}
[$offset, $limit] = $this->getOffsetAndLimit();
$filters = $this->getFilters();
$blocks = $this->getBlocks($range, $filters);
return $this->getPaginatedContentResponse(
$blocks->limit($offset, $limit)->getArrayCopy(),
count($blocks)
);
}
private function getBlocks(\Range $range, array $filters): \SimpleCollection
{
if (!$filters['current'] && !$filters['expired']) {
return \SimpleCollection::createFromArray([]);
}
if ($filters['current'] && $filters['expired']) {
return $range->consultation_blocks;
}
$blocks = \ConsultationBlock::findByRange($range, 'ORDER BY start', $filters['expired']);
return \SimpleCollection::createFromArray($blocks);
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use JsonApi\Routes\TimestampTrait;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
/**
* Displays all consultation blocks of a user
*/
class BlocksByUserIndex extends JsonApiController
{
use TimestampTrait, FilterTrait;
protected $allowedFilteringParameters = ['since', 'before'];
// protected $allowedIncludePaths = ['author', 'mentions', 'thread'];
protected $allowedPagingParameters = ['offset', 'limit'];
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __invoke(Request $request, Response $response, $args)
{
$this->validateFilters();
if (!($user = \User::find($args['id']))) {
throw new RecordNotFoundException();
}
if (!Authority::canShowBlubberThread($this->getUser($request), $thread)) {
throw new AuthorizationFailedException();
}
$filters = $this->getFilters();
list($total, $comments) = $this->getComments($thread, $filters);
return $this->getPaginatedContentResponse($comments, $total);
}
private function getComments(\BlubberThread $thread, array $filters)
{
list($offset, $limit) = $this->getOffsetAndLimit();
$query = 'thread_id = :thread_id';
$params = ['thread_id' => $thread->id];
if (isset($filters['before'])) {
$query .= ' AND mkdate <= :before';
$params['before'] = $filters['before'];
}
if (isset($filters['since'])) {
$query .= ' AND mkdate >= :since';
$params['since'] = $filters['since'];
}
if (isset($filters['search'])) {
$query .= ' AND content LIKE :search';
$params['search'] = '%' . $filters['search'] . '%';
}
$query .= ' ORDER BY mkdate ASC LIMIT :limit OFFSET :offset';
$params['limit'] = $limit + 1;
$params['offset'] = $offset;
$comments = \BlubberComment::findBySQL($query, $params);
return [count($comments) <= $limit ? count($comments) + $offset : null, array_slice($comments, 0, $limit)];
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class BookingsBySlotIndex extends JsonApiController
{
public function __invoke(Request $request, Response $response, $args)
{
$slot = \ConsultationSlot::find($args['id']);
if (!$slot) {
throw new RecordNotFoundException();
}
if (!Authority::canShowSlot($this->getUser($request), $slot)) {
throw new AuthorizationFailedException();
}
return $this->getContentResponse($slot->bookings);
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\ConflictException;
use JsonApi\JsonApiController;
use JsonApi\Routes\ValidationTrait;
use JsonApi\Schemas\ConsultationSlot;
use JsonApi\Schemas\User;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class BookingsCreate extends JsonApiController
{
use ValidationTrait;
public function __invoke(Request $request, Response $response, $args)
{
$json = $this->validate($request, $args);
$slot = $this->getBookingSlot($json, $args);
$booking_user = $this->getBookingUser($json);
if (!Authority::canBookSlot($booking_user, $slot)) {
throw new AuthorizationFailedException();
}
if ($slot->isOccupied()) {
throw new ConflictException('The slot is already occupied');
}
$booking = \ConsultationBooking::create([
'slot_id' => $slot->id,
'user_id' => $booking_user->id,
'reason' => self::arrayGet($json, 'data.attributes.reason', ''),
]);
return $this->getCreatedResponse($booking);
}
protected function validateResourceDocument($json, $data)
{
$user_validation_error = $this->validateRequestContainsValidUser($json, $data);
$slot_validation_error = $this->validateRequestContainsValidSlot($json, $data);
return $user_validation_error ?? $slot_validation_error;
}
protected function validateRequestContainsValidUser($json, $data)
{
if (!self::arrayHas($json, 'data.relationships.user')) {
return 'No user relationship defined for booking';
}
$booking_user = self::arrayGet($json, 'data.relationships.user');
if (!isset($booking_user['data']['type'], $booking_user['data']['id']) || $booking_user['data']['type'] !== User::TYPE) {
return 'Defined booking user has invalid format.';
}
if (!\User::exists($booking_user['data']['id'])) {
return 'Defined booking user does not exist.';
}
return null;
}
protected function validateRequestContainsValidSlot($json, $data)
{
if (isset($data['id']) && \ConsultationSlot::exists($data['id'])) {
return null;
}
if (!self::arrayHas($json, 'data.relationships.slot')) {
return 'No slot relationship defined for booking';
}
$booking_slot = self::arrayGet($json, 'data.relationships.slot');
if (!isset($booking_slot['data']['type'], $booking_slot['data']['id']) || $booking_slot['data']['type'] !== ConsultationSlot::TYPE) {
return 'Defined slot for booking has invalid format.';
}
if (!\ConsultationSlot::exists($booking_slot['data']['id'])) {
return 'Defined slot for booking does not exist.';
}
return null;
}
protected function getBookingUser($json): \User
{
$user_id = self::arrayGet($json, 'data.relationships.user.data.id');
return \User::find($user_id);
}
protected function getBookingSlot($json, $data): \ConsultationSlot
{
$slot_id = self::arrayGet($json, 'data.relationships.slot.data.id', $data['id'] ?? null);
return \ConsultationSlot::find($slot_id);
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use JsonApi\Routes\ValidationTrait;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class BookingsDelete extends JsonApiController
{
use ValidationTrait;
public function __invoke(Request $request, Response $response, $args)
{
$json = $this->validate($request);
$booking = \ConsultationBooking::find($args['id']);
if (!$booking) {
throw new RecordNotFoundException();
}
$user = $this->getUser($request);
if (!Authority::canEditBooking($user, $booking)) {
throw new AuthorizationFailedException();
}
$reason = self::arrayGet($json, 'data.attributes.reason', '');
$booking->cancel($reason);
return $this->getCodeResponse(204);
}
protected function validateResourceDocument($json, $data)
{
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use JsonApi\Schemas\ConsultationBooking;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class BookingsShow extends JsonApiController
{
protected $allowedIncludePaths = [
ConsultationBooking::REL_SLOT,
ConsultationBooking::REL_USER,
];
public function __invoke(Request $request, Response $response, $args)
{
$booking = \ConsultationBooking::find($args['id']);
if (!$booking) {
throw new RecordNotFoundException();
}
if (!Authority::canShowBooking($this->getUser($request), $booking)) {
throw new AuthorizationFailedException();
}
return $this->getContentResponse($booking);
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\BadRequestException;
trait FilterTrait
{
private function validateFilters()
{
$filtering = $this->getQueryParameters()->getFilteringParameters() ?? [];
if (array_key_exists('current', $filtering)) {
if (!ctype_digit($filtering['current']) || !in_array($filtering['current'], [0, 1])) {
throw new BadRequestException('Filter "current" may only be 0 or 1.');
}
}
if (array_key_exists('expired', $filtering)) {
if (!ctype_digit($filtering['expired']) || !in_array($filtering['expired'], [0, 1])) {
throw new BadRequestException('Filter "expired" may only be 0 or 1.');
}
}
}
private function getFilters(): array
{
$filtering = $this->getQueryParameters()->getFilteringParameters() ?? [];
$has_filter = isset($filtering['current']) || isset($filtering['expired']);
$filters['current'] = $filtering['current'] ?? !$has_filter;
$filters['expired'] = $filtering['expired'] ?? !$has_filter;
return $filters;
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use JsonApi\Schemas\ConsultationSlot;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class SlotShow extends JsonApiController
{
protected $allowedIncludePaths = [
ConsultationSlot::REL_BLOCK,
ConsultationSlot::REL_BOOKINGS,
];
public function __invoke(Request $request, Response $response, $args)
{
$slot = \ConsultationSlot::find($args['id']);
if (!$slot) {
throw new RecordNotFoundException();
}
if (!Authority::canShowSlot($this->getUser($request), $slot)) {
throw new AuthorizationFailedException();
}
return $this->getContentResponse($slot);
}
}
<?php
namespace JsonApi\Routes\Consultations;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use JsonApi\Schemas\ConsultationSlot;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class SlotsByBlockIndex extends JsonApiController
{
protected $allowedIncludePaths = [
ConsultationSlot::REL_BLOCK,
ConsultationSlot::REL_BOOKINGS,
];
protected $allowedPagingParameters = ['offset', 'limit'];
public function __invoke(Request $request, Response $response, $args)
{
$block = \ConsultationBlock::find($args['id']);
if (!$block) {
throw new RecordNotFoundException();
}
if (!Authority::canShowBlock($this->getUser($request), $block)) {
throw new AuthorizationFailedException();
}
[$offset, $limit] = $this->getOffsetAndLimit();
return $this->getPaginatedContentResponse(
$block->slots->limit($offset, $limit),
count($block->slots)
);
}
}
......@@ -19,6 +19,9 @@ class SchemaMap
\BlubberThread::class => Schemas\BlubberThread::class,
\CalendarEvent::class => Schemas\CalendarEvent::class,
\ConsultationBlock::class => Schemas\ConsultationBlock::class,
\ConsultationBooking::class => Schemas\ConsultationBooking::class,
\ConsultationSlot::class => Schemas\ConsultationSlot::class,
\ConfigValue::class => Schemas\ConfigValue::class,
\CourseEvent::class => Schemas\CourseEvent::class,
\ContentTermsOfUse::class => Schemas\ContentTermsOfUse::class,
......
......@@ -8,9 +8,13 @@ use Neomerx\JsonApi\Schema\Link;
class ConsultationBlock extends SchemaProvider
{
const TYPE = 'consultation-blocks';
const REL_SLOTS = 'slots';
const REL_RANGE = 'range';
/**
* @param \ConsultationBlock $resource
*/
public function getId($resource): ?string
{
return $resource->id;
......@@ -22,15 +26,16 @@ class ConsultationBlock extends SchemaProvider
'start' => date('c', $resource->start),
'end' => date('c', $resource->end),
'room' => $resource->room,
'size' => (int) $resource->size,
'show-participants' => (bool) $resource->show_participants,
'require-reason' => $resource->require_reason,
'confirmation-text' => $resource->confirmation_text ?: null,
'confirmation-text-html' => formatLinks($resource->confirmation_text) ?: null,
'room' => $resource->room,
'room-html' => formatLinks($resource->room),
'note' => $resource->note,
'note-html' => formatLinks($resource->note),
......@@ -49,28 +54,27 @@ class ConsultationBlock extends SchemaProvider
public function getRelationships($resource, ContextInterface $context): iterable
{
$relationships = [];
$relationships = $this->getSlotsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_SLOTS));
$isPrimary = $context->getPosition()->getLevel() === 0;
if (!$isPrimary) {
return $relationships;
if ($isPrimary) {
$relationships = $this->getSlotsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_SLOTS));
$relationships = $this->getRangeRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_RANGE));
}
$relationships = $this->getRangeRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_RANGE));
return $relationships;
}
// #### PRIVATE HELPERS ####
private function getSlotsRelationship(array $relationships, \BlubberComment $resource, $includeData)
private function getSlotsRelationship(array $relationships, \ConsultationBlock $resource, $includeData)
{
if ($includeData) {
$relatedSlots = $resource->slots;
} else {
$relatedSlots = array_map(function ($slot) {
$relatedSlots = $resource->slots->map(function ($slot) {
return \ConsultationSlot::build(['id' => $slot->id], false);
}, $resource->slots);
});
}
$relationships[self::REL_SLOTS] = [
......@@ -97,7 +101,7 @@ class ConsultationBlock extends SchemaProvider
return $relationships;
}
private function getLinkForRange(Range $range)
private function getLinkForRange(\Range $range)
{
if (
$range instanceof \Course ||
......@@ -110,18 +114,18 @@ class ConsultationBlock extends SchemaProvider
throw new \Exception('Unknown range type');
}
private function getMinimalRange(Range $range)
private function getMinimalRange(\Range $range)
{
if ($range instanceof \Course) {
return Course::build(['id' => $range->id], false);
return \Course::build(['id' => $range->id], false);
}
if ($range instanceof \Institute) {
return Institute::build(['id' => $range->id], false);
return \Institute::build(['id' => $range->id], false);
}
if ($range instanceof \User) {
return User::build(['id' => $range->id], false);
return \User::build(['id' => $range->id], false);
}
throw new \Exception('Unknown range type');
......
......@@ -8,14 +8,21 @@ use Neomerx\JsonApi\Schema\Link;
class ConsultationBooking extends SchemaProvider
{
const TYPE = 'consultation-bookings';
const REL_SLOT = 'slot';
const REL_USER = 'user';
/**
* @param \ConsultationBooking $resource
*/
public function getId($resource): ?string
{
return $resource->id;
}
/**
* @param \ConsultationBooking $resource
*/
public function getAttributes($resource, ContextInterface $context): iterable
{
$attributes = [
......@@ -28,23 +35,22 @@ class ConsultationBooking extends SchemaProvider
return $attributes;
}
/**
* In dieser Methode können Relationships zu anderen Objekten
* spezifiziert werden.
* {@inheritdoc}
*/
public function getRelationships($resource, ContextInterface $context): iterable
{
$relationships = [];
$isPrimary = $context->getPosition()->getLevel() === 0;
if ($isPrimary) {
$relationships = $this->getSlotRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_SLOT));
$relationships = $this->getUserRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_USER));
}
return $relationships;
}
// #### PRIVATE HELPERS ####
private function getSlotRelationship(array $relationships, \BlubberComment $resource, $includeData)
private function getSlotRelationship(array $relationships, \ConsultationBooking $resource, $includeData)
{
$slot = $resource->slot;
......@@ -58,7 +64,7 @@ class ConsultationBooking extends SchemaProvider
return $relationships;
}
private function getRangeRelationship($relationships, $resource, $includeData)
private function getUserRelationship($relationships, \ConsultationBooking $resource, $includeData)
{
$user = $resource->user;
......
......@@ -8,11 +8,10 @@ use Neomerx\JsonApi\Schema\Link;
class ConsultationSlot extends SchemaProvider
{
const TYPE = 'consultation-slots';
const REL_BLOCK = 'block';
const REL_BOOKINGS = 'bookings';
public function getId($resource): ?string
{
return $resource->id;
......@@ -42,21 +41,20 @@ class ConsultationSlot extends SchemaProvider
public function getRelationships($resource, ContextInterface $context): iterable
{
$relationships = [];
$relationships = $this->getBlockRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_BLOCK));
$isPrimary = $context->getPosition()->getLevel() === 0;
if (!$isPrimary) {
return $relationships;
if ($isPrimary) {
$relationships = $this->getBlockRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_BLOCK));
$relationships = $this->getBookingsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_BOOKINGS));
}
$relationships = $this->getBookingsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_BOOKINGS));
return $relationships;
}
// #### PRIVATE HELPERS ####
private function getBlockRelationship($relationships, $resource, $includeData)
private function getBlockRelationship($relationships, \ConsultationSlot $resource, $includeData)
{
$block = $resource->block;
......@@ -70,14 +68,14 @@ class ConsultationSlot extends SchemaProvider
return $relationships;
}
private function getBookingsRelationship(array $relationships, \BlubberComment $resource, $includeData)
private function getBookingsRelationship(array $relationships, \ConsultationSlot $resource, $includeData)
{
if ($includeData) {
$relatedBookings = $resource->bookings;
} else {
$relatedBookings = array_map(function ($booking) {
return \ConsultationBooking::build(['slot_id' => $booking->slot_id], false);
}, $resource->bookings);
$relatedBookings = $resource->bookings->map(function ($booking) {
return \ConsultationBooking::build(['booking_id' => $booking->id], false);
});
}
$relationships[self::REL_BOOKINGS] = [
......
......@@ -31,7 +31,7 @@ final class RangeFactory
* @param string $type Range type
* @param mixed $id Range id
* @return mixed any of the supported range types
* @throws Exception when an invalid range type was given
* @throws Exception when an invalid range type was given
*
* @todo Should this be more dynamic in case any more ranges are added?
*/
......
......@@ -94,6 +94,26 @@ class ConsultationBooking extends SimpleORMap implements PrivacyObject
parent::configure($config);
}
/**
* Returns whether a user may create a booking for the given range.
*
* @param User $user
* @return bool
*/
public static function userMayCreateBookingForRange(\Range $range, \User $user): bool
{
if (!($range instanceof \User)) {
return true;
}
$allowed_perms = ['user', 'autor', 'tutor'];
if (Config::get()->CONSULTATION_ALLOW_DOCENTS_RESERVING) {
$allowed_perms[] = 'dozent';
}
return in_array($user->perms, $allowed_perms);
}
public function cancel($reason = '')
{
if ($GLOBALS['user']->id !== $this->user_id) {
......
......@@ -142,6 +142,11 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
'on_delete' => 'delete',
'on_store' => 'store',
];
$config['has_many']['consultation_blocks'] = [
'class_name' => ConsultationBlock::class,
'assoc_foreign_key' => 'range_id',
'on_delete' => 'delete',
];
$config['has_and_belongs_to_many']['semesters'] = [
'class_name' => Semester::class,
......
......@@ -107,6 +107,11 @@ class Institute extends SimpleORMap implements Range
'on_delete' => 'delete',
'on_store' => 'store',
];
$config['has_many']['consultation_blocks'] = [
'class_name' => ConsultationBlock::class,
'assoc_foreign_key' => 'range_id',
'on_delete' => 'delete',
];
$config['has_many']['tools'] = [
'class_name' => ToolActivation::class,
'assoc_foreign_key' => 'range_id',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment