Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • alexander.vorwerk/studip
  • hochschule-wismar/stud-ip
  • tleilax/studip
  • marcus/studip
  • manschwa/studip
  • eberhardt/studip
  • uol/studip
  • pluta/studip
  • thienel/extern-uni-b
  • studip/studip
  • strohm/studip
  • uni-osnabrueck/studip
  • FloB/studip
  • universit-t-rostock/studip
  • Robinyyy/studip
  • jakob.diel/studip
  • HyperSpeeed/studip
  • ann/studip
  • nod3zer0/stud-ip-siple-saml-php-plugin
  • erik.hillmann/studip
20 results
Select Git revision
Show changes
Showing
with 2267 additions and 1604 deletions
......@@ -7,10 +7,28 @@ class Admin_TreeController extends AuthenticatedController
$GLOBALS['perm']->check('root');
Navigation::activateItem('/admin/locations/range_tree');
PageLayout::setTitle(_('Einrichtungshierarchie bearbeiten'));
$this->startId = Request::get('node_id', 'RangeTreeNode_root');
$this->semester = Request::option('semester', Semester::findCurrent()->id);
$this->classname = RangeTreeNode::class;
$this->setupSidebar();
$this->render_vue_app(
Studip\VueApp::create('tree/StudipTree')
->withProps([
'breadcrumb-icon' => 'institute',
'create-url' => $this->createURL(),
'delete-url' => $this->deleteURL(),
'edit-url' => $this->editURL(),
'editable' => true,
'semester' => $this->semester,
'show-structure-as-navigation' => true,
'start-id' => Request::get('node_id', 'RangeTreeNode_root'),
'title' => _('Einrichtungshierarchie bearbeiten'),
'view-type' => 'table',
'visible-children-only' => false,
'with-courses' => true,
])
);
}
public function semtree_action()
......@@ -18,10 +36,30 @@ class Admin_TreeController extends AuthenticatedController
$GLOBALS['perm']->check('root');
Navigation::activateItem('/admin/locations/sem_tree');
PageLayout::setTitle(_('Veranstaltungshierarchie bearbeiten'));
$this->startId = Request::get('node_id', 'StudipStudyArea_root');
$this->semester = Request::option('semester', Semester::findCurrent()->id);
$this->classname = StudipStudyArea::class;
$this->setupSidebar();
$this->render_vue_app(
Studip\VueApp::create('tree/StudipTree')
->withProps([
'breadcrumb-icon' => 'literature',
'create-url' => $this->createURL(),
'delete-url' => $this->deleteURL(),
'edit-url' => $this->editURL(),
'editable' => true,
'semester' => $this->semester,
'show-structure-as-navigation' => true,
'start-id' => Request::get('node_id', 'StudipStudyArea_root'),
'title' => _('Veranstaltungshierarchie bearbeiten'),
'view-type' => 'table',
'visible-children-only' => false,
'with-course-assign' => true,
'with-courses' => true,
])
);
}
/**
......@@ -130,12 +168,15 @@ class Admin_TreeController extends AuthenticatedController
$node->parent_id = Request::option('parent_id');
$parent = $classname::getNode(Request::option('parent_id'));
$maxprio = max(array_map(
$children = $parent->getChildNodes();
$maxprio = !empty($children)
? max(array_map(
function ($c) {
return $c->priority;
},
$parent->getChildNodes()
));
$children
))
: 0;
$node->priority = $maxprio + 1;
if (Request::option('studip_object_id')) {
......@@ -151,9 +192,9 @@ class Admin_TreeController extends AuthenticatedController
}
if ($node->store() !== false) {
Pagelayout::postSuccess(_('Die Daten wurden gespeichert.'));
PageLayout::postSuccess(_('Die Daten wurden gespeichert.'));
} else {
Pagelayout::postError(_('Die Daten konnten nicht gespeichert werden.'));
PageLayout::postError(_('Die Daten konnten nicht gespeichert werden.'));
}
$this->relocate(Request::get('from'));
......@@ -197,8 +238,8 @@ class Admin_TreeController extends AuthenticatedController
$courseIds = Request::optionArray('assign_semtree');
$order = Config::get()->IMPORTANT_SEMNUMBER
? "ORDER BY `start_time` DESC, `VeranstaltungsNummer`, `Name`"
: "ORDER BY `start_time` DESC, `Name`";
? "ORDER BY `VeranstaltungsNummer`, `Name`"
: "ORDER BY `Name`";
$this->courses = array_filter(
Course::findMany($courseIds, $order),
function (Course $course): bool {
......
......@@ -16,10 +16,6 @@
* @since 2.1
*/
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
require_once 'vendor/email_message/blackhole_message.php';
require_once 'lib/statusgruppe.inc.php';
......@@ -102,6 +98,7 @@ class Admin_UserController extends AuthenticatedController
$request['username'] = trim($request['username']);
$request['email'] = trim($request['email']);
$request['matriculation_number'] = trim($request['matriculation_number']);
$request['vorname'] = trim($request['vorname']);
$request['nachname'] = trim($request['nachname']);
$request['inaktiv'] = $inaktiv;
......@@ -135,6 +132,7 @@ class Admin_UserController extends AuthenticatedController
'vorname',
'nachname',
'email',
'matriculation_number',
'inaktiv',
'locked',
'show_only_not_lectures',
......@@ -162,16 +160,16 @@ class Admin_UserController extends AuthenticatedController
PageLayout::postInfo(_('Sie haben keine Suchkriterien ausgewählt!'));
} elseif (count($this->users) < 1 && Request::submitted('search')) {
PageLayout::postInfo(_('Es wurden keine Personen mit diesen Suchkriterien gefunden.'));
} else {
} elseif (!Request::submitted('export')) {
$_SESSION['admin']['user']['results'] = true;
PageLayout::postInfo(sprintf(_('Es wurden %s Personen mit diesen Suchkriterien gefunden.'), count($this->users)));
}
if (is_array($this->users) && Request::submitted('export')) {
$tmpname = md5(uniqid('tmp'));
$captions = ['username',
'vorname',
'nachname',
'email',
'matriculation_number',
'status',
'authentifizierung',
'domänen',
......@@ -189,11 +187,12 @@ class Admin_UserController extends AuthenticatedController
$u['Vorname'],
$u['Nachname'],
$u['Email'],
$u['matriculation_number'],
$u['perms'],
$u['auth_plugin'],
join(';', $userdomains),
implode(';', $userdomains),
$u['mkdate'] ? strftime('%x', $u['mkdate']) : '',
$u->online->last_lifesign ? strftime('%x', $u->online->last_lifesign) : ''
isset($u->online->last_lifesign) ? strftime('%x', $u->online->last_lifesign) : ''
];
foreach ($this->datafields as $datafield) {
$df = new DatafieldEntryModel(
......@@ -207,14 +206,15 @@ class Admin_UserController extends AuthenticatedController
}
return $data;
};
if (array_to_csv(array_map($mapper, $this->users), $GLOBALS['TMP_PATH'] . '/' . $tmpname, $captions)) {
$this->redirect(
FileManager::getDownloadURLForTemporaryFile(
$tmpname,
$this->render_csv(
array_merge(
[$captions],
array_map($mapper, $this->users),
),
'nutzer-export.csv'
)
);
}
return;
}
}
......@@ -305,7 +305,18 @@ class Admin_UserController extends AuthenticatedController
$umanager->getFromDatabase($user_id);
//delete
if ($umanager->deleteUser($delete_documents, $delete_content_from_course, $delete_personal_documents, $delete_personal_content, $delete_names, $delete_memberships, !Request::bool('mail'), $delete_courseware)) {
if (
$umanager->deleteUser(
$delete_documents,
$delete_content_from_course,
$delete_personal_documents,
$delete_personal_content,
$delete_names,
$delete_memberships,
Request::bool('mail', false),
$delete_courseware
)
) {
$details = explode('§', str_replace(['msg§', 'info§', 'error§'], '', mb_substr($umanager->msg, 0, -1)));
PageLayout::postSuccess(htmlReady(sprintf(_('"%s (%s)" wurde erfolgreich gelöscht.'), $user->getFullName(), $user->username)), $details);
} else {
......@@ -342,7 +353,18 @@ class Admin_UserController extends AuthenticatedController
$umanager->getFromDatabase($_user_id);
//delete
if ($umanager->deleteUser($delete_documents, $delete_content_from_course, $delete_personal_documents, $delete_personal_content, $delete_names, $delete_memberships, !Request::bool('mail'))) {
if (
$umanager->deleteUser(
$delete_documents,
$delete_content_from_course,
$delete_personal_documents,
$delete_personal_content,
$delete_names,
$delete_memberships,
Request::bool('mail', false),
$delete_courseware
)
) {
$details = explode('§', str_replace(['msg§', 'info§', 'error§'], '', mb_substr($umanager->msg, 0, -1)));
PageLayout::postSuccess(htmlReady(sprintf(_('"%s (%s)" wurde erfolgreich gelöscht'), $users[$i]->getFullName(), $users[$i]->username)), $details);
} else {
......@@ -389,8 +411,21 @@ class Admin_UserController extends AuthenticatedController
$this->user_roles = $this->user->getRoles();
// get ilias account data
if ($GLOBALS['perm']->have_perm('root') && Config::get()->ILIAS_INTERFACE_ENABLE) {
$this->ilias_list = [];
foreach (Config::get()->ILIAS_INTERFACE_SETTINGS as $ilias_index => $ilias_config) {
if ($ilias_config['is_active']) {
$this->ilias_list[$ilias_index] = new ConnectedIlias($ilias_index);
$this->ilias_list[$ilias_index]->soap_client->clearCache();
$this->ilias_user[$ilias_index] = new IliasUser($ilias_index, $ilias_config['version'], $user_id);
}
}
}
// Änderungen speichern
if (Request::submitted('edit')) {
CSRFProtection::verifyUnsafeRequest();
if (Request::get('auth_plugin') === 'preliminary') {
Request::set('auth_plugin', null);
}
......@@ -402,11 +437,19 @@ class Admin_UserController extends AuthenticatedController
if (count($editPerms)) {
$editUser['auth_user_md5.perms'] = $editPerms[0];
}
foreach (['Vorname', 'Nachname', 'matriculation_number', 'auth_plugin', 'visible'] as $param) {
if (Request::get($param)) $editUser['auth_user_md5.' . $param] = Request::get($param);
foreach (['Vorname', 'Nachname', 'auth_plugin', 'visible'] as $param) {
if (Request::get($param)) {
$editUser['auth_user_md5.' . $param] = Request::get($param);
}
}
if (Request::submitted('matriculation_number')) {
$editUser['auth_user_md5.matriculation_number'] = Request::get('matriculation_number');
}
foreach (words('title_front title_rear geschlecht preferred_language') as $param) {
if (Request::get($param) !== null) $editUser['user_info.' . $param] = Request::get($param);
if (Request::submitted($param)) {
$editUser['user_info.' . $param] = Request::get($param);
}
}
//change username
if (Request::get('username') && $this->user['username'] !== Request::get('username')) {
......@@ -534,12 +577,12 @@ class Admin_UserController extends AuthenticatedController
StudipLog::log('INST_USER_ADD', $institute_id, $user_id, $editPerms[0]);
NotificationCenter::postNotification('UserInstitutionDidUpdate', $institute_id, $user_id);
InstituteMember::ensureDefaultInstituteForUser($user_id);
$details[] = sprintf(_('%s wurde hinzugefügt.'), htmlReady($membership->institute->getFullname()));
$details[] = sprintf(_('%s wurde hinzugefügt.'), htmlReady($membership->institute->getFullName()));
}
} elseif ($institute_id != '' && Request::option('new_student_inst') == $institute_id && $editPerms[0] != 'root') {
$details[] = sprintf(
_('<b>%s wurde nicht hinzugefügt.</b> Sie können keine Person gleichzeitig als Studierende/-r und als Mitarbeiter/-in einer Einrichtung hinzufügen.'),
htmlReady(Institute::find($institute_id)->getFullname())
htmlReady(Institute::find($institute_id)->getFullName())
);
}
}
......@@ -779,7 +822,7 @@ class Admin_UserController extends AuthenticatedController
. " und steht Ihnen als neuer Ansprechpartner bei Fragen oder Problemen "
. "in Stud.IP zur Verfügung. "),
$admin['Vorname'], $admin['Nachname'],
$institute->getFullname(), $this->user['Vorname'], $this->user['Nachname']);
$institute->getFullName(), $this->user['Vorname'], $this->user['Nachname']);
StudipMail::sendMessage($admin['Email'], $subject, $mailbody);
$notin[] = $admin['user_id'];
......@@ -812,7 +855,7 @@ class Admin_UserController extends AuthenticatedController
. " und steht Ihnen als neuer Ansprechpartner bei Fragen oder Problemen "
. "in Stud.IP zur Verfügung. "),
$admin['Vorname'], $admin['Nachname'],
$institute->getFullname(), $this->user['Vorname'], $this->user['Nachname']);
$institute->getFullName(), $this->user['Vorname'], $this->user['Nachname']);
StudipMail::sendMessage($admin['Email'], $subject, $mailbody);
$i++;
......@@ -824,20 +867,20 @@ class Admin_UserController extends AuthenticatedController
_('Es wurden ingesamt %s Mails an die %s der Einrichtung "%s" geschickt.'),
$i,
$wem,
htmlReady($institute->getFullname())
htmlReady($institute->getFullName())
);
}
}
$details[] = sprintf(
_('Person wurde erfolgreich in die Einrichtung "%s" mit dem Status "%s" eingetragen.'),
htmlReady($institute->getFullname()),
htmlReady($institute->getFullName()),
$UserManagement->user_data['auth_user_md5.perms']
);
} else {
$details[] = sprintf(
_('Person konnte nicht in die Einrichtung "%s" eingetragen werden.'),
htmlReady($institute->getFullname())
htmlReady($institute->getFullName())
);
}
}
......@@ -988,7 +1031,7 @@ class Admin_UserController extends AuthenticatedController
public function lock_comment_action($user_id)
{
$this->user = User::find($user_id);
PageLayout::setTitle(sprintf(_('%s sperren'), $this->user->getFullname()));
PageLayout::setTitle(sprintf(_('%s sperren'), $this->user->getFullName()));
$this->params = [];
if (Request::int('from_index')) {
......@@ -1012,7 +1055,7 @@ class Admin_UserController extends AuthenticatedController
if ($user->store()) {
PageLayout::postSuccess(sprintf(
_('%s wurde gesperrt.'),
htmlReady($user->getFullname())
htmlReady($user->getFullName())
));
}
......@@ -1040,12 +1083,12 @@ class Admin_UserController extends AuthenticatedController
if ($user->store()) {
PageLayout::postSuccess(sprintf(
_('%s wurde entsperrt.'),
htmlReady($user->getFullname())
htmlReady($user->getFullName())
));
} else {
PageLayout::postError(sprintf(
_('%s konnte nicht entsperrt werden.'),
htmlReady($user->getFullname())
htmlReady($user->getFullName())
));
}
......@@ -1089,7 +1132,7 @@ class Admin_UserController extends AuthenticatedController
*/
public function store_user_institute_action($user_id, $institute_id)
{
CSRFProtection::verifyRequest();
CSRFProtection::verifyUnsafeRequest();
$inst_membership = InstituteMember::findOneBySQL('user_id = ? AND institut_id = ?', [$user_id, $institute_id]);
......@@ -1242,7 +1285,7 @@ class Admin_UserController extends AuthenticatedController
public function activities_action($user_id)
{
$this->user = User::find($user_id);
$this->fullname = $this->user->getFullname();
$this->fullname = $this->user->getFullName();
$this->params = [];
if (Request::int('from_index')) {
......@@ -1380,7 +1423,7 @@ class Admin_UserController extends AuthenticatedController
if (is_null($this->range)) {
$this->range = Institute::find($range_id);
}
PageLayout::setTitle(sprintf(_('Dateiübersicht für %s'), $this->range->getFullname()));
PageLayout::setTitle(sprintf(_('Dateiübersicht für %s'), $this->range->getFullName()));
}
/**
......@@ -1440,16 +1483,12 @@ class Admin_UserController extends AuthenticatedController
];
$queries[] = [
'desc' => _("Anzahl der Wikiseiten"),
'query' => "SELECT COUNT(*) FROM wiki WHERE user_id = ? GROUP BY user_id",
'query' => "SELECT COUNT(*) FROM `wiki_pages` WHERE `user_id` = ? GROUP BY `user_id`",
];
$queries[] = [
'desc' => _("Anzahl der Umfragen"),
'query' => "SELECT COUNT(*) FROM questionnaires WHERE user_id = ? GROUP BY user_id",
];
$queries[] = [
'desc' => _("Anzahl der Evaluationen"),
'query' => "SELECT COUNT(*) FROM eval WHERE author_id = ? GROUP BY author_id",
];
$queries[] = [
'desc' => _("Anzahl der Dateien in Veranstaltungen und Einrichtungen"),
'query' => "SELECT COUNT(file_refs.id)
......@@ -1478,7 +1517,7 @@ class Admin_UserController extends AuthenticatedController
'details' => "files",
];
foreach (PluginEngine::getPlugins('ForumModule') as $plugin) {
foreach (PluginEngine::getPlugins(ForumModule::class) as $plugin) {
$table = $plugin->getEntryTableInfo();
$queries[] = [
'desc' => $plugin->getPluginName() . ' - ' . _("Anzahl der Postings"),
......@@ -1569,21 +1608,22 @@ class Admin_UserController extends AuthenticatedController
{
CSRFProtection::verifyUnsafeRequest();
$course_ids = [];
if (Request::get('course_id')) {
$courses = [Request::get('course_id')];
$course_ids = [Request::option('course_id')];
} else {
$courses = Request::getArray('courses');
$course_ids = Request::optionArray('courses');
}
if (empty($courses)) {
if (empty($course_ids)) {
PageLayout::postError(_('Sie haben keine Veranstaltungen ausgewählt.'));
} else {
$courses = array_map('Seminar::GetInstance', $courses);
$courses = Course::findMany($course_ids);
$successes = 0;
$fails = 0;
foreach ($courses as $course) {
if ($course->deleteMember($user->id)) {
if ($course->deleteMember($user)) {
$successes++;
} else {
$fails++;
......@@ -1618,79 +1658,6 @@ class Admin_UserController extends AuthenticatedController
$this->redirect($this->show_user_coursesURL($user));
}
public function batch_export_members_action()
{
PageLayout::setTitle(_('Teilnehmendendaten exportieren'));
$courseIds = Request::optionArray('export_members');
$order = Config::get()->IMPORTANT_SEMNUMBER
? "ORDER BY `start_time` DESC, `VeranstaltungsNummer`, `Name`"
: "ORDER BY `start_time` DESC, `Name`";
$this->courses = array_filter(
Course::findMany($courseIds, $order),
function (Course $course): bool {
/*
* Check if sem_tree entries are allowed and may be changed and remove all courses
* where this is not the case.
*/
return !LockRules::Check($course->id, 'sem_tree', 'sem')
&& $course->getSemClass()['bereiche'];
}
);
// check if at least one course was selected (this can only happen from admin courses overview):
if (count($this->courses) === 0) {
PageLayout::postWarning('Es wurde keine Veranstaltung gewählt.');
$this->relocate('admin/courses');
}
}
/*
* Export member data of all selected courses
*/
public function do_batch_export_action()
{
CSRFProtection::verifyUnsafeRequest();
if (Request::submitted('xlsx')) {
$export_format = 'xlsx';
} else if (Request::submitted('csv')) {
$export_format = 'csv';
} else {
PageLayout::postError('Nicht unterstütztes Exportformat.');
$this->relocate('admin/courses');
}
$tmp_folder = $GLOBALS['TMP_PATH'] . '/temp_folder_' . md5(uniqid());
mkdir($tmp_folder);
$courses = Course::findMany(Request::optionArray('courses'));
foreach ($courses as $course) {
$header = ['Status', 'Anrede', 'Titel', 'Vorname', 'Nachname', 'Titel nachgestellt', 'Benutzername', 'Adresse', 'Telefonnr.',
'E-Mail', 'Anmeldedatum', 'Matrikelnummer', 'Studiengänge'];
$members = CourseMember::getMemberDataByCourse($course->seminar_id);
foreach ($members as &$member) {
$member['Anmeldedatum'] = $member['Anmeldedatum'] ? date('d.m.Y', $member['Anmeldedatum']) : _('unbekannt');
unset($member['user_id']);
}
$filename = FileManager::cleanFileName('Teilnehmendenexport ' . $course->Name . '.' . $export_format);
$filepath = $tmp_folder . '/'. $filename;
$this->render_spreadsheet($header, $members, $export_format, $filename, $filepath);
}
$archive_file_path = $GLOBALS['TMP_PATH'] . '/archiv.zip';
$archive_filename = 'Export_Teilnehmendendaten.zip';
FileArchiveManager::createArchiveFromPhysicalFolder($tmp_folder, $archive_file_path);
rmdirr($tmp_folder);
$this->render_temporary_file($archive_file_path, $archive_filename, 'application/zip');
}
/**
* Init sidebar
*/
......@@ -1714,7 +1681,7 @@ class Admin_UserController extends AuthenticatedController
)->asDialog();
$actions->addLink(
_('Konten zusammenführen'),
$this->url_for('admin/user/migrate/' . ((!empty($this->user) && is_array($this->user)) ? $this->user['user_id'] : '')),
$this->url_for('admin/user/migrate/' . (!empty($this->user['user_id']) ? $this->user['user_id'] : '')),
Icon::create('community')
);
......
<?php
# Lifter010: TODO
/**
* webservice_access.php - access rules für webservices admin controller
*
* 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 André Noack <noack@data-quest.de>
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
* @category Stud.IP
* @package admin
*/
class Admin_WebserviceAccessController extends AuthenticatedController
{
/**
* common tasks for all actions
*/
function before_filter (&$action, &$args)
{
global $perm, $template_factory;
parent::before_filter($action, $args);
$perm->check('root');
if (!Config::get()->WEBSERVICES_ENABLE) {
throw new AccessDeniedException(_("Die Webservices sind in diesem System nicht aktiviert."));
}
PageLayout::setTitle(_('Verwaltung der Zugriffsregeln für Webservices'));
Navigation::activateItem('/admin/config/webservice_access');
$this->get_all_rules();
}
/**
* Display the list of ws access rules
*/
function index_action()
{
}
/**
* Mark one rule as editable and display the list of ws access rules
*/
function edit_action($id)
{
$this->edit = $id;
$this->render_action('index');
}
/**
* Add a new rule on top, mark as editable and display the list of ws access rules
*/
function new_action()
{
array_unshift($this->ws_rules, new WebserviceAccessRule());
$this->edit = 0;
$this->render_action('index');
}
function delete_action($id)
{
$rule = $this->ws_rules[$id];
if ($rule && !$rule->isNew() && $rule->delete()) {
PageLayout::postMessage(MessageBox::success(_("Die Regel wurde gelöscht.")));
}
$this->redirect($this->url_for('admin/webservice_access'));
}
function update_action()
{
CSRFProtection::verifyUnsafeRequest();
if (Request::submitted('ok')) {
if (!($rule = $this->ws_rules[Request::int('ws_rule_id')])) {
$rule = new WebserviceAccessRule();
$rule->id = 0;
array_unshift($this->ws_rules, $rule);
}
$rule->api_key = trim(Request::get('ws_rule_api_key'));
$rule->method = trim(Request::get('ws_rule_method'));
$rule->ip_range = trim(Request::get('ws_rule_ip_range'));
$rule->type = trim(Request::get('ws_rule_type'));
$msg = [];
if (mb_strlen($rule->api_key) < 5) {
$msg['error'][] = _("Bitte geben Sie einen API-Key mit min. 5 Zeichen an.");
}
foreach ($rule->ip_range as $key => $ip) {
if (!$ip) {
unset($rule->ip_range[$key]);
continue;
}
list($ip_address, $mask) = explode('/', $ip);
if (!ip2long($ip_address) || ($mask && ($mask < 8 || $mask > 30))) {
$msg['error'][] = sprintf(_("Der IP Bereich %s ist ungültig."), htmlready($ip));
unset($rule->ip_range[$key]);
}
}
if (!$rule->method) {
$msg['info'][] = _("Eine Regel ohne angegebene Methode gilt für alle Methoden!");
}
if (!count($rule->ip_range)) {
$msg['info'][] = _("Eine Regel ohne IP Bereich gilt für alle IP Adressen!");
}
if ($msg['error']) {
PageLayout::postMessage(MessageBox::error(_("Die Regel wurde nicht gespeichert."), $msg['error']));
$this->edit = $rule->id;
$this->render_action('index');
return;
} else {
if ($rule->store()) {
PageLayout::postMessage(MessageBox::success(_("Die Regel wurde gespeichert."), $msg['info']));
}
}
}
$this->redirect($this->url_for('admin/webservice_access'));
}
function test_action()
{
if (Request::submitted('ok')) {
CSRFProtection::verifyUnsafeRequest();
$test_api_key = trim(Request::get("test_api_key"));
$test_method = trim(Request::get("test_method"));
$test_ip = trim(Request::get("test_ip"));
if ($test_api_key && $test_method && $test_ip) {
if (WebserviceAccessRule::checkAccess($test_api_key, $test_method, $test_ip)) {
PageLayout::postMessage(MessageBox::success(_("Zugriff erlaubt.")));
} else {
PageLayout::postMessage(MessageBox::error(_("Zugriff verboten.")));
}
}
}
}
/**
* reload all rules from database
*/
function get_all_rules()
{
$this->ws_rules = [];
foreach (WebserviceAccessRule::findAll() as $rule) {
$this->ws_rules[$rule->id] = $rule;
}
}
}
......@@ -23,7 +23,6 @@ class Admission_CoursesetController extends AuthenticatedController
{
parent::before_filter($action, $args);
if (!Request::isXhr()) {
PageLayout::setTitle(_('Anmeldesets'));
// Get only own courses if user doesn't have permission to edit institute-wide coursesets.
$this->onlyOwnCourses = true;
......@@ -34,8 +33,6 @@ class Admission_CoursesetController extends AuthenticatedController
} else {
throw new AccessDeniedException();
}
}
PageLayout::addScript('studip-admission.js');
$views = new ActionsWidget();
$views->addLink(
......@@ -44,6 +41,7 @@ class Admission_CoursesetController extends AuthenticatedController
Icon::create('add')
)->setActive($action === 'configure');
Sidebar::Get()->addWidget($views);
if (!isset($this->instant_course_set_view)) {
$this->instant_course_set_view = false;
}
......@@ -85,11 +83,11 @@ class Admission_CoursesetController extends AuthenticatedController
$filter['semester_id'] = $this->current_semester_id != 'all' ? $this->current_semester_id : null;
$filter['rule_types'] = array_keys($this->current_rule_types);
$this->myInstitutes = CoursesetModel::getInstitutes($filter);
if (!$this->current_institut_id) {
if (!$this->current_institut_id && count($this->myInstitutes) > 0) {
if (!isset($this->myInstitutes['all']['count']) || $this->myInstitutes['all']['count'] < 100) {
$this->current_institut_id = 'all';
} else {
list($this->current_institut_id) = reset($this->myInstitutes);
$this->current_institut_id = array_key_first($this->myInstitutes) ?? 'all';
}
}
$chunks = explode('_', $this->current_institut_id);
......@@ -105,7 +103,7 @@ class Admission_CoursesetController extends AuthenticatedController
}
foreach ($institutes as $one) {
if ($this->myInstitutes[$one]['count']) {
if (!empty($this->myInstitutes[$one]['count'])) {
$sets = CourseSet::getCoursesetsByInstituteId($one, $filter);
foreach ($sets as $set) {
$courseset = new CourseSet($set['set_id']);
......@@ -258,6 +256,66 @@ class Admission_CoursesetController extends AuthenticatedController
$tpl->set_attribute('rights', false);
}
$this->instTpl = $tpl->render();
$this->semesters = array_reverse(array_map(
fn ($s) => $s->toArray(),
Semester::getAll()
));
if ($GLOBALS['perm']->have_perm('root')) {
$this->isearch = new StandardSearch('Institut_id');
$this->myinst = [];
} else {
$this->isearch = null;
$this->myinst = array_map(
fn ($i) => [
'id' => $i['Institut_id'],
'name' => $i['Name'],
'faculty' => $i['is_fak'] ? null : $i['fakultaets_id']
],
Institute::getMyInstitutes()
);
}
$props = [
'all-semesters' => $this->semesters,
'my-institutes'=> $this->myinst,
'my-user-lists' => array_values(
array_map(
fn ($list) => [
'id' => $list->getId(),
'name' => $list->getName(),
'factor' => $list->getFactor(),
'count' => $list->getUserCount()
],
$this->myUserlists
)
),
'institute-search' => (string) $this->isearch,
'instant-course-set-view' => $this->instant_course_set_view
];
if ($this->courseset) {
$props['course-set-id'] = $this->courseset->getId();
}
$this->render_vue_app(
Studip\VueApp::create('admission/ConfigureCourseSet')
->withProps($props)
);
Helpbar::get()->addPlainText(
_('Regeln'),
_('Hier können Sie die Regeln, Eigenschaften und Zuordnungen des Anmeldesets bearbeiten.')
);
Helpbar::get()->addPlainText(
_('Info'),
_('Sie können das Anmeldeset allen Einrichtungen zuordnen, an denen Sie mindestens Lehrendenrechte haben.')
);
Helpbar::get()->addPlainText(
_('Sichtbarkeit'),
_('Alle Veranstaltungen der Einrichtungen, an denen Sie mindestens Lehrendenrechte haben, können zum Anmeldeset hinzugefügt werden.')
);
}
/**
......@@ -315,9 +373,9 @@ class Admission_CoursesetController extends AuthenticatedController
}
PageLayout::postSuccess(sprintf(_('Das Anmeldeset: %s wurde gespeichert'), htmlReady($courseset->getName())));
if ($this->instant_course_set_view) {
$this->redirect($this->url_for('course/admission'));
$this->relocate('course/admission');
} else {
$this->redirect($this->url_for('admission/courseset/configure', $courseset->getId()));
$this->relocate('admission/courseset/configure', $courseset->getId());
}
}
}
......@@ -327,15 +385,19 @@ class Admission_CoursesetController extends AuthenticatedController
*
* @param String $coursesetId the course set to delete
*/
public function delete_action($coursesetId) {
public function delete_action($coursesetId)
{
$this->courseset = new CourseSet($coursesetId);
if (Request::int('really')) {
$this->courseset->delete();
$this->redirect($this->url_for('admission/courseset'));
if (!$this->courseset->isUserAllowedToEdit(User::findCurrent()->id)) {
throw new AccessDeniedException(_('Sie dürfen diese Anmelderegel nicht löschen.'));
}
if (Request::int('cancel')) {
$this->redirect($this->url_for('admission/courseset'));
if (Request::bool('really')) {
$this->courseset->delete();
}
$this->relocate('admission/courseset');
}
/**
......@@ -544,7 +606,7 @@ class Admission_CoursesetController extends AuthenticatedController
if ($ok) {
PageLayout::postSuccess(_('Die zugeordneten Veranstaltungen wurden konfiguriert.'));
}
$this->redirect($this->url_for('admission/courseset/configure/' . $courseset->getId()));
$this->relocate('admission/courseset/configure/' . $courseset->getId());
return;
}
}
......@@ -757,12 +819,18 @@ class Admission_CoursesetController extends AuthenticatedController
$ids = Request::optionArray('ids');
if (Request::submitted('delete')) {
$deleted = 0;
foreach ($ids as $id) {
$courseset = new CourseSet($id);
if ($courseset->isUserAllowedToEdit(User::findCurrent()->id)) {
$courseset->delete();
$deleted += 1;
}
}
if ($deleted > 0) {
PageLayout::postSuccess(_('Die Anmeldesets wurden gelöscht.'));
}
}
$this->redirect('admission/courseset');
}
......
......@@ -50,16 +50,16 @@ class Admission_RestrictedCoursesController extends AuthenticatedController
$this->current_institut_id = 'all';
}
if (!$this->current_semester_id) {
$this->current_semester_id = $_SESSION['_default_sem'];
$this->current_semester_id = $_SESSION['_default_sem'] ?? Semester::findDefault()->id;
} else {
$_SESSION['_default_sem'] = $this->current_semester_id;
}
$semester = Semester::find($this->current_semester_id);
$sem_condition .= "
AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = " . DBManager::get()->quote($semester->getId()) . ")
AND (`semester_courses`.`semester_id` IS NULL OR `semester_courses`.`semester_id` = " . DBManager::get()->quote($semester->getId()) . ")
";
if ($this->sem_name_prefix) {
$sem_condition .= sprintf('AND (seminare.Name LIKE %1$s OR seminare.VeranstaltungsNummer LIKE %1$s) ', DBManager::get()->quote($this->sem_name_prefix . '%'));
$sem_condition .= sprintf('AND (`seminare`.`Name` LIKE %1$s OR `seminare`.`VeranstaltungsNummer` LIKE %1$s) ', DBManager::get()->quote($this->sem_name_prefix . '%'));
}
if ($GLOBALS['perm']->have_perm('dozent')) {
$this->my_inst = $this->get_institutes($sem_condition);
......@@ -72,6 +72,7 @@ class Admission_RestrictedCoursesController extends AuthenticatedController
foreach (words('current_institut_id sem_name_prefix') as $param) {
$_SESSION[get_class($this)][$param] = $this->$param;
}
$this->additional_data = [];
if (Request::get('csv')) {
$captions = [_("Anmeldeset"),
_("Nummer"),
......@@ -86,18 +87,34 @@ class Admission_RestrictedCoursesController extends AuthenticatedController
_("Endzeitpunkt")];
$data = [];
foreach ($this->courses as $course) {
$additional_data = $this->getAdditionalCourseData($course);
$start_time = '';
$start_semester = $course->getStartSemester();
if ($start_semester) {
$start_time = date('d.m.Y H:i', $start_semester->beginn);
}
$end_time = '';
$end_semester = $course->getEndSemester();
if ($end_semester) {
$end_time = date('d.m.Y H:i', $end_semester->ende);
}
$row = [];
$row[] = $course['cs_name'];
$row[] = $course['course_number'];
$row[] = $course['course_name'];
$row[] = (int)$course['admission_turnout'];
$row[] = $course['count_teilnehmer'] + $course['count_prelim'];
$row[] = (int)$course['count_claiming'];
$row[] = (int)$course['count_prelim'];
$row[] = (int)$course['count_waiting'];
$row[] = $course['distribution_time'] ? strftime('%x %R', $course['distribution_time']) : '';
$row[] = isset($course['start_time']) ? strftime('%x %R', $course['start_time']) : '';
$row[] = isset($course['end_time']) ? strftime('%x %R', $course['end_time']) : '';
$row[] = $additional_data['courseset_name'];
$row[] = $course->veranstaltungsnummer;
$row[] = $course->name;
$row[] = (int)$course->admission_turnout ?: '';
$row[] = $additional_data['participant_count'] + $additional_data['accepted_count'];
$row[] = (int)$additional_data['claiming_count'];
$row[] = (int)$additional_data['accepted_count'];
$row[] = (int)$additional_data['awaiting_count'];
$row[] = $additional_data['distribution_time'] ? date('d.m.Y H:i', $additional_data['distribution_time']) : '';
$row[] = $start_time;
$row[] = $end_time;
$data[] = $row;
}
......@@ -111,7 +128,14 @@ class Admission_RestrictedCoursesController extends AuthenticatedController
);
return;
}
} else {
//We need to loop over each course and fetch additional data to fill the
//not_distributed_coursesets attribute before showing the view.
foreach ($this->courses as $course) {
$this->additional_data[$course->id] = $this->getAdditionalCourseData($course);
}
}
if (is_array($this->not_distributed_coursesets)) {
PageLayout::postInfo(
_("Es existieren Anmeldesets, die zum Zeitpunkt der Platzverteilung nicht gelost wurden. Stellen Sie sicher, dass der Cronjob \"Losverfahren überprüfen\" ausgeführt wird."),
......@@ -119,83 +143,92 @@ class Admission_RestrictedCoursesController extends AuthenticatedController
}
}
function get_courses($seminare_condition)
/**
* Fetches additional data for a course and sets the not_distributed_coursesets
* attribute in some cases.
*
* @param Course $course The course to fetch data for.
*
* @return array An associative array with additional data.
*/
protected function getAdditionalCourseData(Course $course) : array
{
$data = [];
$courseset = $course->getCourseSet();
if ($courseset) {
$data['courseset_id'] = $courseset->getId();
$data['courseset_name'] = $courseset->getName();
if ($courseset->hasAlgorithmRun()) {
$data['claiming_count'] = 0;
} else {
$data['claiming_count'] = count(AdmissionPriority::getPrioritiesByCourse($courseset->getId(), $course->id));
}
$data['distribution_time'] = $courseset->getSeatDistributionTime();
if (
$data['distribution_time'] < time() - 1000
&& !$courseset->hasAlgorithmRun()
) {
$this->not_distributed_coursesets[] = $courseset->getName();
}
$timed_admission = $courseset->getAdmissionRule(TimedAdmission::class);
if ($timed_admission) {
$data['admission_start_time'] = $timed_admission->getStartTime();
$data['admission_end_time'] = $timed_admission->getEndTime();
}
} else {
$data['courseset_id'] = '';
$data['courseset_name'] = '';
$data['claiming_count'] = 0;
$data['distribution_time'] = 0;
$data['admission_start_time'] = '';
$data['admission_end_time'] = '';
}
$data['participant_count'] = CourseMember::countByCourseAndStatus($course->id, ['user', 'autor']);
$data['accepted_count'] = AdmissionApplication::countBySql(
"`seminar_id` = :course_id AND `status` = 'accepted'",
['course_id' => $course->id]
);
$data['awaiting_count'] = AdmissionApplication::countBySql(
"`seminar_id` = :course_id AND `status` = 'awaiting'",
['course_id' => $course->id]
);
return $data;
}
protected function get_courses($seminare_condition)
{
$chunks = explode('_', $this->current_institut_id);
$institut_id = $chunks[0];
$all = $chunks[1] ?? null;
// Prepare count statements
$query = "SELECT count(*)
FROM seminar_user
WHERE seminar_id = ? AND status IN ('user', 'autor')";
$count0_statement = DBManager::get()->prepare($query);
$query = "SELECT SUM(status = 'accepted') AS count2,
SUM(status = 'awaiting') AS count3
FROM admission_seminar_user
WHERE seminar_id = ?
GROUP BY seminar_id";
$count1_statement = DBManager::get()->prepare($query);
$parameters = [];
$sql = "SELECT seminare.seminar_id,seminare.Name as course_name,seminare.VeranstaltungsNummer as course_number,
admission_prelim, admission_turnout,seminar_courseset.set_id
FROM seminar_courseset
INNER JOIN courseset_rule csr ON csr.set_id=seminar_courseset.set_id AND csr.type='ParticipantRestrictedAdmission'
INNER JOIN seminare ON seminar_courseset.seminar_id=seminare.seminar_id
LEFT JOIN semester_courses ON (seminare.Seminar_id = semester_courses.course_id)
$sql = "JOIN `seminar_courseset`
USING (`seminar_id`)
JOIN `courseset_rule` csr
ON csr.`set_id` = `seminar_courseset`.`set_id`
AND csr.`type` = 'ParticipantRestrictedAdmission'
LEFT JOIN `semester_courses`
ON `seminare`.`Seminar_id` = `semester_courses`.`course_id`
";
if ($institut_id === 'all' && $GLOBALS['perm']->have_perm('root')) {
$sql .= "WHERE 1 {$seminare_condition} ";
} elseif ($all == 'all') {
$sql .= "INNER JOIN Institute USING (Institut_id)
WHERE Institute.fakultaets_id = ? {$seminare_condition}
WHERE Institute.fakultaets_id = :faculty_id {$seminare_condition}
";
$parameters[] = $institut_id;
$parameters['faculty_id'] = $institut_id;
} else {
$sql .= "WHERE seminare.Institut_id = ? {$seminare_condition}
$sql .= "WHERE seminare.Institut_id = :institute_id {$seminare_condition}
";
$parameters[] = $institut_id;
$parameters['institute_id'] = $institut_id;
}
$sql .= "GROUP BY seminare.Seminar_id ORDER BY seminar_courseset.set_id, seminare.Name";
$sql .= "GROUP BY `seminare`.`Seminar_id` ORDER BY `seminar_courseset`.`set_id`, `seminare`.`Name`";
$statement = DBManager::get()->prepare($sql);
$statement->execute($parameters);
$csets = [];
$ret = [];
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
$seminar_id = $row['seminar_id'];
$ret[$seminar_id] = $row;
$count0_statement->execute([$seminar_id]);
$count = $count0_statement->fetchColumn();
$ret[$seminar_id]['count_teilnehmer'] = $count;
$count1_statement->execute([$seminar_id]);
$counts = $count1_statement->fetch(PDO::FETCH_ASSOC);
$ret[$seminar_id]['count_prelim'] = (int) ($counts['count2'] ?? 0);
$ret[$seminar_id]['count_waiting'] = (int) ($counts['count3'] ?? 0);
if (!isset($csets[$row['set_id']])) {
$csets[$row['set_id']] = new CourseSet($row['set_id']);
}
$cs = $csets[$row['set_id']];
$ret[$seminar_id]['cs_name'] = $cs->getName();
$ret[$seminar_id]['distribution_time'] = $cs->getSeatDistributionTime();
if ($ret[$seminar_id]['distribution_time'] < (time() - 1000) && !$cs->hasAlgorithmRun()) {
$this->not_distributed_coursesets[] = $cs->getName();
}
if ($ta = $cs->getAdmissionRule('TimedAdmission')) {
$ret[$seminar_id]['start_time'] = $ta->getStartTime();
$ret[$seminar_id]['end_time'] = $ta->getEndTime();
}
if (!$cs->hasAlgorithmRun()) {
$ret[$seminar_id]['count_claiming'] = count(AdmissionPriority::getPrioritiesByCourse($row['set_id'], $seminar_id));
}
}
return $ret;
return Course::findBySql($sql, $parameters);
}
function get_institutes($seminare_condition)
......
<?php
/**
* Admission_RuleController - Admission rules
*
* 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 Thomas Hackl <thomas.hackl@uni-passau.de>
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
* @category Stud.IP
* @since 3.0
*/
class Admission_RuleController extends AuthenticatedController
{
/**
* @see AuthenticatedController::before_filter
*/
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
if ($GLOBALS['perm']->have_perm('admin') || ($GLOBALS['perm']->have_perm('dozent') && Config::get()->ALLOW_DOZENT_COURSESET_ADMIN)) {
Navigation::activateItem('/browse/coursesets');
}
PageLayout::setTitle(_('Anmeldesets'));
}
/**
* Gets the template for the rule configuration form.
*
* @param String $ruleType Class name of the rule to configure.
* @param String $ruleId Optional ID of an existing rule.
*/
public function configure_action($ruleType = '', $ruleId = '')
{
$this->ruleTypes = AdmissionRule::getAvailableAdmissionRules();
UserFilterField::getAvailableFilterFields();
$this->ruleType = $ruleType;
// Check if rule data has been given via request.
if (Request::getArray('rules')) {
$rule_siblings = [];
foreach (Request::getManyObjects('rules', 'AdmissionRule') as $rule) {
if ($ruleType == get_class($rule) && $rule->getId() == Request::get('ruleId')) {
$this->rule = $rule;
} else {
$rule_siblings[$rule->getId()] = $rule;
}
}
if (!$this->rule && in_array($ruleType, array_keys($this->ruleTypes))) {
$this->rule = new $ruleType($ruleId);
}
$this->rule->setSiblings($rule_siblings);
} elseif (Request::get('rule')) {
$rule = Request::getObject('rule', 'AdmissionRule');
if ($ruleType == get_class($rule)) {
$this->rule = $rule;
}
} elseif (in_array($ruleType, array_keys($this->ruleTypes))) {
$this->rule = new $ruleType($ruleId);
}
if ($this->rule) {
$this->ruleTemplate = $this->rule->getTemplate();
}
}
/**
* Shows a form for selecting which rule type to use.
*
* @param String $cs_id ID of a courseset the rule shall belong to.
*/
public function select_type_action($cs_id = '')
{
$this->ruleTypes = AdmissionRule::getAvailableAdmissionRules();
$this->courseset = new CourseSet($cs_id);
$this->courseset->clearAdmissionRules();
foreach (Request::getManyObjects('rules', 'AdmissionRule') as $rule) {
$this->courseset->addAdmissionRule($rule);
}
}
/**
* Saves the given rule.
*
* @param String $ruleType The class name of the configured rule.
* @param String $ruleId ID of the rule to save, or empty if this is a new rule.
*/
public function save_action($ruleType, $ruleId = '')
{
CSRFProtection::verifyUnsafeRequest();
$this->rule = $this->loadRule($ruleType, $ruleId);
$requestData = Request::getInstance();
// Check for start and end date and parse the String values to timestamps.
if (!empty($requestData['start_date'])) {
$parsed = date_parse($requestData['start_date']);
$timestamp = mktime($parsed['hour'], $parsed['minute'], 0,
$parsed['month'], $parsed['day'], $parsed['year']);
$requestData['start_time'] = $timestamp;
}
if (!empty($requestData['end_date'])) {
$parsed = date_parse($requestData['end_date']);
$timestamp = mktime($parsed['hour'], $parsed['minute'], 0,
$parsed['month'], $parsed['day'], $parsed['year']);
$requestData['end_time'] = $timestamp;
}
$this->rule->setAllData($requestData);
}
/**
* Validates if the values given in the current request are sufficient to
* configure a rule of the given type.
*
* @param String $ruleType Class name of the rule to check.
* @param String $ruleId ID of the rule to save, or empty if this is a new rule.
*/
public function validate_action($ruleType, $ruleId = '')
{
$rule = $this->loadRule($ruleType, $ruleId);
$this->errors = $rule->validate(Request::getInstance());
}
/**
* Loads a rule by string and ensures that it is a subclass of the abstract
* admission rule.
*
* @param string $rule_type
* @param string $rule_id
* @return AdmissionRule
*/
private function loadRule(string $rule_type, string $rule_id = ''): AdmissionRule
{
static $initialized = false;
if (!$initialized) {
// This is neccessary so that all admission rules are correctly
// loaded and known to the system
AdmissionRule::getAvailableAdmissionRules();
$initialized = true;
}
if (!is_a($rule_type, AdmissionRule::class, true)) {
throw new InvalidArgumentException('Rule type must be a subclass of ' . AdmissionRule::class);
}
return new $rule_type($rule_id);
}
}
......@@ -27,7 +27,6 @@ class Admission_RuleadministrationController extends AuthenticatedController
$GLOBALS['perm']->check('root');
Navigation::activateItem('/admin/config/admissionrules');
PageLayout::addScript('studip-admission.js');
$sidebar = Sidebar::Get();
......
......@@ -26,7 +26,6 @@ class Admission_UserlistController extends AuthenticatedController
PageLayout::setTitle(_('Personenlisten'));
Navigation::activateItem('/browse/coursesets/userlists');
PageLayout::addScript('studip-admission.js');
Sidebar::get()->addWidget(new ActionsWidget())->addLink(
_('Personenliste anlegen'),
......@@ -146,7 +145,7 @@ class Admission_UserlistController extends AuthenticatedController
PageLayout::postInfo(
sprintf(_('%s wurde von der Liste entfernt, die Liste ist aber noch nicht gespeichert.'),
User::find($userId)->getFullname()));
User::find($userId)->getFullName()));
$this->redirect($this->url_for('admission/userlist/configure', $userlistId));
}
......
<?php
require_once 'lib/bootstrap-api.php';
/**
* @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
**/
class Api_AuthorizationsController extends AuthenticatedController
{
/**
*
**/
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
$GLOBALS['perm']->check('autor');
Navigation::activateItem('/profile/settings/api');
PageLayout::setTitle(_('Applikationen'));
$this->types = [
'website' => _('Website'),
'program' => _('Herkömmliches Desktopprogramm'),
'app' => _('Mobile App')
];
}
/**
*
**/
public function index_action()
{
$this->consumers = RESTAPI\UserPermissions::get($GLOBALS['user']->id)->getConsumers();
$this->types = [
'website' => _('Website'),
'program' => _('Herkömmliches Desktopprogramm'),
'app' => _('Mobile App')
];
$widget = new SidebarWidget();
$widget->setTitle(_('Informationen'));
$widget->addElement(new WidgetElement(_('Dies sind die Apps, die Zugriff auf Ihren Account haben.')));
Sidebar::Get()->addWidget($widget);
}
/**
*
**/
public function revoke_action($id)
{
$consumer = new RESTAPI\Consumer\OAuth($id);
$consumer->revokeAccess($GLOBALS['user']->id);
PageLayout::postMessage(MessageBox::success(_('Der Applikation wurde der Zugriff auf Ihre Daten untersagt.')));
$this->redirect('api/authorizations');
}
}
<?php
require_once 'lib/bootstrap-api.php';
/**
* @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
**/
class Api_OauthController extends StudipController
{
/**
*
**/
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
# initialize Stud.IP-Session
page_open(['sess' => 'Seminar_Session',
'auth' => 'Seminar_Default_Auth',
'perm' => 'Seminar_Perm',
'user' => 'Seminar_User']);
$this->set_layout(null);
}
/**
*
**/
public function index_action()
{
$this->render_text('TODO');
}
/**
*
**/
public function request_token_action()
{
$server = new OAuthServer();
$token = $server->requestToken();
$this->response->headers = [];
$this->render_nothing();
}
/**
*
**/
public function authorize_action()
{
global $user, $auth;
$auth_plugin = Config::get()->API_OAUTH_AUTH_PLUGIN;
if ($GLOBALS['user']->id === 'nobody' && $auth_plugin !== 'Standard' && !Request::option('sso')) {
$params = $_GET;
$params['sso'] = strtolower($auth_plugin);
$this->redirect($this->url_for('api/oauth/authorize?' . http_build_query($params)));
return;
} else {
$auth->login_if($user->id === 'nobody');
}
$user_id = RESTAPI\Consumer\OAuth::getOAuthId($GLOBALS['user']->id);
try {
$consumer = RESTAPI\Consumer\Base::detectConsumer('oauth', 'request');
if (!$consumer) {
$this->response->set_status(400, 'No consumer detected');
$this->render_nothing();
return;
}
if (Request::submitted('allow')) {
$result = $consumer->grantAccess($GLOBALS['user']->id);
$redirect_uri = Request::get('oauth_callback', $consumer->callback);
if ($redirect_uri) {
$this->redirect($redirect_uri);
} else {
// No oauth_callback, show the user the result of the authorization
// ** your code here **
PageLayout::postMessage(MessageBox::success(_('Sie haben der Applikation Zugriff auf Ihre Daten gewährt.')));
$this->redirect('api/authorizations#' . $consumer->auth_key);
}
return;
}
} catch (OAuthException2 $e) {
// No token to be verified in the request, show a page where the user can enter the token to be verified
// **your code here**
die('invalid');
}
PageLayout::disableHeader();
PageLayout::setTitle(sprintf(_('"%s" bittet um Zugriff'), $consumer->title));
$this->set_layout($GLOBALS['template_factory']->open('layouts/base.php'));
$this->consumer = $consumer;
$this->token = Request::option('oauth_token');
$this->oauth_callback = Request::get('oauth_callback');
}
/**
*
**/
public function access_token_action()
{
$server = new OAuthServer();
$server->accessToken();
$this->response->headers = [];
$this->render_nothing();
}
}
......@@ -31,7 +31,7 @@ class Api_Oauth2_ApplicationsController extends AuthenticatedController
$this->application = $this->formatApplication($accessToken);
if (!$this->application) {
throw new Trails_Exception(500, 'Error finding client.');
throw new Trails\Exception(500, 'Error finding client.');
}
}
......@@ -42,7 +42,7 @@ class Api_Oauth2_ApplicationsController extends AuthenticatedController
$user = User::findCurrent();
$accessToken = AccessToken::find(Request::option('application'));
if (!$accessToken) {
throw new Trails_Exception(404);
throw new Trails\Exception(404);
}
if ($accessToken['user_id'] !== $user->id) {
throw new AccessDeniedException();
......
......@@ -13,7 +13,7 @@ class Api_Oauth2_AuthorizeController extends OAuth2Controller
parent::before_filter($action, $args);
if ('index' !== $action) {
throw new Trails_Exception(404);
throw new Trails\Exception(404);
}
$action = $this->determineAction();
......@@ -24,7 +24,9 @@ class Api_Oauth2_AuthorizeController extends OAuth2Controller
$method = $this->getMethod();
if (Request::submitted('auth_token')) {
$GLOBALS['auth']->login_if('nobody' === $GLOBALS['user']->id);
if ('nobody' === $GLOBALS['user']->id) {
throw new LoginException();
}
CSRFProtection::verifyUnsafeRequest();
switch ($method) {
......@@ -50,16 +52,8 @@ class Api_Oauth2_AuthorizeController extends OAuth2Controller
$authToken = randomString(32);
$this->freezeSessionVars($authRequest, $authToken);
// show login form if not logged in
$authPlugin = Config::get()->getValue('API_OAUTH_AUTH_PLUGIN');
if ('nobody' === $GLOBALS['user']->id && 'Standard' !== $authPlugin && !Request::option('sso')) {
$queryParams = $psrRequest->getQueryParams();
$queryParams['sso'] = strtolower($authPlugin);
$this->redirect($this->authorizeURL($queryParams));
return;
} else {
$GLOBALS['auth']->login_if('nobody' === $GLOBALS['user']->id);
if ('nobody' === $GLOBALS['user']->id) {
throw new LoginException();
}
$this->client = $client;
......
......@@ -8,6 +8,8 @@ abstract class OAuth2Controller extends StudipController
{
use NegotiatesWithPsr7;
protected $with_session = true;
/**
* @return void
*/
......@@ -15,13 +17,6 @@ abstract class OAuth2Controller extends StudipController
{
parent::before_filter($action, $args);
page_open([
'sess' => 'Seminar_Session',
'auth' => 'Seminar_Default_Auth',
'perm' => 'Seminar_Perm',
'user' => 'Seminar_User',
]);
$this->set_layout(null);
$this->container = new Studip\OAuth2\Container();
......@@ -42,7 +37,7 @@ abstract class OAuth2Controller extends StudipController
return $this->convertPsrResponse($psrResponse);
}
return new Trails_Response($exception->getMessage(), [], 500);
return new Trails\Response($exception->getMessage(), [], 500);
}
protected function getAuthorizationServer(): AuthorizationServer
......
......@@ -8,11 +8,11 @@ class Api_Oauth2_TokenController extends OAuth2Controller
parent::before_filter($action, $args);
if ('index' !== $action) {
throw new Trails_Exception(404);
throw new Trails\Exception(404);
}
if (!Request::isPost()) {
throw new Trails_Exception(405);
throw new Trails\Exception(405);
}
$action = 'issue_token';
......
<?php
/*
* Copyright (C) 2009 - Marcus Lunzenauer <mlunzena@uos.de>
*
* 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.
*/
class AuthenticatedController extends StudipController
{
protected $with_session = true; //we do need to have a session for this controller
protected $allow_nobody = false; //nobody is not allowed and always gets a login-screen
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
// Restore request if present
if (isset($this->flash['request'])) {
foreach ($this->flash['request'] as $key => $value) {
Request::set($key, $value);
}
}
}
protected function keepRequest()
{
$this->flash['request'] = Request::getInstance()->getIterator()->getArrayCopy();
}
}
<?php
/**
* AvatarController - Administration of all avatar related settings
*
* 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 Jan-Hendrik Willms <tleilax+studip@gmail.com>
* @author Thomas Hackl <thomas.hackl@uni-passau.de>
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
* @category Stud.IP
* @since 4.2
*/
class AvatarController extends AuthenticatedController
{
/**
* Display the avatar information of a user, course or institute
* @param string $type object type: 'user', 'course' or 'institute'
* @param string $id ID of the object this avatar belongs to
*/
public function update_action($type, $id)
{
// Check for permission to save a new avatar.
if ($type == 'user') {
PageLayout::setHelpKeyword('Basis.HomepageBild');
PageLayout::setTitle(_('Profilbild ändern'));
$has_perm = $GLOBALS['perm']->have_profile_perm('user', $id);
$class = Avatar::class;
$this->cancel_link = $this->url_for('profile', ['username' => User::find($id)->username]);
} else if ($type == 'institute') {
PageLayout::setTitle(Context::getHeaderLine() . ' - ' . _('Einrichtungsbild ändern'));
$has_perm = $GLOBALS['perm']->have_studip_perm('admin', $id);
$class = InstituteAvatar::class;
$this->cancel_link = $this->url_for('institute/basicdata/index', ['cid' => $id]);
} else {
PageLayout::setTitle(Context::getHeaderLine() . ' - ' . _('Veranstaltungsbild ändern'));
$has_perm = $GLOBALS['perm']->have_studip_perm('tutor', $id);
$sem = Seminar::getInstance($id);
$studygroup_mode = $sem->getSemClass()->offsetget('studygroup_mode');
if ($studygroup_mode) {
$class = StudygroupAvatar::class;
$this->cancel_link = $this->url_for('course/studygroup/edit?cid=' . $id);
} else {
$class = CourseAvatar::class;
$this->cancel_link = $this->url_for('course/management?cid=' . $id);
}
}
if (!$has_perm) {
throw new AccessDeniedException(_('Sie haben keine Berechtigung, das Bild zu ändern.'));
}
if ($type === 'user') {
Navigation::activateItem('/profile/index');
} else if ($type === 'institute') {
Navigation::activateItem('/admin/institute/details');
} else {
Navigation::activateItem('/course/admin/avatar');
}
$avatar = $class::getAvatar($id);
$this->avatar = $avatar->getURL($class::NORMAL);
$this->customized = $avatar->is_customized();
$this->type = $type;
$this->id = $id;
}
/**
* Upload a new avatar or removes the current avatar.
* Sends an information email to the user if the action was not invoked by himself.
* @param string $type object type: 'user', 'course' or 'institute'
* @param string $id ID of the object this avatar belongs to
*/
public function upload_action($type, $id)
{
CSRFProtection::verifyUnsafeRequest();
// Check for permission to save a new avatar.
if ($type == 'user') {
$has_perm = $GLOBALS['perm']->have_profile_perm('user', $id);
$class = Avatar::class;
$redirect = 'profile?username=' . User::find($id)->username;
} else if ($type == 'institute') {
$has_perm = $GLOBALS['perm']->have_studip_perm('admin', $id);
$class = InstituteAvatar::class;
$redirect = 'institute/basicdata/index';
} else {
$has_perm = $GLOBALS['perm']->have_studip_perm('tutor', $id);
$sem = Seminar::getInstance($id);
$studygroup_mode = $sem->getSemClass()->offsetget('studygroup_mode');
if ($studygroup_mode) {
$class = StudygroupAvatar::class;
$redirect = 'course/studygroup/edit/?cid=' . $id;
} else {
$class = CourseAvatar::class;
$redirect = 'course/management';
}
}
if (!$has_perm) {
throw new AccessDeniedException(_('Sie haben keine Berechtigung, das Bild zu ändern.'));
}
if (Request::submitted('reset')) {
$class::getAvatar($id)->reset();
if ($type == 'user') {
Visibility::removePrivacySetting('picture', $id);
}
PageLayout::postSuccess(_('Bild gelöscht.'));
} elseif (Request::submitted('upload')) {
try {
// Get the Base64-encoded data from cropper.
$imgdata = Request::get('cropped-image');
// Extract actual image data (prepended by mime type and meta data)
list($type, $imgdata) = explode(';', $imgdata);
list(, $imgdata) = explode(',', $imgdata);
$imgdata = base64_decode($imgdata);
// Write data to file.
$filename = $GLOBALS['TMP_PATH'] . '/avatar-' . $id . '.png';
file_put_contents($filename, $imgdata);
// Use new image file for avatar creation.
$class::getAvatar($id)->createFrom($filename);
NotificationCenter::postNotification('AvatarDidUpload', $id);
$message = _('Die Bilddatei wurde erfolgreich hochgeladen. '
.'Eventuell sehen Sie das neue Bild erst, nachdem Sie diese Seite '
.'neu geladen haben (in den meisten Browsern F5 drücken).');
PageLayout::postSuccess($message);
// Send message to user if necessary.
if ($type == 'user') {
setTempLanguage($id);
$this->postPrivateMessage(_("Ein neues Bild wurde hochgeladen.\n"));
restoreLanguage();
Visibility::addPrivacySetting(_('Eigenes Bild'), 'picture', 'commondata', 1, $id);
}
unlink($filename);
} catch (Exception $e) {
PageLayout::postError($e->getMessage());
}
}
$this->relocate($redirect);
}
/**
* Deletes a custom avatar.
* @param string $type object type: 'user', 'course' or 'institute'
* @param string $id ID of the object this avatar belongs to
*/
public function delete_action($type, $id)
{
// Check for permission to delete avatar.
if ($type == 'user') {
$has_perm = $GLOBALS['perm']->have_profile_perm('user', $id);
$class = 'Avatar';
$redirect = 'profile';
} else if ($type == 'institute') {
$has_perm = $GLOBALS['perm']->have_studip_perm('admin', $id);
$class = 'InstituteAvatar';
$redirect = 'institute/basicdata/index';
} else {
$has_perm = $GLOBALS['perm']->have_studip_perm('tutor', $id);
$sem = Seminar::getInstance($id);
$studygroup_mode = $sem->getSemClass()->offsetget('studygroup_mode');
if ($studygroup_mode) {
$class = 'StudygroupAvatar';
$redirect = 'course/studygroup/edit/?cid=' . $id;
} else {
$class = 'CourseAvatar';
$redirect = 'course/management';
}
}
if (!$has_perm) {
throw new AccessDeniedException(_('Sie haben keine Berechtigung, das Bild zu ändern.'));
}
$class::getAvatar($id)->reset();
PageLayout::postMessage(MessageBox::success(_('Das Bild wurde gelöscht.')));
$this->relocate($redirect);
}
}
......@@ -56,8 +56,8 @@ class BlubberController extends AuthenticatedController
sprintf(
_('Wollen Sie ein Avatar-Bild nutzen? %sLaden Sie jetzt ein Bild hoch%s.'),
'<a href="' .
URLHelper::getLink('dispatch.php/avatar/update/user/' . $GLOBALS['user']->id) .
'" data-dialog>',
URLHelper::getLink('dispatch.php/settings/avatar/') .
'" >',
'</a>'
)
);
......@@ -135,7 +135,7 @@ class BlubberController extends AuthenticatedController
'user_id' => $user_id,
]);
}
$this->redirect("blubber/index/{$blubber->getId()}");
$this->relocate("blubber/index/{$blubber->getId()}");
return;
}
......@@ -149,17 +149,16 @@ class BlubberController extends AuthenticatedController
public function delete_action($thread_id)
{
CSRFProtection::verifyUnsafeRequest();
$this->thread = BlubberThread::find($thread_id);
if (!$this->thread->isWritable()) {
throw new AccessDeniedException();
}
if (Request::isPost()) {
CSRFProtection::verifySecurityToken();
$this->thread->delete();
PageLayout::postSuccess(_('Der Blubber wurde gelöscht.'));
}
$this->redirect('blubber/index');
return;
}
public function write_to_action($user_id = null)
......@@ -271,13 +270,10 @@ class BlubberController extends AuthenticatedController
$output = [];
foreach ($_FILES as $file) {
$newfile = null; //is filled below
$file_ref = null; //is also filled below
if ($file['size']) {
$document['user_id'] = $GLOBALS['user']->id;
$document['filesize'] = $file['size'];
$success = false;
$url = '';
try {
$root_dir = Folder::findTopFolder($GLOBALS['user']->id);
$root_dir = $root_dir->getTypedFolder();
......@@ -339,7 +335,6 @@ class BlubberController extends AuthenticatedController
}
} catch (Exception $e) {
$output['errors'][] = $e->getMessage();
$success = false;
}
if ($success) {
......@@ -357,7 +352,7 @@ class BlubberController extends AuthenticatedController
if ($type) {
$output['inserts'][] = "[{$type}]{$url}";
} else {
$output['inserts'][] = "[{$file_ref['name']}]{$url}";
$output['inserts'][] = "[{$file['name']}]{$url}";
}
}
}
......@@ -373,21 +368,22 @@ class BlubberController extends AuthenticatedController
}
PageLayout::setTitle(_('Person hinzufügen'));
if (Request::isPost() && Request::option('user_id')) {
$query = "INSERT IGNORE INTO blubber_mentions
SET thread_id = :thread_id,
user_id = :user_id,
external_contact = 0,
mkdate = UNIX_TIMESTAMP()";
$statement = DBManager::get()->prepare($query);
$statement->execute([
'thread_id' => $thread_id,
$data = [
'user_id' => Request::option('user_id'),
]);
$this->response->add_header('X-Dialog-Execute', 'STUDIP.Blubber.refreshThread');
$this->response->add_header('X-Dialog-Close', '1');
$this->render_json([
'thread_id' => $thread_id,
]);
'external_contact' => 0,
];
$blubber_mention = BlubberMention::findOneBySQL('user_id = ? AND thread_id = ?', [Request::option('user_id'), $thread_id]);
if ($blubber_mention) {
$blubber_mention->setData($data);
} else {
$blubber_mention = BlubberMention::create($data);
}
$blubber_mention->store();
$this->relocate('blubber/index/' . $thread_id);
return;
}
}
......@@ -408,13 +404,9 @@ class BlubberController extends AuthenticatedController
CourseAvatar::getAvatar($course->getId())->createFromUpload('avatar');
}
$query = "SELECT user_id
FROM blubber_mentions
WHERE thread_id = ?";
$statement = DBManager::get()->prepare($query);
$statement->execute([$this->thread->id]);
foreach ($statement->fetchFirst() as $user_id) {
CourseMember::insertCourseMember($course->getId(), $user_id, $user_id === $this->thread['user_id'] ? 'dozent' : 'tutor');
$blubber_mentions = BlubberMention::findBySQL('thread_id = ?', [$this->thread->id]);
foreach ($blubber_mentions as $blubber_mention) {
CourseMember::insertCourseMember($course->getId(), $blubber_mention->user_id, $blubber_mention->user_id === $this->thread['user_id'] ? 'dozent' : 'tutor');
}
$this->thread['context_type'] = 'course';
......@@ -424,14 +416,14 @@ class BlubberController extends AuthenticatedController
PluginManager::getInstance()->setPluginActivated(
PluginManager::getInstance()
->getPlugin('Blubber')
->getPlugin(Blubber::class)
->getPluginId(),
$course->getId(),
true
);
PageLayout::postSuccess(sprintf(_("Studiengruppe '%s' wurde angelegt."), htmlReady($course['name'])));
$this->redirect(URLHelper::getURL('seminar_main.php', ['auswahl' => $course->getId()]));
PageLayout::postSuccess(sprintf(_('Studiengruppe "%s" wurde angelegt.'), htmlReady($course['name'])));
$this->redirect(URLHelper::getURL('dispatch.php/course/go', ['to' => $course->getId()]));
}
}
......
......@@ -18,619 +18,972 @@ 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);
if (!Context::isCourse() && Navigation::hasItem('/calendar')) {
Navigation::activateItem('/calendar');
}
}
$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');
protected function buildSidebar(
bool $schedule = false,
string $user_id = '',
string $group_id = ''
) {
$sidebar = Sidebar::get();
$actions = new ActionsWidget();
$export = new ExportWidget();
if ($schedule) {
//Add the semester selector widget first:
$semester_widget = new SemesterSelectorWidget(
$this->url_for('calendar/calendar/schedule')
);
$sidebar->addWidget($semester_widget);
//Then add the actions for the action widget:
$actions->addLink(
_('Neuer Eintrag'),
$this->url_for('calendar/calendar/add_schedule_entry'),
Icon::create('add'),
['data-dialog' => 'size=default']
);
} else {
$params = [
'timestamp' => time()
];
if ($user_id) {
$params['user_id'] = $user_id;
} elseif ($group_id) {
$params['group_id'] = $group_id;
}
$actions->addLink(
_('Termin anlegen'),
$this->url_for('calendar/date/add', $params),
Icon::create('add'),
['data-dialog' => 'size=auto', 'class' => 'calendar-action']
);
}
if (!$GLOBALS['perm']->have_perm('admin')) {
$actions->addLink(
_('Veranstaltungen auswählen'),
$this->url_for('calendar/calendar/add_courses'),
Icon::create('seminar'),
['data-dialog' => 'size=medium']
);
}
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 (!$schedule) {
$export->addLink(
_('Termine exportieren'),
$this->url_for('calendar/calendar/export', ['timestamp' => time()]),
Icon::create('export'),
['data-dialog' => 'size=auto', 'class' => 'calendar-action']
);
$export->addLink(
_('Termine importieren'),
$this->url_for('calendar/calendar/import'),
Icon::create('import'),
['data-dialog' => 'size=auto']
);
$actions->addLink(
_('Kalender teilen'),
$this->url_for('calendar/calendar/share_select'),
Icon::create('share'),
['data-dialog' => 'size=auto']
);
$actions->addLink(
_('Gruppen verwalten'),
$this->url_for('contact/index'),
Icon::create('group2')
);
}
$export->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);
$sidebar->addWidget($export);
URLHelper::bindLinkParam('last_view', $this->last_view);
if (!$schedule) {
$date = new DateSelectWidget();
$date->setDate(\Studip\Calendar\Helper::getDefaultCalendarDate());
$date->setCalendarControl(true);
$sidebar->addWidget($date);
}
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 getUserCalendarSlotSettings() : array
{
$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::get()->addWidget($settings);
}
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 index_action()
{
// switch to the view the user has selected in his personal settings
$default_view = $this->settings['view'] ?: 'week';
PageLayout::setTitle(_('Kalender'));
// Remove cid
if (Request::option('self')) {
Context::close();
$default_date = \Studip\Calendar\Helper::getDefaultCalendarDate();
$this->redirect(URLHelper::getURL('dispatch.php/' . $this->base
. $default_view . '/' . $GLOBALS['user']->id, [], true));
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->redirect(URLHelper::getURL('dispatch.php/' . $this->base
. $default_view));
$this->redirect('calendar/calendar');
}
return;
}
public function edit_action($range_id = null, $event_id = null)
{
$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)));
} else {
$this->event->setStart($this->atime);
$this->event->setEnd($this->atime + 3600);
if (!Context::isCourse() && Navigation::hasItem('/calendar/calendar')) {
Navigation::activateItem('/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')));
$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();
}
} 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;
}
}
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;
}
return null;
}
if (!Request::isXhr()) {
PageLayout::setTitle($this->getTitle($this->calendar, _('Termin bearbeiten')));
}
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) {
$this->buildSidebar(
false,
$calendar_owner ? $calendar_owner->id : '',
$selected_group ? $selected_group->id : ''
);
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');
}
// 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
*
*/
$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->addLayoutCSSClass('calendar-action');
$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);
}
$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;
}
}
$stored = false;
if (Request::submitted('store')) {
$stored = $this->storeEventData($this->event, $this->calendar);
}
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]);
if ($available_groups) {
$group_select = new SelectWidget(
_('Gruppe'),
$this->url_for('calendar/calendar/index', ['view' => 'group']),
'group_id'
);
$group_select->addLayoutCSSClass('calendar-action');
$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'
);
$calendar_select->addLayoutCSSClass('calendar-action');
$select_options = [
'' => _('(bitte wählen)'),
User::findCurrent()->id => _('Eigener Kalender')
];
foreach ($other_users as $user) {
$select_options[$user->id] = sprintf('%1$s %2$s', $user->vorname, $user->nachname);
}
} else {
PageLayout::postMessage(MessageBox::success(_('Der Termin wurde gespeichert.')));
$this->relocate('calendar/single/' . $this->last_view, ['atime' => $this->atime]);
$calendar_select->setOptions($select_options, Request::get('user_id'));
$sidebar->addWidget($calendar_select);
}
}
$this->createSidebar('edit', $this->calendar);
$this->createSidebarFilter();
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);
}
public function edit_status_action($range_id, $event_id)
{
global $user;
$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' => ''
];
}
}
$calendar_group_title = $selected_group->name;
}
$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:
$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' => true,
'allDayText' => '',
'header' => [
'left' => (
$timeline_view
? 'resourceTimelineWeek,resourceTimelineDay'
: 'dayGridYear,dayGridMonth,timeGridWeek,timeGridDay'
),
'center' => 'title',
'right' => 'prev,today,next'
],
'weekNumbers' => true,
'views' => [
'dayGridMonth' => [
'eventTimeFormat' => ['hour' => 'numeric', 'minute' => '2-digit'],
'titleFormat' => ['year' => 'numeric', 'month' => 'long'],
'displayEventEnd' => true
],
'timeGridWeek' => [
'columnHeaderFormat' => ['weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true],
'weekends' => $calendar_settings['type_week'] === 'LONG',
'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'],
'slotDuration' => $slot_durations['week']
],
'timeGridDay' => [
'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']
],
'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/user_' . $calendar_owner->id
),
$data_url_params
),
'method' => 'GET',
'extraParams' => []
]
],
'resources' => $calendar_resources,
'resourceLabelText' => $calendar_group_title
]
);
}
$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;
public function course_action($course_id)
{
PageLayout::setTitle(_('Veranstaltungskalender'));
if (Request::submitted('store')) {
if (!$course_id || !Config::get()->CALENDAR_GROUP_ENABLE || !Config::get()->COURSE_CALENDAR_ENABLE) {
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();
$course = Course::find($course_id);
if (!$course) {
throw new AccessDeniedException(_('Sie dürfen diesen Kalender nicht sehen!'));
}
$status = Request::int('status', 1);
if ($status > 0 && $status < 6) {
$this->event->group_status = $status;
$stored = $this->event->store();
if (!$course->isVisibleForUser() || !$course->isCalendarReadable()) {
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]);
if (Navigation::hasItem('/course/calendar')) {
Navigation::activateItem('/course/calendar');
}
} 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());
$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', 'class' => 'calendar-action']
);
$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',
'center' => 'title',
'right' => 'prev,today,next'
],
'weekNumbers' => true,
'views' => [
'dayGridMonth' => [
'eventTimeFormat' => ['hour' => 'numeric', 'minute' => '2-digit'],
'titleFormat' => ['year' => 'numeric', 'month' => 'long'],
'displayEventEnd' => true
],
'timeGridWeek' => [
'columnHeaderFormat' => [ 'weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ],
'weekends' => $calendar_settings['type_week'] === 'LONG',
'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'],
'slotDuration' => $slot_settings['week']
],
'timeGridDay' => [
'columnHeaderFormat' => [ 'weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ],
'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'],
'slotDuration' => $slot_settings['day']
]
],
'defaultView' => 'timeGridWeek',
'timeGridEventMinHeight' => 20,
'eventSources' => [
[
'url' => $this->url_for('calendar/calendar/calendar_data/course_' . $course->id),
'method' => 'GET',
'extraParams' => []
]
]
]
);
}
$msg_text .= "\n**" . _('Ende') . ':** ';
if ($this->event->isDayEvent()) {
$msg_text .= strftime('%x ', $this->event->getEnd());
} else {
$msg_text .= strftime('%c', $this->event->getEnd());
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();
}
$msg_text .= "\n**" . _('Zusammenfassung') . ':** ' . $this->event->getTitle() . "\n";
if ($event_data = $this->event->getDescription()) {
$msg_text .= '**' . _('Beschreibung') . ":** $event_data\n";
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 = '';
$event->text_colour = '#000000';
$event->border_colour = '';
$result[] = $event->toFullcalendarEvent();
}
if ($event_data = $this->event->toStringCategories()) {
$msg_text .= '**' . _('Kategorie') . ":** $event_data\n";
//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 = '';
$event->text_colour = '#000000';
$event->border_colour = '';
$result[] = $event->toFullcalendarEvent();
}
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";
//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');
}
if ($event_data = $this->event->toStringRecurrence()) {
$msg_text .= '**' . _('Wiederholung') . ":** $event_data\n";
$this->render_json($result);
}
$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)
. ')';
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();
}
$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();
foreach ($group->items as $item) {
if ($item->user->isCalendarReadable()) {
$users[] = $item->user;
}
PageLayout::postMessage(MessageBox::success(_('Der Teilnahmestatus wurde gespeichert.')));
$this->relocate('calendar/single/' . $this->last_view, ['atime' => $this->atime]);
}
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!'));
}
}
$this->createSidebar('edit', $this->calendar);
$this->createSidebarFilter();
$result = [];
uasort($users, function(User $a, User $b) {
$fullname_a = $a->getFullName('no_title');
$fullname_b = $b->getFullName('no_title');
if ($fullname_a == $fullname_b) {
return 0;
}
return ($fullname_a > $fullname_b) ? 1 : -1;
});
public function switch_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;
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) {
$result[] = $data->toFullcalendarEvent();
} else {
//Prevent duplicate entries:
$data->title = $user->getFullName('no_title');
if (array_key_exists($event->calendar_date_id, $result)) {
$result[$event->calendar_date_id]['title'] .= ', ' . $data->title;
} else {
$result[$event->calendar_date_id] = $data->toFullcalendarEvent();
}
}
public function jump_to_action()
{
$date = Request::get('jmp_date');
if ($date) {
$atime = strtotime($date . strftime(' %T', $this->atime));
}
}
}
if ($timeline_view) {
$this->render_json($result);
} else {
$atime = 'now';
//Clean up the array keys:
$this->render_json(array_values($result));
}
$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 add_courses_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]));
$selected_semester_pseudo_id = Request::option('semester_id');
$this->selected_semester_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);
protected function storeEventData(CalendarEvent $event, SingleCalendar $calendar)
{
$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')
);
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;
}
}
$event->setStart($this->parseDateTime($dt_string));
if (Request::int('isdayevent')) {
$dt_string = Request::get('end_date') . ' 23:59:59';
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 {
$dt_string = sprintf(
'%s %u:%02u',
Request::get('end_date'),
Request::int('end_hour'),
Request::int('end_minute')
$this->selected_semester_id = $selected_semester_pseudo_id ?? '';
if (!Semester::exists($this->selected_semester_id)) {
$semester = Semester::findCurrent();
$this->selected_semester_id = $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/index');
}
}
$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 export_action()
{
PageLayout::setTitle(_('Termine exportieren'));
$this->begin = new DateTimeImmutable();
if (Request::submitted('timestamp')) {
$this->begin = $this->begin->setTimestamp(Request::int('timestamp'));
}
$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;
}
$this->relocate($this->url_for('calendar/calendar/export_file', [
'begin' => $this->begin->format('d.m.Y'),
'end' => $this->end->format('d.m.Y'),
'dates_to_export' => $this->dates_to_export
]));
}
}
public function export_file_action()
{
$begin = Request::getDateTime('begin', 'd.m.Y');
$end = Request::getDateTime('end', 'd.m.Y');
$dates_to_export = Request::option('dates_to_export', 'user');
$ical = '';
$calendar_export = new ICalendarExport();
if ($dates_to_export === 'user') {
$ical = $calendar_export->exportCalendarDates(User::findCurrent()->id, $begin, $end);
} elseif ($dates_to_export === 'course') {
$ical = $calendar_export->exportCourseDates(User::findCurrent()->id, $begin, $end);
$ical .= $calendar_export->exportCourseExDates(User::findCurrent()->id, $begin, $end);
} elseif ($dates_to_export === 'all') {
$ical = $calendar_export->exportCalendarDates(User::findCurrent()->id, $begin, $end);
$ical .= $calendar_export->exportCourseDates(User::findCurrent()->id, $begin, $end);
$ical .= $calendar_export->exportCourseExDates(User::findCurrent()->id, $begin, $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);
}
public function import_action() {}
public function import_file_action()
{
if (Request::submitted('import')) {
CSRFProtection::verifyUnsafeRequest();
$range_id = Context::getId() ?? User::findCurrent()->id;
$calendar_import = new ICalendarImport($range_id);
$calendar_import->convertPublicToPrivate(Request::bool('import_privat', false));
$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/'));
}
}
public function share_select_action()
{
if (!Config::get()->CALENDAR_GROUP_ENABLE) {
$this->redirect($this->url_for('calendar/calendar/publish'));
}
}
if ($event->getStart() > $event->getEnd()) {
$messages[] = _('Die Startzeit muss vor der Endzeit liegen.');
public function share_action()
{
PageLayout::setTitle(_('Kalender teilen'));
if (!Config::get()->CALENDAR_GROUP_ENABLE) {
throw new FeatureDisabledException();
}
$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');
foreach ($selected_user_ids as $user_id) {
$user = User::find($user_id);
if (!$user) {
//No user? No contact!
continue;
}
$exp_date = $exp_date ?: strftime('%x', time());
$rrule['expire'] = $this->parseDateTime($exp_date . ' 12:00');
$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;
}
switch ($rec_type) {
case 'daily':
if (Request::option('type_daily', 'day') === 'day') {
$rrule['linterval'] = Request::int('linterval_d', 1);
$rrule['rtype'] = 'DAILY';
if (in_array($user->id, $write_permissions)) {
$contact->calendar_permissions = 'WRITE';
} else {
$rrule['linterval'] = 1;
$rrule['wdays'] = '12345';
$rrule['rtype'] = 'WEEKLY';
$contact->calendar_permissions = 'READ';
}
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->store();
}
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()));
//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 {
$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()));
}
break;
$stmt = DBManager::get()->prepare(
"UPDATE `contact` SET `calendar_permissions` = ''
WHERE `owner_id` = :owner_id"
);
$stmt->execute(['owner_id' => User::findCurrent()->id]);
}
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);
PageLayout::postSuccess(
_('Die Kalenderfreigaben wurden geändert.')
);
$this->response->add_header('X-Dialog-Close', '1');
}
}
public function publish_action()
{
$this->short_id = null;
if (Request::submitted('delete_id')) {
CSRFProtection::verifyUnsafeRequest();
IcalExport::deleteKey(User::findCurrent()->id);
PageLayout::postSuccess(_('Die Adresse, unter der Ihre Termine abrufbar sind, wurde gelöscht'));
}
/**
* 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]);
if (Request::submitted('new_id')) {
CSRFProtection::verifyUnsafeRequest();
$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 {
$dates[] = strtotime($match[1].$match[2].$match[3].$match[4].$match[5]);
PageLayout::postError(_('Bitte geben Sie eine gültige Email-Adresse an.'));
}
$this->short_id = IcalExport::getKeyByUser(User::findCurrent()->id);
}
return $dates;
PageLayout::setTitle(_('Kalender veröffentlichen'));
}
/**
* 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)
{
$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']);
}
}
......@@ -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;
}
}
......@@ -74,98 +78,124 @@ class Calendar_ContentboxController extends StudipController
if ($this->admin) {
$this->isProfile = $this->single && $this->userRange;
}
// Sort dates
usort($this->termine, function ($a, $b) {
[$a_begin, $a_end] = $this->parseBeginAndEndFromDate($a);
[$b_begin, $b_end] = $this->parseBeginAndEndFromDate($b);
return $a_begin - $b_begin
?: $a_end - $b_end;
});
}
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'));
}
// Display only date and time if in course range, include course title
// otherwise
$date_format = $this->course_range ? 'include-room' : 'verbose';
// Store for view
$description = '';
if ($courseDate instanceof CourseExDate) {
$description = $courseDate->content;
} elseif ($courseDate->cycle instanceof SeminarCycleDate) {
$description = $courseDate->cycle->description;
}
$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
];
}
$this->termine = Course::find($id)->getDatesWithExdates($this->start, $this->start + $this->timespan)
->map(function ($course_date) use ($date_format) {
$this->titles[$course_date->id] = $course_date->getFullName($date_format);
return $course_date;
});
}
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) {
$course_dates = CalendarCourseDate::getEvents($begin, $end, $id);
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;
}
$cancelled_course_dates = CalendarCourseExDate::getEvents($begin, $end, $id);
foreach ($cancelled_course_dates as $cancelled_course_date) {
$this->titles[$cancelled_course_date->id] = sprintf(
'%1$s: %2$s',
$cancelled_course_date->course->name,
$cancelled_course_date->getFullName()
);
$this->termine[] = $cancelled_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;
}
}
private function parseBeginAndEndFromDate($date): array
{
if ($date instanceof CalendarDateAssignment) {
return [
$date->calendar_date->begin,
$date->calendar_date->end,
];
}
if ($date instanceof CourseDate || $date instanceof CourseExDate) {
return [
$date->date,
$date->end_time,
];
}
throw new Exception('Invalid date type passed: ' . get_class($date));
}
}
<?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'];
}
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) {
$range_id = Request::option('user_id', $GLOBALS['user']->id);
$range = 'user';
}
$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->user_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->user_calendar_assignments) > 1;
if ($this->user_calendar_assignments) {
$writable_assignment_c = 0;
$more_than_one_assignment = count($this->user_calendar_assignments) > 1;
//Find the calendar assignment of the user and set the participation message
//according to the participation status.
foreach ($this->user_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->user_calendar_assignments[$index]);
}
} else {
if ($assignment->isWritable($GLOBALS['user']->id)) {
$writable_assignment_c++;
}
}
}
$this->all_assignments_writable = $writable_assignment_c === count($this->user_calendar_assignments);
//Order all calendar assignments by type and name:
uasort($this->user_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;
}
}
});
} else {
//Check for other calendar assignments (course calendar):
$writable_assignment_c = 0;
$other_assignment_c = 0;
foreach ($this->date->calendars as $assignment) {
if (Course::exists($assignment->range_id)) {
//It is a course assignment:
$other_assignment_c++;
if ($assignment->isWritable($GLOBALS['user']->id)) {
$writable_assignment_c++;
}
}
}
$this->all_assignments_writable = $writable_assignment_c == $other_assignment_c;
}
}
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;
} elseif (Request::submitted('begin_str') && Request::submitted('end_str')) {
//Assume the textual format d.m.Y H:i:
$begin = Request::getDateTime('begin_str', 'd.m.Y H:i');
$end = Request::getDateTime('end_str', 'd.m.Y H:i');
$this->date->begin = $begin->getTimestamp();
$this->date->end = $end->getTimestamp();
} else {
$time = new DateTime();
if (Request::submitted('timestamp')) {
$time->setTimestamp(Request::int('timestamp'));
} elseif (Request::submitted('defaultDate')) {
$date_parts = explode('-', Request::get('defaultDate'));
if (count($date_parts) === 3) {
$time->setDate($date_parts[0], $date_parts[1], $date_parts[2]);
}
}
$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 owner by default:
$this->calendar_assignment_items[] = [
'value' => $owner->id,
'name' => $owner->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::bool('all_day')) {
$this->all_day_event = true;
//Check for a fullcalendar all-day event ending. In that case, remove one second to have an all-day event
//that abides to the Stud.IP rules.
$end = new DateTime();
$end->setTimestamp($this->date->end);
if ($end->format('His') === '000000') {
$end = $end->sub(new DateInterval('PT1S'));
}
$this->date->end = $end->getTimestamp();
} 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_str', 'd.m.Y H:i');
$end = Request::getDateTime('end_str', 'd.m.Y H:i');
if (Request::get('all_day') === '1') {
$this->all_day_event = true;
$begin->setTime(0,0,0);
$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', CalendarDate::REPETITION_SINGLE);
if (
!in_array($this->date->repetition_type, [
CalendarDate::REPETITION_SINGLE,
CalendarDate::REPETITION_DAILY,
CalendarDate::REPETITION_WEEKLY,
CalendarDate::REPETITION_MONTHLY,
CalendarDate::REPETITION_YEARLY,
'WORKDAYS',
])
) {
$this->form_errors[_('Wiederholung')] = _('Bitte wählen Sie ein gültiges Wiederholungsintervall aus.');
} elseif ($this->date->repetition_type !== CalendarDate::REPETITION_SINGLE) {
$this->date->interval = '';
if ($this->date->repetition_type !== 'WORKDAYS') {
$this->date->interval = Request::int('repetition_interval');
}
if ($this->date->repetition_type === CalendarDate::REPETITION_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 = CalendarDate::REPETITION_WEEKLY;
$this->date->days = '12345';
$this->date->interval = 1;
} elseif ($this->date->repetition_type === CalendarDate::REPETITION_MONTHLY) {
$month_type = Request::get('repetition_month_type');
if ($month_type === 'dom') {
$this->date->offset = Request::int('repetition_dom');
} elseif ($month_type === 'dow') {
$this->date->days = Request::get('repetition_dow');
$this->date->offset = Request::int('repetition_dow_week');
}
} elseif ($this->date->repetition_type === CalendarDate::REPETITION_YEARLY) {
$month = Request::int('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::int('repetition_dom');
} elseif ($month_type === 'dow') {
$this->date->days = Request::get('repetition_dow');
$this->date->offset = Request::int('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::int('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];
} elseif (Context::isCourse()) {
//Set the course as allowed calendar:
$allowed_calendar_ids = [Context::getId()];
} 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();
}
}
//Remove the date from calendars that are not in the $valid_assigned_calendar_ids array:
CalendarDateAssignment::deleteBySQL(
'`range_id` NOT IN ( :assigned_calendar_ids ) AND `calendar_date_id` = :calendar_date_id',
[
'assigned_calendar_ids' => $valid_assigned_calendar_ids,
'calendar_date_id' => $this->date->id
]
);
//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();
}
//In case the moved event is a repetition event, we must know the original date from where
//it was moved from to correctly move the whole date series:
$original_date = Request::getDateTime('original_date');
$real_begin = new DateTime();
$real_begin->setTimestamp($this->date->begin);
if (
$original_date
&& $original_date->format('Ymd') !== $real_begin->format('Ymd')
) {
//The original date is set. If it differs from the beginning, a repetition date
//of a date series has been moved. In this case, the difference of the beginning
//and the original date has to be calculated and the begin and end fields have
//to be updated.
$original_date->setTime(
$real_begin->format('H'),
$real_begin->format('i'),
$real_begin->format('s')
);
$diff = $real_begin->diff($original_date);
$this->begin = $this->begin->sub($diff);
$this->end = $this->end->sub($diff);
}
if ($this->date->repetition_type !== CalendarDate::REPETITION_SINGLE) {
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_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();
}
if (!$this->date->isWritable($GLOBALS['user']->id)) {
throw new AccessDeniedException(_('Sie sind nicht berechtigt, diesen Termin zu löschen.'));
}
$this->date_has_repetitions = $this->date->repetition_type !== CalendarDate::REPETITION_SINGLE;
$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->date_is_in_multiple_calendars = count($this->date->calendars) > 1;
$this->repetition_handling = Request::get('repetition_handling', 'create_exception');
$this->multiple_calendar_handling = Request::get('multiple_calendar_handling', 'delete_from_mine');
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();
return;
} 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' && $this->multiple_calendar_handling === 'delete_all') {
$delete_whole_date = true;
}
} else {
$delete_whole_date = !$this->date_is_in_multiple_calendars
|| $this->multiple_calendar_handling === 'delete_all';
}
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!'));
}
}
} elseif ($this->multiple_calendar_handling === 'delete_from_mine') {
$result = CalendarDateAssignment::deleteBySQL(
'`calendar_date_id` = :calendar_date_id AND `range_id` = :current_user_id',
[
'calendar_date_id' => $this->date->id,
'current_user_id' => User::findCurrent()->id
]
);
if ($result) {
PageLayout::postSuccess(_('Der Termin wurde aus Ihrem Kalender gelöscht.'));
$this->response->add_header('X-Dialog-Close', '1');
$this->render_nothing();
} else {
PageLayout::postError(_('Der Termin konnte nicht aus Ihrem Kalender 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();
}
}