summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIgor Scheller <igor.scheller@igorshp.de>2019-11-27 23:43:21 +0100
committerIgor Scheller <igor.scheller@igorshp.de>2019-12-08 02:20:48 +0100
commit42721e95726559b4a601240bb5b0fe4e5d755b2a (patch)
tree6810e05f845ca787acc1d02fa82d3df15cd0ef9b /src
parent377b390c97afb9106fd9a139819d00306f996f24 (diff)
Added Schedule parsing and replaced old Fahrplan importer
Resolves #553 (Change Frab Import from xCal to XML) Resolves #538 (Feature Request: Multi Frab Import)
Diffstat (limited to 'src')
-rw-r--r--src/Helpers/Schedule/CalculatesTime.php24
-rw-r--r--src/Helpers/Schedule/Conference.php129
-rw-r--r--src/Helpers/Schedule/Day.php88
-rw-r--r--src/Helpers/Schedule/Event.php337
-rw-r--r--src/Helpers/Schedule/Room.php52
-rw-r--r--src/Helpers/Schedule/Schedule.php111
-rw-r--r--src/Helpers/Schedule/XmlParser.php172
-rw-r--r--src/Http/GuzzleServiceProvider.php25
-rw-r--r--src/Middleware/LegacyMiddleware.php18
-rw-r--r--src/Models/Shifts/Schedule.php31
-rw-r--r--src/Models/Shifts/ScheduleShift.php38
11 files changed, 1017 insertions, 8 deletions
diff --git a/src/Helpers/Schedule/CalculatesTime.php b/src/Helpers/Schedule/CalculatesTime.php
new file mode 100644
index 00000000..c9dbeea1
--- /dev/null
+++ b/src/Helpers/Schedule/CalculatesTime.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Engelsystem\Helpers\Schedule;
+
+trait CalculatesTime
+{
+ /**
+ * @param string $time
+ * @return int
+ */
+ protected function secondsFromTime(string $time): int
+ {
+ $seconds = 0;
+ $duration = explode(':', $time);
+
+ foreach (array_slice($duration, 0, 2) as $key => $times) {
+ $seconds += [60 * 60, 60][$key] * $times;
+ }
+
+ return $seconds;
+ }
+}
diff --git a/src/Helpers/Schedule/Conference.php b/src/Helpers/Schedule/Conference.php
new file mode 100644
index 00000000..0819d059
--- /dev/null
+++ b/src/Helpers/Schedule/Conference.php
@@ -0,0 +1,129 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Engelsystem\Helpers\Schedule;
+
+class Conference
+{
+ use CalculatesTime;
+
+ /** @var string required */
+ protected $title;
+
+ /** @var string required */
+ protected $acronym;
+
+ /** @var string|null */
+ protected $start;
+
+ /** @var string|null */
+ protected $end;
+
+ /** @var int|null */
+ protected $days;
+
+ /** @var string|null */
+ protected $timeslotDuration;
+
+ /** @var string|null */
+ protected $baseUrl;
+
+ /**
+ * Event constructor.
+ *
+ * @param string $title
+ * @param string $acronym
+ * @param string|null $start
+ * @param string|null $end
+ * @param int|null $days
+ * @param string|null $timeslotDuration
+ * @param string|null $baseUrl
+ */
+ public function __construct(
+ string $title,
+ string $acronym,
+ ?string $start = null,
+ ?string $end = null,
+ ?int $days = null,
+ ?string $timeslotDuration = null,
+ ?string $baseUrl = null
+ ) {
+ $this->title = $title;
+ $this->acronym = $acronym;
+ $this->start = $start;
+ $this->end = $end;
+ $this->days = $days;
+ $this->timeslotDuration = $timeslotDuration;
+ $this->baseUrl = $baseUrl;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTitle(): string
+ {
+ return $this->title;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAcronym(): string
+ {
+ return $this->acronym;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getStart(): ?string
+ {
+ return $this->start;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getEnd(): ?string
+ {
+ return $this->end;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getDays(): ?int
+ {
+ return $this->days;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getTimeslotDuration(): ?string
+ {
+ return $this->timeslotDuration;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getTimeslotDurationSeconds(): ?int
+ {
+ $duration = $this->getTimeslotDuration();
+ if (!$duration) {
+ return null;
+ }
+
+ return $this->secondsFromTime($duration);
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getBaseUrl(): ?string
+ {
+ return $this->baseUrl;
+ }
+}
diff --git a/src/Helpers/Schedule/Day.php b/src/Helpers/Schedule/Day.php
new file mode 100644
index 00000000..03106e8f
--- /dev/null
+++ b/src/Helpers/Schedule/Day.php
@@ -0,0 +1,88 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Engelsystem\Helpers\Schedule;
+
+use Carbon\Carbon;
+
+class Day
+{
+ /** @var string required */
+ protected $date;
+
+ /** @var Carbon required */
+ protected $start;
+
+ /** @var Carbon required */
+ protected $end;
+
+ /** @var int required */
+ protected $index;
+
+ /** @var Room[] */
+ protected $room;
+
+ /**
+ * Day constructor.
+ *
+ * @param string $date
+ * @param Carbon $start
+ * @param Carbon $end
+ * @param int $index
+ * @param Room[] $rooms
+ */
+ public function __construct(
+ string $date,
+ Carbon $start,
+ Carbon $end,
+ int $index,
+ array $rooms = []
+ ) {
+ $this->date = $date;
+ $this->start = $start;
+ $this->end = $end;
+ $this->index = $index;
+ $this->room = $rooms;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDate(): string
+ {
+ return $this->date;
+ }
+
+ /**
+ * @return Carbon
+ */
+ public function getStart(): Carbon
+ {
+ return $this->start;
+ }
+
+ /**
+ * @return Carbon
+ */
+ public function getEnd(): Carbon
+ {
+ return $this->end;
+ }
+
+ /**
+ * @return int
+ */
+ public function getIndex(): int
+ {
+ return $this->index;
+ }
+
+ /**
+ * @return Room[]
+ */
+ public function getRoom(): array
+ {
+ return $this->room;
+ }
+}
diff --git a/src/Helpers/Schedule/Event.php b/src/Helpers/Schedule/Event.php
new file mode 100644
index 00000000..46970e7b
--- /dev/null
+++ b/src/Helpers/Schedule/Event.php
@@ -0,0 +1,337 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Engelsystem\Helpers\Schedule;
+
+use Carbon\Carbon;
+
+class Event
+{
+ use CalculatesTime;
+
+ /** @var string required globally unique */
+ protected $guid;
+
+ /** @var int required globally unique */
+ protected $id;
+
+ /** @var Room required, string in XML */
+ protected $room;
+
+ /** @var string required */
+ protected $title;
+
+ /** @var string required */
+ protected $subtitle;
+
+ /** @var string required */
+ protected $type;
+
+ /** @var Carbon required */
+ protected $date;
+
+ /** @var string required time (hh:mm:ss || hh:mm) */
+ protected $start;
+
+ /** @var string required (h?h:mm:ss || h?h:mm) */
+ protected $duration;
+
+ /** @var string required */
+ protected $abstract;
+
+ /** @var string required globally unique */
+ protected $slug;
+
+ /** @var string required */
+ protected $track;
+
+ /** @var string|null */
+ protected $logo;
+
+ /** @var string[] id => name */
+ protected $persons;
+
+ /** @var string|null two letter code */
+ protected $language;
+
+ /** @var string|null */
+ protected $description;
+
+ /** @var string|null license (and opt out in XML, null if not recorded, empty if no license defined) */
+ protected $recording;
+
+ /** @var array href => title */
+ protected $links;
+
+ /** @var array href => name */
+ protected $attachments;
+
+ /** @var string|null */
+ protected $url;
+
+ /** @var string|null */
+ protected $videoDownloadUrl;
+
+ /** @var Carbon Calculated */
+ protected $endDate;
+
+ /**
+ * Event constructor.
+ *
+ * @param string $guid
+ * @param int $id
+ * @param Room $room
+ * @param string $title
+ * @param string $subtitle
+ * @param string $type
+ * @param Carbon $date
+ * @param string $start
+ * @param string $duration
+ * @param string $abstract
+ * @param string $slug
+ * @param string $track
+ * @param string|null $logo
+ * @param string[] $persons
+ * @param string|null $language
+ * @param string|null $description
+ * @param string|null $recording license
+ * @param array $links
+ * @param array $attachments
+ * @param string|null $url
+ * @param string|null $videoDownloadUrl
+ */
+ public function __construct(
+ string $guid,
+ int $id,
+ Room $room,
+ string $title,
+ string $subtitle,
+ string $type,
+ Carbon $date,
+ string $start,
+ string $duration,
+ string $abstract,
+ string $slug,
+ string $track,
+ ?string $logo = null,
+ array $persons = [],
+ ?string $language = null,
+ ?string $description = null,
+ string $recording = '',
+ array $links = [],
+ array $attachments = [],
+ ?string $url = null,
+ ?string $videoDownloadUrl = null
+ ) {
+ $this->guid = $guid;
+ $this->id = $id;
+ $this->room = $room;
+ $this->title = $title;
+ $this->subtitle = $subtitle;
+ $this->type = $type;
+ $this->date = $date;
+ $this->start = $start;
+ $this->duration = $duration;
+ $this->abstract = $abstract;
+ $this->slug = $slug;
+ $this->track = $track;
+ $this->logo = $logo;
+ $this->persons = $persons;
+ $this->language = $language;
+ $this->description = $description;
+ $this->recording = $recording;
+ $this->links = $links;
+ $this->attachments = $attachments;
+ $this->url = $url;
+ $this->videoDownloadUrl = $videoDownloadUrl;
+
+ $this->endDate = $this->date
+ ->copy()
+ ->addSeconds($this->getDurationSeconds());
+ }
+
+ /**
+ * @return string
+ */
+ public function getGuid(): string
+ {
+ return $this->guid;
+ }
+
+ /**
+ * @return int
+ */
+ public function getId(): int
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return Room
+ */
+ public function getRoom(): Room
+ {
+ return $this->room;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTitle(): string
+ {
+ return $this->title;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSubtitle(): string
+ {
+ return $this->subtitle;
+ }
+
+ /**
+ * @return string
+ */
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ /**
+ * @return Carbon
+ */
+ public function getDate(): Carbon
+ {
+ return $this->date;
+ }
+
+ /**
+ * @return string
+ */
+ public function getStart(): string
+ {
+ return $this->start;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDuration(): string
+ {
+ return $this->duration;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDurationSeconds(): int
+ {
+ return $this->secondsFromTime($this->duration);
+ }
+
+ /**
+ * @return string
+ */
+ public function getAbstract(): string
+ {
+ return $this->abstract;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSlug(): string
+ {
+ return $this->slug;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTrack(): string
+ {
+ return $this->track;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLogo(): ?string
+ {
+ return $this->logo;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getPersons(): array
+ {
+ return $this->persons;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLanguage(): ?string
+ {
+ return $this->language;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getDescription(): ?string
+ {
+ return $this->description;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getRecording(): ?string
+ {
+ return $this->recording;
+ }
+
+ /**
+ * @return array
+ */
+ public function getLinks(): array
+ {
+ return $this->links;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAttachments(): array
+ {
+ return $this->attachments;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getUrl(): ?string
+ {
+ return $this->url;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getVideoDownloadUrl(): ?string
+ {
+ return $this->videoDownloadUrl;
+ }
+
+ /**
+ * @return Carbon
+ */
+ public function getEndDate(): Carbon
+ {
+ return $this->endDate;
+ }
+}
diff --git a/src/Helpers/Schedule/Room.php b/src/Helpers/Schedule/Room.php
new file mode 100644
index 00000000..45a09f5f
--- /dev/null
+++ b/src/Helpers/Schedule/Room.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Engelsystem\Helpers\Schedule;
+
+class Room
+{
+ /** @var string required */
+ protected $name;
+
+ /** @var Event[] */
+ protected $event;
+
+ /**
+ * Room constructor.
+ *
+ * @param string $name
+ * @param Event[] $events
+ */
+ public function __construct(
+ string $name,
+ array $events = []
+ ) {
+ $this->name = $name;
+ $this->event = $events;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return Event[]
+ */
+ public function getEvent(): array
+ {
+ return $this->event;
+ }
+
+ /**
+ * @param Event[] $event
+ */
+ public function setEvent(array $event): void
+ {
+ $this->event = $event;
+ }
+}
diff --git a/src/Helpers/Schedule/Schedule.php b/src/Helpers/Schedule/Schedule.php
new file mode 100644
index 00000000..7150480c
--- /dev/null
+++ b/src/Helpers/Schedule/Schedule.php
@@ -0,0 +1,111 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Engelsystem\Helpers\Schedule;
+
+use Carbon\Carbon;
+
+class Schedule
+{
+ /** @var string */
+ protected $version;
+
+ /** @var Conference */
+ protected $conference;
+
+ /** @var Day[] */
+ protected $day;
+
+ /**
+ * @param string $version
+ * @param Conference $conference
+ * @param Day[] $days
+ */
+ public function __construct(
+ string $version,
+ Conference $conference,
+ array $days
+ ) {
+ $this->version = $version;
+ $this->conference = $conference;
+ $this->day = $days;
+ }
+
+ /**
+ * @return string
+ */
+ public function getVersion(): string
+ {
+ return $this->version;
+ }
+
+ /**
+ * @return Conference
+ */
+ public function getConference(): Conference
+ {
+ return $this->conference;
+ }
+
+ /**
+ * @return Day[]
+ */
+ public function getDay(): array
+ {
+ return $this->day;
+ }
+
+ /**
+ * @return Room[]
+ */
+ public function getRooms(): array
+ {
+ $rooms = [];
+ foreach ($this->day as $day) {
+ foreach ($day->getRoom() as $room) {
+ $name = $room->getName();
+ $rooms[$name] = $room;
+ }
+ }
+
+ return $rooms;
+ }
+
+
+ /**
+ * @return Carbon|null
+ */
+ public function getStartDateTime(): ?Carbon
+ {
+ $start = null;
+ foreach ($this->day as $day) {
+ $time = $day->getStart();
+ if ($time > $start && $start) {
+ continue;
+ }
+
+ $start = $time;
+ }
+
+ return $start;
+ }
+
+ /**
+ * @return Carbon|null
+ */
+ public function getEndDateTime(): ?Carbon
+ {
+ $end = null;
+ foreach ($this->day as $day) {
+ $time = $day->getEnd();
+ if ($time < $end && $end) {
+ continue;
+ }
+
+ $end = $time;
+ }
+
+ return $end;
+ }
+}
diff --git a/src/Helpers/Schedule/XmlParser.php b/src/Helpers/Schedule/XmlParser.php
new file mode 100644
index 00000000..1492aaca
--- /dev/null
+++ b/src/Helpers/Schedule/XmlParser.php
@@ -0,0 +1,172 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Engelsystem\Helpers\Schedule;
+
+use Carbon\Carbon;
+use SimpleXMLElement;
+
+class XmlParser
+{
+ /** @var SimpleXMLElement */
+ protected $scheduleXML;
+
+ /** @var Schedule */
+ protected $schedule;
+
+ /**
+ * @param string $xml
+ * @return bool
+ */
+ public function load(string $xml): bool
+ {
+ $this->scheduleXML = simplexml_load_string($xml);
+
+ if (!$this->scheduleXML) {
+ return false;
+ }
+
+ $this->parseXml();
+
+ return true;
+ }
+
+ /**
+ * Parse the predefined XML content
+ */
+ protected function parseXml(): void
+ {
+ $version = $this->getFirstXpathContent('version');
+ $conference = new Conference(
+ $this->getFirstXpathContent('conference/title'),
+ $this->getFirstXpathContent('conference/acronym'),
+ $this->getFirstXpathContent('conference/start'),
+ $this->getFirstXpathContent('conference/end'),
+ (int)$this->getFirstXpathContent('conference/days'),
+ $this->getFirstXpathContent('conference/timeslot_duration'),
+ $this->getFirstXpathContent('conference/base_url')
+ );
+ $days = [];
+
+ foreach ($this->scheduleXML->xpath('day') as $day) {
+ $rooms = [];
+
+ foreach ($day->xpath('room') as $roomElement) {
+ $room = new Room(
+ (string)$roomElement->attributes()['name']
+ );
+
+ $events = $this->parseEvents($roomElement->xpath('event'), $room);
+ $room->setEvent($events);
+ $rooms[] = $room;
+ }
+
+ $days[] = new Day(
+ (string)$day->attributes()['date'],
+ new Carbon($day->attributes()['start']),
+ new Carbon($day->attributes()['end']),
+ (int)$day->attributes()['index'],
+ $rooms
+ );
+ }
+
+ $this->schedule = new Schedule(
+ $version,
+ $conference,
+ $days
+ );
+ }
+
+ /**
+ * @param SimpleXMLElement[] $eventElements
+ * @param Room $room
+ * @return array
+ */
+ protected function parseEvents(array $eventElements, Room $room): array
+ {
+ $events = [];
+
+ foreach ($eventElements as $event) {
+ $persons = $this->getListFromSequence($event, 'persons', 'person', 'id');
+ $links = $this->getListFromSequence($event, 'links', 'link', 'href');
+ $attachments = $this->getListFromSequence($event, 'attachments', 'attachment', 'href');
+
+ $recording = '';
+ $recordingElement = $event->xpath('recording')[0];
+ if ($this->getFirstXpathContent('optout', $recordingElement) == 'false') {
+ $recording = $this->getFirstXpathContent('license', $recordingElement);
+ }
+
+ $events[] = new Event(
+ (string)$event->attributes()['guid'],
+ (int)$event->attributes()['id'],
+ $room,
+ $this->getFirstXpathContent('title', $event),
+ $this->getFirstXpathContent('subtitle', $event),
+ $this->getFirstXpathContent('type', $event),
+ new Carbon($this->getFirstXpathContent('date', $event)),
+ $this->getFirstXpathContent('start', $event),
+ $this->getFirstXpathContent('duration', $event),
+ $this->getFirstXpathContent('abstract', $event),
+ $this->getFirstXpathContent('slug', $event),
+ $this->getFirstXpathContent('track', $event),
+ $this->getFirstXpathContent('logo', $event) ?: null,
+ $persons,
+ $this->getFirstXpathContent('language', $event) ?: null,
+ $this->getFirstXpathContent('description', $event) ?: null,
+ $recording,
+ $links,
+ $attachments,
+ $this->getFirstXpathContent('url', $event) ?: null,
+ $this->getFirstXpathContent('video_download_url', $event) ?: null
+ );
+ }
+
+ return $events;
+ }
+
+ /**
+ * @param string $path
+ * @param SimpleXMLElement|null $xml
+ * @return string
+ */
+ protected function getFirstXpathContent(string $path, ?SimpleXMLElement $xml = null): string
+ {
+ $element = ($xml ?: $this->scheduleXML)->xpath($path);
+
+ return $element ? (string)$element[0] : '';
+ }
+
+ /**
+ * Resolves a list from a sequence of elements
+ *
+ * @param SimpleXMLElement $element
+ * @param string $firstElement
+ * @param string $secondElement
+ * @param string $idAttribute
+ * @return array
+ */
+ protected function getListFromSequence(
+ SimpleXMLElement $element,
+ string $firstElement,
+ string $secondElement,
+ string $idAttribute
+ ): array {
+ $items = [];
+
+ foreach ($element->xpath($firstElement)[0]->xpath($secondElement) as $item) {
+ $items[(string)$item->attributes()[$idAttribute]] = (string)$item;
+ }
+
+ return $items;
+ }
+
+ /**
+ * @return Schedule
+ */
+ public function getSchedule(): Schedule
+ {
+ return $this->schedule;
+ }
+}
diff --git a/src/Http/GuzzleServiceProvider.php b/src/Http/GuzzleServiceProvider.php
new file mode 100644
index 00000000..f81a91f5
--- /dev/null
+++ b/src/Http/GuzzleServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Engelsystem\Http;
+
+use Engelsystem\Container\ServiceProvider;
+use GuzzleHttp\Client as GuzzleClient;
+
+class GuzzleServiceProvider extends ServiceProvider
+{
+ public function register()
+ {
+ $this->app->when(GuzzleClient::class)
+ ->needs('$config')
+ ->give(
+ function () {
+ return [
+ // No exception on >= 400 responses
+ 'http_errors' => false,
+ // Wait max n seconds for a response
+ 'timeout' => 2.0,
+ ];
+ }
+ );
+ }
+}
diff --git a/src/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php
index e516f1f3..5e08858d 100644
--- a/src/Middleware/LegacyMiddleware.php
+++ b/src/Middleware/LegacyMiddleware.php
@@ -205,10 +205,6 @@ class LegacyMiddleware implements MiddlewareInterface
$title = admin_groups_title();
$content = admin_groups();
return [$title, $content];
- case 'admin_import':
- $title = admin_import_title();
- $content = admin_import();
- return [$title, $content];
case 'admin_shifts':
$title = admin_shifts_title();
$content = admin_shifts();
@@ -239,9 +235,15 @@ class LegacyMiddleware implements MiddlewareInterface
return response($content, (int)$page);
}
- return response(view('layouts/app', [
- 'title' => $title,
- 'content' => msg() . $content,
- ]), 200);
+ return response(
+ view(
+ 'layouts/app',
+ [
+ 'title' => $title,
+ 'content' => msg() . $content,
+ ]
+ ),
+ 200
+ );
}
}
diff --git a/src/Models/Shifts/Schedule.php b/src/Models/Shifts/Schedule.php
new file mode 100644
index 00000000..c1eb2d9e
--- /dev/null
+++ b/src/Models/Shifts/Schedule.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Engelsystem\Models\Shifts;
+
+use Engelsystem\Models\BaseModel;
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Database\Query\Builder as QueryBuilder;
+
+/**
+ * @property int $id
+ * @property string $url
+ *
+ * @property-read QueryBuilder|Collection|ScheduleShift[] $scheduleShifts
+ *
+ * @method static QueryBuilder|Schedule[] whereId($value)
+ * @method static QueryBuilder|Schedule[] whereUrl($value)
+ */
+class Schedule extends BaseModel
+{
+ /** @var array Values that are mass assignable */
+ protected $fillable = ['url'];
+
+ /**
+ * @return HasMany
+ */
+ public function scheduleShifts()
+ {
+ return $this->hasMany(ScheduleShift::class);
+ }
+}
diff --git a/src/Models/Shifts/ScheduleShift.php b/src/Models/Shifts/ScheduleShift.php
new file mode 100644
index 00000000..368c60b1
--- /dev/null
+++ b/src/Models/Shifts/ScheduleShift.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Engelsystem\Models\Shifts;
+
+use Engelsystem\Models\BaseModel;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Query\Builder as QueryBuilder;
+
+/**
+ * @property int $shift_id
+ * @property int $schedule_id
+ * @property string $guid
+ *
+ * @property-read QueryBuilder|Schedule $schedule
+ *
+ * @method static QueryBuilder|ScheduleShift[] whereShiftId($value)
+ * @method static QueryBuilder|ScheduleShift[] whereScheduleId($value)
+ * @method static QueryBuilder|ScheduleShift[] whereGuid($value)
+ */
+class ScheduleShift extends BaseModel
+{
+ /** @var string The primary key for the model */
+ protected $primaryKey = 'shift_id';
+
+ /** @var string Required because it is not schedule_shifts */
+ protected $table = 'schedule_shift';
+
+ /** @var array Values that are mass assignable */
+ protected $fillable = ['shift_id', 'schedule_id', 'guid'];
+
+ /**
+ * @return BelongsTo
+ */
+ public function schedule()
+ {
+ return $this->belongsTo(Schedule::class);
+ }
+}