From ba2e254f1eb37658fdc00204ddd1ec0f642b6b22 Mon Sep 17 00:00:00 2001
From: Alexander Bias <alexander.bias@uni-ulm.de>
Date: Fri, 9 Apr 2021 10:36:05 +0200
Subject: [PATCH] Allow admin to define the internal term representation

Before this patch, the internal representation of the terms (as they are stored in the Moodle database) was hardcoded to ST = 0 and WT = 1.
This was a problem if you wanted to integrate Moodle with an external lecture management system which uses a different representation of ST = 1 and WT = 2 and wanted to fill the custom field directly with values from this external system.

This patch adds an admin settings which lets the admin control how the terms are represented internally.
Currently, it just has to options (ST = 0 / WT = 1 and ST = 1 / WT = 2), but the code is prepared to add additional options to the admin setting as well as add the possibility to control the internal representation of the term-independent value (which is 1 currently).

This implementation does _not_ update existing field values when the setting is changed. The admin will have to consider a switch of the representation carefully.
---
 classes/data_controller.php      | 125 +++++++++++++++++++++++++++----
 lang/de/customfield_semester.php |   5 ++
 lang/en/customfield_semester.php |   5 ++
 locallib.php                     |   9 +++
 settings.php                     |  10 +++
 version.php                      |   2 +-
 6 files changed, 141 insertions(+), 15 deletions(-)

diff --git a/classes/data_controller.php b/classes/data_controller.php
index 98fe54c..e99b39e 100644
--- a/classes/data_controller.php
+++ b/classes/data_controller.php
@@ -18,8 +18,12 @@
  * Semester customfield data controller
  *
  * Semesters are encoded as YYYYS, where YYYY is the year when the semester begins and
- * S is 0 = summersemester and 1 = wintersemester. So 20191 stands for WiSe 2019/2020.
- * Exception: 0 stands for semesterindepentent.
+ * S is the identifier of the term.
+ *
+ * The identifier of the term is depending on the admin setting customfield_semester/internaltermrepresentation.
+ * By default, it is 0 = summer term and 1 = winter term. So YYYYS = 20191 stands for the winter term 2019/20.
+ *
+ * In addition to that, there are term-independent terms which are represented by YYYYS = 1.
  *
  * @package   customfield_semester
  * @copyright 2020 Justus Dieckmann WWU
