aboutsummaryrefslogtreecommitdiff
path: root/lib/models/calendar/CalendarDate.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/models/calendar/CalendarDate.php')
-rw-r--r--lib/models/calendar/CalendarDate.php946
1 files changed, 946 insertions, 0 deletions
diff --git a/lib/models/calendar/CalendarDate.php b/lib/models/calendar/CalendarDate.php
new file mode 100644
index 0000000..0318dab
--- /dev/null
+++ b/lib/models/calendar/CalendarDate.php
@@ -0,0 +1,946 @@
+<?php
+/**
+ * CalendarDate.php - Model class for calendar dates.
+ *
+ * CalendarDate represents a date in the personal calendar.
+ *
+ * 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 Peter Thienel <thienel@data-quest.de>
+ * @author Moritz Strohm <strohm@data-quest.de>
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ * @since 3.2
+ *
+ * @property string id database column
+ * @property string author_id database column
+ * @property string editor_id database column
+ * @property string unique_id database column
+ * @property string begin database column
+ * @property string end database column
+ * @property string title database column
+ * @property string description database column
+ * @property string access database column
+ * @property string user_category database column
+ * @property string category database column
+ * @property string location database column
+ * @property string interval database column
+ * @property string offset database column
+ * @property string days database column
+ * @property string month database column
+ * @property string day_offset database column
+ * @property string repetition_type database column
+ * @property string number_of_dates database column
+ * @property string repetition_end database column
+ * @property string mkdate database column
+ * @property string chdate database column
+ * @property string import_date database column
+ */
+class CalendarDate extends SimpleORMap implements PrivacyObject
+{
+ /**
+ * NEVER_ENDING represents the value of the repetition_end field for
+ * a date that never ends. The value is the result of computing
+ * 2 ^ 31 - 1.
+ *
+ * NOTE: This constant must be changed long before 2038-01-19 03:14:07 UTC
+ * or else dates that should end at some specific point in time may end
+ * never.
+ */
+ public const NEVER_ENDING = 2147483647;
+
+ protected static function configure($config = [])
+ {
+ $config['db_table'] = 'calendar_dates';
+
+ $config['belongs_to']['author'] = [
+ 'class_name' => User::class,
+ 'foreign_key' => 'author_id',
+ ];
+ $config['belongs_to']['editor'] = [
+ 'class_name' => User::class,
+ 'foreign_key' => 'editor_id',
+ ];
+ $config['has_many']['calendars'] = [
+ 'class_name' => CalendarDateAssignment::class,
+ 'assoc_foreign_key' => 'calendar_date_id',
+ 'on_store' => 'store',
+ 'on_delete' => 'delete'
+ ];
+ $config['has_many']['exceptions'] = [
+ 'class_name' => CalendarDateException::class,
+ 'assoc_foreign_key' => 'calendar_date_id',
+ 'on_store' => 'store',
+ 'on_delete' => 'delete'
+ ];
+
+ $config['default_values']['interval'] = 0;
+ $config['default_values']['offset'] = 0;
+
+ $config['registered_callbacks']['before_store'][] = 'calculateExpiration';
+ $config['registered_callbacks']['after_store'][] = 'cbSendDateModificationMail';
+ $config['registered_callbacks']['before_store'][] = 'cbGenerateUniqueId';
+
+ parent::configure($config);
+
+ }
+
+ public static function garbageCollect()
+ {
+ DBManager::get()->query(
+ 'DELETE `calendar_dates`
+ FROM `calendar_date_assignments`
+ LEFT JOIN `calendar_dates` ON (`calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id`)
+ WHERE `range_id` IS NULL'
+ );
+ }
+
+ /**
+ * @deprecated
+ */
+ public function getDefaultValue($field)
+ {
+ if ($field == 'begin') {
+ return time();
+ }
+ if ($field == 'end' && $this->content['begin']) {
+ return $this->content['begin'] + 3600;
+ }
+ return parent::getDefaultValue($field);
+ }
+
+ public function cbSendDateModificationMail()
+ {
+ $template_factory = new Flexi\Factory($GLOBALS['STUDIP_BASE_PATH'] . '/locale/');
+
+ foreach ($this->calendars as $calendar) {
+ if ($calendar->range_id === $this->editor_id) {
+ //The editor shall not get a mail about the changes they just made.
+ continue;
+ }
+ if (!$calendar->user) {
+ //Wrong range or not a user.
+ continue;
+ }
+ setTempLanguage($calendar->range_id);
+
+ $lang_path = getUserLanguagePath($calendar->range_id);
+ $template = $template_factory->open($lang_path . '/LC_MAILS/date_changed.php');
+ $template->set_attribute('date', $this);
+ $template->set_attribute('receiver', $calendar->user);
+ $template->set_attribute('receiver_date_assignment', $calendar);
+ $mail_text = $template->render();
+ Message::send(
+ '____%system%____',
+ [$calendar->user->username],
+ sprintf(_('Terminänderung durch %s'), $this->editor->getFullName()),
+ $mail_text
+ );
+
+ restoreLanguage();
+ }
+ }
+
+ /**
+ * Generates an unique id if it isn't present.
+ * @return void
+ */
+ public function cbGenerateUniqueId()
+ {
+ if (!$this->unique_id) {
+ $this->unique_id = 'Stud.IP-' . $this->id . '@' . ($_SERVER['SERVER_NAME'] ?? '');
+ }
+ }
+
+ /**
+ * TODO
+ *
+ * @param string $range_id
+ * @return bool
+ */
+ public function isVisible(string $range_id)
+ {
+ if (CalendarDateAssignment::exists([$range_id, $this->id])) {
+ //Users may see the dates in their calendar:
+ return true;
+ }
+
+ $assignments = CalendarDateAssignment::findByCalendar_date_id($this->id);
+ foreach ($assignments as $assignment) {
+ if ($assignment->course instanceof Course) {
+ if ($assignment->course->isCalendarReadable($range_id)) {
+ return true;
+ }
+ } elseif ($assignment->user instanceof User) {
+ if ($assignment->user->isCalendarReadable($range_id)) {
+ return true;
+ }
+ }
+ }
+
+ //In case the date is not in a calendar of the user or a course
+ //where the user has access to, it is only visible when it is public.
+ return $this->access === 'PUBLIC';
+ }
+
+
+ public function isWritable(string $range_id)
+ {
+ if (CalendarDateAssignment::exists([$range_id, $this->id])) {
+ //The date is in the calendar of the user/course
+ //and therefore, the user or course administrator (tutor, dozent)
+ //may change the date.
+ return true;
+ }
+ //In case $range_id is a User-ID, a check has to be made if the calendar
+ //date is bound to a course and the user has at least "tutor" permissions
+ //in the course.
+ if (User::exists($range_id)) {
+ $writable_via_course = false;
+ $assignments = CalendarDateAssignment::findByCalendar_date_id($this->id);
+ foreach ($assignments as $assignment) {
+ if (Course::exists($assignment->range_id)
+ && $GLOBALS['perm']->have_studip_perm('tutor', $assignment->range_id, $range_id)) {
+ $writable_via_course = true;
+ break;
+ }
+ }
+ if ($writable_via_course) {
+ return true;
+ }
+ }
+
+ //Check contacts: Has the contact of the user that is represented by
+ //$range_id write permissions to all the calendars of all the users that
+ //are assigned to the date?
+
+ $contacts_with_write_permissions = Contact::countBySql(
+ "JOIN `calendar_date_assignments` cda
+ ON `contact`.`user_id` = cda.`range_id`
+ WHERE `contact`.`owner_id` = :current_range_id
+ AND `contact`.`calendar_permissions` = 'WRITE'
+ AND cda.`calendar_date_id` = :calendar_date_id
+ AND cda.`range_id` <> :current_range_id",
+ [
+ 'calendar_date_id' => $this->id,
+ 'current_range_id' => $range_id
+ ]
+ );
+ $other_participant_count = CalendarDateAssignment::countBySql(
+ "`calendar_date_id` = :calendar_date_id
+ AND `range_id` <> :current_range_id",
+ [
+ 'calendar_date_id' => $this->id,
+ 'current_range_id' => $range_id
+ ]
+ );
+
+ if ($contacts_with_write_permissions === $other_participant_count) {
+ //The user represented by $range_id has write permissions to all
+ //calendars of all the other users that are assigned to the date.
+ return true;
+ }
+
+ //NOTE: CALENDAR_GRANT_ALL_INSERT MUST NOT be regarded here, because it only
+ //defines the behavior when inserting calendar dates and not when modifying them.
+
+ //In case it is a course date, we must check if the user has write
+ //permissions from the course:
+ $course_assignments = CalendarDateAssignment::findBySql(
+ "JOIN `seminare`
+ ON `calendar_date_assignments`.`range_id` = `seminare`.`seminar_id`
+ WHERE `calendar_date_id` = :calendar_date_id",
+ ['calendar_date_id' => $this->id]
+ );
+ foreach ($course_assignments as $course_assignment) {
+ if ($course_assignment->course->isCalendarWritable($range_id)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines whether the date spans over one whole day. This means that the date takes
+ * place on one day from 0:00:00 to 23:59:59.
+ *
+ * @return bool True, if the date spans over the whole day, false otherwise.
+ */
+ public function isWholeDay() : bool
+ {
+ $begin = new DateTime();
+ $begin->setTimestamp($this->begin);
+ $end = new DateTime();
+ $end->setTimestamp($this->end);
+
+ if ($begin->format('Ymd') !== $end->format('Ymd')) {
+ //Beginning and end are on different days.
+ return false;
+ }
+ //If the beginning is on midnight and the end is one second before midnight of the next day,
+ //the date spans over the whole day.
+ return $begin->format('His') === '000000'
+ && $end->format('His') === '235959';
+ }
+
+
+ /**
+ * Calculates the value of the "expire" column in case the CalendarDate object
+ * has a repetition defined.
+ *
+ * @return void
+ */
+ public function calculateExpiration()
+ {
+ if (!in_array($this->repetition_type, ['DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'])) {
+ //No repetition. Nothing to do.
+ return;
+ }
+ if ($this->number_of_dates > 1) {
+ //There is a certain amount of repetitions, so that the expiration date
+ //has to be calculated by that.
+ $expiration = new DateTime();
+ $expiration->setTimestamp($this->begin);
+ $interval_str = '';
+ if ($this->repetition_type === 'DAILY') {
+ $interval_str = sprintf('P%dD', ((int) $this->number_of_dates - 1) * $this->interval);
+ } elseif ($this->repetition_type === 'WEEKLY') {
+ $days_length = mb_strlen($this->days);
+ if ($days_length > 0) {
+ $wday = $expiration->format('N');
+ // set next weekday as first repetition
+ $expiration->modify($this->getWeekdayName());
+
+ $rep_offset = ($this->number_of_dates - 1) % $days_length;
+
+ $rep_count = $this->number_of_dates - 1;
+
+ $days_offset = floor($rep_count / $days_length) * 7 *
+ $this->interval + $rep_offset - 1;
+ $interval_str = sprintf('P%dD', $days_offset);
+ } else {
+ $interval_str = sprintf('P%dW', ($this->number_of_dates - 1) * $this->interval);
+ }
+ } elseif ($this->repetition_type === 'MONTHLY') {
+ $interval_str = sprintf('P%dM', ($this->number_of_dates - 1) * $this->interval);
+ } elseif ($this->repetition_type === 'YEARLY') {
+ $interval_str = sprintf('P%dY', ($this->number_of_dates - 1) * $this->interval);
+ }
+ try {
+ $interval = new DateInterval($interval_str);
+ $expiration->add($interval);
+ $expiration->setTime(23, 59, 59);
+ $this->repetition_end = $expiration->getTimestamp();
+ } catch (Exception $e) {
+ //Nothing to do.
+ }
+ } elseif (!$this->repetition_end) {
+ //No expiration date is specified.
+ //This would mean that the event "never" expires.
+ $this->repetition_end = self::NEVER_ENDING;
+ }
+ }
+
+
+ /**
+ *
+ * Returns the DateInterval for the repetition of this calendar date.
+ *
+ * @return DateInterval|null The DateInterval for this calendar date or null
+ * in case the date has no repetition.
+ * @throws Exception In case a DateInterval cannot be constructed.
+ */
+ public function getRepetitionInterval() : ?DateInterval
+ {
+ if ($this->repetition_type === 'DAILY') {
+ return new DateInterval(sprintf('P%uD', $this->interval));
+ } elseif ($this->repetition_type === 'WORKDAYS') {
+ return new DateInterval('P1W');
+ } elseif ($this->repetition_type === 'WEEKLY') {
+ return new DateInterval(sprintf('P%uW', $this->interval));
+ } elseif ($this->repetition_type === 'MONTHLY') {
+ return new DateInterval(sprintf('P%uM', $this->interval));
+ } elseif ($this->repetition_type === 'YEARLY') {
+ return new DateInterval(sprintf('P%uY', $this->interval));
+ }
+ //No repetition: no interval.
+ return null;
+ }
+
+
+ public function getRepetitionOffset() : ?DateInterval
+ {
+ if (!$this->offset) {
+ return null;
+ }
+
+ if ($this->repetition_type === 'MONTHLY') {
+ if ($this->days_offset) {
+ return new DateInterval(sprintf('P%1$uM%2$uD', $this->offset, $this->days_offset));
+ } else {
+ return new DateInterval(sprintf('P%uM', $this->offset));
+ }
+ } elseif ($this->repetition_type === 'YEARLY') {
+ return new DateInterval(sprintf('P%uM', $this->offset));
+ }
+ return null;
+ }
+
+
+ /**
+ * Export available data of a given user into a storage object
+ * (an instance of the StoredUserData class) for that user.
+ *
+ * @param StoredUserData $storage object to store data into
+ */
+ public static function exportUserData(StoredUserData $storage)
+ {
+ $sorm = self::findThru($storage->user_id, [
+ 'thru_table' => 'calendar_date_assignments',
+ 'thru_key' => 'range_id',
+ 'thru_assoc_key' => 'calendar_date_id',
+ 'assoc_foreign_key' => 'id',
+ ]);
+ if ($sorm) {
+ $field_data = [];
+ foreach ($sorm as $row) {
+ $field_data[] = $row->toRawArray();
+ }
+ if ($field_data) {
+ $storage->addTabularData(_('Kalendereinträge'), 'calendar_dates', $field_data);
+ }
+ }
+ }
+
+
+ /**
+ * This is a helper method to set all the fields for date repetition to an empty string.
+ *
+ * @return void
+ */
+ public function clearRepetitionFields()
+ {
+ $this->repetition_type = '';
+ $this->interval = '';
+ $this->offset = '';
+ $this->days = '';
+ $this->month = '';
+ $this->number_of_dates = '1';
+ $this->repetition_end = '';
+ }
+
+ public function getAccessAsString() : string
+ {
+ if ($this->access === 'PUBLIC') {
+ return _('Öffentlich');
+ } elseif ($this->access === 'PRIVATE') {
+ return _('Privat');
+ } elseif ($this->access === 'CONFIDENTIAL') {
+ return _('Vertraulich');
+ } else {
+ return _('Keine Angabe');
+ }
+ }
+
+ public function getRepetitionAsString() : string
+ {
+ require_once 'lib/dates.inc.php';
+
+ $repetition_string = '';
+
+ if ($this->repetition_type === 'SINGLE') {
+ $repetition_string = _('Keine Wiederholung');
+ } elseif ($this->repetition_type === 'DAILY') {
+ if ($this->interval > 0) {
+ if ($this->interval == '1') {
+ //Each day
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ _('Täglich (%u Termine)'),
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < CalendarDate::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Täglich bis zum %1$s'),
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = _('Täglich ohne Begrenzung');
+ }
+ } else {
+ //Every %u day
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ _('Jeden %1$u. Tag (%2$u Termine)'),
+ $this->interval,
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < self::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Jeden %1$u. Tag bis zum %2$s'),
+ $this->interval,
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = sprintf(
+ _('Jeden %u. Tag ohne Begrenzung'),
+ $this->interval
+ );
+ }
+ }
+ }
+ } elseif ($this->repetition_type === 'WEEKLY') {
+ $weekday_string = '';
+ if (strlen($this->days) > 1) {
+ //Multiple days
+ $days = [];
+ foreach (str_split($this->days) as $day_number) {
+ if ($day_number == '7') {
+ $day_number = '0';
+ }
+ $days[] = getWeekday($day_number, false);
+ }
+ $all_but_last_day = array_slice($days, 0, -1);
+ $weekday_string = sprintf(
+ _('%1$s und %2$s'),
+ implode(', ', $all_but_last_day),
+ end($days)
+ );
+ } else {
+ //One day
+ $weekday_string = getWeekday($this->days[0], false);
+ }
+ if ($this->interval == '1') {
+ //Each week
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ ngettext('Einmal am folgenden %s', 'Jeden %1$s (%2$u Termine)', $this->number_of_dates - 1),
+ $weekday_string,
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < self::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Jeden %1$s bis zum %2$s'),
+ $weekday_string,
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = sprintf(
+ _('Jeden %s ohne Begrenzung'),
+ $weekday_string
+ );
+ }
+ } else {
+ //Every %u week
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ _('Jeden %1$u. %2$s (%3$u Termine)'),
+ $this->interval,
+ $weekday_string,
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < self::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Jeden %1$u. %2$s bis zum %3$s'),
+ $this->interval,
+ $weekday_string,
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = sprintf(
+ _('Jeden %1$u. %2$s ohne Begrenzung'),
+ $this->interval,
+ $weekday_string
+ );
+ }
+ }
+ } elseif ($this->repetition_type === 'MONTHLY') {
+ if ($this->interval == '1') {
+ //Each month
+ if ($this->days) {
+ if ($this->offset < 0) {
+ //Repetition on one specific day of week in the last week.
+ $repetition_string = sprintf(
+ _('Jeden Monat am letzten %s'),
+ getWeekday($this->days, false)
+ );
+ } else {
+ //Repetition on one specific day of week in a specific week.
+ $repetition_string = sprintf(
+ _('Jeden Monat am %1$u. %2$s'),
+ $this->offset,
+ getWeekday($this->days, false)
+ );
+ }
+ } else {
+ //Repetition on one specific day of month.
+ $repetition_string = sprintf(
+ _('Jeden Monat am %u. Tag'),
+ $this->offset
+ );
+ }
+ } else {
+ //Every %u month
+ if ($this->days) {
+ if ($this->offset < 0) {
+ //Repetition on one specific day of week on the last week.
+ $repetition_string = sprintf(
+ _('Jeden %1$u. Monat am letzten %2$s'),
+ $this->interval,
+ getWeekday($this->days, false)
+ );
+ } else {
+ //Repetition on one specific day of week in a specific week.
+ $repetition_string = sprintf(
+ _('Jeden %1$u. Monat am %2$u. %3$s'),
+ $this->interval,
+ $this->offset,
+ getWeekday($this->days, false)
+ );
+ }
+ } else {
+ //Repetition on one specific day of month.
+ $repetition_string = sprintf(
+ _('Jeden %1$u. Monat am %2$u.'),
+ $this->interval,
+ $this->offset
+ );
+ }
+ }
+ } elseif ($this->repetition_type === 'YEARLY') {
+ if ($this->interval == '1') {
+ //Each year
+ if ($this->days) {
+ //Repetition on one specific day of week in a specific week
+ //in a specific month.
+ if ($this->offset < 0) {
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ _('Jedes Jahr im %1$s am letzten %2$s (%3$u Termine)'),
+ getMonthName($this->month, false),
+ getWeekday($this->days, false),
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < self::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Jedes Jahr im %1$s am letzten %2$s bis zum %3$s'),
+ getMonthName($this->month, false),
+ getWeekday($this->days, false),
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = sprintf(
+ _('Jedes Jahr im %1$s am letzten %2$s ohne Begrenzung'),
+ getMonthName($this->month, false),
+ getWeekday($this->days, false)
+ );
+ }
+ } else {
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ _('Jedes Jahr im %1$s am %2$u. %3$s (%4$u Termine'),
+ getMonthName($this->month, false),
+ $this->offset,
+ getWeekday($this->days, false),
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < self::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Jedes Jahr im %1$s am %2$u. %3$s bis zum %4$s'),
+ getMonthName($this->month, false),
+ $this->offset,
+ getWeekday($this->days, false),
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = sprintf(
+ _('Jedes Jahr im %1$s am %2$u. %3$s ohne Begrenzung'),
+ getMonthName($this->month, false),
+ $this->offset,
+ getWeekday($this->days, false)
+ );
+ }
+ }
+ } else {
+ //Repetition on one specific day of month.
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ _('Jedes Jahr am %1$u. %2$s (%3$u Termine)'),
+ $this->offset,
+ getMonthName($this->month, false),
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < self::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Jedes Jahr am %1$u. %2$s bis zum %3$s'),
+ $this->offset,
+ getMonthName($this->month, false),
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = sprintf(
+ _('Jedes Jahr am %1$u. %2$s ohne Begrenzung'),
+ $this->offset,
+ getMonthName($this->month, false)
+ );
+ }
+ }
+ } else {
+ //Every %u years
+ if ($this->days) {
+ //Repetition on one specific day of week in a specific week
+ //in a specific month.
+ if ($this->offset < 0) {
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ _('Jedes %1$u. Jahr im %2$s am letzten %3$s (%4$u Termine)'),
+ $this->interval,
+ getMonthName($this->month, false),
+ getWeekday($this->days, false),
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < self::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Jedes %1$u. Jahr im %2$s am letzten %3$s bis zum %4$s'),
+ $this->interval,
+ getMonthName($this->month, false),
+ getWeekday($this->days, false),
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = sprintf(
+ _('Jedes %1$u. Jahr im %2$s am letzten %3$s ohne Begrenzung'),
+ $this->interval,
+ getMonthName($this->month, false),
+ getWeekday($this->days, false)
+ );
+ }
+ } else {
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ _('Jedes %1$u. Jahr im %2$s am %3$u. %4$s (%5$u Termine)'),
+ $this->interval,
+ getMonthName($this->month, false),
+ $this->offset,
+ getWeekday($this->days, false),
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < self::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Jedes %1$u. Jahr im %2$s am %3$u. %4$s bis zum %5$s'),
+ $this->interval,
+ getMonthName($this->month, false),
+ $this->offset,
+ getWeekday($this->days, false),
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = sprintf(
+ _('Jedes %1$u. Jahr im %2$s am %3$u. %4$s ohne Begrenzung'),
+ $this->interval,
+ getMonthName($this->month, false),
+ $this->offset,
+ getWeekday($this->days, false)
+ );
+ }
+ }
+ } else {
+ //Repetition on one specific day of month.
+ if ($this->number_of_dates > 1) {
+ $repetition_string = sprintf(
+ _('Jedes %1$u. Jahr am %2$u. %3$s (%4$u Termine)'),
+ $this->interval,
+ $this->offset,
+ getMonthName($this->month, false),
+ $this->number_of_dates
+ );
+ } elseif ($this->repetition_end < self::NEVER_ENDING) {
+ $repetition_string = sprintf(
+ _('Jedes %1$u. Jahr am %2$u. %3$s bis zum %4$s'),
+ $this->interval,
+ $this->offset,
+ getMonthName($this->month, false),
+ date('d.m.Y', $this->repetition_end)
+ );
+ } else {
+ $repetition_string = sprintf(
+ _('Jedes %1$u. Jahr am %2$u. %3$s ohne Begrenzung'),
+ $this->interval,
+ $this->offset,
+ getMonthName($this->month, false)
+ );
+ }
+ }
+ }
+ }
+
+ return $repetition_string;
+ }
+
+
+ /**
+ * Creates the HTML for creating a repetition input Vue component instance
+ * and fills it with the values from the model.
+ *
+ * @param string $element_name The name of the element.
+ *
+ * @return string The HTML code for creating the repetition input vue instance.
+ */
+ public function getRepetitionInputHtml(string $element_name = 'repetition') : string
+ {
+ $repetition_end_type = '';
+ $repetition_end_date = '';
+ $repetition_dow = '[]';
+ $repetition_dow_week = '';
+
+ if ($this->isNew()) {
+ $repetition_end_date = htmlReady(date('d.m.Y', $this->end));
+ $repetition_dow = sprintf('["%s"]', date('N', $this->begin));
+ $repetition_dow_week = '1';
+ } else {
+
+ if ($this->repetition_end && intval($this->repetition_end) !== self::NEVER_ENDING) {
+ $repetition_end_date = htmlReady(date('d.m.Y', $this->repetition_end));
+ } else {
+ //Provide a good default value in case the user wants to enable or change the repetition:
+ $repetition_end_date = htmlReady(date('d.m.Y', $this->end));
+ }
+ if ($this->days) {
+ $repetition_dow = json_encode(str_split($this->days));
+ $repetition_dow_week = $this->offset;
+ } else {
+ //The days field is not in use. Use the day of the beginning as a good default.
+ $repetition_dow = sprintf('["%s"]', date('N', $this->begin));
+ //Also set repetition_dow_week to 1 as a good default in case the user
+ //switches to the monthly repetition type where a specific day of week
+ //is selected instead of a specific day of month:
+ $repetition_dow_week = '1';
+ }
+
+ if ($this->number_of_dates > 1) {
+ $repetition_end_type = 'end_count';
+ } elseif ($this->repetition_end && intval($this->repetition_end) !== self::NEVER_ENDING) {
+ //The end date is at some certain date and not on the virtual "never" date.
+ $repetition_end_type = 'end_date';
+ }
+ }
+
+ $attributes = [
+ 'name' => $element_name,
+ 'default_date' => $this->begin,
+ 'repetition_type' => $this->isNew() ? '' : $this->repetition_type,
+ 'repetition_interval' => $this->isNew() ? '1' : $this->interval,
+ ':repetition_dow' => $repetition_dow,
+ ':repetition_dow_week' => $repetition_dow_week,
+ ':repetition_month' => $this->isNew() ? date('m', $this->begin) : $this->month,
+ ':repetition_month_type' => $this->isNew() ? "'dom'" : ($this->days ? "'dow'" : "'dom'"),
+ ':repetition_dom' => $this->isNew() ? date('d', $this->begin) : $this->offset,
+ ':repetition_end_type' => sprintf("'%s'", $repetition_end_type),
+ ':number_of_dates' => $this->isNew() ? '1' : $this->number_of_dates,
+ ':repetition_end_date' => sprintf("'%s'", $repetition_end_date)
+ ];
+ return sprintf('<repetition-input %s></repetition-input>', arrayToHtmlAttributes($attributes));
+ }
+
+ public function getCategoryAsString() : string
+ {
+ if ($this->user_category) {
+ return $this->user_category;
+ }
+ return $GLOBALS['PERS_TERMIN_KAT'][$this->category]['name'] ?? '';
+ }
+
+ /**
+ * Returns the textual ordinal for the offset of a weekday from property offset
+ * or an empty string if offset is not set.
+ *
+ * @return string The textual ordinal.
+ */
+ public function getOrdinalName(): string
+ {
+ if (mb_strlen($this->offset)) {
+ $ordinal_array = [
+ '1' => 'first',
+ '2' => 'second',
+ '3' => 'third',
+ '4' => 'fourth',
+ '5' => 'fifth',
+ '-1' => 'last'
+ ];
+ return $ordinal_array[$this->offset];
+ }
+ return '';
+ }
+
+ /**
+ * Returns the short name of first weekday from property days or an
+ * empty string if days is not set.
+ *
+ * @param $offset int Offset of days.
+ * @return string Short name of weekday.
+ */
+ public function getWeekdayName(int $offset = 0): string
+ {
+ if (mb_strlen($this->days)) {
+ $wdays = [
+ '1' => 'mon',
+ '2' => 'tue',
+ '3' => 'wed',
+ '4' => 'thu',
+ '5' => 'fri',
+ '6' => 'sat',
+ '7' => 'sun'
+ ];
+ return $wdays[substr($this->days, $offset, 1)];
+ }
+ return '';
+ }
+
+ /**
+ * Returns a string representation of the access field.
+ *
+ * @return string A localised string of the access field.
+ */
+ public function getVisibilityAsString() : string
+ {
+ if ($this->access === 'PUBLIC') {
+ return _('Öffentlich');
+ } elseif ($this->access === 'CONFIDENTIAL') {
+ return _('Vertraulich');
+ } else {
+ return _('Privat');
+ }
+ }
+
+ /**
+ * Returns the names of the participants of the date. This also includes courses
+ * to which the date is assigned.
+ *
+ * @param string $user_id The user for which to generate the participant array.
+ * The user with that ID is excluded from that list.
+ * @return array A list with the names of the participants of the date.
+ */
+ public function getParticipantsAsStringArray(string $user_id = '') : array
+ {
+ $participant_strings = [];
+ foreach ($this->calendars as $calendar) {
+ if ($calendar->range_id === $user_id) {
+ //Exclude the user for which to generate the list.
+ continue;
+ }
+ if ($calendar->course instanceof Course) {
+ $participant_strings[] = $calendar->course->getFullName();
+ } elseif ($calendar->user instanceof User) {
+ $participant_strings[] = $calendar->user->getFullName();
+ }
+ }
+
+ asort($participant_strings);
+
+ return $participant_strings;
+ }
+}