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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
W)xp!H;E7Z46zYG5m1pYz-vj_poic0y

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