From 3d8cb20aef2c55cadd38dccc3f51128d7e357ecf Mon Sep 17 00:00:00 2001
From: Thomas Hackl <hackl@data-quest.de>
Date: Wed, 7 Dec 2022 07:17:21 +0000
Subject: [PATCH] =?UTF-8?q?Resolve=20"Erweiterung=20Courseware:=20Zertifik?=
 =?UTF-8?q?ate,=20Erinnerungen=20und=20R=C3=BCcksetzen=20des=20Fortschritt?=
 =?UTF-8?q?s"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1660

Merge request studip/studip!1172
---
 .../5.3.11_courseware_cron_events.php         |  49 +++
 lib/classes/CoursewarePDFCertificate.php      |  40 ++
 .../Courseware/CoursewareInstancesUpdate.php  |  33 ++
 .../JsonApi/Schemas/Courseware/Instance.php   |   3 +
 lib/cronjobs/courseware.php                   | 341 ++++++++++++++++++
 lib/models/Courseware/Certificate.php         |  39 ++
 lib/models/Courseware/Instance.php            | 155 ++++++++
 .../images/pdf/pdf_default_background.jpg     | Bin 0 -> 135997 bytes
 .../courseware/CoursewareToolsAdmin.vue       | 222 +++++++++++-
 .../vue/store/courseware/courseware.module.js |   7 +-
 templates/courseware/mails/certificate.php    |  22 ++
 11 files changed, 902 insertions(+), 9 deletions(-)
 create mode 100644 db/migrations/5.3.11_courseware_cron_events.php
 create mode 100644 lib/classes/CoursewarePDFCertificate.php
 create mode 100644 lib/cronjobs/courseware.php
 create mode 100644 lib/models/Courseware/Certificate.php
 create mode 100755 public/assets/images/pdf/pdf_default_background.jpg
 create mode 100644 templates/courseware/mails/certificate.php

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 00000000000..48dbfcf130a
--- /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 00000000000..e3ef02f70bb
--- /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 965e3b64980..2b70cbf90b6 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 055c387defd..63a4d950b83 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 00000000000..16e0ad96475
--- /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 00000000000..d0c9fd9a5e1
--- /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 c823a4edbde..0b388fe93ca 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
GIT binary patch
literal 135997
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

literal 0
HcmV?d00001

diff --git a/resources/vue/components/courseware/CoursewareToolsAdmin.vue b/resources/vue/components/courseware/CoursewareToolsAdmin.vue
index 337a39e47fe..974e6fbbf8d 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 1380ea008db..e6e14acea08 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 00000000000..ed1e099de03
--- /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>';
-- 
GitLab