@@ -77,7 +81,7 @@ class data_controller extends \core_customfield\data_controller {
         // Compose the field values.
         $field = $this->get_field();
         $formattedoptions = array(
-                1 => get_string('semesterindependent', 'customfield_semester')
+                self::get_termindependent_representation() => get_string('semesterindependent', 'customfield_semester')
         );
         $showmonthsintofuture = $this->get_field()->get_configdata_property('showmonthsintofuture');
         $endtime = new DateTime("+$showmonthsintofuture months");
@@ -87,14 +91,15 @@ class data_controller extends \core_customfield\data_controller {
 
         $beginofsemesters = $this->get_field()->get_configdata_property('beginofsemesters');
         for ($year = $beginofsemesters; $year <= $endyear; $year++) {
-            $formattedoptions[$year * 10] = get_string('summersemester', 'customfield_semester', $year);
+            $formattedoptions[$year * 10 + self::get_summerterm_representation()] =
+                    get_string('summersemester', 'customfield_semester', $year);
 
-            if ($year == $endyear && $endsemester == 0) {
+            if ($year == $endyear && $endsemester == self::get_summerterm_representation()) {
                 break;
             }
 
-            $formattedoptions[$year * 10 + 1] = get_string('wintersemester', 'customfield_semester',
-                    $year . '/' . substr($year + 1, 2, 2));
+            $formattedoptions[$year * 10 + self::get_winterterm_representation()] =
+                    get_string('wintersemester', 'customfield_semester', $year . '/' . substr($year + 1, 2, 2));
         }
 
         // The values were composed in CUSTOMFIELD_SEMESTER_PRESENTATION_ASC order here.
@@ -146,22 +151,24 @@ class data_controller extends \core_customfield\data_controller {
     /**
      * Returns the human readable Semester name for a semesterid.
      *
-     * @param int $value the semesterid (YYYYS as descibed at the top of the file).
+     * @param int $value the semesterid (YYYYS as described at the top of the file).
      * @return string|null The human readable semester name
      */
     public static function get_name_for_semester(int $value) {
-        if ($value === 1) {
+        if ($value === self::get_termindependent_representation()) {
             return get_string('semesterindependent', 'customfield_semester');
         } else if ($value == null) {
             return null;
         } else {
             $year = intdiv($value, 10);
             $semester = $value % 10;
-            if ($semester === 0) {
+            if ($semester === self::get_summerterm_representation()) {
                 return get_string('summersemester', 'customfield_semester', $year);
-            } else {
+            } else if ($semester === self::get_winterterm_representation()) {
                 return get_string('wintersemester', 'customfield_semester',
                         $year . '/' . substr($year + 1, 2, 2));
+            } else {
+                return null;
             }
         }
     }
@@ -178,11 +185,11 @@ class data_controller extends \core_customfield\data_controller {
         $wintertermstartmonth = self::get_winterterm_startmonth();
         if ($month < $summertermstartmonth) {
             $year--;
-            $semester = 1;
+            $semester = self::get_winterterm_representation();
         } else if ($month < $wintertermstartmonth) {
-            $semester = 0;
+            $semester = self::get_summerterm_representation();
         } else {
-            $semester = 1;
+            $semester = self::get_winterterm_representation();
         }
         return $year * 10 + $semester;
     }
@@ -234,4 +241,94 @@ class data_controller extends \core_customfield\data_controller {
 
         return $config->wintertermstartmonth;
     }
+
+    /**
+     * Returns the configured internal representation for the summer term.
+     *
+     * @return int
+     */
+    public static function get_summerterm_representation(): int {
+        global $CFG;
+
+        // Static variable to remember the return value for subsequent calls of this function.
+        static $returnvalue = null;
+
+        // If not already done in a previous call, calculate the return value.
+        if ($returnvalue === null) {
+            // Require local library.
+            require_once($CFG->dirroot.'/customfield/field/semester/locallib.php');
+
+            // Get config from DB.
+            $config = get_config('customfield_semester');
+
+            // If the setting was changed to something different from the default.
+            if ($config->internaltermrepresentation == CUSTOMFIELD_SEMESTER_INTERNAL_ST1WT2) {
+                $returnvalue = CUSTOMFIELD_SEMESTER_INTERNAL_ST1WT2_ST;
+
+                // Otherwise, return the default representation.
+            } else {
+                $returnvalue = CUSTOMFIELD_SEMESTER_INTERNAL_ST0WT1_ST;
+            }
+        }
+
+        // Return value.
+        return $returnvalue;
+    }
+
+    /**
+     * Returns the configured internal representation for the winter term.
+     *
+     * @return int
+     */
+    public static function get_winterterm_representation(): int {
+        global $CFG;
+
+        // Static variable to remember the return value for subsequent calls of this function.
+        static $returnvalue = null;
+
+        // If not already done in a previous call, calculate the return value.
+        if ($returnvalue === null) {
+            // Require local library.
+            require_once($CFG->dirroot.'/customfield/field/semester/locallib.php');
+
+            // Get config from DB.
+            $config = get_config('customfield_semester');
+
+            // If the setting was changed to something different from the default.
+            if ($config->internaltermrepresentation == CUSTOMFIELD_SEMESTER_INTERNAL_ST1WT2) {
+                $returnvalue = CUSTOMFIELD_SEMESTER_INTERNAL_ST1WT2_WT;
+
+                // Otherwise, return the default representation.
+            } else {
+                $returnvalue = CUSTOMFIELD_SEMESTER_INTERNAL_ST0WT1_WT;
+            }
+        }
+
+        // Return value.
+        return $returnvalue;
+    }
+
+    /**
+     * Returns the configured internal representation for the term-independent term.
+     *
+     * @return int
+     */
+    public static function get_termindependent_representation(): int {
+        global $CFG;
+
+        // Static variable to remember the return value for subsequent calls of this function.
+        static $returnvalue = null;
+
+        // If not already done in a previous call, calculate the return value.
+        if ($returnvalue === null) {
+            // Require local library.
+            require_once($CFG->dirroot.'/customfield/field/semester/locallib.php');
+
+            // Return the default representation.
+            $returnvalue = CUSTOMFIELD_SEMESTER_INTERNAL_TERMINDEPENDENT;
+        }
+
+        // Return value.
+        return $returnvalue;
+    }
 }
diff --git a/lang/de/customfield_semester.php b/lang/de/customfield_semester.php
index c96b301..75ffc7e 100644
--- a/lang/de/customfield_semester.php
+++ b/lang/de/customfield_semester.php
@@ -32,6 +32,11 @@ $string['specificsettings'] = 'Einstellungen für das Semesterfeld';
 $string['showmonthsintofuture'] = 'Ein Semester ist auswählbar, wenn es in weniger als X Monaten beginnt.';
 $string['defaultmonthsintofuture'] = 'Standard ist das Semester in X Monaten.';
 $string['beginofsemesters'] = 'Das Jahr, in dem die Liste der Semester anfängt.';
+$string['internaltermrepresentation'] = 'Interne Semester Repräsentierung';
+$string['internaltermrepresentation_desc'] = 'Mit dieser Einstellung definitieren Sie wie das Kursfeld die Semester intern (in der Moodle Datenbank) repräsentiert. Dies ist relevant falls Sie Moodle mit externen Kursplanungssystemen integrieren und das Kursfeld direkt mit Werten eines externen Systems befüllen wollen.';
+$string['internaltermrepresentationwarning'] = '<strong>Warnung:</strong> Eine Änderung dieser Einstellung bewirkt <em>nicht</em> dass existierende Feldwerte in existierenden Kursen aktualisiert werden. Bitte ändern Sie diese Einstellung ausschließlich wenn Sie dies wirklich benötigen und Sie sich bewusst sind dass Sie danach die Feldwerte existierender Kurse neu befüllen müssen.';
+$string['internaltermrepresentationst0wt1'] = 'Repräsentiere das Sommersemester als Semester 0 und das Wintersemester als Semester 1';
+$string['internaltermrepresentationst1wt2'] = 'Repräsentiere das Sommersemester als Semester 1 und das Wintersemester als Semester 2';
 $string['summertermstartmonth'] = 'Der Monat in dem das Sommersemester startet';
 $string['summertermstartmonth_desc'] = 'Mit dieser Einstellung definieren Sie in welchem Monat das Sommersemester startet.';
 $string['termpresentationasc'] = 'Ältere Semester zuerst, semesterunabhängiger Eintrag am Beginn der Liste';
diff --git a/lang/en/customfield_semester.php b/lang/en/customfield_semester.php
index 4ee5539..e07959c 100644
--- a/lang/en/customfield_semester.php
+++ b/lang/en/customfield_semester.php
@@ -32,6 +32,11 @@ $string['specificsettings'] = 'Semester field settings';
 $string['showmonthsintofuture'] = 'A semester will be selectable, if it begins in less than X months.';
 $string['defaultmonthsintofuture'] = 'The default option is the semester in X months.';
 $string['beginofsemesters'] = 'The year, the list of semesters begins in.';
+$string['internaltermrepresentation'] = 'Internal term representation';
+$string['internaltermrepresentation_desc'] = 'With this setting, you control how the custom field represents the terms internally (in the Moodle database). This is relevant if you want to integrate Moodle with external lecture management systems and want to fill the custom field directly with values from an external system.';
+$string['internaltermrepresentationwarning'] = '<strong>Warning:</strong> Changing this setting will <em>not</em> update existing field values in existing courses. Please change this setting only if you really need to and if you are aware that you will have to fill the fields of existing courses again.';
+$string['internaltermrepresentationst0wt1'] = 'Represent the summer term as term 0 and the winter term as term 1';
+$string['internaltermrepresentationst1wt2'] = 'Represent the summer term as term 1 and the winter term as term 2';
 $string['summertermstartmonth'] = 'The month when summer term starts';
 $string['summertermstartmonth_desc'] = 'With this setting, you define in which month the summer term starts.';
 $string['termpresentationasc'] = 'Older terms first, term-independent entry at the beginning of the list';
diff --git a/locallib.php b/locallib.php
index 72eb692..e45274b 100644
--- a/locallib.php
+++ b/locallib.php
@@ -31,3 +31,12 @@ define('CUSTOMFIELD_SEMESTER_WINTERTERMSTART', 10);
 // Constants for term presentation order.
 define('CUSTOMFIELD_SEMESTER_PRESENTATION_ASC', 'asc');
 define('CUSTOMFIELD_SEMESTER_PRESENTATION_DESC', 'desc');
+
+// Constants for internal term representation.
+define('CUSTOMFIELD_SEMESTER_INTERNAL_ST0WT1', 'st0wt1');
+define('CUSTOMFIELD_SEMESTER_INTERNAL_ST0WT1_ST', 0);
+define('CUSTOMFIELD_SEMESTER_INTERNAL_ST0WT1_WT', 1);
+define('CUSTOMFIELD_SEMESTER_INTERNAL_ST1WT2', 'st1wt2');
+define('CUSTOMFIELD_SEMESTER_INTERNAL_ST1WT2_ST', 1);
+define('CUSTOMFIELD_SEMESTER_INTERNAL_ST1WT2_WT', 2);
+define('CUSTOMFIELD_SEMESTER_INTERNAL_TERMINDEPENDENT', 1);
diff --git a/settings.php b/settings.php
index 75bbcfe..9837909 100644
--- a/settings.php
+++ b/settings.php
@@ -55,4 +55,14 @@ if ($ADMIN->fulltree) {
     $description = get_string('termpresentationorder_desc', 'customfield_semester', null, true);
     $setting = new admin_setting_configselect($name, $title, $description, CUSTOMFIELD_SEMESTER_PRESENTATION_ASC, $options);
     $settings->add($setting);
+
+    // Setting for the internal term representation.
+    $options = array (CUSTOMFIELD_SEMESTER_INTERNAL_ST0WT1 => get_string('internaltermrepresentationst0wt1', 'customfield_semester'),
+            CUSTOMFIELD_SEMESTER_INTERNAL_ST1WT2 => get_string('internaltermrepresentationst1wt2', 'customfield_semester'));
+    $name = 'customfield_semester/internaltermrepresentation';
+    $title = get_string('internaltermrepresentation', 'customfield_semester', null, true);
+    $description = get_string('internaltermrepresentation_desc', 'customfield_semester', null, true).'<br />'.
+            get_string('internaltermrepresentationwarning', 'customfield_semester', null, true);
+    $setting = new admin_setting_configselect($name, $title, $description, CUSTOMFIELD_SEMESTER_INTERNAL_ST0WT1, $options);
+    $settings->add($setting);
 }
diff --git a/version.php b/version.php
index 6f326aa..2cf08e9 100644
--- a/version.php
+++ b/version.php
@@ -25,5 +25,5 @@
 defined('MOODLE_INTERNAL') || die();
 
 $plugin->component = 'customfield_semester';
-$plugin->version   = 2020041303;
+$plugin->version   = 2020041304;
 $plugin->requires  = 2019111800;
-- 
GitLab