aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMoritz Strohm <strohm@data-quest.de>2024-01-29 15:16:24 +0000
committerMoritz Strohm <strohm@data-quest.de>2024-01-29 15:16:24 +0000
commit7c1df847d94d3956bc763b94b73cebfe108dc9a1 (patch)
treee18e003bff65c5bf0748c644d6cd3d235cb1feca
parentda0110d5e85279123e8dde392cb4c926397238bf (diff)
StEP 01354, closes #1354
Closes #1354 Merge request studip/studip!2116
-rw-r--r--app/controllers/calendar/calendar.php1350
-rw-r--r--app/controllers/calendar/contentbox.php134
-rw-r--r--app/controllers/calendar/date.php828
-rw-r--r--app/controllers/calendar/group.php336
-rw-r--r--app/controllers/calendar/instschedule.php190
-rw-r--r--app/controllers/calendar/schedule.php8
-rw-r--r--app/controllers/calendar/single.php571
-rw-r--r--app/controllers/contact.php31
-rw-r--r--app/controllers/ical.php19
-rw-r--r--app/controllers/institute/overview.php4
-rw-r--r--app/controllers/institute/schedule.php179
-rw-r--r--app/controllers/settings/calendar.php9
-rw-r--r--app/views/calendar/calendar/add_courses.php15
-rw-r--r--app/views/calendar/calendar/course.php1
-rw-r--r--app/views/calendar/calendar/export.php45
-rw-r--r--app/views/calendar/calendar/import.php (renamed from app/views/calendar/single/import.php)21
-rw-r--r--app/views/calendar/calendar/index.php1
-rw-r--r--app/views/calendar/calendar/publish.php (renamed from app/views/calendar/single/share.php)9
-rw-r--r--app/views/calendar/calendar/share.php13
-rw-r--r--app/views/calendar/contentbox/_termin.php107
-rw-r--r--app/views/calendar/contentbox/display.php69
-rw-r--r--app/views/calendar/date/_add_edit_form.php154
-rw-r--r--app/views/calendar/date/add.php4
-rw-r--r--app/views/calendar/date/delete.php51
-rw-r--r--app/views/calendar/date/edit.php4
-rw-r--r--app/views/calendar/date/index.php135
-rw-r--r--app/views/calendar/date/move.php22
-rw-r--r--app/views/calendar/group/_attendees.php82
-rw-r--r--app/views/calendar/group/_tooltip.php27
-rw-r--r--app/views/calendar/group/_tooltip_year.php21
-rw-r--r--app/views/calendar/group/day.php128
-rw-r--r--app/views/calendar/group/month.php110
-rw-r--r--app/views/calendar/group/week.php144
-rw-r--r--app/views/calendar/group/year.php122
-rw-r--r--app/views/calendar/instschedule/_entry_details.php29
-rw-r--r--app/views/calendar/instschedule/index.php15
-rw-r--r--app/views/calendar/schedule/stylesheet.php (renamed from app/views/calendar/stylesheet.php)0
-rw-r--r--app/views/calendar/single/_attendees.php31
-rw-r--r--app/views/calendar/single/_calhead.php16
-rw-r--r--app/views/calendar/single/_calhead_label_day.php3
-rw-r--r--app/views/calendar/single/_calhead_label_week.php3
-rw-r--r--app/views/calendar/single/_day.php90
-rw-r--r--app/views/calendar/single/_day_cell.php54
-rw-r--r--app/views/calendar/single/_day_dayevents.php33
-rw-r--r--app/views/calendar/single/_day_table.php28
-rw-r--r--app/views/calendar/single/_edit_status.php48
-rw-r--r--app/views/calendar/single/_event_data.php100
-rw-r--r--app/views/calendar/single/_include_month.php135
-rw-r--r--app/views/calendar/single/_jump_to.php13
-rw-r--r--app/views/calendar/single/_select_calendar.php80
-rw-r--r--app/views/calendar/single/_select_category.php16
-rw-r--r--app/views/calendar/single/_semester_filter.php30
-rw-r--r--app/views/calendar/single/_tooltip.php113
-rw-r--r--app/views/calendar/single/day.php13
-rw-r--r--app/views/calendar/single/edit.php454
-rw-r--r--app/views/calendar/single/edit_status.php3
-rw-r--r--app/views/calendar/single/event.php1
-rw-r--r--app/views/calendar/single/export_calendar.php54
-rw-r--r--app/views/calendar/single/manage_access.php99
-rw-r--r--app/views/calendar/single/month.php109
-rw-r--r--app/views/calendar/single/seminar_events.php104
-rw-r--r--app/views/calendar/single/week.php199
-rw-r--r--app/views/calendar/single/year.php145
-rw-r--r--app/views/course/cancel_dates/index.php2
-rw-r--r--app/views/course/dates/details-edit.php20
-rw-r--r--app/views/course/dates/details.php16
-rw-r--r--app/views/course/details/index.php7
-rw-r--r--app/views/course/timesrooms/_cancel_form.php2
-rw-r--r--app/views/institute/overview/index.php1
-rw-r--r--app/views/institute/schedule/index.php1
-rw-r--r--app/views/settings/calendar.php78
-rw-r--r--app/views/settings/general.php2
-rw-r--r--config/config.inc.php.dist123
-rw-r--r--db/migrations/1.160_step_00283_update_calendar_settings.php12
-rw-r--r--db/migrations/5.4.1.1_alter_calendar_tables.php247
-rw-r--r--db/studip_default_data.sql2
-rw-r--r--lib/bootstrap-autoload.php1
-rw-r--r--lib/calendar/CalendarColumn.class.php2
-rw-r--r--lib/calendar/CalendarExport.class.php87
-rw-r--r--lib/calendar/CalendarExportException.class.php18
-rw-r--r--lib/calendar/CalendarExportFile.class.php122
-rw-r--r--lib/calendar/CalendarImport.class.php91
-rw-r--r--lib/calendar/CalendarImportFile.class.php141
-rw-r--r--lib/calendar/CalendarParser.class.php132
-rw-r--r--lib/calendar/CalendarView.class.php4
-rw-r--r--lib/calendar/CalendarWeekView.class.php2
-rw-r--r--lib/calendar/CalendarWidgetView.php2
-rw-r--r--lib/calendar/CalendarWriter.class.php53
-rw-r--r--lib/calendar/CalendarWriterICalendar.class.php629
-rw-r--r--lib/calendar/lib/CalendarError.class.php56
-rw-r--r--lib/calendar/lib/ErrorHandler.class.php119
-rw-r--r--lib/classes/Event.class.php223
-rw-r--r--lib/classes/Event.interface.php184
-rw-r--r--lib/classes/IcalExport.php17
-rw-r--r--lib/classes/JsonApi/Routes/Events/CourseEventsIndex.php16
-rw-r--r--lib/classes/JsonApi/Routes/Events/UserEventsIcal.php14
-rw-r--r--lib/classes/JsonApi/Routes/Events/UserEventsIndex.php9
-rw-r--r--lib/classes/JsonApi/SchemaMap.php5
-rw-r--r--lib/classes/JsonApi/Schemas/CalendarEvent.php24
-rw-r--r--lib/classes/JsonApi/Schemas/Course.php25
-rw-r--r--lib/classes/JsonApi/Schemas/CourseEvent.php14
-rw-r--r--lib/classes/JsonApi/Schemas/Semester.php1
-rw-r--r--lib/classes/Privacy.php2
-rw-r--r--lib/classes/UserManagement.class.php10
-rw-r--r--lib/classes/calendar/Calendar.php187
-rw-r--r--lib/classes/calendar/CalendarInstscheduleModel.php148
-rw-r--r--lib/classes/calendar/CalendarScheduleModel.php2
-rw-r--r--lib/classes/calendar/EventData.class.php (renamed from lib/calendar/EventData.class.php)10
-rw-r--r--lib/classes/calendar/EventSource.interface.php (renamed from lib/calendar/EventSource.interface.php)0
-rw-r--r--lib/classes/calendar/Helper.php107
-rw-r--r--lib/classes/calendar/ICalendarExport.class.php638
-rw-r--r--lib/classes/calendar/ICalendarImport.class.php (renamed from lib/calendar/CalendarParserICalendar.class.php)406
-rw-r--r--lib/classes/calendar/Owner.interface.php40
-rw-r--r--lib/classes/calendar/SingleCalendar.php1488
-rw-r--r--lib/classes/forms/SelectedRangesInput.php58
-rw-r--r--lib/classes/searchtypes/StandardSearch.class.php20
-rw-r--r--lib/classes/sidebar/DateSelectWidget.php51
-rw-r--r--lib/exceptions/FeatureDisabledException.php11
-rw-r--r--lib/models/CalendarEvent.class.php1394
-rw-r--r--lib/models/CalendarUser.class.php114
-rw-r--r--lib/models/ConsultationBooking.php10
-rw-r--r--lib/models/ConsultationEvent.php6
-rw-r--r--lib/models/ConsultationSlot.php32
-rw-r--r--lib/models/Contact.class.php16
-rw-r--r--lib/models/ContactGroup.class.php44
-rw-r--r--lib/models/ContactGroupItem.class.php61
-rw-r--r--lib/models/Course.class.php38
-rw-r--r--lib/models/CourseCancelledEvent.class.php149
-rw-r--r--lib/models/CourseDate.class.php188
-rw-r--r--lib/models/CourseEvent.class.php596
-rw-r--r--lib/models/CourseExDate.class.php174
-rw-r--r--lib/models/CourseMarkedEvent.class.php132
-rw-r--r--lib/models/EventData.class.php144
-rw-r--r--lib/models/User.class.php54
-rw-r--r--lib/models/calendar/CalendarCourseDate.class.php34
-rw-r--r--lib/models/calendar/CalendarCourseExDate.class.php35
-rw-r--r--lib/models/calendar/CalendarDate.class.php944
-rw-r--r--lib/models/calendar/CalendarDateAssignment.class.php714
-rw-r--r--lib/models/calendar/CalendarDateException.class.php40
-rw-r--r--lib/modules/CoreCalendar.class.php9
-rw-r--r--lib/modules/CoreOverview.class.php2
-rw-r--r--lib/modules/ScheduleWidget.php42
-rw-r--r--lib/modules/TerminWidget.php7
-rw-r--r--lib/navigation/CalendarNavigation.php27
-rw-r--r--lib/navigation/ProfileNavigation.php2
-rw-r--r--lib/navigation/StartNavigation.php4
-rw-r--r--lib/seminar_open.php2
-rw-r--r--locale/de/LC_MAILS/_date_information.php28
-rw-r--r--locale/de/LC_MAILS/date_changed.php10
-rw-r--r--locale/de/LC_MAILS/date_created.php10
-rw-r--r--locale/de/LC_MAILS/date_deleted.php6
-rw-r--r--locale/de/LC_MAILS/date_participation.php14
-rw-r--r--locale/en/LC_MAILS/_date_information.php24
-rw-r--r--locale/en/LC_MAILS/date_changed.php10
-rw-r--r--locale/en/LC_MAILS/date_created.php10
-rw-r--r--locale/en/LC_MAILS/date_deleted.php6
-rw-r--r--locale/en/LC_MAILS/date_participation.php14
-rw-r--r--resources/assets/javascripts/bootstrap/calendar_dialog.js11
-rw-r--r--resources/assets/javascripts/bootstrap/forms.js18
-rw-r--r--resources/assets/javascripts/bootstrap/fullcalendar.js1
-rw-r--r--resources/assets/javascripts/bootstrap/resources.js64
-rw-r--r--resources/assets/javascripts/entry-base.js1
-rw-r--r--resources/assets/javascripts/init.js6
-rw-r--r--resources/assets/javascripts/lib/calendar_dialog.js64
-rw-r--r--resources/assets/javascripts/lib/dates.js4
-rw-r--r--resources/assets/javascripts/lib/datetime.js61
-rw-r--r--resources/assets/javascripts/lib/fullcalendar.js227
-rw-r--r--resources/assets/stylesheets/highcontrast.scss292
-rw-r--r--resources/assets/stylesheets/less/calendar.less587
-rw-r--r--resources/assets/stylesheets/scss/calendar.scss135
-rw-r--r--resources/assets/stylesheets/scss/forms.scss19
-rw-r--r--resources/assets/stylesheets/studip.less1
-rw-r--r--resources/assets/stylesheets/studip.scss1
-rw-r--r--resources/vue/base-components.js12
-rw-r--r--resources/vue/components/Datepicker.vue76
-rw-r--r--resources/vue/components/EditableList.vue6
-rw-r--r--resources/vue/components/StudipDateTime.vue22
-rw-r--r--resources/vue/components/form_inputs/CalendarPermissionsTable.vue86
-rw-r--r--resources/vue/components/form_inputs/DateListInput.vue98
-rw-r--r--resources/vue/components/form_inputs/DayOfWeekSelect.vue60
-rw-r--r--resources/vue/components/form_inputs/MyCoursesColouredTable.vue166
-rw-r--r--resources/vue/components/form_inputs/RepetitionInput.vue364
-rw-r--r--templates/forms/date_list_input.php3
-rw-r--r--templates/forms/selected_ranges_input.php7
-rw-r--r--templates/sidebar/date-select-widget.php13
-rw-r--r--tests/jsonapi/UserEventsIcalTest.php22
-rw-r--r--tests/jsonapi/UserEventsIndexTest.php20
-rw-r--r--tests/jsonapi/_bootstrap.php1
188 files changed, 8251 insertions, 11987 deletions
diff --git a/app/controllers/calendar/calendar.php b/app/controllers/calendar/calendar.php
index 2e17ff2..eecf900 100644
--- a/app/controllers/calendar/calendar.php
+++ b/app/controllers/calendar/calendar.php
@@ -1,636 +1,896 @@
<?php
-/*
- * The controller for 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>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @since
- */
class Calendar_CalendarController extends AuthenticatedController
{
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
- PageLayout::setHelpKeyword('Basis.Terminkalender');
- $this->settings = $GLOBALS['user']->cfg->CALENDAR_SETTINGS;
- if ($this->settings['start'] < 0 || $this->settings['start'] > 23) {
- $this->settings['start'] = 0;
- }
- if ($this->settings['end'] < 0 || $this->settings['end'] > 23) {
- $this->settings['end'] = 23;
- }
- if (!in_array($this->settings['view'], ['day','week','month','year'])) {
- $this->settings['view'] = 'week';
- }
- if (!is_array($this->settings)) {
- $this->settings = Calendar::getDefaultUserSettings();
- }
- URLHelper::bindLinkParam('atime', $this->atime);
- $this->atime = Request::int('atime', time());
- $this->category = Request::int('category');
- $this->last_view = Request::option('last_view',
- $this->settings['view']);
- $this->action = $action;
- $this->restrictions = [
- 'STUDIP_CATEGORY' => $this->category ?: null,
- // hide events with status 3 (CalendarEvent::PARTSTAT_DECLINED)
- 'STUDIP_GROUP_STATUS' => !empty($this->settings['show_declined']) ? null : [0,1,2,5]
- ];
- if ($this->category) {
- URLHelper::bindLinkParam('category', $this->category);
- }
-
- $this->range_id = '';
- if (Config::get()->COURSE_CALENDAR_ENABLE
- && !Request::get('self')
- && Course::findCurrent()) {
- $current_seminar = new Seminar(Course::findCurrent());
- if ($current_seminar->getSlotModule('calendar') instanceOf CoreCalendar) {
- $this->range_id = $current_seminar->id;
- Navigation::activateItem('/course/calendar');
- }
- }
- if (!$this->range_id) {
- $this->range_id = Request::option('range_id', $GLOBALS['user']->id);
- Navigation::activateItem('/calendar/calendar');
- URLHelper::bindLinkParam('range_id', $this->range_id);
+ if (!Context::isCourse() && Navigation::hasItem('/calendar')) {
+ Navigation::activateItem('/calendar');
}
-
- URLHelper::bindLinkParam('last_view', $this->last_view);
}
- protected function createSidebar($active = null, $calendar = null)
- {
- $active = $active ?: $this->last_view;
- $sidebar = Sidebar::Get();
-
- $views = new ViewsWidget();
- $views->addLink(_('Tag'), $this->url_for($this->base . 'day'))
- ->setActive($active == 'day');
- $views->addLink(_('Woche'), $this->url_for($this->base . 'week'))
- ->setActive($active == 'week');
- $views->addLink(_('Monat'), $this->url_for($this->base . 'month'))
- ->setActive($active == 'month');
- $views->addLink(_('Jahr'), $this->url_for($this->base . 'year'))
- ->setActive($active == 'year');
- $sidebar->addWidget($views);
- }
- protected function createSidebarFilter()
+ protected function buildSidebar($schedule = false)
{
- $tmpl_factory = $this->get_template_factory();
-
- $filters = new SidebarWidget();
- $filters->setTitle('Auswahl');
-
- $tmpl = $tmpl_factory->open('calendar/single/_jump_to');
- $tmpl->atime = $this->atime;
- $tmpl->action = $this->action;
- $tmpl->action_url = $this->url_for('calendar/single/jump_to');
- $filters->addElement(new WidgetElement($tmpl->render()));
-
- $tmpl = $tmpl_factory->open('calendar/single/_select_category');
- $tmpl->action_url = $this->url_for();
- $tmpl->category = $this->category;
- $filters->addElement(new WidgetElement($tmpl->render()));
- Sidebar::get()->addWidget($filters);
-
- if (Config::get()->CALENDAR_GROUP_ENABLE
- || Config::get()->COURSE_CALENDAR_ENABLE) {
- $tmpl = $tmpl_factory->open('calendar/single/_select_calendar');
- $tmpl->range_id = $this->range_id;
- $tmpl->action_url = $this->url_for('calendar/group/switch');
- $tmpl->view = $this->action;
- $filters->addElement(new WidgetElement($tmpl->render()));
-
- $settings = new OptionsWidget();
- $settings->addCheckbox(
- _('Abgelehnte Termine anzeigen'),
- $this->settings['show_declined'] ?? false,
- $this->url_for($this->base . 'show_declined', ['show_declined' => 1]),
- $this->url_for($this->base . 'show_declined', ['show_declined' => 0])
+ $sidebar = Sidebar::get();
+
+ $actions = new ActionsWidget();
+ if ($schedule) {
+ $actions->addLink(
+ _('Neuer Eintrag'),
+ $this->url_for('calendar/calendar/add_schedule_entry'),
+ Icon::create('add'),
+ ['data-dialog' => 'size=default']
+ );
+ } else {
+ $actions->addLink(
+ _('Termin anlegen'),
+ $this->url_for('calendar/date/add'),
+ Icon::create('add'),
+ ['data-dialog' => 'size=auto']
);
- Sidebar::get()->addWidget($settings);
+ }
+
+ if (!$GLOBALS['perm']->have_perm('admin')) {
+ $actions->addLink(
+ _('Veranstaltung auswählen'),
+ $this->url_for('calendar/calendar/add_courses'),
+ Icon::create('add'),
+ ['data-dialog' => 'size=medium']
+ );
+ }
+ if (!$schedule) {
+ $actions->addLink(
+ _('Termine exportieren'),
+ $this->url_for('calendar/calendar/export'),
+ Icon::create('export'),
+ ['data-dialog' => 'size=auto']
+ );
+ $actions->addLink(
+ _('Termine importieren'),
+ $this->url_for('calendar/calendar/import'),
+ Icon::create('import'),
+ ['data-dialog' => 'size=auto']
+ );
+ $actions->addLink(
+ _('Kalender veröffentlichen'),
+ $this->url_for('calendar/calendar/publish'),
+ Icon::create('export'),
+ ['data-dialog' => 'size=auto']
+ );
+ }
+ if (!$schedule && Config::get()->CALENDAR_GROUP_ENABLE) {
+ $actions->addLink(
+ _('Kalender teilen'),
+ $this->url_for('calendar/calendar/share'),
+ Icon::create('share'),
+ ['data-dialog' => 'size=default']
+ );
+ }
+ $actions->addLink(
+ _('Drucken'),
+ 'javascript:void(window.print());',
+ Icon::create('print')
+ );
+ $actions->addLink(
+ _('Einstellungen'),
+ $this->url_for('settings/calendar'),
+ Icon::create('settings'),
+ ['data-dialog' => 'size=auto;reload-on-close']
+ );
+ $sidebar->addWidget($actions);
+
+ if (!$schedule) {
+ $date = new DateSelectWidget();
+ $date->setDate(\Studip\Calendar\Helper::getDefaultCalendarDate());
+ $date->setCalendarControl(true);
+ $sidebar->addWidget($date);
}
}
- public function index_action()
+ protected function getUserCalendarSlotSettings() : array
{
- // switch to the view the user has selected in his personal settings
- $default_view = $this->settings['view'] ?: 'week';
-
- // Remove cid
- if (Request::option('self')) {
- Context::close();
-
- $this->redirect(URLHelper::getURL('dispatch.php/' . $this->base
- . $default_view . '/' . $GLOBALS['user']->id, [], true));
- } else {
- $this->redirect(URLHelper::getURL('dispatch.php/' . $this->base
- . $default_view));
- }
+ return [
+ 'day' => \Studip\Calendar\Helper::getCalendarSlotDuration('day'),
+ 'week' => \Studip\Calendar\Helper::getCalendarSlotDuration('week'),
+ 'day_group' => \Studip\Calendar\Helper::getCalendarSlotDuration('day_group'),
+ 'week_group' => \Studip\Calendar\Helper::getCalendarSlotDuration('week_group')
+ ];
}
- public function edit_action($range_id = null, $event_id = null)
+ public function index_action()
{
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
- $this->event = $this->calendar->getEvent($event_id);
-
- if ($this->event->isNew()) {
- // $this->event = $this->calendar->getNewEvent();
- if (Request::get('isdayevent')) {
- $this->event->setStart(mktime(0, 0, 0, date('n', $this->atime),
- date('j', $this->atime), date('Y', $this->atime)));
- $this->event->setEnd(mktime(23, 59, 59, date('n', $this->atime),
- date('j', $this->atime), date('Y', $this->atime)));
+ PageLayout::setTitle(_('Kalender'));
+
+ if (Request::isPost()) {
+ //In case the checkbox of the options widget is clicked, the resulting
+ //POST request must be catched here and result in a redirect.
+ CSRFProtection::verifyUnsafeRequest();
+ if (Request::bool('show_declined')) {
+ $this->redirect('calendar/calendar', ['show_declined' => '1']);
} else {
- $this->event->setStart($this->atime);
- $this->event->setEnd($this->atime + 3600);
+ $this->redirect('calendar/calendar');
}
- $this->event->setAuthorId($GLOBALS['user']->id);
- $this->event->setEditorId($GLOBALS['user']->id);
- $this->event->setAccessibility('PRIVATE');
- if (!Request::isXhr()) {
- PageLayout::setTitle($this->getTitle($this->calendar, _('Neuer Termin')));
+ return;
+ }
+
+ if (!Context::isCourse() && Navigation::hasItem('/calendar/calendar')) {
+ Navigation::activateItem('/calendar/calendar');
+ }
+
+ $view = Request::get('view', 'single');
+ $group_view = false;
+ $timeline_view = false;
+ if (Config::get()->CALENDAR_GROUP_ENABLE) {
+ $group_view = in_array($view, ['group', 'timeline']);
+ $timeline_view = $view === 'timeline';
+ }
+
+ $calendar_owner = null;
+ $selected_group = null;
+ $user_id = Request::option('user_id', User::findCurrent()->id);
+ $group_id = Request::option('group_id');
+
+ if (Config::get()->CALENDAR_GROUP_ENABLE) {
+ if ($group_id) {
+ $selected_group = ContactGroup::find($group_id);
+ if ($selected_group->owner_id !== User::findCurrent()->id) {
+ //Thou shalt not see the groups of others!
+ throw new AccessDeniedException(_('Sie dürfen diesen Kalender nicht sehen!'));
+ }
+ $view = $view === 'timeline' ? 'timeline' : 'group';
+ } elseif ($user_id) {
+ $calendar_owner = User::getCalendarOwner($user_id);
+ $view = 'single';
}
} else {
- // open read only events and course events not as form
- // show information in dialog instead
- if (!$this->event->havePermission(Event::PERMISSION_WRITABLE)
- || $this->event instanceof CourseEvent) {
- if (!$this->event instanceof CourseEvent && $this->event->attendees->count() > 1) {
- if ($this->event->group_status) {
- $this->redirect($this->url_for('calendar/single/edit_status/' . implode('/',
- [$this->range_id, $this->event->event_id])));
- } else {
- $this->redirect($this->url_for('calendar/single/event/' . implode('/',
- [$this->range_id, $this->event->event_id])));
+ //Show the calendar of the current user.
+ $view = 'single';
+ $calendar_owner = User::findCurrent();
+ }
+
+ //Check for permissions:
+ $read_permissions = false;
+ $write_permissions = false;
+ if ($calendar_owner) {
+ $read_permissions = $calendar_owner->isCalendarReadable();
+ $write_permissions = $calendar_owner->isCalendarWritable();
+ } elseif ($selected_group) {
+ //Count on how many group member calendars the current user has read or write permissions:
+ foreach ($selected_group->items as $item) {
+ if ($item->user) {
+ if ($item->user->isCalendarReadable()) {
+ $read_permissions = true;
+ }
+ if ($item->user->isCalendarWritable()) {
+ $write_permissions = true;
}
- } else {
- $this->redirect($this->url_for('calendar/single/event/' . implode('/',
- [$this->range_id, $this->event->event_id])));
}
- return null;
- }
- if (!Request::isXhr()) {
- PageLayout::setTitle($this->getTitle($this->calendar, _('Termin bearbeiten')));
+ if ($read_permissions && $write_permissions) {
+ //We only need to determine one read and one write permission to set the relevant fullcalendar
+ //properties. The action to add/edit a date determines in which calendars the current user
+ //may write into.
+ break;
+ }
}
}
+ if (!$read_permissions) {
+ throw new AccessDeniedException(_('Sie dürfen diesen Kalender nicht sehen!'));
+ }
- if (Config::get()->CALENDAR_GROUP_ENABLE
- && $this->calendar->getRange() == Calendar::RANGE_USER) {
-
- if (Config::get()->CALENDAR_GRANT_ALL_INSERT) {
- $search_obj = SQLSearch::get("SELECT DISTINCT auth_user_md5.user_id, "
- . "{$GLOBALS['_fullname_sql']['full_rev_username']} as fullname, "
- . "auth_user_md5.perms, auth_user_md5.username "
- . "FROM auth_user_md5 "
- . "LEFT JOIN user_info ON (auth_user_md5.user_id = user_info.user_id) "
- . 'WHERE auth_user_md5.user_id <> ' . DBManager::get()->quote($GLOBALS['user']->id)
- . ' AND (username LIKE :input OR Vorname LIKE :input '
- . "OR CONCAT(Vorname,' ',Nachname) LIKE :input "
- . "OR CONCAT(Nachname,' ',Vorname) LIKE :input "
- . "OR Nachname LIKE :input OR {$GLOBALS['_fullname_sql']['full_rev']} LIKE :input "
- . ") ORDER BY fullname ASC",
- _('Person suchen'), 'user_id');
- } else {
- $search_obj = SQLSearch::get("SELECT DISTINCT auth_user_md5.user_id, "
- . "{$GLOBALS['_fullname_sql']['full_rev_username']} as fullname, "
- . "auth_user_md5.perms, auth_user_md5.username "
- . "FROM calendar_user "
- . "LEFT JOIN auth_user_md5 ON calendar_user.owner_id = auth_user_md5.user_id "
- . "LEFT JOIN user_info ON (auth_user_md5.user_id = user_info.user_id) "
- . 'WHERE calendar_user.user_id = '
- . DBManager::get()->quote($GLOBALS['user']->id)
- . ' AND calendar_user.permission > ' . Event::PERMISSION_READABLE
- . ' AND auth_user_md5.user_id <> ' . DBManager::get()->quote($GLOBALS['user']->id)
- . ' AND (username LIKE :input OR Vorname LIKE :input '
- . "OR CONCAT(Vorname,' ',Nachname) LIKE :input "
- . "OR CONCAT(Nachname,' ',Vorname) LIKE :input "
- . "OR Nachname LIKE :input OR {$GLOBALS['_fullname_sql']['full_rev']} LIKE :input "
- . ") ORDER BY fullname ASC",
- _('Person suchen'), 'user_id');
+ $this->buildSidebar(false);
+
+ $sidebar = Sidebar::get();
+
+ if (Config::get()->CALENDAR_GROUP_ENABLE) {
+ if ($calendar_owner && $calendar_owner->id === User::findCurrent()->id) {
+ //The user is viewing their own calendar.
+ $options = new OptionsWidget();
+ $options->addCheckbox(
+ _('Abgelehnte Termine anzeigen'),
+ Request::bool('show_declined'),
+ $this->url_for('calendar/calendar', ['show_declined' => '1']),
+ $this->url_for('calendar/calendar')
+ );
+ $sidebar->addWidget($options);
}
- // SEMBBS
- // Eintrag von Terminen bereits ab PERMISSION_READABLE
- /*
- $search_obj = new SQLSearch('SELECT DISTINCT auth_user_md5.user_id, '
- . $GLOBALS['_fullname_sql']['full_rev'] . ' as fullname, username, perms '
- . 'FROM calendar_user '
- . 'LEFT JOIN auth_user_md5 ON calendar_user.owner_id = auth_user_md5.user_id '
- . 'LEFT JOIN user_info ON (auth_user_md5.user_id = user_info.user_id) '
- . 'WHERE calendar_user.user_id = '
- . DBManager::get()->quote($GLOBALS['user']->id)
- . ' AND calendar_user.permission >= ' . Event::PERMISSION_READABLE
- . ' AND (username LIKE :input OR Vorname LIKE :input '
- . "OR CONCAT(Vorname,' ',Nachname) LIKE :input "
- . "OR CONCAT(Nachname,' ',Vorname) LIKE :input "
- . 'OR Nachname LIKE :input OR '
- . $GLOBALS['_fullname_sql']['full_rev'] . ' LIKE :input '
- . ') ORDER BY fullname ASC',
- _('Nutzer suchen'), 'user_id');
- // SEMBBS
- *
- */
-
-
- $this->quick_search = QuickSearch::get('user_id', $search_obj)
- ->fireJSFunctionOnSelect('STUDIP.Messages.add_adressee')
- ->withButton();
-
- // $default_selected_user = array($this->calendar->getRangeId());
- $this->mps = MultiPersonSearch::get('add_adressees')
- ->setLinkText(_('Mehrere Teilnehmende hinzufügen'))
- // ->setDefaultSelectedUser($default_selected_user)
- ->setTitle(_('Mehrere Teilnehmende hinzufügen'))
- ->setExecuteURL($this->url_for($this->base . 'edit'))
- ->setJSFunctionOnSubmit('STUDIP.Messages.add_adressees')
- ->setSearchObject($search_obj);
- $owners = SimpleORMapCollection::createFromArray(
- CalendarUser::findByUser_id($this->calendar->getRangeId()))
- ->pluck('owner_id');
- foreach (Calendar::getGroups($GLOBALS['user']->id) as $group) {
- $this->mps->addQuickfilter(
- $group->name,
- $group->members->filter(
- function ($member) use ($owners) {
- if (in_array($member->user_id, $owners)) {
- return $member;
- }
- })->pluck('user_id')
+ //Check if the user has groups. If so, display a select widget to select a group.
+ $groups = ContactGroup::findBySQL(
+ 'owner_id = :owner_id ORDER BY name ASC',
+ [
+ 'owner_id' => User::findCurrent()->id
+ ]
+ );
+ if ($groups) {
+ $available_groups = [];
+
+ //Check if the user has at least read permissions for the calendar of one user of one group:
+ foreach ($groups as $group) {
+ foreach ($group->items as $item) {
+ if ($item->user && $item->user->isCalendarReadable()) {
+ $available_groups[] = $group;
+ break 1;
+ }
+ }
+ }
+ if ($available_groups) {
+ $group_select = new SelectWidget(
+ _('Gruppe'),
+ $this->url_for('calendar/calendar/index', ['view' => 'group']),
+ 'group_id'
+ );
+ $options = [
+ '' => _('(bitte wählen)')
+ ];
+ foreach ($available_groups as $available_group) {
+ $options[$available_group->id] = $available_group->name;
+ }
+ $group_select->setOptions($options);
+ $group_select->setSelection($group_id);
+ $sidebar->addWidget($group_select);
+ }
+ }
+ //Get all calendars where the user has access to:
+ $other_users = User::findBySql(
+ "INNER JOIN `contact` c
+ ON `auth_user_md5`.`user_id` = c.`owner_id`
+ WHERE c.`user_id` = :current_user_id
+ AND c.`calendar_permissions` <> ''
+ ORDER BY `auth_user_md5`.`Vorname` ASC, `auth_user_md5`.`Nachname` ASC",
+ ['current_user_id' => User::findCurrent()->id]
+ );
+ if ($other_users) {
+ $calendar_select = new SelectWidget(
+ _('Kalender auswählen'),
+ $this->url_for('calendar/calendar'),
+ 'user_id'
);
+ $select_options = [
+ '' => _('(bitte wählen)'),
+ User::findCurrent()->id => _('Eigener Kalender')
+ ];
+ foreach ($other_users as $user) {
+ $select_options[$user->id] = $user->getFullName();
+ }
+ $calendar_select->setOptions($select_options, Request::get('user_id'));
+ $sidebar->addWidget($calendar_select);
}
}
- $stored = false;
- if (Request::submitted('store')) {
- $stored = $this->storeEventData($this->event, $this->calendar);
+ if (Config::get()->CALENDAR_GROUP_ENABLE && $selected_group) {
+ $views = new ViewsWidget();
+ $views->setTitle(_('Kalenderansicht'));
+ $views->addLink(
+ _('Gruppenkalender'),
+ $this->url_for('calendar/calendar', ['view' => 'group', 'group_id' => $group_id])
+ )->setActive($view === 'group');
+ $views->addLink(
+ _('Zeitleiste'),
+ $this->url_for('calendar/calendar', ['view' => 'timeline', 'group_id' => $group_id])
+ )->setActive($view === 'timeline');
+ $sidebar->addWidget($views);
}
- if ($stored !== false) {
- if ($stored === 0) {
- if (Request::isXhr()) {
- header('X-Dialog-Close: 1');
- exit;
- } else {
- PageLayout::postMessage(MessageBox::success(_('Der Termin wurde nicht geändert.')));
- $this->relocate('calendar/single/' . $this->last_view, ['atime' => $this->atime]);
+ $calendar_resources = [];
+ $calendar_group_title = '';
+ if ($group_view && $selected_group) {
+ //All users in the selected group that have granted read permissions to the user can be shown.
+ foreach ($selected_group->items as $item) {
+ if ($item->user && $item->user->isCalendarReadable()) {
+ $calendar_resources[] = [
+ 'id' => $item->user_id,
+ 'title' => $item->user ? $item->user->getFullName() : '',
+ 'parent_name' => ''
+ ];
}
- } else {
- PageLayout::postMessage(MessageBox::success(_('Der Termin wurde gespeichert.')));
- $this->relocate('calendar/single/' . $this->last_view, ['atime' => $this->atime]);
}
+ $calendar_group_title = $selected_group->name;
}
- $this->createSidebar('edit', $this->calendar);
- $this->createSidebarFilter();
+ $fullcalendar_studip_urls = [];
+ if ($write_permissions) {
+ if ($calendar_owner) {
+ $fullcalendar_studip_urls['add'] = $this->url_for('calendar/date/add', ['user_id' => $calendar_owner->id]);
+ } elseif ($selected_group) {
+ $fullcalendar_studip_urls['add'] = $this->url_for('calendar/date/add', ['group_id' => $group->id]);
+ }
+ }
+
+ $calendar_settings = User::findCurrent()->getConfiguration()->CALENDAR_SETTINGS ?? [];
+
+ //Map calendar settings to fullcalendar settings:
+
+ $default_view = 'timeGridWeek';
+ if ($timeline_view) {
+ $default_view = 'resourceTimelineWeek';
+ if ($calendar_settings['view'] === 'day') {
+ $default_view = 'resourceTimelineDay';
+ }
+ } elseif (!empty($calendar_settings['view'])) {
+ if ($calendar_settings['view'] === 'day') {
+ $default_view = 'timeGridDay';
+ } elseif ($calendar_settings['view'] === 'month') {
+ $default_view = 'dayGridMonth';
+ }
+ }
+
+ $slot_durations = $this->getUserCalendarSlotSettings();
+
+ //Create the fullcalendar object:
+ $default_date = \Studip\Calendar\Helper::getDefaultCalendarDate();
+
+ $data_url_params = [];
+ if (Request::bool('show_declined')) {
+ $data_url_params['show_declined'] = '1';
+ }
+ if ($timeline_view) {
+ $data_url_params['timeline_view'] = '1';
+ }
+
+ $this->fullcalendar = Studip\Fullcalendar::create(
+ _('Kalender'),
+ [
+ 'editable' => $write_permissions,
+ 'selectable' => $write_permissions,
+ 'studip_urls' => $fullcalendar_studip_urls,
+ 'dialog_size' => 'auto',
+ 'minTime' => sprintf('%02u:00', $calendar_settings['start'] ?? 8),
+ 'maxTime' => sprintf('%02u:00', $calendar_settings['end'] ?? 20),
+ 'defaultDate' => $default_date->format('Y-m-d'),
+ 'allDaySlot' => !$group_view,
+ 'allDayText' => '',
+ 'header' => [
+ 'left' => (
+ $timeline_view
+ ? 'resourceTimelineWeek,resourceTimelineDay'
+ : 'dayGridYear,dayGridMonth,timeGridWeek,timeGridDay'
+ ),
+ 'right' => 'prev,today,next'
+ ],
+ 'weekNumbers' => true,
+ 'views' => [
+ 'dayGridMonth' => [
+ 'eventTimeFormat' => ['hour' => 'numeric', 'minute' => '2-digit'],
+ 'displayEventEnd' => true
+ ],
+ 'timeGridWeek' => [
+ 'columnHeaderFormat' => ['weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true],
+ 'weekends' => $calendar_settings['type_week'] === 'LONG',
+ 'slotDuration' => $slot_durations['week']
+ ],
+ 'timeGridDay' => [
+ 'columnHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true],
+ 'slotDuration' => $slot_durations['day']
+ ],
+ 'resourceTimelineWeek' => [
+ 'columnHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true],
+ 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'],
+ 'weekends' => $calendar_settings['type_week'] === 'LONG',
+ 'slotDuration' => $slot_durations['week_group']
+ ],
+ 'resourceTimelineDay' => [
+ 'columnHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true],
+ 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'],
+ 'slotDuration' => $slot_durations['day_group']
+ ]
+ ],
+ 'defaultView' => $default_view,
+ 'timeGridEventMinHeight' => 20,
+ 'eventSources' => [
+ [
+ 'url' => $this->url_for(
+ (
+ $group_view
+ ? 'calendar/calendar/calendar_group_data/' . $selected_group->id
+ : 'calendar/calendar/calendar_data/' . $calendar_owner->id
+ ),
+ $data_url_params
+ ),
+ 'method' => 'GET',
+ 'extraParams' => []
+ ]
+ ],
+ 'resources' => $calendar_resources,
+ 'resourceLabelText' => $calendar_group_title
+ ]
+ );
}
- public function edit_status_action($range_id, $event_id)
+ public function course_action($course_id)
{
- global $user;
+ PageLayout::setTitle(_('Veranstaltungskalender'));
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
- $this->event = $this->calendar->getEvent($event_id);
- $stored = false;
- $old_status = $this->event->group_status;
+ if (!$course_id || !Config::get()->CALENDAR_GROUP_ENABLE || !Config::get()->COURSE_CALENDAR_ENABLE) {
+ throw new AccessDeniedException(_('Sie dürfen diesen Kalender nicht sehen!'));
+ }
- if (Request::submitted('store')) {
+ $course = Course::find($course_id);
+ if (!$course) {
+ throw new AccessDeniedException(_('Sie dürfen diesen Kalender nicht sehen!'));
+ }
- if ($this->event->isNew()
- || !Config::get()->CALENDAR_GROUP_ENABLE
- || !$this->calendar->havePermission(Calendar::PERMISSION_OWN)
- || !$this->calendar->getRange() == Calendar::RANGE_USER
- || !$this->event->havePermission(Event::PERMISSION_READABLE)) {
- throw new AccessDeniedException();
+ if (!$course->isVisibleForUser() || !$course->isCalendarReadable()) {
+ throw new AccessDeniedException(_('Sie dürfen diesen Kalender nicht sehen!'));
+ }
+
+ if (Navigation::hasItem('/course/calendar')) {
+ Navigation::activateItem('/course/calendar');
+ }
+
+ $sidebar = Sidebar::get();
+
+ $actions = new ActionsWidget();
+ $actions->addLink(
+ _('Termin anlegen'),
+ $this->url_for('calendar/date/add/course_' . $course->id),
+ Icon::create('add'),
+ ['data-dialog' => 'size=default']
+ );
+ $actions->addLink(
+ _('Drucken'),
+ 'javascript:void(window.print());',
+ Icon::create('print')
+ );
+ $actions->addLink(
+ _('Einstellungen'),
+ $this->url_for('settings/calendar'),
+ Icon::create('settings'),
+ ['data-dialog' => 'reload-on-close']
+ );
+ $sidebar->addWidget($actions);
+
+ $date = new DateSelectWidget();
+ $date->setCalendarControl(true);
+ $sidebar->addWidget($date);
+
+ //Create the fullcalendar object:
+
+ $calendar_writable = $course->isCalendarWritable();
+ $calendar_settings = User::findCurrent()->getConfiguration()->CALENDAR_SETTINGS ?? [];
+ $slot_settings = $this->getUserCalendarSlotSettings();
+
+ $fullcalendar_studip_urls = [];
+ if ($calendar_writable) {
+ $fullcalendar_studip_urls['add'] = $this->url_for('calendar/date/add/course_' . $course->id);
+ }
+
+ $this->fullcalendar = Studip\Fullcalendar::create(
+ _('Veranstaltungskalender'),
+ [
+ 'editable' => $calendar_writable,
+ 'selectable' => $calendar_writable,
+ 'studip_urls' => $fullcalendar_studip_urls,
+ 'minTime' => sprintf('%02u:00', $calendar_settings['start'] ?? 8),
+ 'maxTime' => sprintf('%02u:00', $calendar_settings['end'] ?? 20),
+ 'allDaySlot' => true,
+ 'allDayText' => '',
+ 'header' => [
+ 'left' => 'dayGridYear,dayGridMonth,timeGridWeek,timeGridDay',
+ 'right' => 'prev,today,next'
+ ],
+ 'weekNumbers' => true,
+ 'views' => [
+ 'dayGridMonth' => [
+ 'eventTimeFormat' => ['hour' => 'numeric', 'minute' => '2-digit'],
+ 'displayEventEnd' => true
+ ],
+ 'timeGridWeek' => [
+ 'columnHeaderFormat' => [ 'weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ],
+ 'weekends' => $calendar_settings['type_week'] === 'LONG',
+ 'slotDuration' => $slot_settings['week']
+ ],
+ 'timeGridDay' => [
+ 'columnHeaderFormat' => [ 'weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ],
+ 'slotDuration' => $slot_settings['day']
+ ]
+ ],
+ 'defaultView' => 'timeGridWeek',
+ 'timeGridEventMinHeight' => 20,
+ 'eventSources' => [
+ [
+ 'url' => $this->url_for('calendar/calendar/calendar_data/course_' . $course->id),
+ 'method' => 'GET',
+ 'extraParams' => []
+ ]
+ ]
+ ]
+ );
+ }
+
+ public function calendar_data_action($range_and_id)
+ {
+ $range_and_id = explode('_', $range_and_id);
+ $range = '';
+ $range_id = '';
+ if (!empty($range_and_id[1])) {
+ $range = $range_and_id[0];
+ $range_id = $range_and_id[1];
+ }
+ if (!$range) {
+ //Show the personal calendar of the current user:
+ $range = 'user';
+ $range_id = User::findCurrent()->id;
+ }
+ $owner = null;
+ if (!$range_id) {
+ //Assume a user calendar. $range contains the user-ID.
+ $owner = User::getCalendarOwner($range);
+ } elseif ($range === 'user') {
+ $owner = User::getCalendarOwner($range_id);
+ } elseif ($range === 'course') {
+ $owner = Course::getCalendarOwner($range_id);
+ }
+
+ if (!$owner || !$owner->isCalendarReadable()) {
+ throw new AccessDeniedException(_('Sie dürfen diesen Kalender nicht sehen!'));
+ }
+
+ $begin = Request::getDateTime('start', \DateTime::RFC3339);
+ $end = Request::getDateTime('end', \DateTime::RFC3339);
+ if (!($begin instanceof \DateTime) || !($end instanceof \DateTime)) {
+ //No time range specified.
+ throw new InvalidArgumentException('Invalid parameters!');
+ }
+
+ $calendar_events = CalendarDateAssignment::getEvents(
+ $begin,
+ $end,
+ $owner->id,
+ ['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'],
+ Request::bool('show_declined', false)
+ );
+
+ $result = [];
+
+ foreach ($calendar_events as $date) {
+ $event = $date->toEventData(User::findCurrent()->id);
+ $result[] = $event->toFullcalendarEvent();
+ }
+
+ if ($range === 'user') {
+ //Include course dates of courses that shall be displayed in the calendar:
+ $course_dates = CalendarCourseDate::getEvents($begin, $end, $owner->id);
+ foreach ($course_dates as $course_date) {
+ $event = $course_date->toEventData(User::findCurrent()->id);
+ $event->background_colour = '#ffffff';
+ $event->text_colour = '#000000';
+ $event->border_colour = '#000000';
+ $event->event_classes = [];
+ $result[] = $event->toFullcalendarEvent();
+ }
+ //Include relevant cancelled course dates:
+ $cancelled_course_dates = CalendarCourseExDate::getEvents($begin, $end, $owner->id);
+ foreach ($cancelled_course_dates as $cancelled_course_date) {
+ $event = $cancelled_course_date->toEventData(User::findCurrent()->id);
+ $event->background_colour = '#ffffff';
+ $event->text_colour = '#000000';
+ $event->border_colour = '#000000';
+ $event->event_classes = [];
+ $result[] = $event->toFullcalendarEvent();
}
+ }
+ //At this point, everything went fine. We can save the beginning as default date
+ //if the current user is looking at their own calendar:
+ if ($owner instanceof User && $owner->id === User::findCurrent()->id) {
+ $_SESSION['calendar_date'] = $begin->format('Y-m-d');
+ }
+ $this->render_json($result);
+ }
- $status = Request::int('status', 1);
- if ($status > 0 && $status < 6) {
- $this->event->group_status = $status;
- $stored = $this->event->store();
+ public function calendar_group_data_action($group_id)
+ {
+ $begin = Request::getDateTime('start', \DateTime::RFC3339);
+ $end = Request::getDateTime('end', \DateTime::RFC3339);
+ $timeline_view = Request::bool('timeline_view', false);
+
+ if (!($begin instanceof \DateTime) || !($end instanceof \DateTime)) {
+ //No time range specified.
+ throw new InvalidArgumentException('Invalid parameters!');
+ }
+
+ $group = null;
+ $users = [];
+ if ($group_id) {
+ //Get the group first:
+ $group = ContactGroup::find($group_id);
+ if ($group->owner_id !== User::findCurrent()->id) {
+ throw new AccessDeniedException();
+ }
+ foreach ($group->items as $item) {
+ if ($item->user->isCalendarReadable()) {
+ $users[] = $item->user;
+ }
+ }
+ if (!$users) {
+ //No user has granted read access to the calendar for the current user.
+ throw new AccessDeniedException(_('Sie dürfen diesen Kalender nicht sehen!'));
}
+ }
- if ($stored !== false) {
- if ($stored === 0) {
- if (Request::isXhr()) {
- header('X-Dialog-Close: 1');
- exit;
- } else {
- PageLayout::postMessage(MessageBox::success(_('Der Teilnahmestatus wurde nicht geändert.')));
- $this->relocate('calendar/single/' . $this->last_view, ['atime' => $this->atime]);
- }
- } else {
- // send message to organizer...
- if ($this->event->author_id != $user->id) {
- setTempLanguage($this->event->author_id);
- $message = new messaging();
- $msg_text = sprintf(_('%s hat den Terminvorschlag für "%s" am %s von %s auf %s geändert.'),
- get_fullname(), $this->event->getTitle(),
- strftime('%c', $this->event->getStart()),
- $this->event->toStringGroupStatus($old_status), $this->event->toStringGroupStatus());
- if ($status == CalendarEvent::PARTSTAT_DELEGATED) {
- $msg_text .= "\n"
- . sprintf(_('Der Termin wird akzeptiert, aber %s nimmt nicht selbst am Termin teil.'),
- get_fullname());
- }
- $subject = sprintf(_('Terminvorschlag am %s von %s %s'),
- strftime('%c', $this->event->getStart()), get_fullname(), $this->event->toStringGroupStatus());
- $msg_text .= "\n\n**" . _('Beginn') . ':** ';
- if ($this->event->isDayEvent()) {
- $msg_text .= strftime('%x ', $this->event->getStart());
- $msg_text .= _('ganztägig');
- } else {
- $msg_text .= strftime('%c', $this->event->getStart());
- }
- $msg_text .= "\n**" . _('Ende') . ':** ';
- if ($this->event->isDayEvent()) {
- $msg_text .= strftime('%x ', $this->event->getEnd());
- } else {
- $msg_text .= strftime('%c', $this->event->getEnd());
- }
- $msg_text .= "\n**" . _('Zusammenfassung') . ':** ' . $this->event->getTitle() . "\n";
- if ($event_data = $this->event->getDescription()) {
- $msg_text .= '**' . _('Beschreibung') . ":** $event_data\n";
- }
- if ($event_data = $this->event->toStringCategories()) {
- $msg_text .= '**' . _('Kategorie') . ":** $event_data\n";
- }
- if ($event_data = $this->event->toStringPriority()) {
- $msg_text .= '**' . _('Priorität') . ":** $event_data\n";
- }
- if ($event_data = $this->event->toStringAccessibility()) {
- $msg_text .= '**' . _('Zugriff') . ":** $event_data\n";
- }
- if ($event_data = $this->event->toStringRecurrence()) {
- $msg_text .= '**' . _('Wiederholung') . ":** $event_data\n";
- }
- $member = [];
- foreach ($this->event->attendees as $attendee) {
- if ($attendee->range_id == $this->event->getAuthorId()) {
- $member[] = $attendee->user->getFullName()
- . ' ('. _('Organisator') . ')';
- } else {
- $member[] = $attendee->user->getFullName()
- . ' (' . $this->event->toStringGroupStatus($attendee->group_status)
- . ')';
- }
- }
- $msg_text .= '**' . _('Teilnehmende') . ':** ' . implode(', ', $member);
- $msg_text .= "\n\n" . _('Hier kommen Sie direkt zum Termin in Ihrem Kalender:') . "\n";
- $msg_text .= URLHelper::getURL('dispatch.php/calendar/single/edit/'
- . $this->event->getAuthorId() . '/' . $this->event->event_id);
- $message->insert_message(
- addslashes($msg_text),
- [get_username($this->event->getAuthorId())],
- $this->event->range_id,
- '', '', '', '', addslashes($subject));
- restoreLanguage();
+ $result = [];
+
+ foreach ($users as $user) {
+ $events = CalendarDateAssignment::getEvents($begin, $end, $user->id);
+ if ($events) {
+ foreach ($events as $event) {
+ $data = $event->toEventData(User::findCurrent()->id);
+ if (!$timeline_view) {
+ $data->title = $user->getFullName();
}
- PageLayout::postMessage(MessageBox::success(_('Der Teilnahmestatus wurde gespeichert.')));
- $this->relocate('calendar/single/' . $this->last_view, ['atime' => $this->atime]);
+ $result[] = $data->toFullcalendarEvent();
}
}
}
-
- $this->createSidebar('edit', $this->calendar);
- $this->createSidebarFilter();
+ $this->render_json($result);
}
- public function switch_action()
+ public function add_courses_action()
{
- $default_view = $this->settings['view'] ?: 'week';
- $view = Request::option('last_view', $default_view);
- $this->range_id = Request::option('range_id', $GLOBALS['user']->id);
- $object_type = get_object_type($this->range_id);
- switch ($object_type) {
- case 'user':
- URLHelper::addLinkParam('cid', '');
- $this->redirect($this->url_for('calendar/single/'
- . $view . '/' . $this->range_id));
- break;
- case 'sem':
- case 'inst':
- case 'fak':
- URLHelper::addLinkParam('cid', $this->range_id);
- $this->redirect($this->url_for('calendar/single/'
- . $view . '/' . $this->range_id));
- break;
- case 'group':
- URLHelper::addLinkParam('cid', '');
- $this->redirect($this->url_for('calendar/group/'
- . $view . '/' . $this->range_id));
- break;
+ $selected_semester_pseudo_id = Request::option('semester_id');
+ $this->selected_semesters_id = '';
+ $this->available_semester_data = [];
+ $semesters = Semester::getAll();
+ foreach ($semesters as $semester) {
+ $this->available_semester_data[$semester['id']] = [
+ 'id' => $semester['id'],
+ 'name' => $semester['name']
+ ];
}
- }
+ $this->available_semester_data = array_reverse($this->available_semester_data);
- public function jump_to_action()
- {
- $date = Request::get('jmp_date');
- if ($date) {
- $atime = strtotime($date . strftime(' %T', $this->atime));
+ if (!$selected_semester_pseudo_id) {
+ $selected_semester_pseudo_id = User::findCurrent()->getConfiguration()->MY_COURSES_SELECTED_CYCLE;
+ if (!Config::get()->MY_COURSES_ENABLE_ALL_SEMESTERS && $selected_semester_pseudo_id === 'all') {
+ $selected_semester_pseudo_id = 'next';
+ }
+ if (!$selected_semester_pseudo_id) {
+ $selected_semester_pseudo_id = Config::get()->MY_COURSES_DEFAULT_CYCLE;
+ }
+ }
+ if ($selected_semester_pseudo_id === 'next') {
+ $semester = Semester::findNext();
+ $this->selected_semester_id = $semester->id;
+ } elseif (in_array($selected_semester_pseudo_id, ['all', 'current'])) {
+ $semester = Semester::findCurrent();
+ $this->selected_semester_id = $semester->id;
+ } elseif ($selected_semester_pseudo_id === 'last') {
+ $semester = Semester::findPrevious();
+ $this->selected_semester_id = $semester->id;
} else {
- $atime = 'now';
+ $this->selected_semester_id = $selected_semester_pseudo_id ?? '';
+ if (!Semester::exists($this->selected_semesters_id)) {
+ $this->selected_semester_id = '';
+ }
+ }
+
+ $this->selected_course_ids = SimpleCollection::createFromArray(
+ CourseMember::findBySQL(
+ 'user_id = :user_id AND bind_calendar = 1',
+ ['user_id' => User::findCurrent()->id]
+ )
+ )->pluck('seminar_id');
+
+ $this->semester_data = [];
+ $all_semesters = Semester::getAll();
+ foreach ($all_semesters as $semester) {
+ $data = [
+ 'id' => $semester->id,
+ 'name' => $semester->name,
+ 'courses' => []
+ ];
+ $this->semester_data[] = $data;
+ }
+
+ if (Request::submitted('add')) {
+ CSRFProtection::verifyUnsafeRequest();
+
+ $course_ids = Request::getArray('courses_course_ids');
+ foreach ($course_ids as $course_id => $selected) {
+ $course_membership = CourseMember::findOneBySQL(
+ 'seminar_id = :course_id AND user_id = :user_id',
+ [
+ 'course_id' => $course_id,
+ 'user_id' => User::findCurrent()->id
+ ]
+ );
+ if ($course_membership) {
+ $course_membership->bind_calendar = $selected ? '1' : '0';
+ $course_membership->store();
+ }
+ }
+ PageLayout::postSuccess(_('Die Zuordnung von Veranstaltungen zum Kalender wurde aktualisiert.'));
+ $this->redirect('calendar/calendar');
}
- $action = Request::option('action', 'week');
- $this->range_id = $this->range_id ?: $GLOBALS['user']->id;
- $this->redirect($this->url_for($this->base . $action,
- ['atime' => $atime, 'range_id' => $this->range_id]));
}
- public function show_declined_action ()
+ public function export_action()
{
- $config = UserConfig::get($GLOBALS['user']->id);
- $this->settings['show_declined'] = Request::int('show_declined') ? '1' : '0';
- // var_dump($this->settings); exit;
- $config->store('CALENDAR_SETTINGS', $this->settings);
- $action = Request::option('action', 'week');
- $this->range_id = $this->range_id ?: $GLOBALS['user']->id;
- $this->redirect($this->url_for($this->base . $action,
- ['range_id' => $this->range_id]));
+ PageLayout::setTitle(_('Termine exportieren'));
+ $this->begin = new DateTimeImmutable();
+ $this->end = $this->begin->add(new DateInterval('P1Y'));
+ $this->dates_to_export = 'user';
+ if (Request::submitted('export')) {
+ CSRFProtection::verifyUnsafeRequest();
+ $this->begin = Request::getDateTime('begin', 'd.m.Y');
+ $this->end = Request::getDateTime('end', 'd.m.Y');
+ if ($this->begin >= $this->end) {
+ PageLayout::postError(_('Der Startzeitpunkt darf nicht nach dem Endzeitpunkt liegen!'));
+ return;
+ }
+ $this->dates_to_export = Request::get('dates_to_export');
+ if (!in_array($this->dates_to_export, ['user', 'course', 'all'])) {
+ PageLayout::postError(_('Bitte wählen Sie aus, welche Termine exportiert werden sollen!'));
+ return;
+ }
+ $ical = '';
+ $calendar_export = new ICalendarExport();
+ if ($this->dates_to_export === 'user') {
+ $ical = $calendar_export->exportCalendarDates(User::findCurrent()->id, $this->begin, $this->end);
+ } elseif ($this->dates_to_export === 'course') {
+ $ical = $calendar_export->exportCourseDates(User::findCurrent()->id, $this->begin, $this->end);
+ $ical .= $calendar_export->exportCourseExDates(User::findCurrent()->id, $this->begin, $this->end);
+ } elseif ($this->dates_to_export === 'all') {
+ $ical = $calendar_export->exportCalendarDates(User::findCurrent()->id, $this->begin, $this->end);
+ $ical .= $calendar_export->exportCourseDates(User::findCurrent()->id, $this->begin, $this->end);
+ $ical .= $calendar_export->exportCourseExDates(User::findCurrent()->id, $this->begin, $this->end);
+ }
+ $ical = $calendar_export->writeHeader() . $ical . $calendar_export->writeFooter();
+ $this->response->add_header('Content-Type', 'text/calendar;charset=utf-8');
+ $this->response->add_header('Content-Disposition', 'attachment; filename="studip.ics"');
+ $this->response->add_header('Content-Transfer-Encoding', 'binary');
+ $this->response->add_header('Pragma', 'public');
+ $this->response->add_header('Cache-Control', 'private');
+ $this->response->add_header('Content-Length', strlen($ical));
+ $this->render_text($ical);
+ }
}
- protected function storeEventData(CalendarEvent $event, SingleCalendar $calendar)
+ public function import_action() {}
+
+ public function import_file_action()
{
- $messages = [];
- if (Request::int('isdayevent')) {
- $dt_string = Request::get('start_date') . ' 00:00:00';
- } else {
- $dt_string = sprintf(
- '%s %u:%02u',
- Request::get('start_date'),
- Request::int('start_hour'),
- Request::int('start_minute')
- );
- }
- $event->setStart($this->parseDateTime($dt_string));
- if (Request::int('isdayevent')) {
- $dt_string = Request::get('end_date') . ' 23:59:59';
- } else {
- $dt_string = sprintf(
- '%s %u:%02u',
- Request::get('end_date'),
- Request::int('end_hour'),
- Request::int('end_minute')
- );
+ if (Request::submitted('import')) {
+ CSRFProtection::verifySecurityToken();
+ $range_id = Context::getId() ?? User::findCurrent()->id;
+ $calendar_import = new ICalendarImport($range_id);
+ $calendar_import->convertPublicToPrivate(Request::bool('import_as_private_imp'));
+ $calendar_import->import(file_get_contents($_FILES['importfile']['tmp_name']));
+ $import_count = $calendar_import->getCountEvents();
+ PageLayout::postSuccess(sprintf(
+ ngettext(
+ 'Ein Termin wurde importiert.',
+ 'Es wurden %u Termine importiert.',
+ $import_count
+ ),
+ $import_count
+ ));
+ $this->redirect($this->url_for('calendar/calendar/'));
}
- $event->setEnd($this->parseDateTime($dt_string));
+ }
- if (!$this->validate_datetime(sprintf('%02u:%02u',Request::int('start_hour'),Request::int('start_minute')))
- || !$this->validate_datetime(sprintf('%02u:%02u',Request::int('end_hour'),Request::int('end_minute')))
- ) {
- $messages[] = _('Die Start- und/oder Endzeit ist ungültig!');
+ public function share_action()
+ {
+ PageLayout::setTitle(_('Kalender teilen'));
+ if (!Config::get()->CALENDAR_GROUP_ENABLE) {
+ throw new FeatureDisabledException();
}
- if ($event->getStart() > $event->getEnd()) {
- $messages[] = _('Die Startzeit muss vor der Endzeit liegen.');
+ $calendar_contacts = Contact::findBySql(
+ "JOIN `auth_user_md5` USING (`user_id`)
+ WHERE `contact`.`owner_id` = :user_id
+ AND `contact`.`calendar_permissions` <> ''
+ ORDER BY `auth_user_md5`.`Vorname`, `auth_user_md5`.`Nachname`",
+ [
+ 'user_id' => User::findCurrent()->id
+ ]
+ );
+ $user_data = [];
+ foreach ($calendar_contacts as $contact) {
+ $user_data[$contact->user_id] = [
+ 'id' => $contact->user_id,
+ 'name' => $contact->friend->getFullName(),
+ 'write_permissions' => $contact->calendar_permissions === 'WRITE'
+ ];
}
+ $this->selected_users_json = json_encode($user_data, JSON_FORCE_OBJECT);
+ $this->searchtype = new StandardSearch('user_id', ['simple_name' => true]);
- $event->setTitle(Request::get('summary', ''));
- $event->event->description = Request::get('description', '');
- $event->setUserDefinedCategories(Request::get('categories', ''));
- $event->event->location = Request::get('location', '');
- $event->event->category_intern = Request::int('category_intern', 1);
- $event->setAccessibility(Request::option('accessibility', 'PRIVATE'));
- $event->setPriority(Request::int('priority', 0));
+ if (Request::submitted('share')) {
+ CSRFProtection::verifyUnsafeRequest();
+ $selected_user_ids = Request::getArray('calendar_permissions', []);
+ $write_permissions = Request::getArray('calendar_write_permissions', []);
- if (!$event->getTitle()) {
- $messages[] = _('Es muss eine Zusammenfassung angegeben werden.');
- }
+ //Add/update contacts with calendar permissions:
- $rec_type = Request::option('recurrence', 'single');
- $expire = Request::option('exp_c', 'never');
- $rrule = [
- 'linterval' => null,
- 'sinterval' => null,
- 'wdays' => null,
- 'month' => null,
- 'day' => null,
- 'rtype' => 'SINGLE',
- 'count' => null,
- 'expire' => null
- ];
- if ($expire == 'count') {
- $rrule['count'] = Request::int('exp_count', 10);
- } else if ($expire == 'date') {
- if (Request::isXhr()) {
- $exp_date = Request::get('exp_date');
- } else {
- $exp_date = Request::get('exp_date');
- }
- $exp_date = $exp_date ?: strftime('%x', time());
- $rrule['expire'] = $this->parseDateTime($exp_date . ' 12:00');
- }
- switch ($rec_type) {
- case 'daily':
- if (Request::option('type_daily', 'day') === 'day') {
- $rrule['linterval'] = Request::int('linterval_d', 1);
- $rrule['rtype'] = 'DAILY';
- } else {
- $rrule['linterval'] = 1;
- $rrule['wdays'] = '12345';
- $rrule['rtype'] = 'WEEKLY';
+ foreach ($selected_user_ids as $user_id) {
+ $user = User::find($user_id);
+ if (!$user) {
+ //No user? No contact!
+ continue;
}
- break;
- case 'weekly':
- $rrule['rtype'] = 'WEEKLY';
- $rrule['linterval'] = Request::int('linterval_w', 1);
- $rrule['wdays'] = implode('', Request::intArray('wdays',
- [strftime('%u', $event->getStart())]));
- break;
- case 'monthly':
- $rrule['rtype'] = 'MONTHLY';
- if (Request::option('type_m', 'day') === 'day') {
- $rrule['linterval'] = Request::int('linterval_m1', 1);
- $rrule['day'] = Request::int('day_m',
- strftime('%e', $event->getStart()));
- } else {
- $rrule['linterval'] = Request::int('linterval_m2', 1);
- $rrule['sinterval'] = Request::int('sinterval_m', 1);
- $rrule['wdays'] = Request::int('wday_m',
- strftime('%u', $event->getStart()));
+ $contact = Contact::findOneBySql(
+ 'owner_id = :owner_id AND user_id = :user_id',
+ [
+ 'owner_id' => User::findCurrent()->id,
+ 'user_id' => $user_id
+ ]
+ );
+ if (!$contact) {
+ $contact = new Contact();
+ $contact->owner_id = User::findCurrent()->id;
+ $contact->user_id = $user->id;
}
- break;
- case 'yearly':
- $rrule['rtype'] = 'YEARLY';
- $rrule['linterval'] = 1;
- if (Request::option('type_y', 'day') === 'day') {
- $rrule['day'] = Request::int('day_y',
- strftime('%e', $event->getStart()));
- $rrule['month'] = Request::int('month_y1',
- date('n', $event->getStart()));
+ if (in_array($user->id, $write_permissions)) {
+ $contact->calendar_permissions = 'WRITE';
} else {
- $rrule['sinterval'] = Request::int('sinterval_y', 1);
- $rrule['wdays'] = Request::int('wday_y',
- strftime('%u', $event->getStart()));
- $rrule['month'] = Request::int('month_y2',
- date('n', $event->getStart()));
+ $contact->calendar_permissions = 'READ';
}
- break;
- }
- if (sizeof($messages)) {
- PageLayout::postMessage(MessageBox::error(_('Bitte Eingaben korrigieren'), $messages));
- return false;
- } else {
- $event->setRecurrence($rrule);
- $exceptions = array_diff(Request::getArray('exc_dates'),
- Request::getArray('del_exc_dates'));
- $event->setExceptions($this->parseExceptions($exceptions));
- // if this is a group event, store event in the calendars of each attendee
- if (Config::get()->CALENDAR_GROUP_ENABLE) {
- $attendee_ids = Request::optionArray('attendees');
- return $calendar->storeEvent($event, $attendee_ids);
- } else {
- return $calendar->storeEvent($event);
+ $contact->store();
}
- }
- }
- /**
- * Parses a string with exception dates from input form and returns an array
- * with all dates as unix timestamp identified by an internally used pattern.
- *
- * @param string $exc_dates
- * @return array An array of unix timestamps.
- */
- protected function parseExceptions($exc_dates) {
- $matches = [];
- $dates = [];
- preg_match_all('%(\d{1,2})\h*([/.])\h*(\d{1,2})\h*([/.])\h*(\d{4})\s*%',
- implode(' ', $exc_dates), $matches, PREG_SET_ORDER);
- foreach ($matches as $match) {
- if ($match[2] == '/') {
- $dates[] = strtotime($match[1].'/'.$match[3].'/'.$match[5]);
+ //Revoke calendar permissions for all users that aren't in the list
+ //of selected users:
+ if ($selected_user_ids) {
+ $stmt = DBManager::get()->prepare(
+ "UPDATE `contact` SET `calendar_permissions` = ''
+ WHERE `owner_id` = :owner_id
+ AND `user_id` NOT IN ( :user_ids )"
+ );
+ $stmt->execute([
+ 'owner_id' => User::findCurrent()->id,
+ 'user_ids' => $selected_user_ids
+ ]);
} else {
- $dates[] = strtotime($match[1].$match[2].$match[3].$match[4].$match[5]);
+ $stmt = DBManager::get()->prepare(
+ "UPDATE `contact` SET `calendar_permissions` = ''
+ WHERE `owner_id` = :owner_id"
+ );
+ $stmt->execute(['owner_id' => User::findCurrent()->id]);
}
+
+ PageLayout::postSuccess(
+ _('Die Kalenderfreigaben wurden geändert.')
+ );
+ $this->response->add_header('X-Dialog-Close', '1');
}
- return $dates;
}
- /**
- * Parses a string as date time in the format "j.n.Y H:i:s" and returns the
- * corresponding unix time stamp.
- *
- * @param string $dt_string The date time string.
- * @return int A unix time stamp
- */
- protected function parseDateTime($dt_string)
+ public function publish_action()
{
- $dt_array = date_parse_from_format('j.n.Y H:i:s', $dt_string);
- return mktime($dt_array['hour'], $dt_array['minute'], $dt_array['second'],
- $dt_array['month'], $dt_array['day'], $dt_array['year']);
- }
+ $this->short_id = null;
+ if (Request::submitted('delete_id')) {
+ CSRFProtection::verifySecurityToken();
+ IcalExport::deleteKey(User::findCurrent()->id);
+ PageLayout::postSuccess(_('Die Adresse, unter der Ihre Termine abrufbar sind, wurde gelöscht'));
+ }
+
+ if (Request::submitted('new_id')) {
+ CSRFProtection::verifySecurityToken();
+ $this->short_id = IcalExport::setKey(User::findCurrent()->id);
+ PageLayout::postSuccess(_('Eine Adresse, unter der Ihre Termine abrufbar sind, wurde erstellt.'));
+ } else {
+ $this->short_id = IcalExport::getKeyByUser(User::findCurrent()->id);
+ }
+ $text = '';
+ if (Request::submitted('submit_email')) {
+ $email_reg_exp = '/^([-.0-9=?A-Z_a-z{|}~])+@([-.0-9=?A-Z_a-z{|}~])+\.[a-zA-Z]{2,6}$/i';
+ if (preg_match($email_reg_exp, Request::get('email')) !== 0) {
+ $subject = '[' .Config::get()->UNI_NAME_CLEAN . ']' . _('Exportadresse für Ihre Termine');
+ $text .= _('Diese Email wurde vom Stud.IP-System verschickt. Sie können auf diese Nachricht nicht antworten.') . "\n\n";
+ $text .= _('Über diese Adresse erreichen Sie den Export für Ihre Termine:') . "\n\n";
+ $text .= $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/ical/index/'
+ . IcalExport::getKeyByUser(User::findCurrent()->id);
+ StudipMail::sendMessage(Request::get('email'), $subject, $text);
+ PageLayout::postSuccess(_('Die Adresse wurde verschickt!'));
+ } else {
+ PageLayout::postError(_('Bitte geben Sie eine gültige Email-Adresse an.'));
+ }
+ $this->short_id = IcalExport::getKeyByUser(User::findCurrent()->id);
+ }
+ PageLayout::setTitle(_('Kalender veröffentlichen'));
+ }
}
diff --git a/app/controllers/calendar/contentbox.php b/app/controllers/calendar/contentbox.php
index 129fb59..5d77ed4 100644
--- a/app/controllers/calendar/contentbox.php
+++ b/app/controllers/calendar/contentbox.php
@@ -24,6 +24,7 @@ class Calendar_ContentboxController extends StudipController
$this->admin = false;
$this->single = false;
$this->userRange = false;
+ $this->course_range = false;
$this->termine = [];
// Fetch time if needed
@@ -36,6 +37,8 @@ class Calendar_ContentboxController extends StudipController
$range_id = [$range_id];
}
+ $this->titles = [];
+
foreach ($range_id as $id) {
switch (get_object_type($id, ['user', 'sem'])) {
case 'user':
@@ -44,6 +47,7 @@ class Calendar_ContentboxController extends StudipController
break;
case 'sem':
$this->parseSeminar($id);
+ $this->course_range = true;
break;
}
}
@@ -79,93 +83,91 @@ class Calendar_ContentboxController extends StudipController
private function parseSeminar($id)
{
$course = Course::find($id);
- $dates = $course->getDatesWithExdates()->findBy('end_time', [$this->start, $this->start + $this->timespan], '><');
- $this->termine = [];
- foreach ($dates as $courseDate) {
- // Build info
- $info = [];
- if (count($courseDate->dozenten) > 0) {
- $info[_('Durchführende Lehrende')] = implode(', ', $courseDate->dozenten->getFullname());
- }
- if (count($courseDate->statusgruppen) > 0) {
- $info[_('Beteiligte Gruppen')] = implode(', ', $courseDate->statusgruppen->getValue('name'));
- }
-
- // Store for view
- $description = '';
- if ($courseDate instanceof CourseExDate) {
- $description = $courseDate->content;
- } elseif ($courseDate->cycle instanceof SeminarCycleDate) {
- $description = $courseDate->cycle->description;
+ $this->termine = $course->getDatesWithExdates()->findBy('end_time', [$this->start, $this->start + $this->timespan], '><');
+ foreach ($this->termine as $course_date) {
+ if ($this->course_range) {
+ //Display only date and time:
+ $this->titles[$course_date->id] = $course_date->getFullname('include-room');
+ } else {
+ //Include the course title:
+ $this->titles[$course_date->id] = $course_date->getFullname('verbose');
}
- $this->termine[] = [
- 'id' => $courseDate->id,
- 'chdate' => $courseDate->chdate,
- 'title' => $courseDate->getFullname() . (count($courseDate->topics) > 0 ? ', ' . implode(', ', $courseDate->topics->getValue('title')) : ''),
- 'description' => $description,
- 'topics' => $courseDate->topics->toArray('title description'),
- 'room' => $courseDate->getRoomName(),
- 'info' => $info
- ];
}
}
private function parseUser($id)
{
- $restrictions = $GLOBALS['user']->id === $id ? [] : ['CLASS' => 'PUBLIC'];
- $events = SingleCalendar::getEventList(
- $id,
- $this->start,
- $this->start + $this->timespan,
- null,
- $restrictions
- );
+ $begin = new DateTime();
+ $begin->setTimestamp($this->start);
+ $end = new DateTime();
+ $end->setTimestamp($this->start + $this->timespan);
+
$this->termine = [];
- // Prepare termine
- foreach ($events as $termin) {
- // Exclude events that begin after the given time range
- if ($termin->getStart() > $this->start + $this->timespan) {
+
+ if ($GLOBALS['user']->id === $id) {
+ //The current user is looking at their dates.
+ //Get course dates, too:
+ $relevant_courses = Course::findBySQL(
+ "JOIN `seminar_user` USING (`seminar_id`)
+ WHERE `user_id` = :user_id",
+ ['user_id' => $id]
+ );
+ foreach ($relevant_courses as $course) {
+ $course_dates = $course->getDatesWithExdates()->findBy('end_time', [$this->start, $this->start + $this->timespan], '><');
+ foreach ($course_dates as $course_date) {
+ $this->titles[$course_date->id] = sprintf(
+ '%1$s: %2$s',
+ $course_date->course->name,
+ $course_date->getFullname()
+ );
+ $this->termine[] = $course_date;
+ }
+ }
+ }
+
+ //Get personal dates:
+
+ $assignments = [];
+ if (User::findCurrent()->id === $id) {
+ $assignments = CalendarDateAssignment::getEvents($begin, $end, $id);
+ } else {
+ //Only show public events:
+ $assignments = CalendarDateAssignment::getEvents($begin, $end, $id, ['PUBLIC']);
+ }
+ foreach ($assignments as $assignment) {
+ //Exclude events that begin after the given time range:
+ if ($assignment->calendar_date->begin > $this->start + $this->timespan) {
continue;
}
+ $title = '';
+
// Adjust title
- if (date('Ymd', $termin->getStart()) == date('Ymd')) {
- $title = _('Heute') . date(', H:i', $termin->getStart());
+ if (date('Ymd', $assignment->calendar_date->begin) == date('Ymd')) {
+ $title = _('Heute') . date(', H:i', $assignment->calendar_date->begin);
} else {
- $title = mb_substr(strftime('%a', $termin->getStart()), 0, 2);
- $title .= date('. d.m.Y, H:i', $termin->getStart());
+ $title = mb_substr(strftime('%a', $assignment->calendar_date->begin), 0, 2);
+ $title .= date('. d.m.Y, H:i', $assignment->calendar_date->begin);
}
- if ($termin->getStart() < $termin->getEnd()) {
- if (date('Ymd', $termin->getStart()) < date('Ymd', $termin->getEnd())) {
- $title .= ' - ' . mb_substr(strftime('%a', $termin->getEnd()), 0, 2);
- $title .= date('. d.m.Y, H:i', $termin->getEnd());
+ if ($assignment->calendar_date->begin < $assignment->calendar_date->end) {
+ if (date('Ymd', $assignment->calendar_date->begin) < date('Ymd', $assignment->calendar_date->end)) {
+ $title .= ' - ' . mb_substr(strftime('%a', $assignment->calendar_date->end), 0, 2);
+ $title .= date('. d.m.Y, H:i', $assignment->calendar_date->end);
} else {
- $title .= ' - ' . date('H:i', $termin->getEnd());
+ $title .= ' - ' . date('H:i', $assignment->calendar_date->end);
}
}
- if ($termin->getTitle()) {
- $tmp_titel = mila($termin->getTitle()); //Beschneiden des Titels
- $title .= ', ' . $tmp_titel;
+ if ($assignment->calendar_date->title) {
+ //Cut the title:
+ $tmp_title = mila($assignment->calendar_date->title);
+ $title .= ', ' . $tmp_title;
}
+ $this->titles[$assignment->getObjectId()] = $title;
// Store for view
- $this->termine[] = [
- 'id' => $termin->id,
- 'type' => get_class($termin),
- 'range_id' => $termin->range_id,
- 'event_id' => $termin->event_id,
- 'chdate' => $termin->chdate,
- 'title' => $title,
- 'description' => $termin->getDescription(),
- 'room' => $termin->getLocation(),
- 'info' => [
- _('Kategorie') => $termin->toStringCategories(),
- _('Priorität') => $termin->toStringPriority(),
- _('Sichtbarkeit') => $termin->toStringAccessibility(),
- _('Wiederholung') => $termin->toStringRecurrence()]
- ];
+ $this->termine[] = $assignment;
}
}
}
diff --git a/app/controllers/calendar/date.php b/app/controllers/calendar/date.php
new file mode 100644
index 0000000..32cf99c
--- /dev/null
+++ b/app/controllers/calendar/date.php
@@ -0,0 +1,828 @@
+<?php
+
+class Calendar_DateController extends AuthenticatedController
+{
+ protected function getCategoryOptions()
+ {
+ if (empty($GLOBALS['PERS_TERMIN_KAT'])) {
+ return [];
+ }
+ $options = [];
+ foreach ($GLOBALS['PERS_TERMIN_KAT'] as $key => $data) {
+ $options[$key] = $data['name'];
+ }
+ if (!array_key_exists(255, $options)) {
+ $options[255] = _('Sonstige');
+ }
+ return $options;
+ }
+
+ protected function getCalendarOwner($range_and_id)
+ {
+ $range = '';
+ $range_id = '';
+ $range_and_id = explode('_', $range_and_id ?? []);
+ if (!empty($range_and_id[1])) {
+ $range = $range_and_id[0];
+ $range_id = $range_and_id[1];
+ }
+ if (!$range) {
+ //Show the personal calendar of the current user:
+ $range = 'user';
+ $range_id = $GLOBALS['user']->id;
+ }
+
+ $owner = null;
+ if (!$range_id) {
+ //Assume a user calendar. $range contains the user-ID.
+ $owner = User::getCalendarOwner($range);
+ } else {
+ if ($range === 'user') {
+ $owner = User::getCalendarOwner($range_id);
+ } elseif ($range === 'course') {
+ $owner = Course::getCalendarOwner($range_id);
+ }
+ }
+
+ if (!$owner || !$owner->isCalendarReadable()) {
+ throw new AccessDeniedException(_('Sie dürfen diesen Kalender nicht sehen!'));
+ }
+ return $owner;
+ }
+
+ /**
+ * A helper method to determine whether the current user may write the date.
+ *
+ * @return Studip\Calendar\Owner[] The owners in which the current user may add a date.
+ */
+ protected function getCalendarOwnersWithWriteAccess(?CalendarDate $date, ?Studip\Calendar\Owner $owner) : array
+ {
+ $result = [];
+ if ($owner instanceof Course) {
+ //For course calendars, only the course can be the owner.
+ $result[$owner->id] = $owner;
+ return $result;
+ }
+ if ($date) {
+ foreach ($date->calendars as $calendar) {
+ if ($calendar->user) {
+ $result[$calendar->user->id] = $calendar->user;
+ } elseif ($calendar->course) {
+ $result[$calendar->course->id] = $calendar->course;
+ }
+ }
+ } else {
+ if ($group_id = Request::get('group_id')) {
+ $group = ContactGroup::find($group_id);
+ if ($group) {
+ foreach ($group->items as $item) {
+ if ($item->user && $item->user->isCalendarWritable()) {
+ $result[$item->user_id] = $item->user;
+ }
+ }
+ }
+ } elseif ($user_id = Request::get('user_id', $GLOBALS['user']->id)) {
+ $user = User::find($user_id);
+ if ($user && $user->isCalendarWritable()) {
+ $result[$user->id] = $user;
+ }
+ }
+ if ($other_calendar_ids = Request::getArray('other_calendar_ids')) {
+ foreach ($other_calendar_ids as $other_calendar_id) {
+ $user = User::find($other_calendar_id);
+ if ($user && $user->isCalendarWritable()) {
+ $result[$user->id] = $user;
+ }
+ }
+ }
+ }
+ return $result;
+ }
+
+ public function index_action($date_id)
+ {
+ $this->date = CalendarDate::find($date_id);
+ if (!$this->date) {
+ PageLayout::postError(_('Der angegebene Termin wurde nicht gefunden.'));
+ return;
+ }
+ if (!$this->date->isVisible($GLOBALS['user']->id)) {
+ throw new AccessDeniedException();
+ }
+ PageLayout::setTitle(
+ sprintf(
+ _('%1$s (am %2$s von %3$s bis %4$s Uhr)'),
+ $this->date->title,
+ date('d.m.Y', $this->date->begin),
+ date('H:i', $this->date->begin),
+ date('H:i', $this->date->end)
+ )
+ );
+ $this->selected_date = '';
+ if ($this->date->repetition_type) {
+ $this->selected_date = Request::get('selected_date');
+ }
+ $this->calendar_assignments = CalendarDateAssignment::findBySql(
+ "INNER JOIN `auth_user_md5`
+ ON `calendar_date_assignments`.`range_id` = `auth_user_md5`.`user_id`
+ WHERE
+ `calendar_date_id` = :calendar_date_id",
+ ['calendar_date_id' => $this->date->id]
+ );
+ $this->participation_message = null;
+ $this->user_participation_status = '';
+ $this->all_assignments_writable = false;
+ $this->is_group_date = count($this->calendar_assignments) > 1;
+
+ if ($this->calendar_assignments) {
+ $writable_assignment_c = 0;
+ $more_than_one_assignment = count($this->calendar_assignments) > 1;
+ //Find the calendar assignment of the user and set the participation message
+ //according to the participation status.
+ foreach ($this->calendar_assignments as $index => $assignment) {
+ if ($assignment->range_id === $GLOBALS['user']->id && $this->is_group_date) {
+ $this->user_participation_status = $assignment->participation;
+ if ($assignment->participation === 'ACCEPTED') {
+ $this->participation_message = MessageBox::info(_('Sie nehmen am Termin teil.'));
+ } elseif ($assignment->participation === 'DECLINED') {
+ $this->participation_message = MessageBox::info(_('Sie nehmen nicht am Termin teil.'));
+ } elseif ($assignment->participation === 'ACKNOWLEDGED') {
+ $this->participation_message = MessageBox::info(_('Sie haben den Termin zur Kenntnis genommen.'));
+ } else {
+ $this->participation_message = MessageBox::info(_('Sie haben keine Angaben zur Teilnahme gemacht.'));
+ }
+ if ($more_than_one_assignment) {
+ $writable_assignment_c++;
+ } else {
+ //We don't need the users own assignment in the list of assignments
+ //when there is only one assignment to the users own calendar.
+ unset($this->calendar_assignments[$index]);
+
+ }
+ } else {
+ if ($assignment->isWritable($GLOBALS['user']->id)) {
+ $writable_assignment_c++;
+ }
+ }
+ }
+
+ $this->all_assignments_writable = $writable_assignment_c === count($this->calendar_assignments);
+
+ //Order all calendar assignments by type and name:
+ uasort($this->calendar_assignments, function ($a, $b) {
+ $compare_name = ($a->course instanceof Course && $b->course instanceof Course)
+ || ($a->user instanceof User && $b->user instanceof User);
+ if ($compare_name) {
+ $a_name = '';
+ if ($a->course instanceof Course) {
+ $a_name = $a->course->getFullname();
+ } elseif ($a->user instanceof User) {
+ $a_name = $a->user->getFullName();
+ }
+ $b_name = '';
+ if ($b->course instanceof Course) {
+ $b_name = $b->course->getFullname();
+ } elseif ($b->user instanceof User) {
+ $b_name = $b->user->getFullName();
+ }
+ if ($a_name < $b_name) {
+ return -1;
+ } elseif ($a_name > $b_name) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ //Compare types.
+ $a_is_course = $a->course instanceof Course;
+ if ($a_is_course) {
+ return -1;
+ } else {
+ //$b is a course:
+ return 1;
+ }
+ }
+ });
+ }
+ }
+
+ public function add_action($range_and_id = '')
+ {
+ PageLayout::setTitle(_('Termin anlegen'));
+
+ $owner = $this->getCalendarOwner($range_and_id);
+
+ $this->date = new CalendarDate();
+ if (Request::submitted('begin') && Request::submitted('end')) {
+ $this->date->begin = Request::get('begin');
+ $this->date->end = Request::get('end');
+ $this->date->repetition_end = $this->date->end;
+ } else {
+ $time = new DateTime();
+ $time = $time->add(new DateInterval('PT1H'));
+ $time->setTime(intval($time->format('H')), 0, 0);
+ $this->date->begin = $time->getTimestamp();
+ $time = $time->add(new DateInterval('PT30M'));
+ $this->date->end = $time->getTimestamp();
+ $this->date->repetition_end = $this->date->end;
+ }
+ if ($owner instanceof Course) {
+ $this->form_post_link = $this->link_for('calendar/date/add/course_' . $owner->id);
+ } else {
+ //Personal calendar or group calendar
+ $this->form_post_link = $this->link_for('calendar/date/add');
+ }
+ $this->handleForm('add', $owner);
+ }
+
+ public function edit_action($date_id)
+ {
+ PageLayout::setTitle(_('Termin bearbeiten'));
+
+ $this->date = CalendarDate::find($date_id);
+ if (!$this->date) {
+ throw new Exception(_('Der Termin wurde nicht gefunden!'));
+ }
+ //Set the repetition end date to the end of the date in case it isn't set:
+ if (!$this->date->repetition_end) {
+ $this->date->repetition_end = $this->date->end;
+ }
+
+ $this->form_post_link = $this->link_for('calendar/date/edit/' . $this->date->id);
+ $this->handleForm();
+ }
+
+ protected function handleForm($mode = 'edit', $owner = null)
+ {
+ $this->form_errors = [];
+ $this->calendar_assignment_items = [];
+
+ $this->writable_calendars = $this->getCalendarOwnersWithWriteAccess($mode === 'edit' ? $this->date : null, $owner);
+ if (!$this->writable_calendars) {
+ throw new AccessDeniedException();
+ }
+ $this->user_id = Request::get('user_id', $owner->id ?? '');
+ $this->group_id = '';
+ if (!$owner) {
+ $this->group_id = Request::get('group_id');
+ }
+ $this->owner_id = $owner ? $owner->id : '';
+
+ $this->category_options = $this->getCategoryOptions();
+ $this->exceptions = [];
+
+ if (!$owner || !($owner instanceof Course)) {
+ $this->user_quick_search_type = null;
+ $this->multi_person_search = null;
+ if (Config::get()->CALENDAR_GROUP_ENABLE) {
+ if (Config::get()->CALENDAR_GRANT_ALL_INSERT) {
+ $this->user_quick_search_type = new StandardSearch('user_id');
+ } else {
+ //Only get those users where the current user has
+ //write access to the calendar.
+ $this->user_quick_search_type = new SQLSearch(
+ "SELECT
+ `auth_user_md5`.`user_id`, "
+ . $GLOBALS['_fullname_sql']['full'] . " AS fullname
+ FROM `auth_user_md5`
+ INNER JOIN `contact`
+ ON `auth_user_md5`.`user_id` = `contact`.`owner_id`
+ INNER JOIN `user_info`
+ ON `user_info`.`user_id` = `auth_user_md5`.`user_id`
+ WHERE
+ `auth_user_md5`.`user_id` <> " . DBManager::get()->quote($GLOBALS['user']->id) . "
+ AND `contact`.`user_id` = " . DBManager::get()->quote($GLOBALS['user']->id) . "
+ AND `contact`.`calendar_permissions` = 'WRITE'
+ AND (
+ `auth_user_md5`.`username` LIKE :input
+ OR CONCAT(`auth_user_md5`.`Vorname`, ' ', `auth_user_md5`.`Nachname`) LIKE :input
+ OR CONCAT(`auth_user_md5`.`Nachname`, ' ', `auth_user_md5`.`Vorname`) LIKE :input
+ OR `auth_user_md5`.`Nachname` LIKE :input
+ OR " . $GLOBALS['_fullname_sql']['full'] . " LIKE :input
+ )
+ GROUP BY `auth_user_md5`.`user_id`
+ ORDER BY fullname ASC",
+ _('Person suchen'),
+ 'user_id'
+ );
+ }
+ }
+ }
+
+ if ($this->date->isNew()) {
+ if (!($owner instanceof Course)) {
+ //Assign the date to the calendar of the current user by default:
+ $user = User::findCurrent();
+ if ($user) {
+ $this->calendar_assignment_items[] = [
+ 'value' => $user->id,
+ 'name' => $user->getFullName(),
+ 'deletable' => true
+ ];
+ }
+ }
+ } else {
+ $exceptions = CalendarDateException::findBySql(
+ 'calendar_date_id = :date_id ORDER BY `date` ASC',
+ ['date_id' => $this->date->id]
+ );
+ foreach ($exceptions as $exception) {
+ $this->exceptions[] = $exception->date;
+ }
+
+ $calendars_assignments = CalendarDateAssignment::findByCalendar_date_id($this->date->id);
+ foreach ($calendars_assignments as $assignment) {
+ $range_avatar = $assignment->getRangeAvatar();
+ $this->calendar_assignment_items[] = [
+ 'value' => $assignment->range_id,
+ 'name' => $assignment->getRangeName(),
+ 'deletable' => true
+ ];
+ }
+ }
+
+ $this->all_day_event = false;
+ if ($mode === 'add' && Request::get('all_day') === '1') {
+ $this->all_day_event = true;
+ } else {
+ $begin = new DateTime();
+ $begin->setTimestamp(intval($this->date->begin));
+ $end = new DateTime();
+ $end->setTimestamp(intval($this->date->end));
+ $duration = $end->diff($begin);
+ if ($duration->h === 23 && $duration->i === 59 && $duration->s === 59 && $begin->format('H:i:s') === '00:00:00') {
+ //The event starts at midnight and ends on 23:59:59. It is an all-day event.
+ $this->all_day_event = true;
+ }
+ }
+
+ if (!Request::isPost()) {
+ return;
+ }
+ if (Request::submitted('save')) {
+ CSRFProtection::verifyUnsafeRequest();
+
+ if ($this->date->isNew()) {
+ $this->date->author_id = $GLOBALS['user']->id;
+ }
+ $this->date->editor_id = $GLOBALS['user']->id;
+
+ $begin = Request::getDateTime('begin', 'd.m.Y H:i');
+ $end = Request::getDateTime('end', 'd.m.Y H:i');
+ if (Request::get('all_day') === '1') {
+ $this->all_day_event = true;
+ $begin->setTime(0,0,0);
+ $end = clone $begin;
+ $end->setTime(23,59,59);
+ }
+ $this->date->begin = $begin->getTimestamp();
+ $this->date->end = $end->getTimestamp();
+ if (!$this->date->begin) {
+ $this->form_errors[_('Beginn')] = _('Bitte geben Sie einen Startzeitpunkt ein.');
+ }
+ if (!$this->date->end && !$this->all_day_event) {
+ $this->form_errors[_('Ende')] = _('Bitte geben Sie einen Endzeitpunkt ein.');
+ }
+ if ($this->date->begin && $this->date->end && ($this->date->end < $this->date->begin)) {
+ $this->form_errors[_('Ende')] = _('Der Startzeitpunkt darf nicht nach dem Endzeitpunkt liegen!');
+ }
+
+ $this->date->title = Request::get('title');
+ if (!$this->date->title) {
+ $this->form_errors[_('Titel')] = _('Bitte geben Sie einen Titel ein.');
+ }
+
+ $this->date->access = Request::get('access');
+ if (!in_array($this->date->access, ['PUBLIC', 'CONFIDENTIAL', 'PRIVATE'])) {
+ $this->form_errors[_('Zugriff')] = _('Bitte wählen Sie einen Zugriffstyp aus.');
+ }
+
+ $this->date->description = Request::get('description');
+
+ $this->date->category = Request::get('category');
+ if (!in_array($this->date->category, array_keys($this->category_options))) {
+ $this->form_errors[_('Kategorie')] = _('Bitte wählen Sie eine gültige Kategorie aus.');
+ }
+
+ $this->date->user_category = Request::get('user_category');
+
+ $this->date->location = Request::get('location');
+
+ //Store the repetition information:
+
+ $this->date->clearRepetitionFields();
+ $this->date->repetition_type = Request::get('repetition_type', '');
+ if (!in_array($this->date->repetition_type, ['', 'DAILY', 'WEEKLY', 'WORKDAYS', 'MONTHLY', 'YEARLY'])) {
+ $this->form_errors[_('Wiederholung')] = _('Bitte wählen Sie ein gültiges Wiederholungsintervall aus.');
+ }
+ if ($this->date->repetition_type !== '') {
+ $this->date->interval = '';
+ if (in_array($this->date->repetition_type, ['DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'])) {
+ $this->date->interval = Request::get('repetition_interval');
+ }
+
+ if ($this->date->repetition_type === 'WEEKLY') {
+ $dow = array_unique(Request::getArray('repetition_dow'));
+ foreach ($dow as $day) {
+ if ($day < 1 || $day > 7) {
+ $this->form_errors[_('Wiederholung an bestimmtem Wochentag')] = _('Bitte wählen Sie einen Wochentag zwischen Montag und Sonntag aus.');
+ }
+ }
+ $this->date->days = implode('', $dow);
+ } elseif ($this->date->repetition_type === 'WORKDAYS') {
+ //Special case: The "WORKDAYS" repetition type is a shorthand type
+ //for a weekly repetition from Monday to Friday.
+ $this->date->repetition_type = 'WEEKLY';
+ $this->date->days = '12345';
+ $this->date->interval = '1';
+ } elseif ($this->date->repetition_type === 'MONTHLY') {
+ $month_type = Request::get('repetition_month_type');
+ if ($month_type === 'dom') {
+ $this->date->offset = Request::get('repetition_dom');
+ } elseif ($month_type === 'dow') {
+ $this->date->days = Request::get('repetition_dow');
+ $this->date->offset = Request::get('repetition_dow_week');
+ }
+ } elseif ($this->date->repetition_type === 'YEARLY') {
+ $month = Request::get('repetition_month');
+ if ($month < 1 || $month > 12) {
+ $this->form_errors[_('Monat')] = _('Bitte wählen Sie einen Monat zwischen Januar und Dezember aus.');
+ }
+ $this->date->month = $month;
+ $month_type = Request::get('repetition_month_type');
+ if ($month_type === 'dom') {
+ $this->date->offset = Request::get('repetition_dom');
+ } elseif ($month_type === 'dow') {
+ $this->date->days = Request::get('repetition_dow');
+ $this->date->offset = Request::get('repetition_dow_week');
+ }
+ }
+
+ $end_type = Request::get('repetition_rep_end_type');
+ if ($end_type === 'end_date') {
+ $end_date = Request::getDateTime('repetition_rep_end_date', 'd.m.Y');
+ $end_date->setTime(23,59,59);
+ $this->date->repetition_end = $end_date->getTimestamp();
+ } elseif ($end_type === 'end_count') {
+ $this->date->number_of_dates = Request::get('repetition_number_of_dates');
+ } else {
+ //Repetition never ends:
+ $this->date->repetition_end = CalendarDate::NEVER_ENDING;
+ }
+ }
+
+ $assigned_calendar_ids = Request::getArray('assigned_calendar_ids');
+ if (!$assigned_calendar_ids || (count($assigned_calendar_ids) === 0)) {
+ $this->form_errors[_('Teilnehmende Personen')] = _('Der Termin ist keinem Kalender zugewiesen!');
+ }
+
+ if ($this->form_errors) {
+ return;
+ }
+
+ $stored = false;
+ if ($this->date->isDirty()) {
+ $stored = $this->date->store();
+ } else {
+ $stored = true;
+ }
+ if (!$stored) {
+ PageLayout::postError(
+ _('Beim Speichern des Termins ist ein Fehler aufgetreten.')
+ );
+ return;
+ }
+
+ //Assign the calendar date to all writable calendars.
+
+ //Check the assigned calendar-IDs first if they are valid:
+ $valid_assigned_calendar_ids = [];
+ if (($owner instanceof Course)) {
+ //Set the course as calendar:
+ $allowed_calendar_ids = [$owner->id];
+ } else {
+ //Assign the date to the calendars of all the selected users:
+ $allowed_calendar_ids = [$GLOBALS['user']->id];
+ if (Config::get()->CALENDAR_GROUP_ENABLE) {
+ $allowed_calendar_results = $this->user_quick_search_type->getResults('%%%%');
+ foreach ($allowed_calendar_results as $result) {
+ $allowed_calendar_ids[] = $result[0];
+ }
+ }
+ }
+
+ foreach ($assigned_calendar_ids as $assigned_calendar_id) {
+ if (Course::exists($assigned_calendar_id) || User::exists($assigned_calendar_id)) {
+ //Valid ID of an existing calendar (range-ID).
+ if (in_array($assigned_calendar_id, $allowed_calendar_ids)) {
+ //The calendar is writable.
+ $valid_assigned_calendar_ids[] = $assigned_calendar_id;
+ }
+ }
+ }
+ if (count($valid_assigned_calendar_ids) < 1) {
+ PageLayout::postError(
+ _('Die Zuweisungen des Termins zu Kalendern sind ungültig!')
+ );
+ return;
+ }
+
+ //Remove the date from all user calendars that aren't in the array of writable calendars.
+ CalendarDateAssignment::deleteBySQL(
+ '`range_id` NOT IN ( :owner_ids ) AND `calendar_date_id` = :calendar_date_id',
+ ['owner_ids' => $allowed_calendar_ids, 'calendar_date_id' => $this->date->id]
+ );
+
+ //Now add the date to all selected calendars:
+ foreach($valid_assigned_calendar_ids as $assigned_calendar_id) {
+ $assignment = CalendarDateAssignment::findOneBySql(
+ 'range_id = :assigned_calendar_id AND calendar_date_id = :calendar_date_id',
+ [
+ 'assigned_calendar_id' => $assigned_calendar_id,
+ 'calendar_date_id' => $this->date->id
+ ]
+ );
+ if (!$assignment) {
+ $assignment = new CalendarDateAssignment();
+ $assignment->range_id = $assigned_calendar_id;
+ $assignment->calendar_date_id = $this->date->id;
+ $assignment->store();
+ }
+ }
+
+ //Clear all exceptions for the event and set them again:
+ CalendarDateException::deleteByCalendar_date_id($this->date->id);
+ $new_exceptions = Request::getArray('exceptions');
+ $stored_c = 0;
+ foreach ($new_exceptions as $exception) {
+ $date_parts = explode('-', $exception);
+ if (count($date_parts) === 3) {
+ //Should be a valid date string.
+ $e = new CalendarDateException();
+ $e->calendar_date_id = $this->date->id;
+ $e->date = $exception;
+ if ($e->store()) {
+ $stored_c++;
+ }
+ }
+ }
+ if ($stored_c === count($new_exceptions)) {
+ PageLayout::postSuccess(_('Der Termin wurde gespeichert.'));
+ } else {
+ PageLayout::postWarning(_('Der Termin wurde gespeichert, aber nicht mit allen Terminausfällen!'));
+ }
+ if (Request::submitted('selected_date')) {
+ $selected_date = Request::getDateTime('selected_date');
+ if ($selected_date) {
+ //Set the calendar default date to the previously selected date:
+ $_SESSION['calendar_date'] = $selected_date->format('Y-m-d');
+ }
+ } else {
+ //Set the calendar default date to the beginning of the date:
+ $_SESSION['calendar_date'] = $begin->format('Y-m-d');
+ }
+ $this->response->add_header('X-Dialog-Close', '1');
+ }
+ }
+
+ public function move_action($date_id)
+ {
+ $this->date = CalendarDate::find($date_id);
+ if (!$this->date) {
+ throw new InvalidArgumentException(
+ _('Der angegebene Termin wurde nicht gefunden.')
+ );
+ }
+ if (!$this->date->isWritable($GLOBALS['user']->id)) {
+ throw new AccessDeniedException(
+ _('Sie sind nicht berechtigt, diesen Termin zu ändern.')
+ );
+ }
+
+ $this->begin = Request::getDateTime('begin', \DateTime::RFC3339);
+ $this->end = Request::getDateTime('end', \DateTime::RFC3339);
+ if (!$this->begin || !$this->end) {
+ throw new InvalidArgumentException();
+ }
+
+ if ($this->date->repetition_type) {
+ PageLayout::setTitle(_('Verschieben eines Termins aus einer Terminserie'));
+ //Show the dialog to decide what shall be done with the repetition.
+ if (Request::submitted('move')) {
+ CSRFProtection::verifyUnsafeRequest();
+ $repetition_handling = Request::get('repetition_handling');
+ $store_old_date = false;
+ if ($repetition_handling === 'create_single_date') {
+ //Create a new date with the new time range and then
+ //create an exception for the old date.
+ $new_date = new CalendarDate();
+ $new_date->setData($this->date->toArray());
+ $new_date->id = $new_date->getNewId();
+ $new_date->unique_id = '';
+ $new_date->begin = $this->begin->getTimestamp();
+ $new_date->end = $this->end->getTimestamp();
+ $new_date->author_id = $GLOBALS['user']->id;
+ $new_date->editor_id = $GLOBALS['user']->id;
+ $new_date->clearRepetitionFields();
+ $new_date->store();
+ foreach ($this->date->calendars as $calendar) {
+ $new_date_calendar = new CalendarDateAssignment();
+ $new_date_calendar->calendar_date_id = $new_date->id;
+ $new_date_calendar->range_id = $calendar->range_id;
+ $new_date_calendar->store();
+ }
+ $exception = CalendarDateException::findBySQL(
+ '`calendar_date_id` = :calendar_date_id AND `date` = :date',
+ [
+ 'calendar_date_id' => $this->date->id,
+ 'date' => $this->begin->format('Y-m-d')
+ ]
+ );
+ if (!$exception) {
+ $exception = new CalendarDateException();
+ $exception->calendar_date_id = $this->date->id;
+ $exception->date = $this->begin->format('Y-m-d');
+ $exception->store();
+ }
+ $this->response->add_header('X-Dialog-Close', '1');
+ return;
+ } elseif ($repetition_handling === 'change_times') {
+ //Set the new time for begin and end:
+ $date_begin = new DateTime();
+ $date_begin->setTimestamp($this->date->begin);
+ $date_begin->setTime(
+ intval($this->begin->format('H')),
+ intval($this->begin->format('i')),
+ intval($this->begin->format('s'))
+ );
+ $this->date->begin = $date_begin->getTimestamp();
+ $date_end = new DateTime();
+ $date_end->setTimestamp($this->date->end);
+ $date_end->setTime(
+ intval($this->end->format('H')),
+ intval($this->end->format('i')),
+ intval($this->end->format('s'))
+ );
+ $this->date->end = $date_end->getTimestamp();
+
+ //Set the editor-ID:
+ $this->date->editor_id = $GLOBALS['user']->id;
+
+ $store_old_date = true;
+ } elseif ($repetition_handling === 'change_all') {
+ $this->date->begin = $this->begin->getTimestamp();
+ if ($this->date->repetition_end && intval($this->date->repetition_end) != pow(2,31) - 1) {
+ //The repetition end date is set to one specific date.
+ //It must be recalculated from the end date.
+ $old_end = new DateTime();
+ $old_end->setTimestamp($this->date->end);
+ $old_repetition_end = new DateTime();
+ $old_repetition_end ->setTimestamp($this->date->repetition_end);
+ $distance = $old_end->diff($old_repetition_end);
+ $this->date->end = $this->end->getTimestamp();
+ $new_repetition_end = clone $this->end;
+ $new_repetition_end = $new_repetition_end->add($distance);
+ $this->date->repetition_end = $new_repetition_end->getTimestamp();
+ }
+ $this->date->end = $this->end->getTimestamp();
+
+ //Set the editor-ID:
+ $this->date->editor_id = $GLOBALS['user']->id;
+
+ $store_old_date = true;
+ } else {
+ //Invalid choice.
+ PageLayout::postError(_('Ungültige Auswahl!'));
+ return;
+ }
+ if ($store_old_date) {
+ $success = false;
+ if ($this->date->isDirty()) {
+ $success = $this->date->store();
+ } else {
+ $success = true;
+ }
+ if ($success) {
+ $this->response->add_header('X-Dialog-Close', '1');
+ $this->render_nothing();
+ } else {
+ throw new Exception(_('Der Termin konnte nicht gespeichert werden.'));
+ }
+ }
+ }
+ } else {
+ //Set the new date and time directly.
+ $this->date->begin = $this->begin->getTimestamp();
+ $this->date->end = $this->end->getTimestamp();
+ //Set the editor-ID:
+ $this->date->editor_id = $GLOBALS['user']->id;
+
+ $success = false;
+ if ($this->date->isDirty()) {
+ $success = $this->date->store();
+ } else {
+ $success = true;
+ }
+ if ($success) {
+ $this->response->add_header('X-Dialog-Close', '1');
+ $this->render_nothing();
+ } else {
+ throw new Exception(_('Der Termin konnte nicht gespeichert werden.'));
+ }
+ }
+ }
+
+ public function delete_action($date_id)
+ {
+ PageLayout::setTitle(_('Termin löschen'));
+ $this->date = CalendarDate::find($date_id);
+ if (!$this->date) {
+ PageLayout::postError(
+ _('Der Termin wurde nicht gefunden!')
+ );
+ $this->render_nothing();
+ }
+ $this->date_has_repetitions = !empty($this->date->repetition_type);
+ $this->selected_date = null;
+ if ($this->date_has_repetitions) {
+ $this->selected_date = Request::getDateTime('selected_date');
+ if (!$this->selected_date) {
+ $this->selected_date = new DateTime();
+ $this->selected_date->setTimestamp($this->date->begin);
+ }
+ }
+ $this->repetition_handling = Request::get('repetition_handling', 'create_exception');
+ if (Request::submitted('delete')) {
+ $delete_whole_date = false;
+ CSRFProtection::verifyUnsafeRequest();
+ if ($this->date_has_repetitions) {
+ if ($this->repetition_handling === 'create_exception') {
+ $exception = new CalendarDateException();
+ $exception->calendar_date_id = $this->date->id;
+ $exception->date = $this->selected_date->format('Y-m-d');
+ if ($exception->store()) {
+ PageLayout::postSuccess(
+ sprintf(
+ _('Die Ausnahme am %s wurde der Terminserie hinzugefügt.'),
+ $this->selected_date->format('d.m.Y')
+ )
+ );
+ $this->response->add_header('X-Dialog-Close', '1');
+ $this->render_nothing();
+ } else {
+ PageLayout::postError(
+ sprintf(
+ _('Die Ausnahme am %s konnte der Terminserie nicht hinzugefügt werden.'),
+ $this->selected_date->format('d.m.Y')
+ )
+ );
+ }
+ } elseif ($this->repetition_handling === 'delete_all') {
+ $delete_whole_date = true;
+ }
+ } else {
+ $delete_whole_date = true;
+ }
+ if ($delete_whole_date) {
+ if ($this->date->delete()) {
+ if ($this->date_has_repetitions) {
+ PageLayout::postSuccess(_('Die Terminserie wurde gelöscht!'));
+ } else {
+ PageLayout::postSuccess(_('Der Termin wurde gelöscht!'));
+ }
+ $this->response->add_header('X-Dialog-Close', '1');
+ $this->render_nothing();
+ } else {
+ if ($this->date_has_repetitions) {
+ PageLayout::postError(_('Die Terminserie konnte nicht gelöscht werden!'));
+ } else {
+ PageLayout::postError(_('Der Termin konnte nicht gelöscht werden!'));
+ }
+ }
+ }
+ }
+ }
+
+ public function participation_action($date_id)
+ {
+ $this->calendar_assignment = CalendarDateAssignment::find([$GLOBALS['user']->id, $date_id]);
+ if (!$this->calendar_assignment) {
+ throw new AccessDeniedException();
+ }
+ CSRFProtection::verifyUnsafeRequest();
+
+ $participation = Request::get('participation');
+ if (!in_array($participation, ['', 'ACCEPTED', 'DECLINED', 'ACKNOWLEDGED'])) {
+ throw new InvalidArgumentException();
+ }
+
+ $this->calendar_assignment->participation = $participation;
+ if ($this->calendar_assignment->isDirty()) {
+ $this->calendar_assignment->store();
+ $this->calendar_assignment->sendParticipationStatus();
+ }
+ $this->response->add_header('X-Dialog-Close', '1');
+ PageLayout::postSuccess(_('Ihre Teilnahmestatus wurde geändert.'));
+ $this->render_nothing();
+ }
+}
diff --git a/app/controllers/calendar/group.php b/app/controllers/calendar/group.php
deleted file mode 100644
index f440a57..0000000
--- a/app/controllers/calendar/group.php
+++ /dev/null
@@ -1,336 +0,0 @@
-<?php
-
-require_once 'app/controllers/calendar/calendar.php';
-require_once 'app/controllers/authenticated_controller.php';
-
-class Calendar_GroupController extends Calendar_CalendarController
-{
- public function before_filter(&$action, &$args)
- {
- $this->base = 'calendar/group/';
- parent::before_filter($action, $args);
- }
-
- protected function createSidebar($active = 'week', $calendar = null)
- {
- parent::createSidebar($active, $calendar);
- $sidebar = Sidebar::Get();
- $actions = new ActionsWidget();
- $actions->addLink(_('Termin anlegen'),
- $this->url_for('calendar/group/edit'),
- Icon::create('add'),
- ['data-dialog' => 'size=auto']);
- $actions->addLink(_('Kalender freigeben'),
- $this->url_for('calendar/single/manage_access/' . $GLOBALS['user']->id,
- ['group_filter' => $this->range_id]),
- Icon::create('community'),
- ['id' => 'calendar-open-manageaccess',
- 'data-dialog' => '', 'data-dialogname' => 'manageaccess']);
- $sidebar->addWidget($actions);
- }
-
- protected function getTitle($group)
- {
- $title = sprintf(_('Terminkalender der Gruppe "%s"'), $group->name);
- return $title;
- }
-
- public function index_action()
- {
- // switch to the view the user has selected in his personal settings
- $default_view = $this->settings['view'] ?: 'week';
- $this->redirect($this->url_for('calendar/group/' . $default_view));
- }
-
- public function edit_action($range_id = null, $event_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- // get group and the calendars of the members
- // the first calendar is the calendar of the actual user
- $this->calendar = new SingleCalendar($GLOBALS['user']->id);
- $group = $this->getGroup($this->calendar);
- $this->attendee_ids = [];
- if ($group) {
- $calendar_owners = CalendarUser::getOwners($GLOBALS['user']->id,
- Calendar::PERMISSION_WRITABLE)->pluck('owner_id');
- $members = $group->members->pluck('user_id');
- $user_id = Request::option('user_id');
- $this->attendee_ids = array_intersect($calendar_owners, $members);
- $this->attendee_ids[] = $GLOBALS['user']->id;
- if ($user_id && in_array($user_id, $this->attendee_ids)) {
- $this->attendee_ids = [$user_id];
- }
- }
-
- $this->event = $this->calendar->getEvent($event_id);
-
- if ($this->event->isNew()) {
- $this->event = $this->calendar->getNewEvent();
- if (Request::get('isdayevent')) {
- $this->event->setStart(mktime(0, 0, 0, date('n', $this->atime),
- date('j', $this->atime), date('Y', $this->atime)));
- $this->event->setEnd(mktime(23, 59, 59, date('n', $this->atime),
- date('j', $this->atime), date('Y', $this->atime)));
- } else {
- $this->event->setStart($this->atime);
- $this->event->setEnd($this->atime + 3600);
- }
- $this->event->setAuthorId($GLOBALS['user']->id);
- $this->event->setEditorId($GLOBALS['user']->id);
- $this->event->setAccessibility('PRIVATE');
- if ($this->attendee_ids) {
- foreach ($this->attendee_ids as $attendee_id) {
- $attendee_event = clone $this->event;
- $attendee_event->range_id = $attendee_id;
- $this->attendees[] = $attendee_event;
- }
- }
- if (!Request::isXhr()) {
- PageLayout::setTitle($this->getTitle($this->calendar, _('Neuer Termin')));
- }
- } else {
- // open read only events and course events not as form
- // show information in dialog instead
- if (!$this->event->havePermission(Event::PERMISSION_WRITABLE)
- || $this->event instanceof CourseEvent) {
- $this->redirect($this->url_for('calendar/single/event/' . implode('/',
- [$this->range_id, $this->event->event_id])));
- return null;
- }
- $this->attendees = $this->event->attendees;
- if (!Request::isXhr()) {
- PageLayout::setTitle($this->getTitle($this->calendar, _('Termin bearbeiten')));
- }
- }
-
- if (Config::get()->CALENDAR_GROUP_ENABLE
- && $this->calendar->getRange() == Calendar::RANGE_USER) {
- $search_obj = new SQLSearch("SELECT auth_user_md5.user_id, {$GLOBALS['_fullname_sql']['full_rev']} as fullname, username, perms "
- . "FROM calendar_user "
- . "LEFT JOIN auth_user_md5 ON calendar_user.owner_id = auth_user_md5.user_id "
- . "LEFT JOIN user_info ON (auth_user_md5.user_id = user_info.user_id) "
- . 'WHERE calendar_user.user_id = '
- . DBManager::get()->quote($GLOBALS['user']->id)
- . ' AND calendar_user.permission > ' . Event::PERMISSION_READABLE
- . ' AND (username LIKE :input OR Vorname LIKE :input '
- . "OR CONCAT(Vorname,' ',Nachname) LIKE :input "
- . "OR CONCAT(Nachname,' ',Vorname) LIKE :input "
- . "OR Nachname LIKE :input OR {$GLOBALS['_fullname_sql']['full_rev']} LIKE :input "
- . ") ORDER BY fullname ASC",
- _('Nutzer suchen'), 'user_id');
- $this->quick_search = QuickSearch::get('user_id', $search_obj)
- ->fireJSFunctionOnSelect('STUDIP.Messages.add_adressee');
-
- // $default_selected_user = array($this->calendar->getRangeId());
- $this->mps = MultiPersonSearch::get('add_adressees')
- ->setLinkText(_('Mehrere Teilnehmende hinzufügen'))
- // ->setDefaultSelectedUser($default_selected_user)
- ->setTitle(_('Mehrere Teilnehmende hinzufügen'))
- ->setExecuteURL($this->url_for($this->base . 'edit'))
- ->setJSFunctionOnSubmit('STUDIP.Messages.add_adressees')
- ->setSearchObject($search_obj);
- $owners = SimpleORMapCollection::createFromArray(
- CalendarUser::findByUser_id($this->calendar->getRangeId()))
- ->pluck('owner_id');
- foreach (Calendar::getGroups($GLOBALS['user']->id) as $group) {
- $this->mps->addQuickfilter(
- $group->name,
- $group->members->filter(
- function ($member) use ($owners) {
- if (in_array($member->user_id, $owners)) {
- return $member;
- }
- })->pluck('user_id')
- );
- }
- }
-
- $stored = false;
- if (Request::submitted('store')) {
- $stored = $this->storeEventData($this->event, $this->calendar);
- }
-
- if ($stored !== false) {
- // switch back to group context
- $this->range_id = $group->getId();
- if ($stored === 0) {
- if (Request::isXhr()) {
- header('X-Dialog-Close: 1');
- exit;
- } else {
- PageLayout::postSuccess(_('Der Termin wurde nicht geändert.'));
- $this->relocate('calendar/group/' . $this->last_view, ['atime' => $this->atime]);
- }
- } else {
- PageLayout::postSuccess(_('Der Termin wurde gespeichert.'));
- $this->relocate('calendar/group/' . $this->last_view, ['atime' => $this->atime]);
- }
- } else {
- $this->createSidebar('edit', $this->calendar);
- $this->createSidebarFilter();
- $this->render_template('calendar/single/edit', $this->layout);
- }
- }
-
- public function day_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- // get group and the calendars of the members
- // the first calendar is the calendar of the actual user
- $this->calendars[0] = SingleCalendar::getDayCalendar(
- $GLOBALS['user']->id, $this->atime);
- $group = $this->getGroup($this->calendars[0]);
- foreach ($group->members as $member) {
- $calendar = new SingleCalendar($member->user_id);
- if ($calendar->havePermission(Calendar::PERMISSION_READABLE)) {
- $this->calendars[] = SingleCalendar::getDayCalendar($calendar,
- $this->atime, null, $this->restrictions);
- }
- }
-
- PageLayout::setTitle($this->getTitle($group)
- . ' - ' . _('Tagesansicht'));
- Navigation::activateItem('/calendar/calendar');
-
- $this->last_view = 'day';
-
- $this->createSidebar('day');
- $this->createSidebarFilter();
- }
-
- /**
- * Returns the Statusgruppe for the given calendar.
- *
- * @param SingleCalendar The calendar of the group owner.
- * @return Statusgruppen The found group.
- * @throws AccessDeniedException If the group does not exists or the owner
- * of the calendar is not the owner of the group.
- */
- private function getGroup($calendar)
- {
- $group = Statusgruppen::find($this->range_id);
- if (!$group) {
- throw new AccessDeniedException();
- }
- // is the user the owner of this group
- if ($group->range_id != $calendar->getRangeId()) {
- // not the owner...
- throw new AccessDeniedException();
- }
- return $group;
- }
-
- public function week_action($range_id = null)
- {
- $this->calendars = [];
- $this->range_id = $range_id ?: $this->range_id;
- $timestamp = mktime(12, 0, 0, date('n', $this->atime),
- date('j', $this->atime), date('Y', $this->atime));
- $monday = $timestamp - 86400 * (strftime('%u', $timestamp) - 1);
- $day_count = $this->settings['type_week'] == 'SHORT' ? 5 : 7;
- // one calendar for each day for the actual user
- for ($i = 0; $i < $day_count; $i++) {
- // one calendar holds the events of one day
- $this->calendars[0][$i] =
- SingleCalendar::getDayCalendar($GLOBALS['user']->id,
- $monday + $i * 86400, null, $this->restrictions);
- }
- // check and get the group
- $group = $this->getGroup($this->calendars[0][0]);
- $n = 1;
- foreach ($group->members as $member) {
- $calendar = new SingleCalendar($member->user_id);
- if ($calendar->havePermission(Calendar::PERMISSION_READABLE)) {
- for ($i = 0; $i < $day_count; $i++) {
- $this->calendars[$n][$i] =
- SingleCalendar::getDayCalendar($member->user_id,
- $monday + $i * 86400, null, $this->restrictions);
- }
- $n++;
- }
- }
-
- PageLayout::setTitle($this->getTitle($group)
- . ' - ' . _('Wochenansicht'));
- Navigation::activateItem('/calendar/calendar');
-
- $this->last_view = 'week';
-
- $this->createSidebar('week');
- $this->createSidebarFilter();
- }
-
- public function month_action($range_id = null)
- {
- $this->calendars = [];
- $this->range_id = $range_id ?: $this->range_id;
- $month_start = mktime(12, 0, 0, date('n', $this->atime), 1, date('Y', $this->atime));
- $month_end = mktime(12, 0, 0, date('n', $this->atime), date('t', $this->atime), date('Y', $this->atime));
- $adow = strftime('%u', $month_start) - 1;
- $cor = date('n', $this->atime) == 3 ? 1 : 0;
- $this->first_day = $month_start - $adow * 86400;
- $this->last_day = ((42 - ($adow + date('t', $this->atime))) % 7 + $cor) * 86400 + $month_end;
- // one calendar each day for the actual user
- for ($start_day = $this->first_day; $start_day <= $this->last_day; $start_day += 86400) {
- $this->calendars[0][] = SingleCalendar::getDayCalendar(
- $GLOBALS['user']->id, $start_day, null, $this->restrictions);
- }
- // check and get the group
- $group = $this->getGroup($this->calendars[0][0]);
- $n = 1;
- // get the calendars of the group members
- foreach ($group->members as $member) {
- $calendar = new SingleCalendar($member->user_id);
- if ($calendar->havePermission(Calendar::PERMISSION_READABLE)) {
- for ($start_day = $this->first_day; $start_day <= $this->last_day; $start_day += 86400) {
- $this->calendars[$n][] =
- SingleCalendar::getDayCalendar($member->user_id,
- $start_day, null, $this->restrictions);
- }
- $n++;
- }
- }
- PageLayout::setTitle($this->getTitle($group)
- . ' - ' . _('Monatssicht'));
- Navigation::activateItem('/calendar/calendar');
-
- $this->last_view = 'month';
-
- $this->createSidebar('month');
- $this->createSidebarFilter();
- }
-
- public function year_action($range_id = null)
- {
- $this->calendars = [];
- $this->count_lists = [];
-
- $this->range_id = $range_id ?: $this->range_id;
- $start = mktime(0, 0, 0, 1, 1, date('Y', $this->atime));
- $end = mktime(23, 59, 59, 12, 31, date('Y', $this->atime));
- $this->calendars[0] = new SingleCalendar(
- $GLOBALS['user']->id, $start, $end);
- $this->count_lists[0] = $this->calendars[0]->getListCountEvents();
-
- // check and get the group
- $group = $this->getGroup($this->calendars[0]);
- $n = 1;
- // get the calendars of the group members
- foreach ($group->members as $member) {
- $calendar = new SingleCalendar($member->user_id);
- if ($calendar->havePermission(Calendar::PERMISSION_READABLE)) {
- $this->calendars[$n] = $calendar->setStart($start)->setEnd($end);
- $this->count_lists[$n] = $this->calendars[$n]->getListCountEvents();
- $n++;
- }
- }
-
- PageLayout::setTitle($this->getTitle($group)
- . ' - ' . _('Jahresansicht'));
- Navigation::activateItem("/calendar/calendar");
-
- $this->last_view = 'year';
- $this->createSidebar('year');
- $this->createSidebarFilter();
- }
-}
diff --git a/app/controllers/calendar/instschedule.php b/app/controllers/calendar/instschedule.php
deleted file mode 100644
index 452e71d..0000000
--- a/app/controllers/calendar/instschedule.php
+++ /dev/null
@@ -1,190 +0,0 @@
-<?php
-# Lifter010: TODO
-
-/**
- * This controller displays an institute-calendar for seminars
- *
- * 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 Till Glöggler <tgloeggl@uos.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @since 2.0
- */
-class Calendar_InstscheduleController extends AuthenticatedController
-{
- /**
- * this action is the main action of the schedule-controller, setting the environment for the timetable,
- * accepting a comma-separated list of days.
- *
- * @param string $days a list of an arbitrary mix of the numbers 0-6, separated with a comma (e.g. 1,2,3,4,5 (for Monday to Friday, the default))
- */
- function index_action($days = false)
- {
- if ($GLOBALS['perm']->have_perm('admin')) {
- $inst_mode = true;
- }
- $my_schedule_settings = $GLOBALS['user']->cfg->SCHEDULE_SETTINGS;
- // set the days to be displayed
- if ($days === false) {
- if (Request::getArray('days')) {
- $this->days = array_keys(Request::getArray('days'));
- } else {
- $this->days = CalendarScheduleModel::getDisplayedDays($my_schedule_settings['glb_days']);
- }
- } else {
- $this->days = explode(',', $days);
- }
-
- // try to find the correct institute-id
- $institute_id = Request::option('institute_id', Context::getId());
-
- if (!$institute_id) {
- $institute_id = $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT;
- }
-
- if (!$institute_id || (in_array(get_object_type($institute_id), words('inst fak')) === false)) {
- throw new Exception(sprintf(_('Kann Einrichtungskalendar nicht anzeigen!'
- . 'Es wurde eine ungültige Instituts-Id übergeben (%s)!', $institute_id)));
- }
-
- // load semester-data and current semester
- $this->semesters = Semester::findAllVisible(false);
-
- if (Request::option('semester_id')) {
- $this->current_semester = Semester::find(Request::option('semester_id'));
- } else {
- $this->current_semester = Semester::findCurrent();
- }
-
- $this->entries = (array)CalendarInstscheduleModel::getInstituteEntries($GLOBALS['user']->id,
- $this->current_semester, 8, 20, $institute_id, $this->days);
-
- Navigation::activateItem('/course/main/schedule');
- PageLayout::setHelpKeyword('Basis.TerminkalenderStundenplan');
- PageLayout::setTitle(Context::getHeaderLine().' - '._('Veranstaltungs-Stundenplan'));
-
- $zoom = Request::int('zoom', 0);
- $this->controller = $this;
- $this->calendar_view = new CalendarWeekView($this->entries, 'instschedule');
- $this->calendar_view->setHeight(40 + (20 * $zoom));
- $this->calendar_view->setRange($my_schedule_settings['glb_start_time'], $my_schedule_settings['glb_end_time']);
- $this->calendar_view->groupEntries(); // if enabled, group entries with same start- and end-date
-
- URLHelper::addLinkParam('zoom', $zoom);
- URLHelper::addLinkParam('semester_id', $this->current_semester['semester_id']);
-
- $style_parameters = [
- 'whole_height' => $this->calendar_view->getOverallHeight(),
- 'entry_height' => $this->calendar_view->getHeight()
- ];
-
- $factory = new Flexi_TemplateFactory($this->dispatcher->trails_root . '/views');
- PageLayout::addStyle($factory->render('calendar/stylesheet', $style_parameters));
-
- if (Request::option('printview')) {
- PageLayout::addStylesheet('print.css');
-
- // remove all stylesheets that are not used for printing to have a more reasonable printing preview
- PageLayout::addHeadElement('script', [], "$('head link[media=screen]').remove();");
- } else {
- PageLayout::addStylesheet('print.css', ['media' => 'print']);
- }
-
- Helpbar::Get()->addPlainText(_('Information'), _('Der Stundenplan zeigt die regelmäßigen Veranstaltungen dieser Einrichtung.'), Icon::create('info'));
-
- $views = new ViewsWidget();
- $views->addLink(_('klein'), URLHelper::getURL('', ['zoom' => 0]))->setActive($zoom == 0);
- $views->addLink(_('mittel'), URLHelper::getURL('', ['zoom' => 2]))->setActive($zoom == 2);
- $views->addLink(_('groß'), URLHelper::getURL('', ['zoom' => 4]))->setActive($zoom == 4);
- $views->addLink(_('extra groß'), URLHelper::getURL('', ['zoom' => 7]))->setActive($zoom == 7);
-
- Sidebar::Get()->addWidget($views);
- $actions = new ActionsWidget();
- $actions->addLink(_('Druckansicht'),
- $this->url_for('calendar/instschedule/index/'. implode(',', $this->days),
- ['printview' => 'true',
- 'semester_id' => $this->current_semester['semester_id']]),
- Icon::create('print'),
- ['target' => '_blank']);
-
- // Only admins should have the ability to change their schedule settings here - they have no other schedule
- if ($GLOBALS['perm']->have_perm('admin')) {
- $actions->addLink(_("Darstellung ändern"),
- $this->url_for('calendar/schedule/settings'),
- Icon::create('admin'),
- ['data-dialog' => '']
- );
-
- // only show this setting if we have indeed a faculty where children might exist
- if (Context::get()->isFaculty()) {
- if ($GLOBALS['user']->cfg->MY_INSTITUTES_INCLUDE_CHILDREN) {
- $actions->addLink(_("Untergeordnete Institute ignorieren"),
- $this->url_for('calendar/instschedule/include_children/0'),
- Icon::create('checkbox-checked')
- );
- } else {
- $actions->addLink(_("Untergeordnete Institute einbeziehen"),
- $this->url_for('calendar/instschedule/include_children/1'),
- Icon::create('checkbox-unchecked')
- );
- }
- }
- }
-
- Sidebar::Get()->addWidget($actions);
- $semesterSelector = new SemesterSelectorWidget($this->url_for('calendar/instschedule'), 'semester_id', 'post');
- $semesterSelector->includeAll(false);
- Sidebar::Get()->addWidget($semesterSelector);
-
- }
-
- /**
- * Returns an HTML fragment of a grouped entry in the schedule of an institute.
- *
- * @param string $start the start time of the group, e.g. "1000"
- * @param string $end the end time of the group, e.g. "1200"
- * @param string $seminars the IDs of the courses
- * @param string $day numeric day to show
- *
- * @return void
- */
- function groupedentry_action($start, $end, $seminars, $day)
- {
- $this->response->add_header('Content-Type', 'text/html; charset=utf-8');
-
- // strucutre of an id: seminar_id-cycle_id
- // we do not need the cycle id here, so we trash it.
- $seminar_list = [];
-
- foreach (explode(',', $seminars) as $seminar) {
- $zw = explode('-', $seminar);
- $this->seminars[$zw[0]] = Seminar::getInstance($zw[0]);
- }
-
- $this->start = mb_substr($start, 0, 2) .':'. mb_substr($start, 2, 2);
- $this->end = mb_substr($end, 0, 2) .':'. mb_substr($end, 2, 2);
-
- $day_names = [_("Montag"),_("Dienstag"),_("Mittwoch"),
- _("Donnerstag"),_("Freitag"),_("Samstag"),_("Sonntag")];
-
- $this->day = $day_names[(int)$day];
-
- $this->render_template('calendar/instschedule/_entry_details');
- }
-
- /**
- * Toggle config setting to include children in schedule for the current faculty
- *
- * @param int $include_childs 0 / false to exclude children 1 / true to include them
- */
- function include_children_action($include_childs)
- {
- $GLOBALS['user']->cfg->store('MY_INSTITUTES_INCLUDE_CHILDREN', $include_childs ? 1 : 0);
-
- $this->redirect('calendar/instschedule/index');
- }
-}
diff --git a/app/controllers/calendar/schedule.php b/app/controllers/calendar/schedule.php
index 6fb51c4..bdd0f51 100644
--- a/app/controllers/calendar/schedule.php
+++ b/app/controllers/calendar/schedule.php
@@ -144,7 +144,7 @@ class Calendar_ScheduleController extends AuthenticatedController
];
$factory = new Flexi_TemplateFactory($this->dispatcher->trails_root . '/views');
- PageLayout::addStyle($factory->render('calendar/stylesheet', $style_parameters), 'screen, print');
+ PageLayout::addStyle($factory->render('calendar/schedule/stylesheet', $style_parameters), 'screen, print');
if (Request::option('printview')) {
$this->calendar_view->setReadOnly();
@@ -243,11 +243,7 @@ class Calendar_ScheduleController extends AuthenticatedController
$this->render_template('calendar/schedule/_entry_course');
} else if ($id) {
$entry_columns = CalendarScheduleModel::getScheduleEntries($GLOBALS['user']->id, 0, 0, $id);
- $entries = [];
- $entry_columns = array_pop($entry_columns);
- if ($entry_columns) {
- $entries = $entry_columns->getEntries();
- }
+ $entries = array_pop($entry_columns)->getEntries();
$this->show_entry = array_pop($entries);
$this->render_template('calendar/schedule/_entry_schedule');
}
diff --git a/app/controllers/calendar/single.php b/app/controllers/calendar/single.php
deleted file mode 100644
index af802bf..0000000
--- a/app/controllers/calendar/single.php
+++ /dev/null
@@ -1,571 +0,0 @@
-<?php
-/*
- * This is the controller for the single calendar view
- *
- * 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>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- */
-
-require_once 'app/controllers/calendar/calendar.php';
-
-class Calendar_SingleController extends Calendar_CalendarController
-{
- public function before_filter(&$action, &$args) {
- $this->base = 'calendar/single/';
- parent::before_filter($action, $args);
- }
-
- protected function createSidebar($active = null, $calendar = null)
- {
- parent::createSidebar($active, $calendar);
- $sidebar = Sidebar::Get();
- if ($calendar->havePermission(Calendar::PERMISSION_WRITABLE)) {
- $actions = new ActionsWidget();
- $actions->addLink(
- _('Termin anlegen'),
- $this->url_for('calendar/single/edit'),
- Icon::create('add'),
- ['data-dialog' => 'size=auto']
- );
- if ($calendar->havePermission(Calendar::PERMISSION_OWN)) {
- if (Config::get()->CALENDAR_GROUP_ENABLE) {
- $actions->addLink(
- _('Kalender freigeben'),
- $this->url_for('calendar/single/manage_access'),
- Icon::create('community'),
- [
- 'id' => 'calendar-open-manageaccess',
- 'data-dialog' => '',
- 'data-dialogname' => 'manageaccess'
- ]
- );
- }
- $actions->addLink(
- _('Veranstaltungstermine'),
- $this->url_for('calendar/single/seminar_events'),
- Icon::create('seminar'),
- ['data-dialog' => 'size=auto']
- );
- }
- $sidebar->addWidget($actions);
- }
- if ($calendar->havePermission(Calendar::PERMISSION_OWN)) {
- $export = new ExportWidget();
- $export->addLink(_('Termine exportieren'),
- $this->url_for('calendar/single/export_calendar'),
- Icon::create('download'),
- ['data-dialog' => 'size=auto']
- )->setActive($active == 'export_calendar');
- $export->addLink(
- _('Termine importieren'),
- $this->url_for('calendar/single/import'),
- Icon::create('upload'),
- ['data-dialog' => 'size=auto']
- )->setActive($active == 'import');
- $export->addLink(
- _('Kalender teilen'),
- $this->url_for('calendar/single/share'),
- Icon::create('group2'),
- ['data-dialog' => 'size=auto']
- )->setActive($active == 'share');
- $sidebar->addWidget($export);
- }
- }
-
- public function day_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = SingleCalendar::getDayCalendar($this->range_id,
- $this->atime, null, $this->restrictions);
-
- PageLayout::setTitle($this->getTitle($this->calendar, _('Tagesansicht')));
-
- $this->last_view = 'day';
-
- $this->createSidebar('day', $this->calendar);
- $this->createSidebarFilter();
- }
-
- public function week_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $timestamp = mktime(12, 0, 0, date('n', $this->atime), date('j', $this->atime), date('Y', $this->atime));
- $monday = $timestamp - 86400 * (strftime('%u', $timestamp) - 1);
- $day_count = $this->settings['type_week'] == 'SHORT' ? 5 : 7;
-
- $this->calendars = [];
- for ($i = 0; $i < $day_count; $i++) {
- $this->calendars[$i] =
- SingleCalendar::getDayCalendar(
- $this->range_id,
- $monday + $i * 86400,
- null,
- $this->restrictions
- );
- }
-
- PageLayout::setTitle($this->getTitle($this->calendars[0], _('Wochenansicht')));
-
- $this->last_view = 'week';
-
- $this->createSidebar('week', $this->calendars[0]);
- $this->createSidebarFilter();
- }
-
- public function month_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $month_start = mktime(12, 0, 0, date('n', $this->atime), 1, date('Y', $this->atime));
- $month_end = mktime(12, 0, 0, date('n', $this->atime), date('t', $this->atime), date('Y', $this->atime));
- $adow = strftime('%u', $month_start) - 1;
- $cor = date('n', $this->atime) == 3 ? 1 : 0;
- $this->first_day = $month_start - $adow * 86400;
- $this->last_day = ((42 - ($adow + date('t', $this->atime))) % 7 + $cor) * 86400 + $month_end;
- $this->calendars = [];
- for ($start_day = $this->first_day; $start_day <= $this->last_day; $start_day += 86400) {
- $this->calendars[] = SingleCalendar::getDayCalendar($this->range_id,
- $start_day, null, $this->restrictions);
- }
-
- PageLayout::setTitle($this->getTitle($this->calendars[0], _('Monatsansicht')));
-
- $this->last_view = 'month';
- $this->createSidebar('month', $this->calendars[0]);
- $this->createSidebarFilter();
- }
-
- public function year_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $start = mktime(0, 0, 0, 1, 1, date('Y', $this->atime));
- $end = mktime(23, 59, 59, 12, 31, date('Y', $this->atime));
- $this->calendar = new SingleCalendar($this->range_id, $start, $end);
- $this->count_list = $this->calendar->getListCountEvents(null, null,
- $this->restrictions);
-
- PageLayout::setTitle($this->getTitle($this->calendar, _('Jahresansicht')));
-
- $this->last_view = 'year';
- $this->createSidebar('year', $this->calendar);
- $this->createSidebarFilter();
- }
-
- public function event_action($range_id = null, $event_id = null)
- {
- PageLayout::setTitle(_('Termindaten'));
-
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
- $this->event = $this->calendar->getEvent($event_id);
-
- $this->createSidebar('edit', $this->calendar);
- $this->createSidebarFilter();
- }
-
- public function delete_action($range_id, $event_id)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
- if ($this->calendar->deleteEvent($event_id, true)) {
- PageLayout::postSuccess(_('Der Termin wurde gelöscht.'));
- }
- $this->redirect($this->url_for('calendar/single/' . $this->last_view));
- }
-
- public function delete_recurrence_action($range_id, $event_id, $atime)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $calendar = new SingleCalendar($this->range_id);
- $event = $calendar->getEvent($event_id);
- if ($event->getRecurrence('rtype') != 'SINGLE') {
- $exceptions = $event->getExceptions();
- $exceptions[] = $atime;
- $event->setExceptions($exceptions);
- if ($event->store() !== false) {
- PageLayout::postSuccess(strftime(_('Termin am %x aus Serie gelöscht.'), $atime));
- }
- }
- $this->redirect($this->url_for('calendar/single/' . $this->last_view));
- }
-
- public function export_event_action($event_id, $range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $calendar = new SingleCalendar($this->range_id);
- $event = $calendar->getEvent($event_id);
- if (!$event->isNew()) {
- $calender_writer = new CalendarWriterICalendar();
- $export = new CalendarExportFile($calender_writer);
- $export->exportFromObjects($event);
- $export->sendFile();
- }
- $this->render_nothing();
- }
-
- public function export_calendar_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
-
- if (Request::submitted('export')) {
- $calender_writer = new CalendarWriterICalendar();
- $export = new CalendarExportFile($calender_writer);
- if (Request::get('event_type') == 'user') {
- $types = ['CalendarEvent'];
- } else if (Request::get('event_type') == 'course') {
- $types = ['CourseEvent', 'CourseCancelledEvent'];
- } else {
- $types = ['CalendarEvent', 'CourseEvent', 'CourseCancelledEvent'];
- }
- if (Request::get('export_time') == 'date') {
- $exstart = $this->parseDateTime(Request::get('export_start'));
- $exend = $this->parseDateTime(Request::get('export_end'));
- } else {
- $exstart = 0;
- $exend = Calendar::CALENDAR_END;
- }
- $export->exportFromDatabase($this->calendar->getRangeId(), $exstart, $exend, $types);
- $export->sendFile();
- $this->render_nothing();
- exit;
- }
-
- PageLayout::setTitle($this->getTitle($this->calendar, _('Termine exportieren')));
-
- $this->createSidebar('export_calendar', $this->calendar);
- $this->createSidebarFilter();
- }
-
- public function import_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
-
- if ($this->calendar->havePermission(Calendar::PERMISSION_OWN)) {
- if (Request::submitted('import')) {
- CSRFProtection::verifySecurityToken();
- $calender_parser = new CalendarParserICalendar();
- $import = new CalendarImportFile($calender_parser, $_FILES['importfile']);
- if (Request::get('import_as_private_imp')) {
- $import->changePublicToPrivate();
- }
- $import->importIntoDatabase($range_id);
- $import_count = $import->getCount();
- PageLayout::postMessage(MessageBox::success(
- sprintf('Es wurden %s Termine importiert.', $import_count)));
- $this->redirect($this->url_for('calendar/single/' . $this->last_view));
- }
- }
- PageLayout::setTitle($this->getTitle($this->calendar, _('Termine importieren')));
- $this->createSidebar('import', $this->calendar);
- $this->createSidebarFilter();
- }
-
- public function share_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
-
- $this->short_id = null;
- if ($this->calendar->havePermission(Calendar::PERMISSION_OWN)) {
- if (Request::submitted('delete_id')) {
- CSRFProtection::verifySecurityToken();
- IcalExport::deleteKey($GLOBALS['user']->id);
- PageLayout::postSuccess(_('Die Adresse, unter der Ihre Termine abrufbar sind, wurde gelöscht'));
- }
-
- if (Request::submitted('new_id')) {
- CSRFProtection::verifySecurityToken();
- $this->short_id = IcalExport::setKey($GLOBALS['user']->id);
- PageLayout::postSuccess(_('Eine Adresse, unter der Ihre Termine abrufbar sind, wurde erstellt.'));
- } else {
- $this->short_id = IcalExport::getKeyByUser($GLOBALS['user']->id);
- }
-
- $text = "";
- if (Request::submitted('submit_email')) {
- $email_reg_exp = '/^([-.0-9=?A-Z_a-z{|}~])+@([-.0-9=?A-Z_a-z{|}~])+\.[a-zA-Z]{2,6}$/i';
- if (preg_match($email_reg_exp, Request::get('email')) !== 0) {
- $subject = '[' .Config::get()->UNI_NAME_CLEAN . ']' . _('Exportadresse für Ihre Termine');
- $text .= _('Diese Email wurde vom Stud.IP-System verschickt. Sie können auf diese Nachricht nicht antworten.') . "\n\n";
- $text .= _('Über diese Adresse erreichen Sie den Export für Ihre Termine:') . "\n\n";
- $text .= $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/ical/index/'
- . IcalExport::getKeyByUser($GLOBALS['user']->id);
- StudipMail::sendMessage(Request::get('email'), $subject, $text);
- PageLayout::postSuccess(_('Die Adresse wurde verschickt!'));
- } else {
- PageLayout::postError(_('Bitte geben Sie eine gültige Email-Adresse an.'));
- }
- $this->short_id = IcalExport::getKeyByUser($GLOBALS['user']->id);
- }
- }
- PageLayout::setTitle($this->getTitle($this->calendar, _('Kalender teilen oder einbetten')));
-
- $this->createSidebar('share', $this->calendar);
- $this->createSidebarFilter();
- }
-
- public function manage_access_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
-
- $all_calendar_users =
- CalendarUser::getUsers($this->calendar->getRangeId());
-
- $this->filter_groups = Statusgruppen::findByRange_id(
- $this->calendar->getRangeId());
-
- $this->users = [];
- $this->group_filter_selected = Request::option('group_filter', 'list');
- if ($this->group_filter_selected != 'list') {
- $contact_group = Statusgruppen::find($this->group_filter_selected);
- $calendar_users = [];
- foreach ($contact_group->members as $member) {
- $calendar_users[] = new CalendarUser([$this->calendar->getRangeId(), $member->user_id]);
- }
- $this->calendar_users = SimpleORMapCollection::createFromArray($calendar_users);
- } else {
- $this->group_filter_selected = 'list';
- $this->calendar_users = $all_calendar_users;
- }
-
- $this->own_perms = [];
- foreach ($this->calendar_users as $calendar_user) {
- $other_user = CalendarUser::find([$calendar_user->user_id, $this->calendar->getRangeId()]);
- if ($other_user) {
- $this->own_perms[$calendar_user->user_id] = $other_user->permission;
- } else {
- $this->own_perms[$calendar_user->user_id] = Calendar::PERMISSION_FORBIDDEN;
- }
- $this->users[mb_strtoupper(SimpleCollection::translitLatin1($calendar_user->nachname[0]))][] = $calendar_user;
- }
-
- ksort($this->users);
- $this->users = array_map(function ($g) {
- return SimpleCollection::createFromArray($g)->orderBy('nachname, vorname');
- }, $this->users);
-
- $this->mps = MultiPersonSearch::get('calendar-manage_access')
- ->setTitle(_('Personhinzufügen'))
- ->setLinkText(_('Person hinzufügen'))
- ->setDefaultSelectedUser($all_calendar_users->pluck('user_id'))
- ->setJSFunctionOnSubmit('STUDIP.CalendarDialog.closeMps')
- ->setExecuteURL($this->url_for('calendar/single/add_users/' . $this->calendar->getRangeId()))
- ->setSearchObject(new StandardSearch('user_id'));
-
- PageLayout::setTitle($this->getTitle($this->calendar, _('Kalender freigeben')));
-
- $this->createSidebar('manage_access', $this->calendar);
- $this->createSidebarFilter();
- }
-
- public function add_users_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
- if (Request::isXhr()) {
- $added_users = Request::optionArray('added_users');
- } else {
- $mps = MultiPersonSearch::load('calendar-manage_access');
- $added_users = $mps->getAddedUsers();
- $mps->clearSession();
- }
-
- $added = 0;
- foreach ($added_users as $user_id) {
- $user_to_add = User::find($user_id);
- if ($user_to_add) {
- $calendar_user = new CalendarUser(
- [$this->calendar->getRangeId(), $user_to_add->id]);
- if ($calendar_user->isNew()) {
- $calendar_user->permission = Calendar::PERMISSION_READABLE;
- $added += $calendar_user->store();
- }
- }
- }
- if ($added) {
- PageLayout::postSuccess(sprintf(
- ngettext(
- 'Eine Person wurde mit der Berechtigung zum Lesen des Kalenders hinzugefügt.',
- '%s Personen wurden mit der Berechtigung zum Lesen des Kalenders hinzugefügt.',
- $added
- ),
- $added
- ));
- }
-
- if (Request::isXhr()) {
- $this->response->add_header('X-Dialog-Close', 1);
- $this->response->set_status(200);
- $this->render_nothing();
- } else {
- $this->redirect($this->url_for('calendar/single/manage_access/' . $this->calendar->getRangeId()));
- }
- }
-
- public function remove_user_action($range_id = null, $user_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $user_id = $user_id ?: Request::option('user_id');
- $this->calendar = new SingleCalendar($this->range_id);
- $calendar_user = new CalendarUser([$this->calendar->getRangeId(), $user_id]);
- if (!$calendar_user->isNew()) {
- $name = $calendar_user->user->getFullname();
- $calendar_user->delete();
- }
- if (Request::isXhr()) {
- $this->response->set_status(200);
- $this->render_nothing();
- } else {
- PageLayout::postSuccess(sprintf(_('Person %s wurde entfernt.', htmlReady($name))));
- $this->redirect($this->url_for('calendar/single/manage_access/' . $this->calendar->getRangeId()));
- }
- }
-
- public function store_permissions_action($range_id = null)
- {
- $this->range_id = $range_id ?: $this->range_id;
- $this->calendar = new SingleCalendar($this->range_id);
-
- $deleted = 0;
- $read = 0;
- $write = 0;
- $submitted_permissions = Request::intArray('perm');
- foreach ($submitted_permissions as $user_id => $new_perm) {
- $calendar_user = new CalendarUser([$this->calendar->getRangeId(), $user_id]);
- if (!$calendar_user->isNew() && $new_perm == 1) {
- $deleted += $calendar_user->delete();
- $new_perm = 0;
- }
- if ($new_perm >= Calendar::PERMISSION_READABLE
- && $calendar_user->permission != $new_perm) {
- $calendar_user->permission = $new_perm;
- if ($calendar_user->store()) {
- if ($new_perm == Calendar::PERMISSION_READABLE) {
- $read++;
- } else {
- $write++;
- }
- }
- }
- }
- $sum = $deleted + $read + $write;
- if ($sum) {
- if ($deleted) {
- $details[] = sprintf(ngettext('Einer Person wurde die Berechtigungen entzogen.',
- '%s Personen wurden die Berechtigungen entzogen.', $deleted), $deleted);
- }
- if ($read) {
- $details[] = sprintf(ngettext('Eine Person wurde auf leseberechtigt gesetzt.',
- '%s Personen wurden auf leseberechtigt gesetzt.', $read), $read);
- }
- if ($write) {
- $details[] = sprintf(ngettext('Eine Person wurde auf schreibberechtigt gesetzt.',
- '%s Personen wurden auf schreibberechtigt gesetzt.', $write), $write);
- }
- PageLayout::postSuccess(sprintf(
- ngettext('Die Berechtigungen von einer Person wurde geändert.',
- 'Die Berechtigungen von %s Personen wurden geändert.',
- $sum), $sum),
- $details
- );
- // no message if the group was changed
- } else if (!Request::submitted('calendar_group_submit')) {
- PageLayout::postSuccess(_('Es wurden keine Berechtigungen geändert.'));
- }
- $this->redirect($this->url_for(
- 'calendar/single/manage_access/' . $this->calendar->getRangeId(),
- ['group_filter' => Request::option('group_filter', 'list')])
- );
- }
-
- public function seminar_events_action($order_by = null, $order = 'asc')
- {
- $config_sem = $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE;
- if (!Config::get()->MY_COURSES_ENABLE_ALL_SEMESTERS && $config_sem == 'all') {
- $config_sem = 'future';
- }
- $this->sem_data = Semester::findAllVisible();
-
- $sem = $config_sem ?: Config::get()->MY_COURSES_DEFAULT_CYCLE;
- if (Request::option('sem_select')) {
- $sem = Request::get('sem_select', $sem);
- }
- if (!in_array($sem, words('future all last current')) && isset($sem)) {
- Request::set('sem_select', $sem);
- }
- $this->group_field = 'sem_number';
- $this->order_by = $order_by;
- $this->config_sem_number = Config::get()->IMPORTANT_SEMNUMBER;
- // Needed parameters for selecting courses
- $params = [
- 'group_field' => $this->group_field,
- 'order_by' => $order_by,
- 'order' => $order,
- 'studygroups_enabled' => false,
- 'deputies_enabled' => false
- ];
-
- $this->sem_courses = MyRealmModel::getPreparedCourses($sem, $params);
- $semesters = new SimpleCollection(Semester::getAll());
- $this->sem = $sem;
- $this->semesters = $semesters->orderBy('beginn desc');
-
- $this->bind_calendar = SimpleCollection::createFromArray(
- CourseMember::findBySQL('user_id = ? AND bind_calendar = 1', [$GLOBALS['user']->id])
- )->pluck('seminar_id');
-
- }
-
- public function store_selected_sem_action()
- {
- CSRFProtection::verifySecurityToken();
- if (Request::submitted('store')) {
- $selected_sems = Request::intArray('selected_sem');
- $courses = SimpleORMapCollection::createFromArray(
- CourseMember::findBySQL('user_id = ? AND Seminar_id IN (?)',
- [$GLOBALS['user']->id, array_keys($selected_sems)]));
- $courses->each(function ($a) use ($selected_sems) {
- $a->bind_calendar = $selected_sems[$a->seminar_id];
- $a->store();
- });
- PageLayout::postSuccess(_('Die Auswahl der Veranstaltungen wurde gespeichert.'));
- }
- $this->redirect($this->url_for('calendar/single/' . $this->last_view));
- }
-
- /**
- * Retrieve the title of the calendar depending on calendar owner (range).
- *
- * @param SingleCalendar $calendar The calendar
- * @param string $title_end Additional text
- * @return string The complete title for the headline
- */
- protected function getTitle(SingleCalendar $calendar, $title_end)
- {
- $status = '';
- if ($calendar->getRangeId() == $GLOBALS['user']->id) {
- $title = _('Mein persönlicher Terminkalender');
- } else {
- if ($calendar->getRange() == Calendar::RANGE_USER) {
- $title = sprintf(_('Terminkalender von %s'),
- $calendar->range_object->getFullname());
- } else {
- $title = Context::getHeaderLine();
- }
- if ($calendar->havePermission(Calendar::PERMISSION_WRITABLE)) {
- $status = ' (' . _('schreibberechtigt') . ')';
- } else {
- $status = ' (' . _('leseberechtigt') . ')';
- }
- }
- return $title . ' - ' . $title_end . $status ;
- }
-}
diff --git a/app/controllers/contact.php b/app/controllers/contact.php
index 0225149..7dd2b05 100644
--- a/app/controllers/contact.php
+++ b/app/controllers/contact.php
@@ -20,15 +20,15 @@ class ContactController extends AuthenticatedController
parent::before_filter($action, $args);
// Load statusgroups
- $this->groups = SimpleCollection::createFromArray(Statusgruppen::findByRange_id(User::findCurrent()->id));
+ $this->groups = SimpleCollection::createFromArray(ContactGroup::findByOwner_id(User::findCurrent()->id));
// Load requested group
if (!empty($args[0])) {
- $this->group = $this->groups->findOneBy('statusgruppe_id', $args[0]);
+ $this->group = $this->groups->findOneBy('id', $args[0]);
//Check for cheaters
- if ($this->group->range_id != User::findCurrent()->id) {
- throw new AccessDeniedException;
+ if ($this->group->owner_id !== User::findCurrent()->id) {
+ throw new AccessDeniedException();
}
}
@@ -43,16 +43,17 @@ class ContactController extends AuthenticatedController
// Check if we need to add contacts
$mps = MultiPersonSearch::load('contacts');
$imported = 0;
- foreach ($mps->getAddedUsers() as $userId) {
- $user_to_add = User::find($userId);
+ foreach ($mps->getAddedUsers() as $user_id) {
+ $user_to_add = User::find($user_id);
if ($user_to_add) {
$new_contact = [
'owner_id' => User::findCurrent()->id,
- 'user_id' => $user_to_add->id];
+ 'user_id' => $user_to_add->id,
+ ];
if ($filter && $this->group) {
- $new_contact['group_assignments'][] = [
- 'statusgruppe_id' => $this->group->id,
- 'user_id' => $user_to_add->id
+ $new_contact['groups'][] = [
+ 'group_id' => $this->group->id,
+ 'user_id' => $user_to_add->id,
];
}
$imported += (bool)Contact::import($new_contact)->store();
@@ -74,7 +75,7 @@ class ContactController extends AuthenticatedController
if ($filter) {
$selected = $this->group;
- $contacts = SimpleCollection::createFromArray(User::findMany($selected->members->pluck('user_id')));
+ $contacts = SimpleCollection::createFromArray(User::findMany($selected->items->pluck('user_id')));
} else {
$selected = false;
$contacts = User::findCurrent()->contacts;
@@ -125,7 +126,7 @@ class ContactController extends AuthenticatedController
$contact = Contact::find([User::findCurrent()->id, User::findByUsername($contact_username)->id]);
if ($contact) {
if ($group) {
- $contact->group_assignments->unsetBy('statusgruppe_id', $group);
+ $contact->groups->unsetBy('group_id', $group);
if ($contact->store()) {
$removed_group_number++;
}
@@ -144,7 +145,7 @@ class ContactController extends AuthenticatedController
$contact = Contact::find([User::findCurrent()->id, User::findByUsername(Request::username('user'))->id]);
if ($contact) {
if ($group) {
- $contact->group_assignments->unsetBy('statusgruppe_id', $group);
+ $contact->group_assignments->unsetBy('group_id', $group);
if ($contact->store()) {
PageLayout::postSuccess(_('Der Kontakt wurde aus der Gruppe entfernt.'));
}
@@ -161,8 +162,8 @@ class ContactController extends AuthenticatedController
public function editGroup_action()
{
if (!$this->group) {
- $this->group = new Statusgruppen();
- $this->group->range_id = User::findCurrent()->id;
+ $this->group = new ContactGroup();
+ $this->group->owner_id = User::findCurrent()->id;
}
if (Request::submitted('store')) {
CSRFProtection::verifyRequest();
diff --git a/app/controllers/ical.php b/app/controllers/ical.php
index 4ecdc13..44ff2a1 100644
--- a/app/controllers/ical.php
+++ b/app/controllers/ical.php
@@ -51,17 +51,14 @@ class iCalController extends StudipController
$GLOBALS['user'] = new Seminar_User($user_id);
$GLOBALS['perm'] = new Seminar_Perm();
- $extype = 'ALL_EVENTS';
- $calender_writer = new CalendarWriterICalendar();
- $export = new CalendarExport($calender_writer);
- $export->exportFromDatabase($user_id, strtotime('-4 week'), 2114377200, 'ALL_EVENTS');
-
- if ($GLOBALS['_calendar_error']->getMaxStatus(ErrorHandler::ERROR_CRITICAL)) {
- $this->set_status(500);
- $this->render_nothing();
- return;
- }
- $content = join($export->getExport());
+ $end = DateTime::createFromFormat('U', '2114377200');
+ $start = new DateTime();
+ $start->modify('-4 week');
+ $ical_export = new ICalendarExport();
+ $ical = $ical_export->exportCalendarDates($user_id, $start, $end)
+ . $ical_export->exportCourseDates($user_id, $start, $end)
+ . $ical_export->exportCourseExDates($user_id, $start, $end);
+ $content = $ical_export->writeHeader() . $ical . $ical_export->writeFooter();
if (mb_stripos($_SERVER['HTTP_USER_AGENT'], 'google-calendar') !== false) {
$content = str_replace(['CLASS:PRIVATE','CLASS:CONFIDENTIAL'], 'CLASS:PUBLIC', $content);
}
diff --git a/app/controllers/institute/overview.php b/app/controllers/institute/overview.php
index f511bd4..66d55e1 100644
--- a/app/controllers/institute/overview.php
+++ b/app/controllers/institute/overview.php
@@ -144,10 +144,6 @@ class Institute_OverviewController extends AuthenticatedController
$response = $this->relay('questionnaire/widget/' . $this->institute_id . '/institute');
$this->questionnaires = $response->body;
}
-
- // Fetch dates
- $response = $this->relay("calendar/contentbox/display/$this->institute_id/1210000");
- $this->dates = $response->body;
}
}
diff --git a/app/controllers/institute/schedule.php b/app/controllers/institute/schedule.php
new file mode 100644
index 0000000..1c5d091
--- /dev/null
+++ b/app/controllers/institute/schedule.php
@@ -0,0 +1,179 @@
+<?php
+class Institute_ScheduleController extends AuthenticatedController
+{
+ public function before_filter(&$action, &$args)
+ {
+ parent::before_filter($action, $args);
+
+ if (Navigation::hasItem('/course/main')) {
+ Navigation::activateItem('/course/main');
+ }
+
+ if (!$GLOBALS['perm']->have_studip_perm('autor', Context::getId())) {
+ throw new AccessDeniedException();
+ }
+ }
+
+ public function index_action($institute_id)
+ {
+ PageLayout::setTitle(_('Veranstaltungs-Stundenplan'));
+
+ if (Navigation::hasItem('/course/main/schedule')) {
+ Navigation::activateItem('/course/main/schedule');
+ }
+
+ $semester = null;
+ if (Request::submitted('semester_id')) {
+ $semester = Semester::find(Request::option('semester_id'));
+ } else {
+ $semester = Semester::findCurrent();
+ }
+
+ $extra_params = [];
+ if ($semester) {
+ $extra_params['semester_id'] = $semester->id;
+ }
+
+ $sidebar = Sidebar::get();
+ $semester_widget = new SemesterSelectorWidget($this->url_for('institute/schedule/index/' . $institute_id));
+ if ($semester) {
+ $semester_widget->setSelection($semester->id);
+ }
+ $sidebar->addWidget($semester_widget);
+
+ $calendar_settings = $GLOBALS['user']->cfg->CALENDAR_SETTINGS ?? [];
+ $week_slot_duration = \Studip\Calendar\Helper::getCalendarSlotDuration('week');
+
+ $this->fullcalendar = \Studip\Fullcalendar::create(
+ _('Veranstaltungs-Stundenplan'),
+ [
+ 'minTime' => '08:00',
+ 'maxTime' => '20:00',
+ 'allDaySlot' => false,
+ 'header' => [
+ 'left' => '',
+ 'right' => ''
+ ],
+ 'views' => [
+ 'timeGridWeek' => [
+ 'columnHeaderFormat' => ['weekday' => 'long'],
+ 'weekends' => $calendar_settings['type_week'] === 'LONG',
+ 'slotDuration' => $week_slot_duration
+ ]
+ ],
+ 'defaultView' => 'timeGridWeek',
+ 'defaultDate' => date('Y-m-d'),
+ 'timeGridEventMinHeight' => 20,
+ 'eventSources' => [
+ [
+ 'url' => $this->url_for('institute/schedule/data/' . $institute_id),
+ 'method' => 'GET',
+ 'extraParams' => $extra_params
+ ]
+ ]
+ ]
+ );
+ }
+
+
+ public function data_action($institute_id)
+ {
+ //Fullcalendar sets the week time range in which to put the course dates
+ //of the semester. Therefore, start and end are handled in here.
+ $begin = Request::getDateTime('start', \DateTime::RFC3339);
+ $end = Request::getDateTime('end', \DateTime::RFC3339);
+ if (!($begin instanceof DateTime) || !($end instanceof DateTime)) {
+ //No time range specified.
+ throw new InvalidArgumentException('Invalid parameters!');
+ }
+
+ $semester_id = Request::option('semester_id');
+ $semester = Semester::find($semester_id);
+ if (!$semester) {
+ $this->render_json([]);
+ return;
+ }
+
+ //Get all regular course dates for that semester:
+ $cycle_dates = SeminarCycleDate::findBySql(
+ 'JOIN `termine` USING (`metadate_id`)
+ JOIN `seminare` USING (`seminar_id`)
+ JOIN `seminar_inst` USING (`seminar_id`)
+ WHERE `seminar_inst`.`institut_id` = :institute_id
+ AND (
+ `termine`.`date` BETWEEN :begin AND :end
+ OR `termine`.`end_time` BETWEEN :begin AND :end
+ )
+ GROUP BY `metadate_id`',
+ [
+ 'institute_id' => $institute_id,
+ 'begin' => $semester->beginn,
+ 'end' => $semester->ende
+ ]
+ );
+
+ if (!$cycle_dates) {
+ $this->render_json([]);
+ return;
+ }
+
+ foreach ($cycle_dates as $cycle_date) {
+ //Calculate a fake begin and end that lies in the week
+ //fullcalendar has specified.
+ $fake_begin = clone $begin;
+ $fake_end = clone $begin;
+ if ($cycle_date->weekday > 1) {
+ $fake_begin = $fake_begin->add(new DateInterval('P' . ($cycle_date->weekday - 1) . 'D'));
+ $fake_end = $fake_end->add(new DateInterval('P' . ($cycle_date->weekday - 1) . 'D'));
+ }
+ $start_time_parts = explode(':', $cycle_date->start_time);
+ $end_time_parts = explode(':', $cycle_date->end_time);
+ $fake_begin->setTime(
+ (int) $start_time_parts[0],
+ (int) $start_time_parts[1],
+ (int) $start_time_parts[2]
+ );
+ $fake_end->setTime(
+ (int) $end_time_parts[0],
+ (int) $end_time_parts[1],
+ (int) $end_time_parts[2]
+ );
+
+ //Get the course colour:
+ $course_membership = CourseMember::findOneBySQL(
+ 'seminar_id = :course_id AND user_id = :user_id',
+ [
+ 'course_id' => $cycle_date->seminar_id,
+ 'user_id' => $GLOBALS['user']->id
+ ]
+ );
+ $event_classes = [];
+ if ($course_membership) {
+ $event_classes[] = sprintf('course-color-%u', $course_membership->gruppe);
+ }
+
+ $event = new \Studip\Calendar\EventData(
+ $fake_begin,
+ $fake_end,
+ $cycle_date->course->getFullName(),
+ $event_classes,
+ '',
+ '',
+ false,
+ 'SeminarCycleDate',
+ $cycle_date->id,
+ '',
+ '',
+ 'course',
+ $cycle_date->seminar_id,
+ [
+ 'show' => $this->url_for('course/details', ['cid' => $cycle_date->seminar_id, 'link_to_course' => '1'])
+ ]
+ );
+
+ $result[] = $event->toFullcalendarEvent();
+ }
+
+ $this->render_json($result);
+ }
+}
diff --git a/app/controllers/settings/calendar.php b/app/controllers/settings/calendar.php
index 5704246..b2f4f1c 100644
--- a/app/controllers/settings/calendar.php
+++ b/app/controllers/settings/calendar.php
@@ -34,7 +34,7 @@ class Settings_CalendarController extends Settings_SettingsController
parent::before_filter($action, $args);
PageLayout::setHelpKeyword('Basis.MyStudIPTerminkalender');
- PageLayout::setTitle(_('Einstellungen des Terminkalenders anpassen'));
+ PageLayout::setTitle(_('Einstellungen des Kalenders anpassen'));
Navigation::activateItem('/profile/settings/calendar_new');
}
@@ -71,6 +71,11 @@ class Settings_CalendarController extends Settings_SettingsController
]);
PageLayout::postSuccess(_('Ihre Einstellungen wurden gespeichert'));
- $this->redirect('settings/calendar');
+ if (Request::isDialog()) {
+ $this->response->add_header('X-Dialog-Close', '1');
+ $this->render_nothing();
+ } else {
+ $this->redirect('settings/calendar');
+ }
}
}
diff --git a/app/views/calendar/calendar/add_courses.php b/app/views/calendar/calendar/add_courses.php
new file mode 100644
index 0000000..cf3d282
--- /dev/null
+++ b/app/views/calendar/calendar/add_courses.php
@@ -0,0 +1,15 @@
+<form class="default" method="post" action="<?= $controller->link_for('calendar/calendar/add_courses') ?>">
+ <?= CSRFProtection::tokenTag() ?>
+ <fieldset class="simplevue">
+ <legend><?= _('Veranstaltungen für den Kalender auswählen') ?></legend>
+ <my-courses-coloured-table name="courses"
+ :selected_course_ids="<?= htmlReady(json_encode($selected_course_ids)) ?>"
+ :default_semester_id="'<?= htmlReady($selected_semester_id) ?>'"
+ :semester_data="<?= htmlReady(json_encode($available_semester_data)) ?>"
+ ></my-courses-coloured-table>
+ </fieldset>
+ <div data-dialog-button>
+ <?= \Studip\Button::create(_('Übernehmen'), 'add') ?>
+ <?= \Studip\LinkButton::createCancel(_('Abbrechen'), $controller->url_for('calendar/calendar')) ?>
+ </div>
+</form>
diff --git a/app/views/calendar/calendar/course.php b/app/views/calendar/calendar/course.php
new file mode 100644
index 0000000..23602db
--- /dev/null
+++ b/app/views/calendar/calendar/course.php
@@ -0,0 +1 @@
+<?= $fullcalendar ?>
diff --git a/app/views/calendar/calendar/export.php b/app/views/calendar/calendar/export.php
new file mode 100644
index 0000000..3fa302e
--- /dev/null
+++ b/app/views/calendar/calendar/export.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * @var Calendar_CalendarController $controller
+ * @var string $user_id
+ * @var string $dates_to_export
+ * @var DateTimeImmutable $begin
+ * @var DateTimeImmutable $end
+ */
+?>
+<form class="default" method="post"
+ action="<?= $controller->link_for('calendar/calendar/export/' . $user_id) ?>">
+ <?= CSRFProtection::tokenTag() ?>
+ <fieldset>
+ <legend><?= _('Termine exportieren') ?></legend>
+ <label>
+ <?= _('Zu exportierende Termine') ?>
+ <select name="dates_to_export">
+ <option value="user"
+ <?= $dates_to_export === 'user' ? 'selected' : '' ?>>
+ <?= _('Persönliche Termine') ?>
+ </option>
+ <option value="course"
+ <?= $dates_to_export === 'course' ? 'selected' : '' ?>>
+ <?= _('Veranstaltungstermine') ?>
+ </option>
+ <option value="all"
+ <?= $dates_to_export === 'all' ? 'selected' : '' ?>>
+ <?= _('Alle Termine') ?>
+ </option>
+ </select>
+ </label>
+ <label>
+ <?= _('Startdatum') ?>
+ <input type="text" value="<?= htmlReady($begin->format('d.m.Y')) ?>" name="begin" data-date-picker>
+ </label>
+ <label>
+ <?= _('Enddatum') ?>
+ <input type="text" value="<?= htmlReady($end->format('d.m.Y')) ?>" name="end" data-date-picker>
+ </label>
+ </fieldset>
+ <div data-dialog-button>
+ <?= \Studip\Button::create(_('Exportieren'), 'export') ?>
+ <?= \Studip\Button::createCancel(_('Abbrechen')) ?>
+ </div>
+</form>
diff --git a/app/views/calendar/single/import.php b/app/views/calendar/calendar/import.php
index db1feb3..d91025a 100644
--- a/app/views/calendar/single/import.php
+++ b/app/views/calendar/calendar/import.php
@@ -1,27 +1,30 @@
-<?
-use Studip\Button, Studip\LinkButton;
+<?php
+/**
+ * @var Calendar_CalendarController $controller
+ */
?>
-<form action="<?= $controller->link_for('calendar/single/import/' . $calendar->getRangeId(), ['atime' => $atime, 'last_view' => $last_view]) ?>" method="post" enctype="multipart/form-data" class="default">
+<form class="default"
+ method="post"
+ data-dialog="size=auto"
+ enctype="multipart/form-data"
+ action="<?= $controller->link_for('calendar/calendar/import_file/') ?>">
<input type="hidden" name="studip_ticket" value="<?= get_ticket() ?>">
<?= CSRFProtection::tokenTag() ?>
<fieldset>
<legend>
<?= sprintf(_('Termine importieren')) ?>
</legend>
-
<label for="event-type">
<input type="checkbox" name="import_privat" value="1" checked>
<?= _('Öffentliche Termine als "privat" importieren') ?>
</label>
-
- <label class="file-upload">
+ <label>
<span class="required"><?= _('Datei zum Importieren wählen') ?></span>
<input required type="file" name="importfile" accept=".ics,.ifb,.iCal,.iFBf">
</label>
</fieldset>
-
<footer data-dialog-button>
- <?= Button::createAccept(_('Termine importieren'), 'import') ?>
- <?= LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/single/' . $last_view)) ?>
+ <?= \Studip\Button::create(_('Importieren'), 'import') ?>
+ <?= \Studip\Button::createCancel(_('Abbrechen')) ?>
</footer>
</form>
diff --git a/app/views/calendar/calendar/index.php b/app/views/calendar/calendar/index.php
new file mode 100644
index 0000000..23602db
--- /dev/null
+++ b/app/views/calendar/calendar/index.php
@@ -0,0 +1 @@
+<?= $fullcalendar ?>
diff --git a/app/views/calendar/single/share.php b/app/views/calendar/calendar/publish.php
index eef5ca3..71901b5 100644
--- a/app/views/calendar/single/share.php
+++ b/app/views/calendar/calendar/publish.php
@@ -1,10 +1,5 @@
<? use Studip\Button, Studip\LinkButton; ?>
-<? if (Request::isXhr()) : ?>
- <? foreach (PageLayout::getMessages() as $messagebox) : ?>
- <?= $messagebox ?>
- <? endforeach ?>
-<? endif; ?>
-<form data-dialog="size=auto" action="<?= $controller->url_for('calendar/single/share/' . $calendar->getRangeId()) ?>" method="post" class="default">
+<form data-dialog="size=auto" action="<?= $controller->link_for('calendar/calendar/publish/') ?>" method="post" class="default">
<input type="hidden" name="studip_ticket" value="<?= get_ticket() ?>">
<?= CSRFProtection::tokenTag() ?>
@@ -54,7 +49,7 @@
<? endif ?>
<? if (!Request::isXhr()) : ?>
- <?= LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/single/' . $last_view)) ?>
+ <?= LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/calendar')) ?>
<? endif; ?>
</footer>
</form>
diff --git a/app/views/calendar/calendar/share.php b/app/views/calendar/calendar/share.php
new file mode 100644
index 0000000..69663d2
--- /dev/null
+++ b/app/views/calendar/calendar/share.php
@@ -0,0 +1,13 @@
+<form class="default" method="post"
+ action="<?= $controller->link_for('calendar/calendar/share') ?>"
+ data-dialog="reload-on-close">
+ <?= CSRFProtection::tokenTag() ?>
+ <fieldset class="simplevue">
+ <calendar-permissions-table name="calendar"
+ :selected_users="<?= htmlReady($selected_users_json ?? '{}') ?>"
+ searchtype="<?= htmlReady((string) $searchtype) ?>"></calendar-permissions-table>
+ </fieldset>
+ <div data-dialog-button>
+ <?= \Studip\Button::create(_('Aktualisieren'), 'share') ?>
+ </div>
+</form>
diff --git a/app/views/calendar/contentbox/_termin.php b/app/views/calendar/contentbox/_termin.php
index 3c3fcff..ded7d0b 100644
--- a/app/views/calendar/contentbox/_termin.php
+++ b/app/views/calendar/contentbox/_termin.php
@@ -1,53 +1,88 @@
-<article class="studip toggle <?= ContentBoxHelper::classes($termin['id']) ?>" id="<?= $termin['id'] ?>">
+<article class="studip toggle <?= ContentBoxHelper::classes($termin->getObjectId()) ?>"
+ id="<?= htmlReady($termin->getObjectId()) ?>">
<header>
<h1>
- <a href="<?= ContentBoxHelper::href($termin['id']) ?>">
- <?= Icon::create('date', 'inactive')->asImg(['class' => 'text-bottom']) ?>
- <?= htmlReady($termin['title']) ?>
+ <a href="<?= ContentBoxHelper::href($termin->getObjectId()) ?>">
+ <?= Icon::create('date', Icon::ROLE_INACTIVE)->asImg(['class' => 'text-bottom']) ?>
+ <?= htmlReady($titles[$termin->getObjectId()] ?? $termin->getTitle()) ?>
</a>
</h1>
<nav>
<span>
- <?= $termin['room'] ? _('Raum') . ': ' . formatLinks($termin['room']) : '' ?>
+ <?= $termin->getLocation() ? _('Raum') . ': ' . formatLinks($termin->getLocation()) : '' ?>
</span>
- <? if($admin && $isProfile && $termin['type'] === 'CalendarEvent'): ?>
- <a href="<?= URLHelper::getLink('dispatch.php/calendar/single/edit/' . $termin['range_id'] . '/' . $termin['event_id'], ['source_page' => 'dispatch.php/profile']) ?>">
- <?= Icon::create('edit', 'clickable')->asImg(['class' => 'text-bottom']) ?>
- </a>
- <? endif; ?>
+ <? if ($admin && $isProfile && $termin->getObjectClass() === 'CalendarDateAssignment') : ?>
+ <a href="<?= URLHelper::getLink('dispatch.php/calendar/calendar') ?>"
+ title="<?= _('Zum Kalender') ?>">
+ <?= Icon::create('schedule')->asImg(['class' => 'text-bottom']) ?>
+ </a>
+ <? if ($termin->calendar_date->isWritable($GLOBALS['user']->id)) : ?>
+ <a href="<?= URLHelper::getLink('dispatch.php/calendar/date/edit/' . $termin->getPrimaryObjectId()) ?>"
+ title="<?= _('Termin bearbeiten') ?>"
+ data-dialog>
+ <?= Icon::create('edit')->asImg(['class' => 'text-bottom']) ?>
+ </a>
+ <? endif ?>
+ <? elseif (!$course_range && in_array($termin->getObjectClass(), ['CourseDate', 'CourseExDate'])) : ?>
+ <a href="<?= URLHelper::getLink('dispatch.php/course/dates', ['cid' => $termin->getPrimaryObjectId()]) ?>"
+ title="<?= _('Zur Veranstaltung') ?>">
+ <?= Icon::create('seminar')->asImg(['class'=> 'text-bottom']) ?>
+ </a>
+ <? endif ?>
</nav>
</header>
<div>
- <? $themen = $termin['topics'] ?? [] ?>
- <? if ($termin['description'] || count($themen)) : ?>
- <p><?= formatReady($termin['description']) ?></p>
- <? if (count($themen)) : ?>
- <? foreach ($themen as $thema) : ?>
- <h3>
- <?= Icon::create('topic', Icon::ROLE_INFO)->asImg(20, ['class' => "text-bottom"]) ?>
- <?= htmlReady($thema['title']) ?>
- </h3>
- <div>
- <?= formatReady($thema['description']) ?>
- </div>
- <? endforeach ?>
- <? endif ?>
+ <?
+ $themen = [];
+ if ($termin instanceof CourseDate) {
+ $themen = $termin->topics->toArray('title description');
+ }
+ $description = '';
+ if ($termin instanceof CourseExDate) {
+ $description = $termin->content;
+ } elseif ($termin instanceof CourseDate && isset($termin->cycle)) {
+ $description = $termin->cycle->description;
+ } else {
+ $description = $termin->getDescription();
+ }
+ ?>
+ <? if ($description || count($themen) > 0) : ?>
+ <p><?= formatReady($description) ?></p>
+ <? if (count($themen)) : ?>
+ <? foreach ($themen as $thema) : ?>
+ <h3>
+ <?= Icon::create('topic', Icon::ROLE_INFO)->asImg(20, ['class' => "text-bottom"]) ?>
+ <?= htmlReady($thema['title']) ?>
+ </h3>
+ <div>
+ <?= formatReady($thema['description']) ?>
+ </div>
+ <? endforeach ?>
+ <? endif ?>
<? else : ?>
<?= _('Keine Beschreibung vorhanden') ?>
<? endif ?>
<ul class="list-csv" style="text-align: center;">
- <? foreach($termin['info'] as $type => $info): ?>
- <? if (trim($info)) : ?>
- <li>
- <small>
- <? if (!is_numeric($type)): ?>
- <em><?= htmlReady($type) ?>:</em>
- <? endif; ?>
- <?= htmlReady(trim($info)) ?>
- </small>
- </li>
- <? endif ?>
- <? endforeach; ?>
+ <? foreach ($termin->getAdditionalDescriptions() as $type => $info) : ?>
+ <? if (trim($info)) : ?>
+ <li>
+ <small>
+ <? if (!is_numeric($type)): ?>
+ <em><?= htmlReady($type) ?>:</em>
+ <? endif; ?>
+ <?= htmlReady(trim($info)) ?>
+ </small>
+ </li>
+ <? endif ?>
+ <? endforeach ?>
</ul>
+ <? if (!$course_range && in_array($termin->getObjectClass(), [CourseDate::class, CourseExDate::class])) : ?>
+ <div>
+ <a href="<?= URLHelper::getLink('dispatch.php/course/dates', ['cid' => $termin->getPrimaryObjectId()]) ?>">
+ <?= Icon::create('link-intern')->asImg(['class'=> 'text-bottom']) ?>
+ <?= _('Zur Veranstaltung') ?>
+ </a>
+ </div>
+ <? endif ?>
</div>
</article>
diff --git a/app/views/calendar/contentbox/display.php b/app/views/calendar/contentbox/display.php
index 41498f4..c0387fd 100644
--- a/app/views/calendar/contentbox/display.php
+++ b/app/views/calendar/contentbox/display.php
@@ -1,36 +1,35 @@
-<? if ($admin || $termine): ?>
-<article class="studip">
- <header>
- <h1>
- <?= Icon::create('schedule', 'info')->asImg() ?>
- <?= htmlReady($title) ?>
- </h1>
- <nav>
- <? if ($admin): ?>
- <? if ($isProfile): ?>
- <a href="<?= URLHelper::getLink('dispatch.php/calendar/single/edit/', ['source_page' => 'dispatch.php/profile']) ?>">
- <?= Icon::create('add', 'clickable')->asImg(['class' => 'text-bottom']) ?>
- </a>
+<? if ($admin || $termine) : ?>
+ <article class="studip">
+ <header>
+ <h1>
+ <?= Icon::create('schedule', 'info')->asImg() ?>
+ <?= htmlReady($title) ?>
+ </h1>
+ <nav>
+ <? if ($admin) : ?>
+ <? if ($isProfile) : ?>
+ <a href="<?= URLHelper::getLink('dispatch.php/calendar/date/add') ?>"
+ data-dialog="reload-on-close"
+ title="<?= _('Neuen Termin anlegen') ?>">
+ <?= Icon::create('add', 'clickable')->asImg(['class' => 'text-bottom']) ?>
+ </a>
+ <? else: ?>
+ <a href="<?= URLHelper::getLink("dispatch.php/course/timesrooms", ['cid' => $range_id]) ?>"
+ title="<?= _('Neuen Termin anlegen') ?>">
+ <?= Icon::create('admin', 'clickable')->asImg(['class' => 'text-bottom']) ?>
+ </a>
+ <? endif ?>
+ <? endif ?>
+ </nav>
+ </header>
+ <? if ($termine) : ?>
+ <? foreach ($termine as $termin) : ?>
+ <?= $this->render_partial('calendar/contentbox/_termin.php', ['termin' => $termin, 'course_range' => $course_range]) ?>
+ <? endforeach ?>
<? else: ?>
- <a href="<?= URLHelper::getLink("dispatch.php/course/timesrooms", ['cid' => $range_id]) ?>">
- <?= Icon::create('admin', 'clickable')->asImg(['class' => 'text-bottom']) ?>
- </a>
- <? endif; ?>
- <? endif; ?>
- </nav>
- </header>
- <? if ($termine): ?>
- <? foreach ($termine as $termin): ?>
- <?= $this->render_partial('calendar/contentbox/_termin.php', ['termin' => $termin]); ?>
- <? endforeach; ?>
-<? else: ?>
- <section>
- <? if ($isProfile): ?>
- <?= _('Es sind keine aktuellen Termine vorhanden. Um neue Termine zu erstellen, klicken Sie rechts auf das Plus.') ?>
- <? else: ?>
- <?= _('Es sind keine aktuellen Termine vorhanden. Um neue Termine zu erstellen, klicken Sie rechts auf die Zahnräder.') ?>
- <? endif; ?>
- </section>
- <? endif; ?>
-</article>
-<? endif; ?>
+ <section>
+ <?= _('Es sind keine aktuellen Termine vorhanden. Zum Anlegen neuer Termine können Sie die Aktion „Neuen Termin anlegen“ benutzen.') ?>
+ </section>
+ <? endif ?>
+ </article>
+<? endif ?>
diff --git a/app/views/calendar/date/_add_edit_form.php b/app/views/calendar/date/_add_edit_form.php
new file mode 100644
index 0000000..7c922e4
--- /dev/null
+++ b/app/views/calendar/date/_add_edit_form.php
@@ -0,0 +1,154 @@
+<form class="default new-calendar-date-form" method="post" action="<?= $form_post_link ?>"
+ data-dialog="reload-on-close">
+ <?= CSRFProtection::tokenTag() ?>
+
+ <? if ($return_path = Request::get('return_path')) : ?>
+ <input type="hidden" name="return_path" value="<?= htmlReady($return_path) ?>">
+ <? endif ?>
+
+ <? if ($user_id) : ?>
+ <input type="hidden" name="user_id" value="<?= htmlReady($user_id) ?>">
+ <? endif ?>
+ <? if ($group_id) : ?>
+ <input type="hidden" name="group_id" value="<?= htmlReady($group_id) ?>">
+ <? endif ?>
+
+ <article aria-live="assertive"
+ class="validation_notes studip">
+ <header>
+ <h1>
+ <?= Icon::create('info-circle', Icon::ROLE_INFO)->asImg(['class' => 'text-bottom validation_notes_icon']) ?>
+ <?= _('Hinweise zum Ausfüllen des Formulars') ?>
+ </h1>
+ </header>
+ <div class="required_note">
+ <div aria-hidden="true">
+ <?= _('Pflichtfelder sind mit Sternchen gekennzeichnet.') ?>
+ </div>
+ <div class="sr-only">
+ <?= _('Dieses Formular enthält Pflichtfelder.') ?>
+ </div>
+ </div>
+ <? if ($form_errors) : ?>
+ <div>
+ <?= _('Folgende Angaben müssen korrigiert werden, um das Formular abschicken zu können:') ?>
+ <ul>
+ <? foreach ($form_errors as $field => $error) : ?>
+ <li><?= htmlReady($field) ?>: <?= htmlReady($error) ?></li>
+ <? endforeach ?>
+ </ul>
+ </div>
+ <? endif ?>
+ </article>
+
+ <fieldset>
+ <legend><?= _('Grunddaten') ?></legend>
+ <label class="studiprequired">
+ <?= _('Titel') ?>
+ <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span>
+ <input type="text" name="title" required="required"
+ value="<?= htmlReady($date->title) ?>">
+ </label>
+ <div class="hgroup">
+ <label class="studiprequired">
+ <?= _('Beginn') ?>
+ <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span>
+ <input type="text" name="begin" class="begin-input" data-datetime-picker
+ required="required" value="<?= date('d.m.Y H:i', $date->begin) ?>">
+ </label>
+ <label class="studiprequired">
+ <?= _('Ende') ?>
+ <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span>
+ <input type="text" name="end" class="end-input" data-datetime-picker
+ required="required" value="<?= date('d.m.Y H:i', $date->end) ?>">
+ </label>
+ </div>
+ <label>
+ <input type="checkbox" name="all_day" value="1" <?= $all_day_event ? 'checked' : '' ?>
+ data-deactivates=".new-calendar-date-form input[name='end']">
+ <?= _('Ganztägiger Termin') ?>
+ </label>
+ <label>
+ <?= _('Zugriff') ?>
+ <div class="flex-row">
+ <select name="access">
+ <option value="PUBLIC" <?= $date->access === 'PUBLIC' ? 'selected' : '' ?>>
+ <?= _('Öffentlich zugänglich') ?>
+ </option>
+ <option value="PRIVATE" <?= $date->access === 'PRIVATE' ? 'selected' : '' ?>>
+ <?= _('Privat') ?>
+ </option>
+ <option value="CONFIDENTIAL" <?= $date->access === 'CONFIDENTIAL' ? 'selected' : '' ?>>
+ <?= _('Vertraulich') ?>
+ </option>
+ </select>
+ <?= tooltipIcon(
+ _('Öffentliche Termine sind systemweit sichtbar. Private Termine sind für Personen, denen der Kalender freigegeben wurde, sichtbar. Vertrauliche Termine sind hingegen nur für einen selbst sichtbar.')
+ ) ?>
+ </div>
+ </label>
+ <label>
+ <?= _('Beschreibung') ?>
+ <textarea name="description"><?= htmlReady($date->description) ?></textarea>
+ </label>
+ <label class="studiprequired">
+ <?= _('Kategorie') ?>
+ <span class="asterisk" title="<?= _('Dies ist ein Pflichtfeld') ?>" aria-hidden="true">*</span>
+ <select class="" name="category" required>
+ <? foreach ($category_options as $key => $option) : ?>
+ <option value="<?= htmlReady($key) ?>" <?= $key === intval($date->category) ? 'selected' : '' ?>>
+ <?= htmlReady($option) ?>
+ </option>
+ <? endforeach ?>
+ </select>
+ </label>
+ <label>
+ <?= _('Eigene Kategorie') ?>
+ <input type="text" name="user_category" value="<?= htmlReady($date->user_category) ?>">
+ </label>
+ <label>
+ <?= _('Ort') ?>
+ <input type="text" name="location" value="<?= htmlReady($date->location) ?>">
+ </label>
+ </fieldset>
+ <fieldset class="simplevue">
+ <legend><?= _('Wiederholung') ?></legend>
+ <?= $date->getRepetitionInputHtml('repetition') ?>
+ </fieldset>
+ <fieldset class="simplevue">
+ <legend><?= _('Ausnahmen') ?></legend>
+ <date-list-input name="exceptions" :selected_dates="<?= htmlReady(json_encode($exceptions)) ?>"></date-list-input>
+ </fieldset>
+ <? if (Config::get()->CALENDAR_GROUP_ENABLE && $user_quick_search_type) : ?>
+ <fieldset class="simplevue">
+ <legend><?= _('Teilnehmende Personen') ?></legend>
+ <editable-list
+ name="assigned_calendar_ids"
+ quicksearch="<?= htmlReady($user_quick_search_type) ?>"
+ :items="<?= htmlReady(json_encode($calendar_assignment_items)) ?>"
+ ></editable-list>
+ </fieldset>
+ <? elseif ($calendar_assignment_items) : ?>
+ <? foreach ($calendar_assignment_items as $item) : ?>
+ <input type="hidden" name="assigned_calendar_ids[]" value="<?= htmlReady($item['value']) ?>">
+ <? endforeach ?>
+ <? elseif ($owner_id): ?>
+ <input type="hidden" name="assigned_calendar_ids[]" value="<?= htmlReady($owner_id) ?>">
+ <? endif ?>
+
+ <footer data-dialog-button>
+ <? if ($date->isNew()) : ?>
+ <?= \Studip\Button::create(_('Anlegen'), 'save') ?>
+ <? else : ?>
+ <?= \Studip\Button::create(_('Speichern'), 'save') ?>
+ <? endif ?>
+ <? if (!$date->isNew()) : ?>
+ <?= \Studip\LinkButton::create(
+ _('Löschen'),
+ $controller->url_for('calendar/date/delete/' . $date->id),
+ ['data-dialog' => 'reload-on-close']
+ ) ?>
+ <? endif ?>
+ <?= \Studip\LinkButton::createCancel(_('Abbrechen'), $controller->url_for('calendar/calendar')) ?>
+ </footer>
+</form>
diff --git a/app/views/calendar/date/add.php b/app/views/calendar/date/add.php
new file mode 100644
index 0000000..29c94c2
--- /dev/null
+++ b/app/views/calendar/date/add.php
@@ -0,0 +1,4 @@
+<?= $this->render_partial('calendar/date/_add_edit_form', [
+ 'action_link' => $controller->link_for('calendar/date/add'),
+ 'event' => null
+]) ?>
diff --git a/app/views/calendar/date/delete.php b/app/views/calendar/date/delete.php
new file mode 100644
index 0000000..ccb9829
--- /dev/null
+++ b/app/views/calendar/date/delete.php
@@ -0,0 +1,51 @@
+<form class="default" method="post" data-dialog="reload-on-close"
+ action="<?= $controller->link_for('calendar/date/delete/' . $date->id) ?>">
+ <?= CSRFProtection::tokenTag() ?>
+ <? if ($date_has_repetitions) : ?>
+ <input type="hidden" name="selected_date" value="<?= $selected_date->format('Y-m-d') ?>">
+ <fieldset>
+ <legend><?= _('Es handelt sich um einen Termin in einer Terminserie. Was möchten Sie tun?') ?></legend>
+ <label>
+ <input type="radio" name="repetition_handling" value="create_exception"
+ <?= $repetition_handling === 'create_exception' ? 'checked' : '' ?>>
+ <?= sprintf(
+ _('Am %s soll aus dem Einzeltermin eine Ausnahme der Terminserie werden.'),
+ $selected_date->format('d.m.Y')
+ ) ?>
+ </label>
+ <label>
+ <input type="radio" name="repetition_handling" value="delete_all"
+ <?= $repetition_handling === 'delete_all' ? 'checked' : '' ?>>
+ <?= _('Die gesamte Terminserie soll gelöscht werden.') ?>
+ </label>
+ </fieldset>
+ <? else : ?>
+ <?= MessageBox::warning(_('Soll der folgende Termin wirklich gelöscht werden?')) ?>
+ <? endif ?>
+ <fieldset>
+ <legend><?= _('Informationen') ?></legend>
+ <dl>
+ <dt><?= _('Zeitbereich') ?></dt>
+ <dd>
+ <?= htmlReady(date('d.m.Y H:i', $date->begin)) ?>
+ -
+ <?= htmlReady(date('d.m.Y H:i', $date->end)) ?>
+ </dd>
+ <dt><?= _('Titel') ?></dt>
+ <dd><?= htmlReady($date->title) ?></dd>
+ <? if ($date->description) : ?>
+ <dt><?= _('Beschreibung') ?></dt>
+ <dd><?= htmlReady($date->description) ?></dd>
+ <? endif ?>
+ <dt><?= _('Zugriff') ?></dt>
+ <dd><?= htmlReady($date->getAccessAsString()) ?></dd>
+ <? if ($date->repetition_type) : ?>
+ <dt><?= _('Wiederholung') ?></dt>
+ <dd><?= htmlReady($date->getRepetitionAsString()) ?></dd>
+ <? endif ?>
+ </dl>
+ </fieldset>
+ <div data-dialog-button>
+ <?= \Studip\Button::create(_('Löschen'), 'delete') ?>
+ </div>
+</form>
diff --git a/app/views/calendar/date/edit.php b/app/views/calendar/date/edit.php
new file mode 100644
index 0000000..6090dfc
--- /dev/null
+++ b/app/views/calendar/date/edit.php
@@ -0,0 +1,4 @@
+<?= $this->render_partial('calendar/date/_add_edit_form', [
+ 'action_link' => $controller->link_for('calendar/date/edit/' . $date->id),
+ 'date' => $date
+]) ?>
diff --git a/app/views/calendar/date/index.php b/app/views/calendar/date/index.php
new file mode 100644
index 0000000..faec7c7
--- /dev/null
+++ b/app/views/calendar/date/index.php
@@ -0,0 +1,135 @@
+<? if ($participation_message) : ?>
+ <?= $participation_message ?>
+ <article class="studip">
+ <header><h1><?= _('Neuen Teilnahmestatus wählen') ?></h1></header>
+ <section>
+ <form class="default" method="post" data-dialog="reload-on-close"
+ action="<?= $controller->link_for('calendar/date/participation/' . $date->id) ?>">
+ <?= CSRFProtection::tokenTag() ?>
+ <fieldset>
+ <? if ($user_participation_status) : ?>
+ <label>
+ <input type="radio" name="participation" value=""
+ data-activates="button[name='update_participation']">
+ <?= _('Abwartend') ?>
+ </label>
+ <? endif ?>
+ <? if ($user_participation_status !== 'ACCEPTED') : ?>
+ <label>
+ <input type="radio" name="participation" value="ACCEPTED"
+ data-activates="button[name='update_participation']">
+ <?= _('Angenommen') ?>
+ </label>
+ <? endif ?>
+ <? if ($user_participation_status !== 'DECLINED') : ?>
+ <label>
+ <input type="radio" name="participation" value="DECLINED"
+ data-activates="button[name='update_participation']">
+ <?= _('Abgelehnt') ?>
+ </label>
+ <? endif ?>
+ <? if ($user_participation_status !== 'ACKNOWLEDGED') : ?>
+ <label>
+ <input type="radio" name="participation" value="ACKNOWLEDGED"
+ data-activates="button[name='update_participation']">
+ <?= _('Angenommen (keine Teilnahme)') ?>
+ </label>
+ <? endif ?>
+ </fieldset>
+ <div data-dialog-button>
+ <?= \Studip\Button::create(_('Teilnahmestatus ändern'), 'update_participation') ?>
+ </div>
+ </form>
+ </section>
+ </article>
+<? endif ?>
+<? if ($date->description) : ?>
+ <article class="studip">
+ <header><h1><?= _('Beschreibung') ?></h1></header>
+ <section><?= htmlReady($date->description) ?></section>
+ </article>
+<? endif ?>
+<article class="studip">
+ <header><h1><?= _('Informationen') ?></h1></header>
+ <section>
+ <dl>
+ <dt><?= _('Zeit') ?></dt>
+ <dd><?= date('d.m.Y H:i', $date->begin) ?> - <?= date('d.m.Y H:i', $date->end) ?></dd>
+ <dt><?= _('Kategorie') ?></dt>
+ <dd><?= htmlReady($date->getCategoryAsString()) ?></dd>
+ <dt><?= _('Zugriff') ?></dt>
+ <dd><?= htmlReady($date->getAccessAsString()) ?></dd>
+ <? if ($date->repetition_type) : ?>
+ <dt><?= _('Wiederholung') ?></dt>
+ <dd><?= htmlReady($date->getRepetitionAsString()) ?></dd>
+ <? endif ?>
+ <? if (
+ $date->author && $date->editor
+ && (
+ ($date->author_id !== User::findCurrent()->id)
+ || ($date->editor_id !== User::findCurrent()->id)
+ )
+ ) : ?>
+ <dt><?= _('Bearbeitung') ?></dt>
+ <dd>
+ <? if ($date->author->id === $date->editor->id) : ?>
+ <? if ($date->mkdate === $date->chdate) : ?>
+ <?= sprintf(
+ _('Erstellt von %s'),
+ htmlReady($date->author->getFullName())
+ ) ?>
+ <? else : ?>
+ <?= sprintf(
+ _('Erstellt und zuletzt bearbeitet von %s'),
+ htmlReady($date->author->getFullName())
+ ) ?>
+ <? endif ?>
+ <? else : ?>
+ <?= sprintf(
+ _('Erstellt von %1$s, zuletzt bearbeitet von %2$s'),
+ htmlReady($date->author->getFullName()),
+ htmlReady($date->editor->getFullName())
+ ) ?>
+ <? endif ?>
+ </dd>
+ <? endif ?>
+ </dl>
+ </section>
+</article>
+<? if ($is_group_date) : ?>
+ <article class="studip">
+ <header><h1><?= _('Teilnahmen') ?></h1></header>
+ <section>
+ <table class="default">
+ <body>
+ <? foreach ($calendar_assignments as $assignment) : ?>
+ <tr>
+ <td><?= htmlReady($assignment->getRangeName()) ?></td>
+ <td><?= htmlReady($assignment->getParticipationAsString()) ?></td>
+ </tr>
+ <? endforeach ?>
+ </body>
+ </table>
+ </section>
+ </article>
+<? endif ?>
+<div data-dialog-button>
+ <? if ($date->isWritable(User::findCurrent()->id) && $all_assignments_writable) : ?>
+ <?
+ $button_params = [];
+ if ($selected_date) {
+ $button_params['selected_date'] = $selected_date;
+ }
+ ?>
+ <?= Studip\LinkButton::create(
+ _('Bearbeiten'),
+ $controller->url_for('calendar/date/edit/' . $date->id, array_merge($button_params, ['return_path' => '/calendar/calendar'])),
+ ['data-dialog' => 'size=auto;reload-on-close']
+ ) ?>
+ <?= \Studip\LinkButton::create(
+ _('Löschen'),
+ $controller->url_for('calendar/date/delete/' . $date->id, $button_params),
+ ['data-dialog' => 'reload-on-close']
+ ) ?>
+ <? endif ?>
+</div>
diff --git a/app/views/calendar/date/move.php b/app/views/calendar/date/move.php
new file mode 100644
index 0000000..3ea2b89
--- /dev/null
+++ b/app/views/calendar/date/move.php
@@ -0,0 +1,22 @@
+<?= MessageBox::info(_('Es handelt sich um einen Termin in einer Terminserie. Was möchten Sie tun?')) ?>
+<form class="default" method="post" data-dialog="reload-on-close"
+ action="<?= $controller->link_for('calendar/date/move/' . $date->id) ?>">
+ <?= CSRFProtection::tokenTag() ?>
+ <input type="hidden" name="begin" value="<?= htmlReady($begin->format(\DateTime::RFC3339)) ?>">
+ <input type="hidden" name="end" value="<?= htmlReady($end->format(\DateTime::RFC3339)) ?>">
+ <label>
+ <input type="radio" name="repetition_handling" value="create_single_date">
+ <?= _('Der Termin soll aus der Terminserie herausgelöst werden.') ?>
+ </label>
+ <label>
+ <input type="radio" name="repetition_handling" value="change_times">
+ <?= _('Start- und Enduhrzeit der gesamten Terminserie soll geändert werden.') ?>
+ </label>
+ <label>
+ <input type="radio" name="repetition_handling" value="change_all">
+ <?= _('Die gesamte Terminserie soll verschoben werden und erst am gewählten Datum beginnen.') ?>
+ </label>
+ <div data-dialog-button>
+ <?= \Studip\Button::create(_('Verschieben'), 'move') ?>
+ </div>
+</form>
diff --git a/app/views/calendar/group/_attendees.php b/app/views/calendar/group/_attendees.php
deleted file mode 100644
index 3fbe27e..0000000
--- a/app/views/calendar/group/_attendees.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<fieldset class="collapsed">
- <legend>
- <?= _('Teilnehmende/n hinzufügen') ?>
- </legend>
-
- <a class="toggler">
- <?
- if ($event->attendees->count()) {
- $count_attendees = $event->attendees->filter(
- function ($att, $k) use ($calendar) {
- if ($att->range_id != $calendar->getRangeId()) {
- return $att;
- }
- })->count();
- } else {
- $count_attendees = 0;
- }
- ?>
- <? if ($count_attendees) : ?>
- <? if ($count_attendees < $event->attendees->count()) : ?>
- <?= sprintf(ngettext('(%s weitere/r Teilnehmende/r)', '(%s weitere Teilnehmende)', $count_attendees), $count_attendees) ?>
- <? else : ?>
- <?= sprintf(_('(%s Teilnehmende)'), $count_attendees) ?>
- <? endif; ?>
- <? endif; ?>
- </a>
-
- <div>
- <label for="user_id_1"><h4><?= _('Teilnehmende') ?></h4></label>
- <ul class="clean" id="adressees">
- <li id="template_adressee" style="display: none;" class="adressee">
- <input type="hidden" name="attendees[]" value="">
- <span class="visual"></span>
- <a class="remove_adressee"><?= Icon::create('trash', 'clickable')->asImg(16, ['class' => 'text-bottom']) ?></a>
- </li>
- <? if ($event->isNew()) : ?>
- <li style="padding: 0px;" class="adressee">
- <input type="hidden" name="attendees[]" value="<?= $event->owner->id ?>">
- <span class="visual">
- <a href="<?= URLHelper::getLink('dispatch.php/profile', ['username' => $event->owner->username], true) ?>"><?= htmlReady($event->owner->getFullname()) ?></a>
- </span>
- <a class="remove_adressee"><?= Icon::create('trash', 'clickable')->asImg(16, ['class' => 'text-bottom']) ?></a>
- </li>
- <? endif; ?>
- <? $group_status = [
- CalendarEvent::PARTSTAT_TENTATIVE => _('Abwartend'),
- CalendarEvent::PARTSTAT_ACCEPTED => _('Angenommen'),
- CalendarEvent::PARTSTAT_DECLINED => _('Abgelehnt'),
- CalendarEvent::PARTSTAT_DELEGATED => _('Angenommen (keine Teilnahme)'),
- CalendarEvent::PARTSTAT_NEEDS_ACTION => ''] ?>
- <? foreach ($event->attendees as $attendee) : ?>
- <? if ($attendee->owner) : ?>
- <li style="padding: 0px;" class="adressee">
- <input type="hidden" name="attendees[]" value="<?= htmlReady($attendee->owner->id) ?>">
- <span class="visual">
- <a href="<?= URLHelper::getLink('dispatch.php/profile', ['username' => $attendee->owner->username], true) ?>"><?= htmlReady($attendee->owner->getFullname()) ?></a>
- <? if ($event->havePermission(Event::PERMISSION_OWN, $attendee->owner->id)) : ?>
- (<?= _('Organisator') ?>)
- <? elseif ($group_status[$attendee->group_status]) : ?>
- (<?= $group_status[$attendee->group_status] ?>)
- <? endif; ?>
- </span>
- <a class="remove_adressee"><?= Icon::create('trash', 'clickable', ['title' => _('Teilnehmende/n entfernen')])->asImg(16, ['class' => 'text-bottom']) ?></a>
- </li>
- <? endif; ?>
- <? endforeach ?>
- </ul>
-
- <section>
- <?= $quick_search->render() ?>
- <br clear="both">
- </section>
-
- <section>
- <?= $mps->render(); ?>
- </section>
-
- <script>
- STUDIP.MultiPersonSearch.init();
- </script>
- </div>
-</fieldset>
diff --git a/app/views/calendar/group/_tooltip.php b/app/views/calendar/group/_tooltip.php
deleted file mode 100644
index aa8a9b1..0000000
--- a/app/views/calendar/group/_tooltip.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<? $events = $events ?: $calendar->events ?>
-<? if (sizeof($events)) : ?>
-<div class="calendar-tooltip tooltip-content">
- <h3><?= htmlReady($calendar->range_object->getFullname('no_title')) ?></h3>
- <? foreach ($events as $event) : ?>
- <div>
- <? if (date('Ymd', $event->getStart()) == date('Ymd', $event->getEnd())) : ?>
- <? if ($event->isDayEvent()) : ?>
- <?= strftime('%x ', $event->getStart()) . '(' . _('ganztägig') . ')' ?>
- <? else : ?>
- <?= strftime('%x %X', $event->getStart()) . strftime(' - %X', $event->getEnd()) ?>
- <? endif; ?>
- <? else : ?>
- <? if ($event->isDayEvent()) : ?>
- <?= strftime('%x', $event->getStart()) . strftime(' - %x', $event->getEnd()) . '(' . _('ganztägig') . ')' ?>
- <? else : ?>
- <?= strftime('%x %X', $event->getStart()) . strftime(' - %x %X', $event->getEnd()) ?>
- <? endif; ?>
- <? endif; ?>
- </div>
- <div>
- <?= htmlReady($event->getTitle()) ?>
- </div>
- <hr>
- <? endforeach; ?>
-</div>
-<? endif; ?>
diff --git a/app/views/calendar/group/_tooltip_year.php b/app/views/calendar/group/_tooltip_year.php
deleted file mode 100644
index edd81ca..0000000
--- a/app/views/calendar/group/_tooltip_year.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<? $i = 0 ?>
-<? $html = '' ?>
-<? $list_day = date('Ymd', $aday) ?>
-<? foreach ($calendars as $calendar) : ?>
- <? if ($count_lists[$i][$list_day]) : ?>
- <?
- $html .= '<div>'
- . sprintf(ngettext('%s hat 1 Termin', '%s hat %s Termine',
- count($count_lists[$i][$list_day])),
- $calendar->range_object->getFullname('no_title'),
- count($count_lists[$i][$list_day]))
- . '</div>';
- ?>
- <? endif; ?>
- <? $i++ ?>
-<? endforeach; ?>
-<? if ($html) : ?>
-<div class="calendar-tooltip tooltip-content">
- <?= $html ?>
-</div>
-<? endif; ?>
diff --git a/app/views/calendar/group/day.php b/app/views/calendar/group/day.php
deleted file mode 100644
index a452320..0000000
--- a/app/views/calendar/group/day.php
+++ /dev/null
@@ -1,128 +0,0 @@
-<?
-$step_day = $settings['step_day_group'];
-$step = 3600 / $step_day;
-// add one cell for day events
-$cells = (($settings['end'] - $settings['start']) / (1 / $step)) + 2;
-$width1 = floor(90 / $cells);
-$width2 = 10 + (90 - $width1 * $cells);
-$start = $settings['start'] * 3600;
-$end = ($settings['end'] + 1) * 3600;
-?>
-<table id="main_content" style="width:100%; table-layout:fixed;">
- <thead>
- <tr>
- <td style="text-align: center; width: 10%; height: 40px;">
- <div style="text-align: left; width: 20%; display: inline-block; white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/group/day', ['atime' => $atime - 86400]) ?>">
- <?= Icon::create('arr_1left', 'clickable', ['title' => _('Einen Tag zurück')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- <?= strftime(_('%x'), strtotime('-1 day', $calendars[0]->getStart())) ?>
- </a>
- </div>
- <div class="calhead" style="width: 50%; display: inline-block;">
- <?= strftime('%A, %e. %B %Y', $atime) ?>
- <div style="text-align: center; font-size: 12pt; color: #bbb; height: auto; overflow: visible; font-weight: bold;"><? $hd = holiday($atime); echo $holiday['name']; ?></div>
- </div>
- <div style="text-align: right; width: 20%; display: inline-block; white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/group/day', ['atime' => $atime + 86400]) ?>">
- <?= strftime(_('%x'), strtotime('+1 day', $calendars[0]->getStart())) ?>
- <?= Icon::create('arr_1right', 'clickable', ['title' => _('Einen Tag vor')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- </a>
- </div>
- </td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>
- <div style="overflow:auto; width:100%;">
- <table style="width: 100%;">
- <? $time = mktime(0, 0, 0, date('n', $atime), date('j', $atime), date('Y', $atime)); ?>
- <? if ($step_day < 3600) : $colsp = ' colspan="' . $step . '"'; endif ?>
- <tr>
- <td class="precol1w" nowrap="nowrap" style="text-align: center; width: <?= $width2 ?>%;">
- <?= _('Mitglied') ?>
- </td>
- <td class="precol1w" style="text-align: center; width: <?= $width1 ?>%">
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Tagestermin am %x'), $calendars[0]->getStart()) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $calendars[0]->getStart(), 'isdayevent' => '1']) ?>">
- <?= Icon::create('schedule', 'clickable')->asImg() ?>
- </a>
- </td>
- <? for ($i = $time + $start; $i < $time + $end; $i += $step_day) : ?>
- <? if (!($i % 3600)) : ?>
- <td<?= $colsp ?> class="precol1w" style="text-align: center;">
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Termin um %R Uhr'), $i) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $i]) ?>" class="calhead">
- <?= date('G', $i) ?>
- </a>
- </td>
- <? endif ?>
- <? endfor ?>
- </tr>
- <? foreach ($calendars as $calendar) : ?>
- <? $adapted = $calendar->adapt_events($start, $end, $settings['step_day_group']); ?>
- <tr>
- <td style="width: <?= $width2 ?>%; white-space: nowrap;" class="month">
- <a class="calhead" href="<?= $controller->url_for('calendar/single/day/' . $calendar->getRangeId(), ['atime' => $atime,]); ?>">
- <?= htmlReady(($calendar->havePermission(Calendar::PERMISSION_OWN) ? _('Eigener Kalender') : get_fullname($calendar->getRangeId(), 'no_title_short'))) ?>
- </a>
- </td>
- <? // display day events
- $js_events = []; ?>
- <? for ($i = 0; $i < sizeof($adapted['day_events']); $i++) :
- $js_events[] = $calendar->events[$adapted['day_map'][$i]];
- endfor; ?>
- <? if ($calendar->havePermission(Calendar::PERMISSION_WRITABLE)) : ?>
- <td <?= sizeof($js_events) ? 'data-tooltip ' : '' ?>style="width: <?= $width1 ?>%; text-align: right;" class="calendar-day-edit<?= sizeof($js_events) ? ' calendar-group-events' : ' lightmonth' ?>">
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $calendar, 'events' => $js_events]) ?>
- <a title="<?= strftime(_('Neuer Tagestermin am %x'), $calendar->getStart()) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $calendar->getStart(), 'isdayevent' => '1', 'user_id' => $calendar->getRangeId()]) ?>">+</a>
- <? else : ?>
- <td <?= sizeof($js_events) ? 'data-tooltip ' : '' ?>style="width: <?= $width1 ?>%;" class="<?= sizeof($js_events) ? 'calendar-group-events' : 'lightmonth' ?>">
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $calendar, 'events' => $js_events]) ?>
- <? endif; ?>
- </td>
- <? $k = 0;
- for ($i = $start + $calendar->getStart(); $i < $end + $calendar->getStart(); $i += $step_day) :
- if (!($i % 3600)) {
- $k++;
- }
- $js_events = [];
- ?>
- <? for ($j = 0; $j < sizeof($adapted['events']); $j++) : ?>
- <? if (($adapted['events'][$j]->getStart() <= $i && $adapted['events'][$j]->getEnd() > $i) || ($adapted['events'][$j]->getStart() > $i && $adapted['events'][$j]->getStart() < $i + $step_day)) :
- $js_events[] = $calendar->events[$adapted['map'][$j]];
- endif; ?>
- <? endfor; ?>
- <? if ($calendar->havePermission(Calendar::PERMISSION_WRITABLE)) : ?>
- <td <?= sizeof($js_events) ? 'data-tooltip ' : '' ?>style="width: <?= $width1 ?>%; text-align: right;" class="calendar-day-edit<?= sizeof($js_events) ? ' calendar-group-events' : ' lightmonth' ?>">
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $calendar, 'events' => $js_events]) ?>
- <a title="<?= strftime(_('Neuer Termin um %R Uhr'), $i) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $i, 'user_id' => $calendar->getRangeId()]) ?>">+</a>
- <? else : ?>
- <td <?= sizeof($js_events) ? 'data-tooltip ' : '' ?> style="width: <?= $width1 ?>%; text-align: right;" class="<?= sizeof($js_events) ? 'calendar-group-events' : 'lightmonth' ?>">
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $calendar, 'events' => $js_events]) ?>
- <? endif; ?>
- </td>
- <? endfor ?>
- </tr>
- <? endforeach; ?>
- <tr>
- <td style="width: <?= $width2 ?>%;" class="precol1w"> </td>
- <td width="<?= $width1 ?>%" class="precol1w" style="text-align: center;">
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Tagestermin am %x'), $calendars[0]->getStart()) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $calendars[0]->getStart(), 'isdayevent' => '1']) ?>">
- <?= Icon::create('schedule', 'clickable')->asImg() ?>
- </a>
- </td>
- <? for ($i = $time + $start; $i < $time + $end; $i += $step_day) : ?>
- <? if (!($i % 3600)) : ?>
- <td<?= $colsp ?> class="precol1w" style="text-align: center;">
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Termin um %R Uhr'), $i) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $i]) ?>" class="calhead">
- <?= date('G', $i) ?>
- </a>
- </td>
- <? endif ?>
- <? endfor ?>
- </tr>
- </table>
- </div>
- </td>
- </tr>
- </tbody>
-</table>
diff --git a/app/views/calendar/group/month.php b/app/views/calendar/group/month.php
deleted file mode 100644
index 67212dd..0000000
--- a/app/views/calendar/group/month.php
+++ /dev/null
@@ -1,110 +0,0 @@
-<? $month = $calendar->view; ?>
-<table class="calendar-month">
- <thead>
- <tr>
- <td colspan="8" style="text-align: center; vertical-align: middle;">
- <div style="text-align: left; display: inline-block; width: 33%; white-space: nowrap;">
- <a style="padding-right: 2em;" href="<?= $controller->url_for('calendar/group/month', ['atime' => mktime(12, 0, 0, date('n', $calendars[0][15]->getStart()), 15, date('Y', $calendars[0][15]->getStart()) - 1)]) ?>">
- <?= Icon::create('arr_2left', 'clickable', ['title' => _('Ein Jahr zurück')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- <?= strftime('%B %Y', strtotime('-1 year', $calendars[0][15]->getStart())) ?>
- </a>
- <a href="<?= $controller->url_for('calendar/group/month', ['atime' => $calendars[0][0]->getStart() - 1]) ?>">
- <?= Icon::create('arr_1left', 'clickable', ['title' => _('Einen Monat zurück')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- <?= strftime('%B %Y', strtotime('-1 month', $calendars[0][15]->getStart())) ?>
- </a>
- </div>
- <div class="calhead" style="text-align: center; display: inline-block; width: 33%;">
- <?= htmlReady(strftime("%B ", $calendars[0][15]->getStart())) .' '. date('Y', $calendars[0][15]->getStart()); ?>
- </div>
- <div style="text-align: right; display: inline-block; width: 33%; white-space: nowrap;">
- <a style="padding-right: 2em;" href="<?= $controller->url_for('calendar/group/month', ['atime' => strtotime('+1 month', $calendars[0][15]->getStart())]) ?>">
- <?= strftime('%B %Y', strtotime('+1 month', $calendars[0][15]->getStart())) ?>
- <?= Icon::create('arr_1right', 'clickable', ['title' => _('Einen Monat vor')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- </a>
- <a href="<?= $controller->url_for('calendar/group/month', ['atime' => mktime(12, 0, 0, date('n', $calendars[0][15]->getStart()), 15, date('Y', $calendars[0][15]->getEnd()) + 1)]) ?>">
- <?= strftime('%B %Y', strtotime('+1 year', $calendars[0][15]->getStart())) ?>
- <?= Icon::create('arr_2right', 'clickable', ['title' => _('Ein Jahr vor')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- </a>
- </div>
- </td>
- </tr>
- <tr class="calendar-month-weekdays">
- <? $week_days = [39092400, 39178800, 39265200, 39351600, 39438000, 39524400, 39610800]; ?>
- <? foreach ($week_days as $week_day) : ?>
- <td class="precol1w">
- <?= strftime('%a', $week_day) ?>
- </td>
- <? endforeach; ?>
- <td align="center" class="precol1w">
- <?= _('Woche') ?>
- </td>
- </tr>
- </thead>
- <tbody>
- <? for ($i = $first_day, $j = 0; $i <= $last_day; $i += 86400, $j++) : ?>
- <? $aday = date('j', $i); ?>
- <?
- $class_day = '';
- if (($aday - $j - 1 > 0) || ($j - $aday > 6)) {
- $class_cell = 'lightmonth';
- $class_day = 'light';
- } elseif (date('Ymd', $i) == date('Ymd')) {
- $class_cell = 'celltoday';
- } else {
- $class_cell = 'month';
- }
- $hday = holiday($i);
-
- if ($j % 7 == 0) {
- ?><tr><?
- }
- ?>
- <td class="<?= $class_cell ?>">
- <? if (($j + 1) % 7 == 0) : ?>
- <a class="<?= $class_day . 'sday' ?>" href="<?= $controller->url_for('calendar/group/day/' . $this->range_id, ['atime' => $i]) ?>">
- <?= $aday ?>
- </a>
- <? if ($hday["name"] != "") : ?>
- <div style="color: #aaaaaa;" class="inday"><?= $hday['name'] ?></div>
- <? endif; ?>
- <? foreach($calendars as $user_calendars) : ?>
- <? $count = sizeof($user_calendars[$j]->events) ?>
- <? if ($count) : ?>
- <div data-tooltip="">
- <a class="inday calendar-event-text" href="<?= $controller->url_for('calendar/single/day/' . $user_calendars[$j]->getRangeId(), ['atime' => $user_calendars[$j]->getStart()]) ?>"><?= htmlReady($user_calendars[$j]->range_object->getFullname('no_title')) ?></a>
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $user_calendars[$j]]) ?>
- </div>
- <? endif; ?>
- <? endforeach; ?>
- </td>
- <td class="lightmonth calendar-month-week">
- <a style="font-weight: bold;" class="calhead" href="<?= $controller->url_for('calendar/group/week/' . $this->range_id, ['atime' => $i]) ?>"><?= strftime("%V", $i) ?></a>
- </td>
- </tr>
- <? else : ?>
- <? $hday_class = ['day', 'day', 'shday', 'hday'] ?>
- <? if ($hday['col']) : ?>
- <a class="<?= $class_day . $hday_class[$hday['col']] ?>" href="<?= $controller->url_for('calendar/group/day', ['atime' => $i]) ?>">
- <?= $aday ?>
- </a>
- <div style="color: #aaaaaa;" class="inday"><?= $hday['name'] ?></div>
- <? else : ?>
- <a class="<?= $class_day . 'day' ?>" href="<?= $controller->url_for('calendar/single/day', ['atime' => $i]) ?>">
- <?= $aday ?>
- </a>
- <? endif; ?>
- <? foreach($calendars as $user_calendars) : ?>
- <? $count = sizeof($user_calendars[$j]->events) ?>
- <? if ($count) : ?>
- <div data-tooltip>
- <a class="inday calendar-event-text" href="<?= $controller->url_for('calendar/single/day/' . $user_calendars[$j]->getRangeId(), ['atime' => $user_calendars[$j]->getStart()]) ?>"><?= htmlReady($user_calendars[$j]->range_object->getFullname('no_title')) ?></a>
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $user_calendars[$j]]) ?>
- </div>
- <? endif; ?>
- <? endforeach; ?>
- </td>
- <? endif; ?>
- <? endfor; ?>
- </tr>
- </tbody>
-</table>
diff --git a/app/views/calendar/group/week.php b/app/views/calendar/group/week.php
deleted file mode 100644
index cdac0b5..0000000
--- a/app/views/calendar/group/week.php
+++ /dev/null
@@ -1,144 +0,0 @@
-<?
-$width1 = 0;
-$width2 = 0;
-$cols = ceil(($settings['end'] - $settings['start'] + 1) * 3600 / $settings['step_week_group']) + 1;
-$start = $settings['start'] * 3600;
-$end = ($settings['end'] + 1) * 3600;
-$wlength = count($calendars[0]) - 1;
-?>
-<table style="width: 100%">
- <tr>
- <td colspan="<?= $colspan_2 ?>" style="vertical-align: middle; text-align: center;">
- <div style="text-align: left; width: 25%; display: inline-block; white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/group/week', ['atime' => mktime(12, 0, 0, date('n', $atime), date('j', $atime) - 7, date('Y', $atime))]) ?>">
- <?= Icon::create('arr_1left', 'clickable', ['title' => _('Eine Woche zurück')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- <?= sprintf(_('%u. Woche'), strftime('%V'), strtotime('-1 week', $atime)) ?>
- </a>
- </div>
- <div style="display: inline-block; text-align: center;" class="calhead">
- <? printf(_("%s. Woche vom %s bis %s"), strftime("%V", $calendars[0][0]->getStart()), strftime("%x", $calendars[0][0]->getStart()), strftime("%x", $calendars[0][$wlength]->getEnd())) ?>
- </div>
- <div style="width: 25%; text-align: right; display: inline-block; white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/group/week', ['atime' => mktime(12, 0, 0, date('n', $atime), date('j', $atime) + 7, date('Y', $atime))]) ?>">
- <?= sprintf(_('%u. Woche'), strftime('%V', strtotime('+1 week', $atime))) ?>
- <?= Icon::create('arr_1right', 'clickable', ['title' => _('Eine Woche vor')])->asImg(16, ["style" => 'vertical-align: text-top;']) ?>
- </a>
- </div>
- </td>
- </tr>
-</table>
-<div style="overflow:auto; width:100%;">
- <table id="main_content" style="width: 100%;">
- <thead>
- <tr>
- <td width="<?= $width2 ?>%" class="precol1w"> </td>
- <? $time = $calendars[0][0]->getStart(); ?>
- <? for ($i = 0; $i <= $wlength; $i++) : ?>
- <td colspan="<?= $cols ?>" style="text-align: center;" class="precol1w">
- <a href="<?= $controller->url_for('calendar/group/day/' . $this->range_id, ['atime' => $time]) ?>" class="calhead">
- <?= strftime('%a', $time) . ' ' . date('d', $time) ?>
- </a>
- </td>
- <? $time += 86400; ?>
- <? endfor ?>
- </tr>
- <tr>
- <td width="<?= $width2 ?>%" class="precol1w" style="text-align: center;">
- <?= _('Mitglied') ?>
- </td>
- <? foreach ($calendars[0] as $day) : ?>
- <td class="precol1w" style="text-align: center; width: <?= $width1 ?>%;">
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Tagestermin am %x'), $day->getStart()) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $day->getStart(), 'isdayevent' => '1']) ?>">
- <?= Icon::create('schedule', 'clickable')->asImg() ?>
- </a>
- </td>
- <? for ($i = $day->getStart() + $start; $i < $day->getStart() + $end; $i += 3600 * ceil($settings['step_week_group'] / 3600)) : ?>
- <td colspan="<?= ceil(3600 / $settings['step_week_group']) ?>" class="precol2w" style="text-align: center;">
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Termin um %R Uhr'), $i) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $i]) ?>" class="calhead">
- <?= (date('G', $i) < 10 ? '&nbsp;' . date('G', $i) . '&nbsp;' : date('G', $i)) ?>
- </a>
- </td>
- <? endfor ?>
- <? endforeach ?>
- </tr>
- </thead>
- <tbody>
- <? foreach ($calendars as $user_calendar) : ?>
- <tr>
- <td style="width: <?= $width2 ?>%; white-space: nowrap;" class="month">
- <a class="calhead" href="<?= $controller->url_for('calendar/single/week/' . $user_calendar[0]->getRangeId(), ['atime' => $atime]) ?>">
- <?= htmlReady($user_calendar[0]->havePermission(Calendar::PERMISSION_OWN) ? _('Eigener Kalender') : get_fullname($user_calendar[0]->getRangeId(), 'no_title_short')) ?>
- </a>
- </td>
- <? $k = 1; ?>
- <? foreach ($user_calendar as $day) : ?>
- <?
- if (date('Ymd', $day->getStart()) == date('Ymd')) {
- $css_class = 'celltoday';
- } else {
- if ($k % 2) {
- $css_class = 'lightmonth';
- } else {
- $css_class = 'month';
- }
- }
- $k++;
- $adapted = $day->adapt_events($start, $end, $settings['step_week_group']);
-
- // display day events
- $js_events = []; ?>
- <? for ($i = 0; $i < count($adapted['day_events']); $i++) : ?>
- <? $js_events[] = $day->events[$adapted['day_map'][$i]]; ?>
- <? endfor; ?>
- <? if (count($js_events) && $day->havePermission(Calendar::PERMISSION_WRITABLE)) : ?>
- <td <?= count($js_events) ? 'data-tooltip ' : '' ?>style="text-align: right; width: <?= $width1 ?>%" class="calendar-day-edit <?= $css_class ?><?= count($js_events) ? ' calendar-group-events' : '' ?>">
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $day, 'events' => $js_events]) ?>
- <a title="<?= strftime(_('Neuer Tagestermin am %x'), $day->getStart()) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $day->getStart(), 'isdayevent' => '1', 'user_id' => $day->getRangeId()]) ?>">+</a>
- <? else : ?>
- <td <?= count($js_events) ? 'data-tooltip ' : '' ?>style="text-align: right; width: <?= $width1 ?>%" class="<?= $css_class ?><?= count($js_events) ? ' calendar-group-events' : '' ?>">
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $day, 'events' => $js_events]) ?>
- <? endif;?>
- </td>
-
- <? for ($i = $start + $day->getStart(); $i < $end + $day->getStart(); $i += $settings['step_week_group']) : ?>
- <? $js_events = []; ?>
- <? for ($j = 0; $j < count($adapted['events']); $j++) : ?>
- <? if (($adapted['events'][$j]->getStart() <= $i && $adapted['events'][$j]->getEnd() > $i) || ($adapted['events'][$j]->getStart() > $i && $adapted['events'][$j]->getStart() < $i + $settings['step_week_group'])) : ?>
- <? $js_events[] = $day->events[$adapted['map'][$j]]; ?>
- <? endif ?>
- <? endfor ?>
- <? if ($day->havePermission(Calendar::PERMISSION_WRITABLE)) : ?>
- <td <?= count($js_events) ? 'data-tooltip ' : '' ?>style="text-align: right; width: <?= $width1 ?>%" class="calendar-day-edit <?= $css_class ?><?= count($js_events) ? ' calendar-group-events' : '' ?>">
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $day, 'events' => $js_events]) ?>
- <a title="<?= strftime(_('Neuer Termin um %R Uhr'), $i) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $i, 'user_id' => $day->getRangeId()]) ?>">+</a>
- <? else : ?>
- <td <?= count($js_events) ? 'data-tooltip ' : '' ?>style="text-align: right; width: <?= $width1 ?>%" class="<?= $css_class ?><?= count($js_events) ? ' calendar-group-events' : '' ?>">
- <?= $this->render_partial('calendar/group/_tooltip', ['calendar' => $day, 'events' => $js_events]) ?>
- <? endif; ?>
- </td>
- <? endfor; ?>
- <? endforeach; ?>
- </tr>
- <? endforeach; ?>
- </tbody>
- <tfoot>
- <tr>
- <td style="width:<?= $width2 ?>%; text-align: center;" class="precol1"> </td>
- <? foreach ($calendars[0] as $day) : ?>
- <td style="width:<?= $width1 ?>%; text-align: center;" class="precol1w">
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Tagestermin am %x'), $day->getStart()) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $atime, 'isdayevent' => '1']) ?>">
- <?= Icon::create('schedule', 'clickable')->asImg() ?>
- </a>
- </td>
- <? for ($i = $day->getStart() + $start; $i < $day->getStart() + $end; $i += 3600 * ceil($settings['step_week_group'] / 3600)) : ?>
- <td colspan="<?= ceil(3600 / $settings['step_week_group']) ?>" class="precol2w" style="text-align: center;">
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Termin um %R Uhr'), $i) ?>" href="<?= $controller->url_for('calendar/group/edit/' . $range_id, ['atime' => $i]) ?>" class="calhead">
- <?= (date('G', $i) < 10 ? '&nbsp;' . date('G', $i) . '&nbsp;' : date('G', $i)) ?>
- </a>
- </td>
- <? endfor; ?>
- <? endforeach; ?>
- </tr>
- </tfoot>
- </table>
-</div>
diff --git a/app/views/calendar/group/year.php b/app/views/calendar/group/year.php
deleted file mode 100644
index b07f749..0000000
--- a/app/views/calendar/group/year.php
+++ /dev/null
@@ -1,122 +0,0 @@
-<table style="width: 100%;">
- <tr>
- <td colspan="3" style="text-align: center; vertical-align: middle;">
- <div style="text-align: left; display: inline-block; width: 20%; white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/group/year', ['atime' => strtotime('-1 year', $atime)]) ?>">
- <?= Icon::create('arr_2left', 'clickable', ['title' => _('Ein Jahr zurück')])->asImg(16, ['style' => 'vertical-align: text-bottom;']) ?>
- <?= strftime('%Y', strtotime('-1 year', $atime)) ?>
- </a>
- </div>
- <div class="calhead" style="text-align: center; display: inline-block; width:50%;">
- <?= date('Y', $calendars[0]->getStart()) ?>
- </div>
- <div style="text-align: right; display: inline-block; width: 20%; white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/group/year', ['atime' => strtotime('+1 year', $atime)]) ?>">
- <?= strftime('%Y', strtotime('+1 year', $atime)) ?>
- <?= Icon::create('arr_2right', 'clickable', ['title' => _('Ein Jahr vor')])->asImg(16, ['style' => 'vertical-align: text-bottom;']) ?>
- </a>
- </div>
- </td>
- </tr>
- <tr>
- <td colspan="3" class="blank">
- <table style="width: 100%;">
- <? $days_per_month = [31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
- if (date('L', $calendars[0]->getStart())) {
- $days_per_month[2]++;
- }
- ?>
- <tr>
- <? for ($i = 1; $i < 13; $i++) : ?>
- <? $ts_month += ( $days_per_month[$i] - 1) * 86400; ?>
- <td style="text-align: center; width: 8%;">
- <a class="calhead" href="<?= $controller->url_for('calendar/group/month', ['atime' => $calendars[0]->getStart() + $ts_month]) ?>">
- <?= strftime('%B', $ts_month); ?>
- </a>
- </td>
- <? endfor; ?>
- </tr>
- <? $now = date('Ymd'); ?>
- <? for ($i = 1; $i < 32; $i++) : ?>
- <tr>
- <? for ($month = 1; $month < 13; $month++) : ?>
- <? $aday = mktime(12, 0, 0, $month, $i, date('Y', $calendars[0]->getStart())); ?>
- <? if ($i <= $days_per_month[$month]) : ?>
- <? $wday = date('w', $aday);
- // emphasize current day
- if (date('Ymd', $aday) == $now) {
- $day_class = ' class="celltoday"';
- } else if ($wday == 0 || $wday == 6) {
- $day_class = ' class="weekend"';
- } else {
- $day_class = ' class="weekday"';
- }
- ?>
- <? if ($month == 1) : ?>
- <td<?= $day_class ?> height="25">
- <? else : ?>
- <td<?= $day_class ?>>
- <? endif; ?>
- <? $tooltip = $this->render_partial('calendar/group/_tooltip_year',
- ['aday' => $aday, 'calendars' => $calendars, 'count_lists' => $count_lists]) ?>
- <? if (trim($tooltip)) : ?>
- <table style="width: 100%;">
- <tr>
- <td<?= $day_class ?>>
- <? endif; ?>
- <? $weekday = strftime('%a', $aday); ?>
-
- <? $hday = holiday($aday); ?>
- <? if ($hday['col'] == '1') : ?>
- <? if (date('w', $aday) == '0') : ?>
- <a style="font-weight:bold;" class="sday" href="<?= $controller->url_for('calendar/group/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? $count++; ?>
- <? else : ?>
- <a style="font-weight:bold;" class="day" href="<?= $controller->url_for('calendar/group/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? endif; ?>
- <? elseif ($hday['col'] == '2' || $hday['col'] == '3') : ?>
- <? if (date('w', $aday) == '0') : ?>
- <a style="font-weight:bold;" class="sday" href="<?= $controller->url_for('calendar/group/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? $count++; ?>
- <? else : ?>
- <a style="font-weight:bold;" class="hday" href="<?= $controller->url_for('calendar/group/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? endif; ?>
- <? else : ?>
- <? if (date('w', $aday) == '0') : ?>
- <a style="font-weight:bold;" class="sday" href="<?= $controller->url_for('calendar/group/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? $count++; ?>
- <? else : ?>
- <a style="font-weight:bold;" class="day" href="<?= $controller->url_for('calendar/group/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? endif; ?>
- <? endif; ?>
- <? if (trim($tooltip)) : ?>
- </td>
- <td<?= $day_class ?> style="text-align: right;" data-tooltip="">
- <?= Icon::create('date', 'clickable', ['title' => $event_count_txt])->asImg(16, ["alt" => $event_count_txt]); ?>
- <?= $tooltip ?>
- </td>
- </tr>
- </table>
- <? endif; ?>
- </td>
- <? else : ?>
- <td class="weekday"> </td>
- <? endif; ?>
- <? endfor; ?>
- </tr>
- <? endfor; ?>
- <tr>
- <? $ts_month = 0; ?>
- <? for ($i = 1; $i < 13; $i++) : ?>
- <? $ts_month += ( $days_per_month[$i] - 1) * 86400; ?>
- <td align="center" width="8%">
- <a class="calhead" href="<?= $controller->url_for('calendar/group/month', ['atime' => $calendars[0]->getStart() + $ts_month]) ?>">
- <?= strftime('%B', $ts_month); ?>
- </a>
- </td>
- <? endfor; ?>
- </tr>
- </table>
- </td>
- </tr>
-</table>
diff --git a/app/views/calendar/instschedule/_entry_details.php b/app/views/calendar/instschedule/_entry_details.php
deleted file mode 100644
index 7fd4a38..0000000
--- a/app/views/calendar/instschedule/_entry_details.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<table class="default">
- <caption>
- <?= sprintf(_('Veranstaltungen mit regelmäßigen Zeiten am %s, %s Uhr'), htmlReady($day), htmlReady($start) .' - '. htmlReady($end)) ?>
- </caption>
- <colgroup>
- <col width="15%">
- <col width="85%">
- </colgroup>
- <thead>
- <tr>
- <th><?= _('Nummer') ?></th>
- <th><?= _('Name') ?></th>
- </tr>
- </thead>
- <tbody>
- <? foreach ($seminars as $seminar) : ?>
- <tr class="<?= TextHelper::cycle('table_row_odd', 'table_row_even')?>">
- <td><?= htmlReady($seminar->getNumber()) ?></td>
- <td>
- <a href="<?= URLHelper::getLink('dispatch.php/course/details/', ['sem_id' => $seminar->getId()]) ?>">
- <?= Icon::create('link-intern', 'clickable')->asImg() ?>
- <?= htmlReady($seminar->getName()) ?>
- </a>
- </td>
- </tr>
- <? endforeach ?>
- </tbody>
-</table>
-<br>
diff --git a/app/views/calendar/instschedule/index.php b/app/views/calendar/instschedule/index.php
deleted file mode 100644
index bcfa72e..0000000
--- a/app/views/calendar/instschedule/index.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<h1>
- <?= htmlReady(Context::getHeaderLine()) ?> <?= _("im") ?>
- <?= htmlReady($current_semester['name']) ?>
-</h1>
-
-<? if (Request::get('show_settings')) : ?>
- <div class="ui-widget-overlay" style="width: 100%; height: 100%; z-index: 1001;"></div>
- <?= $this->render_partial('calendar/schedule/_dialog', [
- 'content_for_layout' => $this->render_partial('calendar/schedule/settings', [
- 'settings' => $my_schedule_settings]),
- 'title' => _('Darstellung ändern')
- ]) ?>
-<? endif ?>
-
-<?= $calendar_view->render() ?>
diff --git a/app/views/calendar/stylesheet.php b/app/views/calendar/schedule/stylesheet.php
index aaf7334..aaf7334 100644
--- a/app/views/calendar/stylesheet.php
+++ b/app/views/calendar/schedule/stylesheet.php
diff --git a/app/views/calendar/single/_attendees.php b/app/views/calendar/single/_attendees.php
deleted file mode 100644
index c2ee568..0000000
--- a/app/views/calendar/single/_attendees.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<? use Studip\Button, Studip\LinkButton; ?>
-<? $show_members = $event->attendees->findOneBy('range_id',
- $calendar->getRangeId(), '!=') ?>
-<? // Entkommentieren, wenn Mitglieder eines Termins sichtbar sein
- // sollen, auch wenn man nicht selbst Mitglied ist und ... ?>
-<? // $show_members_visiter = $event->attendees->findOneBy('range_id', $GLOBALS['user']->id) ?>
-<? // folgende Zeile auskommentieren (siehe _attendees.php). ?>
-<? $show_members_visiter = true; ?>
-<? if ($show_members && $show_members_visiter) : ?>
- <? $group_status = [
- CalendarEvent::PARTSTAT_TENTATIVE => _('Abwartend'),
- CalendarEvent::PARTSTAT_ACCEPTED => _('Angenommen'),
- CalendarEvent::PARTSTAT_DECLINED => _('Abgelehnt'),
- CalendarEvent::PARTSTAT_DELEGATED => _('Angenommen (keine Teilnahme)'),
- CalendarEvent::PARTSTAT_NEEDS_ACTION => ''] ?>
- <div>
- <b><?= _('Teilnehmende:') ?></b>
- <?= implode(', ', $event->attendees->map(
- function ($att) use ($event, $group_status) {
- $profil_link = ObjectdisplayHelper::link($att->user);
- if ($event->havePermission(Event::PERMISSION_OWN, $att->user->getId())) {
- $profil_link .= ' (' . _('Organisator') . ')';
- } else {
- if ($group_status[$att->group_status]) {
- $profil_link .= ' (' . $group_status[$att->group_status] . ')';
- }
- }
- return $profil_link;
- })); ?>
- </div>
-<? endif; ?>
diff --git a/app/views/calendar/single/_calhead.php b/app/views/calendar/single/_calhead.php
deleted file mode 100644
index 0d3e777..0000000
--- a/app/views/calendar/single/_calhead.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<div class="calhead" style="white-space: nowrap; position: relative;">
- <label>
- <?= $calLabel ?>
- <?= Icon::create('arr_1down', 'clickable') ?>
-
- <input type="text"
- id="date-chooser"
- value="<?= strftime('%F', $atime) ?>"
- data-url="<?= $controller->url_for('calendar/single/' . $calType, ['atime' => '%ATIME%']) ?>"
- style="width: 0.1px; height: 0.1px; opacity: 0; overflow: hidden; position: absolute; left: 50%; z-index: -1;">
- </label>
-
- <script>
- jQuery('#date-chooser').datepicker({ dateFormat: 'yy-mm-dd', onSelect: function () { window.location = $(this).data('url').replace(encodeURI("%ATIME%"), Math.floor($(this).datepicker('getDate').valueOf() / 1000)) } })
- </script>
-</div>
diff --git a/app/views/calendar/single/_calhead_label_day.php b/app/views/calendar/single/_calhead_label_day.php
deleted file mode 100644
index df4f8bc..0000000
--- a/app/views/calendar/single/_calhead_label_day.php
+++ /dev/null
@@ -1,3 +0,0 @@
-<span class="hidden-tiny-down"><?= strftime('%A, ', $atime) ?></span>
-<?= strftime('%d.%m.%Y', $atime) ?>
-<span class="hidden-medium-down" style="font-size: 12pt; color: #bbb; font-weight: bold;"><? if ($hd = holiday($atime)) echo $hd['name']; ?></span>
diff --git a/app/views/calendar/single/_calhead_label_week.php b/app/views/calendar/single/_calhead_label_week.php
deleted file mode 100644
index fc0a544..0000000
--- a/app/views/calendar/single/_calhead_label_week.php
+++ /dev/null
@@ -1,3 +0,0 @@
-<? printf(_("%s. Woche"), strftime("%V", $calendars[0]->getStart())) ?>
-<span class="hidden-large-up"><?= date('Y', $calendars[0]->getStart()) ?></span>
-<span class="hidden-medium-down"><? printf(_("vom %s bis %s"), strftime("%x", $calendars[0]->getStart()), strftime("%x", $calendars[$week_type - 1]->getStart())) ?></span>
diff --git a/app/views/calendar/single/_day.php b/app/views/calendar/single/_day.php
deleted file mode 100644
index 8b70fe9..0000000
--- a/app/views/calendar/single/_day.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?
-$at = date('G', $atime);
-if ($at >= $settings['start']
- && $at <= $settings['end'] || !$atime) {
- $start = $settings['start'] * 3600;
- $end = $settings['end'] * 3600;
-} elseif ($at < $settings['start']) {
- $start = 0;
- $end = ($settings['start'] + 2) * 3600;
-} else {
- $start = ($settings['end'] - 2) * 3600;
- $end = 23 * 3600;
-}
-$em = $calendar->createEventMatrix($start, $end, $settings['step_day']);
-$max_columns = $em['max_cols'] ?: 1;
-?>
-
-<nav class="calendar-nav">
- <span style="white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/single/day', ['atime' => strtotime('-1 day', $atime)]) ?>">
- <?= Icon::create('arr_1left', 'clickable', ['title' => _('Einen Tag zurück')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- <span class="hidden-tiny-down">
- <?= strftime(_('%x'), strtotime('-1 day', $calendar->getStart())) ?>
- </span>
- </a>
- </span>
-
- <?
- $calType = 'day';
- $calLabel = $this->render_partial('calendar/single/_calhead_label_day');
- ?>
-
- <?= $this->render_partial('calendar/single/_calhead', compact('calendar', 'atime', 'calType', 'calLabel')) ?>
-
- <span style="white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/single/day', ['atime' => strtotime('+1 day', $atime)]) ?>">
- <span class="hidden-tiny-down">
- <?= strftime(_('%x'), strtotime('+1 day', $calendar->getStart())) ?>
- </span>
- <?= Icon::create('arr_1right', 'clickable', ['title' => _('Einen Tag vor')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- </a>
- </span>
-</nav>
-
-<table class="calendar-day">
- <colgroup>
- <col style="max-width: 2em; width: 2em;">
- <? if ($settings['step_day'] < 3600) : ?>
- <col style="max-width: 2em; width: 2em;">
- <? $max_columns_head = $max_columns + 3 ?>
- <? else : ?>
- <? $max_columns_head = $max_columns + 2 ?>
- <? endif; ?>
- <col span="<?= $em['max_cols'] ?: '1' ?>" style="width: <?= 100 / ($em['max_cols'] ?: 1) ?>%">
- <col style="max-width: 0.8em; width: 0.8em;">
- </colgroup>
- <thead>
- <? if ($start > 0) : ?>
- <tr>
- <td align="center"<?= $settings['step_day'] < 3600 ? ' colspan="2"' : '' ?>>
- <a href="<?= $controller->url_for('calendar/single/day', ['atime' => ($atime - (date('G', $atime) * 3600 - $start + 3600))]) ?>">
- <?= Icon::create('arr_1up', 'clickable', ['title' => _('Früher')])->asImg() ?>
- </a>
- </td>
- <td colspan="<?= $max_columns + 1 ?>">
- </td>
- </tr>
- <? endif; ?>
- </thead>
- <tbody>
- <?= $this->render_partial('calendar/single/_day_table', ['start' => $start, 'end' => $end, 'em' => $em]) ?>
- </tbody>
- <tfoot>
- <? if ($end / 3600 < 23) : ?>
- <tr>
- <td align="center"<?= $settings['step_day'] < 3600 ? ' colspan="2"' : '' ?>>
- <a href="<?= $controller->url_for('calendar/single/day', ['atime' => ($atime + $end - date('G', $atime) * 3600 + 3600)]) ?>">
- <?= Icon::create('arr_1down', 'clickable', ['title' => _('Später')])->asImg() ?>
- </a>
- </td>
- <td colspan="<?= $max_columns + 1 ?>">
- </td>
- </tr>
- <? else : ?>
- <tr>
- <td>&nbsp;</td>
- </tr>
- <? endif ?>
- </tfoot>
-</table>
diff --git a/app/views/calendar/single/_day_cell.php b/app/views/calendar/single/_day_cell.php
deleted file mode 100644
index 31845ef..0000000
--- a/app/views/calendar/single/_day_cell.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<? $link_notset = true ?>
-<? $atime_new = $calendar->getStart() + $i * $step ?>
-<? if (empty($em['term'][$row])) : ?>
- <? if ($calendar->havePermission(Calendar::PERMISSION_WRITABLE)) : ?>
- <td class="calendar-day-edit <?= $class_cell ?>" <?= ($em['max_cols'] > 0 ? ' colspan="' . ($em['max_cols'] + 1) . '"' : '') ?>>
- <a title="<?= strftime(_('Neuer Termin am %x, %R Uhr'), $atime_new) ?>" href="<?= $controller->url_for('calendar/single/edit/' . $calendar->getRangeId(), ['atime' => $atime_new]) ?>">+</a>
- </td>
- <? else : ?>
- <td class="calendar-day-edit <?= $class_cell ?>" <?= ($em['max_cols'] > 0 ? ' colspan="' . ($em['max_cols'] + 1) . '"' : '') ?>>
- </td>
- <? endif; ?>
-<? $link_notset = false ?>
-<? else : ?>
- <? for ($j = 0; $j < $em['colsp'][$row]; $j++) : ?>
- <? $event = $em['term'][$row][$j]; ?>
- <? $mapped_event = $calendar->events[$em['mapping'][$row][$j]]; ?>
- <? if (is_object($event)) : ?>
- <td data-tooltip<?= ($em['cspan'][$row][$j] > 1 ? ' colspan="' . $em['cspan'][$row][$j] . '"' : '') ?><?= ($em['rows'][$row][$j] > 1 ? ' rowspan="' . $em['rows'][$row][$j] . '"' : '') ?> class="<?= $event instanceof CourseEvent ? 'calendar-course-category' : 'calendar-category' ?><?= $event->getCategory() ?> calendar-day-event">
- <? if ($em['rows'][$row][$j] > 1) : ?>
- <div>
- <?= date('H.i-', $mapped_event->getStart()) . date('H.i', $mapped_event->getEnd()) ?>
- </div>
- <? endif ?>
- <div class="calendar-day-event-title">
- <a title="<?= _('Termin bearbeiten') ?>" href="<?= $controller->url_for('calendar/single/edit/' . $calendar->getRangeId() . '/' . $event->event_id, ['atime' => $atime_new, 'evtype' => $event->getType()]) ?>"><?= htmlReady($event->getTitle()) ?></a>
- <?= $this->render_partial('calendar/single/_tooltip', ['event' => $mapped_event]) ?>
- </div>
- </td>
- <? elseif ($event == '#') : ?>
- <td class="<?= $class_cell ?>"<?= ($em['cspan'][$row][$j] > 1 ? ' colspan="' . $em['cspan'][$row][$j] . '"' : '') ?>>
- <span class="inday">&nbsp;</span>
- </td>
- <? elseif ($event == '') : ?>
- <? if ($calendar->havePermission(Calendar::PERMISSION_WRITABLE)) : ?>
- <td class="calendar-day-edit <?= $class_cell ?>"<?= ($em['cspan'][$row][$j] > 1 ? ' colspan="' . $em['cspan'][$row][$j] . '"' : '') ?>>
- <a title="<?= strftime(_('Neuer Termin am %x, %R Uhr'), $atime_new) ?>" href="<?= $controller->url_for('calendar/single/edit/' . $calendar->getRangeId(), ['atime' => $atime_new]) ?>">+</a>
- </td>
- <? else : ?>
- <td class="calendar-day-edit <?= $class_cell ?>"<?= ($em['cspan'][$row][$j] > 1 ? ' colspan="' . $em['cspan'][$row][$j] . '"' : '') ?>></td>
- <? endif ?>
- <? $link_notset = false; ?>
- <? break; ?>
- <? endif ?>
- <? endfor ?>
-<? endif ?>
-<? if ($link_notset) : ?>
- <? if ($calendar->havePermission(Calendar::PERMISSION_WRITABLE)) : ?>
- <td class="calendar-day-edit <?= $class_cell ?>">
- <a title="<?= strftime(_('Neuer Termin am %x, %R Uhr'), $atime_new) ?>" href="<?= $controller->url_for('calendar/single/edit/' . $calendar->getRangeId(), ['atime' => $atime_new]) ?>">+</a>
- </td>
- <? else : ?>
- <td class="calendar-day-edit <?= $class_cell ?>"></td>
- <? endif; ?>
-<? endif ?>
diff --git a/app/views/calendar/single/_day_dayevents.php b/app/views/calendar/single/_day_dayevents.php
deleted file mode 100644
index 397f0bf..0000000
--- a/app/views/calendar/single/_day_dayevents.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<? if (isset($em['day_events']) && count($em['day_events']) > 0) : ?>
- <td class="<?= $class_cell ?>" style="padding: 0px;" <?= (($em['max_cols'] > 0) ? ' colspan="' . ($em['max_cols']) . '"' : '') ?>>
- <table style="width: 100%; border-spacing: 0;">
- <? $i = 0; ?>
- <? foreach ($em['day_events'] as $day_event) : ?>
- <tr>
- <? if ($day_event->getPermission() == Event::PERMISSION_CONFIDENTIAL) : ?>
- <td class="calendar-category<?= $day_event->getCategory() ?>">
- <?= htmlReady($day_event->getTitle()) ?>
- </td>
- <? else : ?>
- <td data-tooltip onclick="STUDIP.Dialog.fromElement(jQuery(this).children('a').first(), {size: 'auto'}); return false;" class="calendar-category<?= $day_event->getCategory() ?>">
- <?= $this->render_partial('calendar/single/_tooltip', ['event' => $calendar->events[$em['day_map'][$i]]]) ?>
- <a style="color:#fff;" data-dialog="size=auto" href="<?= $controller->url_for('calendar/single/edit/' . $calendar->getRangeId() . '/' . $day_event->event_id, ['isdayevent' => '1']) ?>"><?= htmlReady($day_event->getTitle()) ?></a>
- </td>
- <? endif; ?>
- </tr>
- <? $i++; ?>
- <? endforeach ?>
- </table>
- </td>
- <td class="calendar-day-edit <?= $class_cell ?>" onclick="STUDIP.Dialog.fromElement(jQuery(this).children('a').first(), {size: 'auto'}); return false;">
- <? if ($calendar->havePermission(Calendar::PERMISSION_WRITABLE)) : ?>
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Tagestermin am %x'), $calendar->getStart()) ?>" href="<?= $controller->url_for('calendar/single/edit/' . $calendar->getRangeId(), ['atime' => $calendar->getStart(), 'isdayevent' => '1']) ?>">+</a>
- <? endif; ?>
- </td>
-<? else : ?>
- <td class="calendar-day-edit <?= $class_cell ?>" <?= (($em['max_cols'] > 0) ? ' colspan="' . ($em['max_cols'] + 1) . '"' : '') ?>>
- <? if ($calendar->havePermission(Calendar::PERMISSION_WRITABLE)) : ?>
- <a data-dialog="size=auto" title="<?= strftime(_('Neuer Tagestermin am %x'), $calendar->getStart()) ?>" href="<?= $controller->url_for('calendar/single/edit/' . $calendar->getRangeId(), ['atime' => $calendar->getStart(), 'isdayevent' => '1']) ?>">+</a>
- <? endif; ?>
- </td>
-<? endif; ?>
diff --git a/app/views/calendar/single/_day_table.php b/app/views/calendar/single/_day_table.php
deleted file mode 100644
index 1b0ebcd..0000000
--- a/app/views/calendar/single/_day_table.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?
-if ($settings['step_day'] >= 3600) {
- $rowspan_precol = '';
-} else {
- $rowspan_precol = ' rowspan="' . 3600 / $settings['step_day'] . '"';
-}
-?>
-<tr>
- <td class="precol1w" <?= $rowspan_precol ? ' colspan="2"' : '' ?>><?= _('Tag') ?></td>
- <?= $this->render_partial('calendar/single/_day_dayevents', ['em' => $em]); ?>
-</tr>
-<? for ($i = $start / $settings['step_day']; $i < $end / $settings['step_day'] + 3600 / $settings['step_day']; $i++) : ?>
-<? $row = $i - $start / $settings['step_day']; ?>
-<tr>
- <? if (($i * $settings['step_day']) % 3600 == 0) : ?>
- <td class="precol1w" <?= $rowspan_precol ?>>
- <?= $i / (3600 / $settings['step_day']) ?>
- </td>
- <? endif ?>
- <? if ($settings['step_day'] % 3600 != 0) : ?>
- <? $minute = ($row % (3600 / $settings['step_day'])) * ($settings['step_day'] / 60); ?>
- <td class="precol2w" style="height: 20px; width: 1%; padding-right: 3px;">
- <?= $minute ? $minute : '00' ?>
- </td>
- <? endif ?>
- <?= $this->render_partial('calendar/single/_day_cell', ['events' => $calendar->events, 'start' => $start, 'em' => $em, 'row' => $row, 'i' => $i, 'step' => $settings['step_day']]); ?>
-</tr>
-<? endfor; ?> \ No newline at end of file
diff --git a/app/views/calendar/single/_edit_status.php b/app/views/calendar/single/_edit_status.php
deleted file mode 100644
index b5f41aa..0000000
--- a/app/views/calendar/single/_edit_status.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<? use Studip\Button, Studip\LinkButton; ?>
-<form action="" method="post">
- <div>
- <b><?= _('Eigener Teilnahmestatus') ?>:</b>
- <? $group_status = [
- CalendarEvent::PARTSTAT_TENTATIVE => _('Abwartend'),
- CalendarEvent::PARTSTAT_ACCEPTED => _('Angenommen'),
- CalendarEvent::PARTSTAT_DECLINED => _('Abgelehnt'),
- CalendarEvent::PARTSTAT_DELEGATED => _('Angenommen (keine Teilnahme)')] ?>
- <ul>
- <? foreach ($group_status as $value => $name) : ?>
- <ul class="list-unstyled">
- <label>
- <input type="radio" value="<?= $value ?>" name="status" <?= $value == $event->group_status ? ' checked' : '' ?>>
- <?= $name ?>
- </label>
- </li>
- <? endforeach; ?>
- </ul>
- </div>
- <div>
- <? $author = $event->getAuthor() ?>
- <? if ($author) : ?>
- <?= sprintf(_('Eingetragen am: %s von %s'),
- strftime('%x, %X', $event->mkdate),
- htmlReady($author->getFullName('no_title'))) ?>
- <? endif; ?>
- </div>
- <? if ($event->event->mkdate < $event->event->chdate) : ?>
- <? $editor = $event->getEditor() ?>
- <? if ($editor) : ?>
- <div>
- <?= sprintf(_('Zuletzt bearbeitet am: %s von %s'),
- strftime('%x, %X', $event->chdate),
- htmlReady($editor->getFullName('no_title'))) ?>
- </div>
- <? endif; ?>
- <? endif; ?>
- <div style="text-align: center;" data-dialog-button>
- <?= Button::create(_('Speichern'), 'store', ['title' => _('Termin speichern')]) ?>
- <? if ($event->havePermission(Event::PERMISSION_DELETABLE)) : ?>
- <?= LinkButton::create(_('Löschen'), $controller->url_for('calendar/single/delete/' . implode('/', $event->getId()))) ?>
- <? endif; ?>
- <? if (!Request::isXhr()) : ?>
- <?= LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/single/' . $last_view, [$event->getStart()])) ?>
- <? endif; ?>
- </div>
-</form>
diff --git a/app/views/calendar/single/_event_data.php b/app/views/calendar/single/_event_data.php
deleted file mode 100644
index da9a5fe..0000000
--- a/app/views/calendar/single/_event_data.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<? use Studip\LinkButton; ?>
-<div>
- <h4><?= htmlReady($event->getTitle()) ?></h4>
- <div>
- <b><?= _('Beginn') ?>:</b> <?= strftime('%c', $event->getStart()) ?>
- </div>
- <div>
- <b><?= _('Ende') ?>:</b> <?= strftime('%c', $event->getEnd()) ?>
- </div>
- <? if ($event->havePermission(Event::PERMISSION_READABLE)) : ?>
- <? if ($event instanceof CourseEvent) : ?>
- <div>
- <b><?= _('Veranstaltung') ?>:</b>
- <? if ($GLOBALS['perm']->have_studip_perm('user', $event->range_id)) : ?>
- <a href="<?= URLHelper::getLink('dispatch.php/course/details/?cid=' . $event->range_id) ?>">
- <? else : ?>
- <a href="<?= URLHelper::getLink('dispatch.php/course/details/index/' . $event->range_id) ?>">
- <? endif; ?>
- <?= htmlReady($event->course->getFullname()) ?>
- </a>
- </div>
- <? endif;?>
- <? if ($text = $event->getDescription()) : ?>
- <div>
- <b><?= _('Beschreibung') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->toStringCategories()) : ?>
- <div>
- <b><?= _('Kategorie') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->getLocation()) : ?>
- <div>
- <b><?= _('Raum/Ort') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->toStringPriority()) : ?>
- <div>
- <b><?= _('Priorität') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->toStringAccessibility()) : ?>
- <div>
- <b><?= _('Zugriff') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->toStringRecurrence()) : ?>
- <div>
- <b><?= _('Wiederholung') ?>:</b> <?= htmlReady($text) ?>
- </div>
- <? endif; ?>
- <? if ($event instanceof CalendarEvent) : ?>
- <? if (Config::get()->CALENDAR_GROUP_ENABLE) : ?>
- <?= $this->render_partial('calendar/single/_attendees.php') ?>
- <? if ($calendar->havePermission(Calendar::PERMISSION_OWN)
- && $event->toStringGroupStatus()) : ?>
- <?= $this->render_partial('calendar/single/_edit_status') ?>
- <? else : ?>
- <div style="text-align: center;" data-dialog-button>
- <? if ($event->havePermission(Event::PERMISSION_DELETABLE)) : ?>
- <?= LinkButton::create(_('Löschen'), $controller->url_for('calendar/single/delete/' . implode('/', $event->getId()))) ?>
- <? endif; ?>
- <? if (!Request::isXhr()) : ?>
- <?= LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/single/' . $last_view, [$event->getStart()])) ?>
- <? endif; ?>
- </div>
- <? endif; ?>
- <? endif; ?>
- <? else : ?>
- <? // durchführende Lehrende ?>
- <? $related_persons = $event->dozenten; ?>
- <? if (sizeof($related_persons)) : ?>
- <div>
- <b><?= ngettext('Durchführende Lehrperson', 'Durchführende Lehrende', sizeof($related_persons)) ?>:</b>
- <ul class="list-unstyled">
- <? foreach ($related_persons as $related_person) : ?>
- <li>
- <?= ObjectdisplayHelper::link($related_person) ?>
- </li>
- <? endforeach; ?>
- </ul>
- </div>
- <? endif; ?>
- <? // related groups ?>
- <? $related_groups = $event->getRelatedGroups(); ?>
- <? if (sizeof($related_groups)) : ?>
- <div>
- <b><?= _('Betroffene Gruppen') ?>:</b>
- <?= htmlReady(implode(', ', $related_groups->pluck('name'))) ?>
- </div>
- <? endif; ?>
- <? if (!Request::isXhr()) : ?>
- <div style="text-align: center;" data-dialog-button>
- <?= LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/single/' . $last_view, [$event->getStart()])) ?>
- </div>
- <? endif; ?>
- <? endif; ?>
- <? endif; ?>
-</div>
diff --git a/app/views/calendar/single/_include_month.php b/app/views/calendar/single/_include_month.php
deleted file mode 100644
index 6942d2f..0000000
--- a/app/views/calendar/single/_include_month.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<? $now = mktime(12, 0, 0, date('n', time()), date('j', time()), date('Y', time())); ?>
-<table class="blank">
- <tr>
- <td style="text-align: center;">
- <table style="width: 100%;">
- <tr>
- <td colspan="8" style="vertical-align: top; text-align: center; white-space:nowrap;">
- <div style="float:left; width:15%;">
- <? if ($mod == 'NONAVARROWS') : ?>
- &nbsp;
- <? else : ?>
- <a href="<?= $controller->url_for($href, ['imt' => mktime(12, 0, 0, date('n', $imt), 1, date('Y', $imt) - 1)]) ?>">
- <?= Icon::create('arr_2left', 'clickable', ['title' => _('Ein Jahr zurück')])->asImg() ?>
- </a>
- <a href="<?= $controller->url_for($href, ['imt' => mktime(12, 0, 0, date('n', $imt) - 1, 1, date('Y', $imt))]) ?>">
- <?= Icon::create('arr_1left', 'clickable', ['title' => _('Einen Monat zurück')])->asImg() ?>
- </a>
- <? endif; ?>
- </div>
- <div class="precol1w" style="float:left; text-align:center; width:70%;">
- <?= sprintf("%s %s\n", strftime('%B', $imt), date('Y', $imt)) ?>
- </div>
- <div style="float:right; width:15%;">
- <? if ($mod == 'NONAVARROWS') : ?>
- &nbsp;
- <? else : ?>
- <a href="<?= $controller->url_for($href, ['imt' => mktime(12, 0, 0, date('n', $imt) + 1, 1, date('Y', $imt))]) ?>">
- <?= Icon::create('arr_1right', 'clickable', ['title' => _('Einen Monat vor')])->asImg() ?>
- </a>
- <a href="<?= $controller->url_for($href, ['imt' => mktime(12, 0, 0, date('n', $imt), 1, date('Y', $imt) + 1)]) ?>">
- <?= Icon::create('arr_2right', 'clickable', ['title' => _('Ein Jahr vor')])->asImg() ?>
- </a>
- <? endif; ?>
- </div>
- </td>
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- <td class="blank">
- <table class="blank">
- <tr>
- <? $week_days = [39092400, 39178800, 39265200, 39351600, 39438000, 39524400, 39610800]; ?>
- <? foreach ($week_days as $week_day) : ?>
- <td align="center" class="precol2w" width="25">
- <?= strftime('%a', $week_day) ?>
- </td>
- <? endforeach; ?>
- <td class="precol2w" width="25"> </td>
- </tr>
- <? $adow = date('w', mktime(12, 0, 0, date('n', $imt), 1, date('Y', $imt))); ?>
- <? if ($adow == 0) : ?>
- <? $adow = 6; ?>
- <? else : ?>
- <? $adow--; ?>
- <? endif; ?>
- <? $first_day = mktime(12, 0, 0, date('n', $imt), 1, date('Y', $imt)) - $adow * 86400; ?>
- <? $cor = 0; ?>
- <? if (date('n', $imt) == 3) : ?>
- <? $cor = 1; ?>
- <? endif; ?>
- <? $last_day = ((42 - ($adow + date('t', mktime(12, 0, 0, date('n', $imt), 1, date('Y', $imt))))) % 7 + $cor) * 86400
- + mktime(12, 0, 0, date('n', $imt), date('t', $imt), date('Y', $imt)); ?>
- <? for ($i = $first_day, $j = 0; $i <= $last_day; $i += 86400, $j++) : ?>
- <?
- $aday = date('j', $i);
- $style = '';
- if (($aday - $j - 1 > 0) || ($j - $aday > 6)) {
- $style = 'light';
- }
- $hday = holiday($i);
- ?>
- <? if (abs($now - $i) < 43199 && !($style == 'light')) : ?>
- <td class="celltoday" align="center" width="25" height="25">
- <? elseif (date('m', $i) != date('n', $imt)) : ?>
- <td class="lightmonth" align="center" width="25" height="25">
- <? else : ?>
- <td class="month" align="center" width="25" height="25">
- <? endif; ?>
- <? $js_inc = ''; ?>
- <? if (!empty($js_include) && is_array($js_include)) : ?>
- <?
- $js_inc = " onClick=\"{$js_include['function']}(";
- if (sizeof($js_include['parameters'])) {
- $js_inc .= implode(", ", $js_include['parameters']) . ", ";
- }
- $js_inc .= "'" . date('m', $i) . "', '$aday', '" . date('Y', $i) . "')\"";
- ?>
- <? endif; ?>
- <? if (abs($atime - $i) < 43199) : ?>
- <? $aday = '<span class="current">'.$aday.'</span>' ?>
- <? endif; ?>
- <? if (($j + 1) % 7 == 0) : ?>
- <a class="<?= $style ?>sday" href="<?= $controller->url_for($href, ['atime' => $i]) ?>" <?= is_array($hday) ? tooltip($hday['name'] ?: '') : '' ?> <?= $js_inc ?>>
- <?= $aday ?>
- </a>
- </td>
- <td class="lightmonth" style="text-align: center; width: 25px; height: 25px;">
- <a href="<?= $controller->url_for('calendar/single/week/', ['atime' => $i]) ?>">
- <span class="kwmin"><?= strftime('%V', $i) ?></span>
- </a>
- </td>
- </tr>
- <? else : ?>
- <? if (is_array($hday)) : ?>
- <? switch ($hday['col']) {
- case 1:
- ?><a class="<?= $style ?>day" href="<?= $controller->url_for($href, ['atime' => $i]) ?>" <?= tooltip($hday['name']) . $js_inc ?>>
- <?= $aday ?>
- </a><?
- break;
- case 2:
- case 3;
- ?><a class="<?= $style ?>hday" href="<?= $controller->url_for($href, ['atime' => $i]) ?>" <?= tooltip($hday['name']) . $js_inc ?>>
- <?= $aday ?>
- </a><?
- break;
- default:
- ?><a class="<?= $style ?>day" href="<?= $controller->url_for($href, ['atime' => $i]) ?>" <?= $js_inc ?>>
- <?= $aday ?>
- </a>
- <?}?>
- <? else : ?>
- <a class="<?= $style ?>day" href="<?= $controller->url_for($href, ['atime' => $i]) ?>" <?= $js_inc ?>>
- <?= $aday ?>
- </a>
- <? endif ?>
- </td>
- <? endif; ?>
- <? endfor; ?>
- </table>
- </td>
- </tr>
-</table>
diff --git a/app/views/calendar/single/_jump_to.php b/app/views/calendar/single/_jump_to.php
deleted file mode 100644
index b5e96bb..0000000
--- a/app/views/calendar/single/_jump_to.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<form class="default" action="<?= $action_url ?>" method="post" name="jump_to">
- <input type="hidden" name="action" value="<?= $action ?>">
-
- <section class="hgroup">
- <?= _('Gehe zu:') ?>
- <input size="10" style="width: 16em;" type="text" id="jmp_date" name="jmp_date" type="text" value="<?= strftime('%x', $atime)?>">
- <?= Icon::create('accept', 'clickable')->asInput(['class' => 'text-top']) ?>
- </section>
-</form>
-
-<script>
- jQuery('#jmp_date').datepicker();
-</script>
diff --git a/app/views/calendar/single/_select_calendar.php b/app/views/calendar/single/_select_calendar.php
deleted file mode 100644
index 7b2b942..0000000
--- a/app/views/calendar/single/_select_calendar.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<form name="select_calendars" class="default" method="post" action="<?= htmlReady($action_url) ?>">
-
- <section class="hgroup">
- <?= _('Kalender') ?>
- <select class="sidebar-selectlist submit-upon-select" style="width: 16em;" name="range_id">
- <option value="<?= get_userid() ?>"<?= get_userid() === $range_id ? ' selected' : '' ?>>
- <?= _("Eigener Kalender") ?>
- </option>
- <? $groups = Calendar::getGroups($GLOBALS['user']->id); ?>
- <? if (count($groups)) : ?>
- <optgroup style="font-weight:bold;" label="<?= _('Gruppenkalender:') ?>">
- <? foreach ($groups as $group) : ?>
- <option value="<?= $group->getId() ?>"<?= ($range_id == $group->getId() ? ' selected' : '') ?>>
- <?= htmlReady($group->name) ?>
- </option>
- <? endforeach ?>
- </optgroup>
- <? endif; ?>
- <? $calendar_users = CalendarUser::getOwners($GLOBALS['user']->id)->getArrayCopy(); ?>
- <? usort($calendar_users, function ($a, $b) {
- return strnatcmp($a->owner->Nachname, $b->owner->Nachname);
- }); ?>
- <? if (count($calendar_users)) : ?>
- <optgroup style="font-weight:bold;" label="<?= _('Einzelkalender:') ?>">
- <? foreach ($calendar_users as $calendar_user) : ?>
- <? if (!$calendar_user->owner) {
- continue;
- } ?>
- <option value="<?= $calendar_user->owner_id ?>"<?= ($range_id == $calendar_user->owner_id ? ' selected' : '') ?>>
- <?= htmlReady($calendar_user->owner->getFullname('full_rev')) ?>
- </option>
- <? endforeach ?>
- </optgroup>
- <? endif ?>
- <?/*
- if ($GLOBALS['perm']->have_perm('dozent')) {
- $lecturers = Calendar::GetLecturers();
- } else {
- $lecturers = array();
- }
- */
- $lecturers = [];
- ?>
- <? if (count($lecturers)) : ?>
- <optgroup style="font-weight:bold;" label="<?= _('Lehrendenkalender:') ?>">
- <? foreach ($lecturers as $lecturer) : ?>
- <option value="<?= $lecturer['id'] ?>"<?= ($range_id == $lecturer['id'] ? ' selected' : '') ?>>
- <?= htmlReady(my_substr($lecturer['name'] . " ({$lecturer['username']})", 0, 30)) ?>
- </option>
- <? endforeach ?>
- </optgroup>
- <? endif ?>
- <? if (Config::get()->COURSE_CALENDAR_ENABLE) : ?>
- <? $courses = Calendar::GetCoursesActivatedCalendar($GLOBALS['user']->id); ?>
- <? if (count($courses)) : ?>
- <optgroup style="font-weight:bold;" label="<?= _('Veranstaltungskalender:') ?>">
- <? foreach ($courses as $course) : ?>
- <option value="<?= $course->id ?>"<?= ($range_id == $course->id ? ' selected' : '') ?>>
- <?= htmlReady($course->getFullname()) ?>
- </option>
- <? endforeach ?>
- </optgroup>
- <? endif ?>
- <? $insts = Calendar::GetInstituteActivatedCalendar($GLOBALS['user']->id); ?>
- <? if (count($insts)) : ?>
- <optgroup style="font-weight:bold;" label="<?= _('Einrichtungskalender:') ?>">
- <? foreach ($insts as $inst_id => $inst_name) : ?>
- <option value="<?= $inst_id ?>"<?= ($range_id == $inst_id ? ' selected' : '') ?>>
- <?= htmlReady(my_substr($inst_name, 0, 30)); ?>
- </option>
- <? endforeach ?>
- </optgroup>
- <? endif ?>
- <? endif ?>
- </select>
-
- <input type="hidden" name="view" value="<?= $view ?>">
- <?= Icon::create('accept', 'clickable')->asInput(['class' => "text-top"]) ?>
- </section>
-</form>
diff --git a/app/views/calendar/single/_select_category.php b/app/views/calendar/single/_select_category.php
deleted file mode 100644
index fe86bdc..0000000
--- a/app/views/calendar/single/_select_category.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<form class="default" name="filter_categories" method="post" action="<?= $action_url ?>">
-
- <section class="hgroup">
- <?= _('Kategorie') ?>
- <select class="sidebar-selectlist nested-select submit-upon-select" style="width: 16em;" name="category">
- <option value=""><?= _('Alle Kategorien') ?></option>
- <? foreach (Config::get()->getValue('PERS_TERMIN_KAT') as $key => $cat) : ?>
- <option value="<?= $key ?>"<?= ($category == $key ? ' selected="selected"' : '') ?> class="calendar-category<?= $key ?>" data-color-class="calendar-category<?= $key ?>">
- <?= htmlReady($cat['name']) ?>
- </option>
- <? endforeach; ?>
- </select>
-
- <?= Icon::create('accept', 'clickable')->asInput(['class' => "text-top"]) ?>
- </section>
-</form>
diff --git a/app/views/calendar/single/_semester_filter.php b/app/views/calendar/single/_semester_filter.php
deleted file mode 100644
index 016f1e5..0000000
--- a/app/views/calendar/single/_semester_filter.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<form data-dialog action="<?= $controller->url_for('calendar/single/seminar_events/')?>" class="default">
- <section class="hgroup">
- <label>
- <?= _('Semesterfilter') ?>:
- <select name="sem_select" class="submit-upon-select">
- <option <?= ($sem == 'current' ? 'selected' : '')?> value="current"><?= _('Aktuelles Semester') ?></option>
- <option <?= ($sem == 'future' ? 'selected' : '')?> value="future"><?= _('Aktuelles und nächstes Semester') ?></option>
- <option <?= ($sem == 'last' ? 'selected' : '')?> value="last"><?= _('Aktuelles und letztes Semester') ?></option>
- <option <?= ($sem == 'lastandnext' ? 'selected' : '')?> value="lastandnext"><?= _('Letztes, aktuelles, nächstes Semester') ?></option>
- <? if (Config::get()->MY_COURSES_ENABLE_ALL_SEMESTERS) : ?>
- <option <?= ($sem == 'all' ? 'selected' : '')?> value="all"><?= _('Alle Semester') ?></option>
- <? endif ?>
-
- <? if (!empty($semesters)) : ?>
- <optgroup label="<?=_('Semester auswählen')?>">
- <? foreach ($semesters as $semester) :?>
- <option value="<?=$semester->id?>" <?= ($sem == $semester->id ? 'selected' : '')?>>
- <?= htmlReady($semester->name)?>
- </option>
- <? endforeach ?>
- </optgroup>
- <? endif ?>
- </select>
- </label>
- </section>
-
- <noscript>
- <?= \Studip\Button::createAccept(_('Auswählen'))?>
- </noscript>
-</form>
diff --git a/app/views/calendar/single/_tooltip.php b/app/views/calendar/single/_tooltip.php
deleted file mode 100644
index 0979ec2..0000000
--- a/app/views/calendar/single/_tooltip.php
+++ /dev/null
@@ -1,113 +0,0 @@
-<div class="calendar-tooltip tooltip-content">
- <h4><?= htmlReady($event->getTitle()) ?></h4>
- <div>
- <b><?= _('Beginn') ?>:</b> <?= strftime('%c', $event->getStart()) ?>
- </div>
- <div>
- <b><?= _('Ende') ?>:</b> <?= strftime('%c', $event->getEnd()) ?>
- </div>
- <? if ($event->havePermission(Event::PERMISSION_READABLE)) : ?>
- <? if ($event instanceof CourseEvent) : ?>
- <div>
- <b><?= _('Veranstaltung') ?>:</b> <?= htmlReady($event->course->getFullname()) ?>
- </div>
- <? endif;?>
- <? if ($text = $event->getDescription()) : ?>
- <div>
- <b><?= _('Beschreibung') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->toStringCategories()) : ?>
- <div>
- <b><?= _('Kategorie') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->getLocation()) : ?>
- <div>
- <b><?= _('Raum/Ort') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->toStringPriority()) : ?>
- <div>
- <b><?= _('Priorität') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->toStringAccessibility()) : ?>
- <div>
- <b><?= _('Zugriff') ?>:</b> <?= htmlReady(mila($text, 50)) ?>
- </div>
- <? endif; ?>
- <? if ($text = $event->toStringRecurrence()) : ?>
- <div>
- <b><?= _('Wiederholung') ?>:</b> <?= htmlReady($text) ?>
- </div>
- <? endif; ?>
- <? endif; ?>
- <? if ($event->havePermission(Event::PERMISSION_READABLE)) : ?>
- <? if ($event instanceof CalendarEvent
- && Config::get()->CALENDAR_GROUP_ENABLE
- && $calendar->getRange() == Calendar::RANGE_USER) : ?>
- <? $group_status = [
- CalendarEvent::PARTSTAT_TENTATIVE => _('Abwartend'),
- CalendarEvent::PARTSTAT_ACCEPTED => _('Angenommen'),
- CalendarEvent::PARTSTAT_DECLINED => _('Abgelehnt'),
- CalendarEvent::PARTSTAT_DELEGATED => _('Angenommen (keine Teilnahme)'),
- CalendarEvent::PARTSTAT_NEEDS_ACTION => ''] ?>
- <? $show_members = $event->attendees->findOneBy('range_id',
- $calendar->getRangeId(), '!=') ?>
- <? // Entkommentieren, wenn Mitglieder eines Termins sichtbar sein
- // sollen, auch wenn man nicht selbst Mitglied ist und ... ?>
- <? // $show_members_visiter = $event->attendees->findOneBy('range_id', $GLOBALS['user']->id) ?>
- <? // folgende Zeile auskommentieren (siehe _attendees.php). ?>
- <? $show_members_visiter = true; ?>
- <? if ($show_members && $show_members_visiter) : ?>
- <div>
- <b><?= _('Teilnehmende:') ?></b>
- <?= implode(', ', $event->attendees->map(
- function ($att) use ($event, $group_status) {
- if ($event->havePermission(Event::PERMISSION_OWN, $att->owner->id)) {
- $ret = htmlReady($att->owner->getFullname())
- . ' (' . _('Organisator') . ')';
- } else {
- $ret = htmlReady($att->owner->getFullname());
- if ($group_status[$att->group_status]) {
- $ret .= ' (' . $group_status[$att->group_status] . ')';
- }
- }
- return $ret;
- })); ?>
- </div>
- <? endif; ?>
- <? endif; ?>
- <? if ($event instanceof CourseEvent) : ?>
- <? // durchführende Lehrende ?>
- <? $related_persons = $event->dozenten; ?>
- <? if (sizeof($related_persons)) : ?>
- <div>
- <b><?= ngettext('Durchführende Lehrperson', 'Durchführende Lehrende', sizeof($related_persons)) ?>:</b>
- <ul class="list-unstyled">
- <? foreach ($related_persons as $related_person) : ?>
- <li>
- <?= htmlReady($related_person->getFullName()) ?>
- </li>
- <? endforeach; ?>
- </ul>
- </div>
- <? endif; ?>
- <? // related groups ?>
- <? $related_groups = $event->getRelatedGroups(); ?>
- <? if (sizeof($related_groups)) : ?>
- <div>
- <b><?= _('Betroffene Gruppen') ?>:</b>
- <ul class="list-unstyled">
- <? foreach ($related_groups as $group) : ?>
- <li>
- <?= htmlReady($group->name) ?>
- </li>
- <? endforeach; ?>
- </ul>
- </div>
- <? endif; ?>
- <? endif; ?>
- <? endif; ?>
-</div>
diff --git a/app/views/calendar/single/day.php b/app/views/calendar/single/day.php
deleted file mode 100644
index 448509c..0000000
--- a/app/views/calendar/single/day.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<div style="width: 100%; display: flex; flex-wrap: wrap;">
- <div style="flex-grow:2; flex-basis: 60%;">
- <?= $this->render_partial('calendar/single/_day'); ?>
- </div>
- <div class="hidden-medium-down" style="flex-grow:1; padding-left:1em;">
- <? $imt = Request::int('imt', mktime(12, 0, 0, date('n', $atime) - 1, date('j', $atime), date('Y', $atime))) ?>
- <?= $this->render_partial('calendar/single/_include_month', ['imt' => $imt, 'href' => '', 'mod' => '']) ?>
- <? $imt = mktime(12, 0, 0, date('n', $imt) + 1, date('j', $imt), date('Y', $imt)) ?>
- <?= $this->render_partial('calendar/single/_include_month', ['imt' => $imt, 'href' => '', 'mod' => 'NONAVARROWS']) ?>
- <? $imt = mktime(12, 0, 0, date('n', $imt) + 1, date('j', $imt), date('Y', $imt)) ?>
- <?= $this->render_partial('calendar/single/_include_month', ['imt' => $imt, 'href' => '', 'mod' => 'NONAVARROWS']) ?>
- </div>
-</div>
diff --git a/app/views/calendar/single/edit.php b/app/views/calendar/single/edit.php
deleted file mode 100644
index c22f7a6..0000000
--- a/app/views/calendar/single/edit.php
+++ /dev/null
@@ -1,454 +0,0 @@
-<? use Studip\Button, Studip\LinkButton; ?>
-<? if (Request::isXhr()) : ?>
- <? foreach (PageLayout::getMessages() as $messagebox) : ?>
- <?= $messagebox ?>
- <? endforeach ?>
-<? endif; ?>
-<form data-dialog="" method="post" action="<?= $controller->url_for($base . 'edit/' . $range_id . '/' . $event->event_id) ?>" class="default collapsable">
- <?= CSRFProtection::tokenTag() ?>
-
- <fieldset>
- <legend>
- <? if ($event->isNew()) : ?>
- <?= _('Neuen Termin anlegen') ?>
- <? else : ?>
- <?= _('Termin bearbeiten') ?>
- <? endif; ?>
- </legend>
-
- <label class="hidden-tiny-down">
- <input type="checkbox" name="isdayevent" value="1" <?= $event->isDayEvent() ? 'checked' : '' ?>
- onChange="jQuery(this).closest('fieldset').find('input[size=\'2\']').prop('disabled', function (i,val) { return !val; });">
- <?= _('Ganztägig') ?>
- </label>
-
- <section class="required">
- <?= _('Beginn') ?>
- </section>
-
- <label class="col-3">
- <?= _('Datum') ?>
- <input type="text" name="start_date" id="start-date" value="<?= strftime('%x', $event->getStart()) ?>" size="12" required>
- </label>
-
- <label class="col-3">
- <?= _('Uhrzeit') ?>
-
- <div class="hgroup">
- <input class="size-s no-hint"
- type="text"
- name="start_hour"
- value="<?= date('G', $event->getStart()) ?>"
- size="2"
- maxlength="2"<?= $event->isDayEvent() ? ' disabled' : '' ?>
- aria-label="Stunde">
- :
- <input class="size-s no-hint"
- type="text"
- name="start_minute"
- value="<?= date('i', $event->getStart()) ?>"
- size="2"
- maxlength="2"<?= $event->isDayEvent() ? ' disabled' : '' ?>
- aria-label="Minuten">
- </div>
- </label>
-
- <section class="required">
- <?= _('Ende') ?>
- </section>
-
- <label class="col-3">
- <?= _('Datum') ?>
- <input type="text" name="end_date" id="end-date" value="<?= strftime('%x', $event->getEnd()) ?>" size="12" required>
- </label>
-
- <label class="col-3">
- <?= _('Uhrzeit') ?>
-
- <div class="hgroup">
- <input class="size-s no-hint"
- type="text"
- name="end_hour"
- value="<?= date('G', $event->getEnd()) ?>"
- size="2"
- aria-label="<?= _("Stunde") ?>"
- maxlength="2"<?= $event->isDayEvent() ? ' disabled' : '' ?>>
- :
- <input class="size-s no-hint"
- type="text"
- name="end_minute"
- value="<?= date('i', $event->getEnd()) ?>"
- size="2"
- aria-label="<?= _("Minuten") ?>"
- maxlength="2"<?= $event->isDayEvent() ? ' disabled' : '' ?>>
- </div>
- </label>
-
- <label>
- <span class="required">
- <?= _('Zusammenfassung') ?>
- </span>
-
- <input type="text" size="50" name="summary" id="summary" value="<?= htmlReady($event->getTitle()) ?>">
- </label>
-
- <label>
- <?= _('Beschreibung') ?>
- <textarea rows="2" cols="40" id="description" name="description"><?= htmlReady($event->getDescription()) ?></textarea>
- </label>
-
- <label class="col-3">
- <?= _('Kategorie') ?>
- <select name="category_intern" id="category-intern" class="nested-select">
- <? foreach ($GLOBALS['PERS_TERMIN_KAT'] as $key => $category) : ?>
- <option value="<?= $key ?>" <?= $key == $event->getCategory() ? 'selected' : '' ?> class="calendar-category<?= $key ?>" data-color-class="calendar-category<?= $key ?>">
- <?= htmlReady($category['name']) ?>
- </option>
- <? endforeach; ?>
- </select>
- </label>
-
- <label class="col-3">
- <?= tooltipicon(_('Sie können beliebige Kategorien in das Freitextfeld eingeben. Trennen Sie einzelne Kategorien bitte durch ein Komma.')) ?>
- <input type="text" name="categories" value="<?= htmlReady($event->getUserDefinedCategories()) ?>"
- placeholder="<?= _('Eigener Kategoriename') ?>">
- </label>
-
- <label>
- <?= _('Raum/Ort') ?>
- <input type="text" name="location" id="location" value="<?= htmlReady($event->getLocation()) ?>">
- </label>
-
- <? if ($calendar->getPermissionByUser($GLOBALS['user']->id) == Calendar::PERMISSION_OWN) : ?>
- <? $info = _('Private und vertrauliche Termine sind nur für Sie sichtbar.') ?>
-
- <? /* SEMBBS nur private und vertrauliche Termine
- <? $info = _('Private und vertrauliche Termine sind nur für Sie sichtbar. Öffentliche Termine werden auf ihrer internen Homepage auch anderen Nutzern bekanntgegeben.') ?>
- *
- */ ?>
-
- <? elseif ($calendar->getRange() == Calendar::RANGE_SEM) : ?>
- <? $info = _('In Veranstaltungskalendern können nur private Termine angelegt werden.') ?>
- <? elseif ($calendar->getRange() == Calendar::RANGE_INST) : ?>
- <? $info = _('In Einrichtungskalendern können nur private Termine angelegt werden.') ?>
- <? else : ?>
- <? $info = _('Im Kalender eines anderen Nutzers können Sie nur private oder vertrauliche Termine einstellen. Vertrauliche Termine sind nur für Sie und den Kalenderbesitzer sichtbar. Alle anderen sehen den Termin nur als Besetztzeit.') ?>
- <? endif; ?>
-
- <label for="accessibility">
- <?= _('Zugriff') ?>
- <?= tooltipicon($info) ?>
-
- <select name="accessibility" id="accessibility" size="1">
- <? foreach ($event->getAccessibilityOptions($calendar->getPermissionByUser($GLOBALS['user']->id)) as $key => $option) : ?>
- <option value="<?= $key ?>"<?= $event->getAccessibility() == $key ? ' selected' : '' ?>><?= $option ?></option>
- <? endforeach; ?>
- </select>
- </label>
-
- <label>
- <?= _('Priorität') ?>
-
- <? $priority_names = [_('Keine Angabe'), _('Hoch'), _('Mittel'), _('Niedrig')] ?>
- <select name="priority" id="priority" size="1">
- <? foreach ($priority_names as $key => $priority) : ?>
- <option value="<?= $key ?>"<?= $key == $event->getPriority() ? ' selected' : '' ?>><?= $priority ?></option>
- <? endforeach; ?>
- </select>
- </label>
-
- <? if (!$event->isNew() && Config::get()->CALENDAR_GROUP_ENABLE) : ?>
- <section>
- <? $author = $event->getAuthor() ?>
- <? if ($author) : ?>
- <?= sprintf(_('Eingetragen am: %s von %s'),
- strftime('%x, %X', $event->mkdate),
- htmlReady($author->getFullName('no_title'))) ?>
- <? endif; ?>
- </section>
- <? if ($event->event->mkdate < $event->event->chdate) : ?>
- <? $editor = $event->getEditor() ?>
- <? if ($editor) : ?>
- <section>
- <?= sprintf(_('Zuletzt bearbeitet am: %s von %s'),
- strftime('%x, %X', $event->chdate),
- htmlReady($editor->getFullName('no_title'))) ?>
- </section>
- <? endif; ?>
- <? endif; ?>
- <? endif; ?>
- </fieldset>
-
-
- <fieldset class="collapsed">
- <legend>
- <?= _('Wiederholung') ?>
- <? if ($event->getRecurrence('rtype') != 'SINGLE') : ?>
- (<?= $event->toStringRecurrence() ?>)
- <? endif ?>
- </legend>
-
- <h2><?= _('Wiederholungsart') ?></h2>
-
- <section>
- <? $linterval = $event->getRecurrence('linterval') ?: '1' ?>
- <? $rec_type = $event->toStringRecurrence(true) ?>
- <ul class="recurrences">
- <li>
- <label class="rec-label">
- <input type="radio" class="rec-select" id="rec-none" name="recurrence" value="single"<?= $event->getRecurrence('rtype') == 'SINGLE' ? ' checked' : '' ?>>
- <?= _('Keine') ?>
- <?= tooltipIcon(_('Der Termin wird nicht wiederholt.')) ?>
- </label>
- </li>
- <li>
- <label class="rec-label">
- <input type="radio" class="rec-select" id="rec-daily" name="recurrence" value="daily"<?= $event->getRecurrence('rtype') == 'DAILY' ? ' checked' : '' ?>>
- <?= _('Täglich') ?>
- </label>
-
- <div class="rec-content" id="rec-content-daily">
- <div class="hgroup">
- <label>
- <input type="radio" name="type_daily" value="day"<?= in_array($rec_type, ['daily', 'xdaily']) ? ' checked' : '' ?>>
- <?= sprintf(_('Jeden %s. Tag'), '<input type="text" size="3" name="linterval_d" value="' . $linterval . '">') ?>
- </label>
- </div>
-
- <label>
- <input type="radio" name="type_daily" value="workday"<?= $rec_type == 'workdaily' ? ' checked' : '' ?>>
- <?= _('Jeden Werktag') ?>
- </label>
- </div>
- </li>
- <li>
- <? $wdays = [
- '1' => _('Montag'),
- '2' => _('Dienstag'),
- '3' => _('Mittwoch'),
- '4' => _('Donnerstag'),
- '5' => _('Freitag'),
- '6' => _('Samstag'),
- '7' => _('Sonntag')] ?>
- <label class="rec-label" for="rec-weekly">
- <input type="radio" class="rec-select" id="rec-weekly" name="recurrence" value="weekly"<?= $event->getRecurrence('rtype') == 'WEEKLY' ? ' checked' : '' ?>>
- <?= _('Wöchentlich') ?>
- </label>
- <div class="rec-content" id="rec-content-weekly">
- <div class="hgroup">
- <label>
- <?= sprintf(_('Jede %s. Woche am:'), '<input type="text" size="3" name="linterval_w" value="' . $linterval . '">') ?>
- </label>
- </div>
- <div>
- <? $aday = $event->getRecurrence('wdays') ?: date('N', $event->getStart()) ?>
- <? foreach ($wdays as $key => $wday) : ?>
- <label style="white-space: nowrap;">
- <input type="checkbox" name="wdays[]" value="<?= $key ?>"<?= mb_strpos((string) $aday, (string) $key) !== false ? ' checked' : '' ?>>
- <?= $wday ?>
- </label>
- <? endforeach; ?>
- </div>
- </div>
- </li>
- <li>
- <? $mdays = [
- '1' => _('Ersten'),
- '2' => _('Zweiten'),
- '3' => _('Dritten'),
- '4' => _('Vierten'),
- '5' => _('Letzten')] ?>
- <? $mdays_options = '' ?>
- <? $mday_selected = $event->getRecurrence('sinterval') ?>
- <? foreach ($mdays as $key => $mday) :
- $mdays_options .= '<option value="' . $key . '"';
- if ($key == $mday_selected) {
- $mdays_options .= ' selected';
- }
- $mdays_options .= '>' . $mday . '</option>';
- endforeach; ?>
- <? $wdays_options = '' ?>
- <? $wday_selected = $event->getRecurrence('wdays') ?: date('N', $event->getStart()) ?>
- <? foreach ($wdays as $key => $wday) :
- $wdays_options .= '<option value="' . $key . '"';
- if ($key == $wday_selected) {
- $wdays_options .= ' selected';
- }
- $wdays_options .= '>' . $wday . '</option>';
- endforeach; ?>
-
- <label class="rec-label" for="rec-monthly">
- <input type="radio" class="rec-select" id="rec-monthly" name="recurrence" value="monthly"<?= $event->getRecurrence('rtype') == 'MONTHLY' ? ' checked' : '' ?>>
- <?= _('Monatlich') ?>
- </label>
- <div class="rec-content" id="rec-content-monthly">
- <div class="hgroup">
- <label>
- <input type="radio" value="day" name="type_m"<?= in_array($rec_type, ['mday_monthly', 'mday_xmonthly']) ? ' checked' : '' ?>>
- <? $mday = $event->getRecurrence('day') ?: date('j', $event->getStart()) ?>
- <?= sprintf(_('Wiederholt am %s. jeden %s. Monat'),
- '<input type="text" name="day_m" size="2" value="'
- . $mday . '">',
- '<input type="text" name="linterval_m1" size="3" value="'
- . $linterval . '">') ?>
- </label>
- </div>
- <div class="hgroup">
- <label>
- <input type="radio" value="wday" name="type_m"<?= in_array($rec_type, ['xwday_xmonthly', 'lastwday_xmonthly', 'xwday_monthly', 'lastwday_monthly']) ? ' checked' : '' ?>>
- <?= sprintf(_('Jeden %s alle %s Monate'),
- '<select size="1" name="sinterval_m">' . $mdays_options . '</select>'
- . '<select size="1" name="wday_m">' . $wdays_options . '</select>',
- '<input type="text" class="no-hint" size="3" maxlength="3" name="linterval_m2" value="'
- . $linterval . '">')?>
- </label>
- </div>
- </div>
- </li>
- <li>
- <? $months = [
- '1' => _('Januar'),
- '2' => _('Februar'),
- '3' => _('März'),
- '4' => _('April'),
- '5' => _('Mai'),
- '6' => _('Juni'),
- '7' => _('Juli'),
- '8' => _('August'),
- '9' => _('September'),
- '10' => _('Oktober'),
- '11' => _('November'),
- '12' => _('Dezember')] ?>
- <? $months_options = '' ?>
- <? $month_selected = $event->getRecurrence('month') ?: date('n', $event->getStart()) ?>
- <? foreach ($months as $key => $month) :
- $months_options .= '<option value="' . $key . '"';
- if ($key == $month_selected) {
- $months_options .= ' selected';
- }
- $months_options .= '>' . $month . '</option>';
- endforeach; ?>
-
- <label class="rec-label" for="rec-yearly">
- <input type="radio" class="rec-select" id="rec-yearly" name="recurrence" value="yearly"<?= $event->getRecurrence('rtype') == 'YEARLY' ? ' checked' : '' ?>>
- <?= _('Jährlich') ?>
- </label>
- <div class="rec-content" id="rec-content-yearly">
- <div class="hgroup">
- <label>
- <input type="radio" value="day" name="type_y"<?= $rec_type == 'mday_month_yearly' ? ' checked' : '' ?>>
- <?= sprintf(_('Jeden %s. %s'),
- '<input type="text" size="2" maxlength="2" name="day_y" value="'
- . ($event->getRecurrence('day') ?: date('j', $event->getStart())) . '">',
- '<select size="1" name="month_y1">' . $months_options . '</select>') ?>
- </label>
- </div>
-
- <div class="hgroup">
- <label>
- <input type="radio" value="wday" name="type_y"<?= in_array($rec_type, ['xwday_month_yearly', 'lastwday_month_yearly']) ? ' checked' : '' ?>>
- <?= sprintf(_('Jeden %s im %s'),
- '<select size="1" name="sinterval_y">' . $mdays_options . '</select>'
- . '<select size="1" name="wday_y">' . $wdays_options . '</select>',
- '<select size="1" name="month_y2">' . $months_options . '</select>') ?>
- </label>
- </div>
- </div>
- </li>
- </ul>
- </section>
-
- <h2><?= _('Wiederholung endet') ?></h2>
-
- <label>
- <? $checked = (!$event->getRecurrence('expire') || $event->getRecurrence('expire') >= Calendar::CALENDAR_END) && !$event->getRecurrence('count') ?>
- <input type="radio" name="exp_c" value="never"<?= $checked ? ' checked' : '' ?>>
- <?= _('Nie') ?>
- </label>
-
- <? $checked = $event->getRecurrence('expire') && $event->getRecurrence('expire') < Calendar::CALENDAR_END && !$event->getRecurrence('count') ?>
-
- <section class="hgroup">
- <label>
- <input type="radio" name="exp_c" value="date"<?= $checked ? ' checked' : '' ?>>
- <? $exp_date = $event->getRecurrence('expire') != Calendar::CALENDAR_END ? $event->getRecurrence('expire') : $event->getEnd() ?>
- <?= sprintf(_('Am %s'),
- '<input type="text" class="size-s" name="exp_date" id="exp-date" value="'
- . strftime('%x', $exp_date) . '">') ?>
- </label>
- </section>
-
- <section class="hgroup">
- <? $checked = $event->getRecurrence('count') ?>
- <label>
- <input type="radio" name="exp_c" value="count"<?= $checked ? ' checked' : '' ?>>
- <? $exp_count = $event->getRecurrence('count') ?: '10' ?>
- <?= sprintf(_('Nach %s Wiederholungen'),
- '<input type="text" size="5" name="exp_count" value="'
- . $exp_count . '">') ?>
- </label>
- </section>
-
-
- <label for="exc-dates">
- <?= _('Ausnahmen') ?>
- </label>
-
- <ul id="exc-dates">
- <? $exceptions = $event->getExceptions(); ?>
- <? sort($exceptions, SORT_NUMERIC); ?>
- <? foreach ($exceptions as $exception) : ?>
- <li>
- <label class="undecorated">
- <input type="checkbox" name="del_exc_dates[]" value="<?= strftime('%d.%m.%Y', $exception) ?>" style="display: none;">
- <span><?= strftime('%x', $exception) ?><?= Icon::create('trash', 'clickable', ['title' => _('Ausnahme löschen')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?></span>
- </label>
- <input type="hidden" name="exc_dates[]" value="<?= strftime('%d.%m.%Y', $exception) ?>">
- </li>
- <? endforeach; ?>
- </ul>
-
- <div class="hgroup">
- <input style="vertical-align: top; opacity: 0.8;"
- type="text" size="12" name="exc_date" id="exc-date" value=""
- placeholder="<?= _("Datum eingeben") ?>">
- <span style="vertical-align: top;" onclick="STUDIP.CalendarDialog.addException(); return false;">
- <?= Icon::create('add', 'clickable', ['title' => _('Ausnahme hinzufügen')])->asInput(['class' => 'text-bottom']) ?>
- </span>
- </div>
- </fieldset>
-
- <? if (Config::get()->CALENDAR_GROUP_ENABLE && $calendar->getRange() == Calendar::RANGE_USER) : ?>
- <?= $this->render_partial('calendar/group/_attendees') ?>
- <? endif; ?>
-
- <footer data-dialog-button>
- <?= Button::create(_('Speichern'), 'store', ['title' => _('Termin speichern')]) ?>
-
- <? if (!$event->isNew()) : ?>
- <? if ($event->getRecurrence('rtype') != 'SINGLE') : ?>
- <?= LinkButton::create(_('Aus Serie löschen'), $controller->url_for('calendar/single/delete_recurrence/' . implode('/', $event->getId()) . '/' . $atime)) ?>
- <? endif; ?>
- <?= LinkButton::create(_('Löschen'), $controller->url_for('calendar/single/delete/' . implode('/', $event->getId()))) ?>
- <? endif; ?>
- <? if (!Request::isXhr()) : ?>
- <?= LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/single/' . $last_view, [$event->getStart()])) ?>
- <? endif; ?>
- </footer>
-</form>
-<script>
- jQuery('#start-date').datepicker({
- altField: '#end-date'
- });
- jQuery('#end-date').datepicker();
- jQuery('#exp-date').datepicker();
- jQuery('#exc-date').datepicker();
-
- $('ul.recurrences input[type=radio][id^=rec]').bind('change', function() {
- $('.rec-content').hide();
-
- if ($(this).is(':checked')) {
- $(this).parent().siblings('.rec-content').show();
- }
- })
-</script>
diff --git a/app/views/calendar/single/edit_status.php b/app/views/calendar/single/edit_status.php
deleted file mode 100644
index 3e3bfc3..0000000
--- a/app/views/calendar/single/edit_status.php
+++ /dev/null
@@ -1,3 +0,0 @@
-<form action="<?= $controller->url_for($base . 'edit_status/' . $range_id . '/' . $event->event_id) ?>" method="post">
- <?= $this->render_partial('calendar/single/_event_data') ?>
-</form>
diff --git a/app/views/calendar/single/event.php b/app/views/calendar/single/event.php
deleted file mode 100644
index e9d544f..0000000
--- a/app/views/calendar/single/event.php
+++ /dev/null
@@ -1 +0,0 @@
-<?= $this->render_partial('calendar/single/_event_data') ?>
diff --git a/app/views/calendar/single/export_calendar.php b/app/views/calendar/single/export_calendar.php
deleted file mode 100644
index 66f86c5..0000000
--- a/app/views/calendar/single/export_calendar.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<? use Studip\Button, Studip\LinkButton; ?>
-<? if (Request::isXhr()) : ?>
- <? foreach (PageLayout::getMessages() as $messagebox) : ?>
- <?= $messagebox ?>
- <? endforeach ?>
-<? endif; ?>
-<form action="<?= $controller->url_for('calendar/single/export_calendar/' . $calendar->getRangeId(), ['atime' => $atime, 'last_view' => $last_view]) ?>" method="post" name="sync_form" id="calendar_sync" class="default">
- <fieldset>
- <legend>
- <?= sprintf(_('Termine exportieren')) ?>
- </legend>
-
- <label for="event-type">
- <?= _('Welche Termine sollen exportiert werden') ?>:
-
- <select name="event_type" id="event-type" size="1">
- <option value="user" selected><?= _('Nur eigene Termine') ?></option>
- <option value="course"><?= _('Nur Veranstaltungs-Termine') ?></option>
- <option value="all"><?= _('Alle Termine') ?></option>
- </select>
- </label>
-
- <label>
- <input type="radio" name="export_time" value="all" id="export-all" checked>
- <?= _('Alle Termine exportieren') ?>
- </label>
-
- <label>
- <input type="radio" name="export_time" value="date" id="export-date">
- <?= _('Nur Termin in folgendem Zeitraum exportieren') ?>
- </label>
-
- <section class="hgroup">
- <? $start = strtotime('now') ?>
- <? $end = strtotime('+1 year') ?>
- <input id="export-start" type="text" name="export_start" class="no-hint"
- maxlength="10" class="hasDatepicker" value="<?= strftime('%x', $start) ?>">
- <input id="export-end" type="text" name="export_end" class="no-hint"
- maxlength="10" class="hasDatepicker" value="<?= strftime('%x', $end) ?>">
- </section>
- </fieldset>
-
- <footer data-dialog-button>
- <?= Button::createAccept(_('Termine exportieren'), 'export', ['title' => _('Termine exportieren')]) ?>
-
- <? if (!Request::isXhr()) : ?>
- <?= LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/single/' . $last_view)) ?>
- <? endif; ?>
- </footer>
-</form>
-<script>
- jQuery('#export-start').datepicker();
- jQuery('#export-end').datepicker();
-</script>
diff --git a/app/views/calendar/single/manage_access.php b/app/views/calendar/single/manage_access.php
deleted file mode 100644
index 4f7be8f..0000000
--- a/app/views/calendar/single/manage_access.php
+++ /dev/null
@@ -1,99 +0,0 @@
-<? if (Request::isXhr()) : ?>
- <? foreach (PageLayout::getMessages() as $messagebox) : ?>
- <?= $messagebox ?>
- <? endforeach ?>
-<? endif; ?>
-<form id="calendar-manage-access" data-dialog="" method="post" action="<?= $controller->url_for('calendar/single/store_permissions/' . $calendar->getRangeId()) ?>">
- <? CSRFProtection::tokenTag() ?>
- <? $perms = [1 => _('Keine'), 2 => _('Lesen'), 4 => _('Schreiben')] ?>
- <table class="default">
- <caption>
- <?= _('Bestehende Freigaben') ?>
- <span class="actions" style="font-size: 0.8em;">
- <label>
- <?= _('Auswahl') ?>:
- <select name="group_filter" size="1" class="submit-upon-select">
- <option value="list"<?= $group_filter_selected == 'list' ? ' selected' : '' ?>><?= _('Alle Personen anzeigen') ?></option>
- <? foreach ($filter_groups as $filter_group) : ?>
- <option value="<?= $filter_group->getId() ?>"<?= $group_filter_selected == $filter_group->getId() ? ' selected' : '' ?>><?= htmlReady($filter_group->name) ?></option>
- <? endforeach; ?>
- </select>
- </label>
- <?= Icon::create('accept', 'clickable')
- ->asInput([
- 'id' => "calendar-group-submit",
- 'name' => "calendar_group_submit",
- 'class' => "text-top"]) ?>
- <span style="padding-left: 1em;">
- <?= $mps->render() ?>
- </span>
- <script>
- STUDIP.MultiPersonSearch.init();
- </script>
- </span>
- </caption>
- <thead>
- <tr>
- <th>
- <?= _('Name') ?>
- </th>
- <th>
- <?= _('Berechtigung') ?>
- </th>
- <th>
- <?= _('Eigene Berechtigung') ?>
- </th>
- <th class="actions">
- <?= _('Aktionen') ?>
- </th>
- </tr>
- </thead>
- <tbody>
- <? foreach ($users as $header => $usergroup): ?>
- <tr id="letter_<?= $header ?>" class="calendar-user-head">
- <th colspan="4">
- <?= $header ?>
- </th>
- </tr>
- <? foreach ($usergroup as $user): ?>
- <tr id="contact_<?= $user->user_id ?>">
- <td>
- <?= ObjectdisplayHelper::avatarlink($user->user) ?>
- </td>
- <td style="white-space: nowrap;">
- <label>
- <input type="radio" name="perm[<?= $user->user_id ?>]" value="<?= Calendar::PERMISSION_FORBIDDEN ?>"
- <?= $user->permission < Calendar::PERMISSION_READABLE ? ' checked' : '' ?>>
- <?= _('Keine') ?>
- </label>
- <label>
- <input type="radio" name="perm[<?= $user->user_id ?>]" value="<?= Calendar::PERMISSION_READABLE ?>"
- <?= $user->permission == Calendar::PERMISSION_READABLE ? ' checked' : '' ?>>
- <?= _('Lesen') ?>
- </label>
- <label>
- <input type="radio" name="perm[<?= $user->user_id ?>]" value="<?= Calendar::PERMISSION_WRITABLE ?>"
- <?= $user->permission == Calendar::PERMISSION_WRITABLE ? ' checked' : '' ?>>
- <?= _('Schreiben') ?>
- </label>
- </td>
- <td>
- <?= $perms[$own_perms[$user->user_id]] ?>
- </td>
- <td class="actions">
- <a title="<?= _('Benutzer entfernen') ?>" onClick="STUDIP.CalendarDialog.removeUser(this);" href="<?= $controller->url_for('calendar/single/remove_user/' . $calendar->getRangeId(), ['user_id' => $user->user_id]) ?>">
- <?= Icon::create('trash', 'clickable')->asImg() ?>
- </a>
- </td>
- </tr>
- <? endforeach; ?>
- <? endforeach; ?>
- </tbody>
- </table>
- <div style="text-align: center;" data-dialog-button>
- <?= Studip\Button::create(_('Speichern'), 'store') ?>
- <? if (!Request::isXhr()) : ?>
- <?= Studip\LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/single/' . $last_view)) ?>
- <? endif; ?>
- </div>
-</form>
diff --git a/app/views/calendar/single/month.php b/app/views/calendar/single/month.php
deleted file mode 100644
index da715f5..0000000
--- a/app/views/calendar/single/month.php
+++ /dev/null
@@ -1,109 +0,0 @@
-<nav class="calendar-nav" style="vertical-align: middle">
- <span style="white-space: nowrap;">
- <a class="hidden-medium-down" style="padding-right: 2em;" href="<?= $controller->url_for('calendar/single/month', ['atime' => strtotime('-1 year', $atime)]) ?>">
- <?= Icon::create('arr_2left', 'clickable', ['title' => _('Ein Jahr zurück')])->asImg(['style' => 'vertical-align: text-top;']) ?>
- <?= strftime('%B %Y', strtotime('-1 year', $atime)) ?>
- </a>
- <a class="hidden-tiny-down" href="<?= $controller->url_for('calendar/single/month', ['atime' => strtotime('-1 month', $atime)]) ?>">
- <?= Icon::create('arr_1left', 'clickable', ['title' => _('Einen Monat zurück')])->asImg(['style' => 'vertical-align: text-top;']) ?>
- <?= strftime('%B %Y', strtotime('-1 month', $atime)) ?>
- </a>
- </span>
-
- <?
- $calType = 'month';
- $calLabel = htmlReady(strftime("%B ", $calendars[15]->getStart())) .' '. date('Y', $calendars[15]->getStart());
- ?>
-
- <?= $this->render_partial('calendar/single/_calhead', compact('atime', 'calType', 'calLabel')) ?>
-
- <span style="text-align: right; white-space: nowrap;">
- <a class="hidden-tiny-down" style="padding-right: 2em;" href="<?= $controller->url_for('calendar/single/month', ['atime' => strtotime('+1 month', $atime)]) ?>">
- <?= strftime('%B %Y', strtotime('+1 month', $atime)) ?>
- <?= Icon::create('arr_1right', 'clickable', ['title' => _('Einen Monat vor')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- </a>
- <a class="hidden-medium-down" href="<?= $controller->url_for('calendar/single/month', ['atime' => strtotime('+1 year', $atime)]) ?>">
- <?= strftime('%B %Y', strtotime('+1 year', $atime)) ?>
- <?= Icon::create('arr_2right', 'clickable', ['title' => _('Ein Jahr vor')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- </a>
- </span>
-</nav>
-
-<div class="table-scrollbox-horizontal">
-<table class="calendar-month">
- <thead>
- <tr class="calendar-month-weekdays">
- <? $week_days = [39092400, 39178800, 39265200, 39351600, 39438000, 39524400, 39610800]; ?>
- <? foreach ($week_days as $week_day) : ?>
- <td class="precol1w">
- <?= strftime('%a', $week_day) ?>
- </td>
- <? endforeach; ?>
- <td align="center" class="precol1w">
- <?= _('Woche') ?>
- </td>
- </tr>
- </thead>
- <tbody>
- <? for ($i = $first_day, $j = 0; $i <= $last_day; $i += 86400, $j++) : ?>
- <? $aday = date('j', $i); ?>
- <?
- $class_day = '';
- if (($aday - $j - 1 > 0) || ($j - $aday > 6)) {
- $class_cell = 'lightmonth';
- $class_day = 'light';
- } elseif (date('Ymd', $i) == date('Ymd')) {
- $class_cell = 'celltoday';
- } else {
- $class_cell = 'month';
- }
- $hday = holiday($i);
-
- if ($j % 7 == 0) {
- ?><tr><?
- }
- ?>
- <td class="<?= $class_cell ?>">
- <? if (($j + 1) % 7 == 0) : ?>
- <a class="<?= $class_day . 'sday' ?>" href="<?= $controller->url_for('calendar/single/day', ['atime' => $i]) ?>">
- <?= $aday ?>
- </a>
- <? if (!empty($hday['name'])) : ?>
- <div style="color: #aaaaaa;" class="inday"><?= $hday['name'] ?></div>
- <? endif; ?>
- <? foreach ($calendars[$j]->events as $event) : ?>
- <div data-tooltip>
- <a data-dialog="size=auto" title="<?= _('Termin bearbeiten') ?>" class="inday <?= $event instanceof CourseEvent ? 'calendar-course-event-text' : 'calendar-event-text' ?><?= $event->getCategory() ?>" href="<?= $controller->url_for('calendar/single/edit/' . $calendars[$j]->getRangeId() . '/' . $event->event_id, ['atime' => $event->getStart()]) ?>"><?= htmlReady($event->getTitle()) ?></a>
- <?= $this->render_partial('calendar/single/_tooltip', ['event' => $event, 'calendar' => $calendars[$j]]) ?>
- </div>
- <? endforeach; ?>
- </td>
- <td class="lightmonth calendar-month-week">
- <a style="font-weight: bold;" class="calhead" href="<?= $controller->url_for('calendar/single/week', ['atime' => $i]) ?>"><?= strftime("%V", $i) ?></a>
- </td>
- </tr>
- <? else : ?>
- <? $hday_class = ['day', 'day', 'shday', 'hday'] ?>
- <? if (!empty($hday['col'])) : ?>
- <a class="<?= $class_day . $hday_class[$hday['col']] ?>" href="<?= $controller->url_for('calendar/single/day', ['atime' => $i]) ?>">
- <?= $aday ?>
- </a>
- <div style="color: #aaaaaa;" class="inday"><?= $hday['name'] ?></div>
- <? else : ?>
- <a class="<?= $class_day . 'day' ?>" href="<?= $controller->url_for('calendar/single/day', ['atime' => $i]) ?>">
- <?= $aday ?>
- </a>
- <? endif; ?>
- <? foreach ($calendars[$j]->events as $event) : ?>
- <div data-tooltip>
- <a data-dialog="size=auto" title="<?= _('Termin bearbeiten') ?>" class="inday <?= $event instanceof CourseEvent ? 'calendar-course-event-text' : 'calendar-event-text' ?><?= $event->getCategory() ?>" href="<?= $controller->url_for('calendar/single/edit/' . $calendars[$j]->getRangeId() . '/' . $event->event_id, ['atime' => $event->getStart()]) ?>"><?= htmlReady($event->getTitle()) ?></a>
- <?= $this->render_partial('calendar/single/_tooltip', ['event' => $event, 'calendar' => $calendars[$j]]) ?>
- </div>
- <? endforeach; ?>
- </td>
- <? endif; ?>
- <? endfor; ?>
- </tr>
- </tbody>
-</table>
-</div>
diff --git a/app/views/calendar/single/seminar_events.php b/app/views/calendar/single/seminar_events.php
deleted file mode 100644
index 06ed5fa..0000000
--- a/app/views/calendar/single/seminar_events.php
+++ /dev/null
@@ -1,104 +0,0 @@
-<? if (!empty($sem_courses)) : ?>
- <?= $this->render_partial('calendar/single/_semester_filter') ?>
- <? $_order = (!$order_by || $order == 'desc') ? 'asc' : 'desc' ?>
- <form action="<?= $controller->url_for('calendar/single/store_selected_sem') ?>" method="post">
- <?= CSRFProtection::tokenTag() ?>
- <div id="my_seminars">
- <? foreach ($sem_courses as $sem_key => $course_group) : ?>
- <table class="default collapsable">
- <caption>
- <?= htmlReady($sem_data[$sem_key]['name'] ?? _('Unbekanntes Semester')) ?>
- </caption>
- <colgroup>
- <col width="7px">
- <col width="25px">
- <? if ($config_sem_number) : ?>
- <col width="10%">
- <? endif ?>
- <col>
- <col width="45px">
- <col width="10%">
- </colgroup>
- <thead>
- <tr class="sortable">
- <th></th>
- <th></th>
- <? if ($config_sem_number) : ?>
- <th class=<?= ($order_by == 'veranstaltungsnummer') ? ($order == 'desc') ? 'sortdesc' : 'sortasc' : '' ?>>
- <a href="<?= $controller->url_for(sprintf('my_courses/index/veranstaltungsnummer/%s', $_order)) ?>">
- <?= _('Nr.') ?>
- </a>
- </th>
- <? endif ?>
- <th
- class=<?= ($order_by == 'name') ? ($order == 'desc') ? 'sortdesc' : 'sortasc' : '' ?>>
- <a href="<?= $controller->url_for(sprintf('calendar/single/seminar_events/name/%s', $_order)) ?>" data-dialog="size=auto">
- <?= _('Name') ?>
- </a>
- </th>
- <th></th>
- <th><?= _('Auswahl') ?></th>
- </tr>
- </thead>
- <? foreach ($course_group as $course) : ?>
- <? $sem_class = $course['sem_class']; ?>
- <tr>
- <td class="gruppe<?= $course['gruppe'] ?>"></td>
- <td>
- <? if ($sem_class['studygroup_mode']) : ?>
- <?= StudygroupAvatar::getAvatar($course['seminar_id'])->getImageTag(Avatar::SMALL, ['title' => $course['name']])
- ?>
- <? else : ?>
- <?= CourseAvatar::getAvatar($course['seminar_id'])->getImageTag(Avatar::SMALL, ['title' => $course['name']])
- ?>
- <? endif ?>
- </td>
- <? if($config_sem_number) :?>
- <td><?= $course['veranstaltungsnummer']?></td>
- <? endif?>
- <td style="text-align: left">
- <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $course['seminar_id']]) ?>"
- <?= $course['visitdate'] <= $course['chdate'] ? 'style="color: red;"' : '' ?>>
- <?= htmlReady($course['name']) ?>
- <?= ($course['is_deputy'] ? ' ' . _('[Vertretung]') : '');?>
- </a>
- <? if ($course['visible'] == 0) : ?>
- <?= _('[versteckt]') ?>
- <? endif ?>
- </td>
- <td>
- <? if (!$sem_class['studygroup_mode']) : ?>
- <a data-dialog href="<?= $controller->url_for(sprintf('course/details/index/%s', $course['seminar_id'])) ?>">
- <? $params = tooltip2(_('Veranstaltungsdetails')); ?>
- <? $params['style'] = 'cursor: pointer'; ?>
- <?= Icon::create('info-circle', 'inactive')->asImg(20, $params) ?>
- </a>
- <? else : ?>
- <?= Assets::img('blank.gif', ['width' => 20, 'height' => 20]); ?>
- <? endif ?>
- </td>
- <td style="text-align: center;">
- <input type="hidden" name="selected_sem[<?= $course['seminar_id'] ?>]" value="0">
- <input type="checkbox" name="selected_sem[<?= $course['seminar_id'] ?>]" value="1"<?= in_array($course['seminar_id'], $bind_calendar) ? ' checked' : '' ?>>
- </td>
- </tr>
- <? endforeach ?>
- </table>
- <? endforeach ?>
- </div>
- <div style="text-align: center;" data-dialog-button>
- <?= Studip\Button::create(_('Speichern'), 'store') ?>
- <? if (!Request::isXhr()) : ?>
- <?= Studip\LinkButton::create(_('Abbrechen'), $controller->url_for('calendar/single/' . $last_view)) ?>
- <? endif; ?>
- </div>
- </form>
-<? else : ?>
- <?= PageLayout::postMessage(MessageBox::info(_('Es wurden keine Veranstaltungen gefunden. Mögliche Ursachen:'), [
- sprintf(_('Sie haben zur Zeit keine Veranstaltungen belegt, an denen Sie teilnehmen können.<br>Bitte nutzen Sie %s<b>Veranstaltung suchen / hinzufügen</b>%s um sich für Veranstaltungen anzumelden.'),'<a href="' . URLHelper::getLink('dispatch.php/search/courses') . '">', '</a>'),
- _('In dem ausgewählten <b>Semester</b> wurden keine Veranstaltungen belegt.').'<br>'._('Wählen Sie links im <b>Semesterfilter</b> ein anderes Semester aus')
- ]))?>
-<? endif ?>
-<? if (!empty($my_bosses) && is_array($my_bosses) && count($my_bosses)) : ?>
- <?= $this->render_partial('my_courses/_deputy_bosses'); ?>
-<? endif ?>
diff --git a/app/views/calendar/single/week.php b/app/views/calendar/single/week.php
deleted file mode 100644
index 98f8317..0000000
--- a/app/views/calendar/single/week.php
+++ /dev/null
@@ -1,199 +0,0 @@
-<?
-$at = date('G', $atime);
-if ($at >= $settings['start']
- && $at <= $settings['end'] || !$atime) {
- $start = $settings['start'];
- $end = $settings['end'];
-} elseif ($at < $settings['start']) {
- $start = 0;
- $end = $settings['start'] + 2;
-} else {
- $start = $settings['end'] - 2;
- $end = 23;
-}
-$tab_arr = [];
-$max_columns = 0;
-$week_type = $settings['type_week'] == 'SHORT' ? 5 : 7;
-$rows = ($end - $start + 1) * 3600 / $settings['step_week'];
-
-for ($i = 0; $i < $week_type; $i++) {
- $tab_arr[$i] = $calendars[$i]->createEventMatrix($start * 3600, $end * 3600, $settings['step_week']);
- if ($tab_arr[$i]['max_cols']) {
- $max_columns += ($tab_arr[$i]['max_cols'] + 1);
- } else {
- $max_columns++;
- }
-}
-
-$rowspan = ceil(3600 / $settings['step_week']);
-$height = ' height="20"';
-
-if ($rowspan > 1) {
- $colspan_1 = ' colspan="2"';
- $colspan_2 = $max_columns + 4;
- $width_daycols = 100 - (4 + $week_type) * 0.1;
-} else {
- $colspan_1 = '';
- $colspan_2 = $max_columns + 2;
- $width_daycols = 100 - (2 + $week_type) * 0.1;
-}
-?>
-
-<nav class="calendar-nav" style="vertical-align: middle">
- <span style="white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/single/week', ['atime' => strtotime('-1 week', $atime)]) ?>">
- <?= Icon::create('arr_1left', 'clickable', ['title' => _('Eine Woche zurück')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- <span class="hidden-tiny-down"><?= sprintf(_('%u. Woche'), strftime('%V', strtotime('-1 week', $atime))) ?></span>
- </a>
- </span>
-
- <?
- $calType = 'week';
- $calLabel = $this->render_partial('calendar/single/_calhead_label_week', compact('week_type'));
- ?>
-
- <?= $this->render_partial('calendar/single/_calhead', compact('atime', 'calType', 'calLabel')) ?>
-
- <span style="white-space: nowrap; text-align: right;">
- <a href="<?= $controller->url_for('calendar/single/week', ['atime' => strtotime('+1 week', $atime)]) ?>">
- <span class="hidden-tiny-down"><?= sprintf(_('%u. Woche'), strftime('%V', strtotime('+1 week', $atime))) ?></span>
- <?= Icon::create('arr_1right', 'clickable', ['title' => _('Eine Woche vor')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- </a>
- </span>
-</nav>
-
-<table id="main_content" class="calendar-week">
- <colgroup>
- <col style="max-width: 1.5em; width: 1.5em;">
- <? if ($rowspan > 1) : ?>
- <col style="max-width: 1.5em; width: 1.5em;">
- <? endif; ?>
- <? for ($i = 0; $i < $week_type; $i++) : ?>
- <? if ($tab_arr[$i]['max_cols'] > 0) : ?>
- <? $event_cols = $tab_arr[$i]['max_cols'] ?: 1; ?>
- <col span="<?= $event_cols ?>" style="width: <?= 100 / $week_type / $event_cols ?>%">
- <col style="max-width: 0.9em; width: 0.9em;">
- <? else : ?>
- <col style="width: <?= 100 / $week_type ?>%">
- <? endif; ?>
- <? endfor; ?>
- <col class="hidden-tiny-down" style="max-width: 1.5em; width: 1.5em;">
- <? if ($rowspan > 1) : ?>
- <col class="hidden-tiny-down" style="max-width: 1.5em; width: 1.5em;">
- <? endif; ?>
- </colgroup>
- <thead>
- <tr>
- <td style="text-align: center; white-space: nowrap;" <?= $colspan_1 ?>>
- <? if ($start > 0) : ?>
- <a href="<?= $controller->url_for('calendar/single/week', ['atime' => mktime($start - 1, 0, 0, date('n', $atime), date('j', $atime), date('Y', $atime))]) ?>">
- <?= Icon::create('arr_1up', 'clickable', ['title' => _('Früher')])->asImg() ?>
- </a>
- <? endif ?>
- </td>
- <? // weekday and date as title for each column ?>
- <? for ($i = 0; $i < $week_type; $i++) : ?>
- <td style="text-align:center; font-weight:bold;"<?= ($tab_arr[$i]['max_cols'] > 0 ? ' colspan="' . ($tab_arr[$i]['max_cols'] + 1) . '"' : '' ) ?>>
- <a class="calhead" href="<?= $controller->url_for('calendar/single/day', ['atime' => $calendars[$i]->getStart()]) ?>">
- <span class="hidden-tiny-down"><?= strftime('%a', $calendars[$i]->getStart()) ?></span> <?= date('d', $calendars[$i]->getStart()) ?>
- </a>
- <? if ($holiday = holiday($calendars[$i]->getStart())) : ?>
- <div class="hidden-tiny-down" style="font-size:9pt; color:#bbb; height:auto; overflow:visible; font-weight:bold;"><?= $holiday['name'] ?></div>
- <? endif ?>
- </td>
- <? endfor ?>
- <td style="text-align: center; white-space: nowrap;" <?= $colspan_1 ?>>
- <? if ($start > 0) : ?>
- <a href="<?= $controller->url_for('calendar/single/week', ['atime' => mktime($start - 1, 0, 0, date('n', $calendars[0]->getStart()), date('j', $calendars[0]->getStart()), date('Y', $calendars[0]->getStart()))]) ?>">
- <?= Icon::create('arr_1up', 'clickable', ['title' => _('Früher')])->asImg() ?>
- </a>
- <? endif ?>
- </td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <? // Zeile mit Tagesterminen ausgeben ?>
- <td class="precol1w"<?= $colspan_1 ?> height="20">
- <?= _("Tag") ?>
- </td>
- <? for ($i = 0; $i < $week_type; $i++) : ?>
- <?
- if (date('Ymd', $calendars[$i]->getStart()) == date('Ymd')) {
- $class_cell = 'celltoday';
- } else {
- $class_cell = '';
- }
- ?>
- <?= $this->render_partial('calendar/single/_day_dayevents', ['em' => $tab_arr[$i], 'calendar' => $calendars[$i], 'class_cell' => $class_cell]) ?>
- <? endfor ?>
- <td class="precol1w"<?= $colspan_1 ?>>
- <?= _('Tag') ?>
- </td>
- </tr>
- <? $j = $start ?>
- <? for ($i = 0; $i < $rows; $i++) : ?>
- <tr>
- <? if ($i % $rowspan == 0) : ?>
- <? if ($rowspan == 1) : ?>
- <td class="precol1w"<?= $height ?>><?= $j ?></td>
- <? else : ?>
- <td class="precol1w" rowspan="<?= $rowspan ?>"><?= $j ?></td>
- <? endif ?>
- <? endif ?>
- <? if ($rowspan > 1) : ?>
- <? $minutes = (60 / $rowspan) * ($i % $rowspan); ?>
- <? if ($minutes == 0) : ?>
- <td class="precol2w"<?= $height ?>>00</td>
- <? else : ?>
- <td class="precol2w"<?= $height ?>><?= $minutes ?></td>
- <? endif ?>
- <? endif ?>
- <? for ($y = 0; $y < $week_type; $y++) : ?>
- <?
- if (date('Ymd', $calendars[$y]->getStart()) == date('Ymd')) {
- $class_cell = 'celltoday';
- } else {
- $class_cell = '';
- }
- ?>
- <?= $this->render_partial('calendar/single/_day_cell', ['calendar' => $calendars[$y], 'em' => $tab_arr[$y], 'row' => $i, 'start' => $start * 3600, 'i' => $i + ($start * 3600 / $settings['step_week']), 'step' => $settings['step_week'], 'class_cell' => $class_cell]); ?>
- <? endfor ?>
- <? if ($rowspan > 1) : ?>
- <? if ($minutes == 0) : ?>
- <td class="precol2w"<?= $height ?>>00</td>
- <? else : ?>
- <td class="precol2w"<?= $height ?>><?= $minutes ?></td>
- <? endif ?>
- <? endif ?>
- <? if (($i + 2) % $rowspan == 0) : ?>
- <? if ($rowspan == 1) : ?>
- <td class="precol1w"<?= $height ?>><?= $j ?></td>
- <? else : ?>
- <td class="precol1w" rowspan="<?= $rowspan ?>"><?= $j ?></td>
- <? endif ?>
- <? $j = $j + ceil($settings['step_week'] / 3600); ?>
- <? endif ?>
- </tr>
- <? endfor ?>
- </tbody>
- <tfoot>
- <tr>
- <td<?= $colspan_1 ?> style="text-align:center;">
- <? if ($end < 23) : ?>
- <a href="<?= $controller->url_for('calendar/single/week', ['atime' => mktime($end + 1, 0, 0, date('n', $calendars[0]->getStart()), date('j', $calendars[0]->getStart()), date('Y', $calendars[0]->getStart()))]) ?>">
- <?= Icon::create('arr_1down', 'clickable', ['title' => _('Später')])->asImg() ?>
- </a>
- <? endif ?>
- </td>
- <td colspan="<?= $max_columns ?>">&nbsp;</td>
- <td<?= $colspan_1 ?> style="text-align:center;">
- <? if ($end < 23) : ?>
- <a href="<?= $controller->url_for('calendar/single/week', ['atime' => mktime($end + 1, 0, 0, date('n', $calendars[0]->getStart()), date('j', $calendars[0]->getStart()), date('Y', $calendars[0]->getStart()))]) ?>">
- <?= Icon::create('arr_1down', 'clickable', ['title' => _('Später')])->asImg() ?>
- </a>
- <? endif ?>
- </td>
- </tr>
- </tfoot>
-</table>
diff --git a/app/views/calendar/single/year.php b/app/views/calendar/single/year.php
deleted file mode 100644
index 0ebafe1..0000000
--- a/app/views/calendar/single/year.php
+++ /dev/null
@@ -1,145 +0,0 @@
-<div class="calendar-single-year">
-
- <nav class="calendar-nav" style="vertical-align: middle">
- <span style="white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/single/year', ['atime' => strtotime('-1 year', $atime)]) ?>">
- <?= Icon::create('arr_2left', 'clickable', ['title' => _('Ein Jahr zurück')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- <?= strftime('%Y', strtotime('-1 year', $atime)) ?>
- </a>
- </span>
-
- <?
- $calType = 'year';
- $calLabel = date('Y', $calendar->getStart());
- ?>
-
- <?= $this->render_partial('calendar/single/_calhead', compact('calendar', 'atime', 'calType', 'calLabel')) ?>
-
- <span style="text-align: right; white-space: nowrap;">
- <a href="<?= $controller->url_for('calendar/single/year', ['atime' => strtotime('+1 year', $atime)]) ?>">
- <?= strftime('%Y', strtotime('+1 year', $atime)) ?>
- <?= Icon::create('arr_2right', 'clickable', ['title' => _('Ein Jahr vor')])->asImg(16, ['style' => 'vertical-align: text-top;']) ?>
- </a>
- </span>
- </nav>
-
- <div class="table-scrollbox-horizontal">
- <table class="calendar-single-year--table" width="100%">
- <? $days_per_month = [31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
- if (date('L', $calendar->getStart())) {
- $days_per_month[2]++;
- }
- ?>
-
- <thead>
- <tr>
- <? $ts_month = 0; ?>
- <? for ($i = 1; $i < 13; $i++) : ?>
- <? $ts_month += ( $days_per_month[$i] - 1) * 86400; ?>
- <th align="center" width="8%">
- <a class="calhead" href="<?= $controller->url_for('calendar/single/month', ['atime' => $calendar->getStart() + $ts_month]) ?>">
- <b><?= strftime('%B', $ts_month); ?></b>
- </a>
- </th>
- <? endfor; ?>
- </tr>
- </thead>
-
- <tbody>
- <?
- $now = date('Ymd');
- $count = 0;
- ?>
- <? for ($i = 1; $i < 32; $i++) : ?>
- <tr>
- <? for ($month = 1; $month < 13; $month++) : ?>
-
- <? $aday = mktime(12, 0, 0, $month, $i, date('Y', $calendar->getStart())); ?>
- <? $iday = date('Ymd', $aday); ?>
- <? if ($i <= $days_per_month[$month]) : ?>
- <? $wday = date('w', $aday);
- // emphasize current day
- if (date('Ymd', $aday) == $now) {
- $day_class = ' class="celltoday"';
- } else if ($wday == 0 || $wday == 6) {
- $day_class = ' class="weekend"';
- } else {
- $day_class = ' class="weekday"';
- }
- ?>
-
- <td <?= $day_class ?> <?= $month == 1 ? 'height="25"' : '' ?>>
-
- <? if (isset($count_list[$iday]) && count($count_list[$iday])) : ?>
- <table width="100%" cellspacing="0" cellpadding="0">
- <tr>
- <td<?= $day_class ?>>
- <? endif; ?>
-
- <? $weekday = strftime('%a', $aday); ?>
-
- <span class="yday">
- <? $hday = holiday($aday); ?>
- <? if (is_array($hday)) : ?>
- <? if ($hday['col'] == '1') : ?>
- <? if (date('w', $aday) == '0') : ?>
- <a style="font-weight:bold;" class="sday" href="<?= $controller->url_for('calendar/single/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? $count++; ?>
- <? else : ?>
- <a style="font-weight:bold;" class="day" href="<?= $controller->url_for('calendar/single/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? endif; ?>
- <? elseif ($hday['col'] == '2' || $hday['col'] == '3') : ?>
- <? if (date('w', $aday) == '0') : ?>
- <a style="font-weight:bold;" class="sday" href="<?= $controller->url_for('calendar/single/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? $count++; ?>
- <? else : ?>
- <a style="font-weight:bold;" class="hday" href="<?= $controller->url_for('calendar/single/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? endif; ?>
- <? endif ?>
- <? else : ?>
- <? if (date('w', $aday) == '0') : ?>
- <a style="font-weight:bold;" class="sday" href="<?= $controller->url_for('calendar/single/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? $count++; ?>
- <? else : ?>
- <a style="font-weight:bold;" class="day" href="<?= $controller->url_for('calendar/single/day', ['atime' => $aday]) ?>"><?= $i ?></a> <?= $weekday; ?>
- <? endif; ?>
- <? endif; ?>
- </span>
-
- <? if (isset($count_list[$iday]) && count($count_list[$iday])) : ?>
- <? $event_count_txt = sprintf(ngettext('1 Termin', '%s Termine', count($count_list[$iday])), count($count_list[$iday])) ?>
- </td>
- <td<?= $day_class ?> align="right">
- <?= Icon::create('date', 'clickable', ['title' => $event_count_txt])->asImg(16, ["alt" => $event_count_txt]); ?>
- </td>
- </tr>
- </table>
- <? endif; ?>
-
- </td>
-
- <? else : ?>
- <td class="weekday"> </td>
- <? endif; ?>
-
- <? endfor; ?>
- </tr>
- <? endfor; ?>
- </tbody>
-
- <tfoot>
- <tr>
- <? $ts_month = 0; ?>
- <? for ($i = 1; $i < 13; $i++) : ?>
- <? $ts_month += ( $days_per_month[$i] - 1) * 86400; ?>
- <th align="center" width="8%">
- <a class="calhead" href="<?= $controller->url_for('calendar/single/month', ['atime' => $calendar->getStart() + $ts_month]) ?>">
- <b><?= strftime('%B', $ts_month); ?></b>
- </a>
- </th>
- <? endfor; ?>
- </tr>
- </tfoot>
- </table>
- </div>
-</div>
diff --git a/app/views/course/cancel_dates/index.php b/app/views/course/cancel_dates/index.php
index e427849..3c092bc 100644
--- a/app/views/course/cancel_dates/index.php
+++ b/app/views/course/cancel_dates/index.php
@@ -12,7 +12,7 @@
<label>
<?= _('Kommentar') ?>
- <?= tooltipIcon(_('Wenn Sie die nicht stattfindenden Termine mit einem Kommentar versehen, werden die Ausfalltermine im Ablaufplan weiterhin dargestellt und auch im Terminkalender eingeblendet.')) ?>
+ <?= tooltipIcon(_('Wenn Sie die nicht stattfindenden Termine mit einem Kommentar versehen, werden die Ausfalltermine im Ablaufplan weiterhin dargestellt und auch im Kalender angezeigt.')) ?>
<textarea wrap="virtual" name="cancel_dates_comment" id="cancel_dates_comment"></textarea>
</label>
<label>
diff --git a/app/views/course/dates/details-edit.php b/app/views/course/dates/details-edit.php
index 2fec69c..0f91d72 100644
--- a/app/views/course/dates/details-edit.php
+++ b/app/views/course/dates/details-edit.php
@@ -1,4 +1,5 @@
-<form name="edit_termin" action='<?= $controller->url_for('course/dates/save_details/' . $date->id) ?>' method="post" class="default" data-termin-id="<?= htmlReady($date->id) ?>">
+<form name="edit_termin" action='<?= $controller->url_for('course/dates/save_details/' . $date->id) ?>' method="post" class="default" data-termin-id="<?= htmlReady($date->id) ?>"
+ data-course-id="<?= htmlReady(Context::getID()) ?>">
<?= CSRFProtection::tokenTag() ?>
<fieldset>
@@ -93,6 +94,17 @@
</div>
</fieldset>
<? endif; ?>
+<? if (!empty($date->room_booking->resource)) : ?>
+ <? $room = $date->room_booking->resource->getDerivedClassInstance() ?>
+ <? if ($room instanceof Resource) : ?>
+ <fieldset>
+ <legend><?= _('Raum') ?></legend>
+ <section>
+ <?= htmlReady($room->getFullName()) ?>
+ </section>
+ </fieldset>
+ <? endif ?>
+<? endif ?>
<? if (count($groups) > 0): ?>
<fieldset>
@@ -158,6 +170,12 @@
['data-dialog' => '']
) ?>
<? endif ?>
+ <? if (Request::submitted('extra_buttons')) : ?>
+ <?= Studip\LinkButton::create(
+ _('Zur Veranstaltung'),
+ $controller->url_for('course/details', ['cid' => $date->range_id])
+ ) ?>
+ <? endif ?>
</footer>
</form>
diff --git a/app/views/course/dates/details.php b/app/views/course/dates/details.php
index 5a77268..96f7e2e 100644
--- a/app/views/course/dates/details.php
+++ b/app/views/course/dates/details.php
@@ -39,6 +39,17 @@
</td>
</tr>
<? endif; ?>
+ <? if (!empty($date->room_booking->resource)) : ?>
+ <? $room = $date->room_booking->resource->getDerivedClassInstance() ?>
+ <? if ($room instanceof Resource) : ?>
+ <tr>
+ <td><strong><?= _('Raum') ?></strong></td>
+ <td>
+ <?= htmlReady($room->getFullName()) ?>
+ </td>
+ </tr>
+ <? endif ?>
+ <? endif ?>
<? if (count($date->statusgruppen) > 0): ?>
<tr>
<td><strong><?= _('Beteiligte Gruppen') ?></strong></td>
@@ -96,3 +107,8 @@
STUDIP.Table.enhanceSortableTable($('#course_date_files'));
</script>
<? endif; ?>
+<? if (Request::bool('extra_buttons') && $GLOBALS['perm']->have_studip_perm('user', $course->id)) : ?>
+ <div data-dialog-button>
+ <?= \Studip\LinkButton::create(_('Zur Veranstaltung'), $controller->url_for('course/details', ['cid' => $course->id])) ?>
+ </div>
+<? endif ?>
diff --git a/app/views/course/details/index.php b/app/views/course/details/index.php
index 15cee6c..583f715 100644
--- a/app/views/course/details/index.php
+++ b/app/views/course/details/index.php
@@ -522,4 +522,9 @@ if (!empty($mvv_tree)) : ?>
<? endif ?>
</footer>
<? endif ?>
-<?= Feedback::getHTML($course->id, Course::class) ?>
+<? if (Request::bool('link_to_course') && $GLOBALS['perm']->have_studip_perm('autor', $course->id)) : ?>
+ <footer data-dialog-button>
+ <?= \Studip\LinkButton::create(_('Direkt zur Veranstaltung'), URLHelper::getURL('dispatch.php/course/overview', ['cid' => $course->id]))?>
+ </footer>
+<? endif ?>
+<?= Feedback::getHTML($course->id, 'Course'); ?>
diff --git a/app/views/course/timesrooms/_cancel_form.php b/app/views/course/timesrooms/_cancel_form.php
index eaadaf1..ee59d89 100644
--- a/app/views/course/timesrooms/_cancel_form.php
+++ b/app/views/course/timesrooms/_cancel_form.php
@@ -6,7 +6,7 @@ if (isset($termin) && $termin instanceof CourseExDate) {
}
?>
<p>
- <strong> <?= _('Wenn Sie die nicht stattfindenden Termine mit einem Kommentar versehen, werden die Ausfalltermine im Ablaufplan weiterhin dargestellt und auch im Terminkalender eingeblendet.') ?></strong>
+ <strong> <?= _('Wenn Sie die nicht stattfindenden Termine mit einem Kommentar versehen, werden die Ausfalltermine im Ablaufplan weiterhin dargestellt und auch im Kalender angezeigt.') ?></strong>
</p>
<label for="cancel_comment">
diff --git a/app/views/institute/overview/index.php b/app/views/institute/overview/index.php
index 3543d66..cb347b0 100644
--- a/app/views/institute/overview/index.php
+++ b/app/views/institute/overview/index.php
@@ -50,7 +50,6 @@
</article>
<?= $news ?>
-<?= $dates ?>
<?= $evaluations ?>
<?= $questionnaires ?>
diff --git a/app/views/institute/schedule/index.php b/app/views/institute/schedule/index.php
new file mode 100644
index 0000000..23602db
--- /dev/null
+++ b/app/views/institute/schedule/index.php
@@ -0,0 +1 @@
+<?= $fullcalendar ?>
diff --git a/app/views/settings/calendar.php b/app/views/settings/calendar.php
index 6833a0a..889120a 100644
--- a/app/views/settings/calendar.php
+++ b/app/views/settings/calendar.php
@@ -4,8 +4,7 @@ use Studip\Button, Studip\LinkButton;
$cal_views = [
'day' => _('Tagesansicht'),
'week' => _('Wochenansicht'),
- 'month' => _('Monatsansicht'),
- 'year' => _('Jahresansicht'),
+ 'month' => _('Monatsansicht')
];
$cal_deletes = [
12 => _('12 Monate nach Ablauf'),
@@ -27,18 +26,19 @@ $cal_step_weeks = [
];
?>
-<form method="post" action="<?= $controller->url_for('settings/calendar/store') ?>" class="default">
+<form method="post" action="<?= $controller->link_for('settings/calendar/store') ?>" class="default"
+ <?= Request::isDialog() ? 'data-dialog="reload-on-close"' : '' ?>>
<input type="hidden" name="studip_ticket" value="<?= get_ticket() ?>">
<?= CSRFProtection::tokenTag() ?>
<fieldset>
<legend>
- <?= _('Einstellungen des Terminkalenders') ?>
+ <?= _('Einstellungen des Kalenders') ?>
</legend>
<label>
<?= _('Startansicht') ?>
- <select name="cal_view" id="cal_view" size="1">
+ <select name="cal_view" id="cal_view">
<? foreach ($cal_views as $index => $label): ?>
<option value="<?= $index ?>" <? if ($view == $index) echo 'selected'; ?>>
<?= $label ?>
@@ -48,15 +48,14 @@ $cal_step_weeks = [
</label>
<label>
- <?= _('Wochenansicht') ?>
- <select name="cal_type_week">
- <option value="LONG"<?= $type_week == 'LONG' ? ' selected' : "" ?>>
- <?= _('7 Tage-Woche') ?>
- </option>
- <option value="SHORT"<?= $type_week == 'SHORT' ? ' selected' : "" ?>>
- <?= _('5 Tage-Woche') ?>
- </option>
- </select>
+ <input type="radio" name="cal_type_week" value="LONG"
+ <?= $type_week == 'LONG' ? 'checked' : "" ?>>
+ <?= _('Alle Wochentage in der Wochenansicht anzeigen.') ?>
+ </label>
+ <label>
+ <input type="radio" name="cal_type_week" value="SHORT"
+ <?= $type_week == 'SHORT' ? 'checked' : "" ?>>
+ <?= _('Nur Montag bis Freitag in der Wochenansicht anzeigen.') ?>
</label>
</fieldset>
@@ -65,34 +64,27 @@ $cal_step_weeks = [
<?= _('Einzelterminkalender') ?>
</legend>
- <div>
- <?= _('Zeitraum der Tages- und Wochenansicht') ?>
- <section class="hgroup">
- <label>
- <?= _("Von") ?>
- <select name="cal_start" aria-label="<?= _('Startzeit der Tages- und Wochenansicht') ?>" class="size-s">
- <? for ($i = 0; $i < 24; $i += 1): ?>
- <option value="<?= $i ?>" <? if ($start == $i) echo 'selected'; ?>>
- <?= sprintf('%02u:00', $i) ?>
- </option>
- <? endfor; ?>
- </select>
- <?= _("Uhr") ?>
- </label>
-
- <label>
- <?= _("Bis") ?>
- <select name="cal_end" aria-label="<?= _('Endzeit der Tages- und Wochenansicht') ?>" class="size-s">
- <? for ($i = 0; $i < 24; $i += 1): ?>
- <option value="<?= $i ?>" <? if ($end == $i) echo 'selected'; ?>>
- <?= sprintf('%02u:00', $i) ?>
- </option>
- <? endfor; ?>
- </select>
- <?= _("Uhr") ?>.
- </label>
- </section>
- </div>
+ <label>
+ <?= _('Startuhrzeit') ?>
+ <select name="cal_start" aria-label="<?= _('Startzeit der Tages- und Wochenansicht') ?>" class="size-s">
+ <? for ($i = 0; $i < 24; $i += 1): ?>
+ <option value="<?= $i ?>" <? if ($start == $i) echo 'selected'; ?>>
+ <?= sprintf(_('%02u:00 Uhr'), $i) ?>
+ </option>
+ <? endfor; ?>
+ </select>
+ </label>
+
+ <label>
+ <?= _('Enduhrzeit') ?>
+ <select name="cal_end" aria-label="<?= _('Endzeit der Tages- und Wochenansicht') ?>" class="size-s">
+ <? for ($i = 0; $i < 24; $i += 1): ?>
+ <option value="<?= $i ?>" <? if ($end == $i) echo 'selected'; ?>>
+ <?= sprintf(_('%02u:00 Uhr'), $i) ?>
+ </option>
+ <? endfor; ?>
+ </select>
+ </label>
<label>
<?= _('Zeitintervall der Tagesansicht') ?>
@@ -153,7 +145,7 @@ $cal_step_weeks = [
</fieldset>
<? endif ?>
- <footer>
+ <footer data-dialog-button>
<? if (Request::option('atime')): ?>
<input type="hidden" name="atime" value="<?= Request::option('atime') ?>">
<? endif ?>
diff --git a/app/views/settings/general.php b/app/views/settings/general.php
index db1d01d..4bd5433 100644
--- a/app/views/settings/general.php
+++ b/app/views/settings/general.php
@@ -3,7 +3,7 @@ $start_pages = [
'' => _('keine'),
1 => _('Meine Veranstaltungen'),
3 => _('Mein Stundenplan'),
- 5 => _('Mein Terminkalender'),
+ 5 => _('Mein Kalender'),
4 => _('Mein Adressbuch'),
6 => _('Mein globaler Blubberstream'),
7 => _('Mein Arbeitsplatz'),
diff --git a/config/config.inc.php.dist b/config/config.inc.php.dist
index 3ef8fb6..1562248 100644
--- a/config/config.inc.php.dist
+++ b/config/config.inc.php.dist
@@ -205,23 +205,112 @@ $TERMIN_TYP[7]=array("name"=>_("Vorlesung"), "sitzung"=>1);
// more types can be added here
-// Configure the categories for the personal calendar
-$PERS_TERMIN_KAT[1]=array("name"=>_("Sonstiges"));
-$PERS_TERMIN_KAT[2]=array("name"=>_("Sitzung"));
-$PERS_TERMIN_KAT[3]=array("name"=>_("Vorbesprechung"));
-$PERS_TERMIN_KAT[4]=array("name"=>_("Klausur"));
-$PERS_TERMIN_KAT[5]=array("name"=>_("Exkursion"));
-$PERS_TERMIN_KAT[6]=array("name"=>_("Sondersitzung"));
-$PERS_TERMIN_KAT[7]=array("name"=>_("Prüfung"));
-$PERS_TERMIN_KAT[8]=array("name"=>_("Telefonat"));
-$PERS_TERMIN_KAT[9]=array("name"=>_("Besprechung"));
-$PERS_TERMIN_KAT[10]=array("name"=>_("Verabredung"));
-$PERS_TERMIN_KAT[11]=array("name"=>_("Geburtstag"));
-$PERS_TERMIN_KAT[12]=array("name"=>_("Familie"));
-$PERS_TERMIN_KAT[13]=array("name"=>_("Urlaub"));
-$PERS_TERMIN_KAT[14]=array("name"=>_("Reise"));
-$PERS_TERMIN_KAT[15]=array("name"=>_("Vorlesung"));
-// more categories can be added here
+//Configuration for the date categories in the personal calendar:
+$PERS_TERMIN_KAT = [
+ '1' => [
+ 'name' => _('Sonstiges'),
+ 'border_color' => '#682C8B',
+ 'bgcolor' => '#682C8B',
+ 'fgcolor' => '#ffffff'
+ ],
+ '2' => [
+ 'name' => _('Sitzung'),
+ 'border_color' => '#B02E7C',
+ 'bgcolor' => '#B02E7C',
+ 'fgcolor' => '#000000'
+ ],
+ '3' => [
+ 'name' => _('Vorbesprechung'),
+ 'border_color' => '#D60000',
+ 'bgcolor' => '#D60000',
+ 'fgcolor' => '#ffffff'
+ ],
+ '4' => [
+ 'name' => _('Klausur'),
+ 'border_color' => '#F26E00',
+ 'bgcolor' => '#F26E00',
+ 'fgcolor' => '#000000'
+ ],
+ '5' => [
+ 'name' => _('Exkursion'),
+ 'border_color' => '#FFBD33',
+ 'bgcolor' => '#FFBD33',
+ 'fgcolor' => '#000000'
+ ],
+ '6' => [
+ 'name' => _('Sondersitzung'),
+ 'border_color' => '#6EAD10',
+ 'bgcolor' => '#6EAD10',
+ 'fgcolor' => '#000000'
+ ],
+ '7' => [
+ 'name' => _('Prüfung'),
+ 'border_color' => '#008512',
+ 'bgcolor' => '#008512',
+ 'fgcolor' => '#000000'
+ ],
+ '8' => [
+ 'name' => _('Telefonat'),
+ 'border_color' => '#129C94',
+ 'bgcolor' => '#129C94',
+ 'fgcolor' => '#000000'
+ ],
+ '9' => [
+ 'name' => _('Besprechung'),
+ 'border_color' => '#A85D45',
+ 'bgcolor' => '#A85D45',
+ 'fgcolor' => '#000000'
+ ],
+ '10' => [
+ 'name' => _('Verabredung'),
+ 'border_color' => '#A480B9',
+ 'bgcolor' => '#A480B9',
+ 'fgcolor' => '#000000'
+ ],
+ '11' => [
+ 'name' => _('Geburtstag'),
+ 'border_color' => '#D082B0',
+ 'bgcolor' => '#D082B0',
+ 'fgcolor' => '#000000'
+ ],
+ '12' => [
+ 'name' => _('Familie'),
+ 'border_color' => '#E76666',
+ 'bgcolor' => '#E76666',
+ 'fgcolor' => '#000000'
+ ],
+ '13' => [
+ 'name' => _('Urlaub'),
+ 'border_color' => '#F7A866',
+ 'bgcolor' => '#F7A866',
+ 'fgcolor' => '#000000'
+ ],
+ '14' => [
+ 'name' => _('Reise'),
+ 'border_color' => '#FFD785',
+ 'bgcolor' => '#FFD785',
+ 'fgcolor' => '#000000'
+ ],
+ '15' => [
+ 'name' => _('Vorlesung'),
+ 'border_color' => '#A8CE70',
+ 'bgcolor' => '#A8CE70',
+ 'fgcolor' => '#000000'
+ ],
+ '16' => [
+ 'name' => _('Videokonferenz'),
+ 'border_color' => '#8bbd40',
+ 'bgcolor' => '#8bbd40',
+ 'fgcolor' => '#000000'
+ ],
+ '255' => [
+ 'name' => _('Sonstige'),
+ 'border_color' => '#A7ABAF',
+ 'bgcolor' => '#A7ABAF',
+ 'fgcolor' => '#000000'
+ ]
+ //More categories can be added here.
+];
//preset for academic titles - add further titles to the array, if necessary
$TITLE_FRONT_TEMPLATE = array("","Prof.","Prof. Dr.","Dr.","PD Dr.","Dr. des.","Dr. med.","Dr. rer. nat.","Dr. forest.",
diff --git a/db/migrations/1.160_step_00283_update_calendar_settings.php b/db/migrations/1.160_step_00283_update_calendar_settings.php
index f54956b..82a1584 100644
--- a/db/migrations/1.160_step_00283_update_calendar_settings.php
+++ b/db/migrations/1.160_step_00283_update_calendar_settings.php
@@ -17,7 +17,17 @@ class Step00283UpdateCalendarSettings extends Migration {
'showmonth' => 'month',
'showyear' => 'year'];
$res = DBManager::get()->query("SELECT user_id FROM `user_config` WHERE field = 'CALENDAR_SETTINGS'");
- $default_settings = Calendar::getDefaultUserSettings();
+ $default_settings = [
+ 'view' => 'week',
+ 'start' => '9',
+ 'end' => '20',
+ 'step_day' => '900',
+ 'step_week' => '1800',
+ 'type_week' => 'LONG',
+ 'step_week_group' => '3600',
+ 'step_day_group' => '3600',
+ 'show_declined' => '0'
+ ];
Config::get()->store('CALENDAR_SETTINGS', $default_settings);
foreach ($res as $row) {
$config = new UserConfig($row['user_id']);
diff --git a/db/migrations/5.4.1.1_alter_calendar_tables.php b/db/migrations/5.4.1.1_alter_calendar_tables.php
new file mode 100644
index 0000000..b1f477b
--- /dev/null
+++ b/db/migrations/5.4.1.1_alter_calendar_tables.php
@@ -0,0 +1,247 @@
+<?php
+
+
+class AlterCalendarTables extends Migration
+{
+ public function description()
+ {
+ return 'Alters the tables for the personal calendar and related tables.';
+ }
+
+
+ protected function migrateEventData()
+ {
+ $db = DBManager::get();
+
+ $db->exec("RENAME TABLE `event_data` TO calendar_dates");
+
+ //Move the content of the "day" column into the "offset" column
+ //which is still called "sinterval" at this point:
+ $db->exec(
+ "UPDATE `calendar_dates`
+ SET `sinterval` = `day`
+ WHERE `day` <> ''"
+ );
+
+ $db->exec(
+ "ALTER TABLE `calendar_dates`
+ DROP COLUMN `ts`,
+ DROP COLUMN `duration`,
+ DROP COLUMN `priority`,
+ DROP COLUMN `day`,
+ CHANGE COLUMN event_id id CHAR(32) COLLATE latin1_bin NOT NULL,
+ CHANGE COLUMN uid unique_id VARCHAR(255) UNIQUE NOT NULL,
+ CHANGE COLUMN start begin INT(11) NOT NULL DEFAULT 0,
+ CHANGE COLUMN end end INT(11) NOT NULL DEFAULT 0,
+ CHANGE COLUMN summary title VARCHAR(255) NOT NULL DEFAULT '',
+ CHANGE COLUMN class access ENUM('PUBLIC', 'PRIVATE', 'CONFIDENTIAL') COLLATE latin1_bin NOT NULL DEFAULT 'PRIVATE',
+ CHANGE COLUMN categories user_category VARCHAR(64) NULL DEFAULT '',
+ CHANGE COLUMN category_intern category TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+ CHANGE COLUMN location location VARCHAR(255) NULL DEFAULT '',
+ CHANGE COLUMN linterval `interval` TINYINT(2) NULL DEFAULT 0,
+ CHANGE COLUMN sinterval `offset` TINYINT(2) NULL DEFAULT 0,
+ CHANGE COLUMN wdays days VARCHAR(7) NULL DEFAULT '',
+ CHANGE COLUMN rtype repetition_type ENUM('SINGLE', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY') DEFAULT 'SINGLE',
+ CHANGE COLUMN `count` number_of_dates SMALLINT(5) UNSIGNED NOT NULL DEFAULT '1',
+ CHANGE COLUMN `expire` repetition_end BIGINT(10) NOT NULL DEFAULT '0',
+ CHANGE COLUMN mkdate mkdate INT(11) UNSIGNED NOT NULL DEFAULT 0,
+ CHANGE COLUMN chdate chdate INT(11) UNSIGNED NOT NULL DEFAULT 0,
+ CHANGE COLUMN importdate import_date INT(11) NOT NULL DEFAULT 0"
+ );
+
+ $get_stmt = $db->prepare("SELECT `id`, `exceptions` FROM `calendar_dates`");
+ $exception_stmt = $db->prepare(
+ "INSERT INTO `calendar_date_exceptions`
+ (`calendar_date_id`, `date`, `mkdate`, `chdate`)
+ VALUES
+ (:calendar_date_id, :date, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())"
+ );
+ $get_stmt->execute();
+ while ($row = $get_stmt->fetch()) {
+ //Migrate exceptions:
+ $exceptions = explode(',', $row['exceptions'] ?? '');
+ foreach ($exceptions as $exception) {
+ $exception_stmt->execute([
+ 'calendar_date_id' => $row['id'],
+ 'date' => date('Y-m-d', intval(trim($exception)))
+ ]);
+ }
+ }
+
+ $db->exec(
+ "ALTER TABLE `calendar_dates` DROP COLUMN `exceptions`"
+ );
+ }
+
+
+ protected function migrateCalendarEvent()
+ {
+ $db = DBManager::get();
+
+ $db->exec(
+ "RENAME TABLE `calendar_event` TO calendar_date_assignments"
+ );
+
+ $db->exec(
+ "ALTER TABLE `calendar_date_assignments`
+ ADD COLUMN participation ENUM('', 'ACCEPTED', 'DECLINED', 'ACKNOWLEDGED') COLLATE latin1_bin NOT NULL DEFAULT '',
+ CHANGE COLUMN event_id calendar_date_id CHAR(32) COLLATE latin1_bin NOT NULL,
+ CHANGE COLUMN group_status old_group_status TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
+ CHANGE COLUMN mkdate mkdate INT(11) NOT NULL DEFAULT 0,
+ CHANGE COLUMN chdate chdate INT(11) NOT NULL DEFAULT 0"
+ );
+
+ $db->exec(
+ "UPDATE `calendar_date_assignments`
+ SET `participation` = IF (
+ `old_group_status` = '2',
+ 'ACCEPTED',
+ IF (`old_group_status` = '3',
+ 'DECLINED',
+ IF (`old_group_status` = '4',
+ 'ACKNOWLEDGED',
+ ''
+ )
+ )
+ )"
+ );
+
+ $db->exec("ALTER TABLE `calendar_date_assignments` DROP COLUMN `old_group_status`");
+ }
+
+
+ protected function migrateCalendarUser()
+ {
+ //All entries from calendar_user are transferred to the contacts table
+ //which gets an extra column so that it can store the calendar access level.
+ $db = DBManager::get();
+
+ $db->exec(
+ "ALTER TABLE `contact`
+ CHANGE COLUMN mkdate mkdate INT(11) NOT NULL DEFAULT 0,
+ ADD COLUMN chdate INT(11) NOT NULL DEFAULT 0,
+ ADD COLUMN calendar_permissions ENUM('', 'READ', 'WRITE') COLLATE latin1_bin NOT NULL DEFAULT ''"
+ );
+
+ $db->exec(
+ "INSERT INTO `contact`
+ (`owner_id`, `user_id`, `calendar_permissions`, `mkdate`, `chdate`)
+ SELECT `owner_id`, `user_id`,
+ IF(`permission` = '4', 'WRITE', IF(`permission` = '2', 'READ', '')) AS calendar_permissions,
+ `mkdate`, `chdate`
+ FROM `calendar_user`
+ ON DUPLICATE KEY UPDATE `calendar_permissions` = calendar_permissions"
+ );
+
+ $db->exec("DROP TABLE `calendar_user`");
+ }
+
+
+ protected function addContactGroups()
+ {
+ $db = DBManager::get();
+
+ $db->exec(
+ "CREATE TABLE IF NOT EXISTS `contact_groups` (
+ `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(255) NOT NULL,
+ `owner_id` CHAR(32) COLLATE latin1_bin NOT NULL,
+ `old_group_id` CHAR(32) COLLATE latin1_bin NOT NULL,
+ `mkdate` INT(11) UNSIGNED NOT NULL DEFAULT 0,
+ `chdate` INT(11) UNSIGNED NOT NULL DEFAULT 0,
+ PRIMARY KEY(`id`)
+ )"
+ );
+ $db->exec(
+ "CREATE TABLE IF NOT EXISTS `contact_group_items` (
+ `group_id` BIGINT UNSIGNED NOT NULL,
+ `user_id` CHAR(32) COLLATE latin1_bin NOT NULL,
+ `mkdate` INT(11) UNSIGNED NOT NULL DEFAULT 0,
+ `chdate` INT(11) UNSIGNED NOT NULL DEFAULT 0,
+ PRIMARY KEY(`group_id`, `user_id`)
+ )"
+ );
+
+ //Migrate entries from statusgruppen and statusgruppe_user:
+ $old_groups = $db->query(
+ "SELECT `statusgruppe_id`, `name`, `range_id`, `mkdate`, `chdate`
+ FROM `statusgruppen`
+ WHERE `range_id` IN (
+ SELECT `user_id` FROM `auth_user_md5`
+ )"
+ )->fetchAll(PDO::FETCH_ASSOC);
+
+ $new_group_stmt = $db->prepare(
+ "INSERT INTO `contact_groups`
+ (`name`, `owner_id`, `old_group_id`, `mkdate`, `chdate`)
+ VALUES (:name, :user_id, :old_group_id, :mkdate, :chdate)"
+ );
+
+ $group_member_stmt = $db->prepare(
+ "INSERT INTO `contact_group_items`
+ (`group_id`, `user_id`, `mkdate`, `chdate`)
+ SELECT `contact_groups`.`id` AS group_id, `user_id`, `statusgruppe_user`.`mkdate` as mkdate, `statusgruppe_user`.`mkdate` AS chdate
+ FROM `statusgruppe_user`
+ INNER JOIN `contact_groups`
+ ON `statusgruppe_user`.`statusgruppe_id` = `contact_groups`.`old_group_id`
+ WHERE `statusgruppe_id` = :old_group_id"
+ );
+ $old_member_delete_stmt = $db->prepare("DELETE FROM `statusgruppe_user` WHERE `statusgruppe_id` = :old_group_id");
+
+ foreach ($old_groups as $old_group) {
+ $new_group_stmt->execute([
+ 'name' => $old_group['name'],
+ 'user_id' => $old_group['range_id'],
+ 'old_group_id' => $old_group['statusgruppe_id'],
+ 'mkdate' => $old_group['mkdate'],
+ 'chdate' => $old_group['chdate']
+ ]);
+ $group_member_stmt->execute([
+ 'old_group_id' => $old_group['statusgruppe_id']
+ ]);
+ $old_member_delete_stmt->execute([
+ 'old_group_id' => $old_group['statusgruppe_id']
+ ]);
+ }
+
+ //Delete old status groups:
+ $db->exec(
+ "DELETE FROM `statusgruppen` WHERE `range_id` IN (
+ SELECT `user_id` FROM `auth_user_md5`
+ )"
+ );
+
+ //Delete the old group ID:
+ $db->exec("ALTER TABLE `contact_groups` DROP COLUMN `old_group_id`");
+ }
+
+ protected function up()
+ {
+ $db = DBManager::get();
+
+ $db->exec(
+ "CREATE TABLE IF NOT EXISTS `calendar_date_exceptions` (
+ `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `calendar_date_id` CHAR(32) COLLATE latin1_bin NOT NULL,
+ `date` DATE NOT NULL,
+ `mkdate` INT(11) UNSIGNED NOT NULL DEFAULT 0,
+ `chdate` INT(11) UNSIGNED NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`)
+ )"
+ );
+
+ $this->migrateEventData();
+
+ $this->migrateCalendarEvent();
+
+ $this->migrateCalendarUser();
+
+ $this->addContactGroups();
+ }
+
+
+ protected function down()
+ {
+ //I see nothing, I hear nothing, I know nothing! NOTHING!!
+ }
+}
diff --git a/db/studip_default_data.sql b/db/studip_default_data.sql
index 96bb42b..df7ee8b 100644
--- a/db/studip_default_data.sql
+++ b/db/studip_default_data.sql
@@ -162,7 +162,7 @@ INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `c
INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `chdate`, `description`) VALUES('BLUBBER_GLOBAL_MESSENGER_ACTIVATE', '1', 'boolean', 'global', 'global', 1591630778, 1591630778, 'Ist Blubber unter Community global aktiv? Blubber in Veranstaltungen wird über das Plugin Blubber aktiviert oder deaktiviert.');
INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `chdate`, `description`) VALUES('BLUBBER_GLOBAL_THREAD_OPTOUT', '1', 'boolean', 'global', 'global', 1640797278, 1640797278, 'Gibt an, ob beim globalen Blubber Thread ein Opt-Out-Verfahren genutzt werden soll');
INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `chdate`, `description`) VALUES('CALENDAR_ENABLE', '1', 'boolean', 'global', 'calendar', 1293118059, 1293118059, 'Schaltet ein oder aus, ob der Kalender global verfügbar ist.');
-INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `chdate`, `description`) VALUES('CALENDAR_GRANT_ALL_INSERT', '0', 'boolean', 'global', 'calendar', 1462287762, 1462287762, 'Ermöglicht das Eintragen von Terminen in alle Nutzerkalender, ohne Beachtung des Rechtesystems.');
+INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `chdate`, `description`) VALUES('CALENDAR_GRANT_ALL_INSERT', '1', 'boolean', 'global', 'calendar', 1462287762, 1462287762, 'Ermöglicht das Eintragen von Terminen in alle Kalender der Nutzenden, ohne Beachtung des Rechtesystems.');
INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `chdate`, `description`) VALUES('CALENDAR_GROUP_ENABLE', '0', 'boolean', 'global', 'calendar', 1326799692, 1326799692, 'Schaltet die Gruppenterminkalender-Funktionen ein.');
INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `chdate`, `description`) VALUES('CALENDAR_SETTINGS', '{\"view\":\"week\",\"start\":\"9\",\"end\":\"20\",\"step_day\":\"900\",\"step_week\":\"1800\",\"type_week\":\"LONG\",\"step_week_group\":\"3600\",\"step_day_group\":\"3600\"}', 'array', 'user', '', 1403258015, 1403258015, 'persönliche Einstellungen des Kalenders');
INSERT INTO `config` (`field`, `value`, `type`, `range`, `section`, `mkdate`, `chdate`, `description`) VALUES('CONSULTATION_ALLOW_DOCENTS_RESERVING', '1', 'boolean', 'global', 'Terminvergabe', 1557244743, 1557244743, 'Lehrende können sich bei anderen Lehrenden anmelden');
diff --git a/lib/bootstrap-autoload.php b/lib/bootstrap-autoload.php
index 69910b2..6f3f4a7 100644
--- a/lib/bootstrap-autoload.php
+++ b/lib/bootstrap-autoload.php
@@ -8,6 +8,7 @@ StudipAutoloader::register();
// General classes folders
StudipAutoloader::addAutoloadPath('lib/models');
+StudipAutoloader::addAutoloadPath('lib/models/calendar');
StudipAutoloader::addAutoloadPath('lib/models/resources');
StudipAutoloader::addAutoloadPath('lib/classes');
StudipAutoloader::addAutoloadPath('lib/classes', 'Studip');
diff --git a/lib/calendar/CalendarColumn.class.php b/lib/calendar/CalendarColumn.class.php
index 3c071f1..cc3abea 100644
--- a/lib/calendar/CalendarColumn.class.php
+++ b/lib/calendar/CalendarColumn.class.php
@@ -13,6 +13,8 @@
* @author Rasmus Fuhse <fuhse@data-quest.de>
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
* @category Stud.IP
+ *
+ * @deprecated since Stud.IP 5.5
*/
class CalendarColumn
diff --git a/lib/calendar/CalendarExport.class.php b/lib/calendar/CalendarExport.class.php
deleted file mode 100644
index c9c10f6..0000000
--- a/lib/calendar/CalendarExport.class.php
+++ /dev/null
@@ -1,87 +0,0 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * CalendarExport.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-class CalendarExport
-{
- protected $_writer;
- protected $export;
- private $count;
-
- public function __construct(&$writer)
- {
- $this->_writer = $writer;
- }
-
- public function exportFromDatabase($range_id = null, $start = 0, $end = Calendar::CALENDAR_END, $event_types = null, $except = NULL)
- {
- global $_calendar_error, $user;
-
- if (!$range_id) {
- $range_id = $user->id;
- }
- $calendar = new SingleCalendar($range_id);
-
- $this->_export($this->_writer->writeHeader());
- $calendar->getEvents($event_types, $start, $end);
-
- foreach ($calendar->events as $event) {
- $this->_export($this->_writer->write($event));
- }
- $this->count = sizeof($calendar->events);
-
- $this->_export($this->_writer->writeFooter());
- }
-
- public function exportFromObjects($events)
- {
- global $_calendar_error;
-
- $this->_export($this->_writer->writeHeader());
-
- $this->count = 0;
- foreach ($events as $event) {
- $this->_export($this->_writer->write($event));
- $this->count++;
- }
-
- if (!sizeof($events)) {
- $message = _('Es wurden keine Termine exportiert.');
- } else {
- $message = sprintf(ngettext('Es wurde 1 Termin exportiert', 'Es wurden %s Termine exportiert', sizeof($events)), sizeof($events));
- }
-
- $this->_export($this->_writer->writeFooter());
- }
-
- public function _export($exp)
- {
- if (!empty($exp)) {
- $this->export[] = $exp;
- }
- }
-
- public function getExport()
- {
- return $this->export;
- }
-
- public function getCount()
- {
- return $this->count;
- }
-}
diff --git a/lib/calendar/CalendarExportException.class.php b/lib/calendar/CalendarExportException.class.php
deleted file mode 100644
index fbdabda..0000000
--- a/lib/calendar/CalendarExportException.class.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-/**
- * CalendarExportException.php - indicates an error during
- * calendar export or import
- *
- * 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>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
-*/
-
-class CalendarExportException extends Exception
-{
-}
diff --git a/lib/calendar/CalendarExportFile.class.php b/lib/calendar/CalendarExportFile.class.php
deleted file mode 100644
index 1e0f3fb..0000000
--- a/lib/calendar/CalendarExportFile.class.php
+++ /dev/null
@@ -1,122 +0,0 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * CalendarExportFile.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-class CalendarExportFile extends CalendarExport
-{
- private $file_name = 'studip';
- private $tmp_file_name;
- private $path;
-
- public function __construct(&$writer, $path = null, $file_name = null)
- {
- global $TMP_PATH;
-
- parent::__construct($writer);
-
- if (!$file_name) {
- $this->tmp_file_name = $this->makeUniqueFilename();
- $this->file_name .= '.' . $writer->getDefaultFileNameSuffix();
- } else {
- $this->file_name = $file_name;
- $this->tmp_file_name = $file_name;
- }
-
- if (!$path) {
- $this->path = $TMP_PATH . '/';
- }
-
- $this->_writer = $writer;
- }
-
- public function exportFromDatabase($range_id = null, $start = 0, $end = Calendar::CALENDAR_END, $event_types = null, $except = null)
- {
- $this->_createFile();
- parent::exportFromDatabase($range_id, $start, $end, $event_types, $except);
- $this->_closeFile();
- }
-
- public function exportFromObjects($events)
- {
- $this->_createFile();
- parent::exportFromObjects($events);
- $this->_closeFile();
- }
-
- public function sendFile()
- {
- if (file_exists($this->path . $this->tmp_file_name)) {
- header('Location: ' . FileManager::getDownloadURLForTemporaryFile($this->tmp_file_name, $this->file_name));
- } else {
- throw new CalendarExportException(_('Die Export-Datei konnte nicht erstellt werden!'));
- }
- }
-
- public function makeUniqueFileName()
- {
- return md5(uniqid(rand() . "Stud.IP Calendar"));
- }
-
- // returns file handle
- public function getExport()
- {
- return $this->export;
- }
-
- public function getFileName()
- {
- return $this->file_name;
- }
-
- public function getTempFileName()
- {
- return $this->tmp_file_name;
- }
-
- public function _createFile()
- {
- if (!(is_dir($this->path))) {
- if (!mkdir($this->path)) {
- var_dump($this->path); exit;
- throw new CalendarExportException(_('Das Export-Verzeichnis konnte nicht angelegt werden!'));
- } else {
- if (!chmod($this->path, 0777)) {
- throw new CalendarExportException(_('Die Zugriffsrechte auf das Export-Verzeichnis konnten nicht geändert werden!'));
- }
- }
- }
- if (file_exists($this->path . $this->tmp_file_name)) {
- if (!unlink($this->path . $this->tmp_file_name)) {
- throw new CalendarExportException(_('Eine bestehende Export-Datei konnte nicht gelöscht werden!'));
- }
- }
- $this->export = fopen($this->path . $this->tmp_file_name, "wb");
- if (!$this->export) {
- throw new CalendarExportException(_("Die Export-Datei konnte nicht erstellt werden!"));
- }
- }
-
- public function _export($exp)
- {
- fwrite($this->export, $exp);
- }
-
- public function _closeFile()
- {
- fclose($this->export);
- }
-}
diff --git a/lib/calendar/CalendarImport.class.php b/lib/calendar/CalendarImport.class.php
deleted file mode 100644
index 803a58d..0000000
--- a/lib/calendar/CalendarImport.class.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * CalendarImport.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-class CalendarImport
-{
-
- const IGNORE_ERRORS = 1;
-
- protected $_parser;
- private $data;
- private $public_to_private = false;
-
- public function __construct(CalendarParser &$parser, $data = null)
- {
- $this->_parser = $parser;
- $this->data = $data;
- }
-
- public function getContent()
- {
- return $this->data;
- }
-
- public function importIntoDatabase($range_id, $ignore = CalendarImport::IGNORE_ERRORS)
- {
- $this->_parser->changePublicToPrivate($this->public_to_private);
- if ($this->_parser->parseIntoDatabase($range_id, $this->getContent(), $ignore)) {
- return true;
- }
-
- return false;
- }
-
- public function importIntoObjects($ignore = CalendarImport::IGNORE_ERRORS)
- {
- $this->_parser->changePublicToPrivate($this->public_to_private);
- if ($this->_parser->parseIntoObjects($this->getContent(), $ignore)) {
- return true;
- }
-
- return false;
- }
-
- public function getObjects()
- {
- return $objects =& $this->_parser->getObjects();
- }
-
- public function getCount()
- {
- return $this->_parser->getCount($this->getContent());
- }
-
- public function changePublicToPrivate($value = TRUE)
- {
- $this->public_to_private = $value;
- }
-
- public function getClientIdentifier()
- {
- if (!$client_identifier = $this->_parser->getClientIdentifier()) {
- return $this->_parser->getClientIdentifier($this->getContent());
- }
- return $client_identifier;
- }
-
- public function setImportSem($do_import)
- {
- if ($do_import) {
- $this->_parser->import_sem = true;
- } else {
- $this->_parser->import_sem = false;
- }
- }
-
-}
diff --git a/lib/calendar/CalendarImportFile.class.php b/lib/calendar/CalendarImportFile.class.php
deleted file mode 100644
index 3e2ba15..0000000
--- a/lib/calendar/CalendarImportFile.class.php
+++ /dev/null
@@ -1,141 +0,0 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * CalendarImportFile.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-class CalendarImportFile extends CalendarImport
-{
-
- private $file;
- private $path;
-
- /**
- *
- */
- public function __construct(&$parser, $file, $path = '')
- {
- parent::__construct($parser);
- $this->file = $file;
- $this->path = $path;
- }
-
- /**
- *
- */
- public function getContent()
- {
- $data = '';
- if (!$file = @fopen($this->file['tmp_name'], 'rb')) {
- throw new CalendarExportException(_("Die Import-Datei konnte nicht geöffnet werden!"));
- return false;
- }
- if ($file) {
- while (!feof($file)) {
- $data .= fread($file, 1024);
- }
- fclose($file);
- }
- return $data;
- }
-
- /**
- *
- */
- public function getFileName()
- {
- return $this->file['name'];
- }
-
- /**
- *
- */
- public function getFileType()
- {
- return $this->_parser->getType();
- }
-
- /**
- *
- */
- public function getFileSize()
- {
- if (file_exists($this->file['tmp_name'])) {
- return filesize($this->file['tmp_name']);
- }
- return false;
- }
-
- /**
- *
- */
- public function checkFile()
- {
- return true;
- }
-
- /**
- *
- */
- public function importIntoDatabase($range_id, $ignore = CalendarImport::IGNORE_ERRORS)
- {
- if ($this->checkFile()) {
- parent::importIntoDatabase($range_id, $ignore);
- return true;
- }
- throw new CalendarExportException(_('Die Datei konnte nicht gelesen werden!'));
- return false;
- }
-
- /**
- *
- */
- public function importIntoObjects($ignore = CalendarImport::IGNORE_ERRORS)
- {
- global $_calendar_error;
-
- if ($this->checkFile()) {
- parent::importIntoObjects($ignore);
- return true;
- }
- throw new CalendarExportException(_('Die Datei konnte nicht gelesen werden!'));
- }
-
- /**
- *
- */
- public function deleteFile()
- {
- if (!unlink($this->file['tmp_name'])) {
- throw new CalendarExportException(_("Die Datei konnte nicht gelöscht werden!"));
- return false;
- }
- return true;
- }
-
- /**
- *
- */
- public function _getFileExtension()
- {
- $i = mb_strrpos($this->file['name'], '.');
- if (!$i) {
- return '';
- }
- $l = mb_strlen($this->file['name']) - $i;
- $ext = mb_substr($this->file['name'], $i + 1, $l);
- return $ext;
- }
-}
diff --git a/lib/calendar/CalendarParser.class.php b/lib/calendar/CalendarParser.class.php
deleted file mode 100644
index 7578076..0000000
--- a/lib/calendar/CalendarParser.class.php
+++ /dev/null
@@ -1,132 +0,0 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * CalendarParser.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-class CalendarParser
-{
- private $events = [];
- protected $components;
- private $type;
- private $number_of_events;
- protected $public_to_private = false;
- protected $client_identifier;
- private $time;
- protected $import_sem = false;
-
- public function __construct()
- {
- $this->client_identifier = '';
- }
-
- public function parse($data, $ignore = null)
- {
- foreach ($data as $properties) {
- if ($this->public_to_private && $properties['CLASS'] == 'PUBLIC') {
- $properties['CLASS'] = 'PRIVATE';
- }
- $properties['CATEGORIES'] = implode(', ', $properties['CATEGORIES']);
- $this->components[] = $properties;
- }
- }
-
- public function getCount($data)
- {
- return 0;
- }
-
- public function parseIntoDatabase($range_id, $data, $ignore)
- {
- if ($this->parseIntoObjects($range_id, $data, $ignore)) {
- foreach ($this->events as $event) {
- $event->store();
- }
- return true;
- }
-
- return false;
- }
-
- public function parseIntoObjects($range_id, $data, $ignore)
- {
- $this->time = time();
- if ($this->parse($data, $ignore)) {
- if (is_array($this->components)) {
- foreach ($this->components as $component) {
- $calendar_event = CalendarEvent::findByUid($component['UID'], $range_id);
- if ($calendar_event) {
- $this->setProperties($calendar_event, $component);
- $calendar_event->setRecurrence($component['RRULE']);
- $this->events[] = $calendar_event;
- } else {
- $calendar_event = new CalendarEvent();
- $event = new EventData();
- $event->author_id = $GLOBALS['user']->id;
- $event->event_id = $event->getNewId();
- $event->uid = $component['UID'];
- $calendar_event->range_id = $range_id;
- $calendar_event->event_id = $event->event_id;
- $calendar_event->event = $event;
- $this->setProperties($calendar_event, $component);
- $calendar_event->setRecurrence($component['RRULE']);
- $this->events[] = $calendar_event;
- }
- }
- }
- return true;
- }
- $message = _('Die Import-Daten konnten nicht verarbeitet werden!');
-
- return false;
- }
-
- private function setProperties($calendar_event, $component)
- {
- $calendar_event->setStart($component['DTSTART']);
- $calendar_event->setEnd($component['DTEND']);
- $calendar_event->setTitle($component['SUMMARY']);
- $calendar_event->event->description = $component['DESCRIPTION'];
- $calendar_event->setAccessibility($component['CLASS']);
- $calendar_event->setUserDefinedCategories($component['CATEGORIES']);
- $calendar_event->event->category_intern = $component['STUDIP_CATEGORY'] ?: 1;
- $calendar_event->setPriority($component['PRIORITY'] ?? 0);
- $calendar_event->event->location = $component['LOCATION'];
- $calendar_event->setExceptions($component['EXDATE']);
- $calendar_event->event->mkdate = $component['CREATED'] ?? time();
- $calendar_event->event->chdate = $component['LAST-MODIFIED'] ?: $component['CREATED'] ?? time();
- $calendar_event->event->importdate = $this->time;
- }
-
- public function getType()
- {
- return $this->type;
- }
-
- public function &getObjects()
- {
- return $objects =& $this->events;
- }
-
- public function changePublicToPrivate($value = true)
- {
- $this->public_to_private = $value;
- }
-
- public function getClientIdentifier($data = null)
- {
- return $this->client_identifier;
- }
-}
diff --git a/lib/calendar/CalendarView.class.php b/lib/calendar/CalendarView.class.php
index e3bfd05..fc69127 100644
--- a/lib/calendar/CalendarView.class.php
+++ b/lib/calendar/CalendarView.class.php
@@ -38,6 +38,8 @@
* print $plan->render();
*
* @since 2.0
+ *
+ * @deprecated since Stud.IP 5.5
*/
class CalendarView
@@ -213,7 +215,7 @@ class CalendarView
'entry_height' => $this->getHeight()
];
$factory = new Flexi_TemplateFactory(dirname(__file__).'/../../app/views');
- PageLayout::addStyle($factory->render('calendar/stylesheet', $style_parameters));
+ PageLayout::addStyle($factory->render('calendar/schedule/stylesheet', $style_parameters));
$template = $GLOBALS['template_factory']->open("calendar/calendar_view.php");
$template->set_attribute("calendar_view", $this);
diff --git a/lib/calendar/CalendarWeekView.class.php b/lib/calendar/CalendarWeekView.class.php
index e0d0a6b..c1e0f24 100644
--- a/lib/calendar/CalendarWeekView.class.php
+++ b/lib/calendar/CalendarWeekView.class.php
@@ -20,6 +20,8 @@
* Kind of bean class for the calendar view.
*
* @since 2.0
+ *
+ * @deprecated since Stud.IP 5.5
*/
class CalendarWeekView extends CalendarView
diff --git a/lib/calendar/CalendarWidgetView.php b/lib/calendar/CalendarWidgetView.php
index 946592c..df980ff 100644
--- a/lib/calendar/CalendarWidgetView.php
+++ b/lib/calendar/CalendarWidgetView.php
@@ -5,6 +5,8 @@
* @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
* @license GPL2 or any later version
* @since Stud.IP 3.4
+ *
+ * @deprecated since Stud.IP 5.5
*/
class CalendarWidgetView extends CalendarWeekView
{
diff --git a/lib/calendar/CalendarWriter.class.php b/lib/calendar/CalendarWriter.class.php
deleted file mode 100644
index 941c123..0000000
--- a/lib/calendar/CalendarWriter.class.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * CalendarWriter.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-class CalendarWriter
-{
- var $default_filename_suffix;
- var $format;
- var $client_identifier;
-
- public function __construct()
- {
- // initialize error handler
- $GLOBALS['_calendar_error'] = new ErrorHandler();
- }
-
- public function write(Event &$event)
- {
- return $event->properties;
- }
-
- public function writeHeader()
- {
- }
-
- public function writeFooter()
- {
- }
-
- public function getDefaultFilenameSuffix()
- {
- return $this->default_filename_suffix;
- }
-
- public function getFormat()
- {
- return $this->format;
- }
-}
diff --git a/lib/calendar/CalendarWriterICalendar.class.php b/lib/calendar/CalendarWriterICalendar.class.php
deleted file mode 100644
index e65a89f..0000000
--- a/lib/calendar/CalendarWriterICalendar.class.php
+++ /dev/null
@@ -1,629 +0,0 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * CalendarWriterICalendar.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-define('CALENDAR_WEEKSTART', 'MO');
-
-class CalendarWriterICalendar extends CalendarWriter
-{
- var $newline = "\r\n";
-
- public function __construct()
- {
-
- parent::__construct();
- $this->default_filename_suffix = "ics";
- $this->format = "iCalendar";
- }
-
- public function writeHeader()
- {
-
- // Default values
- $header = "BEGIN:VCALENDAR" . $this->newline;
- $header .= "VERSION:2.0" . $this->newline;
- if ($this->client_identifier) {
- $header .= "PRODID:" . $this->client_identifier . $this->newline;
- } else {
- $host = $_SERVER['SERVER_NAME'] ?? parse_url($GLOBALS['ABSOLUTE_URI_STUDIP'], PHP_URL_HOST);
- $header .= "PRODID:-//Stud.IP@{$host}//Stud.IP_iCalendar Library";
- $header .= " //EN" . $this->newline;
- }
- $header .= "METHOD:PUBLISH" . $this->newline;
-
- // time zone definition CET/CEST
- $header .= 'CALSCALE:GREGORIAN' . $this->newline
- . 'BEGIN:VTIMEZONE' . $this->newline
- . 'TZID:Europe/Berlin' . $this->newline
- . 'BEGIN:DAYLIGHT' . $this->newline
- . 'TZOFFSETFROM:+0100' . $this->newline
- . 'TZOFFSETTO:+0200' . $this->newline
- . 'TZNAME:CEST' . $this->newline
- . 'DTSTART:19700329T020000' . $this->newline
- . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3' . $this->newline
- . 'END:DAYLIGHT' . $this->newline
- . 'BEGIN:STANDARD' . $this->newline
- . 'TZOFFSETFROM:+0200' . $this->newline
- . 'TZOFFSETTO:+0100' . $this->newline
- . 'TZNAME:CET' . $this->newline
- . 'DTSTART:19701025T030000' . $this->newline
- . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10' . $this->newline
- . 'END:STANDARD' . $this->newline
- . 'END:VTIMEZONE' . $this->newline;
-
- return $header;
- }
-
- public function writeFooter()
- {
- return "END:VCALENDAR" . $this->newline;
- }
-
- /**
- * Export this component as iCalendar format
- *
- * @param object $event The event to export.
- * @return String iCalendar formatted data
- */
- public function write(Event &$event)
- {
-
- $match_pattern_1 = ['\\', '\n', ';', ','];
- $replace_pattern_1 = ['\\\\', '\\n', '\;', '\,'];
- $match_pattern_2 = ['\\', '\n', ';'];
- $replace_pattern_2 = ['\\\\', '\\n', '\;'];
- $exdate_time = 0;
-
- $result = "BEGIN:VEVENT" . $this->newline;
-
- foreach ($event->getProperties() as $name => $value) {
- $params = [];
- $params_str = '';
-
- if ($name === 'SUMMARY') {
- $value = $event->getTitle();
- }
- if ($value === '' || is_null($value)) {
- continue;
- }
-
- switch ($name) {
- // not supported event properties
- case 'SEMNAME':
- case 'EXPIRE':
- case 'STUDIP_AUTHOR_ID':
- case 'STUDIP_EDITOR_ID':
- case 'STUDIP_ID':
- case 'BEGIN':
- case 'END':
- case 'EVENT_TYPE':
- case 'SEM_ID':
- case 'STUDIP_GROUP_STATUS':
- case 'STUDIP_CATEGORY':
- continue 2;
-
- // text fields
- case 'SUMMARY':
- $value = str_replace($match_pattern_1, $replace_pattern_1, $value);
- break;
- case 'DESCRIPTION':
- $value = str_replace($match_pattern_1, $replace_pattern_1, $event->getDescription());
- break;
- case 'LOCATION':
- $value = str_replace($match_pattern_1, $replace_pattern_1, $event->getLocation());
- break;
-
- case 'CATEGORIES':
- $value = $this->_exportCategories($event);
- break;
-
- // Date fields
- case 'LAST-MODIFIED':
- case 'CREATED':
- case 'COMPLETED':
- $value = $this->_exportDateTime($value, true);
- break;
-
- case 'DTSTAMP':
- $value = $this->_exportDateTime(time(), true);
- break;
-
- case 'DTSTART':
- $exdate_time = $value;
- case 'DTEND':
- if ($event->isDayEvent()) {
- $params['VALUE'] = 'DATE';
- $params_str = ';VALUE=DATE';
- $value++;
- }
- case 'DUE':
- case 'RECURRENCE-ID':
- if (array_key_exists('VALUE', $params)) {
- if ($params['VALUE'] == 'DATE') {
- $value = $this->_exportDate($value);
- } else {
- $value = $this->_exportDateTime($value);
- $params_str = ';TZID=Europe/Berlin';
- }
- } else {
- $value = $this->_exportDateTime($value);
- $params_str = ';TZID=Europe/Berlin';
- }
- break;
-
- case 'EXDATE':
- if (array_key_exists('VALUE', $params)) {
- $value = $this->_exportExDate($value, $params['VALUE']);
- } else {
- $value = $this->_exportExDateTime($value, $exdate_time);
- }
- $params_str = ';TZID=Europe/Berlin';
- break;
-
- case 'RDATE':
- if (array_key_exists('VALUE', $params)) {
- if ($params['VALUE'] == 'DATE') {
- $value = $this->_exportDate($value);
- } else if ($params['VALUE'] == 'PERIOD') {
- $value = $this->_exportPeriod($value);
- } else {
- $value = $this->_exportDateTime($value);
- }
- } else {
- $value = $this->_exportDateTime($value);
- }
- break;
-
- case 'TRIGGER':
- if (array_key_exists('VALUE', $params)) {
- if ($params['VALUE'] == 'DATE-TIME') {
- $value = $this->_exportDateTime($value);
- } else if ($params['VALUE'] == 'DURATION') {
- $value = $this->_exportDuration($value);
- }
- } else {
- $value = $this->_exportDuration($value);
- }
- break;
-
- // Duration fields
- case 'DURATION':
- $value = $this->_exportDuration($value);
- break;
-
- // Period of time fields
- case 'FREEBUSY':
- $value_str = '';
- foreach ($value as $period) {
- $value_str .= empty($value_str) ? '' : ',';
- $value_str .= $this->_exportPeriod($period);
- }
- $value = $value_str;
- break;
-
-
- // UTC offset fields
- case 'TZOFFSETFROM':
- case 'TZOFFSETTO':
- $value = $this->_exportUtcOffset($value);
- break;
-
- // Integer fields
- case 'PERCENT-COMPLETE':
- if ($event->getPermission() == Event::PERMISSION_CONFIDENTIAL)
- $value = '';
- case 'REPEAT':
- case 'SEQUENCE':
- $value = "$value";
- break;
-
- case 'PRIORITY':
- if ($event->getPermission() == Event::PERMISSION_CONFIDENTIAL)
- $value = '0';
- else {
- switch ($value) {
- case 1:
- $value = '1';
- break;
- case 2:
- $value = '5';
- break;
- case 3:
- $value = '9';
- break;
- default:
- $value = '0';
- }
- }
- break;
-
- // Geo fields
- case 'GEO':
- if ($event->getPermission() == Event::PERMISSION_CONFIDENTIAL)
- $value = '';
- else
- $value = $value['latitude'] . ',' . $value['longitude'];
- break;
-
- // Recursion fields
- case 'EXRULE':
- case 'RRULE':
- if ($event->getRecurrence('rtype') != 'SINGLE')
- $value = $this->_exportRecurrence($value);
- else
- continue 2;
- break;
-
- case "UID":
- $value = "$value";
- }
- if ($name) {
- $attr_string = "$name$params_str:$value";
- $result .= $this->_foldLine($attr_string) . $this->newline;
- }
- }
- // if ($event->isGroupEvent()) {
- if ($event instanceof CalendarEvent && $event->attendees->count() > 1) {
- $result .= $this->_exportGroupEventProperties($event);
- }
- // $result .= 'DTSTAMP:' . $this->_exportDateTime(time()) . $this->newline;
- $result .= "END:VEVENT" . $this->newline;
-
- return $result;
- }
-
- /**
- * Export a UTC Offset field
- *
- * @param array $value
- * @return String UTC offset field iCalendar formatted
- */
- public function _exportUtcOffset($value)
- {
- $offset = $value['ahead'] ? '+' : '-';
- $offset .= sprintf('%02d%02d', $value['hour'], $value['minute']);
- if (array_key_exists('second', $value)) {
- $offset .= sprintf('%02d', $value['second']);
- }
-
- return $offset;
- }
-
- /**
- * Export a Time Period field
- *
- * @param array $value
- * @return String Period field iCalendar formatted
- */
- public function _exportPeriod($value)
- {
- $period = $this->_exportDateTime($value['start']);
- $period .= '/';
- if (array_key_exists('duration', $value)) {
- $period .= $this->_exportDuration($value['duration']);
- } else {
- $period .= $this->_exportDateTime($value['end']);
- }
- return $period;
- }
-
- /**
- * Export a DateTime field
- *
- * @param int $value Unix timestamp
- * @return String Date and time (UTC) iCalendar formatted
- */
- public function _exportDateTime($value, $utc = false)
- {
-
-// $TZOffset = 3600 * mb_substr(date('O', $value), 0, 3);
-// $TZOffset += 60 * mb_substr(date('O', $value), 3, 2);
- //transform local time in UTC
- if ($utc) {
- $value -= date('Z', $value);
- }
-
- return $this->_exportDate($value) . 'T' . $this->_exportTime($value, $utc);
- }
-
- /**
- * Export a Time field
- *
- * @param int $value Unix timestamp
- * @return String Time (UTC) iCalendar formatted
- */
- public function _exportTime($value, $utc = false)
- {
- $time = date("His", $value);
- if ($utc) {
- $time .= 'Z';
- }
-
- return $time;
- }
-
- /**
- * Export a Date field
- */
- public function _exportDate($value)
- {
- return date("Ymd", $value);
- }
-
- /**
- * Export a duration value
- */
- public function _exportDuration($value)
- {
- $duration = '';
- if ($value < 0) {
- $value *= - 1;
- $duration .= '-';
- }
- $duration .= 'P';
-
- $weeks = floor($value / (7 * 86400));
- $value = $value % (7 * 86400);
- if ($weeks) {
- $duration .= $weeks . 'W';
- }
-
- $days = floor($value / (86400));
- $value = $value % (86400);
- if ($days) {
- $duration .= $days . 'D';
- }
-
- if ($value) {
- $duration .= 'T';
-
- $hours = floor($value / 3600);
- $value = $value % 3600;
- if ($hours) {
- $duration .= $hours . 'H';
- }
-
- $mins = floor($value / 60);
- $value = $value % 60;
- if ($mins) {
- $duration .= $mins . 'M';
- }
-
- if ($value) {
- $duration .= $value . 'S';
- }
- }
-
- return $duration;
- }
-
- /**
- * Export a recurrence rule
- */
- public function _exportRecurrence($value)
- {
- $rrule = [];
- // the last day of week in a MONTHLY or YEARLY recurrence in the
- // Stud.IP calendar is 5, in iCalendar it is -1
- if ($value['sinterval'] == '5')
- $value['sinterval'] = '-1';
-
- if ($value['count'])
- unset($value['expire']);
-
- foreach ($value as $r_param => $r_value) {
- if ($r_value) {
- switch ($r_param) {
- case 'rtype':
- $rrule[] = 'FREQ=' . $r_value;
- break;
- case 'expire':
- // end of unix epoche (this is also the end of Stud.IP epoche ;-) )
- if ($r_value < Calendar::CALENDAR_END)
- $rrule[] = 'UNTIL=' . $this->_exportDateTime($r_value, true);
- break;
- case 'linterval':
- $rrule[] = 'INTERVAL=' . $r_value;
- break;
- case 'wdays':
- switch ($value['rtype']) {
- case 'WEEKLY':
- $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value);
- break;
- // Some CUAs (e.g. Outlook) don't understand the nWDAY syntax
- // (where n is the nth ocurrence of the day in a given period of
- // time and WDAY is the day of week) the RRULE uses the BYSETPOS
- // rule.
- case 'MONTHLY':
- case 'YEARLY':
- $rrule[] = 'BYDAY=' . $value['sinterval'] . $this->_exportWdays($r_value);
- $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value);
- // The Stud.IP calendar don't support multiple values in a
- // comma separated list.
-
- if ($value['sinterval'])
- $rrule[] = 'BYSETPOS=' . $value['sinterval'];
-
- break;
- }
- break;
- case 'day':
- $rrule[] = 'BYMONTHDAY=' . $r_value;
- break;
- case 'month':
- $rrule[] = 'BYMONTH=' . $r_value;
- break;
- case 'count':
- $rrule[] = 'COUNT=' . $r_value;
- break;
- }
- }
- }
-
- if ($value['rtype'] == 'WEEKLY' && CALENDAR_WEEKSTART != 'MO') {
- $rrule[] = 'WKST=' . CALENDAR_WEEKSTART;
- }
-
- return implode(';', $rrule);
- }
-
- /**
- * Return the Stud.IP calendar wdays attribute of a event recurrence
- */
- public function _exportWdays($value)
- {
- $wdays_map = ['1' => 'MO', '2' => 'TU', '3' => 'WE', '4' => 'TH', '5' => 'FR',
- '6' => 'SA', '7' => 'SU'];
- $wdays = [];
- preg_match_all('/(\d)/', $value, $matches);
- foreach ($matches[1] as $match) {
- $wdays[] = $wdays_map[$match];
- }
-
- return implode(',', $wdays);
- }
-
- public function _exportExDate($value, $param)
- {
- $exdates = [];
- $date_times = explode(',', $value);
- foreach ($date_times as $date_time) {
- $exdates[] = $this->_exportDate($date_time);
- }
-
- return implode(',', $exdates);
- }
-
- public function _exportExDateTime($value, $param)
- {
- $exdates = [];
- $date_times = explode(',', $value);
- foreach ($date_times as $date_time) {
- $exdates[] = $this->_exportDate($date_time) . 'T' . $this->_exportTime($param);
- }
-
- return implode(',', $exdates);
- }
-
- private function _exportGroupEventProperties(Event $event)
- {
- $organizer = User::find($event->getAuthorId());
- if ($organizer) {
- $properties = $this->_foldLine('ORGANIZER;CN="'
- . $organizer->getFullName()
- . '":mailto:' . $organizer->Email)
- . $this->newline;
- } else {
- $properties = $this->_foldLine('ORGANIZER;CN="'
- . _('unbekannt')
- . '":mailto:' . $GLOBALS['user']->email)
- . $this->newline;
- }
- foreach ($event->attendees as $event_member) {
- if ($event->getAuthorId() == $event_member->user->id) {
- if ($event_member->user) {
- $properties .= $this->_foldLine('ATTENDEE;'
- . 'ROLE=REQ-PARTICIPANT;'
- . 'CN="' . $event_member->user->getFullName()
- . '":mailto:' . $event_member->user->Email)
- . $this->newline;
- } else {
- $properties = '';
- /*
- $properties .= $this->_foldLine('ATTENDEE;'
- . 'ROLE=REQ-PARTICIPANT;'
- . 'CN="' . _('unbekannt') . '"')
- . $this->newline;
- *
- */
- }
- } else {
- if ($event_member->user) {
- switch ($event_member->group_status) {
- case CalendarEvent::PARTSTAT_ACCEPTED :
- $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT'
- . ';PARTSTAT=ACCEPTED';
- break;
- case CalendarEvent::PARTSTAT_DELEGATED :
- $attendee = 'ATTENDEE;ROLE=NON-PARTICIPANT'
- . ';PARTSTAT=ACCEPTED'
- . ';DELEGATED-TO="mailto:'
- . $this->getFacultyEmail($organizer->getId())
- . '"';
- break;
- case CalendarEvent::PARTSTAT_DECLINED :
- $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT'
- . ';PARTSTAT=DECLINED';
- break;
- default :
- $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT';
- $attendee .= ';PARTSTAT=TENTATIVE';
- $attendee .= ';RSVP=TRUE';
-
- }
- $attendee .= ';CN="' . $event_member->user->getFullName()
- . '":mailto:' . $event_member->user->Email;
- /*
- } else {
- $attendee .= ';CN="' . _('unbekannt') . '"';
- }
- *
- */
- $properties .= $this->_foldLine($attendee) . $this->newline;
- }
- }
- }
- return $properties;
- }
-
- public function getFacultyEmail($user_id)
- {
- $stmt = DBManager::get()->prepare('SELECT email FROM Institute i '
- . 'LEFT JOIN user_inst ui USING(institut_id) '
- . 'WHERE i.Institut_id = fakultaets_id AND user_id = ?');
- $stmt->execute([$user_id]);
- return $stmt->fetchColumn();
- }
-
- public function _exportCategories($event)
- {
- return implode(',' ,$event->toStringCategories(true));
- }
-
- /**
- * Return the folded version of a line
- */
- public function _foldLine($line)
- {
- $line = preg_replace('/(\r\n|\n|\r)/', '\n', $line);
- if (mb_strlen($line) > 75) {
- $foldedline = '';
- while ($line !== '') {
- $maxLine = mb_substr($line, 0, 75);
- $cutPoint = max(60, max(mb_strrpos($maxLine, ';'), mb_strrpos($maxLine, ':')) + 1);
-
- $foldedline .= ( empty($foldedline)) ?
- mb_substr($line, 0, $cutPoint) :
- $this->newline . ' ' . mb_substr($line, 0, $cutPoint);
-
- $line = (mb_strlen($line) <= $cutPoint) ? '' : mb_substr($line, $cutPoint);
- }
- return $foldedline;
- }
- return $line;
- }
-}
diff --git a/lib/calendar/lib/CalendarError.class.php b/lib/calendar/lib/CalendarError.class.php
deleted file mode 100644
index f796e1a..0000000
--- a/lib/calendar/lib/CalendarError.class.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * Error.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-
-class CalendarError
-{
-
- var $status;
- var $message;
- var $file;
- var $line;
-
- public function __construct($status, $message, $file = '', $line = '')
- {
- $this->status = $status;
- $this->message = $message;
- $this->file = $file;
- $this->line = $line;
- }
-
- public function getStatus()
- {
- return $this->status;
- }
-
- public function getMessage()
- {
- return $this->message;
- }
-
- public function getFile()
- {
- return $this->file;
- }
-
- public function getLine()
- {
- return $this->line;
- }
-
-}
diff --git a/lib/calendar/lib/ErrorHandler.class.php b/lib/calendar/lib/ErrorHandler.class.php
deleted file mode 100644
index 390b5d5..0000000
--- a/lib/calendar/lib/ErrorHandler.class.php
+++ /dev/null
@@ -1,119 +0,0 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * ErrorHandler.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-class ErrorHandler
-{
- // this is the state of the error handling if no error has occured
- const ERROR_NORMAL = 1;
- // this is the state of the error handling a message has to be displayed
- const ERROR_MESSAGE = 2;
- // this is the state of the error handling if something was going wrong but without data-loss
- const ERROR_WARNING = 4;
- // this is the state of the error handling if a critical error occured (maybe with data loss)
- const ERROR_CRITICAL = 8;
- // this is the state of the error handling if a fatal error occured and the execution of the process (e.g. import of events) was stopped
- const ERROR_FATAL = 16;
-
- var $errors;
- var $status;
-
- public function __construct()
- {
- $this->errors = [];
- $this->status = ErrorHandler::ERROR_NORMAL;
- }
-
- public function getStatus($status = NULL)
- {
- if ($status === NULL)
- return $this->status;
-
- return $status & $this->status;
- }
-
- public function getMaxStatus($status)
- {
- if ($status <= $this->status)
- return true;
-
- return false;
- }
-
- public function getMinStatus($status)
- {
- if ($status >= $this->status)
- return true;
-
- return false;
- }
-
- public function getErrors($status = NULL)
- {
- if ($status === NULL)
- return $this->errors;
-
- return $this->errors[$status];
- }
-
- public function getAllErrors()
- {
- $status = [ErrorHandler::ERROR_FATAL, ErrorHandler::ERROR_CRITICAL, ErrorHandler::ERROR_WARNING,
- ErrorHandler::ERROR_MESSAGE, ErrorHandler::ERROR_NORMAL];
- $errors = [];
- foreach ($status as $stat) {
- if (is_array($this->errors[$stat])) {
- $errors = array_merge($errors, $this->errors[$stat]);
- }
- }
- return $errors;
- }
-
- public function nextError($status)
- {
- if(is_array($this->errors[$status])) {
- return reset($this->errors[$status]);
- }
- return false;
- }
-
- public function throwError($status, $message, $file = '', $line = '')
- {
- $this->errors[$status][] = new CalendarError($status, $message, $file, $line);
- $this->status |= $status;
- reset($this->errors[$status]);
- if ($status == ErrorHandler::ERROR_FATAL) {
- echo '<b>';
- while ($error = $this->nextError(ErrorHandler::ERROR_FATAL)) {
- echo '<br />' . $error->getMessage();
- }
- echo '</b><br />';
- page_close();
- exit;
- }
- }
-
- public function throwSingleError($index, $status, $message, $file = '', $line = '')
- {
- static $index_list = [];
-
- if ($index_list[$index] != 1) {
- $this->throwError($status, $message, $file, $line);
- $index_list[$index] = 1;
- }
- }
-}
diff --git a/lib/classes/Event.class.php b/lib/classes/Event.class.php
deleted file mode 100644
index 6254884..0000000
--- a/lib/classes/Event.class.php
+++ /dev/null
@@ -1,223 +0,0 @@
-<?php
-
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
-
-interface Event
-{
- const PERMISSION_FORBIDDEN = 0;
- const PERMISSION_CONFIDENTIAL = 1;
- const PERMISSION_READABLE = 2;
- const PERMISSION_DELETABLE = 3;
- const PERMISSION_WRITABLE = 4;
- const PERMISSION_OWN = 5;
-
- /**
- * Returns a list of all categories the event belongs to.
- * Returns an empty string if no permission.
- *
- * @return string All categories as list.
- */
- public function toStringCategories();
-
- /**
- * Returns an array that represents the recurrence rule for this event.
- * If an index is given, returns only this field of the rule.
- *
- * @return array|string The array with th recurrence rule or only one field.
- */
- public function getRecurrence($index = null);
-
- /**
- * TODO Wird das noch benötigt?
- */
- public function getType();
-
- /**
- * Returns the title of this event.
- * If the user has not the permission Event::PERMISSION_READABLE,
- * the title is "Keine Berechtigung.".
- *
- * @return string
- */
- public function getTitle();
-
- /**
- * Returns the starttime as unix timestamp of this event.
- *
- * @return int The starttime of this event as a unix timestamp
- */
- public function getStart();
-
- /**
- * Returns the endtime as unix timestamp of this event.
- *
- * @return int the endtime of this event as a unix timestamp
- */
- public function getEnd();
-
- /**
- * Returns the duration of this event in seconds.
- *
- * @return int the duration of this event in seconds
- */
- function getDuration();
-
- /**
- * Returns the location.
- * Without permission or the location is not set an empty string is returned.
- *
- * @return string The location
- */
- function getLocation();
-
- /**
- * Returns the global uni id of this event.
- *
- * @return string The global unique id.
- */
- public function getUid();
-
- /**
- * Returns the description of the topic.
- * If the user has no permission or the event has no topic
- * or the topics have no descritopn an empty string is returned.
- *
- * @return String the description
- */
- function getDescription();
-
- /**
- * Returns the index of the category.
- * If the user has no permission, 255 is returned.
- *
- * @see config/config.inc.php $TERMIN_TYP
- * @return int The index of the category
- */
- public function getCategory();
-
- /**
- * Returns the user id of the last editor.
- *
- * @return null|int The editor id.
- */
- public function getEditorId();
-
- /**
- * Returns whether the event is a all day event.
- *
- * @return
- */
- public function isDayEvent();
-
- /**
- * Returns the accessibility of this event. The value is not influenced by
- * the permission of the actual user.
- *
- * According to RFC5545 the accessibility (property CLASS) is represented
- * by the 3 values PUBLIC, PRIVATE and CONFIDENTIAL. In RFC5545 the default
- * value is PUBLIC. In Stud.IP the default is PRIVATE.
- *
- * @return string The accessibility as string.
- */
- function getAccessibility();
-
- /**
- * Returns the unix timestamp of the last change.
- *
- * @access public
- */
- public function getChangeDate();
-
- /**
- * Returns the date time the event was imported.
- *
- * TODO not sure if we need this anymore
- *
- * @return int Date time of import as unix timestamp:
- */
- function getImportDate();
-
- /**
- * Returns all properties of this event.
- * The name of the properties correspond to the properties of the
- * iCalendar calendar data exchange format. There are a few properties with
- * the suffix STUDIP_ which have no eqivalent in the iCalendar format.
- *
- * DTSTART: The start date-time as unix timestamp.
- * DTEND: The end date-time as unix timestamp.
- * SUMMARY: The short description (title) that will be displayed in the views.
- * DESCRIPTION: The long description.
- * UID: The global unique id of this event.
- * CLASS:
- * CATEGORIES: A comma separated list of categories.
- * PRIORITY: The priority.
- * LOCATION: The location.
- * EXDATE: A comma separated list of unix timestamps.
- * CREATED: The creation date-time as unix timestamp.
- * LAST-MODIFIED: The date-time of last modification as unix timestamp.
- * DTSTAMP: The cration date-time of this instance of the event as unix
- * timestamp.
- * RRULE: All data for the recurrence rule for this event as array.
- * EVENT_TYPE:
- *
- *
- * @return array The properties of this event.
- */
- public function getProperties();
-
- /**
- * Returns the value of property with given name.
- *
- * @param type $name See CalendarEvent::getProperties() for accepted values.
- * @return mixed The value of the property.
- * @throws InvalidArgumentException
- */
- public function getProperty($name);
-
- public function havePermission($permission, $user_id = null);
-
- public function getPermission($user_id = null);
-
- /**
- * Returns the priority in a human readable form.
- * If the user has no permission an epmty string will be returned.
- *
- * @return string The priority as a string.
- */
- public function toStringPriority();
-
- /**
- * Returns the accessibilty in a human readable form.
- * If the user has no permission an epmty string will be returned.
- *
- * @return string The accessibility as string.
- */
- public function toStringAccessibility();
-
- /**
- * Returns a string representation of the recurrence rule.
- * If $only_type is true returns only the type of the recurrence.
- *
- * @param bool $only_type If true returns only the type of recurrence.
- * @return string The recurrence rule - human readable
- */
- public function toStringRecurrence($only_type = false);
-
- /**
- * Returns the author of this event as user object.
- *
- * @return User|null User object.
- */
- public function getAuthor();
-
- /**
- * Returns the editor of this event as user object.
- *
- * @return User|null User object.
- */
- public function getEditor();
-} \ No newline at end of file
diff --git a/lib/classes/Event.interface.php b/lib/classes/Event.interface.php
new file mode 100644
index 0000000..23f092b
--- /dev/null
+++ b/lib/classes/Event.interface.php
@@ -0,0 +1,184 @@
+<?php
+
+/*
+ * Event.interface.php - An interface for calendar events.
+ *
+ * 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.
+ *
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+
+
+/**
+ * The Event interface represents calendar events.
+ */
+interface Event
+{
+ /**
+ * Retrieves events that lie in a given time range.
+ *
+ * @param DateTime $begin The beginning of the time range.
+ *
+ * @param DateTime $end The end of the time range.
+ *
+ * @param string $range_id The range for which to get the events. This may be a user-ID,
+ * course-ID or another kind of ID.
+ *
+ * @return Event[] An array with event objects.
+ */
+ public static function getEvents(DateTime $begin, DateTime $end, string $range_id) : array;
+
+ /**
+ * Returns the ID of the event. This is the ID that is only
+ * valid inside of Stud.IP.
+ *
+ * @return string The ID of the event object.
+ */
+ public function getObjectId() : string;
+
+ /**
+ * Returns the ID of the primary object where this object is linked to
+ * in a primary-secondary relationship where this object is a secondary object.
+ *
+ * Example: A course date is a secondary object and the course it belongs to
+ * is the primary object.
+ *
+ * @return string The ID of the primary object or an empty string if the
+ * implementation of the Event interface is a class of primary objects.
+ */
+ public function getPrimaryObjectID() : string;
+
+ /**
+ * Returns the class of the Event implementation.
+ *
+ * @return string The class name of the Event instance.
+ */
+ public function getObjectClass() : string;
+
+ /**
+ * Returns the title of this event.
+ * If the user has not the permission Event::PERMISSION_READABLE,
+ * the title is "Keine Berechtigung.".
+ *
+ * @return string The title of the event.
+ */
+ public function getTitle() : string;
+
+ /**
+ * Returns the start time of the event.
+ *
+ * @return DateTime The start time of the event.
+ */
+ public function getBegin() : DateTime;
+
+ /**
+ * Returns the end time of the event.
+ *
+ * @return DateTime The end time of the event.
+ */
+ public function getEnd() : DateTime;
+
+ /**
+ * Returns the duration of the event.
+ *
+ * @return DateInterval The duration of the event.
+ */
+ public function getDuration() : DateInterval;
+
+ /**
+ * Returns the location where the event takes place, if applicable.
+ *
+ * @return string The location of the event.
+ */
+ public function getLocation() : string;
+
+ /**
+ * Returns the global unique id of the event.
+ *
+ * @return string The global unique id of the event.
+ */
+ public function getUniqueId() : string;
+
+ /**
+ * Returns the description of the event.
+ *
+ * @return string The description of the event.
+ */
+ public function getDescription() : string;
+
+ /**
+ * Returns additional descriptions of the Event object.
+ * These are specific for each implementation.
+ *
+ * @return array Additional descriptions for the Event implementation.
+ * Each array key represents a heading for the description and the
+ * value contains the description itself as plain text.
+ * In case this is not applicable for the implementation,
+ * an empty array is returned.
+ */
+ public function getAdditionalDescriptions() : array;
+
+ /**
+ * Returns whether the event is an all day event or not.
+ *
+ * @return bool True, if the event is an all day event, false otherwise.
+ */
+ public function isAllDayEvent() : bool;
+
+ /**
+ * Determines whether the specified user has write permissions for the event.
+ *
+ * @param string $user_id The user for which to check write permissions.
+ *
+ * @return bool True, if the user has write permissions, false otherwise.
+ */
+ public function isWritable(string $user_id) : bool;
+
+ /**
+ * Returns the creation date of the event.
+ *
+ * @return DateTime The creation date of the event.
+ */
+ public function getCreationDate() : DateTime;
+
+ /**
+ * Returns the modification date of the event.
+ *
+ * @return DateTime The modification date of the event.
+ */
+ public function getModificationDate() : DateTime;
+
+ /**
+ * Returns the import date of the event.
+ *
+ * @return DateTime The import date of the event.
+ */
+ public function getImportDate() : DateTime;
+
+ /**
+ * Returns the author of this event as user object.
+ *
+ * @return User|null The user object of the author of the event, if available.
+ */
+ public function getAuthor() : ?User;
+
+ /**
+ * Returns the editor of this event as user object.
+ *
+ * @return User|null The user object of the editor of the event, if available.
+ */
+ public function getEditor() : ?User;
+
+ /**
+ * Returns a JSON-encoded fullcalendar event object that represents the event.
+ *
+ * @param $user_id string The user for which to generate the fullcalendar event.
+ *
+ * @return \Studip\Calendar\EventData The EventData representation of the event.
+ */
+ public function toEventData(string $user_id) : \Studip\Calendar\EventData;
+}
diff --git a/lib/classes/IcalExport.php b/lib/classes/IcalExport.php
index f011c5e..2bb16d5 100644
--- a/lib/classes/IcalExport.php
+++ b/lib/classes/IcalExport.php
@@ -50,18 +50,25 @@ class IcalExport
while ($length--) {
while (1) {
$rnd = rand(48, 122);
- if ($rnd < 48)
+ if ($rnd < 48) {
continue;
- if ($rnd > 57 && $rnd < 65)
+ }
+ if ($rnd > 57 && $rnd < 65) {
continue;
- if ($rnd > 90 && $rnd < 97)
+ }
+ if ($rnd > 90 && $rnd < 97) {
continue;
- if ($rnd > 122)
+ }
+ if ($rnd > 122) {
continue;
+ }
$char = chr($rnd);
- if ($rejected[$char] > 1) {
+ if (isset($rejected[$char]) && $rejected[$char] > 1) {
continue;
}
+ if (!isset($rejected[$char])) {
+ $rejected[$char] = 0;
+ }
$rejected[$char]++;
$ret .= $char;
break;
diff --git a/lib/classes/JsonApi/Routes/Events/CourseEventsIndex.php b/lib/classes/JsonApi/Routes/Events/CourseEventsIndex.php
index 0ee6ded..250d144 100644
--- a/lib/classes/JsonApi/Routes/Events/CourseEventsIndex.php
+++ b/lib/classes/JsonApi/Routes/Events/CourseEventsIndex.php
@@ -27,19 +27,15 @@ class CourseEventsIndex extends JsonApiController
throw new AuthorizationFailedException();
}
- $dates = $course->dates->map(function ($courseDate) {
- return new \CourseEvent($courseDate->id);
- });
- $exDates = $course->ex_dates->map(function ($courseDate) {
- return new \CourseCancelledEvent($courseDate->id);
- });
-
- $allDates = array_merge($dates, $exDates);
- usort($allDates, function ($date1, $date2) {
+ $all_dates = array_merge(
+ $course->dates->getArrayCopy(),
+ $course->ex_dates->getArrayCopy()
+ );
+ usort($all_dates, function ($date1, $date2) {
return intval($date1->date) <=> intval($date2->date);
});
list($offset, $limit) = $this->getOffsetAndLimit();
- return $this->getPaginatedContentResponse(array_slice($allDates, $offset, $limit), count($allDates));
+ return $this->getPaginatedContentResponse(array_slice($all_dates, $offset, $limit), count($all_dates));
}
}
diff --git a/lib/classes/JsonApi/Routes/Events/UserEventsIcal.php b/lib/classes/JsonApi/Routes/Events/UserEventsIcal.php
index c8080c4..c068e6e 100644
--- a/lib/classes/JsonApi/Routes/Events/UserEventsIcal.php
+++ b/lib/classes/JsonApi/Routes/Events/UserEventsIcal.php
@@ -24,14 +24,14 @@ class UserEventsIcal extends NonJsonApiController
throw new RecordNotFoundException();
}
- $writer = new \CalendarWriterICalendar();
- $export = new \CalendarExport($writer);
- $export->exportFromDatabase($observedUser->id, 0, 2114377200, ['CalendarEvent', 'CourseEvent', 'CourseCancelledEvent']);
- if ($GLOBALS['_calendar_error']->getMaxStatus(\ErrorHandler::ERROR_CRITICAL)) {
- throw new InternalServerError();
- }
+ $end = \DateTime::createFromFormat('U', '2114377200');
+ $start = new \DateTime();
+ $ical_export = new \ICalendarExport();
+ $ical = $ical_export->exportCalendarDates($observedUser->id, $start, $end)
+ . $ical_export->exportCourseDates($observedUser->id, $start, $end)
+ . $ical_export->exportCourseExDates($observedUser->id, $start, $end);
+ $content = $ical_export->writeHeader() . $ical . $ical_export->writeFooter();
- $content = implode($export->getExport());
$response->getBody()->write($content);
return $response->withHeader('Content-Type', 'text/calendar')
diff --git a/lib/classes/JsonApi/Routes/Events/UserEventsIndex.php b/lib/classes/JsonApi/Routes/Events/UserEventsIndex.php
index 353382b..192d279 100644
--- a/lib/classes/JsonApi/Routes/Events/UserEventsIndex.php
+++ b/lib/classes/JsonApi/Routes/Events/UserEventsIndex.php
@@ -38,7 +38,14 @@ class UserEventsIndex extends JsonApiController
}
$end = strtotime('+2 weeks', $start);
- $list = \SingleCalendar::getEventList($user->id, $start, $end, $user->id);
+ //See the RecordNotFoundException above: This route only lets user
+ //retrieve the dates of their own calendar. So no permission check
+ //is needed.
+ $start_dt = new \DateTime();
+ $start_dt->setTimestamp($start);
+ $end_dt = new \DateTime();
+ $end_dt->setTimestamp($end);
+ $list = \CalendarDateAssignment::getEvents($start_dt, $end_dt, $user->id);
list($offset, $limit) = $this->getOffsetAndLimit();
return $this->getPaginatedContentResponse(
diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php
index 71aadf7..a0d21a3 100644
--- a/lib/classes/JsonApi/SchemaMap.php
+++ b/lib/classes/JsonApi/SchemaMap.php
@@ -18,15 +18,16 @@ class SchemaMap
\BlubberStatusgruppeThread::class => Schemas\BlubberStatusgruppeThread::class,
\BlubberThread::class => Schemas\BlubberThread::class,
- \CalendarEvent::class => Schemas\CalendarEvent::class,
+ \CalendarDateAssignment::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,
\Course::class => Schemas\Course::class,
\CourseMember::class => Schemas\CourseMember::class,
+ \CourseDate::class => Schemas\CourseEvent::class,
+ \CourseExDate::class => Schemas\CourseEvent::class,
\FeedbackElement::class => Schemas\FeedbackElement::class,
\FeedbackEntry::class => Schemas\FeedbackEntry::class,
\JsonApi\Models\ForumCat::class => Schemas\ForumCategory::class,
diff --git a/lib/classes/JsonApi/Schemas/CalendarEvent.php b/lib/classes/JsonApi/Schemas/CalendarEvent.php
index 7b348d5..fc5a1d8 100644
--- a/lib/classes/JsonApi/Schemas/CalendarEvent.php
+++ b/lib/classes/JsonApi/Schemas/CalendarEvent.php
@@ -18,17 +18,15 @@ class CalendarEvent extends SchemaProvider
public function getAttributes($resource, ContextInterface $context): iterable
{
return [
- 'title' => $resource->title,
- 'description' => $resource->getDescription(),
- 'start' => date('c', $resource->getStart()),
- 'end' => date('c', $resource->getEnd()),
- 'categories' => $resource->toStringCategories(true),
- 'location' => $resource->getLocation(),
-// TODO: 'is-canceled' => $singledate->isHoliday() ?: false,
-
- 'mkdate' => date('c', $resource->mkdate),
- 'chdate' => date('c', $resource->chdate),
- 'recurrence' => $resource->getRecurrence(),
+ 'title' => $resource->calendar_date->title,
+ 'description' => $resource->calendar_date->description,
+ 'start' => date('c', $resource->calendar_date->begin),
+ 'end' => date('c', $resource->calendar_date->end),
+ 'categories' => $resource->calendar_date->getCategoryAsString(),
+ 'location' => $resource->calendar_date->location,
+ 'mkdate' => date('c', $resource->calendar_date->mkdate),
+ 'chdate' => date('c', $resource->calendar_date->chdate),
+ 'recurrence' => $resource->calendar_date->getRepetitionAsString(),
];
}
@@ -39,7 +37,9 @@ class CalendarEvent extends SchemaProvider
{
$relationships = [];
- if ($owner = $resource->getOwner()) {
+ $owner = $resource->user ?? $resource->course;
+
+ if ($owner) {
$link = $this->createLinkToResource($owner);
$relationships = [
self::REL_OWNER => [self::RELATIONSHIP_LINKS => [Link::RELATED => $link], self::RELATIONSHIP_DATA => $owner],
diff --git a/lib/classes/JsonApi/Schemas/Course.php b/lib/classes/JsonApi/Schemas/Course.php
index 09f1175..29a0c37 100644
--- a/lib/classes/JsonApi/Schemas/Course.php
+++ b/lib/classes/JsonApi/Schemas/Course.php
@@ -388,4 +388,29 @@ class Course extends SchemaProvider
return array_merge($relationships, [self::REL_STATUS_GROUPS => $relation]);
}
+
+ /**
+ * @inheritdoc
+ */
+ public function hasResourceMeta($resource): bool
+ {
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getResourceMeta($resource)
+ {
+ $avatar = \CourseAvatar::getAvatar($resource->id);
+
+ return [
+ 'avatar' => [
+ 'small' => $avatar->getURL(\Avatar::SMALL),
+ 'medium' => $avatar->getURL(\Avatar::MEDIUM),
+ 'normal' => $avatar->getURL(\Avatar::NORMAL),
+ ],
+ ];
+ }
+
}
diff --git a/lib/classes/JsonApi/Schemas/CourseEvent.php b/lib/classes/JsonApi/Schemas/CourseEvent.php
index 77f1d31..e90ae0d 100644
--- a/lib/classes/JsonApi/Schemas/CourseEvent.php
+++ b/lib/classes/JsonApi/Schemas/CourseEvent.php
@@ -18,16 +18,16 @@ class CourseEvent extends SchemaProvider
public function getAttributes($resource, ContextInterface $context): iterable
{
return [
- 'title' => $resource->title,
+ 'title' => isset($resource->course) ? $resource->course->getFullName() : '',
'description' => $resource->getDescription(),
- 'start' => date('c', $resource->getStart()),
- 'end' => date('c', $resource->getEnd()),
- 'categories' => array_filter($resource->toStringCategories(true)),
- 'location' => $resource->getLocation(),
-
+ 'start' => date('c', $resource->date),
+ 'end' => date('c', $resource->end_time),
+ 'categories' => '',
+ 'location' => $resource->raum ?? '',
+ 'is-cancelled' => $resource instanceof \CourseExDate,
'mkdate' => date('c', $resource->mkdate),
'chdate' => date('c', $resource->chdate),
- 'recurrence' => $resource->getRecurrence(),
+ 'recurrence' => isset($resource->cycle) ? $resource->cycle->toString() : '',
];
}
diff --git a/lib/classes/JsonApi/Schemas/Semester.php b/lib/classes/JsonApi/Schemas/Semester.php
index 75aef03..f66f90c 100644
--- a/lib/classes/JsonApi/Schemas/Semester.php
+++ b/lib/classes/JsonApi/Schemas/Semester.php
@@ -23,6 +23,7 @@ class Semester extends SchemaProvider
'start-of-lectures' => date('c', $semester->vorles_beginn),
'end-of-lectures' => date('c', $semester->vorles_ende),
'visible' => (bool) $semester->visible,
+ 'is-current' => $semester->isCurrent(),
];
}
diff --git a/lib/classes/Privacy.php b/lib/classes/Privacy.php
index cbeb0a1..670f302 100644
--- a/lib/classes/Privacy.php
+++ b/lib/classes/Privacy.php
@@ -28,7 +28,7 @@ class Privacy
],
'date' => [
'CalendarEvent',
- 'EventData',
+ 'CalendarDate',
'CourseDate',
'CourseExDate',
],
diff --git a/lib/classes/UserManagement.class.php b/lib/classes/UserManagement.class.php
index 1a49aed..7dd38ea 100644
--- a/lib/classes/UserManagement.class.php
+++ b/lib/classes/UserManagement.class.php
@@ -1169,16 +1169,6 @@ class UserManagement
if ($count) {
$msg .= 'info§' . sprintf(_('%s Einträge aus den Terminen gelöscht.'), $count) . '§';
}
- // delete membership in group calendars
- if (Config::get()->CALENDAR_GROUP_ENABLE) {
- $count = CalendarUser::deleteBySQL(
- 'owner_id = :user_id OR user_id = :user_id',
- [':user_id' => $user_id]
- );
- if ($count) {
- $msg .= 'info§' . sprintf(_('%s Verknüpfungen mit Gruppenterminkalendern gelöscht.'), $count) . '§';
- }
- }
}
// delete all messages send or received by this user
diff --git a/lib/classes/calendar/Calendar.php b/lib/classes/calendar/Calendar.php
deleted file mode 100644
index abb2ec7..0000000
--- a/lib/classes/calendar/Calendar.php
+++ /dev/null
@@ -1,187 +0,0 @@
-<?php
-/**
- * Calendar.class.php - Holds some additional functions and constants
- * related to 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>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @since 3.2
- */
-class Calendar
-{
- /**
- * The (positive) end of unix epche
- */
- const CALENDAR_END = 0x7FFFFFFF;
-
- /**
- * The user is the owner of the calendar.
- */
- const PERMISSION_OWN = 16;
-
- /**
- * The user has administrative access to the calendar.
- * Means, he is not the owner but have the same rights.
- * Not in use at the moment.
- */
- const PERMISSION_ADMIN = 8;
-
- /**
- * The user can add new events and edit existing events in the calendar.
- * If the owner of the calendar has created an confidential event, the only
- * information the user get is the start and end time. The event is shown as
- * busy time in the views for him.
- * If the user adds a confidential event, only he and the owner has full
- * access to it. The event is shown as busy time to all other users.
- */
- const PERMISSION_WRITABLE = 4;
-
- /**
- * The user can read all information of all events, except events marked as
- * confidential. These events are shown as busy times in the views.
- * The user can not add new events nor edit existing events.
- */
- const PERMISSION_READABLE = 2;
-
- /**
- * The user is not allowed to get any information about the calendar.
- * The user has no access to the calendar but he see public events on the
- * profile of the owner.
- */
- const PERMISSION_FORBIDDEN = 1;
-
- /**
- * The calendar is related to one user. He is the owner of the calendar.
- */
- const RANGE_USER = 1;
-
- /**
- * The calendar is related to a group of users
- * ("contact group" or Statusgruppe).
- * Not used at the moment.
- * The implemeted group functionality shows all personal calendars of the
- * members of a contact group. It is not a shared calendar where all members
- * have access to.
- */
- const RANGE_GROUP = 2;
-
- /**
- * The calendar is a module of a course or studygroup. All members with
- * status author, tutor or dozent have write access (PERMISSION_WRITABLE).
- * Users with local status user has only read access (PERMISSION_READABLE).
- */
- const RANGE_SEM = 3;
-
- /**
- * The calendar is a module of an institute or faculty. All members with
- * status author, tutor or dozent have write access (PERMISSION_WRITABLE).
- * Users with local status user has only read access (PERMISSION_READABLE).
- */
- const RANGE_INST = 4;
-
- /**
- * Retrieves all contact groups (statusgruppen) owned by the given user
- * where at least one member has granted access to his calender for the user.
- *
- * @param string $user_id User id of the owner.
- * @return type
- */
- public static function getGroups($user_id)
- {
- $groups = [];
- $calendar_owners = CalendarUser::getOwners($user_id)->pluck('owner_id');
- $sg_groups = SimpleORMapCollection::createFromArray(
- Statusgruppen::findByRange_id($user_id))
- ->orderBy('position')
- ->pluck('statusgruppe_id');
- if (sizeof($calendar_owners)) {
- $sg_users = StatusgruppeUser::findBySQL(
- 'statusgruppe_id IN(?) AND user_id IN(?)',
- [$sg_groups, $calendar_owners]);
- foreach ($sg_users as $sg_user) {
- $groups[$sg_user->group->id] = $sg_user->group;
- }
- }
- return $groups;
- }
-
- public static function GetInstituteActivatedCalendar($user_id)
- {
-
- $ret = [];
- Institute::findAndMapBySQL(function($i) use (&$ret) {
- if ($i->isToolActive('CoreCalendar')) {
- $ret[$i->id] = $i->name;
- }
- },
- "JOIN user_inst USING(Institut_id)
- WHERE user_id = ? AND inst_perms IN ('admin','dozent','tutor','autor')
- ORDER BY Name ASC",
- [$user_id]
- );
- return $ret;
- }
-
- /**
- *
- * @param string $user_id
- * @return array
- */
- public static function GetCoursesActivatedCalendar($user_id)
- {
- $courses_user = SimpleCollection::createFromArray(
- CourseMember::findByUser($user_id));
- $courses = $courses_user->filter(function ($c) {
- if ($c->course->isToolActive('CoreCalendar')) {
- return $c;
- }
- });
- return $courses->pluck('course');
- }
-
- public static function GetLecturers()
- {
- $stmt = DBManager::get()->prepare("SELECT aum.username, "
- . "CONCAT(aum.Nachname,', ',aum.vorname) as fullname, "
- . "aum.user_id FROM auth_user_md5 aum WHERE perms = 'dozent' "
- . "ORDER BY fullname");
- $stmt->execute();
- $lecturers = [];
- foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
- if ($row['user_id'] != $GLOBALS['user']->id) {
- $lecturers[] = ['name' => $row['fullname'],
- 'username' => $row['username'], 'id' => $row['user_id']];
- }
- }
- return $lecturers;
- }
-
- /**
- * Returns an array of default user settings for the calendar or a specific
- * value if the index is given.
- *
- * @param string $index Index of setting to get.
- * @return string|array Array of settings or one setting
- */
- public static function getDefaultUserSettings($index = null)
- {
- $default = [
- 'view' => 'week',
- 'start' => '9',
- 'end' => '20',
- 'step_day' => '900',
- 'step_week' => '1800',
- 'type_week' => 'LONG',
- 'step_week_group' => '3600',
- 'step_day_group' => '3600',
- 'show_declined' => '0'
- ];
- return (is_null($index) ? $default : $default[$index]);
- }
-}
diff --git a/lib/classes/calendar/CalendarInstscheduleModel.php b/lib/classes/calendar/CalendarInstscheduleModel.php
deleted file mode 100644
index e99da9d..0000000
--- a/lib/classes/calendar/CalendarInstscheduleModel.php
+++ /dev/null
@@ -1,148 +0,0 @@
-<?php
-# Lifter010: TODO
-
-/*
- * This class is the model for the institute-calendar for seminars
- *
- * 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 Till Glöggler <tgloeggl@uos.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- */
-
-require_once __DIR__ . '/default_color_definitions.php';
-
-/**
- * Pseudo-namespace containing helper methods for the calendar of institutes.
- *
- * @since 2.0
- */
-class CalendarInstscheduleModel
-{
- /**
- * Returns a schedule entry for a course
- *
- * @param string $seminar_id the ID of the course
- * @param string $user_id the ID of the user
- * @param string $cycle_id optional; if given, specifies the ID of the entry
- * @return array an array containing the properties of the entry
- */
- static function getSeminarEntry($seminar_id, $user_id, $cycle_id = false)
- {
- $ret = [];
-
- $sem = new Seminar($seminar_id);
- foreach ($sem->getCycles() as $cycle) {
- if (!$cycle_id || $cycle->getMetaDateID() == $cycle_id) {
- $entry = [];
-
- $entry['id'] = $seminar_id;
- $entry['cycle_id'] = $cycle->getMetaDateId();
- $entry['start_formatted'] = sprintf("%02d", $cycle->getStartStunde()) .':'
- . sprintf("%02d", $cycle->getStartMinute());
- $entry['end_formatted'] = sprintf("%02d", $cycle->getEndStunde()) .':'
- . sprintf("%02d", $cycle->getEndMinute());
-
- $entry['start'] = ((int)$cycle->getStartStunde() * 100) + ($cycle->getStartMinute());
- $entry['end'] = ((int)$cycle->getEndStunde() * 100) + ($cycle->getEndMinute());
- $entry['day'] = $cycle->getDay();
- $entry['content'] = $sem->getNumber() . ' ' . $sem->getName();
- $entry['url'] = URLHelper::getLink('dispatch.php/calendar/instschedule/entry/' . $seminar_id
- . '/' . $cycle->getMetaDateId());
- $entry['onClick'] = "function(id) { STUDIP.Instschedule.showSeminarDetails('$seminar_id', '"
- . $cycle->getMetaDateId() ."'); }";
-
- $entry['title'] = '';
- $ret[] = $entry;
- }
- }
-
- return $ret;
- }
-
-
- /**
- * Returns an array of CalendarColumn's, containing the seminar-entries
- * for the passed user (in the passed semester belonging to the passed institute)
- * The start- and end-hour are used to constrain the returned
- * entries to the passed time-period. The passed days constrain the entries
- * to these.
- *
- * @param string $user_id the ID of the user
- * @param array $semester an array containing the "beginn" of the semester
- * @param int $start_hour the start hour
- * @param int $end_hour the end hour
- * @param string $institute_id the ID of the institute
- * @param array $days the days to be displayed
- *
- * @return array an array containing the entries
- */
- static function getInstituteEntries($user_id, $semester, $start_hour, $end_hour, $institute_id, $days)
- {
- // fetch seminar-entries, show invisible seminars if the user has enough perms
- $visibility_perms = $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM);
-
- $inst_ids = [];
- $institut = new Institute($institute_id);
-
- if (!$institut->isFaculty() || $GLOBALS['user']->cfg->MY_INSTITUTES_INCLUDE_CHILDREN) {
- // If the institute is not a faculty or the child insts are included,
- // pick the institute IDs of the faculty/institute and of all sub-institutes.
- $inst_ids[] = $institute_id;
- if ($institut->isFaculty()) {
- foreach ($institut->sub_institutes->pluck("Institut_id") as $institut_id) {
- $inst_ids[] = $institut_id;
- }
- }
- } else {
- // If the institute is a faculty and the child insts are not included,
- // pick only the institute id of the faculty:
- $inst_ids[] = $institute_id;
- }
-
- $stmt = DBManager::get()->prepare("SELECT * FROM seminare
- LEFT JOIN seminar_inst ON (seminare.Seminar_id = seminar_inst.seminar_id)
- LEFT JOIN semester_courses ON (semester_courses.course_id = seminare.Seminar_id)
- WHERE seminar_inst.institut_id IN (:institute)
- AND (start_time <= :begin AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id))
- "
- . (!$visibility_perms ? " AND visible='1'" : ""));
-
- $stmt->bindValue(':begin', $semester['beginn']);
- $stmt->bindValue(':semester_id', $semester['id']);
- $stmt->bindValue(':institute', $inst_ids, StudipPDO::PARAM_ARRAY);
- $stmt->execute();
-
- while ($entry = $stmt->fetch()) {
- $seminars[$entry['Seminar_id']] = $entry;
- }
-
- if (is_array($seminars)) foreach ($seminars as $data) {
- $entries = self::getSeminarEntry($data['Seminar_id'], $user_id);
-
- foreach ($entries as $entry) {
- unset($entry['url']);
- $entry['onClick'] = 'function(id) { STUDIP.Instschedule.showInstituteDetails(id); }';
- $entry['visible'] = 1;
-
- if (($entry['start'] >= $start_hour * 100 && $entry['start'] <= $end_hour * 100
- || $entry['end'] >= $start_hour * 100 && $entry['end'] <= $end_hour * 100)) {
-
- $entry['color'] = DEFAULT_COLOR_SEM;
-
- $day_number = ($entry['day'] + 6) % 7;
- if (!isset($ret[$day_number])) {
- $ret[$day_number] = CalendarColumn::create($day_number);
- }
- $ret[$day_number]->addEntry($entry);
- }
- }
- }
-
- return CalendarScheduleModel::addDayChooser($ret, $days, 'instschedule');
- }
-}
diff --git a/lib/classes/calendar/CalendarScheduleModel.php b/lib/classes/calendar/CalendarScheduleModel.php
index ff9bce8..468b7e2 100644
--- a/lib/classes/calendar/CalendarScheduleModel.php
+++ b/lib/classes/calendar/CalendarScheduleModel.php
@@ -20,6 +20,8 @@ require_once __DIR__ . '/default_color_definitions.php';
* Pseudo-namespace containing helper methods for the schedule.
*
* @since 2.0
+ *
+ * @deprecated since Stud.IP 5.5
*/
class CalendarScheduleModel
{
diff --git a/lib/calendar/EventData.class.php b/lib/classes/calendar/EventData.class.php
index 9ab4377..82afe6f 100644
--- a/lib/calendar/EventData.class.php
+++ b/lib/classes/calendar/EventData.class.php
@@ -22,6 +22,8 @@ class EventData
public $view_urls;
public $api_urls;
public $icon;
+ public $border_colour;
+ public $all_day;
public function __construct(
\DateTime $begin,
@@ -39,7 +41,9 @@ class EventData
string $range_id,
Array $view_urls = [],
Array $api_urls = [],
- string $icon = ''
+ string $icon = '',
+ string $border_colour = '',
+ bool $all_day = false
)
{
$this->begin = $begin;
@@ -58,6 +62,8 @@ class EventData
$this->view_urls = $view_urls;
$this->api_urls = $api_urls;
$this->icon = $icon;
+ $this->border_colour = $border_colour ?: $background_colour;
+ $this->all_day = $all_day;
}
@@ -71,10 +77,12 @@ class EventData
'resourceId' => $this->range_id,
'start' => $this->begin->format('Y-m-d\TH:i:s'),
'end' => $this->end->format('Y-m-d\TH:i:s'),
+ 'allDay' => $this->all_day,
'title' => $this->title,
'classNames' => $this->event_classes,
'textColor' => $this->text_colour,
'color' => $this->background_colour,
+ 'borderColor' => $this->border_colour,
'editable' => $this->editable,
'studip_weekday_begin' => $this->begin->format('N'),
'studip_weekday_end' => $this->end->format('N'),
diff --git a/lib/calendar/EventSource.interface.php b/lib/classes/calendar/EventSource.interface.php
index 48506d7..48506d7 100644
--- a/lib/calendar/EventSource.interface.php
+++ b/lib/classes/calendar/EventSource.interface.php
diff --git a/lib/classes/calendar/Helper.php b/lib/classes/calendar/Helper.php
new file mode 100644
index 0000000..10294f4
--- /dev/null
+++ b/lib/classes/calendar/Helper.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Studip\Calendar;
+
+class Helper
+{
+ /**
+ * Retrieves the time slot duration in the calendar for a specified calendar type
+ * and either the current user or a specific user.
+ *
+ * @param string $calendar_type The calendar type for which to retrieve the slot duration.
+ * Valid values: 'week', 'day', 'week_group' (week group calendar), 'week_day' (day group calendar).
+ * Defaults to 'week'.
+ * @param string $user_id The user for which to retrieve the slot duration. Defaults to an
+ * empty string which then in turn means the current users slot duration is retrieved.
+ *
+ * @return string The slot duration as a time string in the form HH:MM:SS.
+ */
+ public static function getCalendarSlotDuration(string $calendar_type = 'week', string $user_id = '') : string
+ {
+ $default_slot_duration = '00:30:00';
+
+ $user_config = new \UserConfig($user_id ?: $GLOBALS['user']->id);
+ $calendar_settings = $user_config->CALENDAR_SETTINGS;
+
+ if (
+ $calendar_type === 'week'
+ && !empty($calendar_settings['step_week'])
+ ) {
+ $step_week = (int) $calendar_settings['step_week'];
+ $hours = floor($step_week / 3600);
+ $minutes = round(($step_week - $hours * 3600) / 60);
+ return sprintf('%1$02u:%2$02u:00', $hours, $minutes);
+ } elseif (
+ $calendar_type === 'day'
+ && !empty($calendar_settings['step_day'])
+ ) {
+ $step_day = (int) $calendar_settings['step_day'];
+ $hours = floor($step_day / 3600);
+ $minutes = round(($step_day - $hours * 3600) / 60);
+ return sprintf('%1$02u:%2$02u:00', $hours, $minutes);
+ } elseif (
+ $calendar_type === 'week_group'
+ && !empty($calendar_settings['step_week_group'])
+ ) {
+ $step_week = (int) $calendar_settings['step_week_group'];
+ $hours = floor($step_week / 3600);
+ $minutes = round(($step_week - $hours * 3600) / 60);
+ return sprintf('%1$02u:%2$02u:00', $hours, $minutes);
+ } elseif (
+ $calendar_type === 'day_group'
+ && !empty($calendar_settings['step_day_group'])
+ ) {
+ $step_day = (int) $calendar_settings['step_day_group'];
+ $hours = floor($step_day / 3600);
+ $minutes = round(($step_day - $hours * 3600) / 60);
+ return sprintf('%1$02u:%2$02u:00', $hours, $minutes);
+ }
+
+ // An unknown slot type or no appropriate match before:
+ // Return the default duration.
+ return $default_slot_duration;
+ }
+
+
+ /**
+ * Retrieves the default calendar date by various methods.
+ *
+ * @return \DateTime The default date for the calendar.
+ * This defaults to the current date if no other date
+ * can be retrieved.
+ */
+ public static function getDefaultCalendarDate() : \DateTime
+ {
+ $default_date = new \DateTime();
+ if (\Request::submitted('date')) {
+ $date = \Request::getDateTime('date', 'Y-m-d');
+ if ($date instanceof \DateTime) {
+ $default_date = $date;
+ //Update the session value:
+ $_SESSION['calendar_date'] = $default_date->format('Y-m-d');
+ }
+ } elseif (\Request::submitted('semester_id')) {
+ //A semester-ID is set, but no specific date that would override it.
+ //Use the first lecture week of the semester as default date.
+ $semester_id = \Request::option('semester_id');
+ $semester = \Semester::find($semester_id);
+ if ($semester) {
+ $default_date->setTimestamp($semester->vorles_beginn);
+ //Update the session value:
+ $_SESSION['calendar_date'] = $default_date->format('Y-m-d');
+ }
+ } elseif (!empty($_SESSION['calendar_date'])) {
+ $date = \DateTime::createFromFormat(
+ 'Y-m-d',
+ $_SESSION['calendar_date'],
+ $default_date->getTimezone()
+ );
+ if ($date instanceof \DateTime) {
+ $default_date = $date;
+ }
+ }
+ $default_date->setTime(0,0,0);
+
+ return $default_date;
+ }
+}
diff --git a/lib/classes/calendar/ICalendarExport.class.php b/lib/classes/calendar/ICalendarExport.class.php
new file mode 100644
index 0000000..fba0974
--- /dev/null
+++ b/lib/classes/calendar/ICalendarExport.class.php
@@ -0,0 +1,638 @@
+<?php
+/**
+ * ICalendarExport.class.php
+ *
+ * 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 5.5
+ */
+
+
+class ICalendarExport
+{
+ /**
+ * Line break used in iCalendar
+ */
+ const NEWLINE = "\r\n";
+
+ /**
+ * Default start of the week
+ */
+ const WEEKSTART = 'MO';
+
+ /**
+ * Holds the time (as unix timestamp) used for
+ * the timestamp in every exported iCalendar object.
+ *
+ * @var int $time
+ */
+ private $time = 0;
+
+ public function __construct()
+ {
+ $this->default_filename_suffix = "ics";
+ $this->format = "iCalendar";
+ }
+
+ public function exportCalendarDates(string $range_id, DateTimeInterface $start, DateTimeInterface $end): string
+ {
+ if ($this->time === 0) {
+ $this->time = time();
+ }
+ $dates = CalendarDate::findBySQL(
+ "LEFT JOIN `calendar_date_assignments`
+ ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id`
+ WHERE
+ `calendar_date_assignments`.`range_id` = :range_id
+ AND (
+ (`calendar_dates`.`begin` <= :end
+ AND `calendar_dates`.`end` >= :begin)
+ OR (`calendar_dates`.`repetition_type` != 'SINGLE'
+ AND (`calendar_dates`.`repetition_end` >= :end
+ OR `calendar_dates`.`repetition_end` = 0)
+ AND `calendar_dates`.`begin` < :end))",
+ [
+ ':range_id' => $range_id,
+ ':begin' => $start->getTimestamp(),
+ ':end' => $end->getTimestamp(),
+ ]
+ );
+ $ical = '';
+ foreach ($dates as $date) {
+ $ical .= $this->writeICalEvent($this->prepareCalendarDate($date));
+ }
+ return $ical;
+ }
+
+ public function exportCourseDates(string $user_id, DateTimeInterface $start, DateTimeInterface $end)
+ {
+ if ($this->time === 0) {
+ $this->time = time();
+ }
+ $dates = CourseDate::findBySql(
+ "LEFT JOIN `seminar_user`
+ ON `termine`.`range_id` = `seminar_user`.`Seminar_id`
+ WHERE
+ `seminar_user`.`user_id` = :user_id
+ AND `seminar_user`.`bind_calendar` = 1
+ AND (`termine`.`date` <= :end
+ AND `termine`.`end_time` >= :begin)",
+ [
+ ':user_id' => $user_id,
+ ':begin' => $start->getTimestamp(),
+ ':end' => $end->getTimestamp(),
+ ]
+ );
+ $ical = '';
+ foreach ($dates as $date) {
+ $ical .= $this->writeICalEvent($this->prepareCourseDate($date));
+ }
+ return $ical;
+ }
+
+ public function exportCourseExDates(string $user_id, DateTimeInterface $start, DateTimeInterface $end)
+ {
+ if ($this->time === 0) {
+ $this->time = time();
+ }
+ $dates = CourseExDate::findBySql(
+ "LEFT JOIN `seminar_user`
+ ON `ex_termine`.`range_id` = `seminar_user`.`Seminar_id`
+ WHERE
+ `seminar_user`.`user_id` = :user_id
+ AND `seminar_user`.`bind_calendar` = 1
+ AND (`ex_termine`.`date` <= :end
+ AND `ex_termine`.`end_time` >= :begin)",
+ [
+ ':user_id' => $user_id,
+ ':begin' => $start->getTimestamp(),
+ ':end' => $end->getTimestamp(),
+ ]
+ );
+ $ical = '';
+ foreach ($dates as $date) {
+ $ical .= $this->writeICalEvent($this->prepareCourseDate($date));
+ }
+ return $ical;
+ }
+
+ /**
+ * @param CalendarDate $date
+ * @return array
+ */
+ public function prepareCalendarDate(CalendarDate $date): array
+ {
+ $properties =
+ [
+ 'SUMMARY' => $date->title,
+ 'DESCRIPTION' => $date->description,
+ 'LOCATION' => $date->location,
+ 'CATEGORIES' => $date->getCategoryAsString(),
+ 'LAST-MODIFIED' => $date->chdate,
+ 'CREATED' => $date->mkdate,
+ 'DTSTAMP' => $this->time,
+ 'DTSTART' => $date->begin,
+ 'DTEND' => $date->end,
+ 'EXDATE' => implode(',', $date->exceptions->pluck('date')),
+ 'PRIORITY' => 5,
+ 'RRULE' => [
+ 'type' => $date->repetition_type,
+ 'offset' => $date->offset,
+ 'interval' => $date->interval,
+ 'days' => $date->days,
+ 'count' => $date->number_of_dates,
+ 'expire' => $date->repetition_end,
+ 'month' => $date->month
+ ]
+ ];
+ return $properties;
+ }
+
+ public function prepareCourseDate(CourseDate $date): array
+ {
+ $properties =
+ [
+ 'SUMMARY' => $date->course->getFullname(),
+ 'DESCRIPTION' => '',
+ 'LOCATION' => $date->getRoomName(),
+ 'CATEGORIES' => $GLOBALS['TERMIN_TYP'][$date->date_typ]['name'],
+ 'LAST-MODIFIED' => $date->chdate,
+ 'CREATED' => $date->mkdate,
+ 'DTSTAMP' => $this->time,
+ 'DTSTART' => $date->date,
+ 'DTEND' => $date->end_time,
+ 'PRIORITY' => ''
+ ];
+ return $properties;
+ }
+
+ /**
+ * Returns an iCalendar header with a rudimentary time zone definition.
+ *
+ * @return string The iCalendar header.
+ */
+ public function writeHeader()
+ {
+ // Default values
+ $header = "BEGIN:VCALENDAR" . self::NEWLINE;
+ $header .= "VERSION:2.0" . self::NEWLINE;
+ if (isset($this->client_identifier)) {
+ $header .= "PRODID:" . $this->client_identifier . self::NEWLINE;
+ } else {
+ $server_name = $_SERVER['SERVER_NAME'] ?? 'unknown';
+
+ $header .= "PRODID:-//Stud.IP@{$server_name}//Stud.IP_iCalendar Library";
+ $header .= " //EN" . self::NEWLINE;
+ }
+ $header .= "METHOD:PUBLISH" . self::NEWLINE;
+
+ // time zone definition CET/CEST
+ $header .= 'CALSCALE:GREGORIAN' . self::NEWLINE
+ . 'BEGIN:VTIMEZONE' . self::NEWLINE
+ . 'TZID:Europe/Berlin' . self::NEWLINE
+ . 'BEGIN:DAYLIGHT' . self::NEWLINE
+ . 'TZOFFSETFROM:+0100' . self::NEWLINE
+ . 'TZOFFSETTO:+0200' . self::NEWLINE
+ . 'TZNAME:CEST' . self::NEWLINE
+ . 'DTSTART:19700329T020000' . self::NEWLINE
+ . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3' . self::NEWLINE
+ . 'END:DAYLIGHT' . self::NEWLINE
+ . 'BEGIN:STANDARD' . self::NEWLINE
+ . 'TZOFFSETFROM:+0200' . self::NEWLINE
+ . 'TZOFFSETTO:+0100' . self::NEWLINE
+ . 'TZNAME:CET' . self::NEWLINE
+ . 'DTSTART:19701025T030000' . self::NEWLINE
+ . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10' . self::NEWLINE
+ . 'END:STANDARD' . self::NEWLINE
+ . 'END:VTIMEZONE' .self::NEWLINE;
+
+ return $header;
+ }
+
+ /**
+ * Returns the footer.
+ *
+ * @return string
+ */
+ public function writeFooter()
+ {
+ return "END:VCALENDAR" . self::NEWLINE;
+ }
+
+ /**
+ * Export prepared calendar data as iCalendar.
+ *
+ * @param array $properties The event to export.
+ * @return string iCalendar formatted data
+ */
+ public function writeICalEvent(array $properties): string
+ {
+ $result = "BEGIN:VEVENT" . self::NEWLINE;
+
+ foreach ($properties as $name => $value) {
+ $params = [];
+ $params_str = '';
+ if ($value === '' || is_null($value)) {
+ continue;
+ }
+ switch ($name) {
+ // not supported event properties
+ case 'SEMNAME':
+ continue 2;
+
+ // Text fields
+ case 'SUMMARY':
+ $value = $this->quoteText($value);
+ break;
+ case 'DESCRIPTION':
+ $value = $this->quoteText($value);
+ break;
+ case 'LOCATION':
+ $value = $this->quoteText($value);
+ break;
+ case 'CATEGORIES':
+ $value = $this->quoteText($value);
+ break;
+
+ // Date fields
+ case 'LAST-MODIFIED':
+ case 'CREATED':
+ case 'COMPLETED':
+ $value = $this->_exportDateTime($value, true);
+ break;
+
+ case 'DTSTAMP':
+ $value = $this->_exportDateTime(time(), true);
+ break;
+
+ case 'DTSTART':
+ $exdate_time = $value;
+ case 'DTEND':
+ case 'DUE':
+ case 'RECURRENCE-ID':
+ if (array_key_exists('VALUE', $params)) {
+ if ($params['VALUE'] == 'DATE') {
+ $value = $this->_exportDate($value);
+ } else {
+ $value = $this->_exportDateTime($value);
+ $params_str = ';TZID=Europe/Berlin';
+ }
+ } else {
+ $value = $this->_exportDateTime($value);
+ $params_str = ';TZID=Europe/Berlin';
+ }
+ break;
+
+ case 'EXDATE':
+ if (array_key_exists('VALUE', $params)) {
+ $value = $this->exportExDate($value);
+ } else {
+ $value = $this->exportExDateTime($value);
+ }
+ $params_str = ';TZID=Europe/Berlin';
+ break;
+
+ // Integer fields
+ case 'PERCENT-COMPLETE':
+ case 'REPEAT':
+ case 'SEQUENCE':
+ $value = "$value";
+ break;
+
+ case 'PRIORITY':
+ switch ($value) {
+ case 1:
+ $value = '1';
+ break;
+ case 2:
+ $value = '5';
+ break;
+ case 3:
+ $value = '9';
+ break;
+ default:
+ $value = '0';
+ }
+ break;
+
+ // Geo fields
+ case 'GEO':
+ $value = $value['latitude'] . ',' . $value['longitude'];
+ break;
+
+ // Recursion fields
+ case 'EXRULE':
+ case 'RRULE':
+ if ($value['type'] !== 'SINGLE') {
+ $value = $this->_exportRecurrence($value);
+ }
+ break;
+
+ case "UID":
+ $value = "$value";
+ }
+ if ($name && !is_array($value)) {
+ $attr_string = $name . $params_str . ':' . $value;
+ $result .= $this->foldLine($attr_string) . self::NEWLINE;
+ }
+ }
+ if (isset($properties['GROUP_EVENT'])) {
+ $result .= $this->exportGroupEventProperties($properties['GROUP_EVENT']);
+ }
+ $result .= "END:VEVENT" . self::NEWLINE;
+
+ return $result;
+ }
+
+ /**
+ * Quotes some characters accordingly to iCalendar format.
+ *
+ * @param string $text The text to quote.
+ * @return string The quoted text.
+ */
+ public function quoteText(string $text): string
+ {
+ $match = ['\\', '\n', ';', ','];
+ $replace = ['\\\\', '\\n', '\;', '\,'];
+ return str_replace($match, $replace, $text);
+ }
+
+ /**
+ * Export a DateTime field
+ *
+ * @param int $value Unix timestamp
+ * @return String Date and time (UTC) iCalendar formatted
+ */
+ public function _exportDateTime($value, $utc = false)
+ {
+ $date_time = new DateTime();
+ $date_time->setTimestamp($value);
+ //transform local time in UTC
+ if ($utc) {
+ $tz_utc = new DateTimeZone('UTC');
+ $date_time->setTimezone($tz_utc);
+ return $date_time->format('Ymd\THis\Z');
+ }
+ return $date_time->format('Ymd\THis');
+ }
+
+ /**
+ * Export a Time field
+ *
+ * @param int $value Unix timestamp
+ * @return String Time (UTC) iCalendar formatted
+ */
+ public function _exportTime($value, $utc = false)
+ {
+ $time = date("His", $value);
+ if ($utc) {
+ $time .= 'Z';
+ }
+
+ return $time;
+ }
+
+ /**
+ * Export a Date field
+ */
+ public function _exportDate($value)
+ {
+ return date("Ymd", $value);
+ }
+
+ /**
+ * Export a recurrence rule
+ */
+ public function _exportRecurrence($value)
+ {
+ $rrule = [];
+ // the last day of week in a MONTHLY or YEARLY recurrence in the
+ // Stud.IP calendar is 5, in iCalendar it is -1
+ if ($value['offset'] == '5') {
+ $value['offset'] = '-1';
+ }
+
+ if ($value['count']) {
+ unset($value['expire']);
+ }
+
+ foreach ($value as $r_param => $r_value) {
+ if ($r_value) {
+ switch ($r_param) {
+ case 'type':
+ $rrule[] = 'FREQ=' . $r_value;
+ break;
+ case 'expire':
+ if ($r_value < CalendarDate::NEVER_ENDING)
+ $rrule[] = 'UNTIL=' . $this->_exportDateTime($r_value, true);
+ break;
+ case 'interval':
+ $rrule[] = 'INTERVAL=' . $r_value;
+ break;
+ case 'days':
+ switch ($value['type']) {
+ case 'WEEKLY':
+ $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value);
+ break;
+ // Some CUAs (e.g. Outlook) don't understand the nWDAY syntax
+ // (where n is the nth ocurrence of the day in a given period of
+ // time and WDAY is the day of week) the RRULE uses the BYSETPOS
+ // rule.
+ case 'MONTHLY':
+ case 'YEARLY':
+ $rrule[] = 'BYDAY=' . $value['offset'] . $this->_exportWdays($r_value);
+ $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value);
+ if ($value['offset']) {
+ $rrule[] = 'BYSETPOS=' . $value['offset'];
+ }
+ break;
+ }
+ break;
+ case 'day':
+ $rrule[] = 'BYMONTHDAY=' . $r_value;
+ break;
+ case 'month':
+ $rrule[] = 'BYMONTH=' . $r_value;
+ break;
+ case 'count':
+ $rrule[] = 'COUNT=' . $r_value;
+ break;
+ }
+ }
+ }
+
+ if ($value['type'] === 'WEEKLY' && self::WEEKSTART != 'MO') {
+ $rrule[] = 'WKST=' . self::WEEKSTART;
+ }
+
+ return implode(';', $rrule);
+ }
+
+ /**
+ * Return the days from CalendarDate::days as attribute of a event recurrence.
+ *
+ * @param string $value
+ * @return string
+ */
+ public function _exportWdays(string $value): string
+ {
+ $wdays_map = ['1' => 'MO', '2' => 'TU', '3' => 'WE', '4' => 'TH', '5' => 'FR',
+ '6' => 'SA', '7' => 'SU'];
+ $wdays = [];
+ preg_match_all('/(\d)/', $value, $matches);
+ foreach ($matches[1] as $match) {
+ $wdays[] = $wdays_map[$match];
+ }
+ return implode(',', $wdays);
+ }
+
+ /**
+ * Formats dates of exception.
+ *
+ * @param string $value Unix timestamps as csv list.
+ * @return string The formatted Exceptions.
+ */
+ public function exportExDate(string $value): string
+ {
+ $exdates = [];
+ $date_times = explode(',', $value);
+ foreach ($date_times as $date_time) {
+ $exdates[] = $this->_exportDate($date_time);
+ }
+ return implode(',', $exdates);
+ }
+
+ /**
+ * Formats date times of exception.
+ *
+ * @param string $value Unix timestamps as csv list.
+ * @return string The formatted Exceptions.
+ */
+ public function exportExDateTime(string $value): string
+ {
+ $ex_dates = [];
+ $ex_date_times = explode(',', $value);
+ foreach ($ex_date_times as $ex_date_time) {
+ $date_time = new DateTime();
+ $date_time->setTimestamp($ex_date_time);
+ $ex_dates[] = $date_time->format('Ymd\THis');
+ }
+ return implode(',', $ex_dates);
+ }
+
+ /**
+ * Returns iCalendar group event properties if the date has mor than one attendee.
+ *
+ * @param CalendarDate $date The date object to extract the group data from.
+ * @return string The formatted group event properties.
+ */
+ private function exportGroupEventProperties(CalendarDate $date): string
+ {
+ if (!count($date->calendars)) {
+ return '';
+ }
+ $organizer = $date->author;
+ if ($organizer) {
+ $properties = $this->foldLine('ORGANIZER;CN="'
+ . $organizer->getFullName()
+ . '":mailto:' . $organizer->Email)
+ . self::NEWLINE;
+ } else {
+ $properties = $this->foldLine('ORGANIZER;CN="'
+ . _('unbekannt')
+ . '":mailto:' . $GLOBALS['user']->email)
+ . self::NEWLINE;
+ }
+ foreach ($date->calendars as $calendar) {
+ if ($date->author_id === $calendar->range_id) {
+ if ($calendar->user) {
+ $properties .= $this->foldLine('ATTENDEE;'
+ . 'ROLE=REQ-PARTICIPANT;'
+ . 'CN="' . $calendar->user->getFullName()
+ . '":mailto:' . $calendar->user->Email)
+ . self::NEWLINE;
+ } else {
+ $properties = '';
+ }
+ } else {
+ if ($calendar->user) {
+ switch ($calendar->participation) {
+ case 'ACCEPTED' :
+ $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT'
+ . ';PARTSTAT=ACCEPTED';
+ break;
+ case 'ACKNOWLEDGED' :
+ $attendee = 'ATTENDEE;ROLE=NON-PARTICIPANT'
+ . ';PARTSTAT=ACCEPTED'
+ . ';DELEGATED-TO="mailto:'
+ . $this->getFacultyEmail($organizer->id)
+ . '"';
+ break;
+ case 'DECLINED' :
+ $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT'
+ . ';PARTSTAT=DECLINED';
+ break;
+ default :
+ $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT';
+ $attendee .= ';PARTSTAT=TENTATIVE';
+ $attendee .= ';RSVP=TRUE';
+
+ }
+ $attendee .= ';CN="' . $calendar->user->getFullName()
+ . '":mailto:' . $calendar->user->Email;
+ $properties .= $this->foldLine($attendee) . self::NEWLINE;
+ }
+ }
+ }
+ return $properties;
+ }
+
+ /**
+ * @param string $user_id
+ * @return string
+ */
+ private function getFacultyEmail(string $user_id): string
+ {
+ $stmt = DBManager::get()->prepare('
+ SELECT `email`
+ FROM `Institute`
+ LEFT JOIN `user_inst` USING(`institut_id`)
+ WHERE `Institute`.`Institut_id` = `fakultaets_id`
+ AND `user_id` = ?');
+ $stmt->execute([$user_id]);
+ return $stmt->fetchColumn();
+ }
+
+ /**
+ * Returns the folded version of a text line.
+ *
+ * @param string $line
+ * @return string
+ */
+ private function foldLine(string $line): string
+ {
+ $line = preg_replace('/(\r\n|\n|\r)/', '\n', $line);
+ if (mb_strlen($line) > 75) {
+ $foldedline = '';
+ while ($line !== '') {
+ $maxLine = mb_substr($line, 0, 75);
+ $cutPoint = max(60, max(mb_strrpos($maxLine, ';'), mb_strrpos($maxLine, ':')) + 1);
+
+ $foldedline .= ( empty($foldedline)) ?
+ mb_substr($line, 0, $cutPoint) :
+ self::NEWLINE . ' ' . mb_substr($line, 0, $cutPoint);
+
+ $line = (mb_strlen($line) <= $cutPoint) ? '' : mb_substr($line, $cutPoint);
+ }
+ return $foldedline;
+ }
+ return $line;
+ }
+}
diff --git a/lib/calendar/CalendarParserICalendar.class.php b/lib/classes/calendar/ICalendarImport.class.php
index 241580a..e78696d 100644
--- a/lib/calendar/CalendarParserICalendar.class.php
+++ b/lib/classes/calendar/ICalendarImport.class.php
@@ -1,91 +1,88 @@
-<?
-# Lifter002: TODO
-# Lifter007: TODO
-
-/**
- * CalendarParserICalendar.class.php
- *
- * 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>, Suchi & Berg GmbH <info@data-quest.de>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @package calendar
- */
-
-class CalendarParserICalendar extends CalendarParser
+<?php
+class ICalendarImport
{
- public $type = '';
- protected $count = null;
+ private $range_id;
- public function __construct()
+ private $count = 0;
+
+ private $dates = [];
+
+ private $import_time;
+
+ private $convert_to_private = false;
+
+ public function __construct($range_id)
{
- parent::__construct();
- $this->type = 'iCalendar';
- // initialize error handler
- $GLOBALS['_calendar_error'] = new ErrorHandler();
+ $this->range_id = $range_id;
+ $this->import_time = time();
}
- public function getCount($data)
+ public function import($ical_data)
+ {
+ $this->parse($ical_data);
+ }
+
+ public function countEvents($ical_data)
{
$matches = [];
if (is_null($this->count)) {
// Unfold any folded lines
- $data = preg_replace('/\x0D?\x0A[\x20\x09]/', '', $data);
- preg_match_all('/(BEGIN:VEVENT(\r\n|\r|\n)[\W\w]*?END:VEVENT\r?\n?)/', $data, $matches);
+ $data = preg_replace('/\x0D?\x0A[\x20\x09]/', '', $ical_data);
+ preg_match_all('/(BEGIN:VEVENT(\r\n|\r|\n)[\W\w]*?END:VEVENT\r?\n?)/', $ical_data, $matches);
$this->count = sizeof($matches[1]);
}
return $this->count;
}
+ public function getCountEvents() : int
+ {
+ return (int) $this->count;
+ }
+
+ public function convertPublicToPrivate(bool $to_private = true) : void
+ {
+ $this->convert_to_private = $to_private;
+ }
+
/**
* Parse a string containing vCalendar data.
*
* @access private
- * @param String $data The data to parse
- *
+ * @param string $data The data to parse
*/
- public function parse($data, $ignore = null)
+ public function parse(string $data)
{
- global $_calendar_error, $PERS_TERMIN_KAT;
-
// match categories
$studip_categories = [];
$i = 1;
- foreach ($PERS_TERMIN_KAT as $cat) {
+ foreach ($GLOBALS['PERS_TERMIN_KAT'] as $cat) {
$studip_categories[mb_strtolower($cat['name'])] = $i++;
}
// Unfold any folded lines
// the CR is optional for files imported from Korganizer (non-standard)
- $data = preg_replace('/\x0D?\x0A[\x20\x09]/', '', $data);
+ $data = $this->unfoldLine($data);
if (!preg_match('/BEGIN:VCALENDAR(\r\n|\r|\n)([\W\w]*)END:VCALENDAR\r?\n?/', $data, $matches)) {
- $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Import-Datei ist keine gültige iCalendar-Datei!"));
- return false;
+ throw new UnexpectedValueException();
}
// client identifier
- if (!$this->_parseClientIdentifier($matches[2])) {
- return false;
+ if (!$this->parseClientIdentifier($matches[2])) {
+ throw new UnexpectedValueException();
}
// All sub components
if (!preg_match_all('/BEGIN:VEVENT(\r\n|\r|\n)([\w\W]*?)END:VEVENT(\r\n|\r|\n)/', $matches[2], $v_events)) {
- $_calendar_error->throwError(ErrorHandler::ERROR_MESSAGE, _("Die importierte Datei enthält keine Termine."));
- return true;
+ // _("Die importierte Datei enthält keine Termine.")
+ throw new UnexpectedValueException();
}
if ($this->count) {
$this->count = 0;
}
foreach ($v_events[2] as $v_event) {
- $properties['CLASS'] = 'PRIVATE';
- // Parse the remain attributes
if (preg_match_all('/(.*):(.*)(\r|\n)+/', $v_event, $matches)) {
$properties = [];
@@ -111,7 +108,7 @@ class CalendarParserICalendar extends CalendarParser
if ($params['ENCODING']) {
switch ($params['ENCODING']) {
case 'QUOTED-PRINTABLE':
- $value = $this->_qp_decode($value);
+ $value = $this->qp_decode($value);
break;
case 'BASE64':
@@ -139,7 +136,7 @@ class CalendarParserICalendar extends CalendarParser
$categories[] = $category;
} else if (!$properties['STUDIP_CATEGORY']) {
$properties['STUDIP_CATEGORY']
- = $studip_categories[mb_strtolower($category)];
+ = $studip_categories[mb_strtolower($category)];
}
}
$properties[$tag] = implode(',', $categories);
@@ -147,12 +144,11 @@ class CalendarParserICalendar extends CalendarParser
// Date fields
case 'DCREATED': // vCalendar property name for "CREATED"
- $tag = "CREATED";
case 'DTSTAMP':
case 'COMPLETED':
case 'CREATED':
case 'LAST-MODIFIED':
- $properties[$tag] = $this->_parseDateTime($value);
+ $properties[$tag] = $this->parseDateTime($value);
break;
case 'DTSTART':
@@ -162,30 +158,30 @@ class CalendarParserICalendar extends CalendarParser
$check['DAY_EVENT'] = true;
case 'DUE':
case 'RECURRENCE-ID':
- $properties[$tag] = $this->_parseDateTime($value);
+ $properties[$tag] = $this->parseDateTime($value);
break;
case 'RDATE':
if (array_key_exists('VALUE', $params)) {
if ($params['VALUE'] == 'PERIOD') {
- $properties[$tag] = $this->_parsePeriod($value);
+ $properties[$tag] = $this->parsePeriod($value);
} else {
- $properties[$tag] = $this->_parseDateTime($value);
+ $properties[$tag] = $this->parseDateTime($value);
}
} else {
- $properties[$tag] = $this->_parseDateTime($value);
+ $properties[$tag] = $this->parseDateTime($value);
}
break;
case 'TRIGGER':
if (array_key_exists('VALUE', $params)) {
if ($params['VALUE'] == 'DATE-TIME') {
- $properties[$tag] = $this->_parseDateTime($value);
+ $properties[$tag] = $this->parseDateTime($value);
} else {
- $properties[$tag] = $this->_parseDuration($value);
+ $properties[$tag] = $this->parseDuration($value);
}
} else {
- $properties[$tag] = $this->_parseDuration($value);
+ $properties[$tag] = $this->parseDuration($value);
}
break;
@@ -198,12 +194,12 @@ class CalendarParserICalendar extends CalendarParser
foreach ($values[1] as $value) {
if (array_key_exists('VALUE', $params)) {
if ($params['VALUE'] == 'DATE-TIME') {
- $dates[] = $this->_parseDateTime($value);
+ $dates[] = $this->parseDateTime($value);
} else if ($params['VALUE'] == 'DATE') {
- $dates[] = $this->_parseDate($value);
+ $dates[] = $this->parseDate($value);
}
} else {
- $dates[] = $this->_parseDateTime($value);
+ $dates[] = $this->parseDateTime($value);
}
}
// some iCalendar exports (e.g. KOrganizer) use an EXDATE-entry for every
@@ -213,7 +209,7 @@ class CalendarParserICalendar extends CalendarParser
// Duration fields
case 'DURATION':
- $attibutes[$tag] = $this->_parseDuration($value);
+ $attibutes[$tag] = $this->parseDuration($value);
break;
// Period of time fields
@@ -222,7 +218,7 @@ class CalendarParserICalendar extends CalendarParser
$periods = [];
preg_match_all('/,([^,]*)/', ',' . $value, $values);
foreach ($values[1] as $value) {
- $periods[] = $this->_parsePeriod($value);
+ $periods[] = $this->parsePeriod($value);
}
$properties[$tag] = $periods;
@@ -231,11 +227,11 @@ class CalendarParserICalendar extends CalendarParser
// UTC offset fields
case 'TZOFFSETFROM':
case 'TZOFFSETTO':
- $properties[$tag] = $this->_parseUtcOffset($value);
+ $properties[$tag] = $this->parseUtcOffset($value);
break;
case 'PRIORITY':
- $properties[$tag] = $this->_parsePriority($value);
+ $properties[$tag] = $this->parsePriority($value);
break;
case 'CLASS':
@@ -269,7 +265,7 @@ class CalendarParserICalendar extends CalendarParser
// Recursion fields
case 'EXRULE':
case 'RRULE':
- $properties[$tag] = $this->_parseRecurrence($value);
+ $properties[$tag] = $this->parseRecurrence($value);
break;
default:
@@ -279,20 +275,22 @@ class CalendarParserICalendar extends CalendarParser
}
}
- if (!$properties['RRULE']['rtype'])
+ if (!$properties['RRULE']['rtype']) {
$properties['RRULE'] = ['rtype' => 'SINGLE'];
+ }
- if (!$properties['LAST-MODIFIED'])
- $properties['LAST-MODIFIED'] = $properties['CREATED'];
+ if (!$properties['LAST-MODIFIED']) {
+ $properties['LAST-MODIFIED'] = $properties['DTSTAMP'] ?: $properties['CREATED'] ?? time();
+ }
if (!$properties['DTSTART'] || ($properties['EXDATE'] && !$properties['RRULE'])) {
- $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Datei ist keine gültige iCalendar-Datei!"));
- $this->count = 0;
- return false;
+ // _("Die Datei ist keine gültige iCalendar-Datei!")
+ throw new UnexpectedValueException();
}
- if (!$properties['DTEND'])
+ if (!$properties['DTEND']) {
$properties['DTEND'] = $properties['DTSTART'];
+ }
// day events starts at 00:00:00 and ends at 23:59:59
if ($check['DAY_EVENT'])
@@ -300,7 +298,7 @@ class CalendarParserICalendar extends CalendarParser
// default: all imported events are set to private
if (!$properties['CLASS']
- || ($this->public_to_private && $properties['CLASS'] == 'PUBLIC')) {
+ || ($this->convert_to_private && $properties['CLASS'] == 'PUBLIC')) {
$properties['CLASS'] = 'PRIVATE';
}
@@ -312,11 +310,10 @@ class CalendarParserICalendar extends CalendarParser
*
*/
- $this->components[] = $properties;
+ $this->createDateFromProperties($properties);
} else {
- $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Datei ist keine gültige iCalendar-Datei!"));
- $this->count = 0;
- return false;
+ // _("Die Datei ist keine gültige iCalendar-Datei!")
+ throw new InvalidValuesException();
}
$this->count++;
}
@@ -324,163 +321,210 @@ class CalendarParserICalendar extends CalendarParser
return true;
}
+ private function createDateFromProperties($properties)
+ {
+ $date = CalendarDate::findOneBySQL(
+ 'LEFT JOIN `calendar_date_assignments`
+ ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id`
+ WHERE `calendar_dates`.`unique_id` = :uid
+ AND `calendar_date_assignments`.`range_id` = :range_id',
+ [
+ ':uid' => $properties['UID'],
+ ':range_id' => $this->range_id
+ ]
+ );
+
+ if (!$date) {
+ $date = new CalendarDate();
+ $date->id = $date->getNewId();
+ $date->author_id = $this->range_id;
+ $date->editor_id = $this->range_id;
+ $range_date = new CalendarDateAssignment();
+ $range_date->range_id = $this->range_id;
+ $range_date->participation = '';
+ $date->calendars[] = $range_date;
+ }
+
+ $date->begin = $properties['DTSTART']->getTimestamp();
+ $date->end = $properties['DTEND']->getTimestamp();
+ $date->title = $properties['SUMMARY'];
+ $date->description = $properties['DESCRIPTION'];
+ $date->access = $properties['CLASS'] ?? 'PRIVATE';
+ $date->user_category = $properties['CATEGORIES'];
+ $date->category = $properties['STUDIP_CATEGORY'] ?: 1;
+ $date->priority = $properties['PRIORITY'] ?? '';
+ $date->location = $properties['LOCATION'];
+ if (is_array($properties['EXDATE'])) {
+ foreach ($properties['EXDATE'] as $exdate) {
+ $exception = new CalendarDateException();
+ $exception->date = $exdate->format('Y-m-d');
+ $date->exceptions[] = $exception;
+ }
+ }
+ $date->mkdate = $properties['CREATED'] ? $properties['CREATED']->getTimestamp() : time();
+ if (isset($properties['LAST-MODIFIED'])) {
+ $date->chdate = $properties['LAST-MODIFIED']->getTimestamp();
+ } else {
+ $date->chdate = $date->mkdate;
+ }
+ $date->import_date = $this->import_time;
+ $date->unique_id = $properties['UID'];
+
+ $this->setRecurrenceRule($date, $properties['RRULE']);
+ $date->store();
+ }
+
+ private function setRecurrenceRule(CalendarDate $date, $rrule)
+ {
+ $date->interval = $rrule['linterval'] ?? 1;
+ if (strlen($rrule['wdays'] ?? '')) {
+ $date->offset = $rrule['sinterval'] ?? 0;
+ $date->days = $rrule['wdays'] ?? null;
+ } else {
+ $date->offset = $rrule['day'] ?? 0;
+ $date->days = $rrule['sinterval'] ?? null;
+ }
+ $date->month = $rrule['month'] ?? null;
+ $date->repetition_type = $rrule['rtype'] ?? 'SINGLE';
+ $date->number_of_dates = $rrule['count'] ?? 1;
+ $date->repetition_end = $rrule['expire'] ?? 0;
+ }
+
+ private function unfoldLine($data)
+ {
+ return preg_replace('/\x0D?\x0A[\x20\x09]/', '', $data);
+ }
+
/**
* Parse a UTC Offset field
*/
- private function _parseUtcOffset($text)
+ private function parseUtcOffset($offset_text)
{
$offset = 0;
- if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $text, $matches)) {
+ if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $offset_text, $matches)) {
$offset += 3600 * intval($matches[2]);
$offset += 60 * intval($matches[3]);
$offset *= ( $matches[1] == '+' ? 1 : -1);
if (array_key_exists(4, $matches)) {
$offset += intval($matches[4]);
}
- return $offset;
- } else {
- return false;
}
+ return $offset;
}
/**
* Parse a Time Period field
*/
- private function _parsePeriod($text)
+ private function parsePeriod($period_text): array
{
- $matches = explode('/', $text);
+ $matches = explode('/', $period_text);
- $start = $this->_parseDateTime($matches[0]);
+ $start = $this->parseDateTime($matches[0]);
- if ($duration = $this->_parseDuration($matches[1])) {
+ if ($duration = $this->parseDuration($matches[1])) {
return ['start' => $start, 'duration' => $duration];
- } else if ($end = $this->_parseDateTime($matches[1])) {
+ } else if ($end = $this->parseDateTime($matches[1])) {
return ['start' => $start, 'end' => $end];
}
+ return [];
}
/**
* Parse a DateTime field
*/
- private function _parseDateTime($text)
+ private function parseDateTime(String $date_time)
{
- $dateParts = explode('T', $text);
- if (count($dateParts) != 2 && !empty($text)) {
- // not a date time field but may be just a date field
- if (!$date = $this->_parseDate($text)) {
- return $date;
- }
- $date = $this->_parseDate($text);
- return mktime(0, 0, 0, $date['month'], $date['mday'], $date['year']);
+ $parts = explode('T', $date_time);
+ if (count($parts) != 2) {
+ // not a date time string but may be just a date string
+ $date = $this->parseDate($date_time);
+ return DateTimeImmutable::createFromFormat('YmdHis', implode('', $date) . '000000');
}
- if (!$date = $this->_parseDate($dateParts[0])) {
- return $date;
- }
- if (!$time = $this->_parseTime($dateParts[1])) {
- return $time;
- }
+ $date = $this->parseDate($parts[0]);
+ $time = $this->parseTime($parts[1]);
if ($time['zone'] == 'UTC') {
- return gmmktime($time['hour'], $time['minute'], $time['second'], $date['month'], $date['mday'], $date['year']);
+ $time_zone = new DateTimeZone('UTC');
} else {
- return mktime($time['hour'], $time['minute'], $time['second'], $date['month'], $date['mday'], $date['year']);
+ $time_zone = new DateTimeZone('Europe/Berlin');
}
+ return DateTimeImmutable::createFromFormat(
+ 'YmdHis',
+ implode('', $date) . $time['hour'] . $time['minute'] . $time['second'],
+ $time_zone
+ );
}
/**
* Parse a Time field
*/
- private function _parseTime($text)
+ private function parseTime($time_text): array
{
- if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $text, $matches)) {
- $time['hour'] = intval($matches[1]);
- $time['minute'] = intval($matches[2]);
- $time['second'] = intval($matches[3]);
+ $matches = [];
+ if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $time_text, $matches)) {
+ $time['hour'] = $matches[1];
+ $time['minute'] = $matches[2];
+ $time['second'] = $matches[3];
if (array_key_exists(4, $matches)) {
$time['zone'] = 'UTC';
} else {
$time['zone'] = 'LOCAL';
}
return $time;
- } else {
- return false;
}
+ throw new InvalidValuesException();
}
/**
* Parse a Date field
*/
- private function _parseDate($text)
+ private function parseDate($date_text): array
{
- if (mb_strlen(trim($text)) !== 8) {
- return false;
+ $matches = [];
+ if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})/', $date_text, $matches)) {
+ $date['year'] = $matches[1];
+ $date['month'] = $matches[2];
+ $date['mday'] = $matches[3];
+ return $date;
}
-
- $date['year'] = intval(mb_substr($text, 0, 4));
- $date['month'] = intval(mb_substr($text, 4, 2));
- $date['mday'] = intval(mb_substr($text, 6, 2));
-
- return $date;
+ throw new InvalidValuesException();
}
/**
* Parse a Duration Value field
*/
- private function _parseDuration($text)
+ private function parseDuration($interval_text): DateInterval
{
- if (preg_match('/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/', trim($text), $matches)) {
- // weeks
- $duration = 7 * 86400 * intval($matches[3]);
- if (count($matches) > 4) {
- // days
- $duration += 86400 * intval($matches[4]);
- }
- if (count($matches) > 5) {
- // hours
- $duration += 3600 * intval($matches[7]);
- // mins
- if (array_key_exists(8, $matches)) {
- $duration += 60 * intval($matches[8]);
- }
- // secs
- if (array_key_exists(9, $matches)) {
- $duration += intval($matches[9]);
- }
- }
- // sign
- if ($matches[1] == "-") {
- $duration *= - 1;
- }
-
- return $duration;
- } else {
- return false;
- }
+ return new DateInterval($interval_text);
}
- private function _parsePriority($value)
+ private function parsePriority($value)
{
$value = intval($value);
if ($value > 0 && $value < 5) {
- return 1;
+ return 'HIGH';
}
if ($value == 5) {
- return 2;
+ return 'MEDIUM';
}
if ($value > 5 && $value < 10) {
- return 3;
+ return 'LOW';
}
- return 0;
+ return '';
}
/**
- * Parse a Recurrence field
+ * Parse a recurrence rule.
+ *
+ * @param $text string The text of the recurrence rule.
+ * @return array The translated recurrence rule as array.
+ * @throws InvalidValuesException
*/
- private function _parseRecurrence($text)
+ private function parseRecurrence($text): array
{
global $_calendar_error;
@@ -498,13 +542,14 @@ class CalendarParserICalendar extends CalendarParser
$r_rule['rtype'] = trim($match[2]);
break;
default:
- $_calendar_error->throwSingleError('parse', ErrorHandler::ERROR_WARNING, _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann."));
- break;
+ throw new InvalidValuesException(
+ _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.")
+ );
}
break;
case 'UNTIL' :
- $r_rule['expire'] = $this->_parseDateTime($match[2]);
+ $r_rule['expire'] = $this->parseDateTime($match[2]);
break;
case 'COUNT' :
@@ -520,22 +565,22 @@ class CalendarParserICalendar extends CalendarParser
case 'BYHOUR' :
case 'BYWEEKNO' :
case 'BYYEARDAY' :
- $_calendar_error->throwSingleError('parse', ErrorHandler::ERROR_WARNING, _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann."));
- break;
-
+ throw new InvalidValuesException(
+ _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.")
+ );
case 'BYDAY' :
- $byday = $this->_parseByDay($match[2]);
+ $byday = $this->parseByDay($match[2]);
$r_rule['wdays'] = $byday['wdays'];
if ($byday['sinterval'])
$r_rule['sinterval'] = $byday['sinterval'];
break;
case 'BYMONTH' :
- $r_rule['month'] = $this->_parseByMonth($match[2]);
+ $r_rule['month'] = $this->parseByMonth($match[2]);
break;
case 'BYMONTHDAY' :
- $r_rule['day'] = $this->_parseByMonthDay($match[2]);
+ $r_rule['day'] = $this->parseByMonthDay($match[2]);
break;
case 'BYSETPOS':
@@ -551,7 +596,7 @@ class CalendarParserICalendar extends CalendarParser
return $r_rule;
}
- private function _parseByDay($text)
+ private function parseByDay($text)
{
global $_calendar_error;
@@ -564,12 +609,15 @@ class CalendarParserICalendar extends CalendarParser
$wdays .= $wdays_map[$match[2]];
if ($match[1]) {
if (!$sinterval && ((int) $match[1]) > 0 || $match[1] == '-1') {
- if ($match[1] == '-1')
+ if ($match[1] == '-1') {
$sinterval = '5';
- else
+ } else {
$sinterval = $match[1];
+ }
} else {
- $_calendar_error->throwSingleError('parse', ErrorHandler::ERROR_WARNING, _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann."));
+ throw new InvalidValuesException(
+ _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.")
+ );
}
}
}
@@ -577,42 +625,40 @@ class CalendarParserICalendar extends CalendarParser
return $wdays ? ['wdays' => $wdays, 'sinterval' => $sinterval] : false;
}
- private function _parseByMonthDay($text)
+ private function parseByMonthDay($text)
{
$days = explode(',', $text);
- if (sizeof($days) > 1 || ((int) $days[0]) < 0) {
+ if (count($days) > 1 || ((int) $days[0]) < 0) {
return false;
}
return $days[0];
}
- private function _parseByMonth($text)
+ private function parseByMonth($text)
{
$months = explode(',', $text);
- if (sizeof($months) > 1) {
+ if (count($months) > 1) {
return false;
}
return $months[0];
}
- private function _qp_decode($value)
+ private function qp_decode($value)
{
return preg_replace_callback("/=([0-9A-F]{2})/", function ($m) {return chr(hexdec($m[1]));}, $value);
}
- private function _parseClientIdentifier(&$data)
+ private function parseClientIdentifier(&$data)
{
global $_calendar_error;
if ($this->client_identifier == '') {
- if (!preg_match('/PRODID((;[\W\w]*)*):([\W\w]+?)(\r\n|\r|\n)/', $data, $matches)) {
- $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Datei ist keine gültige iCalendar-Datei!"));
- return false;
- } elseif (!trim($matches[3])) {
- $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Datei ist keine gültige iCalendar-Datei!"));
- return false;
+ if (!preg_match('/PRODID((;[\W\w]*)*):([\W\w]+?)(\r\n|\r|\n)/', $data, $matches)
+ || !trim($matches[3])) {
+ // _("Die Datei ist keine gültige iCalendar-Datei!")
+ throw new InvalidValuesException();
} else {
$this->client_identifier = trim($matches[3]);
}
@@ -623,7 +669,7 @@ class CalendarParserICalendar extends CalendarParser
public function getClientIdentifier($data = null)
{
if (!is_null($data)) {
- $this->_parseClientIdentifier($data);
+ $this->parseClientIdentifier($data);
}
return $this->client_identifier;
diff --git a/lib/classes/calendar/Owner.interface.php b/lib/classes/calendar/Owner.interface.php
new file mode 100644
index 0000000..a7c2519
--- /dev/null
+++ b/lib/classes/calendar/Owner.interface.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Studip\Calendar;
+
+/**
+ * The Studip\Calendar\Owner interface defines methods that classes whose instances own calendars
+ * shall implement to faciliate permission checks for that calendars.
+ */
+interface Owner
+{
+ /**
+ * Retrieves the Owner object for a specified owner-ID.
+ *
+ * @param string $owner_id The ID of the owner.
+ *
+ * @return Owner|null Either the Owner object if it can be found or null in case
+ * it cannot be found.
+ */
+ public static function getCalendarOwner(string $owner_id) : ?Owner;
+
+ /**
+ * Determines whether the specified user has read permissions to the calendar.
+ *
+ * @param string|null $user_id The ID of the user for which to determine write permissions.
+ * Defaults to the current user if no user-ID is provided.
+ *
+ * @return bool True, if the user has read permissions, false otherwise.
+ */
+ public function isCalendarReadable(?string $user_id = null) : bool;
+
+ /**
+ * Determines whether the specified user has write permissions to the calendar.
+ *
+ * @param string|null $user_id The ID of the user for which to determine write permissions.
+ * Defaults to the current user if no user-ID is provided.
+ *
+ * @return bool True, if the user has write permissions, false otherwise.
+ */
+ public function isCalendarWritable(?string $user_id = null) : bool;
+}
diff --git a/lib/classes/calendar/SingleCalendar.php b/lib/classes/calendar/SingleCalendar.php
deleted file mode 100644
index 938db1a..0000000
--- a/lib/classes/calendar/SingleCalendar.php
+++ /dev/null
@@ -1,1488 +0,0 @@
-<?php
-/**
- * SingleCalendar.php - Model class for a 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>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @since 3.2
- */
-
-class SingleCalendar
-{
- /**
- * This collection holds all Events in this calendar.
- *
- * @var SimpleORMapCollection Collection of Objects which inherits Event.
- */
- public $events;
-
- /**
- * The owner of this calendar.
- *
- * @var Object A Stud.IP object of type User, Institute or Course.
- */
- public $range_object;
-
- public $type;
-
- public $range;
-
- /**
- * The start of this calendar.
- *
- * @var int Unix timestamp.
- */
- public $start;
-
- /**
- * The end of this calendar.
- *
- * @var int Unix timestamp.
- */
- public $end;
-
- public $ts;
-
- public function __construct($range_id, $start = null, $end = null)
- {
- $this->setRangeObject($range_id);
- $this->start = $start ?: 0;
- $this->end = $end ?: Calendar::CALENDAR_END;
- $this->events = new SimpleORMapCollection();
- $this->events->setClassName('Event');
- $this->type = get_class($this->range_object);
- }
-
- /**
- * Sets the range object and checks whether the calendar is available.
- *
- * @param string $range_id The id of a course, institute or user.
- * @throws AccessDeniedException
- */
- private function setRangeObject($range_id)
- {
- $this->range_object = get_object_by_range_id($range_id);
- if (!is_object($this->range_object)) {
- throw new AccessDeniedException();
- }
- $range_map = [
- 'User' => Calendar::RANGE_USER,
- 'Course' => Calendar::RANGE_SEM,
- 'Institute' => Calendar::RANGE_INST
- ];
- $this->range = $range_map[get_class($this->range_object)];
- if ($this->range == Calendar::RANGE_INST
- || $this->range == Calendar::RANGE_SEM) {
-
- if (!$this->range_object->isToolActive('CoreCalendar')) {
- throw new AccessDeniedException();
- }
- }
- }
-
- /**
- * Returns all events of given class names between start and end.
- * Returns events of all types if no class names are given.
- *
- * @param array|null $class_names The names of classes that implements Event.
- * @param int $start The start date time.
- * @param int $end The end date time.
- * @return \SingleCalendar This calendar object.
- * @throws InvalidArgumentException
- */
- public function getEvents($class_names = null, $start = null, $end = null)
- {
- $start = !is_null($start) ? $start : $this->start;
- $end = !is_null($end) ? $end : $this->end;
- if (!is_array($class_names)) {
- $class_names = ['CalendarEvent', 'CourseEvent', 'CourseCancelledEvent', 'CourseMarkedEvent'];
- }
- $events = $this->events->getArrayCopy();
- foreach ($class_names as $type) {
- if (in_array('Event', class_implements($type))) {
- $events = array_merge($events, $type::getEventsByInterval(
- $this->range_object->getId(), new DateTime('@' . $start),
- new DateTime('@' . $end))->getArrayCopy());
- } else {
- throw new InvalidArgumentException(sprintf('Class %s does not implements Event.', $type));
- }
- }
- $this->events = SimpleORMapCollection::createFromArray($events, false);
- $this->events->setClassName('Event');
- return $this;
- }
-
- /**
- * Stores the event in the calendars of all attendees.
- *
- * @param CalendarEvent $event The event to store.
- * @param array $attendee_ids The user ids of the attendees.
- * @return bool|int The number of stored events or false if an error occured.
- */
- public function storeEvent(CalendarEvent $event, $attendee_ids = [])
- {
- $attendee_ids = array_filter($attendee_ids, function($id) {
- return trim($id) != '';
- });
- if (!$attendee_ids) {
- $attendee_ids = [$GLOBALS['user']->id];
- }
- if (count($attendee_ids) === 1) {
- if (!$this->havePermission(Calendar::PERMISSION_WRITABLE)) {
- return false;
- }
- $is_new = $event->isNew();
- $stored = $event->store();
- if ($stored !== false && $this->getRange() == Calendar::RANGE_USER
- && $this->getRangeId() != $GLOBALS['user']->id) {
- $this->sendStoreMessage($event, $is_new);
- }
- return $stored;
- }
-
- if (in_array($this->getRangeId(), $attendee_ids)) {
- // set default status if the organizer is an attendee...
- $event->group_status = CalendarEvent::PARTSTAT_TENTATIVE;
- }
- if ($event->isNew()) {
- return $this->storeAttendeeEvents($event, $attendee_ids);
- }
-
- if (!$event->havePermission(Event::PERMISSION_WRITABLE)) {
- return false;
- }
-
- return $this->storeAttendeeEvents($event, $attendee_ids);
- }
-
- /**
- * Helper function for SingleCalendar::storeEvent().
- *
- * @param CalendarEvent $event The event to store.
- * @param type $attendee_ids The user ids of the attendees.
- * @return bool|int The number of stored events or false if an error occured.
- */
- private function storeAttendeeEvents(CalendarEvent $event, $attendee_ids)
- {
- $ret = 0;
- $new_attendees = [];
- $recipient_ids = [];
- $is_new = false;
- foreach ($attendee_ids as $attendee_id) {
- if (trim($attendee_id)) {
- $attendee_calendar = new SingleCalendar($attendee_id);
-
- // SEMBBS
- // Gruppentermine können ab Calendar::PERMISSION_READABLE angelegt werden
- // if ($attendee_calendar->havePermission(Calendar::PERMISSION_READABLE)) {
-
- if ($attendee_calendar->havePermission(Calendar::PERMISSION_WRITABLE)
- || Config::get()->CALENDAR_GRANT_ALL_INSERT) {
- $attendee_event = new CalendarEvent(
- [$attendee_calendar->getRangeId(), $event->event_id]);
- $attendee_event->event = $event->event;
- $is_new = $attendee_event->isNew();
- if ($is_new) {
- $attendee_event->group_status = $event->group_status;
- }
- $stored = $attendee_event->store();
- if ($stored !== false) {
- // send message if not own calendar
- if (!$attendee_calendar->havePermission(Calendar::PERMISSION_OWN)) {
- $recipient_ids[] = $attendee_event->range_id;
- }
- $new_attendees[] = $attendee_event->range_id;
- $ret += $stored;
- }
- }
- }
- }
- if (count($recipient_ids)) {
- $this->sendStoreMessage($attendee_event, $is_new, $recipient_ids);
- }
-
- $events_delete = CalendarEvent::findBySQL('event_id = ? AND range_id NOT IN(?)',
- [$event->event_id, $new_attendees]);
- foreach ($events_delete as $event_delete) {
- $calendar = new SingleCalendar($event_delete->range_id);
- $calendar->deleteEvent($event_delete);
- }
- return $ret;
- }
-
- /**
- * Sets the start date time by given unix timestamp.
- *
- * @param int $start Unix timestamp.
- */
- public function setStart($start)
- {
- $this->start = $start;
- return $this;
- }
-
- /**
- * Returns the start date time of this calendar as a unix timestamp.
- *
- * @return int Unix timestamp.
- */
- public function getStart()
- {
- return $this->start;
- }
-
- /**
- * Sets the end date time by given unix timestamp.
- *
- * @param int $end Unix timestamp.
- */
- public function setEnd($end)
- {
- $this->end = $end;
- return $this;
- }
-
- /**
- * Returns the end date time of this calendar as a unix timestamp.
- *
- * @return int Unix timestamp.
- */
- public function getEnd()
- {
- return $this->end;
- }
-
- /**
- * Returns a event by given $event_id. Returns a new event of type
- * CalendarEvent with default data if the id is null or unknown.
- * If $class_names is set, only these types of Object will be returned.
- *
- * @param string $event_id
- * @param array $class_names Names of classes which inherits Event.
- * @return Event|null The found event, a new CalendarEvent or null if no
- * event other than a CalendarEvent was found.
- */
- public function getEvent($event_id = null, $class_names = null)
- {
- if (!is_array($class_names)) {
- $class_names = ['CalendarEvent', 'CourseEvent', 'CourseCancelledEvent', 'CourseMarkedEvent'];
- }
- foreach ($class_names as $type) {
- if ($type == 'CalendarEvent') {
- $event = CalendarEvent::find([$this->getRangeId(), $event_id]);
- } else {
- $event = $type::find($event_id);
- }
- if ($event && $event->havePermission(Event::PERMISSION_READABLE)) {
- return $event;
- }
- }
- return $this->getNewEvent();
- }
-
- /**
- * Creates a new event, sets some default data and returns it.
- *
- * @return \CalendarEvent The new event.
- */
- public function getNewEvent()
- {
- $event_data = new EventData();
- $event_data->setId($event_data->getNewId());
- $now = time();
- $event_data->start = $now;
- $event_data->end = $now + 3600;
- $calendar_event = new CalendarEvent();
- $calendar_event->setId([$this->getRangeId(), $event_data->getId()]);
- $calendar_event->event = $event_data;
- return $calendar_event;
- }
-
- /**
- * Sorts all events by start time.
- */
- public function sortEvents()
- {
- $this->events->orderBy('start');
- }
-
- /**
- * An alias for SingleCalendar::getRangeId().
- *
- * @see SingleCalendar::getRangeId()
- */
- public function getId()
- {
- return $this->getRangeId();
- }
-
- /**
- * Returns the range id of this calendar.
- * Possible range id are for objects of type user, inst, fak, group.
- *
- * @return string The range id.
- */
- public function getRangeId()
- {
- return $this->range_object->getId();
- }
-
- /**
- * Returns the object range of this calendar.
- *
- * @return int The object range.
- */
- public function getRange()
- {
- return $this->range;
- }
-
- /**
- * Returns the range object (user, course, institute) of this calendar.
- *
- * @return int The object range.
- */
- public function getRangeObject()
- {
- return $this->range_object;
- }
-
- /**
- * Returns the permission of the given user for this calendar.
- *
- * @param string $user_id User id.
- * @return int The calendar permission.
- */
- public function getPermissionByUser($user_id = null)
- {
- static $user_permission = [];
-
- $user_id = $user_id ?: $GLOBALS['user']->id;
- $id = $user_id . $this->getRangeId();
- if (!empty($user_permission[$id])) {
- return $user_permission[$id];
- }
- // own calendar
- if ($this->range == Calendar::RANGE_USER
- && $this->getRangeId() == $user_id) {
- $user_permission[$id] = Calendar::PERMISSION_OWN;
- return $user_permission[$id];
- }
- switch ($this->type) {
- case 'User' :
- // alle Lehrenden haben gegenseitig schreibenden Zugriff, ab dozent immer schreibenden Zugriff
- /*
- if ($GLOBALS['perm']->have_perm('dozent') && $GLOBALS['perm']->get_perm($this->range_object->getId()) == 'dozent') {
- return Calendar::PERMISSION_WRITABLE;
- }
- *
- */
- $cal_user = CalendarUser::find([$this->getRangeId(), $user_id]);
- if ($cal_user) {
- switch ($cal_user->permission) {
- case 1 :
- $user_permission[$id] = Calendar::PERMISSION_FORBIDDEN;
- break;
- case 2 :
- $user_permission[$id] = Calendar::PERMISSION_READABLE;
- break;
- case 4 :
- $user_permission[$id] = Calendar::PERMISSION_WRITABLE;
- break;
- default :
- $user_permission[$id] = Calendar::PERMISSION_FORBIDDEN;
- }
- } else {
- $user_permission[$id] = Calendar::PERMISSION_FORBIDDEN;
- }
- break;
- /*
- case 'group' :
- $stmt = DBManager::get()->prepare('SELECT range_id FROM statusgruppen WHERE statusgruppe_id = ?');
- $stmt->execute(array($range_id));
- $result = $stmt->fetch(PDO::FETCH_ASSOC);
- if ($result) {
- if ($result['range_id'] == $user_id) {
- return Calendar::PERMISSION_OWN;
- }
- }
- return Calendar::PERMISSION_FORBIDDEN;
- *
- */
- case 'Course' :
- switch ($GLOBALS['perm']->get_studip_perm($this->range_object->getId(), $user_id)) {
- case 'user' :
- case 'autor' :
- $user_permission[$id] = Calendar::PERMISSION_READABLE;
- break;
- case 'tutor' :
- case 'dozent' :
- case 'admin' :
- case 'root' :
- $user_permission[$id] = Calendar::PERMISSION_WRITABLE;
- break;
- default :
- $user_permission[$id] = Calendar::PERMISSION_FORBIDDEN;
- }
- break;
- case 'Institute' :
- switch ($GLOBALS['perm']->get_studip_perm($this->range_object->getId(), $user_id)) {
- case 'user' :
- $user_permission[$id] = Calendar::PERMISSION_READABLE;
- break;
- case 'autor' :
- $user_permission[$id] = Calendar::PERMISSION_READABLE;
- break;
- case 'tutor' :
- case 'dozent' :
- case 'admin' :
- case 'root' :
- $user_permission[$id] = Calendar::PERMISSION_WRITABLE;
- break;
- default :
- // readable for all
- $user_permission[$id] = Calendar::PERMISSION_READABLE;
- }
- break;
- default :
- $user_permission[$id] = Calendar::PERMISSION_FORBIDDEN;
- }
- return $user_permission[$id];
- }
-
- /**
- * Returns whether the given user has at least the $permission to this calendar.
- * It checks for the actual user if $user_id is null.
- *
- * @param int $permission An accepted calendar permission.
- * @param string|null $user_id The id of the user.
- * @return bool True if the user has at least the given permission.
- */
- public function havePermission($permission, $user_id = null)
- {
- $user_id = $user_id ?: $GLOBALS['user']->id;
- return ($permission <= $this->getPermissionByUser($user_id));
- }
-
- /**
- * Returns whether the given user has the $permission to this calendar.
- * It checks for the actual user if $user_id is null.
- *
- * @param int $permission An accepted calendar permission.
- * @param string|null $user_id The id of the user.
- * @return bool True if the user has the given permission.
- */
- public function checkPermission($permission, $user_id = null)
- {
- $user_id = $user_id ?: $GLOBALS['user']->id;
- return $permission == $this->getPermissionByUser($user_id);
- }
-
- /**
- * Sends a message to the owner of the calendar that a new event was inserted
- * or an old event was modified by another user.
- *
- * @param CalendarEvent $event The new or updated event.
- * @param bool $is_new True if the event is new.
- * @param null|array Array with user_ids of the recipients. If not set the
- * owner of the given event is used as recipient.
- */
- protected function sendStoreMessage($event, $is_new, $recipient_ids = null)
- {
- $message = new messaging();
- $event_data = '';
-
- if ($is_new) {
- $msg_text = sprintf(_("%s hat einen neuen Termin in Ihren Kalender eingetragen."), get_fullname());
- $subject = strftime(_('Neuer Termin am %c'), $event->getStart());
- $msg_text .= "\n\n**";
- } else {
- $msg_text = sprintf(_("%s hat einen Termin in Ihrem Kalender geändert."), get_fullname());
- $subject = strftime(_('Termin am %c geändert'), $event->getStart());
- $msg_text .= "\n\n**";
- }
- $msg_text .= _('Zeit') . ':' . '** ' . strftime(' %c - ', $event->getStart())
- . strftime('%c', $event->getEnd()) . "\n**";
- $msg_text .= _("Zusammenfassung") . ':** ' . $event->getTitle() . "\n";
- if ($event_data = $event->getDescription()) {
- $msg_text .= '**' . _('Beschreibung') . ":** $event_data\n";
- }
- if ($event_data = $event->toStringCategories()) {
- $msg_text .= '**' . _('Kategorie') . ":** $event_data\n";
- }
- if ($event_data = $event->toStringPriority()) {
- $msg_text .= '**' . _('Priorität') . ":** $event_data\n";
- }
- if ($event_data = $event->toStringAccessibility()) {
- $msg_text .= '**' . _('Zugriff') . ":** $event_data\n";
- }
- if ($event_data = $event->toStringRecurrence()) {
- $msg_text .= '**' . _('Wiederholung') . ":** $event_data\n";
- }
- if (Config::get()->CALENDAR_GROUP_ENABLE && $event->attendees->count()) {
- $msg_text .= '**' . _("Teilnehmende") . ':** ';
- $msg_text .= implode(', ', $event->attendees->map(
- function ($att) use ($event) {
- $att_name = $att->user->getFullname();
- if ($event->havePermission(Event::PERMISSION_OWN, $att->user->getId())) {
- $att_name .= ' (' . _('Organisator') . ')';
- } else {
- if ($event->toStringGroupStatus()) {
- $att_name .= ' (' . $att->toStringGroupStatus() . ')';
- }
- }
- return $att_name;
- }));
- $msg_text .= "\n";
- }
- $msg_text .= "\n\n" . _('Hier kommen Sie direkt zum Termin in Ihrem Kalender:') . "\n";
- $msg_text .= URLHelper::getURL('dispatch.php/calendar/single/edit/'
- . implode('/', $event->getId()), false);
-
- $recipient_unames = is_array($recipient_ids)
- ? array_map('get_username', $recipient_ids)
- : [get_username($event->range_id)];
-
- $message->insert_message($msg_text, $recipient_unames,
- '____%system%____', '', '', '', '', $subject);
- }
-
-
- /**
- * Deletes a calendar event with regard of the consultation component.
- * Depending whether a CalenderEvent instance is related to a consultation booking
- * or not, the deletion has to be done differently.
- *
- * @param CalendarEvent $event
- * @return bool True on success, false on failure.
- */
- protected function deleteEventWithConsultation(CalendarEvent $event) : bool
- {
- // If the event belongs to a consultation booking, cancel the booking,
- // so that it is available for others.
- $booking = ConsultationBooking::findOneByStudent_event_id($event->event_id);
- if ($booking) {
- // Delete the event indirectly by cancelling the consultation booking:
- $booking->cancel();
- return true;
- }
-
- //Delete the consultation event from the consultation block.
- $consultation_event = ConsultationEvent::findOneByEvent_id($event->event_id);
- // Check if the slot is empty and delete it, if so.
- if ($consultation_event && $consultation_event->slot) {
- $consultation_event->slot->bookings->cancel();
- $consultation_event->slot->delete();
- return true;
- }
-
- // Delete the event
- return $event->delete();
- }
-
- /**
- * Deletes an event from this calendar.
- *
- * @param string|object $calendar_event The id of an event or an event object of type CalendarEvent.
- * @param boolean $all If true all events of a group event will be deleted.
- * @return boolean|int The number of deleted events. False if the event was not deleted.
- */
- public function deleteEvent($calendar_event, $all = false)
- {
- if (!is_object($calendar_event)) {
- $calendar_event = CalendarEvent::find(
- [$this->getRangeId(), $calendar_event]);
- }
- if (!$calendar_event
- || !is_a($calendar_event, 'CalendarEvent')
- || !$calendar_event->havePermission(Event::PERMISSION_DELETABLE)) {
- return false;
- }
- if ($this->havePermission(Calendar::PERMISSION_WRITABLE)
- || $calendar_event->havePermission(Event::PERMISSION_OWN)) {
-
- if (!($calendar_event
- && $calendar_event->havePermission(Event::PERMISSION_WRITABLE))) {
- return false;
- }
-
- if (!is_a($calendar_event, 'CalendarEvent')) {
- return false;
- }
-
- if ($this->getRange() == Calendar::RANGE_USER) {
- $event_message = clone $calendar_event;
- $author_id = $calendar_event->getAuthorId();
- $deleted = $this->deleteEventWithConsultation($calendar_event);
- if ($deleted && !$this->havePermission(Calendar::PERMISSION_OWN)) {
- $this->sendDeleteMessage($event_message);
- }
- if ($all && $deleted && $author_id == $this->getRangeId()) {
- CalendarEvent::findEachBySQL(function ($ce) use ($deleted) {
- $calendar = new SingleCalendar($ce->range_id);
- $deleted += $calendar->deleteEvent($ce);
- }, 'event_id = ?', [$event_message->event_id]);
- }
- return $deleted;
- } else if ($this->getRange() == Calendar::RANGE_SEM) {
- $deleted = $this->deleteEventWithConsultation($calendar_event);
- return $deleted;
- }
- }
- return false;
- }
-
- /**
- * Sends a message to the owner of the calendar that this event was deleted
- * by another user.
- *
- * @param CalendarEvent $event The deleted event.
- * @param null|array Array with user_ids of the recipients. If not set the
- * owner of the given event is used as recipient.
- */
- protected function sendDeleteMessage($event, $recipient_ids = null)
- {
- $message = new messaging();
- $event_data = '';
-
- $subject = strftime(_('Termin am %c gelöscht'), $event->getStart());
- $msg_text = sprintf(_("%s hat folgenden Termin in Ihrem Kalender gelöscht:"), get_fullname());
- $msg_text .= "\n\n";
-
- $msg_text .= '**' . _('Zeit') . ':**' . strftime(' %c - ', $event->getStart())
- . strftime('%c', $event->getEnd()) . "\n";
- $msg_text .= '**' . _('Zusammenfassung') . ':** ' . $event->getTitle() . "\n";
- if ($event_data = $event->getDescription()) {
- $msg_text .= '**' . _('Beschreibung') . ":** $event_data\n";
- }
- if ($event_data = $event->toStringCategories()) {
- $msg_text .= '**' . _('Kategorie') . ":** $event_data\n";
- }
- if ($event_data = $event->toStringPriority()) {
- $msg_text .= '**' . _('Priorität') . ":** $event_data\n";
- }
- if ($event_data = $event->toStringAccessibility()) {
- $msg_text .= '**' . _('Zugriff') . ":** $event_data\n";
- }
- if ($event_data = $event->toStringRecurrence()) {
- $msg_text .= '**' . _('Wiederholung') . ":** $event_data\n";
- }
- $recipient_unames = is_array($recipient_ids)
- ? array_map('get_username', $recipient_ids)
- : [get_username($event->range_id)];
- $message->insert_message($msg_text, $recipient_unames,
- '____%system%____', '', '', '', '', $subject);
- }
-
- /**
- * Returns an array of all events (with calculated recurrences)
- * in the given time range.
- *
- * @param string $owner_id The user id of calendar owner.
- * @param int $time A unix timestamp of this day.
- * @param string $user_id The id of the user who gets access to the calendar (optional, default current user)
- * @param array $restrictions An array with key value pairs of properties to filter the result (optional).
- * @param array $class_names Array of class names. The class must implement Event (optional).
- * @return array All events in the given time range (with calculated recurrences)
- */
- public static function getEventList($owner_id, $start, $end, $user_id = null,
- $restrictions = null, $class_names = null)
- {
- $user_id = $user_id ?: $GLOBALS['user']->id;
- $end_time = mktime(12, 0, 0, date('n', $end), date('j', $end), date('Y', $end));
- $start_day = date('j', $start);
- $events = [];
- do {
- $time = mktime(12, 0, 0, date('n', $start), $start_day, date('Y', $start));
- $start_day++;
- $day = self::getDayCalendar($owner_id, $time, $user_id, $restrictions, $class_names);
- foreach ($day->events as $event) {
- $event_key = implode('', (array) $event->getId()) . $event->getStart();
- $events["$event_key"] = $event;
- }
- } while ($time <= $end_time);
- return $events;
- }
-
- /**
- * Returns a SingleCalendar object with all events of the given owner or
- * SingleCalendar object for one day set by timestamp.
- *
- * @param string|SingleCalendar $owner The user id of calendar owner or a calendar object.
- * @param int $time A unix timestamp of this day.
- * @param string $user_id The id of the user who gets access to the calendar (optional, default current user)
- * @param array $restrictions An array with key value pairs of properties to filter the result (optional).
- * @param array $class_names Array of class names. The class must implement Event (optional).
- * @return \SingleCalendar Calendar Object with all events of given day.
- */
- public static function getDayCalendar($owner, $time, $user_id = null,
- $restrictions = null, $class_names = null)
- {
- $user_id = $user_id ?: $GLOBALS['user']->id;
- if (!is_array($class_names)) {
- $class_names = ['CalendarEvent', 'CourseEvent', 'CourseCancelledEvent', 'CourseMarkedEvent'];
- }
-
- $day = date('Y-m-d-', $time);
- $start = DateTime::createFromFormat('Y-m-d-H:i:s', $day . '00:00:00');
- $end = DateTime::createFromFormat('Y-m-d-H:i:s', $day . '23:59:59');
- if (is_object($owner)) {
- if ($owner instanceof SingleCalendar) {
- $calendar = $owner;
- $calendar->setStart($start->format('U'))->setEnd($end->format('U'));
- } else {
- throw new InvalidArgumentException('The owner must be a user id or an object of type SingleCalendar.');
- }
- } else {
- $calendar = new SingleCalendar($owner,
- $start->format('U'), $end->format('U'));
- }
- $calendar->getEvents($class_names)->sortEvents();
-
- $dow = date('w', $calendar->getStart());
- $month = date('n', $calendar->getStart());
- $year = date('Y', $calendar->getStart());
- $events_created = [];
-
- foreach ($calendar->events as $event) {
- if (!$calendar->havePermission(Calendar::PERMISSION_READABLE, $user_id)
- && $event->getAccessibility() != 'PUBLIC') {
- continue;
- }
- if (!$event->havePermission(Event::PERMISSION_CONFIDENTIAL, $user_id)) {
- continue;
- }
- if (!SingleCalendar::checkRestriction($event, $restrictions)) {
- continue;
- }
- $properties = $event->getProperties();
- $ts = mktime(12, 0, 0, date('n', $calendar->start), date('j', $calendar->start), date('Y', $calendar->start));
- $rep = $properties['RRULE'];
- $duration = (int) ((mktime(12, 0, 0, date('n', $properties['DTEND']), date('j', $properties['DTEND']), date('Y', $properties['DTEND']))
- - mktime(12, 0, 0, date('n', $properties['DTSTART']), date('j', $properties['DTSTART']), date('Y', $properties['DTSTART'])))
- / 86400);
- // single events or first event
- if ($properties['DTSTART'] >= $calendar->getStart()
- && $properties['DTEND'] <= $calendar->getEnd()) {
- self::createDayViewEvent($event, $properties['DTSTART'], $properties['DTEND'],
- $calendar->getStart(), $calendar->getEnd(), $events_created);
- } elseif ($properties['DTSTART'] >= $calendar->getStart()
- && $properties['DTSTART'] <= $calendar->getEnd()) {
- self::createDayViewEvent($event, $properties['DTSTART'], $properties['DTEND'],
- $calendar->getStart(), $calendar->getEnd(), $events_created);
- } elseif ($properties['DTSTART'] < $calendar->getStart()
- && $properties['DTEND'] > $calendar->getEnd()) {
- self::createDayViewEvent($event, $properties['DTSTART'], $properties['DTEND'],
- $calendar->getStart(), $calendar->getEnd(), $events_created);
- } elseif ($properties['DTEND'] > $calendar->getStart()
- && $properties['DTEND'] <= $calendar->getEnd()) {
- self::createDayViewEvent($event, $properties['DTSTART'], $properties['DTEND'],
- $calendar->getStart(), $calendar->getEnd(), $events_created);
- }
- switch ($rep['rtype']) {
- case 'DAILY':
- /*
- if ($calendar->getEnd() > $rep['expire'] + $duration * 86400) {
- continue;
- }
- *
- */
- if ($end > $rep['expire'] + $duration * 86400) {
- continue 2;
- }
- $ts = $ts + (date('I', $rep['ts']) * 3600);
- $pos = (($ts - $rep['ts']) / 86400) % $rep['linterval'];
- $start = $ts - $pos * 86400;
- $end = $start + $duration * 86400;
- self::createDayViewEvent($event, $start, $end, $calendar->getStart(),
- $calendar->getEnd(), $events_created);
- break;
- case 'WEEKLY':
- $rep['ts'] = $rep['ts'] + ((date('I', $rep['ts']) - date('I', $ts)) * 3600);
- for ($i = 0; $i < mb_strlen($rep['wdays']); $i++) {
- $pos = ((($ts - $dow * 86400) - $rep['ts']) / 86400
- - ($rep['wdays'][$i] - 1) + $dow)
- % ($rep['linterval'] * 7);
- $start = $ts - $pos * 86400;
- $end = $start + $duration * 86400;
- if ($start >= $properties['DTSTART'] && $start <= $ts && $end >= $ts) {
- self::createDayViewEvent($event, $start, $end,
- $calendar->getStart(), $calendar->getEnd(), $events_created);
- }
- }
- break;
- case 'MONTHLY':
- if ($rep['day']) {
- $lwst = mktime(12, 0, 0, $month
- - ((($year - date('Y', $rep['ts'])) * 12
- + ($month - date('n', $rep['ts']))) % $rep['linterval']),
- $rep['day'], $year);
- $hgst = $lwst + $duration * 86400;
- self::createDayViewEvent($event, $lwst, $hgst, $calendar->getStart(),
- $calendar->getEnd(), $events_created);
- break;
- }
- if ($rep['sinterval']) {
- $mon = $month - $rep['linterval'];
- do {
- $lwst = mktime(12, 0, 0, $mon
- - ((($year - date('Y', $rep['ts'])) * 12
- + ($mon - date('n', $rep['ts']))) % $rep['linterval']),
- 1, $year) + ($rep['sinterval'] - 1) * 604800;
- $aday = strftime('%u', $lwst);
- $lwst -= ( $aday - $rep['wdays']) * 86400;
- if ($rep['sinterval'] == 5) {
- if (date('j', $lwst) < 10) {
- $lwst -= 604800;
- }
- if (date('n', $lwst) == date('n', $lwst + 604800)) {
- $lwst += 604800;
- }
- } else {
- if ($aday > $rep['wdays']) {
- $lwst += 604800;
- }
- }
- $hgst = $lwst + $duration * 86400;
- if ($ts >= $lwst && $ts <= $hgst) {
- self::createDayViewEvent($event, $lwst, $hgst,
- $calendar->getStart(), $calendar->getEnd(), $events_created);
- }
- $mon += $rep['linterval'];
- } while ($lwst < $ts);
- }
- break;
- case 'YEARLY':
- if ($ts < $rep['ts']) {
- break;
- }
- if ($rep['day']) {
- if (date('Y', $properties['DTEND']) - date('Y', $properties['DTSTART'])) {
- $lwst = mktime(12, 0, 0, $rep['month'], $rep['day'],
- $year - (($year - date('Y', $rep['ts'])) % $rep['linterval'])
- - $rep['linterval']);
- $hgst = $lwst + 86400 * $duration;
- if ($ts >= $lwst && $ts <= $hgst) {
- self::createDayViewEvent($event, $lwst, $hgst,
- $calendar->getStart(), $calendar->getEnd(), $events_created);
- break;
- }
- }
- $lwst = mktime(12, 0, 0, $rep['month'], $rep['day'],
- $year - (($year - date('Y', $rep['ts'])) % $rep['linterval']));
- $hgst = $lwst + 86400 * $duration;
- self::createDayViewEvent($event, $lwst, $hgst, $calendar->getStart(),
- $calendar->getEnd(), $events_created);
- break;
- }
- $ayear = $year - 1;
- do {
- if ($rep['sinterval']) {
- $lwst = mktime(12, 0, 0, $rep['month'],
- 1 + ($rep['sinterval'] - 1) * 7, $ayear);
- $aday = strftime('%u', $lwst);
- $lwst -= ( $aday - $rep['wdays']) * 86400;
- if ($rep['sinterval'] == 5) {
- if (date('j', $lwst) < 10) {
- $lwst -= 604800;
- }
- if (date('n', $lwst) == date('n', $lwst + 604800)) {
- $lwst += 604800;
- }
- } elseif ($aday > $rep['wdays']) {
- $lwst += 604800;
- }
- $ayear++;
- $hgst = $lwst + $duration * 86400;
- if ($ts >= $lwst && $ts <= $hgst) {
- self::createDayViewEvent($event, $lwst, $hgst,
- $calendar->getStart(), $calendar->getEnd(), $events_created);
- }
- }
- } while ($lwst < $ts);
- }
- }
- $calendar->events->exchangeArray(array_values($events_created));
- return $calendar;
- }
-
- /**
- * Creates events for the day view.
- *
- * @param Event $event
- * @param int $lwst
- * @param int $hgst
- * @param int $cl_start
- * @param int $cl_end
- * @param array $events_created
- * @return boolean
- */
- private static function createDayViewEvent($event, $lwst, $hgst,
- $cl_start, $cl_end, Array &$events_created)
- {
- $lwst = mktime(12, 0, 0, date('n', $lwst), date('j', $lwst), date('Y', $lwst));
- $hgst = mktime(12, 0, 0, date('n', $hgst), date('j', $hgst), date('Y', $hgst));
-
- // if this date is in the exceptions?
- if ($event->getProperty('EXDATE')) {
- $exdates = explode(',', $event->getProperty('EXDATE'));
- foreach ($exdates as $exdate) {
- if ($exdate > 0 && $exdate >= $lwst && $exdate <= $hgst) {
- return false;
- }
- }
- }
- // is event expired?
- $rrule = $event->getRecurrence();
- if ($rrule['rtype'] != 'SINGLE' && $rrule['expire'] > 0 && $rrule['expire'] < $hgst) {
- return false;
- }
- $start = mktime(date('G', $event->getStart()), date('i', $event->getStart()),
- date('s', $event->getStart()), date('n', $lwst), date('j', $lwst), date('Y', $lwst));
- $end = mktime(date('G', $event->getEnd()), date('i', $event->getEnd()),
- date('s', $event->getEnd()), date('n', $hgst), date('j', $hgst), date('Y', $hgst));
-
- if (($start <= $cl_start && $end >= $cl_end)
- || ($start >= $cl_start && $start < $cl_end)
- || ($end > $cl_start && $end <= $cl_end)) {
-
- $key = implode('', (array) $event->getId()) . $start;
- if (empty($events_created[$key])) {
- $new_event = clone $event;
- $new_event->setStart($start);
- $new_event->setEnd($end);
- $events_created[$key] = $new_event;
- }
- }
-
- return true;
- }
-
- /**
- * Returns an array with all days between start and end of this SingleCalendar.
- * The keys are the timestamps of the days (12:00) and the values are number
- * of events for a day.
- *
- * @param string $user_id Use the permissions of this user.
- * @param array $restrictions
- * @return array An array with year day as key and number of events per day as value.
- */
- public function getListCountEvents($class_names = null, $user_id = null, $restrictions = null)
- {
- if (!is_array($class_names)) {
- $class_names = ['CalendarEvent', 'CourseEvent', 'CourseCancelledEvent', 'CourseMarkedEvent'];
- }
- $end = $this->getEnd();
- $start = $this->getStart();
- $year = date('Y', $start);
- $end_ts = mktime(12, 0, 0, date('n', $end), date('j', $end), date('Y', $end));
- $start_ts = mktime(12, 0, 0, date('n', $start), date('j', $start), date('Y', $start));
- $this->getEvents($class_names)->sortEvents();
- $daylist = [];
- $this->ts = mktime(12, 0, 0, 1, 1, $year);
- foreach ($this->events as $event) {
- if (!$event->havePermission(Event::PERMISSION_CONFIDENTIAL, $user_id)) {
- continue;
- }
- if (!SingleCalendar::checkRestriction($event, $restrictions)) {
- continue;
- }
- $properties = $event->getProperties();
-
- $rep = $properties['RRULE'];
- $duration = (int) ((mktime(12, 0, 0, date('n', $properties['DTEND']), date('j', $properties['DTEND']), date('Y', $properties['DTEND']))
- - mktime(12, 0, 0, date('n', $properties['DTSTART']), date('j', $properties['DTSTART']), date('Y', $properties['DTSTART'])))
- / 86400);
-
- // single event or first event
- $lwst = mktime(12, 0, 0, date('n', $properties['DTSTART']), date('j', $properties['DTSTART']), date('Y', $properties['DTSTART']));
- if ($start_ts > $lwst) {
- $adate = $start_ts;
- } else {
- $adate = $lwst;
- }
- $hgst = $lwst + $duration * 86400;
- while ($adate >= $start_ts && $adate <= $end_ts && $adate <= $hgst) {
- $md_date = $adate - date('I', $adate) * 3600;
- $this->countListEvent($properties, $md_date, $properties['DTSTART'], $properties['DTEND'], $daylist);
- $adate += 86400;
- }
-
- switch ($rep['rtype']) {
- case 'DAILY' :
- if ($rep['ts'] < $start) {
- // brauche den ersten Tag nach $start an dem dieser Termin wiederholt wird
- if ($rep['linterval'] == 1) {
- $adate = $this->ts;
- } else {
- $adate = $this->ts + ($rep['linterval'] - (($this->ts - $rep['ts']) / 86400)
- % $rep['linterval']) * 86400;
- }
- while ($adate <= $end_ts && $adate >= $this->ts && $adate <= $rep['expire']) {
- $hgst = $adate + $duration * 86400;
- $md_date = $adate;
- while ($md_date <= $end_ts && $md_date >= $this->ts && $md_date <= $hgst) {
- $md_date -= 3600 * date('I', $md_date);
- $this->countListEvent($properties, $md_date, $adate, $hgst, $daylist);
- $md_date += 86400;
- }
- $adate += $rep['linterval'] * 86400;
- }
- } else {
- $adate = $rep['ts'];
- }
- while ($adate <= $end_ts && $adate >= $this->ts && $adate <= $rep['expire']) {
- $hgst = $adate + $duration * 86400;
- $md_date = $adate;
- while ($md_date <= $end_ts && $md_date >= $this->ts && $md_date <= $hgst) {
- $md_date += 3600 * date('I', $md_date);
- $this->countListEvent($properties, $md_date, $adate, $hgst, $daylist);
- $md_date += 86400;
- }
- $adate += $rep['linterval'] * 86400;
- }
- break;
-
- case 'WEEKLY' :
- if ($properties['DTSTART'] >= $start && $properties['DTSTART'] <= $end) {
- $lwst = mktime(12, 0, 0, date('n', $properties['DTSTART']), date('j', $properties['DTSTART']), date('Y', $properties['DTSTART']));
- $hgst = $lwst + $duration * 86400;
- if ($rep['ts'] != $adate) {
- $wdate = $lwst;
- while ($wdate <= $end_ts && $wdate >= $start_ts && $wdate <= $hgst) {
- // $md_date = $wdate - date('I', $wdate) * 3600;
- $this->countListEvent($properties, $wdate, $lwst, $hgst, $daylist);
- $wdate += 86400;
- }
- }
- $aday = strftime('%u', $lwst) - 1;
- for ($i = 0; $i < mb_strlen($rep['wdays']); $i++) {
- $awday = (int) mb_substr($rep['wdays'], $i, 1) - 1;
- if ($awday > $aday) {
- $lwst = $lwst + ($awday - $aday) * 86400;
- $hgst = $lwst + $duration * 86400;
- $wdate = $lwst;
- while ($wdate >= $start_ts && $wdate <= $end_ts && $wdate <= $hgst) {
- // $md_date = $wdate - date('I', $wdate) * 3600;
- $this->countListEvent($properties, $wdate, $lwst, $hgst, $daylist);
- $wdate += 86400;
- }
- }
- }
- }
- if ($rep['ts'] < $start) {
- $adate = $start_ts - (strftime('%u', $start_ts) - 1) * 86400;
- $adate += ( $rep['linterval'] - (($adate - $rep['ts']) / 604800)
- % $rep['linterval']) * 604800;
- $adate -= $rep['linterval'] * 604800;
- } else {
- $adate = $rep['ts'] + 604800 * $rep['linterval'];
- }
-
- while ($adate >= $properties['DTSTART'] && $adate <= $rep['expire'] && $adate <= $end) {
- // event is repeated on different week days
- for ($i = 0; $i < mb_strlen($rep['wdays']); $i++) {
- $awday = (int) $rep['wdays'][$i];
- $lwst = $adate + ($awday - 1) * 86400;
- $hgst = $lwst + $duration * 86400;
- if ($lwst < $start_ts) {
- $lwst = $start_ts;
- }
- $wdate = $lwst;
- while ($wdate >= $start_ts && $wdate <= $end_ts && $wdate <= $hgst) {
- // $md_date = $wdate - date('I', $wdate) * 3600;
- $this->countListEvent($properties, $wdate, $lwst, $hgst, $daylist);
- $wdate += 86400;
- }
- }
- $adate += 604800 * $rep['linterval'];
- }
- break;
-
- case 'MONTHLY' :
- $bmonth = ($rep['linterval'] - ((($year - date('Y', $rep['ts'])) * 12)
- - date('n', $rep['ts'])) % $rep['linterval']) % $rep['linterval'];
-
- for ($amonth = $bmonth - $rep['linterval']; $amonth <= $bmonth; $amonth += $rep['linterval']) {
- if ($rep['ts'] < $start) {
- // is repeated at X. week day of X. month...
- if (!$rep['day']) {
- $lwst = mktime(12, 0, 0, $amonth
- - ((($year - date('Y', $rep['ts'])) * 12
- + ($amonth - date('n', $rep['ts']))) % $rep['linterval']), 1, $year)
- + ($rep['sinterval'] - 1) * 604800;
- $aday = strftime('%u', $lwst);
- $lwst -= ( $aday - $rep['wdays']) * 86400;
- if ($rep['sinterval'] == 5) {
- if (date('j', $lwst) < 10) {
- $lwst -= 604800;
- }
- if (date('n', $lwst) == date('n', $lwst + 604800)) {
- $lwst += 604800;
- }
- } else {
- if ($aday > $rep['wdays']) {
- $lwst += 604800;
- }
- }
- } else {
- // or at X. day of month ?
- $lwst = mktime(12, 0, 0, $amonth
- - ((($year - date('Y', $rep['ts'])) * 12
- + ($amonth - date('n', $rep['ts']))) % $rep['linterval']), $rep['day'], $year);
- }
- } else {
- // first recurrence
- $lwst = $rep['ts'];
- $lwst = mktime(12, 0, 0, $amonth
- - ((($year - date('Y', $rep['ts'])) * 12
- + ($amonth - date('n', $rep['ts']))) % $rep['linterval']), $rep['day'], $year);
-
- }
- $hgst = $lwst + $duration * 86400;
- $md_date = $lwst;
- // events last longer than one day
- while ($md_date >= $start_ts && $md_date <= $hgst && $md_date <= $end_ts) {
- $this->countListEvent($properties, $md_date, $lwst, $hgst, $daylist);
- $md_date += 86400;
- }
- }
- break;
-
- case 'YEARLY' :
- for ($ayear = $year - 1; $ayear <= $year; $ayear++) {
- if ($rep['day']) {
- $lwst = mktime(12, 0, 0, $rep['month'], $rep['day'], $ayear);
- $hgst = $lwst + $duration * 86400;
- $wdate = $lwst;
- while ($hgst >= $start_ts && $wdate <= $hgst && $wdate <= $end_ts) {
- $this->countListEvent($properties, $wdate, $lwst, $hgst, $daylist);
- $wdate += 86400;
- }
- } else {
- if ($rep['ts'] < $start) {
- $adate = mktime(12, 0, 0, $rep['month'], 1, $ayear)
- + ($rep['sinterval'] - 1) * 604800;
- $aday = strftime('%u', $adate);
- $adate -= ( $aday - $rep['wdays']) * 86400;
- if ($rep['sinterval'] == 5) {
- if (date('j', $adate) < 10) {
- $adate -= 604800;
- }
- } elseif ($aday > $rep['wdays']) {
- $adate += 604800;
- }
- } else {
- $adate = $rep['ts'];
- }
- $lwst = $adate;
- $hgst = $lwst + $duration * 86400;
- while ($hgst >= $start_ts && $adate <= $hgst && $adate <= $end_ts) {
- $this->countListEvent($properties, $adate, $lwst, $hgst, $daylist);
- $adate += 86400;
- }
- }
- }
- }
- }
- return $daylist;
- }
-
- private function countListEvent($properties, $date, $lwst, $hgst, &$daylist)
- {
- if ($date < $this->getStart() || $date > $this->getEnd()) {
- return false;
- }
- $lwst = mktime(12, 0, 0, date('n', $lwst), date('j', $lwst), date('Y', $lwst));
- $hgst = mktime(12, 0, 0, date('n', $hgst), date('j', $hgst), date('Y', $hgst));
-
- // if this date is in the exceptions return false
- $exdates = explode(',', $properties['EXDATE']);
- foreach ($exdates as $exdate) {
- if ($exdate > 0 && $exdate >= $lwst && $exdate <= $hgst) {
- return false;
- }
- }
- // is event expired?
- if ($properties['RRULE']['expire'] > 0
- && $properties['RRULE']['expire'] <= $hgst) {
- return false;
- }
- $idate = date('Ymd', $date);
- $daylist["$idate"]["{$properties['STUDIP_ID']}"] =
- $daylist["$idate"]["{$properties['STUDIP_ID']}"]
- ? $daylist["$idate"]["{$properties['STUDIP_ID']}"]++ : 1;
- return true;
- }
-
- /**
- *
- * TODO use filter instead
- *
- * @param Event $event
- * @param array $restrictions
- * @return boolean
- */
- public static function checkRestriction(Event $event, $restrictions)
- {
- $properties = $event->getProperties();
- if (is_array($restrictions)) {
- foreach ($restrictions as $property_name => $restriction) {
- if (isset($properties[mb_strtoupper($property_name)])) {
- if (is_array($restriction)) {
- return in_array($properties[mb_strtoupper($property_name)], $restriction);
- } else if ($restriction != '') {
- return $properties[mb_strtoupper($property_name)] == $restriction;
- }
- }
- }
- }
- return true;
- }
-
- /**
- * Returns an array with all necessary informations to build the day view.
- *
- * @param type $start
- * @param type $end
- * @param type $step
- * @param type $params
- * @return type
- */
- public function createEventMatrix($start, $end, $step)
- {
- // correction of days where dst starts or ends
- $dst_offset = (date('I', $this->getStart()) - date('I', $this->getEnd())) * 3600;
- $start += $dst_offset;
- $end += $dst_offset;
-
- $term = [];
- $em = $this->adapt_events($start, $end, $step);
- $max_cols = 0;
- $mapping = [];
- // calculate maximum number of columns
- $w = 0;
- for ($i = $start / $step; $i < $end / $step + 3600 / $step; $i++) {
- $col = 0;
- $row = $i - $start / $step;
- while ($w < sizeof($em['events']) && $em['events'][$w]->getStart() >= $this->getStart() + $i * $step
- && $em['events'][$w]->getStart() < $this->getStart() + ($i + 1) * $step) {
- $rows = ceil($em['events'][$w]->getDuration() / $step);
- if ($rows < 1) {
- $rows = 1;
- }
- if (empty($term[$row])) {
- $term[$row] = [];
- }
- if (empty($term[$row][$col])) {
- $term[$row][$col] = '';
- }
- while ($term[$row][$col] != '' && $term[$row][$col] != '#') {
- $col++;
- }
- $term[$row][$col] = $em['events'][$w];
- $mapping[$row][$col] = $em['map'][$w];
-
- $count = $rows - 1;
- for ($x = $row + 1; $x < $row + $rows; $x++) {
- for ($y = 0; $y <= $col; $y++) {
- if ($y == $col) {
- $term[$x][$y] = $count--;
- } elseif ($term[$x][$y] == '') {
- $term[$x][$y] = '#';
- }
- }
- }
- if ($max_cols < sizeof($term[$row])) {
- $max_cols = sizeof($term[$row]);
- }
- $w++;
- }
- }
- $row_min = 0;
- for ($i = $start / $step; $i < $end / $step + 3600 / $step; $i++) {
- $row = $i - $start / $step;
- $row_min = $row;
- while ($this->maxValue($term[$row] ?? 0, $step) > 1) {
- $row += $this->maxValue($term[$row] ?? 0, $step) - 1;
- }
- $size = 0;
- for ($j = $row_min; $j <= $row; $j++) {
- if (isset($term[$j]) && count($term[$j]) > $size) {
- $size = count($term[$j]);
- }
- }
- for ($j = $row_min; $j <= $row; $j++) {
- $colsp[$j] = $size;
- }
- $i = $row + $start / $step;
- }
- $rows = [];
- $cspan = [];
- for ($i = $start / $step; $i < $end / $step + 3600 / $step; $i++) {
- $row = $i - $start / $step;
- $cspan_0 = 0;
- if (!empty($term[$row])) {
- if ($colsp[$row] > 0) {
- $cspan_0 = (int) ($max_cols / $colsp[$row]);
- }
- for ($j = 0; $j < $colsp[$row]; $j++) {
- $sp = 0;
- $n = 0;
- if ($j + 1 == $colsp[$row]) {
- $cspan[$row][$j] = $cspan_0 + $max_cols % $colsp[$row];
- }
- if (is_object($term[$row][$j])) {
- // Wieviele Termine sind zum aktuellen Termin zeitgleich?
- $p = 0;
- $count = 0;
- while (array_key_exists($p, $em['events']) && ($aterm = $em['events'][$p])) {
- if ($aterm->getStart() >= $term[$row][$j]->getStart()
- && $aterm->getStart() <= $term[$row][$j]->getEnd()) {
- $count++;
- }
- $p++;
- }
- if ($count == 0) {
- for ($n = $j + 1; $n < $colsp[$row]; $n++) {
- if (!is_int($term[$row][$n])) {
- $sp++;
- } else {
- break;
- }
- }
- $cspan[$row][$j] += $sp;
- }
- $rows[$row][$j] = ceil($term[$row][$j]->getDuration() / $step);
- if ($rows[$row][$j] < 1) {
- $rows[$row][$j] = 1;
- }
- if ($sp > 0) {
- for ($m = $row; $m < $rows + $row; $m++) {
- $colsp[$m] = $colsp[$m] - $sp + 1;
- $v = $j;
- while ($term[$m][$v] == '#') {
- $term[$m][$v] = 1;
- }
- }
- $j = $n;
- }
- } elseif ($term[$row][$j] == '#') {
- $csp = 1;
- while ($term[$row][$j] == '#') {
- $csp += $cspan[$row][$j];
- $j++;
- }
- $cspan[$row][$j] = $csp;
- } elseif ($term[$row][$j] == '') {
- $cspan[$row][$j] = $max_cols - $j + 1;
- }
- }
- }
- }
- if ($max_cols < 1 && isset($em['day_events']) && count($em['day_events'])) {
- $max_cols = 1;
- }
- $em['cspan'] = $cspan;
- $em['rows'] = $rows;
- $em['colsp'] = $colsp;
- $em['term'] = $term;
- $em['max_cols'] = $max_cols;
- $em['mapping'] = $mapping;
- return $em;
- }
-
- /**
- * Returns max value of colspan in calendar tables for day view.
- *
- * @param Array $term Array with table cell content.
- * @param int $st Seconds between each row in calendar table.
- * @return int Max value of colspan.
- */
- private function maxValue($term, $st)
- {
- $max_value = 0;
-
- if (is_array($term)) {
- for ($i = 0; $i < count($term); $i++) {
- if (is_object($term[$i])) {
- $max = ceil($term[$i]->getDuration() / $st);
- } elseif ($term[$i] == '#') {
- continue;
- } elseif ($term[$i] > $max_value) {
- $max = $term[$i];
- }
- if ($max > $max_value) {
- $max_value = $max;
- }
- }
- }
-
- return $max_value;
- }
-
- /**
- * Returns array with events and other information to build calendar tables
- * for day view.
- *
- * @param int $start Start time date as unix timestamp
- * @param int $end End time date as unix timestamp
- * @param int $step Seconds between each row in calendar table.
- * @return Array Array with new calculated events and some other things.
- */
- public function adapt_events($start, $end, $step = 900)
- {
- $tmp_events = [];
- $map_events = [];
- $tmp_day_event = [];
- $map_day_events = [];
- for ($i = 0; $i < sizeof($this->events); $i++) {
- $event = $this->events[$i];
- if (($event->getEnd() > $this->getStart() + $start)
- && ($event->getStart() < $this->getStart() + $end + 3600)) {
- $cloned_event = clone $event;
- if ($event->isDayEvent()
- || ($event->getStart() <= $this->getStart()
- && $event->getEnd() >= $this->getEnd())) {
- $cloned_event->setStart($this->getStart());
- $cloned_event->setEnd($this->getEnd());
- $tmp_day_event[] = $cloned_event;
- $map_day_events[] = $i;
- } else {
- $end_corr = $cloned_event->getEnd() % $step;
- if ($end_corr > 0) {
- $end_corr = $cloned_event->getEnd() + ($step - $end_corr);
- $cloned_event->setEnd($end_corr);
- }
- if ($cloned_event->getStart() < ($this->getStart() + $start)) {
- $cloned_event->setStart($this->getStart() + $start);
- }
- if ($cloned_event->getEnd() > ($this->getStart() + $end + 3600)) {
- $cloned_event->setEnd($this->getStart() + $end + 3600);
- }
- $tmp_events[$cloned_event->id . $cloned_event->getStart()] = $cloned_event;
- $map_events[$cloned_event->id . $cloned_event->getStart()] = $i;
- }
- }
- }
-
- uasort($tmp_events, function($a, $b) {return $a->start - $b->start;});
- $map = [];
- foreach (array_keys($tmp_events) as $key) {
- $map[] = $map_events[$key];
- }
-
- return [
- 'events' => array_values($tmp_events),
- 'map' => $map,
- 'day_events' => $tmp_day_event,
- 'day_map' => $map_day_events];
- }
-
-}
diff --git a/lib/classes/forms/SelectedRangesInput.php b/lib/classes/forms/SelectedRangesInput.php
new file mode 100644
index 0000000..77b2db2
--- /dev/null
+++ b/lib/classes/forms/SelectedRangesInput.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Studip\Forms;
+
+class SelectedRangesInput extends Input
+{
+ protected $selectable_items = [];
+
+ protected $selected_items = [];
+
+ protected $search_type = null;
+
+
+ public function render()
+ {
+ $template = $GLOBALS['template_factory']->open('forms/selected_ranges_input');
+ $template->name = $this->name;
+ $template->selectable_items = $this->selectable_items;
+ $template->selected_items = [];
+ foreach ($this->selected_items as $item) {
+ $item_data = [];
+ if ($item instanceof \Range) {
+ $item_data['name'] = $item->getFullname();
+ $item_data['id'] = $item->getRangeId();
+ } elseif ($item instanceof \StudipItem) {
+ $item_data['name'] = $item->getItemName();
+ $item_data['id'] = $item->id;
+ } elseif (
+ is_array($item)
+ && array_key_exists('name', $item)
+ && array_key_exists('id', $item)
+ ) {
+ $item_data['name'] = $item['name'];
+ $item_data['id'] = $item['id'];
+ }
+ if ($item_data) {
+ $template->selected_items[] = $item_data;
+ }
+ }
+ $template->searchtype = $this->search_type;
+ return $template->render();
+ }
+
+ public function getRequestValue()
+ {
+ return \Request::getArray($this->name);
+ }
+
+ public function setSelectedItems(array $items)
+ {
+ $this->selected_items = $items;
+ }
+
+ public function setSearchType(\SearchType $search_type)
+ {
+ $this->search_type = $search_type;
+ }
+}
diff --git a/lib/classes/searchtypes/StandardSearch.class.php b/lib/classes/searchtypes/StandardSearch.class.php
index fc3135a..837a849 100644
--- a/lib/classes/searchtypes/StandardSearch.class.php
+++ b/lib/classes/searchtypes/StandardSearch.class.php
@@ -94,24 +94,36 @@ class StandardSearch extends SQLSearch
switch ($this->search) {
case "username":
$this->extendedLayout = true;
- return "SELECT DISTINCT auth_user_md5.username, CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname, ' (', auth_user_md5.username, ')'), auth_user_md5.perms " .
- "FROM auth_user_md5 LEFT JOIN user_info ON (user_info.user_id = auth_user_md5.user_id) " .
+ $sql = "SELECT DISTINCT auth_user_md5.username";
+ if (empty($this->search_settings['simple_name'])) {
+ $sql .= ", CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname, ' (', auth_user_md5.username, ')'), auth_user_md5.perms ";
+ } else {
+ $sql .= ", CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) ";
+ }
+ $sql .= "FROM auth_user_md5 LEFT JOIN user_info ON (user_info.user_id = auth_user_md5.user_id) " .
"LEFT JOIN user_visibility ON (user_visibility.user_id = auth_user_md5.user_id) " .
"WHERE (CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " .
"OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " .
"OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " .
"OR auth_user_md5.username LIKE :input) AND " . get_vis_query('auth_user_md5', 'search') .
" ORDER BY Nachname ASC, Vorname ASC";
+ return $sql;
case "user_id":
$this->extendedLayout = true;
- return "SELECT DISTINCT auth_user_md5.user_id, CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname, ' (', auth_user_md5.username, ')'), auth_user_md5.perms " .
- "FROM auth_user_md5 LEFT JOIN user_info ON (user_info.user_id = auth_user_md5.user_id) " .
+ $sql = "SELECT DISTINCT auth_user_md5.user_id";
+ if (empty($this->search_settings['simple_name'])) {
+ $sql .= ", CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname, ' (', auth_user_md5.username, ')'), auth_user_md5.perms ";
+ } else {
+ $sql .= ", CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) ";
+ }
+ $sql .= "FROM auth_user_md5 LEFT JOIN user_info ON (user_info.user_id = auth_user_md5.user_id) " .
"LEFT JOIN user_visibility ON (user_visibility.user_id = auth_user_md5.user_id) " .
"WHERE (CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) LIKE REPLACE(:input, ' ', '% ') " .
"OR CONCAT(auth_user_md5.Nachname, ' ', auth_user_md5.Vorname) LIKE REPLACE(:input, ' ', '% ') " .
"OR CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname) LIKE :input " .
"OR auth_user_md5.username LIKE :input) AND " . get_vis_query('auth_user_md5', 'search') .
" ORDER BY Nachname ASC, Vorname ASC";
+ return $sql;
case "Seminar_id":
return "SELECT seminare.Seminar_id, CONCAT_WS(' ', seminare.VeranstaltungsNummer, seminare.Name, ".$semester.") " .
"FROM seminare " .
diff --git a/lib/classes/sidebar/DateSelectWidget.php b/lib/classes/sidebar/DateSelectWidget.php
new file mode 100644
index 0000000..df34165
--- /dev/null
+++ b/lib/classes/sidebar/DateSelectWidget.php
@@ -0,0 +1,51 @@
+<?php
+
+class DateSelectWidget extends SidebarWidget
+{
+ protected $date = null;
+ protected $calendar_control = false;
+
+ public function __construct()
+ {
+ $this->template = 'sidebar/date-select-widget';
+ $this->date = new DateTime();
+ parent::__construct();
+ }
+
+ public function setCalendarControl(bool $calendar_control = false) : void
+ {
+ $this->calendar_control = $calendar_control;
+ }
+
+ public function setDate(DateTime $date) : void
+ {
+ $this->date = $date;
+ }
+
+ public function getDate() : ?DateTime
+ {
+ return $this->date;
+ }
+
+ public function getCalendarControlStatus() : bool
+ {
+ return $this->calendar_control;
+ }
+
+ public function render($variables = []) : string
+ {
+ $template = $GLOBALS['template_factory']->open($this->template);
+ $template->set_attributes($variables + $this->template_variables);
+ $template->set_attribute('title', _('Datum auswählen'));
+ $template->set_attribute('date', $this->date);
+ $template->set_attribute('calendar_control', $this->calendar_control);
+
+ if ($this->layout) {
+ $layout = $GLOBALS['template_factory']->open($this->layout);
+ $layout->layout_css_classes = $this->layout_css_classes;
+ $template->set_layout($layout);
+ }
+
+ return $template->render();
+ }
+}
diff --git a/lib/exceptions/FeatureDisabledException.php b/lib/exceptions/FeatureDisabledException.php
new file mode 100644
index 0000000..16af0bf
--- /dev/null
+++ b/lib/exceptions/FeatureDisabledException.php
@@ -0,0 +1,11 @@
+<?php
+class FeatureDisabledException extends StudipException
+{
+ public function __construct($message = '', $code = 0, Exception $previous = null)
+ {
+ if (func_num_args() === 0) {
+ $message = _('Diese Funktion ist ausgeschaltet.');
+ }
+ parent::__construct($message, [], $code, $previous);
+ }
+}
diff --git a/lib/models/CalendarEvent.class.php b/lib/models/CalendarEvent.class.php
deleted file mode 100644
index 16ca91a..0000000
--- a/lib/models/CalendarEvent.class.php
+++ /dev/null
@@ -1,1394 +0,0 @@
-<?php
-/**
- * EventRange.class.php - model class for table calendar_event
- *
- * 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>
- * @copyright 2014 Stud.IP Core-Group
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- *
- * @property array $id alias for pk
- * @property string $range_id database column
- * @property string $event_id database column
- * @property int $group_status database column
- * @property int $mkdate database column
- * @property int $chdate database column
- * @property SimpleORMapCollection|CalendarEvent[] $attendees has_many CalendarEvent
- * @property SimpleORMapCollection|ConsultationEvent[] $consultation_events has_many ConsultationEvent
- * @property User $user belongs_to User
- * @property Course $course belongs_to Course
- * @property Institute $institute belongs_to Institute
- * @property ConsultationBooking $consultation_booking belongs_to ConsultationBooking
- * @property EventData $event has_one EventData
- * @property mixed $type additional field
- * @property mixed $name additional field
- * @property mixed $author_id additional field
- * @property mixed $editor_id additional field
- * @property mixed $title additional field
- * @property mixed $start additional field
- * @property mixed $end additional field
- * @property-read mixed $owner additional field
- */
-class CalendarEvent extends SimpleORMap implements Event, PrivacyObject
-{
- const PARTSTAT_TENTATIVE = 1;
- const PARTSTAT_ACCEPTED = 2;
- const PARTSTAT_DECLINED = 3;
- const PARTSTAT_DELEGATED = 4;
- const PARTSTAT_NEEDS_ACTION = 5;
-
- protected static function configure($config = [])
- {
- $config['db_table'] = 'calendar_event';
-
- $config['belongs_to']['user'] = [
- 'class_name' => User::class,
- 'foreign_key' => 'range_id',
- ];
- $config['belongs_to']['course'] = [
- 'class_name' => Course::class,
- 'foreign_key' => 'range_id',
- ];
- $config['belongs_to']['institute'] = [
- 'class_name' => Institute::class,
- 'foreign_key' => 'range_id',
- ];
- $config['has_one']['event'] = [
- 'class_name' => EventData::class,
- 'foreign_key' => 'event_id',
- 'assoc_foreign_key' => 'event_id',
- 'on_delete' => 'delete',
- 'on_store' => 'store'
- ];
- $config['has_many']['attendees'] = [
- 'class_name' => CalendarEvent::class,
- 'foreign_key' => 'event_id',
- 'assoc_foreign_key' => 'event_id'
- ];
- $config['belongs_to']['consultation_booking'] = [
- 'class_name' => ConsultationBooking::class,
- 'foreign_key' => 'event_id',
- 'assoc_foreign_key' => 'student_event_id',
- ];
- $config['has_many']['consultation_events'] = [
- 'class_name' => ConsultationEvent::class,
- 'foreign_key' => 'event_id',
- 'assoc_foreign_key' => 'event_id',
- ];
- $config['additional_fields']['type'] = true;
- $config['additional_fields']['name'] = true;
- $config['additional_fields']['author_id'] = true;
- $config['additional_fields']['editor_id'] = true;
- $config['additional_fields']['title'] = true;
- $config['additional_fields']['start'] = true;
- $config['additional_fields']['end'] = true;
- $config['additional_fields']['owner']['get'] = 'getOwner';
-
- $config['registered_callbacks']['after_delete'][] = function ($event) {
- if ($event->consultation_booking) {
- $event->consultation_booking->student_event_id = null;
- $event->consultation_booking->store();
- }
- $event->consultation_events->delete();
- };
-
- parent::configure($config);
- }
-
- private $properties = null;
- private $permission_user_id = null;
-
- /**
- * Returns the owner of this event as an object of type User, Course
- * or Institute.
- *
- * @return object
- */
- public function getOwner()
- {
- if ($this->user) {
- return $this->user;
- } else if ($this->course) {
- return $this->course;
- } else if ($this->institute) {
- return $this->institute;
- }
- return null;
- }
-
- public static function deleteBySQL($where, $params = [])
- {
- $ret = parent::deleteBySQL($where, $params);
- EventData::garbageCollect();
- return $ret;
- }
-
- /**
- * Finds calendar events by the uid of the event data.
- *
- * @param string $uid The global unique id of this event.
- * @return null|CalendarEvent The calendar event, an array of calendar events or null.
- */
- public static function findByUid($uid, $range_id = null)
- {
- $event_data = EventData::findOneByuid($uid);
- if ($event_data) {
- if ($range_id) {
- return self::find([$range_id, $event_data->getId()]);
- }
- return self::findByevent_id($event_data->getId());
- }
- return null;
- }
-
- /**
- * Keeps the event data
- */
- public function __clone()
- {
- if (is_object($this->event)) {
- $event = clone $this->event;
- parent::__clone();
- $this->event = $event;
- } else {
- parent::__clone();
- }
- }
-
- /**
- * Returns a list of all categories the event belongs to.
- * Returns an empty string if no permission.
- *
- * @return string All categories as list.
- */
- public function toStringCategories($as_array = false)
- {
- global $PERS_TERMIN_KAT;
-
- $categories = [];
- if ($this->havePermission(Event::PERMISSION_READABLE,
- $this->permission_user_id)) {
- if ($this->event->categories) {
- $categories = array_map('trim', explode(',', $this->event->categories));
- }
- if ($this->event->category_intern) {
- array_unshift($categories,
- $PERS_TERMIN_KAT[$this->event->category_intern]['name']);
- }
- }
- return $as_array ? $categories : implode(', ', $categories);
- }
-
- /**
- * Returns the name of the group status.
- * Returns an empty string status is unknown.
- *
- * @return string All categories as list.
- */
- public function toStringGroupStatus($status = null)
- {
- if (is_null($status)) {
- $status = $this->group_status;
- }
- switch ($status) {
- case CalendarEvent::PARTSTAT_TENTATIVE :
- return _('Abwartend');
- case CalendarEvent::PARTSTAT_ACCEPTED :
- return _('Angenommen');
- case CalendarEvent::PARTSTAT_DECLINED :
- return _('Abgelehnt');
- case CalendarEvent::PARTSTAT_DELEGATED :
- return _('Angenommen (keine Teilnahme)');
- }
- return '';
- }
-
- /**
- * Returns all values that defines a recurrence rule or a single value
- * named by $index.
- *
- * @param string $index Name of the value to retrieve (optional).
- * @return string|array The value(s) of the recurrence rule.
- * @throws InvalidArgumentException
- */
- public function getRecurrence($index = null)
- {
- $recurrence = [
- 'ts' => $this->event->ts ?: mktime(12, 0, 0, date('n', $this->getStart()), date('j',
- $this->getStart()), date('Y', $this->getStart())),
- 'linterval' => $this->event->linterval,
- 'sinterval' => $this->event->sinterval,
- 'wdays' => $this->event->wdays,
- 'month' => $this->event->month,
- 'day' => $this->event->day,
- 'rtype' => $this->event->rtype ?: 'SINGLE',
- 'duration' => $this->event->duration,
- 'count' => $this->event->count,
- 'expire' => $this->event->expire ?: Calendar::CALENDAR_END
- ];
- if ($index) {
- if (in_array($index, array_keys($recurrence))) {
- return $recurrence[$index];
- } else {
- throw new InvalidArgumentException('CalendarEvent::getRecurrence '
- . $index . ' is not a field in the recurrence rule.');
- }
- }
- return $recurrence;
- }
-
- /**
- *
- * TODO should throw an exception if input values are wrong
- *
- * @param array $r_rule
- * @return array|false The values of the recurrence rule.
- */
- function setRecurrence($r_rule)
- {
- $start = $this->getStart();
- $end = $this->getEnd();
- $duration = (int) ((mktime(12, 0, 0, date('n', $end),
- date('j', $end), date('Y', $end))
- - mktime(12, 0, 0, date('n', $start),
- date('j', $start), date('Y', $start))) / 86400);
- if (!isset($r_rule['count'])) {
- $r_rule['count'] = 0;
- }
-
- switch ($r_rule['rtype']) {
- case 'SINGLE':
- $ts = mktime(12, 0, 0, date('n', $start),
- date('j', $start), date('Y', $start));
- $rrule = [$ts, 0, 0, '', 0, 0, 'SINGLE', $duration];
- break;
- case 'DAILY':
- $r_rule['linterval'] = $r_rule['linterval'] ? intval($r_rule['linterval']) : 1;
- $ts = mktime(12, 0, 0, date('n', $start),
- date('j', $start) + $r_rule['linterval'], date('Y', $start));
- if ($r_rule['count']) {
- $r_rule['expire'] = mktime(23, 59, 59, date('n', $start), date('j', $start)
- + ($r_rule['count'] - 1) * $r_rule['linterval'], date('Y', $start));
- }
- $rrule = [$ts, $r_rule['linterval'], 0, '', 0, 0, 'DAILY', $duration];
- break;
- case 'WEEKLY':
- $r_rule['linterval'] = $r_rule['linterval'] ? intval($r_rule['linterval']) : 1;
- if (!$r_rule['wdays']) {
- $ts = mktime(12, 0, 0, date('n', $start), date('j', $start) +
- ($r_rule['linterval'] * 7 - (strftime('%u', $start) - 1)),
- date('Y', $start));
- if ($r_rule['count']) {
- $r_rule['expire'] = mktime(23, 59, 59, date('n', $start),
- date('j', $start) + ($r_rule['linterval'] * 7 * ($r_rule['count'] - 1)),
- date('Y', $start));
- }
- $rrule = [$ts, $r_rule['linterval'], 0, strftime('%u', $start),
- 0, 0, 'WEEKLY', $duration];
- } else {
- $ts = mktime(12, 0, 0, date('n', $start),
- date('j', $start) + (7 - (strftime('%u', $start) - 1))
- - ((strftime('%u', $start) <= substr($r_rule['wdays'], -1)) ? 7 : 0),
- date('Y', $start));
-
- if ($r_rule['count']) {
- $dt_ts = DateTime::createFromFormat('U', $ts);
-
- // max. length of selected week days must not exceed
- // number of recurrences
- $r_rule['wdays'] = substr($r_rule['wdays'], 0, $r_rule['count']);
-
- $start_wday = date('N', $start);
- $count_first_week = 0;
- for ($i = 0; $i < strlen($r_rule['wdays']); $i++) {
- if (isset($r_rule['wdays'][$i]) && $r_rule['wdays'][$i] >= $start_wday) {
- $count_first_week++;
- }
- }
-
- $count_first_week += (date('N', $start) < $r_rule['wdays'][0]) ? 1 : 0;
-
- $count_complete = $r_rule['count'] - $count_first_week;
- $weeks_max = floor($count_complete / strlen($r_rule['wdays']));
-
- $dt_expire = $dt_ts->add(new DateInterval('P' . ($weeks_max + 1) . 'W'));
- $count_last_week = $count_complete % strlen($r_rule['wdays']);
- if ($count_last_week && isset($r_rule['wdays'][$count_last_week - 1])) {
- $last_wday = $r_rule['wdays'][$count_last_week - 1];
- $dt_expire = $dt_expire->add(new DateInterval('P' . ($last_wday - 1) . 'D'));
- } else {
- $dt_expire = $dt_expire->sub(new DateInterval('P1D'));
- }
-
- $expire_ts = $dt_expire->format('U');
- $r_rule['expire'] = mktime(23, 59, 59, date('n', $expire_ts),
- date('j', $expire_ts), date('Y', $expire_ts));
- }
- $rrule = [$ts, $r_rule['linterval'], 0, $r_rule['wdays'],
- 0, 0, 'WEEKLY', $duration];
- }
- break;
- case 'MONTHLY':
- if ($r_rule['month']) {
- return false;
- }
- $r_rule['linterval'] = $r_rule['linterval'] ? intval($r_rule['linterval']) : 1;
- if (!$r_rule['day'] && !$r_rule['sinterval'] && !$r_rule['wdays']) {
- $amonth = date('n', $start) + $r_rule['linterval'];
- $ts = mktime(12, 0, 0, $amonth, date('j', $start), date('Y', $start));
- $rrule = [$ts, $r_rule['linterval'], 0, '', 0,
- date('j', $start), 'MONTHLY', $duration];
- } else if (!$r_rule['sinterval'] && !$r_rule['wdays']) {
- if ($r_rule['day'] < date('j', $start)) {
- $amonth = date('n', $start) + $r_rule['linterval'];
- } else {
- $amonth = date('n', $start);
- }
- $ts = mktime(12, 0, 0, $amonth, $r_rule['day'], date('Y', $start));
- $rrule = [$ts, $r_rule['linterval'], 0, '', 0,
- $r_rule['day'], 'MONTHLY', $duration];
- } else if (!$r_rule['day']) {
- $amonth = date('n', $start);
- $adate = mktime(12, 0, 0, $amonth, 1,
- date('Y', $start)) + ($r_rule['sinterval'] - 1) * 604800;
- $awday = strftime('%u', $adate);
- $adate -= ( $awday - $r_rule['wdays']) * 86400;
- if ($r_rule['sinterval'] == 5) {
- if (date('j', $adate) < 10) {
- $adate -= 604800;
- }
- if (date('n', $adate) == date('n', $adate + 604800)) {
- $adate += 604800;
- }
- } else if ($awday > $r_rule['wdays']) {
- $adate += 604800;
- }
- if (date('Ymd', $adate) < date('Ymd', $start)) {
- $amonth = date('n', $start) + $r_rule['linterval'];
- $adate = mktime(12, 0, 0, $amonth, 1,
- date('Y', $start)) + ($r_rule['sinterval'] - 1) * 604800;
- $awday = strftime('%u', $adate);
- $adate -= ( $awday - $r_rule['wdays']) * 86400;
- if ($r_rule['sinterval'] == 5) {
- if (date('j', $adate) < 10) {
- $adate -= 604800;
- }
- if (date('n', $adate) == date('n', $adate + 604800)) {
- $adate += 604800;
- }
- } else if ($awday > $r_rule['wdays']) {
- $adate += 604800;
- }
- }
- $ts = $adate;
- $rrule = [$ts, $r_rule['linterval'], $r_rule['sinterval'],
- $r_rule['wdays'], 0, 0, 'MONTHLY', $duration];
- }
-
- if ($r_rule['count']) {
- $r_rule['expire'] = mktime(23, 59, 59, date('n', $ts) + $r_rule['linterval']
- * ($r_rule['count'] - 1), date('j', $ts), date('Y', $ts));
- }
- break;
- case 'YEARLY':
- if (!$r_rule['month'] && !$r_rule['day'] && !$r_rule['sinterval'] && !$r_rule['wdays']) {
- $ts = mktime(12, 0, 0, date('n', $start),
- date('j', $start), date('Y', $start) + 1);
- $rrule = [$ts, 1, 0, '', date('n', $start),
- date('j', $start), 'YEARLY', $duration];
- } else if (!$r_rule['sinterval'] && !$r_rule['wdays']) {
- if (!$r_rule['day']) {
- $r_rule['day'] = date('j', $start);
- }
- $ts = mktime(12, 0, 0, $r_rule['month'], $r_rule['day'],
- date('Y', $start));
- if ($ts <= mktime(12, 0, 0, date('n', $start), date('j', $start), date('Y', $start))) {
- $ts = mktime(12, 0, 0, $r_rule['month'], $r_rule['day'],
- date('Y', $start) + 1);
- }
- $rrule = [$ts, 1, 0, '', $r_rule['month'],
- $r_rule['day'], 'YEARLY', $duration];
- } else if (!$r_rule['day']) {
- $ayear = date('Y', $start);
- do {
- $adate = mktime(12, 0, 0, $r_rule['month'],
- 1 + ($r_rule['sinterval'] - 1) * 7, $ayear);
- $aday = strftime('%u', $adate);
- $adate -= ( $aday - $r_rule['wdays']) * 86400;
- if ($r_rule['sinterval'] == 5) {
- if (date('j', $adate) < 10) {
- $adate -= 604800;
- }
- if (date('n', $adate) == date('n', $adate + 604800)) {
- $adate += 604800;
- }
- } else if ($aday > $r_rule['wdays']) {
- $adate += 604800;
- }
- $ts = $adate;
- $ayear++;
- } while ($ts <= mktime(12, 0, 0, date('n', $start), date('j', $start), date('Y', $start)));
- $rrule = [$ts, 1, $r_rule['sinterval'], $r_rule['wdays'],
- $r_rule['month'], 0, 'YEARLY', $duration];
- }
-
- if ($r_rule['count']) {
- $r_rule['expire'] = mktime(23, 59, 59, date('n', $ts),
- date('j', $ts), date('Y', $ts) + $r_rule['count'] - 1);
- }
- break;
- default :
- $ts = mktime(12, 0, 0, date('n', $start),
- date('j', $start), date('Y', $start));
- $rrule = [$ts, 0, 0, '', 0, 0, 'SINGLE', $duration];
- $r_rule['count'] = 0;
- }
-
- if (!$r_rule['expire'] || $r_rule['expire'] > Calendar::CALENDAR_END) {
- $r_rule['expire'] = Calendar::CALENDAR_END;
- }
- $this->event->ts = $rrule[0];
- $this->event->linterval = $rrule[1];
- $this->event->sinterval = $rrule[2];
- $this->event->wdays = $rrule[3];
- $this->event->month = $rrule[4];
- $this->event->day = $rrule[5];
- $this->event->rtype = $rrule[6];
- $this->event->duration = $rrule[7];
- $this->event->count = $r_rule['count'];
- $this->event->expire = $r_rule['expire'];
-
- return $r_rule;
- }
-
- /**
- * Returns a string representation of the recurrence rule.
- * If $only_type is true returns only the type of the recurrence.
- *
- * @param bool $only_type If true returns only the type of recurrence.
- * @return string The recurrence rule - human readable
- */
- public function toStringRecurrence($only_type = false)
- {
- $rrule = $this->getRecurrence();
- $replace = [_('Montag') . ', ', _('Dienstag') . ', ', _('Mittwoch') . ', ',
- _('Donnerstag') . ', ', _('Freitag') . ', ', _('Samstag') . ', ', _('Sonntag') . ', '];
- $search = ['1', '2', '3', '4', '5', '6', '7'];
- $wdays = str_replace($search, $replace, $rrule['wdays']);
- $wdays = mb_substr($wdays, 0, -2);
-
- switch ($rrule['rtype']) {
- case 'DAILY':
- if ($rrule['linterval'] > 1) {
- $type = 'xdaily';
- $text = sprintf(_('Der Termin wird alle %s Tage wiederholt.'),
- $rrule['linterval']);
- } else {
- $type = 'daily';
- $text = _('Der Termin wird täglich wiederholt');
- }
- break;
- case 'WEEKLY':
- if ($rrule['linterval'] > 1) {
- $type = 'xweek_wdaily';
- $text = sprintf(_('Der Termin wird alle %s Wochen am %s wiederholt.'),
- $rrule['linterval'], $wdays);
- } else {
- if ($rrule['wdays'] = '12345') {
- $type = 'workdaily';
- } else {
- $type = 'wdaily';
- }
- $text = sprintf(_('Der Termin wird jeden %s wiederholt.'), $wdays);
- }
- break;
- case 'MONTHLY':
- if ($rrule['linterval'] > 1) {
- if ($rrule['day']) {
- $type = 'mday_xmonthly';
- $text = sprintf(_('Der Termin wird am %s. alle %s Monate wiederholt.'),
- $rrule['day'], $rrule['linterval']);
- } else {
- if ($rrule['sinterval'] != '5') {
- $type = 'xwday_xmonthly';
- $text = sprintf(_('Der Termin wird jeden %s. %s alle %s Monate wiederholt.'),
- $rrule['sinterval'], $wdays, $rrule['linterval']);
- } else {
- $type = 'lastwday_xmonthly';
- $text = sprintf(_('Der Termin wird jeden letzten %s alle %s Monate wiederholt.'),
- $wdays, $rrule['linterval']);
- }
- }
- } else {
- if ($rrule['day']) {
- $type = 'mday_monthly';
- $text = sprintf(_('Der Termin wird am %s. jeden Monat wiederholt.'),
- $rrule['day'], $rrule['linterval']);
- } else {
- if ($rrule['sinterval'] != '5') {
- $type = 'xwday_monthly';
- $text = sprintf(_('Der Termin wird am %s. %s jeden Monat wiederholt.'),
- $rrule['sinterval'], $wdays, $rrule['linterval']);
- } else {
- $type = 'lastwday_monthly';
- $text = sprintf(_('Der Termin wird jeden letzten %s jeden Monat wiederholt.'),
- $wdays, $rrule['linterval']);
- }
- }
- }
- break;
- case 'YEARLY':
- $month_names = [_('Januar'), _('Februar'), _('März'), _('April'), _('Mai'),
- _('Juni'), _('Juli'), _('August'), _('September'), _('Oktober'),
- _('November'), _('Dezember')];
- if ($rrule['day']) {
- $type = 'mday_month_yearly';
- $text = sprintf(_('Der Termin wird jeden %s. %s wiederholt.'),
- $rrule['day'], $month_names[$rrule['month'] - 1]);
- } else {
- if ($rrule['sinterval'] != '5') {
- $type = 'xwday_month_yearly';
- $text = sprintf(_('Der Termin wird jeden %s. %s im %s wiederholt.'),
- $rrule['sinterval'], $wdays, $month_names[$rrule['month'] - 1]);
- } else {
- $type = 'lastwday_month_yearly';
- $text = sprintf(_('Der Termin wird jeden letzten %s im %s wiederholt.'),
- $wdays, $month_names[$rrule['month'] - 1]);
- }
- }
- break;
- default:
- $type = 'single';
- $text = _('Der Termin wird nicht wiederholt.');
- }
- return $only_type ? $type : $text;
- }
-
- /**
- * Returns the priority in a human readable form.
- * If the user has no permission an epmty string will be returned.
- *
- * @return string The priority as a string.
- */
- public function toStringPriority()
- {
- if (!$this->havePermission(Event::PERMISSION_READABLE,
- $this->permission_user_id)) {
- return '';
- }
- switch ($this->event->priority) {
- case 1:
- return _('Hoch');
- case 2:
- return _('Mittel');
- case 3:
- return _('Niedrig');
- default:
- return _('Keine Angabe');
- }
- }
-
- /**
- * Returns the accessibilty in a human readable form.
- * If the user has no permission an epmty string will be returned.
- *
- * @return string The accessibility as string.
- */
- public function toStringAccessibility()
- {
- if ($this->havePermission(Event::PERMISSION_READABLE,
- $this->permission_user_id)) {
- switch ($this->event->class) {
- case 'PUBLIC':
- return _('Öffentlich');
- case 'CONFIDENTIAL':
- return _('Vertraulich');
- default:
- return _('Privat');
- }
- }
- return '';
- }
-
- /**
- * Returns the exceptions as array of unix timestamps.
- *
- * @return array Array of unix timestamps.
- */
- public function getExceptions()
- {
- $exceptions = [];
- if (trim($this->event->exceptions)) {
- $exceptions = explode(',', $this->event->exceptions);
- }
- return $exceptions;
- }
-
- /**
- * Sets proper timestamps as exceptions for given unix timestamps.
- *
- * @param array $exceptions Array of exceptions as unix timestamps.
- */
- public function setExceptions($exceptions)
- {
- $exc = [];
- if (is_array($exceptions)) {
- $exc = array_map(function ($exception) {
- $exception = intval($exception);
- return mktime(12, 0, 0, date('n', $exception),
- date('j', $exception), date('Y', $exception));
- }, $exceptions);
- }
- $this->event->exceptions = implode(',', $exc);
- }
-
- /**
- * Returns the title of this event.
- * If the user has not the permission Event::PERMISSION_READABLE,
- * the title is "Keine Berechtigung.".
- *
- * @return string
- */
- public function getTitle()
- {
- if (!$this->havePermission(Event::PERMISSION_READABLE,
- $this->permission_user_id)) {
- return _('Keine Berechtigung.');
- }
- if ($this->event->summary == '') {
- return _('Kein Titel');
- }
- return $this->event->summary;
- }
-
- /**
- * Sets the title of this event.
- *
- * @param type $title The title of this event.
- */
- public function setTitle($title)
- {
- $this->event->summary = $title;
- }
-
- /**
- * Returns the starttime as unix timestamp of this event.
- *
- * @return int The starttime of this event as a unix timestamp
- */
- public function getStart()
- {
- return $this->event->start;
- }
-
- /**
- * Sets the start date time with given unix timestamp.
- *
- * @param string $timestamp Unix timestamp.
- */
- public function setStart($timestamp)
- {
- $this->event->start = $timestamp;
- }
-
- /**
- * Returns the endtime as unix timestamp of this event.
- *
- * @return int the endtime of this event as a unix timestamp
- */
- public function getEnd()
- {
- return $this->event->end;
- }
-
- /**
- * Sets the end date time by given unix timestamp.
- *
- * @param string $timestamp Unix timestamp.
- */
- public function setEnd($timestamp)
- {
- $this->event->end = $timestamp;
- }
-
- /**
- * Returns the user id of the author.
- *
- * @return string User id of the author.
- */
- public function getAuthorId()
- {
- return $this->event->author_id;
- }
-
- /**
- * Sets the author by given user id.
- *
- * @param string $author_id User id of the author.
- */
- public function setAuthorId($author_id)
- {
- $this->event->author_id = $author_id;
- }
-
- /**
- * Sets the editor id by given user id.
- *
- * @param string $editor_id User id of the editor.
- */
- public function setEditorId($editor_id)
- {
- $this->event->editor_id = $editor_id;
- }
-
- /**
- * Returns the duration of this event in seconds.
- *
- * @return int the duration of this event in seconds
- */
- function getDuration()
- {
- return $this->event->end - $this->event->start;
- }
-
- /**
- * Returns the location.
- * Without permission or the location is not set an empty string is returned.
- *
- * @return string The location
- */
- public function getLocation()
- {
- $location = '';
- if ($this->havePermission(Event::PERMISSION_READABLE,
- $this->permission_user_id)) {
- if (trim($this->event->location) != '') {
- $location = $this->event->location;
- }
- }
- return $location;
- }
-
- /**
- * Returns the global unique id of this event.
- *
- * @return string The global unique id.
- */
- public function getUid()
- {
- return $this->event->uid !== ''
- ? $this->event->uid
- : 'Stud.IP-' . $this->event_id . '@' . $_SERVER['SERVER_NAME'];
- }
-
- /**
- * Returns the description of the topic.
- * If the user has no permission or the event has no topic
- * or the topics have no descritopn an empty string is returned.
- *
- * @return String the description
- */
- public function getDescription()
- {
- $description = '';
- if ($this->havePermission(Event::PERMISSION_READABLE,
- $this->permission_user_id)) {
- $description = trim($this->event->description);
- }
- return $description;
- }
-
- /**
- * Returns the index of the category.
- * If the user has no permission, 255 is returned.
- *
- * @see config/config.inc.php $TERMIN_TYP
- * @return int The index of the category
- */
- public function getCategory()
- {
- global $PERS_TERMIN_KAT;
-
- $category = 0;
- if ($this->havePermission(Event::PERMISSION_READABLE,
- $this->permission_user_id)) {
- if ($this->event->category_intern) {
- $category = $this->event->category_intern;
- }
-
- if ($category == 0 && trim($this->event->categories)) {
- $categories = [];
- $i = 1;
- foreach ($PERS_TERMIN_KAT as $pers_cat) {
- $categories[mb_strtolower($pers_cat['name'])] = $i++;
- }
- $cat_event = explode(',', $this->event->categories);
- foreach ($cat_event as $cat) {
- $index = mb_strtolower(trim($cat));
- if ($categories[$index]) {
- $category = $categories[$index];
- break;
- }
- }
- }
- } else {
- $category = 255;
- }
- return $category;
- }
-
- /**
- * Returns a csv list of categories. If no categories are stated or the user
- * has no permission an empty string will be returned.
- *
- * @return string csv list of categories or empty string
- */
- public function getUserDefinedCategories()
- {
- if ($this->havePermission(Event::PERMISSION_READABLE,
- $this->permission_user_id)) {
- return trim((string) $this->event->categories);
- }
- return '';
- }
-
- /**
- * Stores user defined categories as a csv list.
- *
- * @param array|string $categories An array or csv list of user defined categories.
- */
- public function setUserDefinedCategories($categories)
- {
- if (!is_array($categories)) {
- $categories = explode(',', $categories);
- }
- $cat_list = implode(',', array_map('trim', $categories));
- $this->event->categories = $cat_list;
- }
-
- /**
- * Sets the accessibility (class). Possible classes are 'PUBLIC', 'PRIVATE'
- * and 'CONFIDENTIAL'.
- * If the given class is unknown, the event gets the class 'PRIVATE'.
- *
- * @param string $class The name of the class.
- */
- public function setAccessibility($class)
- {
- $class = mb_strtoupper($class);
- if (in_array($class, ['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'])) {
- $this->event->class = $class;
- } else {
- $this->event->class = 'PRIVATE';
- }
- }
-
- /**
- * Sets the priority. Possible values are
- * 0: not specified
- * 1: high
- * 2: middle
- * 3: low
- * Default is 0.
- *
- * @param int $priority The priority between 0 and 3.
- */
- public function setPriority($priority)
- {
- if ($priority >= 0 && $priority < 4)
- {
- $this->event->priority = $priority;
- } else {
- $this->event->priority = 0;
- }
- }
-
- /**
- * Returns the user id of the editor.
- *
- * @return string User id of the editor
- */
- public function getEditorId()
- {
- return $this->event->editor_id;
- }
-
- /**
- * Returns whether this event is an all day event.
- *
- * @return boolean true if all day event
- */
- public function isDayEvent()
- {
- return (date('His', $this->getStart()) == '000000' &&
- (date('His', $this->getEnd()) == '235959'
- || date('His', $this->getEnd() - 1) == '235959'));
- }
-
- /**
- * Returns the state of accessibility as string.
- * Possible values:
- * PUBLIC, PRIVATE, CONFIDENTIAL
- * The default is CONFIDENTIAL.
- *
- * @return string
- */
- public function getAccessibility()
- {
- if ($this->event->class) {
- return $this->event->class;
- }
- return 'CONFIDENTIAL';
- }
-
- /**
- * Returns an array with options for accessibility depending on the permission
- * of the given calendar permission.
- *
- * @param int $permission The calendar permission
- * @return array The accessibility options.
- */
- public function getAccessibilityOptions($permission)
- {
- switch ($permission) {
- case Calendar::PERMISSION_OWN :
- case Calendar::PERMISSION_ADMIN :
- $options = [
- // SEMBBS nur private und vertrauliche Termine
- 'PUBLIC' => _('Öffentlich'),
- 'PRIVATE' => _('Privat'),
- 'CONFIDENTIAL' => _('Vertraulich')
- ];
- break;
- case Calendar::PERMISSION_WRITABLE :
- $options = [
- 'PRIVATE' => _('Privat'),
- 'CONFIDENTIAL' => _('Vertraulich')
- ];
- break;
- default :
- $options = [];
- }
- return $options;
- }
-
- /**
- *
- * @return type
- */
- public function getChangeDate()
- {
- return $this->event->chdate;
- }
-
- /**
- *
- */
- public function getImportDate()
- {
- return $this->event->importdate;
- }
-
-
- /**
- * Returns the object type this event belongs to.
- * Possible values are 'user', 'sem', 'inst', 'fak'.
- *
- * @return string The object type.
- */
- public function getType()
- {
- return get_object_type($this->range_id, ['user', 'sem', 'inst', 'fak']);
- }
-
- /**
- * Returns the priority:
- * 0 means priority is not stated
- * 1 means "high"
- * 2 means "middle"
- * 3 means "low"
- * If the user has no permission it returns 0.
- *
- * @return int The priority.
- */
- public function getPriority()
- {
- if ($this->havePermission(Event::PERMISSION_READABLE,
- $this->permission_user_id)) {
- return $this->event->priority ?: 0;
- }
- return 0;
- }
-
- /**
- * @return string
- */
- public function getName()
- {
- switch ($this->type) {
- case 'user':
- return (string) $this->user->getFullname();
- case 'sem':
- return (string) $this->course->name;
- case 'inst':
- case 'fak':
- return (string) $this->institute->name;
- default:
- return '';
- }
- }
-
- /**
- * Returns all properties of this event.
- * The name of the properties correspond to the properties of the
- * iCalendar calendar data exchange format. There are a few properties with
- * the suffix STUDIP_ which have no eqivalent in the iCalendar format.
- *
- * DTSTART: The start date-time as unix timestamp.
- * DTEND: The end date-time as unix timestamp.
- * SUMMARY: The short description (title) that will be displayed in the views.
- * DESCRIPTION: The long description.
- * UID: The global unique id of this event.
- * CLASS:
- * CATEGORIES: A comma separated list of categories.
- * PRIORITY: The priority.
- * LOCATION: The location.
- * EXDATE: A comma separated list of unix timestamps.
- * CREATED: The creation date-time as unix timestamp.
- * LAST-MODIFIED: The date-time of last modification as unix timestamp.
- * DTSTAMP: The cration date-time of this instance of the event as unix
- * timestamp.
- * RRULE: All data for the recurrence rule for this event as array.
- * EVENT_TYPE:
- *
- *
- * @return array The properties of this event.
- */
- public function getProperties()
- {
- if ($this->properties === null) {
- $this->properties = [
- 'DTSTART' => $this->getStart(),
- 'DTEND' => $this->getEnd(),
- 'SUMMARY' => stripslashes($this->getTitle()),
- 'DESCRIPTION' => stripslashes($this->getDescription()),
- 'UID' => $this->getUid(),
- 'CLASS' => $this->getAccessibility(),
- 'CATEGORIES' => $this->toStringCategories(),
- 'STUDIP_CATEGORY' => $this->getCategory(),
- 'PRIORITY' => $this->getPriority(),
- 'LOCATION' => stripslashes($this->getLocation()),
- 'RRULE' => $this->getRecurrence(),
- 'EXDATE' => (string) $this->event->exceptions,
- 'CREATED' => $this->event->mkdate,
- 'LAST-MODIFIED' => $this->event->chdate,
- 'STUDIP_ID' => $this->event->getId(),
- 'DTSTAMP' => time(),
- 'EVENT_TYPE' => 'cal',
- 'STUDIP_AUTHOR_ID' => $this->event->author_id,
- 'STUDIP_EDITOR_ID' => $this->event->editor_id,
- 'STUDIP_GROUP_STATUS' => $this->group_status];
- }
- return $this->properties;
- }
-
- /**
- * Returns the value of property with given name.
- *
- * @param type $name See CalendarEvent::getProperties() for accepted values.
- * @return mixed The value of the property.
- * @throws InvalidArgumentException
- */
- public function getProperty($name)
- {
- if ($this->properties === null) {
- $this->getProperties();
- }
-
- if (isset($this->properties[$name])) {
- return $this->properties[$name];
- }
- throw new InvalidArgumentException(get_class($this)
- . ': Property ' . $name . ' does not exist.');
- }
-
- /**
- * Returns all CalendarEvents in the given time range for the given range_id.
- *
- * @param string $range_id Id of Stud.IP object from type user, course, inst
- * @param DateTime $start The start date time.
- * @param DateTime $end The end date time.
- * @return SimpleORMapCollection Collection of found CalendarEvents.
- */
- public static function getEventsByInterval($range_id, DateTime $start, DateTime $end)
- {
- $query = "SELECT *
- FROM calendar_event
- INNER JOIN event_data USING (event_id)
- WHERE range_id = :range_id
- AND (
- start BETWEEN :start AND :end
- OR (
- start <= :end
- AND CAST(expire AS SIGNED) + CAST(end AS SIGNED) - CAST(start AS SIGNED) >= :start
- AND rtype != 'SINGLE'
- )
- OR :start BETWEEN start AND end
- )
- ORDER BY start ASC";
- $stmt = DBManager::get()->prepare($query);
- $stmt->execute([
- ':range_id' => $range_id,
- ':start' => $start->getTimestamp(),
- ':end' => $end->getTimestamp(),
- ]);
- $i = 0;
- $event_collection = new SimpleORMapCollection();
- $event_collection->setClassName('Event');
- foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
- $event_collection[$i] = new CalendarEvent();
- $event_collection[$i]->setData($row);
- $event_collection[$i]->setNew(false);
- $event = new EventData();
- $event->setData($row);
- $event->setNew(false);
- $event_collection[$i]->event = $event;
- $i++;
- }
- return $event_collection;
- }
-
- /**
- * Sets the user_id to check his permission.
- *
- * @param string $user_id The id of the user.
- */
- public function setPermissionUser($user_id)
- {
- $this->permission_user_id = $user_id;
- }
-
- /**
- * Checks the permission of the user previously set with
- * CalendarEvent::setPermissisonUser or given by second argument.
- * Returns true if the user have the at least the given permission.
- *
- * @param int $permission
- * @param string $user_id
- * @return boolean
- */
- public function havePermission($permission, $user_id = null)
- {
- $perm = $this->getPermission($user_id);
- return $perm >= $permission;
- }
-
- /**
- * Returns the permission of the given user or the user set by
- * CalendarEvent::setPermssionUser previously.
- *
- * @staticvar array $permissions
- * @param string $user_id The user's id.
- * @return int The permission.
- */
- public function getPermission($user_id = null)
- {
- static $permissions = [];
-
- if (is_null($user_id)) {
- $user_id = $this->permission_user_id ?: $GLOBALS['user']->id;
- }
- if (empty($permissions[$user_id][$this->event_id])) {
- if ($user_id == $this->event->author_id) {
- $permissions[$user_id][$this->event_id] = Event::PERMISSION_OWN;
- } else
-
- // SEMBBS
- // Admins dürfen alle Termine löschen
- /*
- if ($GLOBALS['perm']->have_perm('admin')) {
- $permissions[$user_id][$this->event_id] = Event::PERMISSION_DELETABLE;
- } else
- *
- */
-
- if ($user_id == $this->range_id) {
- if ($this->group_status) {
- $permissions[$user_id][$this->event_id] = Event::PERMISSION_READABLE;
- } else {
- $permissions[$user_id][$this->event_id] = Event::PERMISSION_DELETABLE;
- }
- } else {
- switch ($this->getType()) {
- case 'user':
- $permissions[$user_id][$this->event_id] =
- $this->getUserCalendarPermission($user_id);
- break;
- case 'sem':
- $permissions[$user_id][$this->event_id] =
- $this->getCourseCalendarPermission($user_id);
- break;
- case 'inst':
- case 'fak':
- $permissions[$user_id][$this->event_id] =
- $this->getInstituteCalendarPermission($user_id);
- break;
- default:
- $permissions[$user_id][$this->event_id] =
- Event::PERMISSION_FORBIDDEN;
- }
- }
- }
- return $permissions[$user_id][$this->event_id];
- }
-
- /**
- * Get the user's permission for this event in the actual calendar.
- *
- * @param string $user_id The user id.
- * @return int The permission.
- */
- private function getUserCalendarPermission($user_id)
- {
- $permission = Event::PERMISSION_FORBIDDEN;
- $accessibility = $this->getAccessibility();
- if ($this->user->id) {
- if ($user_id != $this->user->id) {
- if ($accessibility == 'PUBLIC') {
- $permission = Event::PERMISSION_READABLE;
- }
- $calendar_user = CalendarUser::find(
- [$this->user->getId(), $user_id]);
- if ($calendar_user) {
- if ($accessibility == 'CONFIDENTIAL') {
- if ($this->event->calendars->findOneBy('range_id', $user_id)) {
- if ($calendar_user->permission == Calendar::PERMISSION_WRITABLE) {
- $permission = Event::PERMISSION_WRITABLE;
- } else {
- $permission = Event::PERMISSION_READABLE;
- }
- } else {
- $permission = Event::PERMISSION_CONFIDENTIAL;
- }
- } else {
- if ($calendar_user->permission == Calendar::PERMISSION_WRITABLE) {
- $permission = Event::PERMISSION_WRITABLE;
- } else {
- $permission = Event::PERMISSION_READABLE;
- }
- }
- }
- } else {
- $permission = Event::PERMISSION_WRITABLE;
- }
- }
- return $permission;
- }
-
- /**
- * Get the user's permission for this event in the actual calendar if the
- * owner is a course.
- *
- * @param string $user_id The user's id.
- * @return int The permission.
- */
- private function getCourseCalendarPermission($user_id)
- {
- global $perm;
-
- $permission = Event::PERMISSION_FORBIDDEN;
- if ($this->course->id) {
- $course_perm = $perm->get_studip_perm($this->course->id, $user_id);
- switch ($course_perm) {
- case 'user':
- case 'autor':
- $permission = Event::PERMISSION_READABLE;
- break;
- case 'tutor':
- case 'dozent':
- case 'admin':
- $permission = Event::PERMISSION_WRITABLE;
- break;
- default:
- $permission = Event::PERMISSION_FORBIDDEN;
- }
- }
- return $permission;
- }
-
- /**
- * Get the user's permission for this event in the actual calendar if the
- * owner is an institute.
- *
- * @param string $user_id The user's id.
- * @return int The permssion.
- */
- private function getInstituteCalendarPermission($user_id)
- {
- global $perm;
- $permission = Event::PERMISSION_FORBIDDEN;
- if ($this->institute->id) {
- $institute_perm = $perm->get_studip_perm($this->institute->id, $user_id);
- switch ($institute_perm) {
- case 'user';
- case 'autor':
- $permission = Event::PERMISSION_READABLE;
- break;
- case 'tutor':
- case 'dozent':
- case 'admin':
- $permission = Event::PERMISSION_WRITABLE;
- break;
- default:
- $permission = Event::PERMISSION_FORBIDDEN;
- }
- }
- return $permission;
- }
-
- /**
- * Returns the user id of the event's author.
- *
- * @return string The user id of the author.
- */
- public function getAuthor()
- {
- return $this->event->author;
- }
-
- /**
- * Returns teh user id of the event's last editor.
- *
- * @return string The uder id og the editor.
- */
- public function getEditor()
- {
- return $this->event->editor;
- }
-
- /**
- * 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 = CalendarEvent::findBySQL("range_id = ?", [$storage->user_id]);
- if ($sorm) {
- $field_data = [];
- foreach ($sorm as $row) {
- $field_data[] = $row->toRawArray();
- }
- if ($field_data) {
- $storage->addTabularData(_('Kalender'), 'calendar_event', $field_data);
- }
- }
- }
-
-}
diff --git a/lib/models/CalendarUser.class.php b/lib/models/CalendarUser.class.php
deleted file mode 100644
index f079ac6..0000000
--- a/lib/models/CalendarUser.class.php
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-/**
- * CalendarUser.class.php - Model for users with access to other users 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>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @since 3.2
- *
- * @property array $id alias for pk
- * @property string $owner_id database column
- * @property string $user_id database column
- * @property int $permission database column
- * @property int $mkdate database column
- * @property int $chdate database column
- * @property User $user belongs_to User
- * @property User $owner has_one User
- * @property-read mixed $nachname additional field
- * @property-read mixed $vorname additional field
- */
-
-class CalendarUser extends SimpleORMap
-{
- protected static function configure($config = [])
- {
- $config['db_table'] = 'calendar_user';
-
- $config['has_one']['owner'] = [
- 'class_name' => User::class,
- 'foreign_key' => 'owner_id',
- 'assoc_foreign_key' => 'user_id'
- ];
- $config['belongs_to']['user'] = [
- 'class_name' => User::class,
- 'foreign_key' => 'user_id'
- ];
-
- $config['additional_fields']['nachname']['get'] = function ($cu) {
- return $cu->user->nachname;
- };
- $config['additional_fields']['vorname']['get'] = function ($cu) {
- return $cu->user->vorname;
- };
-
- parent::configure($config);
- }
-
- public function setPerm($permission)
- {
- if ($permission == Calendar::PERMISSION_READABLE) {
- $this->permission = Calendar::PERMISSION_READABLE;
- } else if ($permission == Calendar::PERMISSION_WRITABLE) {
- $this->permission = Calendar::PERMISSION_WRITABLE;
- } else {
- throw new InvalidArgumentException(
- 'Calendar permission must be of type PERMISSION_READABLE or PERMISSION_WRITABLE.');
- }
- }
-
- public static function getUsers($user_id, $permission = null)
- {
- $permission_array = [Calendar::PERMISSION_READABLE,
- Calendar::PERMISSION_WRITABLE];
- if (!$permission) {
- $permission = $permission_array;
- } else if (!in_array($permission, $permission_array)) {
- throw new InvalidArgumentException(
- 'Calendar permission must be of type PERMISSION_READABLE or PERMISSION_WRITABLE.');
- } else {
- $permission = [$permission];
- }
- return SimpleORMapCollection::createFromArray(CalendarUser::findBySQL(
- 'owner_id = ? AND permission IN(?)',
- [$user_id, $permission]));
-
- }
-
- public static function getOwners($user_id, $permission = null)
- {
- $permission_array = [Calendar::PERMISSION_READABLE,
- Calendar::PERMISSION_WRITABLE];
- if (!$permission) {
- $permission = $permission_array;
- } else if (!in_array($permission, $permission_array)) {
- throw new InvalidArgumentException(
- 'Calendar permission must be of type PERMISSION_READABLE or PERMISSION_WRITABLE.');
- } else {
- $permission = [$permission];
- }
- $statement = DBManager::get()->prepare("
- SELECT *
- FROM calendar_user
- INNER JOIN auth_user_md5 ON (auth_user_md5.user_id = calendar_user.owner_id)
- WHERE calendar_user.user_id = :user_id
- AND calendar_user.permission IN (:permission)
- ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname
- ");
- $statement->execute([
- 'user_id' => $user_id,
- 'permission' => $permission
- ]);
- $calendar_users = [];
- foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $data) {
- $calendar_users[] = CalendarUser::buildExisting($data);
- }
- return SimpleORMapCollection::createFromArray($calendar_users);
-
- }
-}
diff --git a/lib/models/ConsultationBooking.php b/lib/models/ConsultationBooking.php
index 06e6967..7782681 100644
--- a/lib/models/ConsultationBooking.php
+++ b/lib/models/ConsultationBooking.php
@@ -16,7 +16,7 @@
* @property int $chdate database column
* @property ConsultationSlot $slot belongs_to ConsultationSlot
* @property User $user belongs_to User
- * @property EventData|null $event has_one EventData
+ * @property CalendarDate $event has_one CalendarDate
*/
class ConsultationBooking extends SimpleORMap implements PrivacyObject
{
@@ -37,9 +37,9 @@ class ConsultationBooking extends SimpleORMap implements PrivacyObject
'foreign_key' => 'user_id',
];
$config['has_one']['event'] = [
- 'class_name' => EventData::class,
+ 'class_name' => CalendarDate::class,
'foreign_key' => 'student_event_id',
- 'assoc_foreign_key' => 'event_id',
+ 'assoc_foreign_key' => 'id',
'on_delete' => 'delete',
];
@@ -48,8 +48,8 @@ class ConsultationBooking extends SimpleORMap implements PrivacyObject
setTempLanguage($booking->user_id);
$event = $booking->slot->createEvent($booking->user);
- $event->category_intern = 1;
- $event->summary = sprintf(
+ $event->category = 1;
+ $event->title = sprintf(
_('Termin bei %s'),
$booking->slot->block->range->getFullName()
);
diff --git a/lib/models/ConsultationEvent.php b/lib/models/ConsultationEvent.php
index 2665c6b..e0b6b59 100644
--- a/lib/models/ConsultationEvent.php
+++ b/lib/models/ConsultationEvent.php
@@ -10,7 +10,7 @@
* @property string $event_id database column
* @property int $mkdate database column
* @property ConsultationSlot $slot belongs_to ConsultationSlot
- * @property EventData $event has_one EventData
+ * @property CalendarDate $event belongs_to CalendarDate
*/
class ConsultationEvent extends SimpleORMap
{
@@ -23,9 +23,9 @@ class ConsultationEvent extends SimpleORMap
'foreign_key' => 'slot_id',
];
$config['has_one']['event'] = [
- 'class_name' => EventData::class,
+ 'class_name' => CalendarDate::class,
'foreign_key' => 'event_id',
- 'assoc_foreign_key' => 'event_id',
+ 'assoc_foreign_key' => 'id',
'on_delete' => 'delete',
];
diff --git a/lib/models/ConsultationSlot.php b/lib/models/ConsultationSlot.php
index b988190..2a71fbd 100644
--- a/lib/models/ConsultationSlot.php
+++ b/lib/models/ConsultationSlot.php
@@ -166,26 +166,24 @@ class ConsultationSlot extends SimpleORMap
* Creates a Stud.IP calendar event relating to the slot.
*
* @param User $user User object to create the event for
- * @return EventData Created event
+ * @return CalendarDate Created event
*/
- public function createEvent(User $user)
+ public function createEvent(User $user) : CalendarDate
{
- $event = new EventData();
- $event->uid = $this->createEventId($user);
+ $event = new CalendarDate();
+ $event->unique_id = $this->createEventId($user);
$event->author_id = $user->id;
$event->editor_id = $user->id;
- $event->start = $this->start_time;
+ $event->begin = $this->start_time;
$event->end = $this->end_time;
- $event->class = 'PRIVATE';
- $event->priority = 0;
+ $event->access = 'PRIVATE';
$event->location = $this->block->room;
- $event->rtype = 'SINGLE';
+ $event->repetition_type = '';
$event->store();
- $calendar_event = new CalendarEvent();
- $calendar_event->range_id = $user->id;
- $calendar_event->group_status = 0;
- $calendar_event->event_id = $event->id;
+ $calendar_event = new CalendarDateAssignment();
+ $calendar_event->range_id = $user->id;
+ $calendar_event->calendar_date_id = $event->id;
$calendar_event->store();
return $event;
@@ -267,12 +265,12 @@ class ConsultationSlot extends SimpleORMap
});
if (count($bookings) > 0) {
- $event->event->category_intern = 1;
+ $event->event->category = 1;
if (count($bookings) === 1) {
$booking = $bookings->first();
- $event->event->summary = sprintf(
+ $event->event->title = sprintf(
_('Termin mit %s'),
$booking->user ? $booking->user->getFullName() : _('unbekannt')
);
@@ -288,9 +286,9 @@ class ConsultationSlot extends SimpleORMap
}));
}
} else {
- $event->event->category_intern = 9;
- $event->event->summary = _('Freier Termin');
- $event->event->description = _('Dieser Termin ist noch nicht belegt.');
+ $event->event->category = 9;
+ $event->event->title = _('Freier Termin');
+ $event->event->description = _('Dieser Termin ist noch nicht belegt.');
}
$event->event->store();
diff --git a/lib/models/Contact.class.php b/lib/models/Contact.class.php
index be095de..44d8bcd 100644
--- a/lib/models/Contact.class.php
+++ b/lib/models/Contact.class.php
@@ -10,9 +10,14 @@
* @property string $owner_id database column
* @property string $user_id database column
* @property int|null $mkdate database column
+ * @property string $calendar_permissions database column
+ * An enum with the possible values "", "READ" and "WRITE".
+ * The empty string specifies that no calendar permissions are granted.
* @property SimpleORMapCollection|StatusgruppeUser[] $group_assignments has_many StatusgruppeUser
* @property User $owner belongs_to User
* @property User $friend belongs_to User
+ * @property string $mkdate database column
+ * @property string $chdate database column
*/
class Contact extends SimpleORMap
{
@@ -29,15 +34,16 @@ class Contact extends SimpleORMap
'foreign_key' => 'user_id'
];
- $config['has_many']['group_assignments'] = [
- 'class_name' => 'StatusgruppeUser',
+ $config['has_many']['groups'] = [
+ 'class_name' => ContactGroupItem::class,
'assoc_func' => 'findByContact',
'foreign_key' => function ($me) {
return [$me];
},
- 'assoc_foreign_key' => function ($group, $params) {
- $group->setValue('user_id', $params[0]->user_id);
- },
+ 'assoc_foreign_key' => function ($item, $params) {
+ //Nothing else here. But this has to be present
+ //so that storing a new contact works.
+ },
'on_store' => 'store',
'on_delete' => 'delete'
];
diff --git a/lib/models/ContactGroup.class.php b/lib/models/ContactGroup.class.php
new file mode 100644
index 0000000..0e60d3b
--- /dev/null
+++ b/lib/models/ContactGroup.class.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * The ContactGroup class represents a contact group of a user.
+ *
+ * This file is part of Stud.IP
+ *
+ * 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 2023
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ * @package resources
+ * @since 5.5
+ *
+ * @property string $id The ID of the group.
+ * @property string $name Name of the group.
+ * @property string $owner_id The ID of the owner to whom the group belongs to.
+ * @property string $mkdate The creation date of the group.
+ * @property string $chdate The modification date of the group.
+ * @property User $owner The owner of the group.
+ * @property ContactGroupItem[]|SimpleORMapCollection $items The items (users) that belong to the group.
+ */
+class ContactGroup extends SimpleORMap
+{
+ protected static function configure($config = [])
+ {
+ $config['db_table'] = 'contact_groups';
+ $config['belongs_to']['owner'] = [
+ 'class_name' => User::class,
+ 'foreign_key' => 'owner_id'
+ ];
+ $config['has_many']['items'] = [
+ 'class_name' => ContactGroupItem::class,
+ 'assoc_foreign_key' => 'group_id',
+ 'on_store' => 'store',
+ 'on_delete' => 'delete'
+ ];
+ parent::configure($config);
+ }
+}
diff --git a/lib/models/ContactGroupItem.class.php b/lib/models/ContactGroupItem.class.php
new file mode 100644
index 0000000..0204d0a
--- /dev/null
+++ b/lib/models/ContactGroupItem.class.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * The ContactGroupItem class represents an item in a contact group.
+ *
+ * This file is part of Stud.IP
+ *
+ * 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 2023
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ * @package resources
+ * @since 5.5
+ *
+ * @property string $group_id The ID of the group.
+ * @property string $user_id The ID of the user that is inside the group.
+ * @property string $mkdate The creation date of the group.
+ * @property string $chdate The modification date of the group.
+ * @property ContactGroup $contact_group The group instance for the item.
+ * @property User $user The user instance for the item.
+ */
+class ContactGroupItem extends SimpleORMap
+{
+ protected static function configure($config = [])
+ {
+ $config['db_table'] = 'contact_group_items';
+ $config['belongs_to']['contact_group'] = [
+ 'class_name' => ContactGroup::class,
+ 'foreign_key' => 'group_id'
+ ];
+ $config['belongs_to']['user'] = [
+ 'class_name' => User::class,
+ 'foreign_key' => 'user_id'
+ ];
+ parent::configure($config);
+ }
+
+ /**
+ * Finds and returns all group items for a contact.
+ *
+ * @param Contact $contact The contact for which to find all contact group items.
+ * @return ContactGroupItem[] All memberships of the contact.
+ */
+ public static function findByContact(Contact $contact): array
+ {
+ return self::findBySQL(
+ 'JOIN `contact_groups`
+ ON (`contact_group_items`.`group_id` = `contact_groups`.`id`)
+ WHERE `contact_groups`.`owner_id` = :owner_id
+ AND `contact_group_items`.`user_id` = :user_id',
+ [
+ 'owner_id' => $contact->owner_id,
+ 'user_id' => $contact->user_id
+ ]
+ );
+ }
+}
diff --git a/lib/models/Course.class.php b/lib/models/Course.class.php
index ab58219..75dad33 100644
--- a/lib/models/Course.class.php
+++ b/lib/models/Course.class.php
@@ -80,7 +80,7 @@
* @property-read mixed $config additional field
*/
-class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, FeedbackRange
+class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, FeedbackRange, Studip\Calendar\Owner
{
protected static function configure($config = [])
{
@@ -1081,4 +1081,40 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe
{
return $this->getFullName();
}
+
+ /**
+ * @inheritDoc
+ */
+ public static function getCalendarOwner(string $owner_id): ?\Studip\Calendar\Owner
+ {
+ return self::find($owner_id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isCalendarReadable(?string $user_id = null): bool
+ {
+ if ($user_id === null) {
+ $user_id = self::findCurrent()->id;
+ }
+
+ //Calendar read permissions are granted for all participants
+ //that have at least user permissions.
+ return $GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isCalendarWritable(string $user_id = null): bool
+ {
+ if ($user_id === null) {
+ $user_id = self::findCurrent()->id;
+ }
+
+ //Calendar write permissions are granted for all participants
+ //that have autor permissions or higher.
+ return $GLOBALS['perm']->have_studip_perm('autor', $this->id, $user_id);
+ }
}
diff --git a/lib/models/CourseCancelledEvent.class.php b/lib/models/CourseCancelledEvent.class.php
deleted file mode 100644
index 5fed9c5..0000000
--- a/lib/models/CourseCancelledEvent.class.php
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-/**
- * 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>
- * @copyright 2014 Stud.IP Core-Group
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- *
- * @property string $id alias for pk
- * @property string $termin_id database column
- * @property string $event_id alias column for termin_id
- * @property string $range_id database column
- * @property string $sem_id alias column for range_id
- * @property string $autor_id database column
- * @property string $author_id alias column for autor_id
- * @property string $content database column
- * @property string $ex_description alias column for content
- * @property int $date database column
- * @property int $start alias column for date
- * @property int $end_time database column
- * @property int $end alias column for end_time
- * @property int $mkdate database column
- * @property int $chdate database column
- * @property int $date_typ database column
- * @property int $category_intern alias column for date_typ
- * @property string|null $raum database column
- * @property string|null $metadate_id database column
- * @property string $resource_id database column
- * @property SimpleORMapCollection|Folder[] $folders has_many Folder
- * @property SimpleORMapCollection|RoomRequest[] $room_requests has_many RoomRequest
- * @property SimpleORMapCollection|ResourceRequestAppointment[] $resource_request_appointments has_many ResourceRequestAppointment
- * @property User $author belongs_to User
- * @property Course $course belongs_to Course
- * @property SeminarCycleDate|null $cycle belongs_to SeminarCycleDate
- * @property ResourceBooking $room_booking has_one ResourceBooking
- * @property SimpleORMapCollection|CourseTopic[] $topics has_and_belongs_to_many CourseTopic
- * @property SimpleORMapCollection|Statusgruppen[] $statusgruppen has_and_belongs_to_many Statusgruppen
- * @property SimpleORMapCollection|User[] $dozenten has_and_belongs_to_many User
- * @property-read mixed $location additional field
- * @property mixed $type additional field
- * @property-read mixed $name additional field
- * @property-read mixed $title additional field
- * @property-read mixed $editor_id additional field
- * @property-read mixed $uid additional field
- * @property-read mixed $summary additional field
- * @property-read mixed $description additional field
- */
-
-class CourseCancelledEvent extends CourseEvent
-{
-
- protected static function configure($config = [])
- {
- $config['alias_fields']['ex_description'] = 'content';
-
- if (!self::TableScheme('ex_termine')) {
- throw new Exception('Cannot obtain table meta data for table "ex_termine"');
- }
-
- $config['db_fields'] = self::$schemes['ex_termine']['db_fields'];
- $config['pk'] = self::$schemes['ex_termine']['pk'];
-
- parent::configure($config);
- self::$config['CourseCancelledEvent']['db_table'] = 'ex_termine';
- }
-
- /**
- * Returns all CourseCancelledEvents in the given time range for the given range_id.
- *
- * @param string $user_id Id of Stud.IP object from type user, course, inst
- * @param DateTime $start The start date time.
- * @param DateTime $end The end date time.
- * @return SimpleORMapCollection Collection of found CourseCancelledEvents.
- */
- public static function getEventsByInterval($user_id, DateTime $start, dateTime $end)
- {
- $stmt = DBManager::get()->prepare('SELECT ex_termine.* FROM seminar_user '
- . 'INNER JOIN ex_termine ON seminar_id = range_id '
- . 'WHERE ex_termine.content <> \'\' AND user_id = :user_id '
- . 'AND date BETWEEN :start AND :end '
- . "AND (IFNULL(metadate_id, '') = '' "
- . 'OR metadate_id NOT IN ( '
- . 'SELECT metadate_id FROM schedule_seminare '
- . 'WHERE user_id = :user_id AND visible = 0) ) '
- . 'ORDER BY date ASC');
- $stmt->execute([
- ':user_id' => $user_id,
- ':start' => $start->getTimestamp(),
- ':end' => $end->getTimestamp()
- ]);
- $event_collection = [];
- foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
- $event = new CourseCancelledEvent();
- $event->setData($row);
- $event->setNew(false);
- // related persons (dozenten) or groups
- if (self::checkRelated($event, $user_id)) {
- $event_collection[] = $event;
- }
- }
- $event_collection = SimpleORMapCollection::createFromArray($event_collection, false);
- $event_collection->setClassName('Event');
- return $event_collection;
- }
-
- /**
- * Returns the title of this event.
- * The title of a course event is the name of the course or if a topic is
- * assigned, the title of this topic. If the user has not the permission
- * Event::PERMISSION_READABLE, the title is "Keine Berechtigung.".
- *
- * @return string
- */
- public function getTitle()
- {
- $title = parent::getTitle();
- if ($this->havePermission(Event::PERMISSION_READABLE)) {
- $title .= ' ' . _('(fällt aus)');
- }
- return $title;
- }
-
- /**
- * Returns the index of the category.
- * If the user has no permission, 255 is returned.
- *
- * TODO remove? use getStudipCategory instead?
- *
- * @see config/config.inc.php $TERMIN_TYP
- * @return int The index of the category
- */
- public function getCategory()
- {
- return 255;
- }
-
- public function getDescription()
- {
- if ($this->havePermission(Event::PERMISSION_READABLE)) {
- return $this->ex_description;
- }
- return '';
- }
-
-}
diff --git a/lib/models/CourseDate.class.php b/lib/models/CourseDate.class.php
index 5ba3534..2a5b77e 100644
--- a/lib/models/CourseDate.class.php
+++ b/lib/models/CourseDate.class.php
@@ -34,7 +34,7 @@
* @property SimpleORMapCollection|User[] $dozenten has_and_belongs_to_many User
*/
-class CourseDate extends SimpleORMap implements PrivacyObject
+class CourseDate extends SimpleORMap implements PrivacyObject, Event
{
const FORMAT_DEFAULT = 'default';
const FORMAT_VERBOSE = 'verbose';
@@ -476,4 +476,190 @@ class CourseDate extends SimpleORMap implements PrivacyObject
date('H:i', $this->end_time)
);
}
+
+ //Start of Event interface implementation.
+
+ public static function getEvents(DateTime $begin, DateTime $end, string $range_id): array
+ {
+ return self::findBySQL(
+ "JOIN `seminar_user`
+ ON `seminar_user`.`seminar_id` = `termine`.`range_id`
+ WHERE `seminar_user`.`user_id` = :user_id
+ AND `termine`.`date` BETWEEN :begin AND :end
+ AND (
+ IFNULL(`termine`.`metadate_id`, '') = ''
+ OR `termine`.`metadate_id` NOT IN (
+ SELECT `metadate_id`
+ FROM `schedule_seminare`
+ WHERE `user_id` = :user_id
+ AND `visible` = 0
+ )
+ )
+ ORDER BY date",
+ [
+ 'begin' => $begin->getTimestamp(),
+ 'end' => $end->getTimestamp(),
+ 'user_id' => $range_id
+ ]
+ );
+ }
+
+ //Event interface implementation:
+
+ public function getObjectId() : string
+ {
+ return (string) $this->id;
+ }
+
+ public function getPrimaryObjectID(): string
+ {
+ return $this->range_id;
+ }
+
+ public function getObjectClass(): string
+ {
+ return static::class;
+ }
+
+ public function getTitle(): string
+ {
+ return $this->course->name ?? '';
+ }
+
+ public function getBegin(): DateTime
+ {
+ $begin = new DateTime();
+ $begin->setTimestamp($this->date);
+ return $begin;
+ }
+
+ public function getEnd(): DateTime
+ {
+ $end = new DateTime();
+ $end->setTimestamp($this->end_time);
+ return $end;
+ }
+
+ public function getDuration(): DateInterval
+ {
+ $begin = $this->getBegin();
+ $end = $this->getEnd();
+ return $end->diff($begin);
+ }
+
+ public function getLocation(): string
+ {
+ return $this->raum ?? '';
+ }
+
+ public function getUniqueId(): string
+ {
+ return sprintf('Stud.IP-SEM-%1$s@%2$s', $this->id, $_SERVER['SERVER_NAME']);
+ }
+
+ public function getDescription(): string
+ {
+ $descriptions = $this->topics->map(function ($topic) {
+ $desc = $topic->title . "\n";
+ $desc .= $topic->description;
+
+ return $desc;
+ });
+ return implode("\n\n", $descriptions);
+ }
+
+ public function getAdditionalDescriptions(): array
+ {
+ $descriptions = [];
+ if (count($this->dozenten) > 0) {
+ $descriptions[_('Durchführende Lehrende')] = implode(', ', $this->dozenten->getFullname());
+ }
+ if (count($this->statusgruppen) > 0) {
+ $descriptions[_('Beteiligte Gruppen')] = implode(', ', $this->statusgruppen->getValue('name'));
+ }
+ return $descriptions;
+ }
+
+ public function isAllDayEvent(): bool
+ {
+ //Course dates are never all day events.
+ return false;
+ }
+
+ public function isWritable(string $user_id): bool
+ {
+ return $GLOBALS['perm']->have_studip_perm('dozent', $this->range_id, $user_id);
+ }
+
+ public function getCreationDate(): DateTime
+ {
+ $mkdate = new DateTime();
+ $mkdate->setTimestamp($this->mkdate);
+ return $mkdate;
+ }
+
+ public function getModificationDate(): DateTime
+ {
+ $chdate = new DateTime();
+ $chdate->setTimestamp($this->chdate);
+ return $chdate;
+ }
+
+ public function getImportDate(): DateTime
+ {
+ return $this->getCreationDate();
+ }
+
+ public function getAuthor(): ?User
+ {
+ return $this->author;
+ }
+
+ public function getEditor(): ?User
+ {
+ return null;
+ }
+
+ public function toEventData(string $user_id): \Studip\Calendar\EventData
+ {
+ $begin = new DateTime();
+ $begin->setTimestamp($this->date);
+ $end = new DateTime();
+ $end->setTimestamp($this->end_time);
+
+ $membership = CourseMember::findOneBySQL(
+ 'seminar_id = :course_id AND user_id = :user_id',
+ ['course_id' => $this->range_id, 'user_id' => $user_id]
+ );
+ $class_names = [];
+ if ($membership) {
+ $class_names[] = sprintf('gruppe%u', $membership->status);
+ }
+ $studip_view_urls = [];
+ if ($GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user_id)) {
+ $studip_view_urls['show'] = URLHelper::getURL('dispatch.php/course/dates/details/' . $this->id, ['cid' => $this->range_id, 'extra_buttons' => '1']);
+ }
+
+ return new \Studip\Calendar\EventData(
+ $begin,
+ $end,
+ $this->getTitle(),
+ $class_names,
+ '#000000',
+ '#aaaaaa',
+ $this->isWritable($user_id),
+ CourseDate::class,
+ $this->id,
+ Course::class,
+ $this->range_id,
+ 'course',
+ $this->range_id,
+ $studip_view_urls,
+ [],
+ 'seminar',
+ 'rgba(0,0,0,0)'
+ );
+ }
+
+ //End of Event interface implementation.
}
diff --git a/lib/models/CourseEvent.class.php b/lib/models/CourseEvent.class.php
deleted file mode 100644
index fa68b13..0000000
--- a/lib/models/CourseEvent.class.php
+++ /dev/null
@@ -1,596 +0,0 @@
-<?php
-/**
- * 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>
- * @copyright 2014 Stud.IP Core-Group
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- *
- * @property string $id alias for pk
- * @property string $termin_id database column
- * @property string $event_id alias column for termin_id
- * @property string $range_id database column
- * @property string $sem_id alias column for range_id
- * @property string $autor_id database column
- * @property string $author_id alias column for autor_id
- * @property string $content database column
- * @property int $date database column
- * @property int $start alias column for date
- * @property int $end_time database column
- * @property int $end alias column for end_time
- * @property int $mkdate database column
- * @property int $chdate database column
- * @property int $date_typ database column
- * @property int $category_intern alias column for date_typ
- * @property string|null $raum database column
- * @property string|null $metadate_id database column
- * @property SimpleORMapCollection|Folder[] $folders has_many Folder
- * @property SimpleORMapCollection|RoomRequest[] $room_requests has_many RoomRequest
- * @property SimpleORMapCollection|ResourceRequestAppointment[] $resource_request_appointments has_many ResourceRequestAppointment
- * @property User $author belongs_to User
- * @property Course $course belongs_to Course
- * @property SeminarCycleDate|null $cycle belongs_to SeminarCycleDate
- * @property ResourceBooking $room_booking has_one ResourceBooking
- * @property SimpleORMapCollection|CourseTopic[] $topics has_and_belongs_to_many CourseTopic
- * @property SimpleORMapCollection|Statusgruppen[] $statusgruppen has_and_belongs_to_many Statusgruppen
- * @property SimpleORMapCollection|User[] $dozenten has_and_belongs_to_many User
- * @property-read mixed $location additional field
- * @property mixed $type additional field
- * @property-read mixed $name additional field
- * @property-read mixed $title additional field
- * @property-read mixed $editor_id additional field
- * @property-read mixed $uid additional field
- * @property-read mixed $summary additional field
- * @property-read mixed $description additional field
- */
-
-class CourseEvent extends CourseDate implements Event
-{
- protected static function configure($config = [])
- {
- $config['alias_fields']['event_id'] = 'termin_id';
- $config['alias_fields']['start'] = 'date';
- $config['alias_fields']['end'] = 'end_time';
- $config['alias_fields']['category_intern'] = 'date_typ';
- $config['alias_fields']['author_id'] = 'autor_id';
- $config['alias_fields']['sem_id'] = 'range_id';
-
- $config['additional_fields']['location']['get'] = 'getRoomName';
- $config['additional_fields']['type'] = true;
- $config['additional_fields']['name']['get'] = function ($event) {
- return $event->course->getFullname();
- };
- $config['additional_fields']['title']['get'] = 'getTitle';
- $config['additional_fields']['editor_id']['get'] = function ($date) {
- return null;
- };
- $config['additional_fields']['uid']['get'] = function ($date) {
- $host = $_SERVER['SERVER_NAME'] ?? parse_url($GLOBALS['ABSOLUTE_URI_STUDIP'], PHP_URL_HOST);
- return 'Stud.IP-SEM-' . $date->getId() . '@' . $host;
- };
- $config['additional_fields']['summary']['get'] = function ($date) {
- return $date->course->name;
- };
- $config['additional_fields']['description']['get'] = function ($date) {
- return '';
- };
- parent::configure($config);
- }
-
- private $properties = null;
- private $permission_user_id = null;
-
- public function __construct($id = null)
- {
- $this->permission_user_id = $GLOBALS['user']->id;
- parent::__construct($id);
- }
-
- /**
- * Returns all CourseEvents in the given time range for the given range_id.
- *
- * @param string $user_id Id of Stud.IP object from type user, course, inst
- * @param DateTime $start The start date time.
- * @param DateTime $end The end date time.
- * @return SimpleORMapCollection Collection of found CourseEvents.
- */
- public static function getEventsByInterval($user_id, DateTime $start, dateTime $end)
- {
- $stmt = DBManager::get()->prepare('SELECT termine.* FROM seminar_user '
- . 'INNER JOIN termine ON seminar_id = range_id '
- . 'WHERE user_id = :user_id '
- . 'AND bind_calendar = 1 '
- . 'AND date BETWEEN :start AND :end '
- . "AND (IFNULL(metadate_id, '') = '' "
- . 'OR metadate_id NOT IN ( '
- . 'SELECT metadate_id FROM schedule_seminare '
- . 'WHERE user_id = :user_id AND visible = 0) ) '
- . 'ORDER BY date ASC');
- $stmt->execute([
- ':user_id' => $user_id,
- ':start' => $start->getTimestamp(),
- ':end' => $end->getTimestamp()
- ]);
- $event_collection = [];
- foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
- $event = new CourseEvent();
- $event->setData($row);
- $event->setNew(false);
- // related persons (dozenten) or groups
- if (self::checkRelated($event, $user_id)) {
- $event_collection[] = $event;
- }
- }
- $event_collection = SimpleORMapCollection::createFromArray($event_collection, false);
- $event_collection->setClassName('Event');
- return $event_collection;
- }
-
- /**
- * Checks if given user is the responsible lecturer or is member of a
- * related group
- *
- * @global object $perm The globa perm object.
- * @param CourseEvent $event The course event to check against.
- * @param string $user_id The id of the user.
- * @return boolean
- */
- protected static function checkRelated(CourseEvent $event, $user_id)
- {
- global $perm;
-
- $check_related = false;
- $permission = $perm->get_studip_perm($event->range_id, $user_id);
- switch ($permission) {
- case 'dozent' :
- $related_persons = $event->dozenten->pluck('user_id');
- if (sizeof($related_persons)) {
- if (in_array($user_id, $related_persons)) {
- $check_related = true;
- }
- } else {
- $check_related = true;
- }
- break;
- case 'tutor' :
- $check_related = true;
- break;
- default :
- $group_ids = $event->statusgruppen->pluck('statusgruppe_id');
- if (sizeof($group_ids)) {
- $member = StatusgruppeUser::findBySQL(
- 'statusgruppe_id IN(?) AND user_id = ?',
- [$group_ids, $user_id]);
- $check_related = sizeof($member) > 0;
- } else {
- $check_related = true;
- }
- }
- return $check_related;
- }
-
- /**
- * Returns the name of the category.
- *
- * @return array|string the name of the category
- */
- public function toStringCategories($as_array = false)
- {
- $category = '';
- if (
- $this->havePermission(Event::PERMISSION_READABLE)
- && !empty($GLOBALS['TERMIN_TYP'][$this->getCategory()])
- ) {
- $category = $GLOBALS['TERMIN_TYP'][$this->getCategory()]['name'];
- }
- return $as_array ? [$category] : $category;
- }
-
- /**
- * Returns the id of the related course
- *
- * @return string The id of the related course.
- */
- public function getSeminarId()
- {
- if ($this->havePermission(Event::PERMISSION_READABLE)) {
- return $this->sem_id;
- }
- return null;
- }
-
- /**
- * Returns an array that represents the recurrence rule for this event.
- * If an index is given, returns only this field of the rule.
- *
- * @return array|string The array with th recurrence rule or only one field.
- */
- public function getRecurrence($index = null)
- {
- $rep = ['ts' => 0, 'linterval' => 0, 'sinterval' => 0, 'wdays' => '',
- 'month' => 0, 'day' => 0, 'rtype' => 'SINGLE', 'duration' => 1];
- return $index ? $rep[$index] : $rep;
- }
-
- /**
- * Returns the name of the related course.
- *
- * @return string The name of the related course.
- */
- public function getSemName()
- {
- if ($this->havePermission(Event::PERMISSION_READABLE)) {
- return $this->course->name;
- }
- return '';
- }
-
- /**
- * TODO Wird das noch benötigt?
- */
- public function getType()
- {
- return 1;
- }
-
- /**
- * Returns the title of this event.
- * The title of a course event is the name of the course or if a topic is
- * assigned, the title of this topic. If the user has not the permission
- * Event::PERMISSION_READABLE, the title is "Keine Berechtigung.".
- *
- * @return string
- */
- public function getTitle()
- {
- $title = _('Keine Berechtigung.');
- if ($this->havePermission(Event::PERMISSION_READABLE)) {
- $description = $this->cycle ? trim($this->cycle->description) : '';
- if (sizeof($this->topics)) {
- $title = $this->course->name.": ".implode(', ', $this->topics->pluck('title'));
- } else {
- $title = $this->course->name;
- }
- $title = ($description ? $description . ', ' : '') . $title;
- }
- return $title;
- }
-
- /**
- * Returns the starttime as unix timestamp of this event.
- *
- * @return int The starttime of this event as a unix timestamp.
- */
- public function getStart()
- {
- return $this->date;
- }
-
- /**
- * Sets the start date time with given unix timestamp.
- *
- * @param string $timestamp Unix timestamp.
- */
- public function setStart($timestamp)
- {
- $this->date = $timestamp;
- }
-
- /**
- * Returns the endtime of this event.
- *
- * @return int The endtime of this event as a unix timestamp.
- */
- public function getEnd()
- {
- return $this->end_time;
- }
-
- /**
- * Sets the end date time by given unix timestamp.
- *
- * @param string $timestamp Unix timestamp.
- */
- public function setEnd($timestamp)
- {
- $this->end_time = $timestamp;
- }
-
- /**
- * Returns the duration of this event in seconds.
- *
- * @return int the duration of this event in seconds
- */
- function getDuration()
- {
- return $this->end - $this->start;
- }
-
- /**
- * Returns the location.
- * Without permission or the location is not set an empty string is returned.
- *
- * @see ClendarDate::getRoomName()
- * @return string The location
- */
- function getLocation()
- {
- $location = '';
- if ($this->havePermission(Event::PERMISSION_READABLE)) {
- $location = $this->getRoomName();
- }
- return $location;
- }
-
- /**
- * Returns the global unique id of this event.
- *
- * @return string The global unique id.
- */
- public function getUid()
- {
- return $this->uid;
- }
-
- /**
- * Returns the description of the topic.
- * If the user has no permission or the event has no topic
- * or the topics have no descritopn an empty string is returned.
- *
- * @return String the description
- */
- function getDescription()
- {
- $description = '';
- if ($this->havePermission(Event::PERMISSION_READABLE)) {
- $descriptions = $this->topics->map(function ($topic) {
- $desc = $topic->title . "\n";
- $desc .= $topic->description;
- });
- $description = implode("\n\n", $descriptions);
- }
- return $description;
- }
-
- /**
- * Returns the Stud.IP build in category as integer value.
- * If the user has no permission, 255 is returned.
- *
- * @See config.inc.php $PERS_TERMIN
- * @return int the categories
- */
- public function getStudipCategory()
- {
- if ($this->havePermission(Event::PERMISSION_READABLE)) {
- return $this->date_typ;
- }
- return 255;
- }
-
- /**
- * Returns the index of the category.
- * If the user has no permission, 255 is returned.
- *
- * TODO remove? use getStudipCategory instead?
- *
- * @see config/config.inc.php $TERMIN_TYP
- * @return int The index of the category
- */
- public function getCategory()
- {
- if ($this->havePermission(Event::PERMISSION_READABLE)) {
- return $this->date_typ;
- }
- return 255;
- }
-
- /**
- * Returns the user id of the last editor.
- * Since course events have no editor null is returned.
- *
- * @return null|int Returns always null.
- */
- public function getEditorId()
- {
- return null;
- }
-
- /**
- * Returns whether the event is a all day event.
- *
- * @return
- */
- public function isDayEvent()
- {
- return (($this->end - $this->start) / 60 / 60) > 23;
- }
-
- /**
- * Returns the accessibility of this event. The value is not influenced by
- * the permission of the actual user.
- *
- * According to RFC5545 the accessibility (property CLASS) is represented
- * by the 3 state PUBLIC, PRIVATE and CONFIDENTIAL
- *
- * TODO check this statement:
- * An course event is always CONFIDENTIAL
- *
- * @return string The accessibility as string.
- */
- public function getAccessibility()
- {
- return 'CONFIDENTIAL';
- }
-
- /**
- * Returns the unix timestamp of the last change.
- *
- * @access public
- */
- public function getChangeDate()
- {
- return $this->chdate;
- }
-
- /**
- * Returns the date time the event was imported.
- * Since course events are not imported normaly, returns the date time
- * of creation.
- *
- * @return int Date time of import as unix timestamp:
- */
- public function getImportDate()
- {
- return $this->mkdate;
- }
-
- /**
- * Returns all related groups.
- *
- * TODO remove, use direct access to field CourseDate::statusgruppen.
- *
- * @return SimpleORMapCollection The collection of statusgruppen.
- */
- public function getRelatedGroups()
- {
- return $this->statusgruppen;
- }
-
- public function getProperties()
- {
- if ($this->properties === null) {
- $this->properties = [
- 'DTSTART' => $this->getStart(),
- 'DTEND' => $this->getEnd(),
- 'SUMMARY' => $this->getTitle(),
- 'DESCRIPTION' => $this->getDescription(),
- 'LOCATION' => $this->getLocation(),
- 'CATEGORIES' => $this->toStringCategories(),
- 'STUDIP_CATEGORY' => $this->getStudipCategory(),
- 'CREATED' => $this->mkdate,
- 'LAST-MODIFIED' => $this->chdate,
- 'STUDIP_ID' => $this->termin_id,
- 'SEM_ID' => $this->range_id,
- 'SEMNAME' => $this->course->name,
- 'CLASS' => 'CONFIDENTIAL',
- 'UID' => CourseEvent::getUid(),
- 'RRULE' => CourseEvent::getRecurrence(),
- 'EXDATE' => '',
- 'EVENT_TYPE' => 'sem',
- 'STATUS' => 'CONFIRMED',
- 'DTSTAMP' => time()];
- }
- return $this->properties;
- }
-
- /**
- * Returns the value of property with given name.
- *
- * @param type $name See CalendarEvent::getProperties() for accepted values.
- * @return mixed The value of the property.
- * @throws InvalidArgumentException
- */
- public function getProperty($name)
- {
- if ($this->properties === null) {
- $this->getProperties();
- }
-
- if (isset($this->properties[$name])) {
- return $this->properties[$name];
- }
- throw new InvalidArgumentException(get_class($this)
- . ': Property ' . $name . ' does not exist.');
- }
-
- public function setPermissionUser($user_id)
- {
- $this->permission_user_id = $user_id;
- }
-
- public function havePermission($permission, $user_id = null)
- {
- $perm = $this->getPermission($user_id);
- return $perm >= $permission;
- }
-
- public function getPermission($user_id = null)
- {
- global $perm;
-
- $user_id = $user_id ?: $this->permission_user_id;
- $course_perm = $perm->get_studip_perm($this->range_id, $user_id);
- $permission = Event::PERMISSION_FORBIDDEN;
- switch ($course_perm) {
- case 'tutor':
- case 'dozent':
- $permission = Event::PERMISSION_WRITABLE;
- break;
- case 'user':
- case 'autor':
- $permission = Event::PERMISSION_READABLE;
- break;
- default:
- $permission = Event::PERMISSION_FORBIDDEN;
- }
-
- return $permission;
- }
-
- /**
- * Course events have no priority so returns always an empty string.
- *
- * @return string The priority as a string.
- */
- public function toStringPriority()
- {
- return '';
- }
-
- /**
- * Course events have no accessibility settings so returns always the
- * an empty string.
- *
- * @return string The accessibility as string.
- */
- public function toStringAccessibility()
- {
- return '';
- }
-
- /**
- * Returns a string representation of the recurrence rule.
- * Since course events have no recurence defined it returns an empty string.
- *
- * @param bool $only_type If true returns only the type of recurrence.
- * @return string The recurrence rule - human readable
- */
- public function toStringRecurrence($only_type = false)
- {
- return '';
- }
-
- /**
- * Returns the author of this event as user object.
- *
- * @return User|null User object.
- */
- public function getAuthor()
- {
- return $this->author;
- }
-
- /**
- * Course events have no editor so always null is returned.
- *
- * @return null
- */
- public function getEditor()
- {
- return null;
- }
-}
diff --git a/lib/models/CourseExDate.class.php b/lib/models/CourseExDate.class.php
index 993767a..ce50e31 100644
--- a/lib/models/CourseExDate.class.php
+++ b/lib/models/CourseExDate.class.php
@@ -33,7 +33,7 @@
* @property-read mixed $room_request additional field
*/
-class CourseExDate extends SimpleORMap implements PrivacyObject
+class CourseExDate extends SimpleORMap implements PrivacyObject, Event
{
/**
* Configures this model.
@@ -247,4 +247,176 @@ class CourseExDate extends SimpleORMap implements PrivacyObject
date('H:i', $this->end_time)
);
}
+
+ //Start of Event interface implementation.
+
+ public static function getEvents(DateTime $begin, DateTime $end, string $range_id): array
+ {
+ return self::findBySQL(
+ "JOIN `seminar_user`
+ ON `seminar_user`.`seminar_id` = `ex_termine`.`range_id`
+ WHERE `seminar_user`.`user_id` = :user_id
+ AND `ex_termine`.`date` BETWEEN :begin AND :end
+ AND (
+ IFNULL(`ex_termine`.`metadate_id`, '') = ''
+ OR `ex_termine`.`metadate_id` NOT IN (
+ SELECT `metadate_id`
+ FROM `schedule_seminare`
+ WHERE `user_id` = :user_id
+ AND `visible` = 0
+ )
+ )
+ ORDER BY date",
+ [
+ 'begin' => $begin->getTimestamp(),
+ 'end' => $end->getTimestamp(),
+ 'user_id' => $range_id
+ ]
+ );
+ }
+
+ //Event interface implementation:
+
+ public function getObjectId() : string
+ {
+ return (string) $this->id;
+ }
+
+ public function getPrimaryObjectID(): string
+ {
+ return $this->range_id;
+ }
+
+ public function getObjectClass(): string
+ {
+ return static::class;
+ }
+
+ public function getTitle(): string
+ {
+ return sprintf('%s (fällt aus)', $this->course->name ?? '');
+ }
+
+ public function getBegin(): DateTime
+ {
+ $begin = new DateTime();
+ $begin->setTimestamp($this->date);
+ return $begin;
+ }
+
+ public function getEnd(): DateTime
+ {
+ $end = new DateTime();
+ $end->setTimestamp($this->end_time);
+ return $end;
+ }
+
+ public function getDuration(): DateInterval
+ {
+ $begin = $this->getBegin();
+ $end = $this->getEnd();
+ return $end->diff($begin);
+ }
+
+ public function getLocation(): string
+ {
+ return '';
+ }
+
+ public function getUniqueId(): string
+ {
+ return sprintf('Stud.IP-SEM-%1$s@%2$s', $this->id, $_SERVER['SERVER_NAME']);
+ }
+
+ public function getDescription(): string
+ {
+ return trim($this->getValue('content'));
+ }
+
+ public function getAdditionalDescriptions(): array
+ {
+ $descriptions = [];
+ if (count($this->dozenten) > 0) {
+ $descriptions[_('Durchführende Lehrende')] = implode(', ', $this->dozenten->getFullname());
+ }
+ if (count($this->statusgruppen) > 0) {
+ $descriptions[_('Beteiligte Gruppen')] = implode(', ', $this->statusgruppen->getValue('name'));
+ }
+ return $descriptions;
+ }
+
+ public function isAllDayEvent(): bool
+ {
+ //Course dates are never all day events.
+ return false;
+ }
+
+ public function isWritable(string $user_id): bool
+ {
+ return $GLOBALS['perm']->have_studip_perm('dozent', $this->range_id, $user_id);
+ }
+
+ public function getCreationDate(): DateTime
+ {
+ $mkdate = new DateTime();
+ $mkdate->setTimestamp($this->mkdate);
+ return $mkdate;
+ }
+
+ public function getModificationDate(): DateTime
+ {
+ $chdate = new DateTime();
+ $chdate->setTimestamp($this->chdate);
+ return $chdate;
+ }
+
+ public function getImportDate(): DateTime
+ {
+ return $this->getCreationDate();
+ }
+
+ public function getAuthor(): ?User
+ {
+ return $this->author;
+ }
+
+ public function getEditor(): ?User
+ {
+ return null;
+ }
+
+ public function toEventData(string $user_id): \Studip\Calendar\EventData
+ {
+ $begin = new DateTime();
+ $begin->setTimestamp($this->date);
+ $end = new DateTime();
+ $end->setTimestamp($this->end_time);
+
+ $studip_view_urls = [];
+ if ($GLOBALS['perm']->have_studip_perm('user', $this->range_id, $user_id)) {
+ $studip_view_urls['show'] = URLHelper::getURL('dispatch.php/course/details', ['cid' => $this->range_id, 'link_to_course' => '1']);
+ }
+
+ return new \Studip\Calendar\EventData(
+ $begin,
+ $end,
+ $this->getTitle(),
+ [],
+ '#000000',
+ '#aaaaaa',
+ $this->isWritable($user_id),
+ CourseExDate::class,
+ $this->id,
+ Course::class,
+ $this->range_id,
+ 'course',
+ $this->range_id,
+ $studip_view_urls,
+ [],
+ 'seminar',
+ 'rgba(0,0,0,0)'
+ );
+ }
+
+ //End of Event interface implementation.
}
diff --git a/lib/models/CourseMarkedEvent.class.php b/lib/models/CourseMarkedEvent.class.php
deleted file mode 100644
index 96f0300..0000000
--- a/lib/models/CourseMarkedEvent.class.php
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-/**
- * 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>
- * @copyright 2015 Stud.IP Core-Group
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- *
- * @property string $id alias for pk
- * @property string $termin_id database column
- * @property string $event_id alias column for termin_id
- * @property string $range_id database column
- * @property string $sem_id alias column for range_id
- * @property string $autor_id database column
- * @property string $author_id alias column for autor_id
- * @property string $content database column
- * @property int $date database column
- * @property int $start alias column for date
- * @property int $end_time database column
- * @property int $end alias column for end_time
- * @property int $mkdate database column
- * @property int $chdate database column
- * @property int $date_typ database column
- * @property int $category_intern alias column for date_typ
- * @property string|null $raum database column
- * @property string|null $metadate_id database column
- * @property SimpleORMapCollection|Folder[] $folders has_many Folder
- * @property SimpleORMapCollection|RoomRequest[] $room_requests has_many RoomRequest
- * @property SimpleORMapCollection|ResourceRequestAppointment[] $resource_request_appointments has_many ResourceRequestAppointment
- * @property User $author belongs_to User
- * @property Course $course belongs_to Course
- * @property SeminarCycleDate|null $cycle belongs_to SeminarCycleDate
- * @property ResourceBooking $room_booking has_one ResourceBooking
- * @property SimpleORMapCollection|CourseTopic[] $topics has_and_belongs_to_many CourseTopic
- * @property SimpleORMapCollection|Statusgruppen[] $statusgruppen has_and_belongs_to_many Statusgruppen
- * @property SimpleORMapCollection|User[] $dozenten has_and_belongs_to_many User
- * @property-read mixed $location additional field
- * @property mixed $type additional field
- * @property-read mixed $name additional field
- * @property-read mixed $title additional field
- * @property-read mixed $editor_id additional field
- * @property-read mixed $uid additional field
- * @property-read mixed $summary additional field
- * @property-read mixed $description additional field
- */
-
-class CourseMarkedEvent extends CourseEvent
-{
-
- protected static function configure($config= [])
- {
- parent::configure($config);
- }
-
- /**
- * Returns all CourseMarkedEvents in the given time range for the given range_id.
- *
- * @param string $user_id Id of Stud.IP object from type user, course, inst
- * @param DateTime $start The start date time.
- * @param DateTime $end The end date time.
- * @return SimpleORMapCollection Collection of found CourseMarkedEvents.
- */
- public static function getEventsByInterval($user_id, DateTime $start, dateTime $end)
- {
- $stmt = DBManager::get()->prepare('SELECT DISTINCT termine.* FROM schedule_seminare '
- . 'INNER JOIN termine ON schedule_seminare.seminar_id = range_id '
- . 'LEFT JOIN seminar_user ON seminar_user.seminar_id = range_id AND seminar_user.user_id= :user_id '
- . 'WHERE schedule_seminare.user_id = :user_id AND schedule_seminare.visible = 1 '
- . 'AND seminar_user.seminar_id IS NULL AND date BETWEEN :start AND :end '
- . 'ORDER BY date ASC');
- $stmt->execute([
- ':user_id' => $user_id,
- ':start' => $start->getTimestamp(),
- ':end' => $end->getTimestamp()
- ]);
- $event_collection = [];
- foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
- $event = new CourseMarkedEvent();
- $event->setData($row);
- $event->setNew(false);
- $event_collection[] = $event;
- }
- $event_collection = SimpleORMapCollection::createFromArray($event_collection, false);
- $event_collection->setClassName('Event');
- return $event_collection;
- }
-
- public function getPermission($user_id = null)
- {
- return Event::PERMISSION_READABLE;
- }
-
- /**
- * Returns the title of this event.
- * The title of a course event is the name of the course or if a topic is
- * assigned, the title of this topic. If the user has not the permission
- * Event::PERMISSION_READABLE, the title is "Keine Berechtigung.".
- *
- * @return string
- */
- public function getTitle()
- {
- $title = $this->course->name;
- $title .= ' ' . _('(vorgemerkt)');
-
- return $title;
- }
-
- /**
- * Returns the index of the category.
- * If the user has no permission, 255 is returned.
- *
- * TODO remove? use getStudipCategory instead?
- *
- * @see config/config.inc.php $TERMIN_TYP
- * @return int The index of the category
- */
- public function getCategory()
- {
- return 256;
- }
-
- public function getDescription()
- {
- return '';
- }
-
-}
diff --git a/lib/models/EventData.class.php b/lib/models/EventData.class.php
deleted file mode 100644
index 453272d..0000000
--- a/lib/models/EventData.class.php
+++ /dev/null
@@ -1,144 +0,0 @@
-<?php
-/**
- * EventData.class.php - Model class for calendar events.
- *
- * 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>
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- * @since 3.2
- *
- * @property string $id alias column for event_id
- * @property string $event_id database column
- * @property string $author_id database column
- * @property string|null $editor_id database column
- * @property string $uid database column
- * @property int $start database column
- * @property int $end database column
- * @property string $summary database column
- * @property string|null $description database column
- * @property string $class database column
- * @property string|null $categories database column
- * @property int $category_intern database column
- * @property int $priority database column
- * @property string|null $location database column
- * @property int $ts database column
- * @property int|null $linterval database column
- * @property int|null $sinterval database column
- * @property string|null $wdays database column
- * @property int|null $month database column
- * @property int|null $day database column
- * @property string $rtype database column
- * @property int $duration database column
- * @property int|null $count database column
- * @property int $expire database column
- * @property string|null $exceptions database column
- * @property int $mkdate database column
- * @property int $chdate database column
- * @property int $importdate database column
- * @property SimpleORMapCollection|CalendarEvent[] $calendars has_many CalendarEvent
- * @property User $author belongs_to User
- * @property User|null $editor belongs_to User
- */
-
-class EventData extends SimpleORMap implements PrivacyObject
-{
- protected static function configure($config = [])
- {
- $config['db_table'] = 'event_data';
-
- $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' => CalendarEvent::class,
- 'foreign_key' => 'event_id'
- ];
-
- $config['default_values']['linterval'] = 0;
- $config['default_values']['sinterval'] = 0;
-
- $config['registered_callbacks']['before_create'][] = 'cbDefaultValues';
-
- parent::configure($config);
-
- }
-
- public function delete()
- {
- // do not delete until one calendar is left
- if (sizeof($this->calendars) > 1) {
- return false;
- }
- $calendars = $this->calendars;
- $ret = parent::delete();
- // only one calendar is left
- if ($ret) {
- $calendars->each(function($c) { $c->delete(); });
- }
- return $ret;
- }
-
- public static function garbageCollect()
- {
- DBManager::get()->query('DELETE event_data '
- . 'FROM calendar_event LEFT JOIN event_data USING(event_id)'
- . 'WHERE range_id IS NULL');
- }
-
- public function getDefaultValue($field)
- {
- if ($field == 'start') {
- return time();
- }
- if ($field == 'end' && $this->content['start']) {
- return $this->content['start'] + 3600;
- }
- if ($field == 'ts' && $this->content['start']) {
- return mktime(12, 0, 0, date('n', $this->content['start']),
- date('j', $this->content['start']), date('Y', $this->content['start']));
- }
- return parent::getDefaultValue($field);
- }
-
- protected function cbDefaultValues()
- {
- if (empty($this->content['uid'])) {
- $this->content['uid'] = 'Stud.IP-' . $this->event_id . '@' . ($_SERVER['SERVER_NAME'] ?? parse_url($GLOBALS['ABSOLUTE_URI_STUDIP'], PHP_URL_HOST));
- }
- }
-
- /**
- * 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 = EventData::findThru($storage->user_id, [
- 'thru_table' => 'calendar_event',
- 'thru_key' => 'range_id',
- 'thru_assoc_key' => 'event_id',
- 'assoc_foreign_key' => 'event_id',
- ]);
- if ($sorm) {
- $field_data = [];
- foreach ($sorm as $row) {
- $field_data[] = $row->toRawArray();
- }
- if ($field_data) {
- $storage->addTabularData(_('Kalender Einträge'), 'event_data', $field_data);
- }
- }
- }
-}
diff --git a/lib/models/User.class.php b/lib/models/User.class.php
index 7f6a402..5f1d6a0 100644
--- a/lib/models/User.class.php
+++ b/lib/models/User.class.php
@@ -80,7 +80,7 @@
* @property mixed $lock_rule additional field
* @property mixed $oercampus_description additional field
*/
-class User extends AuthUserMd5 implements Range, PrivacyObject
+class User extends AuthUserMd5 implements Range, PrivacyObject, Studip\Calendar\Owner
{
/**
*
@@ -1541,4 +1541,56 @@ class User extends AuthUserMd5 implements Range, PrivacyObject
return $this->config->EXPIRATION_DATE > 0
&& $this->config->EXPIRATION_DATE < time();
}
+
+ /**
+ * @inheritDoc
+ */
+ public static function getCalendarOwner(string $owner_id): ?\Studip\Calendar\Owner
+ {
+ return self::find($owner_id);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isCalendarReadable(?string $user_id = null): bool
+ {
+ if ($user_id === null) {
+ $user_id = self::findCurrent()->id;
+ }
+
+ if ($this->id === $user_id) {
+ //The owner can always read their own calendar.
+ return true;
+ }
+ return Contact::countBySql(
+ "`owner_id` = :this_user_id AND `user_id` = :other_user_id
+ AND `calendar_permissions` <> ''",
+ ['this_user_id' => $this->id, 'other_user_id' => $user_id]
+ ) > 0;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isCalendarWritable(string $user_id = null): bool
+ {
+ if ($user_id === null) {
+ $user_id = self::findCurrent()->id;
+ }
+
+ if ($this->id === $user_id) {
+ //The owner can always write their own calendar.
+ return true;
+ }
+ if (Config::get()->CALENDAR_GRANT_ALL_INSERT) {
+ //All users can write in all users calendars.
+ return true;
+ }
+ return Contact::countBySql(
+ "`owner_id` = :this_user_id AND `user_id` = :other_user_id
+ AND `calendar_permissions` = 'WRITE'",
+ ['this_user_id' => $this->id, 'other_user_id' => $user_id]
+ ) > 0;
+ }
}
diff --git a/lib/models/calendar/CalendarCourseDate.class.php b/lib/models/calendar/CalendarCourseDate.class.php
new file mode 100644
index 0000000..49179bd
--- /dev/null
+++ b/lib/models/calendar/CalendarCourseDate.class.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * CalendarCourseDate is a specialisation of CourseDate for
+ * course dates that are displayed in the personal calendar.
+ */
+class CalendarCourseDate extends CourseDate
+{
+ public static function getEvents(DateTime $begin, DateTime $end, string $range_id): array
+ {
+ return parent::findBySQL(
+ "JOIN `seminar_user`
+ ON `seminar_user`.`seminar_id` = `termine`.`range_id`
+ WHERE `seminar_user`.`user_id` = :user_id
+ AND `seminar_user`.`bind_calendar` = '1'
+ AND `termine`.`date` BETWEEN :begin AND :end
+ AND (
+ IFNULL(`termine`.`metadate_id`, '') = ''
+ OR `termine`.`metadate_id` NOT IN (
+ SELECT `metadate_id`
+ FROM `schedule_seminare`
+ WHERE `user_id` = :user_id
+ AND `visible` = 0
+ )
+ )
+ ORDER BY date",
+ [
+ 'begin' => $begin->getTimestamp(),
+ 'end' => $end->getTimestamp(),
+ 'user_id' => $range_id
+ ]
+ );
+ }
+}
diff --git a/lib/models/calendar/CalendarCourseExDate.class.php b/lib/models/calendar/CalendarCourseExDate.class.php
new file mode 100644
index 0000000..eb23aeb
--- /dev/null
+++ b/lib/models/calendar/CalendarCourseExDate.class.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * CalendarCourseExDate is a specialisation of CourseExDate for
+ * cancelled course dates that are displayed in the personal calendar.
+ */
+class CalendarCourseExDate extends CourseExDate
+{
+ public static function getEvents(DateTime $begin, DateTime $end, string $range_id): array
+ {
+ return parent::findBySQL(
+ "JOIN `seminar_user`
+ ON `seminar_user`.`seminar_id` = `ex_termine`.`range_id`
+ WHERE `seminar_user`.`user_id` = :user_id
+ AND `seminar_user`.`bind_calendar` = '1'
+ AND `ex_termine`.`date` BETWEEN :begin AND :end
+ AND `ex_termine`.`content` <> ''
+ AND (
+ IFNULL(`ex_termine`.`metadate_id`, '') = ''
+ OR `ex_termine`.`metadate_id` NOT IN (
+ SELECT `metadate_id`
+ FROM `schedule_seminare`
+ WHERE `user_id` = :user_id
+ AND `visible` = 0
+ )
+ )
+ ORDER BY date",
+ [
+ 'begin' => $begin->getTimestamp(),
+ 'end' => $end->getTimestamp(),
+ 'user_id' => $range_id
+ ]
+ );
+ }
+}
diff --git a/lib/models/calendar/CalendarDate.class.php b/lib/models/calendar/CalendarDate.class.php
new file mode 100644
index 0000000..23ea8af
--- /dev/null
+++ b/lib/models/calendar/CalendarDate.class.php
@@ -0,0 +1,944 @@
+<?php
+/**
+ * CalendarDate.class.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 function delete()
+ {
+ // do not delete until one calendar is left
+ if (count($this->calendars) > 1) {
+ return false;
+ }
+ $calendars = $this->calendars;
+ $ret = parent::delete();
+ // only one calendar is left
+ if ($ret) {
+ $calendars->delete();
+ }
+ return $ret;
+ }
+
+ 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_TemplateFactory($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;
+ }
+
+ //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->calendarWritable($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' => 'event_id',
+ 'assoc_foreign_key' => 'event_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) {
+ $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;
+ }
+}
diff --git a/lib/models/calendar/CalendarDateAssignment.class.php b/lib/models/calendar/CalendarDateAssignment.class.php
new file mode 100644
index 0000000..ba0f387
--- /dev/null
+++ b/lib/models/calendar/CalendarDateAssignment.class.php
@@ -0,0 +1,714 @@
+<?php
+/**
+ * CalendarDateAssignment.class.php - Model class for calendar date assignments.
+ *
+ * CalendarDateAssignment represents the assignment of a calendar date
+ * to a specific calendar. The calendar is represented by a range-ID
+ * since it can be a personal calendar, course calendar or institute
+ * 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 Moritz Strohm <strohm@data-quest.de>
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ * @since 5.5
+ *
+ * @property string range_id The range-ID for the assignment.
+ * @property string calendar_date_id The ID of the calendar date for the assignment.
+ * @property string participation The participation status of the receiver (range_id).
+ * This column is an enum with the following values:
+ * - empty string: Participation status is unknown.
+ * - "ACCEPTED": The calendar owner accepted the date.
+ * - "DECLINED": The calendar owner declined the date.
+ * - "ACKNOWLEDGED": The calendar owner only acknowledged that the date exists
+ * but doesn't necessarily participate in it.
+ * @property string mkdate The creation date of the assignment.
+ * @property string chdate The modification date of the assignment.
+ * @property CalendarDate|null calendar_date The associated calendar date object.
+ */
+class CalendarDateAssignment extends SimpleORMap implements Event
+{
+ /**
+ * @var bool This attribute allows the suppression of automatic mail sending
+ * when storing or deleting the calendar date assignment.
+ * By default, mails are sent.
+ */
+ public $suppress_mails = false;
+
+ protected static function configure($config = [])
+ {
+ $config['db_table'] = 'calendar_date_assignments';
+
+ $config['belongs_to']['calendar_date'] = [
+ 'class_name' => CalendarDate::class,
+ 'foreign_key' => 'calendar_date_id',
+ 'assoc_func' => 'find'
+ ];
+ $config['belongs_to']['user'] = [
+ 'class_name' => User::class,
+ 'foreign_key' => 'range_id',
+ 'assoc_func' => 'find'
+ ];
+ $config['belongs_to']['course'] = [
+ 'class_name' => Course::class,
+ 'foreign_key' => 'range_id',
+ 'assoc_func' => 'find'
+ ];
+
+ $config['registered_callbacks']['after_create'][] = 'cbSendNewDateMail';
+ $config['registered_callbacks']['after_delete'][] = 'cbSendDateDeletedMail';
+
+ parent::configure($config);
+ }
+
+
+ public function cbSendNewDateMail()
+ {
+ if ($this->suppress_mails) {
+ return;
+ }
+ if ($this->range_id === $this->calendar_date->editor_id) {
+ return;
+ }
+ if (!$this->calendar_date || !$this->user) {
+ //Wrong calendar range (not a user) or invalid data set.
+ return;
+ }
+
+ $template_factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/locale/');
+
+ setTempLanguage($this->range_id);
+ $lang_path = getUserLanguagePath($this->range_id);
+ $template = $template_factory->open($lang_path . '/LC_MAILS/date_created.php');
+ $template->set_attribute('date', $this->calendar_date);
+ $template->set_attribute('receiver', $this->user);
+ $mail_text = $template->render();
+ Message::send(
+ '____%system%____',
+ [$this->user->username],
+ sprintf(_('%s hat einen Termin im Kalender eingetragen'), $this->calendar_date->editor->getFullName()),
+ $mail_text
+ );
+
+ restoreLanguage();
+ }
+
+ public function cbSendDateDeletedMail()
+ {
+ if ($this->suppress_mails) {
+ return;
+ }
+ if ($this->range_id === $this->calendar_date->editor_id) {
+ return;
+ }
+ if (!$this->calendar_date || !$this->user) {
+ //Wrong calendar range (not a user) or invalid data set.
+ return;
+ }
+
+ $template_factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/locale/');
+
+ setTempLanguage($this->range_id);
+ $lang_path = getUserLanguagePath($this->range_id);
+ $template = $template_factory->open($lang_path . '/LC_MAILS/date_deleted.php');
+ $template->set_attribute('date', $this->calendar_date);
+ $template->set_attribute('receiver', $this->user);
+ $mail_text = $template->render();
+ Message::send(
+ '____%system%____',
+ [$this->user->username],
+ sprintf(_('%s hat einen Termin im Kalender gelöscht'), $this->calendar_date->editor->getFullName()),
+ $mail_text
+ );
+
+ restoreLanguage();
+ }
+
+ /**
+ * Sends the participation status of the calendar the date
+ * is assigned to. This is only done for user calendars
+ * and not for course calendars.
+ *
+ * @return void
+ */
+ public function sendParticipationStatus() : void
+ {
+ if (!($this->user instanceof User)) {
+ //The calendar date is assigned to a course calendar.
+ return;
+ }
+
+ if (!$this->participation || $this->participation === 'ACKNOWLEDGED') {
+ //Nothing shall be done in these two cases.
+ return;
+ }
+
+ if (empty($this->calendar_date->author->username)) {
+ //The calendar date has no author.
+ return;
+ }
+ if ($this->range_id === $this->calendar_date->author_id) {
+ //The author of the date changed their participation status.
+ //So they know what they did and do not have to be notified.
+ return;
+ }
+
+ $template_factory = new Flexi_TemplateFactory($GLOBALS['STUDIP_BASE_PATH'] . '/locale/');
+
+ setTempLanguage($this->range_id);
+ $lang_path = getUserLanguagePath($this->range_id);
+ $template = $template_factory->open($lang_path . '/LC_MAILS/date_participation.php');
+ $template->set_attribute('date_assignment', $this);
+ $mail_text = $template->render();
+
+ $subject = '';
+ if ($this->participation === 'ACCEPTED') {
+ $subject = sprintf(
+ _('%1$s hat Ihren Termin am %2$s angenommen'),
+ $this->user->getFullName(),
+ date('d.m.Y', $this->calendar_date->begin)
+ );
+ } elseif ($this->participation === 'DECLINED') {
+ $subject = sprintf(
+ _('%1$s hat Ihren Termin am %2$s abgelehnt'),
+ $this->user->getFullName(),
+ date('d.m.Y', $this->calendar_date->begin)
+ );
+ }
+
+ Message::send(
+ '____%system%____',
+ [$this->calendar_date->author->username],
+ $subject,
+ $mail_text
+ );
+
+ restoreLanguage();
+ }
+
+ /**
+ * Retrieves calendar dates inside a specified time range that are present in the calendar of a
+ * course or user. They can additionally be filtered by the access level and declined events
+ * can be filtered out, too.
+ *
+ * @param DateTime $begin The beginning of the time range.
+ *
+ * @param DateTime $end The end of the time range.
+ *
+ * @param string $range_id The ID of the course or user whose calendar dates shall be retrieved.
+ *
+ * @param array $access_levels The access level filter: Only include calendar dates that have one of the
+ * access levels in the list.
+ *
+ * @param bool $with_declined Include declined calendar dates (true) or filter them out (false).
+ * Defaults to false.
+ *
+ * @return CalendarDateAssignment[] A list of calendar date assignments in the time range that match the filters.
+ */
+ public static function getEvents(
+ DateTime $begin,
+ DateTime $end,
+ string $range_id,
+ array $access_levels = ['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'],
+ bool $with_declined = false
+ ) : array
+ {
+ $begin->setTime(0, 0);
+ $end->setTime(23, 59, 59);
+
+ $sql = "JOIN `calendar_dates`
+ ON calendar_date_id = `calendar_dates`.`id`
+ WHERE
+ `calendar_date_assignments`.`range_id` = :range_id ";
+ if (!$with_declined) {
+ $sql .= "AND `calendar_date_assignments`.`participation` <> 'DECLINED' ";
+ }
+ $sql .= "AND (
+ `calendar_dates`.`begin` BETWEEN :begin AND :end
+ OR
+ (`calendar_dates`.`begin` <= :end AND `calendar_dates`.`repetition_type` <> ''
+ AND `calendar_dates`.`repetition_end` > :begin)
+ OR
+ :begin BETWEEN `calendar_dates`.`begin` AND `calendar_dates`.`end`
+ )
+ AND
+ `access` IN ( :access_levels )
+ ORDER BY `calendar_dates`.`begin` ASC";
+
+ $events = self::findBySql($sql, [
+ 'range_id' => $range_id,
+ 'begin' => $begin->getTimestamp(),
+ 'end' => $end->getTimestamp(),
+ 'access_levels' => $access_levels
+ ]);
+
+ $m_start = clone $begin;
+ $m_end = clone $end;
+ $events_created = [];
+ while ($m_start < $m_end) {
+
+ foreach ($events as $event) {
+ $e_start = clone $event->getBegin();
+ $e_end = clone $event->getEnd();
+ $e_expire = $event->getExpire();
+
+ // duration in full days
+ $duration = $event->getDurationDays();
+
+ $cal_start = DateTimeImmutable::createFromMutable($m_start);
+ $cal_end = DateTimeImmutable::createFromMutable($m_start)->setTime(23,59,59);
+ $cal_noon = $cal_start->setTime(12, 0);
+ // single events or first event
+ if (
+ ($e_start >= $cal_start && $e_end <= $cal_end)
+ || ($e_start >= $cal_start && $e_start <= $cal_end)
+ || ($e_start < $cal_start && $e_end > $cal_end)
+ || ($e_end > $cal_start && $e_start <= $cal_end)
+ ) {
+ // exception for first event or single event
+ if (!$event->calendar_date->exceptions->findOneBy('date', $cal_start->format('Y-m-d'))) {
+ $events_created = array_merge($events_created, self::createRecurrentDate($event, $cal_noon));
+ }
+ } elseif ($e_expire > $cal_start) {
+ $events_created = array_merge($events_created, self::getRepetition($event, $cal_noon));
+ }
+ }
+
+ $m_start->modify('+1 day');
+ }
+
+ return $events_created;
+ }
+
+ private static function getRepetition(
+ CalendarDateAssignment $date,
+ DateTimeImmutable $cal_noon,
+ bool $calc_prev = true
+ ): array
+ {
+ $rep_dates = [];
+ $ts = $date->getNoonDate();
+ if ($cal_noon >= $ts) {
+ if ($date->isRepeatedAtDate($cal_noon)) {
+ $rep_dates = array_merge($rep_dates, self::createRecurrentDate($date, $cal_noon));
+ }
+ if ($calc_prev) {
+ $rep_noon = $cal_noon->modify(sprintf('-%s days', $date->getDurationDays()));
+ $rep_dates = array_merge(
+ $rep_dates,
+ self::getRepetition(
+ $date,
+ $rep_noon,
+ false
+ )
+ );
+ }
+ }
+ return $rep_dates;
+ }
+
+ private function isRepeatedAtDate(DateTimeImmutable $cal_date): bool
+ {
+ $ts = $this->getNoonDate();
+ $pos = 1;
+ switch ($this->getRepetitionType()) {
+ case 'DAILY':
+ $pos = $cal_date->diff($ts)->days % $this->calendar_date->interval;
+ break;
+ case 'WEEKLY':
+ $cal_ts = $cal_date->modify('monday this week noon');
+ if ($cal_date >= $this->getBegin()) {
+ $pos = $cal_ts->diff($ts)->days % ($this->calendar_date->interval * 7);
+ if (
+ $pos === 0
+ && strpos($this->calendar_date->days, $cal_date->format('N')) === false
+ ) {
+ $pos = 1;
+ }
+ }
+ break;
+ case 'MONTHLY':
+ $cal_ts = $cal_date->modify('first day of this month noon');
+ $diff = $cal_ts->diff($ts);
+ $pos = ($diff->m + $diff->y * 12) % $this->calendar_date->interval;
+ if ($pos === 0) {
+ if (strlen($this->calendar_date->days)) {
+ $cal_ts_dom = $cal_ts->modify(sprintf('%s %s of this month noon',
+ $this->calendar_date->getOrdinalName(),
+ $this->calendar_date->getWeekdayName()));
+ if ($cal_ts_dom != $cal_date->setTime(12, 0)) {
+ $pos = 1;
+ }
+ } elseif ($this->calendar_date->offset !== $cal_date->format('j')) {
+ $pos = 1;
+ }
+ }
+ break;
+ case 'YEARLY':
+ $cal_ts = $cal_date->modify('first day of this year noon');
+ $diff = $cal_ts->diff($ts);
+ $pos = $diff->y % $this->calendar_date->interval;
+ if ($pos === 0) {
+ if (strlen($this->calendar_date->days)) {
+ $ts_doy = $ts->modify(sprintf('%s %s of %s-%s noon',
+ $this->calendar_date->getOrdinalName(),
+ $this->calendar_date->getWeekdayName(),
+ $cal_date->format('Y'),
+ $this->calendar_date->month));
+ if ($ts_doy->format('n-j') !== $cal_date->format('n-j')) {
+ $pos = 1;
+ }
+ } elseif (
+ $cal_date->format('n-j') !== sprintf(
+ '%s-%s',
+ $this->calendar_date->month,
+ $this->calendar_date->offset
+ )
+ ) {
+ $pos = 1;
+ }
+ }
+ break;
+ default:
+ $pos = 1;
+ }
+ //Also check for exceptions before returning:
+ return $pos === 0
+ && !$this->calendar_date->exceptions->findOneBy(
+ 'date',
+ $cal_date->format('Y-m-d'));
+ }
+
+ private static function createRecurrentDate(
+ CalendarDateAssignment $date,
+ DateTimeImmutable $date_time
+ ) : array
+ {
+ $date_begin = $date->getBegin();
+ $date_end = $date->getEnd();
+
+ $rec_date = clone $date;
+ $time_begin = $date_begin->format('H:i:s');
+ $time_end = $date_end->format('H:i:s');
+
+ $rec_date_begin = $date_time->modify(sprintf('today %s', $time_begin));
+ $rec_date_end = $rec_date_begin->add($date->getDuration())->modify(sprintf('today %s', $time_end));
+
+ $rec_date->calendar_date->begin = $rec_date_begin->getTimestamp();
+ $rec_date->calendar_date->end = $rec_date_end->getTimestamp();
+ $index = $date->calendar_date->id . '_' . $rec_date_begin->getTimestamp();
+ return [$index => $rec_date];
+ }
+
+ //Event interface implementation:
+
+ public function getObjectId() : string
+ {
+ return (string)$this->id;
+ }
+
+ public function getPrimaryObjectID(): string
+ {
+ return $this->calendar_date_id;
+ }
+
+ public function getObjectClass(): string
+ {
+ return static::class;
+ }
+
+ public function getTitle() : string
+ {
+ return $this->calendar_date->title ?? '';
+ }
+
+ public function getBegin(): DateTime
+ {
+ $begin = new DateTime();
+ $begin->setTimestamp($this->calendar_date->begin ?? 0);
+ return $begin;
+ }
+
+ public function getEnd(): DateTime
+ {
+ $end = new DateTime();
+ $end->setTimestamp($this->calendar_date->end ?? 0);
+ return $end;
+ }
+
+ public function getDuration(): DateInterval
+ {
+ $begin = $this->getBegin();
+ $end = $this->getEnd();
+ return $begin->diff($end);
+ }
+
+ /**
+ * Returns the "extent" in days of this date.
+ *
+ * @return int The "extent" in days of this date.
+ */
+ public function getDurationDays(): int
+ {
+ return self::getExtent($this->getEnd(), $this->getBegin());
+ }
+
+ /**
+ * Returns the "extent" in days of this date.
+ * The extent is the number of days a date is displayed in a calendar.
+ *
+ * @return int The "extent" in days of this date.
+ */
+ public static function getExtent(DateTimeInterface $date_begin, DateTimeInterface $date_end): int
+ {
+ $days_duration = $date_end->diff($date_begin)->days;
+ if ($date_begin->format('His') > $date_end->format('His')) {
+ $days_duration += 1;
+ }
+ return $days_duration;
+ }
+
+ public function getLocation(): string
+ {
+ return $this->calendar_date->location ?? '';
+ }
+
+ public function getUniqueId(): string
+ {
+ return $this->calendar_date->unique_id ?? '';
+ }
+
+ public function getDescription(): string
+ {
+ return $this->calendar_date->description ?? '';
+ }
+
+ public function getAdditionalDescriptions(): array
+ {
+ return [
+ _('Kategorie') => $this->calendar_date->getCategoryAsString(),
+ _('Sichtbarkeit') => $this->calendar_date->getVisibilityAsString(),
+ _('Wiederholung') => $this->calendar_date->getRepetitionAsString()
+ ];
+ }
+
+ public function isAllDayEvent(): bool
+ {
+ $begin = $this->getBegin();
+ if ($begin->format('His') != '000000') {
+ return false;
+ }
+ $duration = $this->getDuration();
+ return $duration->h === 23 && $duration->i === 59 && $duration->s === 59;
+ }
+
+ public function isWritable(string $user_id): bool
+ {
+ if ($this->calendar_date->author_id === $user_id) {
+ //The author may always modify one of their dates:
+ return true;
+ }
+ if ($this->calendar_date->isWritable($user_id)) {
+ //The date is writable.
+ return true;
+ }
+
+ //The user referenced by $user_id is not the author of the date.
+ //Check if they have write permissions to the calendar where the date is assigned to:
+ if ($this->user instanceof User) {
+ //It is a personal calendar. Check if the owner of the calendar has granted write permissions
+ //to the user:
+ return Contact::countBySQL(
+ "`owner_id` = :owner_id AND `user_id` = :user_id
+ AND `calendar_permissions` = 'WRITE'",
+ ['owner_id' => $this->range_id, 'user_id' => $user_id]
+ ) > 0;
+ } elseif ($this->course instanceof Course) {
+ //It is a course calendar.
+ return $GLOBALS['perm']->have_studip_perm('dozent', $this->range_id, $user_id);
+ }
+
+ //No write permissions are granted.
+ return false;
+ }
+
+ public function getCreationDate(): DateTime
+ {
+ $mkdate = new DateTime();
+ $mkdate->setTimestamp($this->calendar_date->mkdate ?? 0);
+ return $mkdate;
+ }
+
+ public function getModificationDate(): DateTime
+ {
+ $chdate = new DateTime();
+ $chdate->setTimestamp($this->calendar_date->chdate ?? 0);
+ return $chdate;
+ }
+
+ public function getImportDate(): DateTime
+ {
+ $import_date = new DateTime();
+ $import_date->setTimestamp($this->calendar_date->import_date ?? 0);
+ return $import_date;
+ }
+
+ public function getAuthor(): ?User
+ {
+ return $this->calendar_date->author ?? null;
+ }
+
+ public function getEditor(): ?User
+ {
+ return $this->calendar_date->editor ?? null;
+ }
+
+ /**
+ * TODO calculate end of repetition for different types of repetition
+ * @return float|int|object
+ */
+ public function getExpire()
+ {
+ if ($this->calendar_date->repetition_end > 0) {
+ $expire = $this->calendar_date->repetition_end;
+ } else {
+ $expire = CalendarDate::NEVER_ENDING;
+ }
+
+ $end = new DateTime();
+ $end->setTimestamp($expire);
+ return $end;
+ }
+
+ // TODO calculate ts for monthly and yearly repetition
+ public function getNoonDate()
+ {
+ $ts = DateTimeImmutable::createFromMutable($this->getBegin());
+ switch ($this->calendar_date->repetition_type) {
+ case 'DAILY':
+ return $ts->modify('noon');
+ case 'WEEKLY':
+ return $ts->modify('monday this week noon');
+ case 'MONTHLY':
+ return $ts->modify('first day of this month noon');
+ case 'YEARLY':
+ return $ts->modify('first day of this year noon');
+ default:
+ return $ts;
+ }
+ }
+
+ /**
+ * Returns the type of repetition.
+ *
+ * @return string The type of repetition.
+ */
+ public function getRepetitionType(): string
+ {
+ return $this->calendar_date->repetition_type;
+ }
+
+ public function toEventData(string $user_id): \Studip\Calendar\EventData
+ {
+ $begin = $this->getBegin();
+ $end = $this->getEnd();
+ $duration = $this->getDuration();
+
+ $all_day = $begin->format('H:i:s') === '00:00:00'
+ && $duration->h === 23
+ && $duration->i === 59
+ && $duration->s === 59;
+
+
+ $hide_confidential_data = $this->calendar_date->access === 'CONFIDENTIAL'
+ && $user_id !== $this->calendar_date->author_id;
+
+ $event_classes = ['user-date'];
+
+ $text_colour = '#000000';
+ $background_colour = '#ffffff';
+ $border_colour = '#000000';
+ if (!$hide_confidential_data) {
+ if ($this->calendar_date->user_category) {
+ //The date belongs to a personal category that gets a grey colour.
+ $background_colour = '#a7abaf';
+ $border_colour = '#a7abaf';
+ } else {
+ //The date belongs to a system category that has its own colours.
+ $text_colour = $GLOBALS['PERS_TERMIN_KAT'][$this->calendar_date->category]['fgcolor'] ?? $text_colour;
+ $background_colour = $GLOBALS['PERS_TERMIN_KAT'][$this->calendar_date->category]['bgcolor'] ?? $background_colour;
+ $border_colour = $GLOBALS['PERS_TERMIN_KAT'][$this->calendar_date->category]['border_color'] ?? $border_colour;
+ $event_classes[] = sprintf('user-date-category%d', $this->calendar_date->category);
+ }
+ }
+
+ $show_url_params = [];
+ if ($this->calendar_date->repetition_type) {
+ $show_url_params['selected_date'] = $begin->format('Y-m-d');
+ }
+
+ return new \Studip\Calendar\EventData(
+ $begin,
+ $end,
+ !$hide_confidential_data ? $this->getTitle() : '',
+ $event_classes,
+ $text_colour,
+ $background_colour,
+ $this->isWritable($user_id),
+ CalendarDateAssignment::class,
+ $this->id,
+ CalendarDate::class,
+ $this->calendar_date_id,
+ 'user',
+ $this->range_id ?? '',
+ [
+ 'show' => URLHelper::getURL('dispatch.php/calendar/date/index/' . $this->calendar_date_id, $show_url_params)
+ ],
+ [
+ 'resize_dialog' => URLHelper::getURL('dispatch.php/calendar/date/move/' . $this->calendar_date_id),
+ 'move_dialog' => URLHelper::getURL('dispatch.php/calendar/date/move/' . $this->calendar_date_id)
+ ],
+ $this->participation === 'DECLINED' ? 'decline-circle-full' : '',
+ $border_colour,
+ $all_day
+ );
+ }
+
+ public function getRangeName() : string
+ {
+ if ($this->course instanceof Course) {
+ return $this->course->getFullname();
+ } elseif ($this->user instanceof User) {
+ return $this->user->getFullName();
+ }
+ return '';
+ }
+
+ public function getRangeAvatar() : ?Avatar
+ {
+ if ($this->course instanceof Course) {
+ return CourseAvatar::getAvatar($this->range_id);
+ } elseif ($this->user instanceof User) {
+ return Avatar::getAvatar($this->range_id);
+ }
+ return null;
+ }
+
+ public function getParticipationAsString() : string
+ {
+ if ($this->participation === '') {
+ return _('Abwartend');
+ } elseif ($this->participation === 'ACKNOWLEDGED') {
+ return _('Angenommen (keine Teilnahme)');
+ } elseif ($this->participation === 'ACCEPTED') {
+ return _('Angenommen');
+ } elseif ($this->participation === 'DECLINED') {
+ return _('Abgelehnt');
+ }
+ return '';
+ }
+}
diff --git a/lib/models/calendar/CalendarDateException.class.php b/lib/models/calendar/CalendarDateException.class.php
new file mode 100644
index 0000000..b53a728
--- /dev/null
+++ b/lib/models/calendar/CalendarDateException.class.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * The CalendarDateException class represents one exception for a calendar date.
+ *
+ * This file is part of Stud.IP
+ *
+ * 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 2023
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ * @package resources
+ * @since 5.5
+ *
+ * @property string $id The ID of the exception.
+ * @property string $calendar_date_id The ID of the calendar date where the exception belongs to.
+ * @property string $date The date of the exception in the date format YYYY-MM-DD.
+ * @property string $mkdate The creation date of the exception.
+ * @property string $chdate The modification date of the exception.
+ * @property CalendarDate|null $calendar_date The associated calendar date object.
+ */
+class CalendarDateException extends SimpleORMap
+{
+ protected static function configure($config = [])
+ {
+ $config['db_table'] = 'calendar_date_exceptions';
+
+ $config['belongs_to']['calendar_date'] = [
+ 'class_name' => CalendarDate::class,
+ 'foreign_key' => 'calendar_date_id',
+ 'assoc_func' => 'find'
+ ];
+
+ parent::configure($config);
+ }
+}
diff --git a/lib/modules/CoreCalendar.class.php b/lib/modules/CoreCalendar.class.php
index c0df367..318a2ba 100644
--- a/lib/modules/CoreCalendar.class.php
+++ b/lib/modules/CoreCalendar.class.php
@@ -20,7 +20,7 @@ class CoreCalendar extends CorePlugin implements StudipModule
return null;
}
- $navigation = new Navigation(_('Kalender'), "seminar_main.php?auswahl={$course_id}&redirect_to=dispatch.php/calendar/single/");
+ $navigation = new Navigation(_('Kalender'), URLHelper::getURL('dispatch.php/calendar/calendar/course/' . $course_id));
$navigation->setImage(Icon::create('schedule', Icon::ROLE_CLICKABLE));
return $navigation;
}
@@ -34,7 +34,7 @@ class CoreCalendar extends CorePlugin implements StudipModule
return null;
}
- $navigation = new Navigation(_('Kalender'), 'dispatch.php/calendar/single/');
+ $navigation = new Navigation(_('Kalender'), 'dispatch.php/calendar/calendar/course/' . $course_id);
$navigation->setImage(Icon::create('schedule', Icon::ROLE_INFO_ALT));
$navigation->setActiveImage(Icon::create('schedule', Icon::ROLE_INFO));
return ['calendar' => $navigation];
@@ -49,10 +49,10 @@ class CoreCalendar extends CorePlugin implements StudipModule
'summary' => _('Kalender'),
'category' => _('Lehr- und Lernorganisation'),
'icon' => Icon::create('schedule', Icon::ROLE_INFO),
- 'icon_clickable' => Icon::create('schedule', Icon::ROLE_CLICKABLE),
- 'displayname' => _('Planer'),
+ 'displayname' => _('Kalender'),
];
}
+
public function isActivatableForContext(Range $context)
{
return Config::get()->CALENDAR_GROUP_ENABLE && $context->getRangeType() === 'course';
@@ -60,7 +60,6 @@ class CoreCalendar extends CorePlugin implements StudipModule
public function getInfoTemplate($course_id)
{
- // TODO: Implement getInfoTemplate() method.
return null;
}
}
diff --git a/lib/modules/CoreOverview.class.php b/lib/modules/CoreOverview.class.php
index af1b960..94005da 100644
--- a/lib/modules/CoreOverview.class.php
+++ b/lib/modules/CoreOverview.class.php
@@ -90,7 +90,7 @@ class CoreOverview extends CorePlugin implements StudipModule
if ($object_type !== 'sem') {
$navigation->addSubNavigation('info', new Navigation(_('Kurzinfo'), 'dispatch.php/institute/overview'));
$navigation->addSubNavigation('courses', new Navigation(_('Veranstaltungen'), 'show_bereich.php?level=s&id='.$course_id));
- $navigation->addSubNavigation('schedule', new Navigation(_('Veranstaltungs-Stundenplan'), 'dispatch.php/calendar/instschedule?cid='.$course_id));
+ $navigation->addSubNavigation('schedule', new Navigation(_('Veranstaltungs-Stundenplan'), 'dispatch.php/institute/schedule/index/' . $course_id));
if ($GLOBALS['perm']->have_studip_perm('admin', $course_id)) {
$navigation->addSubNavigation('admin', new Navigation(_('Administration der Einrichtung'), 'dispatch.php/institute/basicdata/index?new_inst=TRUE'));
diff --git a/lib/modules/ScheduleWidget.php b/lib/modules/ScheduleWidget.php
index 339beb3..0393176 100644
--- a/lib/modules/ScheduleWidget.php
+++ b/lib/modules/ScheduleWidget.php
@@ -39,15 +39,45 @@ class ScheduleWidget extends CorePlugin implements PortalPlugin
*/
public function getPortalTemplate()
{
- $view = CalendarScheduleModel::getUserCalendarView(
- $GLOBALS['user']->id,
- false,
- false,
- $days = array(0,1,2,3,4)
+ $week_slot_duration = \Studip\Calendar\Helper::getCalendarSlotDuration('week');
+ $calendar_settings = $GLOBALS['user']->cfg->CALENDAR_SETTINGS ?? [];
+
+ $semester = Semester::findCurrent();
+ $fullcalendar = \Studip\Fullcalendar::create(
+ '',
+ [
+ 'minTime' => '08:00',
+ 'maxTime' => '20:00',
+ 'allDaySlot' => false,
+ 'header' => [
+ 'left' => '',
+ 'right' => ''
+ ],
+ 'views' => [
+ 'timeGridWeek' => [
+ 'columnHeaderFormat' => ['weekday' => 'long'],
+ 'weekends' => $calendar_settings['type_week'] === 'LONG',
+ 'slotDuration' => $week_slot_duration
+ ]
+ ],
+ 'defaultView' => 'timeGridWeek',
+ 'defaultDate' => date('Y-m-d'),
+ 'timeGridEventMinHeight' => 20,
+ 'eventSources' => [
+ [
+ 'url' => URLHelper::getURL('dispatch.php/calendar/calendar/schedule_data'),
+ 'method' => 'GET',
+ 'extraParams' => [
+ 'semester_id' => $semester->id,
+ 'full_semester_time_range' => false
+ ]
+ ]
+ ]
+ ]
);
$template = $GLOBALS['template_factory']->open('shared/string');
- $template->content = CalendarWidgetView::createFromWeekView($view)->render();
+ $template->content = $fullcalendar;
return $template;
}
diff --git a/lib/modules/TerminWidget.php b/lib/modules/TerminWidget.php
index c9cf854..773e692 100644
--- a/lib/modules/TerminWidget.php
+++ b/lib/modules/TerminWidget.php
@@ -20,7 +20,7 @@ class TerminWidget extends CorePlugin implements PortalPlugin
public function getMetadata()
{
return [
- 'description' => _('Mit diesem Widget haben Sie ihre aktuellen Termine im Überlick.')
+ 'description' => _('Dieses Widget zeigt die eigenen aktuellen Termine an.')
];
}
@@ -31,8 +31,9 @@ class TerminWidget extends CorePlugin implements PortalPlugin
$template = $GLOBALS['template_factory']->open('shared/string');
$template->content = $response->body;
- $navigation = new Navigation('', 'dispatch.php/calendar/single/week', ['self' => true]);
- $navigation->setImage(Icon::create('add', 'clickable', ["title" => _('Neuen Termin anlegen')]));
+ $navigation = new Navigation('', 'dispatch.php/calendar/date/add');
+ $navigation->setImage(Icon::create('add', Icon::ROLE_CLICKABLE, ['title' => _('Neuen Termin anlegen')]));
+ $navigation->setLinkAttributes(['data-dialog' => 'reload-on-close']);
$template->icons = [$navigation];
return $template;
diff --git a/lib/navigation/CalendarNavigation.php b/lib/navigation/CalendarNavigation.php
index 972f3f8..f201d86 100644
--- a/lib/navigation/CalendarNavigation.php
+++ b/lib/navigation/CalendarNavigation.php
@@ -20,25 +20,19 @@ class CalendarNavigation extends Navigation
*/
public function __construct()
{
- global $perm;
-
- parent::__construct(_('Planer'));
-
- if (
- isset($perm)
- && !$perm->have_perm('admin')
- && Config::get()->SCHEDULE_ENABLE
- ) {
- $planerinfo = _('Stundenplan');
- } else {
- $planerinfo = _('Termine');
+ $title = _('Kalender');
+ $main_url = URLHelper::getURL('dispatch.php/calendar/calendar');
+ if (!$GLOBALS['perm']->have_perm('admin') && Config::get()->SCHEDULE_ENABLE) {
+ $title = _('Stundenplan');
+ $main_url = URLHelper::getURL('dispatch.php/calendar/schedule');
}
+ parent::__construct($title, $main_url);
- $this->setImage(Icon::create('schedule', 'navigation', ["title" => $planerinfo]));
+ $this->setImage(Icon::create('schedule', 'navigation', ['title' => $title]));
}
/**
- * Initialize the subnavigation of this item. This method
+ * Initialize the sub-navigation of this item. This method
* is called once before the first item is added or removed.
*/
public function initSubNavigation()
@@ -47,16 +41,13 @@ class CalendarNavigation extends Navigation
parent::initSubNavigation();
- // schedule
if (!$perm->have_perm('admin') && Config::get()->SCHEDULE_ENABLE) {
$navigation = new Navigation(_('Stundenplan'), 'dispatch.php/calendar/schedule');
$this->addSubNavigation('schedule', $navigation);
}
- // calendar
- $atime = $atime ? intval($atime) : Request::int($atime);
if (Config::get()->CALENDAR_ENABLE) {
- $navigation = new Navigation(_('Terminkalender'), 'dispatch.php/calendar/single', ['self' => 1]);
+ $navigation = new Navigation(_('Kalender'), 'dispatch.php/calendar/calendar');
$this->addSubNavigation('calendar', $navigation);
}
}
diff --git a/lib/navigation/ProfileNavigation.php b/lib/navigation/ProfileNavigation.php
index ffff06c..50dcfec 100644
--- a/lib/navigation/ProfileNavigation.php
+++ b/lib/navigation/ProfileNavigation.php
@@ -103,7 +103,7 @@ class ProfileNavigation extends Navigation
$navigation->addSubNavigation('messaging', new Navigation(_('Nachrichten'), 'dispatch.php/settings/messaging'));
if (Config::get()->CALENDAR_ENABLE) {
- $navigation->addSubNavigation('calendar_new', new Navigation(_('Terminkalender'), 'dispatch.php/settings/calendar'));
+ $navigation->addSubNavigation('calendar_new', new Navigation(_('Kalender'), 'dispatch.php/settings/calendar'));
}
if (!$perm->have_perm('admin') && Config::get()->MAIL_NOTIFICATION_ENABLE) {
diff --git a/lib/navigation/StartNavigation.php b/lib/navigation/StartNavigation.php
index bc938b6..913f3fa 100644
--- a/lib/navigation/StartNavigation.php
+++ b/lib/navigation/StartNavigation.php
@@ -281,10 +281,10 @@ class StartNavigation extends Navigation
$this->addSubNavigation('profile', $navigation);
- $navigation = new Navigation(_('Mein Planer'));
+ $navigation = new Navigation(_('Kalender'));
if (Config::get()->CALENDAR_ENABLE) {
- $navigation->addSubNavigation('calendar', new Navigation(_('Terminkalender'), 'dispatch.php/calendar/single'));
+ $navigation->addSubNavigation('calendar', new Navigation(_('Kalender'), 'dispatch.php/calendar/calendar'));
}
if (Config::get()->SCHEDULE_ENABLE) {
diff --git a/lib/seminar_open.php b/lib/seminar_open.php
index 0ca9991..45c4df3 100644
--- a/lib/seminar_open.php
+++ b/lib/seminar_open.php
@@ -44,7 +44,7 @@ function startpage_redirect($page_code) {
$jump_page = "dispatch.php/contact";
break;
case 5:
- $jump_page = "dispatch.php/calendar/single";
+ $jump_page = "dispatch.php/calendar";
break;
case 6:
// redirect to global blubberstream
diff --git a/locale/de/LC_MAILS/_date_information.php b/locale/de/LC_MAILS/_date_information.php
new file mode 100644
index 0000000..5eab14d
--- /dev/null
+++ b/locale/de/LC_MAILS/_date_information.php
@@ -0,0 +1,28 @@
+*Zeiten:* <?= date('d.m.Y H:i', $date->begin) ?> - <?= date('d.m.Y H:i', $date->end) ?>
+
+*Titel:* <?= $date->title ?>
+
+<?= $date->description ?? '' ?>
+
+--
+
+<? if ($date->category) : ?>
+*Kategorie:* <?= $date->getCategoryAsString() ?>
+<? endif ?>
+
+*Zugriff:* <?= $date->getAccessAsString() ?>
+
+<? if ($date->repetition_type) : ?>
+*Wiederholung:* <?= $date->getRepetitionAsString() ?>
+<? endif ?>
+
+<? if (Config::get()->CALENDAR_GROUP_ENABLE && count($date->calendars) > 1) : ?>
+*Teilnehmende:*
+<? foreach($date->getParticipantsAsStringArray($receiver->user_id) as $participant_string) : ?>
+- <?= $participant_string ?>
+<? endforeach ?>
+<? endif ?>
+
+<? if ($receiver_date_assignment) : ?>
+**Ihre Teilnahme:** <?= $receiver_date_assignment->getParticipationAsString() ?>
+<? endif ?>
diff --git a/locale/de/LC_MAILS/date_changed.php b/locale/de/LC_MAILS/date_changed.php
new file mode 100644
index 0000000..aa92b7f
--- /dev/null
+++ b/locale/de/LC_MAILS/date_changed.php
@@ -0,0 +1,10 @@
+<?= $date->editor->getFullName() ?> hat einen Termin im Kalender geändert.
+
+<?= $this->render_partial(__DIR__ . '/_date_information', [
+ 'date' => $date,
+ 'receiver' => $receiver,
+]) ?>
+
+--
+
+Direkt zum Termin: <?= URLHelper::getURL('dispatch.php/calendar/date/index/' . $date->id) ?>
diff --git a/locale/de/LC_MAILS/date_created.php b/locale/de/LC_MAILS/date_created.php
new file mode 100644
index 0000000..d323b94
--- /dev/null
+++ b/locale/de/LC_MAILS/date_created.php
@@ -0,0 +1,10 @@
+<?= $date->editor->getFullName() ?> hat einen Termin im Kalender eingetragen.
+
+<?= $this->render_partial(__DIR__ . '/_date_information', [
+ 'date' => $date,
+ 'receiver' => $receiver,
+]) ?>
+
+--
+
+Direkt zum Termin: <?= URLHelper::getURL('dispatch.php/calendar/date/index/' . $date->id) ?>
diff --git a/locale/de/LC_MAILS/date_deleted.php b/locale/de/LC_MAILS/date_deleted.php
new file mode 100644
index 0000000..0ff6ef8
--- /dev/null
+++ b/locale/de/LC_MAILS/date_deleted.php
@@ -0,0 +1,6 @@
+<?= $date->editor->getFullName() ?> hat einen Termin im Kalender gelöscht.
+
+<?= $this->render_partial(__DIR__ . '/_date_information', [
+ 'date' => $date,
+ 'receiver' => $receiver,
+]) ?>
diff --git a/locale/de/LC_MAILS/date_participation.php b/locale/de/LC_MAILS/date_participation.php
new file mode 100644
index 0000000..11b0fb2
--- /dev/null
+++ b/locale/de/LC_MAILS/date_participation.php
@@ -0,0 +1,14 @@
+<? if ($date_assignment->participation === 'ACCEPTED') : ?>
+<?= $date_assignment->user->getFullName() ?> hat Ihren Termin angenommen.
+<? elseif ($date_assignment->participation === 'DECLINED') : ?>
+<?= $date_assignment->user->getFullName() ?> hat Ihren Termin abgelehnt.
+<? endif ?>
+
+<?= $this->render_partial(__DIR__ . '/_date_information', [
+ 'date' => $date_assignment->calendar_date,
+ 'receiver' => $date_assignment->calendar_date->author,
+]) ?>
+
+--
+
+Direkt zum Termin: <?= URLHelper::getURL('dispatch.php/calendar/date/index/' . $date_assignment->calendar_date->id) ?>
diff --git a/locale/en/LC_MAILS/_date_information.php b/locale/en/LC_MAILS/_date_information.php
new file mode 100644
index 0000000..a0cb470
--- /dev/null
+++ b/locale/en/LC_MAILS/_date_information.php
@@ -0,0 +1,24 @@
+*Time:* <?= date('d.m.Y H:i', $date->begin) ?> - <?= date('d.m.Y H:i', $date->end) ?>
+
+*Title:* <?= $date->title ?>
+
+<?= $date->description ?? '' ?>
+
+--
+
+<? if ($date->category) : ?>
+*Category:* <?= $date->category ?>
+<? endif ?>
+
+*Access:* <?= $date->getAccessAsString() ?>
+
+<? if ($date->repetition_type) : ?>
+*Repetition:* <?= $date->getRepetitionAsString() ?>
+<? endif ?>
+
+<? if (Config::get()->CALENDAR_GROUP_ENABLE && count($date->calendars) > 1) : ?>
+*Participants:*
+<? foreach($date->getParticipantsAsStringArray($receiver->user_id) as $participant_string) : ?>
+- <?= $participant_string ?>
+<? endforeach ?>
+<? endif ?>
diff --git a/locale/en/LC_MAILS/date_changed.php b/locale/en/LC_MAILS/date_changed.php
new file mode 100644
index 0000000..ca1d670
--- /dev/null
+++ b/locale/en/LC_MAILS/date_changed.php
@@ -0,0 +1,10 @@
+<?= $date->editor->getFullName() ?> has modified a date in the calendar.
+
+<?= $this->render_partial(__DIR__ . '/_date_information', [
+ 'date' => $date,
+ 'receiver' => $receiver,
+]) ?>
+
+--
+
+Go to date: <?= URLHelper::getURL('dispatch.php/calendar/date/index/' . $date->id) ?>
diff --git a/locale/en/LC_MAILS/date_created.php b/locale/en/LC_MAILS/date_created.php
new file mode 100644
index 0000000..bc805ae
--- /dev/null
+++ b/locale/en/LC_MAILS/date_created.php
@@ -0,0 +1,10 @@
+<?= $date->editor->getFullName() ?> has entered a date in the calendar.
+
+<?= $this->render_partial(__DIR__ . '/_date_information', [
+ 'date' => $date,
+ 'receiver' => $receiver,
+]) ?>
+
+--
+
+Go to date: <?= URLHelper::getURL('dispatch.php/calendar/date/index/' . $date->id) ?>
diff --git a/locale/en/LC_MAILS/date_deleted.php b/locale/en/LC_MAILS/date_deleted.php
new file mode 100644
index 0000000..b4bafd6
--- /dev/null
+++ b/locale/en/LC_MAILS/date_deleted.php
@@ -0,0 +1,6 @@
+<?= $date->editor->getFullName() ?> has deleted a date in the calendar.
+
+<?= $this->render_partial(__DIR__ . '/_date_information', [
+ 'date' => $date,
+ 'receiver' => $receiver,
+]) ?>
diff --git a/locale/en/LC_MAILS/date_participation.php b/locale/en/LC_MAILS/date_participation.php
new file mode 100644
index 0000000..f761134
--- /dev/null
+++ b/locale/en/LC_MAILS/date_participation.php
@@ -0,0 +1,14 @@
+<? if ($date_assignment->participation === 'ACCEPTED') : ?>
+<?= $date_assignment->user->getFullName() ?> has accepted your date.
+<? elseif ($date_assignment->participation === 'DECLINED') : ?>
+<?= $date_assignment->user->getFullName() ?> has declined your date.
+<? endif ?>
+
+<?= $this->render_partial(__DIR__ . '/_date_information', [
+ 'date' => $date_assignment->calendar_date,
+ 'receiver' => $date_assignment->calendar_date->author,
+]) ?>
+
+--
+
+Go to date: <?= URLHelper::getURL('dispatch.php/calendar/date/index/' . $date_assignment->calendar_date->id) ?>
diff --git a/resources/assets/javascripts/bootstrap/calendar_dialog.js b/resources/assets/javascripts/bootstrap/calendar_dialog.js
deleted file mode 100644
index ee5ab4c..0000000
--- a/resources/assets/javascripts/bootstrap/calendar_dialog.js
+++ /dev/null
@@ -1,11 +0,0 @@
-jQuery(document).on('click', 'td.calendar-day-edit, td.calendar-day-event', function(event) {
- var elem = jQuery(this)
- .find('a')
- .first();
- if (_.isString(elem.attr('href'))) {
- STUDIP.Dialog.fromURL(elem.attr('href'), { title: elem.attr('title') });
- event.preventDefault();
- } else {
- return false;
- }
-});
diff --git a/resources/assets/javascripts/bootstrap/forms.js b/resources/assets/javascripts/bootstrap/forms.js
index 1f4937d..8f9e5fc 100644
--- a/resources/assets/javascripts/bootstrap/forms.js
+++ b/resources/assets/javascripts/bootstrap/forms.js
@@ -427,6 +427,24 @@ STUDIP.ready(function () {
});
}
+ /*
+ * Form elements with the "simplevue" class are meant for forms that just need some vue components
+ * to do something fancy inside the form but which do not need the full functionality of the form builder.
+ */
+ let simple_vue_items = document.querySelectorAll('form .simplevue:not(.vueified)');
+ if (simple_vue_items.length > 0) {
+ STUDIP.Vue.load().then(({createApp}) => {
+ simple_vue_items.forEach(f => {
+ createApp({
+ el: f,
+ mounted() {
+ this.$el.classList.add('vueified');
+ }
+ });
+ });
+ });
+ }
+
// Well, this is really nasty: Select2 can't determine the select
// element's width if it is hidden (by itself or by its parent).
// This is due to the fact that elements are not rendered when hidden
diff --git a/resources/assets/javascripts/bootstrap/fullcalendar.js b/resources/assets/javascripts/bootstrap/fullcalendar.js
index 62beaa9..44786d9 100644
--- a/resources/assets/javascripts/bootstrap/fullcalendar.js
+++ b/resources/assets/javascripts/bootstrap/fullcalendar.js
@@ -44,4 +44,5 @@ STUDIP.ready(function () {
});
}
+ jQuery(document).on('change', '#date_select[data-calendar-control]', STUDIP.Fullcalendar.submitDatePicker);
});
diff --git a/resources/assets/javascripts/bootstrap/resources.js b/resources/assets/javascripts/bootstrap/resources.js
index 25582d4..8c89b7f 100644
--- a/resources/assets/javascripts/bootstrap/resources.js
+++ b/resources/assets/javascripts/bootstrap/resources.js
@@ -483,7 +483,7 @@ STUDIP.ready(function () {
} else if ($(this).hasClass('fc-today-button')
|| $(this).hasClass('fc-prev-button')
|| $(this).hasClass('fc-next-button')) {
- updateDateURL();
+ STUDIP.Fullcalendar.updateDateURL();
}
}
);
@@ -594,71 +594,11 @@ STUDIP.ready(function () {
$('.booking-plan-allday_view').attr('href', url.toString());
}
- function submitDatePicker() {
- var picked = $('#booking-plan-jmpdate').val();
- var iso_date_string = '';
- if(picked) {
- if (picked.includes('.')) {
- let [day, month, year] = picked.split('.');
- iso_date_string = year.padStart(4, "20") + '-' + month.padStart(2, "0") + '-' + day.padStart(2, "0");
- } else if (picked.includes('/')) {
- let [day, month, year] = picked.split('/');
- iso_date_string = year.padStart(4, "20") + '-' + month.padStart(2, "0") + '-' + day.padStart(2, "0");
- } else if (picked.includes('-')) {
- iso_date_string = picked;
- }
- }
- if (iso_date_string) {
- $('*[data-resources-fullcalendar="1"]').each(function () {
- this.calendar.gotoDate(iso_date_string);
- });
- updateDateURL();
- }
- }
-
- function updateDateURL() {
- let changedMoment;
- $('[data-resources-fullcalendar="1"]').each(function () {
- changedMoment = $(this)[0].calendar.getDate();
- });
- if (changedMoment) {
- let changedDate = STUDIP.Fullcalendar.toRFC3339String(changedMoment).split('T')[0];
- //Get the timestamp:
- let timeStamp = changedMoment.getTime() / 1000;
-
- $('a.resource-bookings-actions').each(function () {
- const url = new URL(this.href);
- url.searchParams.set('timestamp', timeStamp)
- url.searchParams.set('defaultDate', changedDate)
- this.href = url.toString();
- });
-
- // Now change the URL of the window.
- const url = new URL(window.location.href);
- url.searchParams.set('defaultDate', changedDate);
-
- // Update url in history
- history.pushState({}, null, url.toString());
-
- // Adjust links accordingly
- url.searchParams.delete('allday');
- $('.booking-plan-std_view').attr('href', url.toString());
-
- url.searchParams.set('allday', 1);
- $('.booking-plan-allday_view').attr('href', url.toString());
-
- // Update sidebar value
- $('#booking-plan-jmpdate').val(changedMoment.toLocaleDateString('de-DE'));
-
- //Store the date in the sessionStorage:
- sessionStorage.setItem('booking_plan_date', changedDate)
- }
- }
jQuery('#booking-plan-jmpdate').datepicker(
{
dateFormat: 'dd.mm.yy',
- onClose: submitDatePicker
+ onClose: STUDIP.Fullcalendar.submitDatePicker
}
);
jQuery('.resource-booking-time-fields input[type="date"]').datepicker(
diff --git a/resources/assets/javascripts/entry-base.js b/resources/assets/javascripts/entry-base.js
index 37bec89..bb03231 100644
--- a/resources/assets/javascripts/entry-base.js
+++ b/resources/assets/javascripts/entry-base.js
@@ -37,7 +37,6 @@ import "./bootstrap/multi_person_search.js"
import "./bootstrap/skip_links.js"
import "./bootstrap/i18n_input.js"
import "./bootstrap/forms.js"
-import "./bootstrap/calendar_dialog.js"
import "./bootstrap/drag_and_drop_upload.js"
import "./bootstrap/admin_sem_classes.js"
import "./bootstrap/cronjobs.js"
diff --git a/resources/assets/javascripts/init.js b/resources/assets/javascripts/init.js
index 8981e95..2d592be 100644
--- a/resources/assets/javascripts/init.js
+++ b/resources/assets/javascripts/init.js
@@ -14,13 +14,13 @@ import Blubber from './lib/blubber.js';
import Browse from './lib/browse.js';
import Cache from './lib/cache.js';
import Calendar from './lib/calendar.js';
-import CalendarDialog from './lib/calendar_dialog.js';
import Clipboard from './lib/clipboard.js';
import Cookie from './lib/cookie.js';
import CourseWizard from './lib/course_wizard.js';
import { createURLHelper } from './lib/url_helper.ts';
import CSS from './lib/css.js';
import Dates from './lib/dates.js';
+import DateTime from './lib/datetime.js';
import Dialog from './lib/dialog.js';
import DragAndDropUpload from './lib/drag_and_drop_upload.js';
import enrollment from './lib/enrollment.js';
@@ -31,6 +31,7 @@ import FilesDashboard from './lib/files_dashboard.js';
import Folders from './lib/folders.js';
import Forms from './lib/forms.js';
import Forum from './lib/forum.js';
+import Fullcalendar from './lib/fullcalendar.js';
import Fullscreen from './lib/fullscreen.js';
import GlobalSearch from './lib/global_search.js';
import HeaderMagic from './lib/header_magic.js';
@@ -101,11 +102,11 @@ window.STUDIP = _.assign(window.STUDIP || {}, {
Browse,
Cache,
Calendar,
- CalendarDialog,
Cookie,
CourseWizard,
CSS,
Dates,
+ DateTime,
Dialog,
DragAndDropUpload,
enrollment,
@@ -116,6 +117,7 @@ window.STUDIP = _.assign(window.STUDIP || {}, {
Folders,
Forms,
Forum,
+ Fullcalendar,
Fullscreen,
Gettext,
GlobalSearch,
diff --git a/resources/assets/javascripts/lib/calendar_dialog.js b/resources/assets/javascripts/lib/calendar_dialog.js
deleted file mode 100644
index e42a149..0000000
--- a/resources/assets/javascripts/lib/calendar_dialog.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import Dialog from './dialog.js';
-
-const CalendarDialog = {
- closeMps: function(form) {
- var added_users = [];
- jQuery('#calendar-manage_access_selectbox option:selected').each(function() {
- added_users[added_users.length] = jQuery(this).attr('value');
- });
- jQuery.ajax({
- url: STUDIP.ABSOLUTE_URI_STUDIP + 'dispatch.php/calendar/single/add_users/',
- data: {
- added_users: added_users
- },
- type: 'post'
- });
- jQuery(form)
- .closest('.ui-dialog-content')
- .dialog('close');
- Dialog.fromURL(jQuery('#calendar-open-manageaccess').attr('href'));
- return false;
- },
-
- removeUser: function(element) {
- var url = jQuery(element).attr('href');
- jQuery(element).removeAttr('href');
- jQuery.ajax({
- url: url,
- type: 'get',
- success: function() {
- var head_tr = jQuery(element)
- .closest('tr')
- .prev('.calendar-user-head');
- jQuery(element)
- .closest('tr')
- .remove();
- if (head_tr.nextUntil('.calendar-user-head').length === 0) {
- head_tr.remove();
- }
- }
- });
- return false;
- },
-
- addException: function() {
- var exc_date = jQuery('#exc-date').val();
- var exists = jQuery('#exc-dates input').is("input[value='" + exc_date + "']");
- if (!exists) {
- var compiled = _.template(
- '<li><label>' +
- '<input type="checkbox" name="del_exc_dates[]" value="<%- excdate %>" style="display: none">' +
- '<span><%- excdate %><img src="' +
- STUDIP.ASSETS_URL +
- 'images/icons/blue/trash.svg' +
- '"></span></label>' +
- '<input type="hidden" name="exc_dates[]" value="<%- excdate %>">' +
- '</li>'
- );
- jQuery('#exc-dates').append(compiled({ excdate: exc_date, link: '' }));
- }
- return false;
- }
-};
-
-export default CalendarDialog;
diff --git a/resources/assets/javascripts/lib/dates.js b/resources/assets/javascripts/lib/dates.js
index 3be67c0..ccb67a8 100644
--- a/resources/assets/javascripts/lib/dates.js
+++ b/resources/assets/javascripts/lib/dates.js
@@ -4,6 +4,7 @@ const Dates = {
termin_id = $('#new_topic')
.closest('[data-termin-id]')
.data().terminId;
+ let course_id = jQuery('#new_topic').closest('[data-course-id]').data().courseId;
if (!topic_name) {
$('#new_topic').focus();
@@ -12,7 +13,8 @@ const Dates = {
$.post(STUDIP.URLHelper.getURL('dispatch.php/course/dates/add_topic'), {
title: topic_name,
- termin_id: termin_id
+ termin_id: termin_id,
+ cid: course_id
}).done(function(response) {
if (response.li !== undefined) {
$('#new_topic')
diff --git a/resources/assets/javascripts/lib/datetime.js b/resources/assets/javascripts/lib/datetime.js
new file mode 100644
index 0000000..d58fac8
--- /dev/null
+++ b/resources/assets/javascripts/lib/datetime.js
@@ -0,0 +1,61 @@
+import { $gettext, $gettextInterpolate } from "./gettext.ts";
+
+
+const DateTime = {
+ /**
+ * A helper method for padding strings with leading zeros.
+ * @param what The date to pad.
+ * @param length The length of the string to output.
+ * @returns {string} A padded version of $what.
+ */
+ pad(what, length = 2) {
+ return `00000000${what}`.substr(-length);
+ },
+
+ /**
+ * Returns an ISO representation of the specified Date object.
+ * in the format YYYY-MM-DD.
+ *
+ * @param date The Date object to format as ISO date.
+ * @returns {string} The ISO date string of the Date object.
+ */
+ getISODate(date) {
+ return date.getFullYear() + '-' + this.pad(date.getMonth() + 1) + '-' + date.getDate();
+ },
+
+ /**
+ * Returns a formatted version of the specified Date object
+ * in the Stud.IP date formatting.
+ *
+ * @param date The Date object to be formatted.
+ * @param relative_value Whether to return a relative time value (true)
+ * or an absolute one (false). Defaults to false.
+ * @param date_only Whether to return the date only (true) or date and time (false).
+ * Defaults to false. Only regarded when $relative_value is false.
+ * @returns {*|string} The date, formatted according to the Stud.IP format for dates.
+ */
+ getStudipDate(date, relative_value = false, date_only = false) {
+ if (relative_value) {
+ let now = Date.now();
+ if (now - date < 1 * 60 * 1000) {
+ return $gettext('Jetzt');
+ }
+ if (now - date < 2 * 60 * 60 * 1000) {
+ return $gettextInterpolate(
+ $gettext('Vor %{ minutes } Minuten'),
+ {minutes: Math.floor((now - date) / (1000 * 60))}
+ );
+ }
+ return this.pad(date.getHours()) + ':' + this.pad(date.getMinutes());
+ }
+
+ if (date_only) {
+ return this.pad(date.getDate()) + '.' + this.pad(date.getMonth() + 1) + '.' + date.getFullYear();
+ }
+
+ return this.pad(date.getDate()) + '.' + this.pad(date.getMonth() + 1) + '.' + date.getFullYear() + ' ' + this.pad(date.getHours()) + ':' + this.pad(date.getMinutes());
+ }
+};
+
+
+export default DateTime;
diff --git a/resources/assets/javascripts/lib/fullcalendar.js b/resources/assets/javascripts/lib/fullcalendar.js
index ef89150..b5bd78d 100644
--- a/resources/assets/javascripts/lib/fullcalendar.js
+++ b/resources/assets/javascripts/lib/fullcalendar.js
@@ -162,6 +162,16 @@ class Fullcalendar
end: this.toRFC3339String(info.event.end)
}
}).fail(info.revert);
+ } else if (info.event.extendedProps.studip_api_urls.resize_dialog) {
+ STUDIP.Dialog.fromURL(
+ info.event.extendedProps.studip_api_urls.resize_dialog,
+ {
+ data: {
+ begin: this.toRFC3339String(info.event.start),
+ end: this.toRFC3339String(info.event.end)
+ }
+ }
+ );
}
}
@@ -242,40 +252,80 @@ class Fullcalendar
var drop_resource_id = info.newResource ? info.newResource.id : info.event.extendedProps.studip_range_id;
- if (info.event.extendedProps.studip_api_urls.move) {
+ if (info.event.extendedProps.studip_api_urls.move || info.event.extendedProps.studip_api_urls.move_dialog) {
+ let move_dialog = info.event.extendedProps.studip_api_urls.move_dialog;
if (info.event.allDay) {
- $.post({
- async: false,
- url: info.event.extendedProps.studip_api_urls.move,
- data: {
- resource_id: drop_resource_id,
- begin: this.toRFC3339String(info.event.start.setHours(0,0,0)),
- end: this.toRFC3339String(info.event.start.setHours(23,59,59))
- }
- }).fail(info.revert);
+ if (move_dialog) {
+ STUDIP.Dialog.fromURL(
+ move_dialog,
+ {
+ data: {
+ resource_id: drop_resource_id,
+ begin: this.toRFC3339String(info.event.start.setHours(0, 0, 0)),
+ end: this.toRFC3339String(info.event.start.setHours(23, 59, 59))
+ }
+ }
+ );
+ } else {
+ jQuery.post({
+ async: false,
+ url: info.event.extendedProps.studip_api_urls.move,
+ data: {
+ resource_id: drop_resource_id,
+ begin: this.toRFC3339String(info.event.start.setHours(0, 0, 0)),
+ end: this.toRFC3339String(info.event.start.setHours(23, 59, 59))
+ }
+ }).fail(info.revert);
+ }
} else if (info.event.end === null) {
- var real_end = new Date();
+ let real_end = new Date();
real_end.setTime(info.event.start.getTime());
real_end.setHours(info.event.start.getHours()+2);
- $.post({
- async: false,
- url: info.event.extendedProps.studip_api_urls.move,
- data: {
- resource_id: drop_resource_id,
- begin: this.toRFC3339String(info.event.start),
- end: this.toRFC3339String(real_end)
- }
- }).fail(info.revert);
+ if (move_dialog) {
+ STUDIP.Dialog.fromURL(
+ move_dialog,
+ {
+ data: {
+ resource_id: drop_resource_id,
+ begin: this.toRFC3339String(info.event.start),
+ end: this.toRFC3339String(real_end)
+ }
+ }
+ );
+ } else {
+ jQuery.post({
+ async: false,
+ url: info.event.extendedProps.studip_api_urls.move,
+ data: {
+ resource_id: drop_resource_id,
+ begin: this.toRFC3339String(info.event.start),
+ end: this.toRFC3339String(real_end)
+ }
+ }).fail(info.revert);
+ }
} else {
- $.post({
- async: false,
- url: info.event.extendedProps.studip_api_urls.move,
- data: {
- resource_id: drop_resource_id,
- begin: this.toRFC3339String(info.event.start),
- end: this.toRFC3339String(info.event.end)
- }
- }).fail(info.revert);
+ if (move_dialog) {
+ STUDIP.Dialog.fromURL(
+ move_dialog,
+ {
+ data: {
+ resource_id: drop_resource_id,
+ begin: this.toRFC3339String(info.event.start),
+ end: this.toRFC3339String(info.event.end)
+ }
+ }
+ );
+ } else {
+ jQuery.post({
+ async: false,
+ url: info.event.extendedProps.studip_api_urls.move,
+ data: {
+ resource_id: drop_resource_id,
+ begin: this.toRFC3339String(info.event.start),
+ end: this.toRFC3339String(info.event.end)
+ }
+ }).fail(info.revert);
+ }
}
}
}
@@ -370,6 +420,12 @@ class Fullcalendar
studip_functions: [],
resourceAreaWidth: '20%',
select (selectionInfo) {
+ let calendar_config = JSON.parse(selectionInfo.view.context.calendar.el.dataset.config);
+ let dialog_size = 'auto';
+ if (calendar_config.dialog_size !== undefined) {
+ dialog_size = calendar_config.dialog_size;
+ }
+
if (!selectionInfo.view.viewSpec.options.editable || !selectionInfo.view.viewSpec.options.studip_urls) {
//The calendar isn't editable.
return;
@@ -380,15 +436,19 @@ class Fullcalendar
data: {
begin: selectionInfo.start.getTime()/1000,
end: selectionInfo.end.getTime()/1000,
- ressource_id: selectionInfo.resource.id
- }
+ ressource_id: selectionInfo.resource.id,
+ all_day: selectionInfo.allDay ? '1' : '0'
+ },
+ size: dialog_size
});
} else {
STUDIP.Dialog.fromURL(selectionInfo.view.viewSpec.options.studip_urls.add, {
data: {
begin: selectionInfo.start.getTime()/1000,
- end: selectionInfo.end.getTime()/1000
- }
+ end: selectionInfo.end.getTime()/1000,
+ all_day: selectionInfo.allDay ? '1' : '0'
+ },
+ size: dialog_size
});
}
}
@@ -421,15 +481,33 @@ class Fullcalendar
if (extended_props.studip_view_urls === undefined) {
return;
}
+ let calendar_config = JSON.parse(eventClickInfo.view.context.calendar.el.dataset.config);
+ let dialog_size = 'auto';
+ if (calendar_config.dialog_size !== undefined) {
+ //Use the configured default dialog size for the fullcalendar instance:
+ dialog_size = calendar_config.dialog_size;
+ }
+ if (extended_props.dialog_size !== undefined) {
+ //Use the dialog size of the event:
+ dialog_size = extended_props.dialog_size;
+ }
if (!event.startEditable && extended_props.studip_view_urls.show) {
STUDIP.Dialog.fromURL(
- STUDIP.URLHelper.getURL(extended_props.studip_view_urls.show)
- );
- } else if (event.startEditable && extended_props.studip_view_urls.edit) {
- STUDIP.Dialog.fromURL(
- STUDIP.URLHelper.getURL(extended_props.studip_view_urls.edit),
- {'size': 'big'}
+ STUDIP.URLHelper.getURL(extended_props.studip_view_urls.show),
+ {size: dialog_size}
);
+ } else if (event.startEditable) {
+ if (extended_props.studip_view_urls.edit) {
+ STUDIP.Dialog.fromURL(
+ STUDIP.URLHelper.getURL(extended_props.studip_view_urls.edit),
+ {size: dialog_size}
+ );
+ } else if (extended_props.studip_view_urls.show) {
+ STUDIP.Dialog.fromURL(
+ STUDIP.URLHelper.getURL(extended_props.studip_view_urls.show),
+ {size: dialog_size}
+ );
+ }
}
return false;
},
@@ -612,6 +690,77 @@ class Fullcalendar
return this.init(node, config);
}
+
+ static submitDatePicker() {
+ let picked_date = jQuery('#booking-plan-jmpdate').val();
+ let booking_plan_datepicker = true;
+ if (!picked_date) {
+ //Not a booking plan date selector.
+ picked_date = jQuery('#date_select').val();
+ booking_plan_datepicker = false;
+ }
+ let iso_date_string = '';
+ if (picked_date) {
+ if (picked_date.includes('.')) {
+ let [day, month, year] = picked_date.split('.');
+ iso_date_string = year.padStart(4, '20') + '-' + month.padStart(2, '0') + '-' + day.padStart(2, '0');
+ } else if (picked_date.includes('/')) {
+ let [day, month, year] = picked_date.split('/');
+ iso_date_string = year.padStart(4, '20') + '-' + month.padStart(2, '0') + '-' + day.padStart(2, '0');
+ } else if (picked_date.includes('-')) {
+ iso_date_string = picked_date;
+ }
+ }
+ if (iso_date_string) {
+ jQuery('[data-fullcalendar="1"],[data-resources-fullcalendar="1"]').each(function () {
+ this.calendar.gotoDate(iso_date_string);
+ });
+ if (booking_plan_datepicker) {
+ Fullcalendar.updateDateURL();
+ }
+ }
+ }
+
+ static updateDateURL() {
+ let changedMoment;
+ jQuery('[data-fullcalendar="1"],[data-resources-fullcalendar="1"]').each(function () {
+ changedMoment = this.calendar.getDate();
+ });
+ if (changedMoment) {
+ let changed_date = STUDIP.Fullcalendar.toRFC3339String(changedMoment).split('T')[0];
+ //Get the timestamp:
+ let timestamp = changedMoment.getTime() / 1000;
+
+ jQuery('a.resource-bookings-actions').each(function () {
+ const url = new URL(this.href);
+ url.searchParams.set('timestamp', timestamp)
+ url.searchParams.set('defaultDate', changed_date)
+ this.href = url.toString();
+ });
+
+ // Now change the URL of the window.
+ const url = new URL(window.location.href);
+ url.searchParams.set('defaultDate', changed_date);
+
+ // Update url in history
+ history.pushState({}, null, url.toString());
+
+ // Adjust links accordingly
+ url.searchParams.delete('allday');
+ jQuery('.booking-plan-std_view').attr('href', url.toString());
+
+ url.searchParams.set('allday', 1);
+ jQuery('.booking-plan-allday_view').attr('href', url.toString());
+
+ // Update sidebar value
+ let element = jQuery('#booking-plan-jmpdate,#date_select').first();
+ element.val(changedMoment.toLocaleDateString('de-DE'));
+ if (element.is('#booking-plan-jmpdate')) {
+ //Store the date in the sessionStorage:
+ sessionStorage.setItem('booking_plan_date', changed_date);
+ }
+ }
+ }
}
export default Fullcalendar;
diff --git a/resources/assets/stylesheets/highcontrast.scss b/resources/assets/stylesheets/highcontrast.scss
index 6c822b1..47bfbf2 100644
--- a/resources/assets/stylesheets/highcontrast.scss
+++ b/resources/assets/stylesheets/highcontrast.scss
@@ -515,26 +515,6 @@ form.default fieldset.collapsable.collapsed legend {
}
/* Stundenplan / Terminkalender */
-.celltoday {
- background-color: $white;
- border: 1px solid $black;
-
- > a {
- color: $black;
- font-size: 1.5em;
- }
-}
-a:link.calhead {
- color: $contrast-blue;
-}
-
-.calhead label {
- color: $contrast-blue !important;
-
- &:hover {
- text-decoration: underline;
- }
-}
a .hidden-tiny-down {
color: $contrast-blue !important;
@@ -566,40 +546,6 @@ a .hidden-tiny-down {
/* Calendar categories */
-span li.calendar-category1,
-ul li.calendar-category1,
-span li.calendar-category2,
-ul li.calendar-category2,
-span li.calendar-category3,
-ul li.calendar-category3,
-span li.calendar-category4,
-ul li.calendar-category4,
-span li.calendar-category5,
-ul li.calendar-category5,
-span li.calendar-category6,
-ul li.calendar-category6,
-span li.calendar-category7,
-ul li.calendar-category7,
-span li.calendar-category8,
-ul li.calendar-category8,
-span li.calendar-category9,
-ul li.calendar-category9,
-span li.calendar-category10,
-ul li.calendar-category10,
-span li.calendar-category11,
-ul li.calendar-category11,
-span li.calendar-category12,
-ul li.calendar-category12,
-span li.calendar-category13,
-ul li.calendar-category13,
-span li.calendar-category14,
-ul li.calendar-category14,
-span li.calendar-category15,
-ul li.calendar-category15 {
- color: $black;
-
-}
-
div.schedule_entry {
dl {
&.hover:hover { opacity: unset; }
@@ -772,244 +718,6 @@ div.schedule_entry {
}
}
-table.calendar-week,
-table.calendar-day {
- tbody tr td {
- &.calendar-day-event {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-day-event;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-day-event;
- }
- &.calendar-category1,
- &.calendar-course-category5 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-1;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-category-1;
- }
- &.calendar-category2,
- &.calendar-course-category1 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-2;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-category-2;
- }
- &.calendar-category3,
- &.calendar-course-category2 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-3;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-category-3;
- }
- &.calendar-category4,
- &.calendar-course-category3 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-4;
- overflow: hidden;
- color: $black;
- }
- background: $white;
- border: solid 1px $calendar-category-4;
- }
- &.calendar-category5,
- &.calendar-course-category4 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-5;
- overflow: hidden;
- color: $black;
- }
- background: $white;
- border: solid 1px $calendar-category-5;
- }
- &.calendar-category6,
- &.calendar-course-category6 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-6;
- overflow: hidden;
- color: $black;
- }
- background: $white;
- border: solid 1px $calendar-category-6;
- }
- &.calendar-category7,
- &.calendar-course-category8 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-7;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-category-7;
- }
- &.calendar-category8,
- &.calendar-course-category9 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-8;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-category-8;
- }
- &.calendar-category9,
- &.calendar-course-category10 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-9;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-category-9;
- }
- &.calendar-category10,
- &.calendar-course-category11 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-10;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-category-10;
- }
- &.calendar-category11,
- &.calendar-course-category12 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-11;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-category-11;
- }
- &.calendar-category12,
- &.calendar-course-category13 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-12;
- overflow: hidden;
- color: $black;
- }
- background: $white;
- border: solid 1px $calendar-category-12;
- }
- &.calendar-category13,
- &.calendar-course-category14 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-13;
- overflow: hidden;
- color: $black;
- }
- background: $white;
- border: solid 1px $calendar-category-13;
- }
- &.calendar-category14,
- &.calendar-course-category15 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-14;
- overflow: hidden;
- color: $black;
- }
- background: $white;
- border: solid 1px $calendar-category-14;
- }
- &.calendar-category15,
- &.calendar-course-category7 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-15;
- overflow: hidden;
- color: $black;
- }
- background: $white;
- border: solid 1px $calendar-category-15;
- }
- &.calendar-category255,
- &.calendar-course-category255 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $calendar-category-255;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $calendar-category-255;
- }
- /* Termin von im Stundenplan vorgemerkter Kurs */
- &.calendar-course-category256 {
- a {
- color: $contrast-blue !important;
- }
- div:first-child {
- background-color: $contrast-blue-medium;
- overflow: hidden;
- }
- background: $white;
- border: solid 1px $contrast-blue-medium;
- }
- }
-}
-
-
-/* links */
-div.index_container div.index_main nav div.login_link a {
- text-decoration: underline;
-
- p {
- color: $black !important;
- }
-}
-
/* underlined links only in main content,not in navigation */
a,
a:link,
diff --git a/resources/assets/stylesheets/less/calendar.less b/resources/assets/stylesheets/less/calendar.less
deleted file mode 100644
index edb2e57..0000000
--- a/resources/assets/stylesheets/less/calendar.less
+++ /dev/null
@@ -1,587 +0,0 @@
-// TODO: LESSify
-
-/* --- Styles fuer Terminkalender ------------------------------------------- */
-a.day {
- font-weight: bold;
-}
-
-a.sday {
- color: var(--red);
- font-weight: bold;
-}
-
-a.hday {
- color: var(--red-80);
- font-weight: bold;
-}
-
-span.kwmin {
- color: var(--dark-gray-color-80);
- font-weight: bold;
-}
-
-a.lightday {
- color: var(--base-color-40);
- font-weight: bold;
-}
-
-a.lightsday {
- color: var(--red-40);
- font-weight: bold;
-}
-
-.inday {
- font-size: 8pt;
-}
-
-.precol1w {
- font-size: 12pt;
- font-weight: bold;
- color: var(--light-gray-color);
- text-align: center;
- vertical-align: top;
-}
-
-.precol2w {
- font-size: 8pt;
- font-weight: bold;
- color: var(--light-gray-color);
- text-align: center;
-}
-
-td.calhead, div.calhead {
- font-size: 18pt;
- font-weight: bold;
- color: var(--light-gray-color);
- text-align: center;
-}
-
-a:link.calhead {
- color: var(--base-color-60);
- white-space: nowrap;
- font-weight: bold;
-}
-
-a:hover.calhead {
- color: var(--red-60);
-}
-
-.calhead label {
- cursor: pointer;
- &:hover {
- color: var(--base-color-40);
- }
-
- .media-breakpoint-small-down({
- .button();
-
- img {
- padding-left: 0.5em;
- vertical-align: middle;
- }
- })
-}
-
-.celltoday {
- background-color: var(--red-20);
-}
-
-td.weekend {
- background-color: var(--dark-gray-color-15);
-}
-
-td.weekday {
- background-color: var(--dark-gray-color-5);
-}
-
-td.current {
- padding: 2px;
- border: 2px solid var(--red);
-}
-
-table.calendar-week, table.calendar-day {
- border-spacing: 0;
- table-layout: fixed;
- td {
- padding: 0;
- }
-}
-
-table.calendar-month {
- width: 100%;
- tr td {
- max-width: 90px;
- min-width: 90px;
- vertical-align: top;
- }
-}
-
-td.month {
- background-color: fadeout(darken(@dark-gray-color-15, 5), 30);
- padding: 3px;
- div {
- width: 90%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-}
-
-td.lightmonth {
- background-color: fadeout(darken(@dark-gray-color-5, 5), 30);
- padding: 3px;
- div {
- width: 90%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-}
-
-table.calendar-month td.calendar-month-week {
- text-align: center;
- vertical-align: middle;
- height: 80px;
- width: 80px;
-}
-
-td.weekdayevents {
- width: 90px;
-}
-
-nav.calendar-nav {
- display: flex;
- align-items: center;
- padding-bottom: 1em;
-
- > div {
- flex: 1 1 auto;
- }
-
- .calhead {
- color: var(--base-color);
- }
-}
-
-.calendar-day-edit {
- text-align: right;
- font-size: 0.8em;
-}
-
-.calendar-week tbody tr, .calendar-day tbody tr {
- transition: background-color 0.3s;
- &:hover {
- background-color: fadeout(@dark-gray-color-5, 40%);
- }
- & td {
- padding: 3px;
- border-bottom: 1px solid var(--dark-gray-color-5);
- }
-}
-
-.calendar-day-event-title {
- overflow: hidden;
- text-overflow: ellipsis;
- a {
- color: var(--dark-gray-color);
- }
-}
-
-.calendar-category-mixin(@color-bg) {
- vertical-align: top;
- font-size: 11px;
- color: var(--white);
- padding: 0;
- /* necessary for All-day Events */
- a {
- color: contrast(@color-bg, black, white, 60%);
- font-weight: 600;
- }
-}
-
-.calendar-single-year {
- .calendar-single-year--table {
-
- > thead th {
- min-width: 5em;
- text-align: left;
- }
-
- .yday {
- white-space: nowrap;
- }
- }
-}
-/* --- Coloring Styles for Personal TERMIN Categories ---------------------------------------------- */
-select, ul, span {
- option, li, span, input[type="radio"] {
- &.calendar-category1 {
- color: @calendar-category-1;
- }
- &.calendar-category2 {
- color: @calendar-category-2;
- }
- &.calendar-category3 {
- color: @calendar-category-3;
- }
- &.calendar-category4 {
- color: @calendar-category-4;
- }
- &.calendar-category5 {
- color: @calendar-category-5;
- }
- &.calendar-category6 {
- color: @calendar-category-6;
- }
- &.calendar-category7 {
- color: @calendar-category-7;
- }
- &.calendar-category8 {
- color: @calendar-category-8;
- }
- &.calendar-category9 {
- color: @calendar-category-9;
- }
- &.calendar-category10 {
- color: @calendar-category-10;
- }
- &.calendar-category11 {
- color: @calendar-category-11;
- }
- &.calendar-category12 {
- color: @calendar-category-12;
- }
- &.calendar-category13 {
- color: @calendar-category-13;
- }
- &.calendar-category14 {
- color: @calendar-category-14;
- }
- &.calendar-category15 {
- color: @calendar-category-15;
- }
- &.calendar-category255 {
- color: @calendar-category-255;
- }
- }
-}
-
-table.calendar-week, table.calendar-day {
- & tbody tr td {
- &.calendar-day-event {
- div:first-child {
- background-color: @calendar-day-event;
- overflow: hidden;
- }
- background: @calendar-day-event-aux;
- border: solid 1px @calendar-day-event;
- .calendar-category-mixin(@calendar-day-event-aux);
- }
- &.calendar-category1, &.calendar-course-category5 {
- div:first-child {
- background-color: @calendar-category-1;
- overflow: hidden;
- }
- background: @calendar-category-1-aux;
- border: solid 1px @calendar-category-1;
- .calendar-category-mixin(@calendar-category-1-aux);
- }
- &.calendar-category2, &.calendar-course-category1 {
- div:first-child {
- background-color: @calendar-category-2;
- overflow: hidden;
- }
- background: @calendar-category-2-aux;
- border: solid 1px @calendar-category-2;
- .calendar-category-mixin(@calendar-category-2-aux);
- }
- &.calendar-category3, &.calendar-course-category2 {
- div:first-child {
- background-color: @calendar-category-3;
- overflow: hidden;
- }
- background: @calendar-category-3-aux;
- border: solid 1px @calendar-category-3;
- .calendar-category-mixin(@calendar-category-3-aux);
- }
- &.calendar-category4, &.calendar-course-category3 {
- div:first-child {
- background-color: @calendar-category-4;
- overflow: hidden;
- }
- background: @calendar-category-4-aux;
- border: solid 1px @calendar-category-4;
- .calendar-category-mixin(@calendar-category-4-aux);
- }
- &.calendar-category5, &.calendar-course-category4 {
- div:first-child {
- background-color: @calendar-category-5;
- overflow: hidden;
- }
- background: @calendar-category-5-aux;
- border: solid 1px @calendar-category-5;
- .calendar-category-mixin(@calendar-category-5-aux);
- }
- &.calendar-category6, &.calendar-course-category6 {
- div:first-child {
- background-color: @calendar-category-6;
- overflow: hidden;
- }
- background: @calendar-category-6-aux;
- border: solid 1px @calendar-category-6;
- .calendar-category-mixin(@calendar-category-6-aux);
- }
- &.calendar-category7, &.calendar-course-category8 {
- div:first-child {
- background-color: @calendar-category-7;
- overflow: hidden;
- }
- background: @calendar-category-7-aux;
- border: solid 1px @calendar-category-7;
- .calendar-category-mixin(@calendar-category-7-aux);
- }
- &.calendar-category8, &.calendar-course-category9 {
- div:first-child {
- background-color: @calendar-category-8;
- overflow: hidden;
- }
- background: @calendar-category-8-aux;
- border: solid 1px @calendar-category-8;
- .calendar-category-mixin(@calendar-category-8-aux);
- }
- &.calendar-category9, &.calendar-course-category10 {
- div:first-child {
- background-color: @calendar-category-9;
- overflow: hidden;
- }
- background: @calendar-category-9-aux;
- border: solid 1px @calendar-category-9;
- .calendar-category-mixin(@calendar-category-9-aux);
- }
- &.calendar-category10, &.calendar-course-category11 {
- div:first-child {
- background-color: @calendar-category-10;
- overflow: hidden;
- }
- background: @calendar-category-10-aux;
- border: solid 1px @calendar-category-10;
- .calendar-category-mixin(@calendar-category-10-aux);
- }
- &.calendar-category11, &.calendar-course-category12 {
- div:first-child {
- background-color: @calendar-category-11;
- overflow: hidden;
- }
- background: @calendar-category-11-aux;
- border: solid 1px @calendar-category-11;
- .calendar-category-mixin(@calendar-category-11-aux);
- }
- &.calendar-category12, &.calendar-course-category13 {
- div:first-child {
- background-color: @calendar-category-12;
- overflow: hidden;
- }
- background: @calendar-category-12-aux;
- border: solid 1px @calendar-category-12;
- .calendar-category-mixin(@calendar-category-12-aux);
- }
- &.calendar-category13, &.calendar-course-category14 {
- div:first-child {
- background-color: @calendar-category-13;
- overflow: hidden;
- }
- background: @calendar-category-13-aux;
- border: solid 1px @calendar-category-13;
- .calendar-category-mixin(@calendar-category-13-aux);
- }
- &.calendar-category14, &.calendar-course-category15 {
- div:first-child {
- background-color: @calendar-category-14;
- overflow: hidden;
- }
- background: @calendar-category-14-aux;
- border: solid 1px @calendar-category-14;
- .calendar-category-mixin(@calendar-category-14-aux);
- }
- &.calendar-category15, &.calendar-course-category7 {
- div:first-child {
- background-color: @calendar-category-15;
- overflow: hidden;
- }
- background: @calendar-category-15-aux;
- border: solid 1px @calendar-category-15;
- .calendar-category-mixin(@calendar-category-15-aux);
- }
- &.calendar-category255, &.calendar-course-category255 {
- div:first-child {
- background-color: @calendar-category-255;
- overflow: hidden;
- }
- background: @calendar-category-255-aux;
- border: solid 1px @calendar-category-255;
- .calendar-category-mixin(@calendar-category-255-aux);
- }
- /* Termin von im Stundenplan vorgemerkter Kurs */
- &.calendar-course-category256 {
- div:first-child {
- background-color: #2D2C64;
- overflow: hidden;
- }
- background: mix(#2D2C64, #fff, 60%);
- border: solid 1px #2D2C64;
- .calendar-category-mixin(mix(#2D2C64, #fff, 60%));
- }
- }
-}
-
-a.calendar-event-text1,
-a.Calendar-course-event-text5 {
- color: @calendar-category-1;
-}
-
-a.calendar-event-text2,
-a.Calendar-course-event-text1{
- color: @calendar-category-2;
-}
-
-a.calendar-event-text3,
-a.Calendar-course-event-text2 {
- color: @calendar-category-3;
-}
-
-a.calendar-event-text4,
-a.Calendar-course-event-text3 {
- color: @calendar-category-4;
-}
-
-a.calendar-event-text5,
-a.Calendar-course-event-text4 {
- color: @calendar-category-5;
-}
-
-a.calendar-event-text6,
-a.Calendar-course-event-text6 {
- color: @calendar-category-6;
-}
-
-a.calendar-event-text7 {
- color: @calendar-category-7;
-}
-
-a.calendar-event-text8 {
- color: @calendar-category-8;
-}
-
-a.calendar-event-text9 {
- color: @calendar-category-9;
-}
-
-a.calendar-event-text10 {
- color: @calendar-category-10;
-}
-
-a.calendar-event-text11 {
- color: @calendar-category-11;
-}
-
-a.calendar-event-text12 {
- color: @calendar-category-12;
-}
-
-a.calendar-event-text13 {
- color: @calendar-category-13;
-}
-
-a.calendar-event-text14 {
- color: @calendar-category-14;
-}
-
-a.calendar-event-text15,
-a.Calendar-course-event-text7 {
- color: @calendar-category-15;
-}
-
-a.calendar-event-text255,
-a.Calendar-course-event-text255 {
- color: @calendar-category-255;
-}
-.calendar-tooltip {
- display: none;
- font-size: 0.8em;
-}
-
-.calendar-group-events {
- background: linear-gradient(to right, var(--base-color-60), var(--content-color-60)) repeat-x var(--base-color-60);
- border: solid 1px var(--base-gray);
-}
-
-#exc-dates {
- padding: 2px;
- list-style-type: none;
- width: 7.5em;
- min-height: 5em;
- max-height: 10em;
- overflow: auto;
- border: 1px solid var(--dark-gray-color-60);
-
- img {
- vertical-align: text-top;
- }
- input:checked ~ span {
- text-decoration: line-through;
- opacity: 0.6;
- }
-}
-
-/* --- Styles fuer TerminZeile ---------------------------------------------- */
-table.tabdaterow {
- background-color: white;
-}
-
-td.tddaterowp {
- border: 1px solid var(--white);
- background-color: var(--dark-gray-color-10);
- font-weight: bold;
- color: var(--dark-green);
- font-size: 8pt;
-}
-
-td.tddaterowpx {
- border: 1px solid var(--active-color);
- background-color: var(--dark-gray-color-10);
- font-weight: bold;
- color: var(--dark-green);
- font-size: 8pt;
-}
-
-
-.recurrences {
- width: 100%;
- float: none;
- list-style: none;
- text-align: left;
- position: relative;
- padding: 0;
- margin: 0;
- li {
- display: block;
- width: 100%;
- }
- input.rec-select {
- }
- .rec-label {
- cursor: pointer;
- }
- .rec-label:hover {
- }
- .rec-content {
- display: none;
- position: relative;
- padding-left: 3em;
- }
- [id^="rec"]:checked + label.rec-label {
- }
- [id^="rec"]:checked ~ [id^="rec-content"] {
- display: block;
- }
-}
diff --git a/resources/assets/stylesheets/scss/calendar.scss b/resources/assets/stylesheets/scss/calendar.scss
new file mode 100644
index 0000000..4ad94b8
--- /dev/null
+++ b/resources/assets/stylesheets/scss/calendar.scss
@@ -0,0 +1,135 @@
+.fc-body {
+ .fc-event {
+
+ background-color: #fff;
+ color: #000;
+ border-width: 2px;
+
+ &:hover {
+ color: #000;
+ }
+
+ &.course-color-0 {
+ border-color: $group-color-0;
+ background-color: lighten($group-color-0, 45%);
+
+ &:hover {
+ background-color: lighten($group-color-0, 50%);
+ }
+
+ .fc-time {
+ border-bottom: 1px solid $group-color-0;
+ }
+ }
+
+ &.course-color-1 {
+ border-color: $group-color-1;
+ background-color: lighten($group-color-1, 45%);
+
+ &:hover {
+ background-color: lighten($group-color-1, 50%);
+ }
+
+ .fc-time {
+ border-bottom: 1px solid $group-color-1;
+ }
+ }
+
+ &.course-color-2 {
+ border-color: $group-color-2;
+ background-color: lighten($group-color-2, 45%);
+
+ &:hover {
+ background-color: lighten($group-color-2, 50%);
+ }
+
+ .fc-time {
+ border-bottom: 1px solid $group-color-2;
+ }
+ }
+
+ &.course-color-3 {
+ border-color: $group-color-3;
+ background-color: lighten($group-color-3, 45%);
+
+ &:hover {
+ background-color: lighten($group-color-3, 50%);
+ }
+
+ .fc-time {
+ border-bottom: 1px solid $group-color-3;
+ }
+ }
+
+ &.course-color-4 {
+ border-color: $group-color-4;
+ background-color: lighten($group-color-4, 45%);
+
+ &:hover {
+ background-color: lighten($group-color-4, 50%);
+ }
+
+ .fc-time {
+ border-bottom: 1px solid $group-color-4;
+ }
+ }
+
+ &.course-color-5 {
+ border-color: $group-color-5;
+ background-color: lighten($group-color-5, 45%);
+
+ &:hover {
+ background-color: lighten($group-color-5, 50%);
+ }
+
+ .fc-time {
+ border-bottom: 1px solid $group-color-5;
+ }
+ }
+
+ &.course-color-6 {
+ border-color: $group-color-6;
+ background-color: lighten($group-color-6, 45%);
+
+ &:hover {
+ background-color: lighten($group-color-6, 50%);
+ }
+
+ .fc-time {
+ border-bottom: 1px solid $group-color-6;
+ }
+ }
+
+ &.course-color-7 {
+ border-color: $group-color-7;
+ background-color: lighten($group-color-7, 45%);
+
+ &:hover {
+ background-color: lighten($group-color-7, 50%);
+ }
+
+ .fc-time {
+ border-bottom: 1px solid $group-color-7;
+ }
+ }
+
+ &.course-color-8 {
+ border-color: $group-color-8;
+ background-color: lighten($group-color-8, 45%);
+
+ &:hover {
+ background-color: lighten($group-color-8, 50%);
+ }
+
+ .fc-time {
+ border-bottom: 1px solid $group-color-8;
+ }
+ }
+ }
+}
+
+
+/* special rule for the month view: do not underline the time */
+.fc-view.fc-dayGridMonth-view .fc-event .fc-time {
+ border: none;
+}
diff --git a/resources/assets/stylesheets/scss/forms.scss b/resources/assets/stylesheets/scss/forms.scss
index 0b43f04..fc3eb4b 100644
--- a/resources/assets/stylesheets/scss/forms.scss
+++ b/resources/assets/stylesheets/scss/forms.scss
@@ -37,7 +37,7 @@ form.default {
font-style: italic;
}
- input[type=date], input[type=email], input[type=number],
+ input[type=date], input[type=datetime-local], input[type=email], input[type=number],
input[type=password], input[type=text], input[type=time], input[type=url], input[type=tel],
textarea, select {
box-sizing: border-box;
@@ -86,10 +86,14 @@ form.default {
max-width: $max-width-m;
}
- input[type=date], input[type=number], input[type=time], input[type=tel]:not(.size-m) {
+ input[type=date].hasDatepicker, input[type=date][data-date-picker], input[type=number], input[type=time], input[type=tel]:not(.size-m) {
max-width: $max-width-s;
}
+ input[type=date]:not(.hasDatepicker, [data-date-picker]) {
+ max-width: $max-width-m;
+ }
+
textarea {
min-height: 6em;
}
@@ -533,6 +537,17 @@ form.default {
margin-left: 10px;
}
}
+
+ .input-with-icon {
+ input {
+ display: inline;
+ width: calc(100% - 24px);
+ }
+ img.icon {
+ height: 2em;
+ margin-top: 0.5ex;
+ }
+ }
}
form.narrow {
diff --git a/resources/assets/stylesheets/studip.less b/resources/assets/stylesheets/studip.less
index 1a2c794..aee6779 100644
--- a/resources/assets/stylesheets/studip.less
+++ b/resources/assets/stylesheets/studip.less
@@ -12,7 +12,6 @@
@import "less/tables.less";
@import "less/buttons.less";
@import "less/messagebox.less";
-@import "less/calendar.less";
@import "less/schedule.less";
@import "less/files.less";
diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss
index b0e768e..606fa07 100644
--- a/resources/assets/stylesheets/studip.scss
+++ b/resources/assets/stylesheets/studip.scss
@@ -22,6 +22,7 @@
@import "scss/blockquote.scss";
@import "scss/blubber";
@import "scss/buttons";
+@import "scss/calendar";
@import "scss/clipboard";
@import "scss/consultation";
@import "scss/contacts";
diff --git a/resources/vue/base-components.js b/resources/vue/base-components.js
index b8cf935..2390bb9 100644
--- a/resources/vue/base-components.js
+++ b/resources/vue/base-components.js
@@ -1,6 +1,11 @@
+import CalendarPermissionsTable from "./components/form_inputs/CalendarPermissionsTable.vue";
+import DayOfWeekSelect from './components/form_inputs/DayOfWeekSelect.vue';
+import DateListInput from './components/form_inputs/DateListInput.vue';
import Multiselect from './components/Multiselect.vue';
+import MyCoursesColouredTable from './components/form_inputs/MyCoursesColouredTable.vue';
import EditableList from "./components/EditableList.vue";
import Quicksearch from './components/Quicksearch.vue';
+import RepetitionInput from "./components/form_inputs/RepetitionInput.vue";
import SidebarWidget from './components/SidebarWidget.vue';
import StudipActionMenu from './components/StudipActionMenu.vue';
import StudipAssetImg from './components/StudipAssetImg.vue';
@@ -10,6 +15,7 @@ import StudipFileSize from './components/StudipFileSize.vue';
import StudipFolderSize from './components/StudipFolderSize.vue';
import StudipIcon from './components/StudipIcon.vue';
import RangeInput from './components/RangeInput.vue';
+import Datepicker from './components/Datepicker.vue';
import Datetimepicker from './components/Datetimepicker.vue';
import TextareaWithToolbar from './components/TextareaWithToolbar.vue';
import I18nTextarea from "./components/I18nTextarea.vue";
@@ -23,14 +29,20 @@ import StudipSelect from './components/StudipSelect.vue';
import StudipMultiPersonSearch from './components/StudipMultiPersonSearch.vue';
const BaseComponents = {
+ CalendarPermissionsTable,
+ DayOfWeekSelect,
+ DateListInput,
Multiselect,
+ MyCoursesColouredTable,
EditableList,
Quicksearch,
RangeInput,
+ RepetitionInput,
SidebarWidget,
StudipActionMenu,
StudipAssetImg,
StudipDateTime,
+ Datepicker,
Datetimepicker,
StudipDialog,
StudipFileSize,
diff --git a/resources/vue/components/Datepicker.vue b/resources/vue/components/Datepicker.vue
new file mode 100644
index 0000000..3db44ce
--- /dev/null
+++ b/resources/vue/components/Datepicker.vue
@@ -0,0 +1,76 @@
+<template>
+ <span>
+ <input type="hidden" :name="name" :value="value">
+ <input type="text"
+ ref="visibleInput"
+ class="visible_input"
+ @change="setUnixTimestamp"
+ v-bind="$attrs"
+ v-on="$listeners">
+ </span>
+</template>
+
+<script>
+export default {
+ name: "datepicker",
+ inheritAttrs: false,
+ props: {
+ name: {
+ type: String,
+ required: false
+ },
+ value: {
+ required: false
+ },
+ mindate: {
+ required: false
+ },
+ maxdate: {
+ required: false
+ }
+ },
+ methods: {
+ setUnixTimestamp () {
+ let formatted_date = this.$refs.visibleInput.value;
+ let date = formatted_date.match(/(\d+)/g);
+ date = new Date(`${date[2]}-${date[1]}-${date[0]} ${date[3]}:${date[4]}`);
+ this.$emit('input', Math.floor(date / 1000));
+ }
+ },
+ mounted () {
+ let value = !isNaN(parseInt(this.value, 10)) ? parseInt(this.value, 10) : this.value;
+ if (Number.isInteger(value)) {
+ let date = new Date(value * 1000);
+ let formatted_date =
+ (date.getDate() < 10 ? "0" : "") + date.getDate()
+ + "."
+ + (date.getMonth() < 9 ? "0" : "") + (date.getMonth() + 1)
+ + "."
+ + date.getFullYear();
+ this.$refs.visibleInput.value = formatted_date;
+ } else {
+ this.$refs.visibleInput.value = value;
+ }
+ let params = {
+ onSelect: () => {
+ this.setUnixTimestamp();
+ }
+ };
+ if (this.mindate) {
+ params.minDate = new Date(this.mindate * 1000)
+ }
+ if (this.maxdate) {
+ params.maxDate = new Date(this.maxdate * 1000)
+ }
+ $(this.$refs.visibleInput).datetimepicker(params);
+ },
+ watch: {
+ mindate (new_data, old_data) {
+ $(this.$refs.visibleInput).datetimepicker('option', 'minDate', new Date(new_data * 1000));
+ },
+ maxdate (new_data, old_data) {
+ $(this.$refs.visibleInput).datetimepicker('option', 'maxDate', new Date(new_data * 1000));
+ }
+ }
+}
+</script>
diff --git a/resources/vue/components/EditableList.vue b/resources/vue/components/EditableList.vue
index c76b400..cf1716b 100644
--- a/resources/vue/components/EditableList.vue
+++ b/resources/vue/components/EditableList.vue
@@ -78,7 +78,7 @@ export default {
return {
resort: false, //this is just for triggering the computed property sortedItems to be sorted again
preventChangeOfQuickselect: false,
- allItems: this.items
+ allItems: this.items ?? []
};
},
methods: {
@@ -159,8 +159,8 @@ export default {
if (a.icon === b.icon) {
return a.name.localeCompare(b.name);
} else {
- let a_icon = a.icon || '';
- let b_icon = b.icon || '';
+ let a_icon = typeof a.icon === 'string' ? a.icon : '';
+ let b_icon = typeof b.icon === 'string' ? b.icon : '';
if (this.category_order.indexOf(a_icon) > -1 && this.category_order.indexOf(b_icon) > -1) {
return this.category_order.indexOf(a_icon) < this.category_order.indexOf(b_icon) ? -1 : 1;
} else {
diff --git a/resources/vue/components/StudipDateTime.vue b/resources/vue/components/StudipDateTime.vue
index 1cf852c..dfdc0c3 100644
--- a/resources/vue/components/StudipDateTime.vue
+++ b/resources/vue/components/StudipDateTime.vue
@@ -5,9 +5,6 @@
</template>
<script>
- function pad(what, length = 2) {
- return `00000000${what}`.substr(-length);
- }
export default {
name: 'studip-date-time',
@@ -17,6 +14,11 @@
type: Boolean,
required: false,
default: false
+ },
+ date_only: {
+ type: Boolean,
+ required: false,
+ default: false
}
},
computed: {
@@ -40,18 +42,8 @@
return `Should be integer: ${this.timestamp}`;
}
let date = new Date(this.timestamp * 1000);
- let now = Date.now();
- if (!force_absolute && this.relative && this.display_relative()) {
- if (now - date < 1 * 60 * 1000) {
- return this.$gettext('Jetzt');
- }
- if (now - date < 2 * 60 * 60 * 1000) {
- return this.$gettext('Vor %s Minuten').replace('%s', Math.floor((now - date) / (1000 * 60)));
- }
- return pad(date.getHours()) + ':' + pad(date.getMinutes());
- } else {
- return pad(date.getDate()) + '.' + pad(date.getMonth() + 1) + '.' + date.getFullYear() + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes());
- }
+ let relative_value = !force_absolute && this.relative && this.display_relative();
+ return STUDIP.DateTime.getStudipDate(date, relative_value, this.date_only);
}
},
mounted: function () {
diff --git a/resources/vue/components/form_inputs/CalendarPermissionsTable.vue b/resources/vue/components/form_inputs/CalendarPermissionsTable.vue
new file mode 100644
index 0000000..a0a76a2
--- /dev/null
+++ b/resources/vue/components/form_inputs/CalendarPermissionsTable.vue
@@ -0,0 +1,86 @@
+<template>
+ <div class="formpart">
+ <quicksearch v-if="searchtype" :searchtype="searchtype" name="qs" @input="addContact"
+ :placeholder="$gettext('Personen hinzufügen')"></quicksearch>
+ <table class="default">
+ <caption>{{ $gettext('Kontakte, mit denen der Kalender geteilt wird')}}</caption>
+ <thead>
+ <tr>
+ <th>{{ $gettext('Name') }}</th>
+ <th>{{ $gettext('Schreibzugriff') }}</th>
+ <th class="actions">{{ $gettext('Nicht mehr teilen') }}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-if="this.users.length === 0">
+ <td colspan="3">
+ <studip-message-box type="info">
+ {{ $gettext('Der Kalender wird mit keinem Kontakt geteilt.') }}
+ </studip-message-box>
+ </td>
+ </tr>
+ <tr v-for="user in this.users" :key="user.id">
+ <td>
+ <input type="hidden" :name="name + '_permissions[]'"
+ :value="user.id">
+ {{ user.name }}
+ </td>
+ <td>
+ <input type="checkbox" :name="name + '_write_permissions[]'" :value="user.id"
+ v-model="user.write_permissions"
+ :aria-label="$gettextInterpolate(
+ $gettext('Schreibzugriff für %{name}'),
+ {name: user.name}
+ )">
+ </td>
+ <td class="actions">
+ <studip-icon shape="trash" aria-role="button" @click="removeContact(user.id)"
+ :title="$gettextInterpolate(
+ $gettext('Kalender nicht mehr mit %{name} teilen'),
+ {name: user.name}
+ )"></studip-icon>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</template>
+
+<script>
+import StudipMessageBox from "../StudipMessageBox.vue";
+
+export default {
+ name: "calendar-permissions-table",
+ components: {StudipMessageBox},
+ props: {
+ name: {
+ type: String,
+ required: true
+ },
+ selected_users: {
+ type: Object,
+ required: false,
+ default: () => {},
+ },
+ searchtype: {
+ type: String,
+ required: true,
+ }
+ },
+ data() {
+ return {
+ users: {...this.selected_users},
+ }
+ },
+ methods: {
+ addContact(user_id, name) {
+ this.$set(this.users, user_id, {id: user_id, name: name, write_permissions: false});
+ },
+ removeContact(user_id) {
+ if (this.users[user_id] !== undefined) {
+ this.$delete(this.users, user_id);
+ }
+ }
+ }
+}
+</script>
diff --git a/resources/vue/components/form_inputs/DateListInput.vue b/resources/vue/components/form_inputs/DateListInput.vue
new file mode 100644
index 0000000..05f3c57
--- /dev/null
+++ b/resources/vue/components/form_inputs/DateListInput.vue
@@ -0,0 +1,98 @@
+<template>
+ <div class="formpart">
+ <div class="sr-only" aria-live="polite" ref="list_message_field"></div>
+ <ul>
+ <li v-for="date in selected_date_list" v-bind="selected_date_list" :key="date">
+ <input type="hidden" :name="input_name + '[]'" :value="getISODate(date)">
+ <studip-date-time :timestamp="Math.floor(date.getTime() / 1000)" :date_only="true"></studip-date-time>
+ <studip-icon shape="trash" :title="$gettext('Löschen')" @click="removeDate"
+ class="enter-accessible" aria-role="button" tabindex="0"></studip-icon>
+ </li>
+ </ul>
+ <label>
+ {{ $gettext('Datum') }}
+ <div class="flex-row input-with-icon">
+ <input type="text" v-model="selected_date_value" ref="date_select_input">
+ <studip-icon shape="add" :title="$gettext('Hinzufügen')" @click="addDate"
+ class="icon enter-accessible button undecorated" aria-role="button" tabindex="0"></studip-icon>
+ </div>
+ </label>
+ </div>
+</template>
+
+<script>
+import StudipDateTime from "../StudipDateTime.vue";
+import {$gettext, $gettextInterpolate} from "@/assets/javascripts/lib/gettext";
+
+export default {
+ name: "date-list-input",
+ components: {StudipDateTime},
+ props: {
+ name: {
+ type: String,
+ required: true
+ },
+ selected_dates: {
+ type: Array,
+ required: false,
+ default: () => [],
+ }
+ },
+ data () {
+ return {
+ selected_date_value: STUDIP.DateTime.getStudipDate(new Date(), false, true),
+ selected_date_list: this.selected_dates.map(date => new Date(date)),
+ input_name: this.name,
+ };
+ },
+ mounted() {
+
+ //Set up the datepicker for the date selector input:
+ let v = this;
+ jQuery(this.$refs.date_select_input).datepicker({
+ onSelect: () => {
+ this.selected_date_value = this.$refs.date_select_input.value;
+ this.addDate();
+ },
+ });
+ },
+ watch: {
+ selected_date_value(new_value) {
+ this.$emit('selected_date_value', new_value);
+ },
+ selected_date_list: {
+ handler (new_value) {
+ this.$emit('selected_date_list', new_value);
+ },
+ deep: true
+ }
+ },
+ methods: {
+ addDate() {
+ if (this.selected_date_value.length < 8) {
+ //Input too short.
+ return;
+ }
+ let date_parts = this.selected_date_value.split('.');
+ if (date_parts.length !== 3) {
+ //Incorrect input formatting.
+ return;
+ }
+ let reformatted_date = date_parts[2] + '-' + date_parts[1] + '-' + date_parts[0];
+ this.selected_date_list.push(new Date(reformatted_date));
+ this.$refs.list_message_field.innerText = $gettextInterpolate($gettext('Datum %{date} hinzugefügt'), {date: this.selected_date_value});
+ },
+ removeDate(date_key) {
+ if (date_key) {
+ let date = this.selected_date_list.at(date_key);
+ let formatted_date = STUDIP.DateTime.getStudipDate(date, false, true);
+ this.selected_date_list.splice(date_key, 1);
+ this.$refs.list_message_field.innerText = $gettextInterpolate($gettext('Datum %{date} entfernt'), {date: formatted_date});
+ }
+ },
+ getISODate(date) {
+ return STUDIP.DateTime.getISODate(date);
+ }
+ }
+}
+</script>
diff --git a/resources/vue/components/form_inputs/DayOfWeekSelect.vue b/resources/vue/components/form_inputs/DayOfWeekSelect.vue
new file mode 100644
index 0000000..c28ddb6
--- /dev/null
+++ b/resources/vue/components/form_inputs/DayOfWeekSelect.vue
@@ -0,0 +1,60 @@
+<template>
+ <select :name="name" v-model="selected_value">
+ <option v-if="with_indeterminate" value=""
+ :selected="!value">
+ {{ $gettext('Bitte wählen') }}
+ </option>
+ <option value="1" :selected="value == '1'">
+ {{ $gettext('Montag') }}
+ </option>
+ <option value="2" :selected="value == '2'">
+ {{ $gettext('Dienstag') }}
+ </option>
+ <option value="3" :selected="value == '3'">
+ {{ $gettext('Mittwoch') }}
+ </option>
+ <option value="4" :selected="value == '4'">
+ {{ $gettext('Donnerstag') }}
+ </option>
+ <option value="5" :selected="value == '5'">
+ {{ $gettext('Freitag') }}
+ </option>
+ <option value="6" :selected="value == '6'">
+ {{ $gettext('Samstag') }}
+ </option>
+ <option value="7" :selected="value == '7'">
+ {{ $gettext('Sonntag') }}
+ </option>
+ </select>
+</template>
+
+<script>
+export default {
+ name: "day-of-week-select",
+ props: {
+ name: {
+ type: String,
+ required: true
+ },
+ value: {
+ type: String,
+ required: false
+ },
+ with_indeterminate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ }
+ },
+ data () {
+ return {
+ selected_value: this.value
+ };
+ },
+ watch: {
+ selected_value(new_value) {
+ this.$emit('selected_value', new_value);
+ }
+ }
+}
+</script>
diff --git a/resources/vue/components/form_inputs/MyCoursesColouredTable.vue b/resources/vue/components/form_inputs/MyCoursesColouredTable.vue
new file mode 100644
index 0000000..d2cc2f1
--- /dev/null
+++ b/resources/vue/components/form_inputs/MyCoursesColouredTable.vue
@@ -0,0 +1,166 @@
+<template>
+ <div class="formpart">
+ <label v-if="with_semester_selector">
+ {{ $gettext('Semester') }}
+ <select :name="`${name}_semester_id`" v-model="semester_id">
+ <option v-for="semester in available_semesters"
+ :value="semester.id"
+ :key="semester.id"
+ >
+ {{ semester.name }}
+ </option>
+ </select>
+ </label>
+
+ <table class="default mycourses">
+ <caption>{{ semesterName }}</caption>
+ <colgroup>
+ <col style="width: 7px">
+ <col style="width: 25px">
+ <col style="width: 70px">
+ <col>
+ <col>
+ </colgroup>
+ <thead>
+ <tr>
+ <th></th>
+ <th></th>
+ <th>{{ $gettext('Nummer') }}</th>
+ <th>{{ $gettext('Name') }}</th>
+ <th class="actions">{{ $gettext('Auswahl') }}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="course of courses" :key="course.id">
+ <td :class="`gruppe${course.group}`"></td>
+ <td>
+ <img :src="course.avatar_url" alt="" class="my-courses-avatar course-avatar-small">
+ </td>
+ <td>{{ course.number }}</td>
+ <td>{{ course.name }}</td>
+ <td class="actions">
+ <input type="hidden" :name="`${name}_course_ids[${course.id}]`" value="0">
+ <input type="checkbox" :name="`${name}_course_ids[${course.id}]`"
+ value="1" :checked="selected_course_id_list.includes(course.id)"
+ :title="$gettextInterpolate($gettext('%{course} auswählen'), {course: course.name})">
+ </td>
+ </tr>
+ <tr v-if="loadedSemesters.includes(semester_id) && courses.length === 0">
+ <td colspan="5">
+ <studip-message-box>{{ $gettext('Im gewählten Semester stehen keine Veranstaltungen zur Auswahl zur Verfügung.') }}</studip-message-box>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</template>
+
+<script>
+import StudipMessageBox from "../StudipMessageBox.vue";
+
+export default {
+ name: 'my-courses-coloured-table',
+ components: {StudipMessageBox},
+ props: {
+ default_semester_id: {
+ type: String,
+ required: true,
+ },
+ selected_course_ids: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ name: {
+ type: String,
+ required: false,
+ default: 'selected_course_ids',
+ },
+ semester_data: {
+ type: Object,
+ required: false,
+ default: () => {},
+ }
+ },
+ data() {
+ //Retrieve all semesters, if the semester selector is present:
+ let semester_data = this.semester_data;
+ return {
+ available_semesters: semester_data,
+ semester_id: null,
+ semester_courses: Object.values(semester_data).reduce(
+ (carry, current) => {
+ carry[current.id] = [];
+ return carry;
+ },
+ {}
+ ),
+ selected_course_id_list: [...this.selected_course_ids],
+ with_semester_selector: Object.keys(semester_data).length > 0,
+ membershipGroups: {},
+ loadedSemesters: [],
+ };
+ },
+ created() {
+ this.semester_id = this.default_semester_id;
+
+ STUDIP.jsonapi.GET(`users/${STUDIP.USER_ID}/course-memberships`, {
+ data: {
+ 'page[limit]': 1000,
+ }
+ }).done((response) => {
+ this.membershipGroups = Object.values(response.data).reduce(
+ (carry, current) => {
+ carry[current.id.split('_')[0]] = current.attributes.group;
+ return carry;
+ },
+ {}
+ );
+ })
+ },
+ methods: {
+ loadSemesterCourses(semester_id) {
+ if (this.loadedSemesters.includes(semester_id)) {
+ return;
+ }
+
+ // The courses have not yet been retrieved.
+ STUDIP.jsonapi.GET(`users/${STUDIP.USER_ID}/courses`, {
+ data: {
+ 'fields[courses]': 'id,course-number,title,course-type',
+ 'filter[semester]': semester_id,
+ 'include': 'memberships',
+ }
+ }).done((response) => {
+ this.semester_courses[semester_id] = response.data
+ .filter(item => item.type === 'courses')
+ .map(item => ({
+ id: item.id,
+ name: item.attributes.title,
+ number: item.attributes['course-number'] ?? '',
+ group: this.membershipGroups[item.id] ?? item.attributes['course-type'],
+ avatar_url: item.meta.avatar.small,
+ }));
+
+ this.loadedSemesters.push(semester_id);
+ });
+ }
+ },
+ computed: {
+ courses() {
+ return [...this.semester_courses[this.semester_id]].sort((a, b) => {
+ return a.name.localeCompare(b.name)
+ || a.number.localeCompare(b.number);
+ });
+ },
+ semesterName() {
+ return this.available_semesters[this.semester_id].name ?? '';
+ },
+ },
+ watch: {
+ semester_id(current) {
+ this.loadSemesterCourses(current);
+ }
+ }
+}
+</script>
diff --git a/resources/vue/components/form_inputs/RepetitionInput.vue b/resources/vue/components/form_inputs/RepetitionInput.vue
new file mode 100644
index 0000000..ef53ffb
--- /dev/null
+++ b/resources/vue/components/form_inputs/RepetitionInput.vue
@@ -0,0 +1,364 @@
+<template>
+ <div class="formpart">
+ <section>
+ <label>{{ $gettext('Art der Wiederholung') }}
+ <select :name="name + '_type'" v-model="repetition_type_value">
+ <option value="" :selected="!repetition_type_value">
+ {{ $gettext('Keine Wiederholung') }}
+ </option>
+ <option value="DAILY" :selected="repetition_type_value === 'DAILY'">
+ {{ $gettext('Tägliche Wiederholung') }}
+ </option>
+ <option value="WORKDAYS" :selected="repetition_type_value === 'WORKDAYS'">
+ {{ $gettext('Wiederholung an jedem Werktag') }}
+ </option>
+ <option value="WEEKLY" :selected="repetition_type_value === 'WEEKLY'">
+ {{ $gettext('Wöchentliche Wiederholung') }}
+ </option>
+ <option value="MONTHLY" :selected="repetition_type_value === 'MONTHLY'">
+ {{ $gettext('Monatliche Wiederholung') }}
+ </option>
+ <option value="YEARLY" :selected="repetition_type_value === 'YEARLY'">
+ {{ $gettext('Jährliche Wiederholung') }}
+ </option>
+ </select>
+ </label>
+ </section>
+ <section v-if="repetition_type_value === 'DAILY'">
+ <label>
+ {{ $gettext('Abstand in Tagen') }}
+ <input type="number" min="1" :name="name + '_interval'"
+ v-model="repetition_interval_value">
+ </label>
+ </section>
+ <section v-else-if="repetition_type_value === 'WEEKLY'">
+ <label>
+ {{ $gettext('Abstand in Wochen') }}
+ <input type="number" min="1" :name="name + '_interval'"
+ v-model="repetition_interval_value">
+ </label>
+ <div>
+ <p>{{ $gettext('Wiederholung an bestimmten Wochentagen') }}</p>
+ <label>
+ <input type="checkbox" :name="name + '_dow[]'"
+ value="1" :checked="repetition_dow_value.includes('1')">
+ {{ $gettext('Montag') }}
+ </label>
+ <label>
+ <input type="checkbox" :name="name + '_dow[]'"
+ value="2" :checked="repetition_dow_value.includes('2')">
+ {{ $gettext('Dienstag') }}
+ </label>
+ <label>
+ <input type="checkbox" :name="name + '_dow[]'"
+ value="3" :checked="repetition_dow_value.includes('3')">
+ {{ $gettext('Mittwoch') }}
+ </label>
+ <label>
+ <input type="checkbox" :name="name + '_dow[]'"
+ value="4" :checked="repetition_dow_value.includes('4')">
+ {{ $gettext('Donnerstag') }}
+ </label>
+ <label>
+ <input type="checkbox" :name="name + '_dow[]'"
+ value="5" :checked="repetition_dow_value.includes('5')">
+ {{ $gettext('Freitag') }}
+ </label>
+ <label>
+ <input type="checkbox" :name="name + '_dow[]'"
+ value="6" :checked="repetition_dow_value.includes('6')">
+ {{ $gettext('Samstag') }}
+ </label>
+ <label>
+ <input type="checkbox" :name="name + '_dow[]'"
+ value="7" :checked="repetition_dow_value.includes('7')">
+ {{ $gettext('Sonntag') }}
+ </label>
+ </div>
+ </section>
+ <section v-else-if="repetition_type_value === 'YEARLY'">
+ <label>
+ {{ $gettext('Abstand in Jahren') }}
+ <input type="number" min="1" :name="name + '_interval'"
+ v-model="repetition_interval_value">
+ </label>
+ <label>
+ {{ $gettext('Art der jährlichen Wiederholung') }}
+ <select :name="name + '_month_type'"
+ v-model="repetition_month_type_value">
+ <option value="dom"
+ :selected="repetition_month_type_value === 'dom'">
+ {{ $gettext('Wiederholung an einem bestimmten Datum') }}
+ </option>
+ <option value="dow"
+ :selected="repetition_month_type_value === 'dow'">
+ {{ $gettext('Wiederholung an einem bestimmten Wochentag') }}
+ </option>
+ </select>
+ </label>
+ <label v-if="repetition_month_type_value === 'dom'">
+ {{ $gettext('Tag') }}
+ <input type="number" :name="name + '_dom'" min="1" max="31" v-model="repetition_dom_value">
+ </label>
+ <label>
+ {{ $gettext('Monat') }}
+ <select :name="name + '_month'"
+ v-model="repetition_month_value">
+ <option value="1" :selected="repetition_month_value === 1">
+ {{ $gettext('Januar') }}
+ </option>
+ <option value="2" :selected="repetition_month_value === 2">
+ {{ $gettext('Februar') }}
+ </option>
+ <option value="3" :selected="repetition_month_value === 3">
+ {{ $gettext('März') }}
+ </option>
+ <option value="4" :selected="repetition_month_value === 4">
+ {{ $gettext('April') }}
+ </option>
+ <option value="5" :selected="repetition_month_value === 5">
+ {{ $gettext('Mai') }}
+ </option>
+ <option value="6" :selected="repetition_month_value === 6">
+ {{ $gettext('Juni') }}
+ </option>
+ <option value="7" :selected="repetition_month_value === 7">
+ {{ $gettext('Juli') }}
+ </option>
+ <option value="8" :selected="repetition_month_value === 8">
+ {{ $gettext('August') }}
+ </option>
+ <option value="9" :selected="repetition_month_value === 9">
+ {{ $gettext('September') }}
+ </option>
+ <option value="10" :selected="repetition_month_value === 10">
+ {{ $gettext('Oktober') }}
+ </option>
+ <option value="11" :selected="repetition_month_value === 11">
+ {{ $gettext('November') }}
+ </option>
+ <option value="12" :selected="repetition_month_value === 12">
+ {{ $gettext('Dezember') }}
+ </option>
+ </select>
+ </label>
+ </section>
+ <section v-if="repetition_type_value === 'MONTHLY'">
+ <label>
+ {{ $gettext('Abstand in Monaten') }}
+ <input type="number" min="1" :name="name + '_interval'"
+ v-model="repetition_interval_value">
+ </label>
+ <label>
+ {{ $gettext('Art der monatlichen Wiederholung') }}
+ <select :name="name + '_month_type'"
+ v-model="repetition_month_type_value">
+ <option value="dom" :selected="repetition_month_type_value === 'dom'">
+ {{ $gettext('Wiederholung an einem bestimmten Tag des Monats') }}
+ </option>
+ <option value="dow" :selected="repetition_month_type_value === 'dow'">
+ {{ $gettext('Wiederholung an einem bestimmten Wochentag') }}
+ </option>
+ </select>
+ </label>
+ </section>
+ <section v-if="repetition_type_value === 'MONTHLY' && repetition_month_type_value === 'dom'">
+ <label>
+ {{ $gettext('Wiederholung am einem bestimmten Tag des Monats:') }}
+ <input type="number" min="1" :name="name + '_dom'"
+ v-model="repetition_dom_value">
+ </label>
+ </section>
+ <section v-if="['MONTHLY', 'YEARLY'].includes(repetition_type_value) && repetition_month_type_value === 'dow'">
+ <label>
+ {{ $gettext('Wiederholung an einem bestimmten Wochentag:') }}
+ <day-of-week-select :name="name + '_dow'" v-model="repetition_dow_value[0]"
+ :with_indeterminate="true"></day-of-week-select>
+ </label>
+ <label>
+ {{ $gettext('Wann im Monat soll die Wiederholung stattfinden?') }}
+ <select :name="name + '_dow_week'">
+ <option value="" :selected="!repetition_dow_week_value">
+ {{ $gettext('Bitte wählen') }}
+ </option>
+ <option value="1" :selected="repetition_dow_week_value === 1">
+ {{ $gettext('Am ersten gewählten Wochentag') }}
+ </option>
+ <option value="2" :selected="repetition_dow_week_value === 2">
+ {{ $gettext('Am zweiten gewählten Wochentag') }}
+ </option>
+ <option value="3" :selected="repetition_dow_week_value === 3">
+ {{ $gettext('Am dritten gewählten Wochentag') }}
+ </option>
+ <option value="4" :selected="repetition_dow_week_value === 4">
+ {{ $gettext('Am vierten gewählten Wochentag') }}
+ </option>
+ <option value="-1" :selected="repetition_dow_week_value === -1">
+ {{ $gettext('Am letzten gewählten Wochentag') }}
+ </option>
+ </select>
+ </label>
+ </section>
+
+ <section v-if="repetition_type_value">
+ <label>
+ {{ $gettext('Ende der Wiederholung') }}
+ <select :name="name + '_rep_end_type'"
+ v-model="repetition_end_type_value">
+ <option value="" :selected="!repetition_end_type_value">
+ {{ $gettext('Nie') }}
+ </option>
+ <option value="end_date" :selected="repetition_end_type_value === 'end_date'">
+ {{ $gettext('An einem bestimmten Datum') }}
+ </option>
+ <option value="end_count" :selected="repetition_end_type_value === 'end_count'">
+ {{ $gettext('Nach einer Anzahl von Terminen') }}
+ </option>
+ </select>
+ </label>
+ </section>
+ <section v-if="repetition_end_type_value === 'end_date'">
+ <label>
+ {{ $gettext('Enddatum') }}
+ <input type="text" :name="name + '_rep_end_date'"
+ data-date-picker v-model="repetition_end_date_value">
+ </label>
+ </section>
+ <section v-else-if="repetition_end_type_value === 'end_count'">
+ <label>
+ {{ $gettext('Anzahl der Termine') }}
+ <input type="number" min="1" :name="name + '_number_of_dates'"
+ v-model="number_of_dates_value">
+ </label>
+ </section>
+ </div>
+</template>
+
+<script>
+export default {
+ name: "repetition-input",
+ props: {
+ name: {
+ type: String,
+ required: true
+ },
+ default_date: {
+ type: String,
+ required: true
+ },
+ repetition_type: {
+ type: String,
+ required: true
+ },
+ repetition_interval: {
+ type: String,
+ required: true
+ },
+ repetition_dow: {
+ type: Array,
+ required: true
+ },
+ repetition_dow_week: {
+ type: Number,
+ required: true
+ },
+ repetition_month: {
+ type: Number,
+ required: true
+ },
+ repetition_month_type: {
+ type: String,
+ required: false
+ },
+ repetition_dom: {
+ type: Number,
+ required: true
+ },
+ repetition_end_type: {
+ type: String,
+ required: false
+ },
+ repetition_end_date: {
+ type: String,
+ required: true
+ },
+ number_of_dates: {
+ type: Number,
+ required: true
+ }
+ },
+ data () {
+ return {
+ repetition_type_value: '',
+ repetition_interval_value: 1,
+ repetition_dow_value: [],
+ repetition_dow_week_value: 0,
+ repetition_month_type_value: '',
+ repetition_month_value: 0,
+ repetition_dom_value: 0,
+ repetition_end_type_value: '',
+ repetition_end_date_value: '',
+ number_of_dates_value: 0
+ };
+ },
+ mounted () {
+ this.repetition_type_value = this.repetition_type;
+ this.repetition_interval_value = this.repetition_interval;
+ this.repetition_dow_value = this.repetition_dow;
+ this.repetition_dow_week_value = this.repetition_dow_week;
+ if (this.repetition_month_type === undefined) {
+ this.repetition_month_type_value = this.repetition_dow.length > 0 ? 'dow' : 'dom';
+ } else {
+ this.repetition_month_type_value = this.repetition_month_type;
+ }
+
+ this.repetition_month_value = this.repetition_month;
+ this.repetition_dom_value = this.repetition_dom;
+ this.repetition_end_type_value = '';
+ if (this.repetition_end_type !== undefined) {
+ this.repetition_end_type_value = this.repetition_end_type;
+ } else if (this.number_of_dates > 1) {
+ this.repetition_end_type_value = 'end_count';
+ } else if (this.repetition_end_date) {
+ this.repetition_end_type_value = 'end_date';
+ }
+ this.repetition_end_date_value = this.repetition_end_date;
+ this.number_of_dates_value = this.number_of_dates;
+ },
+ watch: {
+ repetition_type_value(new_value) {
+ this.$emit('input_repetition_type', new_value);
+ },
+ repetition_interval_value(new_value) {
+ this.$emit('input_repetition_interval', new_value);
+ },
+ repetition_dow_value: {
+ handler(new_value) {
+ this.$emit('input_repetition_dow', new_value);
+ },
+ deep: true,
+ },
+ repetition_dow_week_value(new_value) {
+ this.$emit('input_repetition_dow_week', new_value);
+ },
+ repetition_month_type_value(new_value) {
+ this.$emit('input_repetition_month_type', new_value);
+ },
+ repetition_month_value(new_value) {
+ this.$emit('input_repetition_month', new_value);
+ },
+ repetition_dom_value(new_value) {
+ this.$emit('input_repetition_dom', new_value);
+ },
+ repetition_end_type_value(new_value) {
+ this.$emit('input_repetition_end_type', new_value);
+ },
+ repetition_end_date_value(new_value) {
+ this.$emit('input_repetition_end_date', new_value);
+ },
+ number_of_dates_value(new_value) {
+ this.$emit('input_number_of_dates', new_value);
+ }
+ }
+}
+</script>
diff --git a/templates/forms/date_list_input.php b/templates/forms/date_list_input.php
new file mode 100644
index 0000000..2f759ad
--- /dev/null
+++ b/templates/forms/date_list_input.php
@@ -0,0 +1,3 @@
+<date-list-input
+ v-model="<?= htmlReady($name) ?>"
+ <?= arrayToHtmlAttributes($vue_attributes) ?>></date-list-input>
diff --git a/templates/forms/selected_ranges_input.php b/templates/forms/selected_ranges_input.php
new file mode 100644
index 0000000..03ebf5d
--- /dev/null
+++ b/templates/forms/selected_ranges_input.php
@@ -0,0 +1,7 @@
+<editable-list name="<?= htmlReady($this->name) ?>"
+ quicksearch="<?= htmlReady((string) $searchtype) ?>"
+ :items="<?= htmlReady(json_encode($selected_items)) ?>"
+ :selectable="<?= htmlReady(json_encode($selectable)) ?>"
+ :category_order="<?= htmlReady(json_encode($category_order)) ?>"
+ @input="output => <?= htmlReady($this->name) ?> = output">
+</editable-list>
diff --git a/templates/sidebar/date-select-widget.php b/templates/sidebar/date-select-widget.php
new file mode 100644
index 0000000..a85c44d
--- /dev/null
+++ b/templates/sidebar/date-select-widget.php
@@ -0,0 +1,13 @@
+<form method="post" name="date_select_form" class="default">
+ <input type="text" id="date_select"
+ name="date_select"
+ value="<?= $date->format('d.m.Y') ?>"
+ data-date-picker
+ <?
+ if ($calendar_control) {
+ echo 'data-calendar-control';
+ } else {
+ echo 'onchange="jQuery(this).closest(\'form\').submit()"';
+ }
+ ?>>
+</form>
diff --git a/tests/jsonapi/UserEventsIcalTest.php b/tests/jsonapi/UserEventsIcalTest.php
index 81230d4..c679f49 100644
--- a/tests/jsonapi/UserEventsIcalTest.php
+++ b/tests/jsonapi/UserEventsIcalTest.php
@@ -23,16 +23,18 @@ class UserEventsIcalTest extends \Codeception\Test\Unit
{
$credentials = $this->tester->getCredentialsForTestAutor();
- $calendar = new \SingleCalendar($credentials['id']);
- $event = $calendar->getNewEvent();
- $event->setTitle('blypyp');
-
- $oldUser = $GLOBALS['user'] ?? null;
- $GLOBALS['user'] = \User::find($credentials['id']);
-
- $calendar->storeEvent($event, [$credentials['id']]);
-
- $GLOBALS['user'] = $oldUser;
+ $event = new \CalendarDate();
+ $event->setId($event->getNewId());
+ $now = time();
+ $event->begin = $now;
+ $event->end = $now + 3600;
+ $event->title = 'blypyp';
+ $event->store();
+ $calendar_date = new \CalendarDateAssignment();
+ $calendar_date->setId([$credentials['id'], $event->getId()]);
+ $calendar_date->calendar_date = $event;
+ $calendar_date->suppress_mails = true;
+ $calendar_date->store();
$app = $this->tester->createApp($credentials, 'get', '/users/{id}/events.ics', UserEventsIcal::class);
diff --git a/tests/jsonapi/UserEventsIndexTest.php b/tests/jsonapi/UserEventsIndexTest.php
index 0941f09..ac07471 100644
--- a/tests/jsonapi/UserEventsIndexTest.php
+++ b/tests/jsonapi/UserEventsIndexTest.php
@@ -55,14 +55,16 @@ class UserEventsIndexTest extends \Codeception\Test\Unit
private function createEvent($credentials)
{
- $calendar = new \SingleCalendar($credentials['id']);
- $event = $calendar->getNewEvent();
-
- $oldUser = $GLOBALS['user'];
- $GLOBALS['user'] = \User::find($credentials['id']);
-
- $calendar->storeEvent($event, [$credentials['id']]);
-
- $GLOBALS['user'] = $oldUser;
+ $event = new \CalendarDate();
+ $event->setId($event->getNewId());
+ $now = time();
+ $event->begin = $now;
+ $event->end = $now + 3600;
+ $event->store();
+ $calendar_date = new \CalendarDateAssignment();
+ $calendar_date->setId([$credentials['id'], $event->getId()]);
+ $calendar_date->calendar_date = $event;
+ $calendar_date->suppress_mails = true;
+ $calendar_date->store();
}
}
diff --git a/tests/jsonapi/_bootstrap.php b/tests/jsonapi/_bootstrap.php
index 61325ef..82ae54e 100644
--- a/tests/jsonapi/_bootstrap.php
+++ b/tests/jsonapi/_bootstrap.php
@@ -41,6 +41,7 @@ StudipAutoloader::register();
// General classes folders
StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/models');
+StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/models/calendar');
StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/models/resources');
StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/classes');
StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/classes', 'Studip');