aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Hackl <hackl@data-quest.de>2022-12-07 07:17:21 +0000
committerThomas Hackl <hackl@data-quest.de>2022-12-07 07:17:21 +0000
commit3d8cb20aef2c55cadd38dccc3f51128d7e357ecf (patch)
treea144f9afa0c2b2bcb2622f61d89455fd84a85614
parentd22c048e14a346feac17aa8983c0b89bf9010490 (diff)
Resolve "Erweiterung Courseware: Zertifikate, Erinnerungen und Rücksetzen des Fortschritts"
Closes #1660 Merge request studip/studip!1172
-rw-r--r--db/migrations/5.3.11_courseware_cron_events.php49
-rw-r--r--lib/classes/CoursewarePDFCertificate.php40
-rw-r--r--lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php33
-rw-r--r--lib/classes/JsonApi/Schemas/Courseware/Instance.php3
-rw-r--r--lib/cronjobs/courseware.php341
-rw-r--r--lib/models/Courseware/Certificate.php39
-rw-r--r--lib/models/Courseware/Instance.php155
-rwxr-xr-xpublic/assets/images/pdf/pdf_default_background.jpgbin0 -> 135997 bytes
-rw-r--r--resources/vue/components/courseware/CoursewareToolsAdmin.vue222
-rw-r--r--resources/vue/store/courseware/courseware.module.js7
-rw-r--r--templates/courseware/mails/certificate.php22
11 files changed, 902 insertions, 9 deletions
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 0000000..48dbfcf
--- /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 0000000..e3ef02f
--- /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 965e3b6..2b70cbf 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 055c387..63a4d95 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 0000000..16e0ad9
--- /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 0000000..d0c9fd9
--- /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 c823a4e..0b388fe 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 0000000..e91edda
--- /dev/null
+++ b/public/assets/images/pdf/pdf_default_background.jpg
Binary files differ
diff --git a/resources/vue/components/courseware/CoursewareToolsAdmin.vue b/resources/vue/components/courseware/CoursewareToolsAdmin.vue
index 337a39e..974e6fb 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 1380ea0..e6e14ac 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 0000000..ed1e099
--- /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>';