diff --git a/db/migrations/5.3.11_courseware_cron_events.php b/db/migrations/5.3.11_courseware_cron_events.php new file mode 100644 index 0000000000000000000000000000000000000000..48dbfcf130a18627dd70f2db755144ea6bd27efb --- /dev/null +++ b/db/migrations/5.3.11_courseware_cron_events.php @@ -0,0 +1,49 @@ +<?php + +require_once('lib/cronjobs/courseware.php'); + +class CoursewareCronEvents extends Migration +{ + public function description() + { + return 'Prepares database tables for storing timestamps of courseware cron events, ' . + 'like certificate sending and reminders.'; + } + + public function up() + { + // Create a table for certificates + DBManager::get()->exec("CREATE TABLE IF NOT EXISTS `cw_certificates` ( + `id` CHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, + `user_id` CHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, + `course_id` CHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, + `mkdate` INT(11) NOT NULL, + PRIMARY KEY (`id`), + INDEX index_user_id (`user_id`), + INDEX index_course_id (`course_id`), + INDEX index_user_ourse (`user_id`, `course_id`) + )"); + + CoursewareCronjob::register()->schedulePeriodic(41, 1)->activate(); + } + + public function down() + { + CoursewareCronjob::unregister(); + + DBManager::get()->exec("DROP TABLE IF EXISTS `cw_certificates`"); + + $fields = [ + 'COURSEWARE_CERTIFICATE_SETTINGS', + 'COURSEWARE_REMINDER_SETTINGS', + 'COURSEWARE_RESET_PROGRESS_SETTINGS', + 'COURSEWARE_LAST_REMINDER', + 'COURSEWARE_LAST_PROGRESS_RESET' + ]; + DBManager::get()->execute("DELETE FROM `config` WHERE `field` IN (:fields)", + ['fields' => $fields]); + DBManager::get()->execute("DELETE FROM `config_values` WHERE `field` IN (:fields)", + ['fields' => $fields]); + } + +} diff --git a/lib/classes/CoursewarePDFCertificate.php b/lib/classes/CoursewarePDFCertificate.php new file mode 100644 index 0000000000000000000000000000000000000000..e3ef02f70bb92de223faf95f18010466185447ef --- /dev/null +++ b/lib/classes/CoursewarePDFCertificate.php @@ -0,0 +1,40 @@ +<?php + +class CoursewarePDFCertificate extends TCPDF +{ + public function __construct($background = false, $orientation = 'P', $unit = 'mm', $format = 'A4', $unicode = true, $encoding = 'UTF-8') + { + $this->config = Config::get(); + if ($this->config->getValue('LOAD_EXTERNAL_MEDIA') === 'proxy') { + $this->media_proxy = new MediaProxy(); + } + parent::__construct($orientation, $unit, $format, $unicode, $encoding, false); + if ($background) { + $fileRef = FileRef::find($background); + $this->background = $fileRef->file->getPath(); + } else { + $this->background = $GLOBALS['STUDIP_BASE_PATH'] . '/public/assets/images/pdf/pdf_default_background.jpg'; + } + + $this->setDefaults(); + } + + public function Header() + { + $bMargin = $this->getBreakMargin(); + $auto_page_break = $this->AutoPageBreak; + $this->SetAutoPageBreak(false, 0); + $this->Image($this->background, 0, 0, 210, 297, '', '', '', false, 300, '', false, false, 0); + $this->setFont('helvetica', 'B', 50); + $this->Cell(0, 160, 'Z E R T I F I K A T', 0, false, 'C', 0, '', 0, false, 'T', 'M'); + $this->SetAutoPageBreak($auto_page_break, $bMargin); + $this->setPageMark(); + } + + private function setDefaults() + { + $this->SetTopMargin(110); + $this->SetLeftMargin(20); + $this->SetRightMargin(20); + } +} diff --git a/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php b/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php index 965e3b64980b6cc9f44d55c369880b0e65a3b2a7..2b70cbf90b605d4f98bdece4714142aa47193e26 100644 --- a/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php +++ b/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php @@ -78,6 +78,30 @@ class CoursewareInstancesUpdate extends JsonApiController return 'Attribute `editing-permission-level` contains an invalid value.'; } } + + if (self::arrayHas($json, 'data.attributes.certificate-settings')) { + $certificateSettings = self::arrayGet($json, 'data.attributes.certificate-settings'); + + if (!$resource->isValidCertificateSettings($certificateSettings)) { + return 'Attribute `certificate-settings` contains an invalid value.'; + } + } + + if (self::arrayHas($json, 'data.attributes.reminder-settings')) { + $reminderSettings = self::arrayGet($json, 'data.attributes.reminder-settings'); + + if (!$resource->isValidReminderSettings($reminderSettings)) { + return 'Attribute `reminder-settings` contains an invalid value.'; + } + } + + if (self::arrayHas($json, 'data.attributes.reset-progress-settings')) { + $resetProgressSettings = self::arrayGet($json, 'data.attributes.reset-progress-settings'); + + if (!$resource->isValidResetProgressSettings($resetProgressSettings)) { + return 'Attribute `reset-progress-settings` contains an invalid value.'; + } + } } private function updateInstance(\User $user, Instance $instance, array $json): Instance @@ -95,6 +119,15 @@ class CoursewareInstancesUpdate extends JsonApiController $editingPermissionLevel = $get('data.attributes.editing-permission-level'); $instance->setEditingPermissionLevel($editingPermissionLevel); + $certificateSettings = $get('data.attributes.certificate-settings'); + $instance->setCertificateSettings($certificateSettings); + + $reminderSettings = $get('data.attributes.reminder-settings'); + $instance->setReminderSettings($reminderSettings); + + $resetProgressSettings = $get('data.attributes.reset-progress-settings'); + $instance->setResetProgressSettings($resetProgressSettings); + return $instance; } } diff --git a/lib/classes/JsonApi/Schemas/Courseware/Instance.php b/lib/classes/JsonApi/Schemas/Courseware/Instance.php index 055c387defd8ca4e76ea3ad55cbecf062cb8a056..63a4d950b831a2d6818a855b221aa29add7c889e 100644 --- a/lib/classes/JsonApi/Schemas/Courseware/Instance.php +++ b/lib/classes/JsonApi/Schemas/Courseware/Instance.php @@ -36,6 +36,9 @@ class Instance extends SchemaProvider 'favorite-block-types' => $resource->getFavoriteBlockTypes($user), 'sequential-progression' => (bool) $resource->getSequentialProgression(), 'editing-permission-level' => $resource->getEditingPermissionLevel(), + 'certificate-settings' => $resource->getCertificateSettings(), + 'reminder-settings' => $resource->getReminderSettings(), + 'reset-progress-settings' => $resource->getResetProgressSettings() ]; } diff --git a/lib/cronjobs/courseware.php b/lib/cronjobs/courseware.php new file mode 100644 index 0000000000000000000000000000000000000000..16e0ad96475180150386cb3696cb5123135a6e69 --- /dev/null +++ b/lib/cronjobs/courseware.php @@ -0,0 +1,341 @@ +<?php +/** + * courseware.php + * + * @author Thomas Hackl <hackl@data-quest.de> + * @access public + * @since 5.3 + */ + +class CoursewareCronjob extends CronJob +{ + public static function getName() + { + return _('Courseware-Erinnerungen und -zertifikate verschicken sowie Fortschritt zurücksetzen'); + } + + public static function getDescription() + { + return _('Versendet Erinnerungen, Zertifikate bei Erreichen eines bestimmten Fortschritts und setzt ' . + 'Fortschritt bei derartig konfigurierten Coursewares zurück.'); + } + + public static function getParameters() + { + return [ + 'verbose' => [ + 'type' => 'boolean', + 'default' => false, + 'status' => 'optional', + 'description' => _('Sollen Ausgaben erzeugt werden (sind später im Log des Cronjobs sichtbar)'), + ] + ]; + } + + public function setUp() + { + } + + public function execute($last_result, $parameters = []) + { + $verbose = $parameters['verbose']; + + /* + * Fetch all courses that have some relevant settings. + */ + $todo = DBManager::get()->fetchAll( + "SELECT c.`range_id`, c. `field`, c.`value` + FROM `config_values` c + JOIN `seminare` s ON (s.`Seminar_id` = c.`range_id`) + WHERE c.`field` IN (:fields)", + ['fields' => [ + // Send certificate when this progress is reached + 'COURSEWARE_CERTIFICATE_SETTINGS', + // Remind all users about courseware + 'COURSEWARE_REMINDER_SETTINGS', + // Reset user progress to 0 + 'COURSEWARE_RESET_PROGRESS_SETTINGS' + ] + ] + ); + + if (count($todo) > 0) { + + if ($verbose) { + echo sprintf("Found %u courses to process.\n", count($todo)); + } + + $timezone = Config::get()->DEFAULT_TIMEZONE; + + // Process all found entries... + foreach ($todo as $one) { + + // Fetch all courseware blocks belonging to the current course. + $blocks = DBManager::get()->fetchFirst( + "SELECT DISTINCT b.`id` + FROM `cw_blocks` b + JOIN `cw_containers` c ON (c.`id` = b.`container_id`) + JOIN `cw_structural_elements` e ON (e.`id` = c.`structural_element_id`) + WHERE e.`range_id` = :course", + ['course' => $one['range_id']] + ); + + // extract details from JSON + $settings = json_decode($one['value'], true); + + // differentiate by setting type + switch ($one['field']) { + // Send certificates to those who have progressed far enough and have not yet gotten one. + case 'COURSEWARE_CERTIFICATE_SETTINGS': + + if ($verbose) { + echo sprintf("Generating certificates for course %s.\n", + $one['range_id']); + } + + // Fetch accumulated progress values for all users in this course. + $progresses = DBManager::get()->fetchAll( + "SELECT DISTINCT p.`user_id`, SUM(p.`grade`) AS progress + FROM `cw_user_progresses` p + WHERE `block_id` IN (:blocks) + AND NOT EXISTS ( + SELECT `id` FROM `cw_certificates` WHERE `user_id` = p.`user_id` AND `course_id` = :course + ) + GROUP BY `user_id`", + ['blocks' => $blocks, 'course' => $one['range_id']] + ); + + // Calculate percentual progress and send certificates if necessary. + foreach ($progresses as $progress) { + $percent = ($progress['progress'] / count($blocks)) * 100; + if ($percent >= $settings['threshold']) { + if ($verbose) { + echo sprintf("User %s will get a certificate for course %s.\n", + $progress['user_id'], $one['range_id']); + } + + $this->sendCertificate($one['range_id'], $progress['user_id'], + $percent, $settings); + + /* + * Insert a new entry into database for tracking who already got a certificate. + * This can be useful if certificates get a validity time or such. + */ + $entry = new Courseware\Certificate(); + $entry->user_id = $progress['user_id']; + $entry->course_id = $one['range_id']; + $entry->store(); + } + } + + break; + + // Send reminders to all course participants. + case 'COURSEWARE_REMINDER_SETTINGS': + + // Check when the last reminder was sent... + $now = new DateTime('', new DateTimeZone($timezone)); + + // What would be the minimum date for the last reminder? + $minReminder = clone $now; + + // The last reminder has been sent at? + $lastReminder = new DateTime('', new DateTimeZone($timezone)); + $lastReminder->setTimestamp( + UserConfig::get($one['range_id'])->COURSEWARE_LAST_REMINDER ?: 0 + ); + + // Check if the settings specify a start and/or end date for reminders + $start = new DateTime($settings['startDate'] ?: '1970-01-01', + new DateTimeZone($timezone)); + $end = new DateTime($settings['endDate'] ?: '2199-12-31', + new DateTimeZone($timezone)); + + $interval = new DateInterval('P1D'); + switch ($settings['interval']) { + case 7: + $interval = new DateInterval('P7D'); + break; + case 14: + $interval = new DateInterval('P14D'); + break; + case 30: + $interval = new DateInterval('P1M'); + break; + case 90: + $interval = new DateInterval('P3M'); + break; + case 180: + $interval = new DateInterval('P6M'); + break; + case 365: + $interval = new DateInterval('P1Y'); + break; + } + $minReminder->sub($interval); + + // ... and send a new one if necessary. + if ($lastReminder <= $minReminder && $now >= $start && $now <= $end) { + if ($verbose) { + echo sprintf("Sending reminders for course %s.\n", + $one['range_id']); + } + + if ($this->sendReminders($one['range_id'], $settings)) { + UserConfig::get($one['range_id'])->store('COURSEWARE_LAST_REMINDER', + $now->getTimestamp() + ); + } + } + + break; + + // Reset courseware progress to 0 for all course participants. + case 'COURSEWARE_RESET_PROGRESS_SETTINGS': + + // Check when the last reset was performed... + $now = new DateTime('', new DateTimeZone($timezone)); + $checkLast = clone $now; + $lastReset = new DateTime('', new DateTimeZone($timezone)); + $lastReset->setTimestamp( + UserConfig::get($one['range_id'])->COURSEWARE_LAST_PROGRESS_RESET ?: 0 + ); + + $interval = new DateInterval('P1D'); + switch ($one['value']) { + case 14: + $interval = new DateInterval('P14D'); + break; + case 30: + $interval = new DateInterval('P1M'); + break; + case 90: + $interval = new DateInterval('P3M'); + break; + case 180: + $interval = new DateInterval('P6M'); + break; + case 365: + $interval = new DateInterval('P1Y'); + break; + } + + // ... and reset again if necessary. + if ($lastReset <= $checkLast->sub($interval)) { + if ($verbose) { + echo sprintf("Resetting all progress for courseware in course %s.\n", + $one['range_id']); + } + + // Remove all progress in the given blocks. + $this->resetProgress($one['range_id'], $blocks, $settings); + + UserConfig::get($one['range_id'])->store('COURSEWARE_LAST_PROGRESS_RESET', + $now->getTimestamp() + ); + } + } + } + + } else if ($verbose) { + echo "Nothing to do.\n"; + } + } + + private function sendCertificate($course_id, $user_id, $progress, $settings) + { + $user = User::find($user_id); + $course = Course::find($course_id); + + $template = $GLOBALS['template_factory']->open('courseware/mails/certificate'); + $html = $template->render( + compact('user', 'course') + ); + + // Generate the PDF. + $pdf = new CoursewarePDFCertificate($settings['image']); + $pdf->AddPage(); + $pdf->writeHTML($html, true, false, true, false, ''); + $pdf_file_name = $user->nachname . '_' . $course->name . '_' . _('Zertifikat') . '.pdf'; + $filename = $GLOBALS['TMP_PATH'] . '/' . $pdf_file_name; + $pdf->Output($filename, 'F'); + + // Send the mail with PDF attached. + $mail = new StudipMail(); + + $message = sprintf( + _('Anbei erhalten Sie Ihr Courseware-Zertifikat zur Veranstaltung %1$s, in der Sie einen Fortschritt ' . + 'von %2$u %% erreicht haben.'), $course->getFullname(), $progress); + $message .= "\n\n" . _('Über folgenden Link kommen Sie direkt zur Courseware') . ': ' . + URLHelper::getURL('seminar_main.php', ['auswahl' => $course->id, + 'redirect_to' => 'dispatch.php/course/courseware']); + + $mail->addRecipient($user->email, $user->getFullname()) + ->setSubject(_('Courseware: Zertifikat') . ' - ' . $course->getFullname()) + ->setBodyText($message) + ->addFileAttachment($filename, $pdf_file_name) + ->send(); + + @unlink($filename); + + // Add database entry for the certificate. + + } + + private function sendReminders($course_id, $settings) + { + $course = Course::find($course_id); + + $recipients = $course->getMembersWithStatus('autor', true); + + $mail = new StudipMail(); + + foreach ($recipients as $rec) { + $mail->addRecipient( + $rec->email, + $rec->getUserFullname(), + 'bcc' + ); + } + + $message = $settings['mailText'] . "\n\n" . _('Über folgenden Link kommen Sie direkt zur Courseware') . ': ' . + URLHelper::getURL('seminar_main.php', ['auswahl' => $course->id, + 'redirect_to' => 'dispatch.php/course/courseware']); + + $mail->setSubject(_('Courseware: Erinnerung') . ' - ' . $course->getFullname()) + ->setBodyText($message); + + return $mail->send(); + } + + private function resetProgress($course_id, $block_ids, $settings) + { + $course = Course::find($course_id); + + DBManager::get()->execute( + "DELETE FROM `cw_user_progresses` WHERE `block_id` IN (:blocks)", + ['blocks' => $block_ids] + ); + + $recipients = $course->getMembersWithStatus('autor', true); + + $mail = new StudipMail(); + + foreach ($recipients as $rec) { + $mail->addRecipient( + $rec->email, + $rec->getUserFullname(), + 'bcc' + ); + } + + $message = $settings['mailText'] . "\n\n" . _('Über folgenden Link kommen Sie direkt zur Courseware') . ': ' . + URLHelper::getURL('seminar_main.php', ['auswahl' => $course->id, + 'redirect_to' => 'dispatch.php/course/courseware']); + + $mail->setSubject(_('Courseware: Fortschritt zurückgesetzt') . ' - ' . $course->getFullname()) + ->setBodyText($message); + + return $mail->send(); + } +} diff --git a/lib/models/Courseware/Certificate.php b/lib/models/Courseware/Certificate.php new file mode 100644 index 0000000000000000000000000000000000000000..d0c9fd9a5e11bccc1857047ffe8eb915de04c4f7 --- /dev/null +++ b/lib/models/Courseware/Certificate.php @@ -0,0 +1,39 @@ +<?php + +namespace Courseware; + +use \User, \Course; + +/** + * Courseware's certificates. + * + * @author Thomas Hackl <hackl@data-quest.de> + * @license GPL2 or any later version + * + * @since Stud.IP 5.3 + * + * @property string $id database column + * @property string $user_id database column + * @property string $course_id database column + * @property int $mkdate database column + */ +class Certificate extends \SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'cw_certificates'; + + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + ]; + + $config['belongs_to']['course'] = [ + 'class_name' => Course::class, + 'foreign_key' => 'course_id', + ]; + + parent::configure($config); + } + +} diff --git a/lib/models/Courseware/Instance.php b/lib/models/Courseware/Instance.php index c823a4edbde736d52530cb77e140ea798e1cad92..0b388fe93ca2db2c340ded696361f95cf2d2fbb3 100644 --- a/lib/models/Courseware/Instance.php +++ b/lib/models/Courseware/Instance.php @@ -231,6 +231,161 @@ class Instance } } + + /** + * Returns the certificate creation settings. + * + * @return array + */ + public function getCertificateSettings(): array + { + $range = $this->getRange(); + /** @var array $certificateSettings */ + $certificateSettings = json_decode( + $range->getConfiguration()->getValue('COURSEWARE_CERTIFICATE_SETTINGS'), + true + )?: []; + $this->validateCertificateSettings($certificateSettings); + + return $certificateSettings; + } + + /** + * Sets the certificate settings for this courseware instance. + * + * @param array $certificateSettings an array of parameters + */ + public function setCertificateSettings(array $certificateSettings): void + { + $this->validateCertificateSettings($certificateSettings); + $range = $this->getRange(); + $range->getConfiguration()->store('COURSEWARE_CERTIFICATE_SETTINGS', + count($certificateSettings) > 0 ? json_encode($certificateSettings) : null); + } + + /** + * Validates certificate settings. + * + * @param array $certificateSettings settings for certificate creation + * + * @return bool true if all given values are valid, false otherwise + */ + public function isValidCertificateSettings(array $certificateSettings): bool + { + return (int) $certificateSettings['threshold'] >= 0 && (int) $certificateSettings['threshold'] <= 100; + } + + private function validateCertificateSettings(array $certificateSettings): void + { + if (!$this->isValidCertificateSettings($certificateSettings)) { + throw new \InvalidArgumentException('Invalid certificate settings given.'); + } + } + + /** + * Returns the reminder message sending settings. + * + * @return array + */ + public function getReminderSettings(): array + { + $range = $this->getRange(); + /** @var int $reminderInterval */ + $reminderSettings = json_decode( + $range->getConfiguration()->getValue('COURSEWARE_REMINDER_SETTINGS'), + true + )?: []; + $this->validateReminderSettings($reminderSettings); + + return $reminderSettings; + } + + /** + * Sets the reminder message settings this courseware instance. + * + * @param array $reminderSettings an array of parameters + */ + public function setReminderSettings(array $reminderSettings): void + { + $this->validateReminderSettings($reminderSettings); + $range = $this->getRange(); + $range->getConfiguration()->store('COURSEWARE_REMINDER_SETTINGS', + count($reminderSettings) > 0 ? json_encode($reminderSettings) : null); + } + + /** + * Validates reminder message settings. + * + * @param array $reminderSettings settings for reminder mail sending + * + * @return bool true if all given values are valid, false otherwise + */ + public function isValidReminderSettings(array $reminderSettings): bool + { + $valid = in_array($reminderSettings['interval'], [0, 7, 14, 30, 90, 180, 365]); + + return $valid; + } + + private function validateReminderSettings(array $reminderSettings): void + { + if (!$this->isValidReminderSettings($reminderSettings)) { + throw new \InvalidArgumentException('Invalid reminder settings given.'); + } + } + + /** + * Returns the progress resetting settings. + * + * @return array + */ + public function getResetProgressSettings(): array + { + $range = $this->getRange(); + /** @var int $reminderInterval */ + $resetProgressSettings = json_decode( + $range->getConfiguration()->getValue('COURSEWARE_RESET_PROGRESS_SETTINGS'), + true + )?: []; + $this->validateResetProgressSettings($resetProgressSettings); + + return $resetProgressSettings; + } + + /** + * Sets the progress resetting settings this courseware instance. + * + * @param array $reminderSettings an array of parameters + */ + public function setResetProgressSettings(array $resetProgressSettings): void + { + $this->validateResetProgressSettings($resetProgressSettings); + $range = $this->getRange(); + $range->getConfiguration()->store('COURSEWARE_RESET_PROGRESS_SETTINGS', + count($resetProgressSettings) > 0 ? json_encode($resetProgressSettings) : null); + } + + /** + * Validates progress resetting settings. + * + * @param array $resetProgressSettings settings for progress resetting + * + * @return bool true if all given values are valid, false otherwise + */ + public function isValidResetProgressSettings(array $resetProgressSettings): bool + { + $valid = in_array($resetProgressSettings['interval'], [0, 14, 30, 90, 180, 365]); + + return $valid; + } + + private function validateResetProgressSettings(array $resetProgressSettings): void + { + if (!$this->isValidResetProgressSettings($resetProgressSettings)) { + throw new \InvalidArgumentException('Invalid progress resetting settings given.'); + } + } + /** * Returns all bookmarks of a user associated to this courseware instance. * diff --git a/public/assets/images/pdf/pdf_default_background.jpg b/public/assets/images/pdf/pdf_default_background.jpg new file mode 100755 index 0000000000000000000000000000000000000000..e91edda46beb097e11b4bc1f64eaf0e71029dd53 Binary files /dev/null and b/public/assets/images/pdf/pdf_default_background.jpg differ diff --git a/resources/vue/components/courseware/CoursewareToolsAdmin.vue b/resources/vue/components/courseware/CoursewareToolsAdmin.vue index 337a39e47fe03682e33ae990297bb8636d3854e7..974e6fbbf8da495b042ddc4fd68f1be72e535338 100644 --- a/resources/vue/components/courseware/CoursewareToolsAdmin.vue +++ b/resources/vue/components/courseware/CoursewareToolsAdmin.vue @@ -2,37 +2,195 @@ <div class="cw-tools cw-tools-admin"> <form class="default" @submit.prevent=""> <fieldset> - <legend><translate>Allgemeine Einstellungen</translate></legend> + <legend>{{ $gettext('Allgemeine Einstellungen') }}</legend> <label> - <span><translate>Art der Inhaltsabfolge</translate></span> + <span>{{ $gettext('Art der Inhaltsabfolge') }}</span> <select class="size-s" v-model="currentProgression"> - <option value="0"><translate>Frei</translate></option> - <option value="1"><translate>Sequentiell</translate></option> + <option value="0">{{ $gettext('Frei') }}</option> + <option value="1">{{ $gettext('Sequentiell') }}</option> </select> </label> <label> - <span><translate>Editierberechtigung für Tutor/-innen</translate></span> + <span>{{ $gettext('Editierberechtigung für Tutor/-innen') }}</span> <select class="size-s" v-model="currentPermissionLevel"> - <option value="dozent"><translate>Nein</translate></option> - <option value="tutor"><translate>Ja</translate></option> + <option value="dozent">{{ $gettext('Nein') }}</option> + <option value="tutor">{{ $gettext('Ja') }}</option> </select> </label> </fieldset> + <fieldset> + <legend> + {{ $gettext('Zertifikate') }} + </legend> + <label> + <input type="checkbox" name="makecert" v-model="makeCert"> + <span> + {{ $gettext('Zertifikat bei Erreichen einer Fortschrittsgrenze versenden') }} + </span> + <studip-tooltip-icon :text="$gettext('Erreicht eine Person in diesem Lernmaterial den ' + + 'hier eingestellten Fortschritt, so erhält Sie ein PDF-Zertifikat per E-Mail.')"/> + </label> + <label v-if="makeCert"> + <span> + {{ $gettext('Erforderlicher Fortschritt (in Prozent), um ein Zertifikat zu erhalten') }} + </span> + <input type="number" min="1" max="100" name="threshold" v-model="certThreshold"> + </label> + <label v-if="makeCert"> + <span> + {{ $gettext('Hintergrundbild des Zertifikats wählen') }} + </span> + <courseware-file-chooser :isImage="true" v-model="certImage" + @selectFile="updateCertImage"></courseware-file-chooser> + </label> + </fieldset> + <fieldset> + <legend> + {{ $gettext('Erinnerungen') }} + </legend> + <label> + <input type="checkbox" name="sendreminders" v-model="sendReminders"> + <span> + {{ $gettext('Erinnerungsnachrichten an alle Teilnehmenden schicken') }} + </span> + <studip-tooltip-icon :text="$gettext('Hier können periodisch Nachrichten an alle ' + + 'Teilnehmenden verschickt werden, um z.B. an die Bearbeitung dieses Lernmaterials zu erinnern.')"/> + </label> + + <label v-if="sendReminders"> + <span> + {{ $gettext('Zeitraum zwischen Erinnerungen') }} + </span> + <select name="reminder_interval" v-model="reminderInterval"> + <option value="7"> + {{ $gettext('wöchentlich') }} + </option> + <option value="14"> + {{ $gettext('14-tägig') }} + </option> + <option value="30"> + {{ $gettext('monatlich') }} + </option> + <option value="90"> + {{ $gettext('vierteljährlich') }} + </option> + <option value="180"> + {{ $gettext('halbjährlich') }} + </option> + <option value="365"> + {{ $gettext('jährlich') }} + </option> + </select> + </label> + <label v-if="sendReminders" class="col-3"> + <span> + {{ $gettext('Erstmalige Erinnerung am') }} + <input type="date" name="reminder_start_date" + v-model="reminderStartDate"> + </span> + </label> + <label v-if="sendReminders" class="col-3"> + <span> + {{ $gettext('Letztmalige Erinnerung am') }} + <input type="date" name="reminder_end_date" + v-model="reminderEndDate"> + </span> + </label> + <label v-if="sendReminders"> + <span> + {{ $gettext('Text der Erinnerungsmail') }} + <textarea cols="70" rows="4" name="reminder_mail_text" data-editor="minimal" + v-model="reminderMailText"></textarea> + </span> + </label> + </fieldset> + <fieldset> + <legend> + {{ $gettext('Fortschritt') }} + </legend> + <label> + <input type="checkbox" name="resetprogress" v-model="resetProgress"> + <span> + {{ $gettext('Fortschritt periodisch auf 0 zurücksetzen') }} + </span> + <studip-tooltip-icon :text="$gettext('Hier kann eingestellt werden, den Fortschritt ' + + 'aller Teilnehmenden periodisch auf 0 zurückzusetzen.')"/> + </label> + <label v-if="resetProgress"> + <span> + {{ $gettext('Zeitraum zum Rücksetzen des Fortschritts') }} + </span> + <select name="reset_progress_interval" v-model="resetProgressInterval"> + <option value="14"> + {{ $gettext('14-tägig') }} + </option> + <option value="30"> + {{ $gettext('monatlich') }} + </option> + <option value="90"> + {{ $gettext('vierteljährlich') }} + </option> + <option value="180"> + {{ $gettext('halbjährlich') }} + </option> + <option value="365"> + {{ $gettext('jährlich') }} + </option> + </select> + </label> + <label v-if="resetProgress" class="col-3"> + <span> + {{ $gettext('Erstmaliges Zurücksetzen am') }} + <input type="date" dataformatas="" name="reset_progress_start_date" + v-model="resetProgressStartDate"> + </span> + </label> + <label v-if="resetProgress" class="col-3"> + <span> + {{ $gettext('Letztmaliges Zurücksetzen am') }} + <input type="date" name="reset_progress_end_date" + v-model="resetProgressEndDate"> + </span> + </label> + <label v-if="resetProgress"> + <span> + {{ $gettext('Text der Rücksetzungsmail') }} + <textarea cols="70" rows="4" name="reset_progress_mail_text" data-editor="minimal" + v-model="resetProgressMailText"></textarea> + </span> + </label> + </fieldset> </form> - <button class="button" @click="store"><translate>Übernehmen</translate></button> + <button class="button" @click="store">{{ $gettext('Übernehmen') }}</button> </div> </template> <script> import { mapActions, mapGetters } from 'vuex'; +import CoursewareFileChooser from "./CoursewareFileChooser.vue"; +import StudipTooltipIcon from '../StudipTooltipIcon.vue'; export default { name: 'cw-tools-admin', + components: { StudipTooltipIcon, CoursewareFileChooser }, data() { return { currentPermissionLevel: '', currentProgression: '', + makeCert: false, + certThreshold: 0, + certImage: '', + sendReminders: false, + reminderInterval: 7, + reminderStartDate: '', + reminderEndDate: '', + reminderMailText: '', + resetProgress: false, + resetProgressInterval: 180, + resetProgressStartDate: '', + resetProgressEndDate: '', + resetProgressMailText: '' }; }, computed: { @@ -46,8 +204,28 @@ export default { companionSuccess: 'companionSuccess', }), initData() { + console.log(this.courseware.attributes); this.currentPermissionLevel = this.courseware.attributes['editing-permission-level']; this.currentProgression = this.courseware.attributes['sequential-progression'] ? '1' : '0'; + this.certSettings = this.courseware.attributes['certificate-settings']; + this.makeCert = typeof(this.certSettings) === 'object' && + Object.keys(this.certSettings).length > 0; + this.certThreshold = this.certSettings.threshold; + this.certImage = this.certSettings.image; + this.reminderSettings = this.courseware.attributes['reminder-settings']; + this.sendReminders = typeof(this.reminderSettings) === 'object' && + Object.keys(this.reminderSettings).length > 0; + this.reminderInterval = this.reminderSettings.interval; + this.reminderStartDate = this.reminderSettings.startDate; + this.reminderEndDate = this.reminderSettings.endDate; + this.reminderMailText = this.reminderSettings.mailText; + this.resetProgressSettings = this.courseware.attributes['reset-progress-settings']; + this.resetProgress = typeof(this.resetProgressSettings) === 'object' && + Object.keys(this.resetProgressSettings).length > 0; + this.resetProgressInterval = this.resetProgressSettings.interval; + this.resetProgressStartDate = this.resetProgressSettings.startDate; + this.resetProgressEndDate = this.resetProgressSettings.endDate; + this.resetProgressMailText = this.resetProgressSettings.mailText; }, store() { this.companionSuccess({ @@ -56,8 +234,36 @@ export default { this.storeCoursewareSettings({ permission: this.currentPermissionLevel, progression: this.currentProgression, + certificateSettings: this.generateCertificateSettings(), + reminderSettings: this.generateReminderSettings(), + resetProgressSettings: this.generateResetProgressSettings() }); }, + generateCertificateSettings() { + return this.makeCert ? { + threshold: this.certThreshold, + image: this.certImage + } : {}; + }, + generateReminderSettings() { + return this.sendReminders ? { + interval: this.reminderInterval, + startDate: this.reminderStartDate, + endDate: this.reminderEndDate, + mailText: this.reminderMailText + } : {}; + }, + generateResetProgressSettings() { + return this.resetProgress ? { + interval: this.resetProgressInterval, + startDate: this.resetProgressStartDate, + endDate: this.resetProgressEndDate, + mailText: this.resetProgressMailText + } : {}; + }, + updateCertImage(file) { + this.certImage = file.id; + } }, mounted() { this.initData(); diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js index 1380ea008dbd508af62da3ee272d5e2d504a9dd6..e6e14acea08abe6e238744e64b1dc40539638675 100644 --- a/resources/vue/store/courseware/courseware.module.js +++ b/resources/vue/store/courseware/courseware.module.js @@ -536,10 +536,15 @@ export const actions = { return dispatch('loadContainer', containerId); }, - async storeCoursewareSettings({ dispatch, getters }, { permission, progression }) { + async storeCoursewareSettings({ dispatch, getters }, + { permission, progression, certificateSettings, reminderSettings, + resetProgressSettings }) { const courseware = getters.courseware; courseware.attributes['editing-permission-level'] = permission; courseware.attributes['sequential-progression'] = progression; + courseware.attributes['certificate-settings'] = certificateSettings; + courseware.attributes['reminder-settings'] = reminderSettings; + courseware.attributes['reset-progress-settings'] = resetProgressSettings; return dispatch('courseware-instances/update', courseware, { root: true }); }, diff --git a/templates/courseware/mails/certificate.php b/templates/courseware/mails/certificate.php new file mode 100644 index 0000000000000000000000000000000000000000..ed1e099de03a4665c0bdb6c4abbee4a8bf4d0c75 --- /dev/null +++ b/templates/courseware/mails/certificate.php @@ -0,0 +1,22 @@ +<?php +$p = '<p style="font-size: 20px; text-align: center;">'; +$span_bold = '<br /><br /><span style="font-size: 20px; text-align: center; font-weight: bold">'; +$span_close = '</span><br /><br />'; +switch($user->geschlecht) { + case 1: + $anrede = _('Herr'); + break; + case 2: + $anrede = _('Frau'); + break; + default: + $anrede= ''; +} +echo $p; +printf( + _("Hiermit wird bescheinigt, dass %s am %s erfolgreich am Seminar %s teilgenommen hat."), + $span_bold . $anrede . ' ' . $user->getFullname() . $span_close, + $span_bold . date('d.m.Y', time()) . $span_close, + $span_bold . $course->name . $span_close +); +echo '</p>';