id)) { throw new AccessDeniedException(); } } public function index_action($id = null) { Navigation::activateItem('/messaging/massmail/message'); PageLayout::setTitle(_('Nachricht an Zielgruppe schreiben')); $message = new \MassMail\MassMailMessage($id); $temp_id = $id ?: md5(uniqid(time())); $folder = $message->findFolder($temp_id); // SearchType needed for course selection $courseSearch = new StandardSearch('Seminar_id'); // SearchType needed for user $userSearch = new StandardSearch('user_id'); $form = \Studip\Forms\Form::fromSORM( $message, [ 'legend' => _('Grunddaten'), 'collapsed' => false, 'collapsable' => false, 'fields' => [ 'target' => [ 'type' => 'select', 'required' => true, 'label' => _('Zielgruppe'), 'value' => $message->target ?? 'all', 'options' => \MassMail\MassMailMessage::getTargets() ], 'student_filters' => [ 'type' => 'userFilter', 'label' => _('Auswahlfilter'), 'if' => 'target === "students"', 'context' => 'MassMail', 'target' => 'students', 'store' => function($value, $input) { if ($input->getContextObject()->target === 'students') { $filters = []; foreach ($value as $one) { $filter = new UserFilter($one['id'] ?? ''); $filter->fields = []; foreach ($one['attributes']['fields'] as $field) { $classname = $field['attributes']['type']; $f = new $classname(); if (!empty($fiele['id'])) { $f->setId($field['id']); } $f->setCompareOperator($field['attributes']['compare-operator']); $f->setValue($field['attributes']['value']); $filter->addField($f); } $filter->store(); $connection = new \MassMail\MassMailFilter(); $connection->filter_id = $filter->getId(); $filters[] = $connection; } $input->getContextObject()->filters = $filters; } } ], 'employee_filters' => [ 'type' => 'userFilter', 'label' => _('Auswahlfilter'), 'if' => 'target === "employees"', 'context' => 'MassMail', 'target' => 'employees', 'store' => function($value, $input) { if ($input->getContextObject()->target === 'employees') { $filters = []; foreach ($value as $one) { $filter = new UserFilter($one['id'] ?? ''); $filter->fields = []; foreach ($one['attributes']['fields'] as $field) { $classname = $field['attributes']['type']; $f = new $classname(); if (!empty($fiele['id'])) { $f->setId($field['id']); } $f->setCompareOperator($field['attributes']['compare-operator']); $f->setValue($field['attributes']['value']); $filter->addField($f); } $filter->store(); $connection = new \MassMail\MassMailFilter(); $connection->filter_id = $filter->getId(); $filters[] = $connection; } $input->getContextObject()->filters = $filters; } } ], 'semester' => [ 'type' => 'select', 'label' => _('Semester wählen'), 'value' => $message->config['semester'] ?? \Semester::findDefault()->id, 'if' => 'target === "lecturers"', 'options' => \MassMail\MassMailMessage::getSemesters(), 'store' => function($value, $input) { if ($input->getContextObject()->target === 'lecturers') { $input->getContextObject()->config = ['semester' => $value]; } } ], 'courses' => [ 'type' => 'quicksearchList', 'label' => _('Veranstaltungen wählen'), 'value' => json_encode($message->config?->getArrayCopy()['courses'] ?? []), 'if' => 'target === "courses"', 'searchtype' => $courseSearch, 'store' => function($value, $input) { if ($input->getContextObject()->target === 'courses') { $input->getContextObject()->config = []; $input->getContextObject()->config['courses'] = \Course::findAndMapMany( function ($course) { return ['id' => $course->id, 'name' => $course->getFullname()]; }, json_decode($value, true) ); } } ], 'course_perm' => [ 'type' => 'multiselect', 'label' => _('Berechtigungsebene wählen'), 'value' => $message->config['perm'] ?? ['autor'], 'if' => 'target === "courses"', 'options' => [ 'dozent' => get_title_for_status('dozent', 2, 1), 'tutor' => get_title_for_status('tutor', 2, 1), 'autor' => get_title_for_status('autor', 2, 1), 'user' => get_title_for_status('user', 2, 1), ], 'store' => function($value, $input) { if ($input->getContextObject()->target === 'courses') { $input->getContextObject()->config['perm'] = $value; } } ], 'manual_usernames' => [ 'type' => 'textarea', 'label' => _('Liste von Benutzernamen, durch Zeilenumbruch getrennt'), 'if' => 'target === "usernames"', 'value' => $message->config['usernames'] ?? '', 'store' => function($value, $input) { if ($input->getContextObject()->target === 'usernames') { $input->getContextObject()->config = []; $input->getContextObject()->config['usernames'] = $value; } } ], 'subject' => [ 'type' => 'text', 'required' => true, 'label' => _('Betreff'), 'value' => $message->subject ], 'message' => [ 'type' => 'serialWysiwyg', 'required' => true, 'label' => _('Nachricht'), 'value' => $message->message, 'markers' => json_encode( array_map( fn ($m) => $m->toArray(), \MassMail\MassMailMarker::findAll( \MassMail\MassMailPermission::has(User::findCurrent()->id, true) ) ) ) ] ] ], $this->url_for('massmail/overview') )->addSORM($message, [ 'legend' => _('Weitere Einstellungen'), 'collapsable' => true, 'collapsed' => true, 'fields' => [ 'author_id' => [ 'type' => 'hidden', 'value' => User::findCurrent()->id ], 'attachments' => [ 'type' => 'file', 'label' => _('Dateianhänge auswählen'), 'value' => $message->folder_id ?? $message->folder_id = $folder->id, 'upload_url' => $this->url_for('massmail/message/attachments', $folder->id), 'multiple' => true, 'if' => $GLOBALS['ENABLE_EMAIL_ATTACHMENTS'] ? 'true' : 'false', 'store' => function($value, $input) { $input->getContextObject()->folder_id = $value; } ], 'tokens' => [ 'type' => 'file', 'label' => _('CSV mit Teilnahmecodes auswählen'), 'value' => $message->folder_id ?? $message->folder_id = $folder->id, 'upload_url' => $this->url_for('massmail/message/tokens', $message->folder_id), 'accept' => '.csv,.txt', 'if' => \MassMail\MassMailPermission::has(User::findCurrent()->id, true) ? 'true' : 'false', 'store' => function($value, $input) { $input->getContextObject()->folder_id = $value; } ], 'send_at_date' => [ 'type' => 'datetimepicker', 'label' => _('Zu einem späteren Zeitpunkt senden'), 'value' => $message->send_at_date ?? time() ], 'send_as' => [ 'type' => 'select', 'label' => ('Nachricht senden als'), 'value' => $message->sender_id ?? User::findCurrent()->id, 'if' => \MassMail\MassMailPermission::has(User::findCurrent()->id, true) ? 'true' : 'false', 'options' => [ User::findCurrent()->id => _('Von meiner Kennung verschicken'), 'user_id' => _('Eine andere Person eintragen'), '____%system%____' => _('Anonym, mit "Stud.IP" als Absender') ], 'store' => function($value, $input) { if ($value === User::findCurrent()->id || $value === '____%system%____') { $input->getContextObject()->sender_id = $value; } } ], 'sender_id' => [ 'type' => 'quicksearch', 'label' => _('Absender:in wählen'), 'value' => $message->sender_id ?? '', 'if' => 'send_as === "user_id"', 'searchtype' => $userSearch, 'store' => function($value, $input) { $sender_id = $input->getContextObject()->sender_id; if ($sender_id !== User::findCurrent()->id && $sender_id !== '____%system%____') { $input->sender_id = $value; } } ], 'exclude_users' => [ 'type' => 'textarea', 'label' => _('Liste von Benutzernamen, die die Nachricht nicht erhalten sollen'), 'value' => $message->exclude_users ?? '' ], 'cc' => [ 'type' => 'textarea', 'label' => _('Liste von Benutzernamen, die die Nachricht als Kopie erhalten sollen'), 'value' => $message->cc ?? '' ], 'flags' => [ 'type' => 'radio', 'label' => _('Besondere Kennzeichnung'), 'value' => $message->is_template ? 'is_template' : ($message->protected ? 'protected' : ''), 'options' => [ '' => _('Keine besondere Kennzeichnung'), 'is_template' => _('Nicht verschicken, sondern als Vorlage speichern'), 'protected' => _('Auch nach dem Versand dauerhaft speichern') ], 'store' => function($value, $input) { switch ($value) { case 'is_template': $input->getContextObject()->is_template = 1; $input->getContextObject()->protected = 0; break; case 'protected': $input->getContextObject()->is_template = 0; $input->getContextObject()->protected = 1; break; default: $input->getContextObject()->is_template = 0; $input->getContextObject()->protected = 0; break; } } ] ] ])->addStoreCallback(function ($form) { $message = $form->getLastPart()->getContextObject(); // Adjust folder range_id to the actual message id. $folder = Folder::find($message->folder_id); $folder->range_id = $message->id; $folder->store(); // Create message tokens if necessary. if ($message->hasMarkers('token')) { foreach ($folder->getTypedFolder()->getFiles() as $ref) { if (isset($ref->file->metadata['is_token_file'])) { $file = fopen($ref->file->getPath(), 'r'); while (!feof($file)) { $token = fgets($file); $t = new \MassMail\MassMailToken(); $t->message_id = $message->id; $t->token = $token; $t->store(); } } } } })->autoStore(); if (Config::get()->MASSMAIL_EXPORT_RECIPIENTS_ENABLE) { $form->addButton( \Studip\Button::create(_('Zielgruppe exportieren'), 'export', ['onclick' => 'STUDIP.MassMail.exportRecipients(event)'] )); } $this->render_form($form); } public function delete_action(int $id) { $message = \MassMail\MassMailMessage::find($id); if ( !$message || ( $message->author_id !== User::findCurrent()->id && !\MassMail\MassMailPermission::has(User::findCurrent()->id, true) ) ) { throw new AccessDeniedException(); } if ($message->delete() !== false) { PageLayout::postSuccess(_('Die Nachricht wurde gelöscht.')); } else { PageLayout::postError(_('Die Nachricht konnte nicht gelöscht werden.')); } $this->relocate('massmail/overview'); } public function attachments_action(string $folder_id) { if (!$GLOBALS['ENABLE_EMAIL_ATTACHMENTS']) { throw new AccessDeniedException(); } $folder = Folder::find($folder_id)->getTypedFolder(); $uploaded = FileManager::handleFileUpload($_FILES['attachments'], $folder); if (!empty($uploaded['error'])) { $this->set_status(400); $this->render_text(implode('
' . $uploaded['error'])); } else { $this->render_nothing(); } } public function tokens_action(string $folder_id) { if (!\MassMail\MassMailPermission::has(User::findCurrent()->id, true)) { throw new AccessDeniedException(); } $data = [ 'name' => [$_FILES['tokens']['name']], 'tmp_name' => [$_FILES['tokens']['tmp_name']], 'type' => [$_FILES['tokens']['type']], 'error' => [$_FILES['tokens']['error']], 'size' => [$_FILES['tokens']['size']], ]; $folder = Folder::find($folder_id)->getTypedFolder(); $uploaded = FileManager::handleFileUpload($data, $folder); if (!empty($uploaded['error'])) { $this->set_status(400); $this->render_text(implode('
' . $uploaded['error'])); } else { // Set metadata for created file, indicating that this is a file with message tokens. foreach ($uploaded['files'] as $ref) { $ref->file->metadata = ['is_token_file' => true]; $ref->file->store(); } $this->render_nothing(); } } public function export_action() { $message = new MassMail\MassMailMessage(); $message->target = Request::get('target'); $message->author_id = User::findCurrent()->id; $data = [[_('Zielgruppe: alle')]]; $currentRow = 2; $createdFilters = []; switch($message->target) { case 'students': case 'employees': $data = [[_('Zielgruppe:') . ' ' . ($message->target === 'students' ? _('Studierende') : _('Beschäftigte'))]]; $value = json_decode( Request::get($message->target === 'students' ? 'student_filters' : 'employee_filters', '[]'), true ); $filters = []; foreach ($value as $one) { $filter = new UserFilter(); $filter->fields = []; foreach ($one['attributes']['fields'] as $field) { $classname = $field['attributes']['type']; $f = new $classname(); if (!empty($fiele['id'])) { $f->setId($field['id']); } $f->setCompareOperator($field['attributes']['compare-operator']); $f->setValue($field['attributes']['value']); $filter->addField($f); } $filter->store(); $connection = new \MassMail\MassMailFilter(); $connection->filter_id = $filter->getId(); $filters[] = $connection; $createdFilters[] = $filter; $data[] = [strip_tags($filter->toString())]; $currentRow++; } $message->filters = $filters; break; case 'lecturers': $data = [[_('Zielgruppe:') . ' ' . _('Aktive Lehrende') . ' (' . Semester::find(Request::get('semester'))->name . ')']]; $message->config = json_encode([ 'semester' => Request::get('semester') ]); break; case 'courses': $data = [[_('Zielgruppe:') . ' ' . _('Veranstaltungen')]]; $message->config = json_encode([ 'courses' => Request::getArray('courses'), 'perm' => Request::get('course_perm') ]); break; case 'usernames': $data = [[_('Zielgruppe:') . ' ' . _('Manuell gewählte Nutzernamen')]]; $message->config = json_encode(['usernames' => Request::getArray('manual_usernames')]); break; } $now = time(); $data[] = [sprintf(_('Stand der Daten vom %s'), date('d.m.Y, H:i', $now))]; $data[] = ['']; $data[] = [_('Nutzername'), _('Nachname'), _('Vorname')]; $data = array_merge( $data, User::findAndMapBySQL( fn ($user) => [$user->username, $user->nachname, $user->vorname], "`username` IN (:usernames) ORDER BY `Nachname`, `Vorname`, `username`", ['usernames' => $message->getRecipients() ?? ['']] ) ); $xls = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $xls->getProperties() ->setCreator(User::findCurrent()->getFullname()) ->setLastModifiedBy(User::findCurrent()->getFullname()) ->setTitle('Zielgruppenexport'); $sheet = $xls->getActiveSheet(); $style = $sheet->getStyle('A1:C1'); $style->getFill() ->setFillType(PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID) ->getStartColor() ->setRGB('dddddd'); $style->getFont() ->setSize(14) ->setBold(true); $style2 = $sheet->getStyle('A2:C' . $currentRow); $style2->getFill() ->setFillType(PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID) ->getStartColor() ->setRGB('dddddd'); $style2->getFont() ->setSize(12) ->setBold(true); $style3 = $sheet->getStyle('A' . ($currentRow + 2) . ':C' . ($currentRow + 2)); $style3->getFont() ->setBold(true); foreach (['A', 'B', 'C'] as $column) { $sheet->getColumnDimension($column) ->setAutoSize(true); } $sheet->fromArray($data); $tmpname = tempnam($GLOBALS['TMP_PATH'], ''); $writer = new PhpOffice\PhpSpreadsheet\Writer\Xlsx($xls); $writer->save($tmpname); array_map(fn ($filter) => $filter->delete(), $createdFilters); $this->render_text( FileManager::getDownloadURLForTemporaryFile( $tmpname, 'zielgruppe-' . date('Y-m-d-h-i', $now) . '.xlsx' ) ); } }