From 3cf6459ce8e3e37669eba5137da60aa01faef743 Mon Sep 17 00:00:00 2001 From: PMKuipers Date: Wed, 14 Feb 2024 23:01:34 +0100 Subject: [PATCH] Added premium key option --- amd/build/page-studyplan-report.min.js.map | 2 +- amd/build/util/premium.min.js | 3 + amd/build/util/premium.min.js.map | 1 + amd/src/page-studyplan-report.js | 2 - amd/src/util/premium.js | 23 ++ classes/premium.php | 345 +++++++++++++++++++++ db/services.php | 12 + lang/en/local_treestudyplan.php | 23 +- lang/nl/local_treestudyplan.php | 23 +- settings.php | 33 ++ version.php | 2 +- 11 files changed, 463 insertions(+), 6 deletions(-) create mode 100644 amd/build/util/premium.min.js create mode 100644 amd/build/util/premium.min.js.map create mode 100644 amd/src/util/premium.js create mode 100644 classes/premium.php diff --git a/amd/build/page-studyplan-report.min.js.map b/amd/build/page-studyplan-report.min.js.map index 8e33fcd..14e2e73 100644 --- a/amd/build/page-studyplan-report.min.js.map +++ b/amd/build/page-studyplan-report.min.js.map @@ -1 +1 @@ -{"version":3,"file":"page-studyplan-report.min.js","sources":["../src/page-studyplan-report.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"off\" */\n/*eslint no-trailing-spaces: \"off\" */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n// You can call it anything you like\n\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\n\nimport Vue from './vue/vue';\n\nimport Debugger from './util/debugger';\nimport {load_strings} from './util/string-helper';\nimport {ProcessStudyplan} from './studyplan-processor';\nimport {studyplanTiming} from './util/date-helper';\n\nimport TSComponents from './treestudyplan-components';\nimport ModalComponents from './modedit-modal';\nVue.use(ModalComponents);\n\nimport PortalVue from './portal-vue/portal-vue.esm';\nVue.use(PortalVue);\nimport BootstrapVue from './bootstrap-vue/bootstrap-vue';\nVue.use(BootstrapVue);\n\n\nlet debug = new Debugger(\"treestudyplanviewer\");\n\nlet strings = load_strings({\n studyplan: {\n studyplan_select_placeholder: 'studyplan_select_placeholder',\n },\n});\n\n/**\n * Initialize the Page\n * @param {Number} studyplanid The id of the studyplan we need to view \n * @param {Number} period The id of the studyplan we need to view \n */\nexport function init(studyplanid,period) {\n // Make sure the id's are numeric and integer\n if (undefined === studyplanid || !Number.isInteger(Number(studyplanid)) ){ \n studyplanid = 0;\n } else {\n studyplanid = Number(studyplanid);\n } // ensure a numeric value instead of string.\n\n const app = new Vue({\n el: '#root',\n data: {\n\n },\n async mounted() {\n \n },\n computed: {\n \n },\n methods: {\n \n },\n });\n}\n"],"names":["studyplanid","period","undefined","Number","isInteger","Vue","el","data","computed","methods","use","ModalComponents","PortalVue","BootstrapVue","Debugger","studyplan","studyplan_select_placeholder"],"mappings":"onBAyCqBA,YAAYC,QAKzBD,iBAHAE,IAAcF,aAAgBG,OAAOC,UAAUD,OAAOH,cAGxCG,OAAOH,aAFP,EAKN,IAAIK,aAAI,CAChBC,GAAI,QACJC,KAAM,qBAMNC,SAAU,GAGVC,QAAS,qXAxCbC,IAAIC,oCAGJD,IAAIE,iCAEJF,IAAIG,uBAGI,IAAIC,kBAAS,wBAEX,8BAAa,CACvBC,UAAW,CACPC,6BAA8B"} \ No newline at end of file +{"version":3,"file":"page-studyplan-report.min.js","sources":["../src/page-studyplan-report.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"off\" */\n/*eslint no-trailing-spaces: \"off\" */\n/*eslint-env es6*/\n\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\n\nimport Vue from './vue/vue';\n\nimport Debugger from './util/debugger';\nimport {load_strings} from './util/string-helper';\nimport {ProcessStudyplan} from './studyplan-processor';\nimport {studyplanTiming} from './util/date-helper';\n\nimport TSComponents from './treestudyplan-components';\nimport ModalComponents from './modedit-modal';\nVue.use(ModalComponents);\n\nimport PortalVue from './portal-vue/portal-vue.esm';\nVue.use(PortalVue);\nimport BootstrapVue from './bootstrap-vue/bootstrap-vue';\nVue.use(BootstrapVue);\n\n\nlet debug = new Debugger(\"treestudyplanviewer\");\n\nlet strings = load_strings({\n studyplan: {\n studyplan_select_placeholder: 'studyplan_select_placeholder',\n },\n});\n\n/**\n * Initialize the Page\n * @param {Number} studyplanid The id of the studyplan we need to view \n * @param {Number} period The id of the studyplan we need to view \n */\nexport function init(studyplanid,period) {\n // Make sure the id's are numeric and integer\n if (undefined === studyplanid || !Number.isInteger(Number(studyplanid)) ){ \n studyplanid = 0;\n } else {\n studyplanid = Number(studyplanid);\n } // ensure a numeric value instead of string.\n\n const app = new Vue({\n el: '#root',\n data: {\n\n },\n async mounted() {\n \n },\n computed: {\n \n },\n methods: {\n \n },\n });\n}\n"],"names":["studyplanid","period","undefined","Number","isInteger","Vue","el","data","computed","methods","use","ModalComponents","PortalVue","BootstrapVue","Debugger","studyplan","studyplan_select_placeholder"],"mappings":"onBAuCqBA,YAAYC,QAKzBD,iBAHAE,IAAcF,aAAgBG,OAAOC,UAAUD,OAAOH,cAGxCG,OAAOH,aAFP,EAKN,IAAIK,aAAI,CAChBC,GAAI,QACJC,KAAM,qBAMNC,SAAU,GAGVC,QAAS,qXAxCbC,IAAIC,oCAGJD,IAAIE,iCAEJF,IAAIG,uBAGI,IAAIC,kBAAS,wBAEX,8BAAa,CACvBC,UAAW,CACPC,6BAA8B"} \ No newline at end of file diff --git a/amd/build/util/premium.min.js b/amd/build/util/premium.min.js new file mode 100644 index 0000000..72ea44c --- /dev/null +++ b/amd/build/util/premium.min.js @@ -0,0 +1,3 @@ +define("local_treestudyplan/util/premium",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.enabled=function(){return!!premiumstatus.enabled};let premiumstatus={enabled:!1,website:"",name:"",expires:""}})); + +//# sourceMappingURL=premium.min.js.map \ No newline at end of file diff --git a/amd/build/util/premium.min.js.map b/amd/build/util/premium.min.js.map new file mode 100644 index 0000000..1e9a6b8 --- /dev/null +++ b/amd/build/util/premium.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"premium.min.js","sources":["../../src/util/premium.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"off\" */\n/*eslint no-trailing-spaces: \"off\" */\n/*eslint-env es6*/\n\nimport {call} from 'core/ajax';\n\n// Prepare default value.\nlet premiumstatus = {\n enabled: false,\n website: \"\",\n name: \"\",\n expires: \"\",\n};\n\n/**\n * Check if premium status is enabled.\n * @returns {Object} The map with strings loaded in\n */\nexport function enabled (){\n return !!premiumstatus.enabled;\n}\n"],"names":["premiumstatus","enabled","website","name","expires"],"mappings":"wLAqBaA,cAAcC,aAZvBD,cAAgB,CAChBC,SAAS,EACTC,QAAS,GACTC,KAAM,GACNC,QAAS"} \ No newline at end of file diff --git a/amd/src/page-studyplan-report.js b/amd/src/page-studyplan-report.js index 6901584..ee00ba9 100644 --- a/amd/src/page-studyplan-report.js +++ b/amd/src/page-studyplan-report.js @@ -3,8 +3,6 @@ /*eslint linebreak-style: "off" */ /*eslint no-trailing-spaces: "off" */ /*eslint-env es6*/ -// Put this file in path/to/plugin/amd/src -// You can call it anything you like import {call} from 'core/ajax'; import notification from 'core/notification'; diff --git a/amd/src/util/premium.js b/amd/src/util/premium.js new file mode 100644 index 0000000..f2561c8 --- /dev/null +++ b/amd/src/util/premium.js @@ -0,0 +1,23 @@ +/*eslint no-var: "error" */ +/*eslint no-unused-vars: "off" */ +/*eslint linebreak-style: "off" */ +/*eslint no-trailing-spaces: "off" */ +/*eslint-env es6*/ + +import {call} from 'core/ajax'; + +// Prepare default value. +let premiumstatus = { + enabled: false, + website: "", + name: "", + expires: "", +}; + +/** + * Check if premium status is enabled. + * @returns {Object} The map with strings loaded in + */ +export function enabled (){ + return !!premiumstatus.enabled; +} diff --git a/classes/premium.php b/classes/premium.php new file mode 100644 index 0000000..e73f8de --- /dev/null +++ b/classes/premium.php @@ -0,0 +1,345 @@ +. +/** + * Determine premium status + * @package local_treestudyplan + * @copyright 2023 P.M. Kuipers + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +namespace local_treestudyplan; +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/externallib.php'); + +use DateTime; +use moodle_url; +use stdClass; + + +/** + * Handle badge information in the same style as the other classes + */ +class premium extends \external_api { + + private static $premiumcrt = "-----BEGIN CERTIFICATE----- +MIIDSzCCAjMCFFlyhmKf1fN7U5lQL/dtlsyP24AQMA0GCSqGSIb3DQEBCwUAMGEx +CzAJBgNVBAYTAk5MMRYwFAYDVQQIDA1Ob29yZC1Ib2xsYW5kMRowGAYDVQQKDBFN +aXFyYSBFbmdpbmVlcmluZzEeMBwGA1UEAwwVVHJlZVN0dWR5cGxhbiBQcmVtaXVt +MCAXDTI0MDIxMDE2MDQwM1oYDzIxMjQwMTE3MTYwNDAzWjBhMQswCQYDVQQGEwJO +TDEWMBQGA1UECAwNTm9vcmQtSG9sbGFuZDEaMBgGA1UECgwRTWlxcmEgRW5naW5l +ZXJpbmcxHjAcBgNVBAMMFVRyZWVTdHVkeXBsYW4gUHJlbWl1bTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAOD7+Nf5UBYGmIadI+kRM7vSPGA12F6cyZuZ +O/JsdCWzZx3cCgVYt29DxHRvFVGrhGGLsoaMY9iXc9LdeO02jKqL3RoPo2kc5moT +SNarsKZcGZXgqo5NATmdMLqQpKAy41H0ybgXZDLq5XKs9YIRlkwSpzQTNeP49mOl +48giVX3icbpMw1TdQotalKXAtcs62o+guQJNANpjBRxPXssrmDoNXrJcAtUjNOjx +8M+8tCmwkKwBoK8F3wWxIo04kZ9KILtybMmn4VJJ6SwLEf4StphTIoru8zS7XUt8 +3HbV3PsiyYErPlwIcobfcjwZJpub23bzetvxRvhpeIpLhrTGrPMCAwEAATANBgkq +hkiG9w0BAQsFAAOCAQEAQwkbP6m3sdQgXEK3mYYZvvs6R/FI9QPu/9ICA+dgfj4y +7wvL0toYYR5oXdhO9At3MYmS+0bFUmqoTS+cxsC4COpEKFbRBWwbJ3NXAw14Hx2U +ELLqMZGJNOwNV+3ZdhADrwA++AjUqu144ObrcNUqo4+A4h9R8qj+o0J50Gvwja9R +Uh67LsF4Ls8fUtqzpqct94bUl6MPMHlH4qpZlgndmQdgOwLWeQEmM8X3WtSJH90S +n8FqBInMBhGu1uz0Qeo09ke0RHRnghP9EXfig/veMegASZeEhFqmS2Bdiy6gqeZ5 +Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q== +-----END CERTIFICATE-----"; + + private static $cachedpremiumstatus = null; + + private static function decrypt($encrypted) { + // Get the public key. + $key = \openssl_get_publickey(self::$premiumcrt); + if ($key === false ){ + throw new \ValueError("Error parsing public key data)"); + } + // Determine the key size. + $keysize = \openssl_pkey_get_details($key)["bits"]; + // + $blocksize = ($keysize / 8); // Bits / 8. Whether padded or not. + + // Decode data in + $b64 = \base64_decode($encrypted); + if ($b64 === false) { + throw new \ValueError("Error in base64 decoding"); + } + + $data = \str_split($b64,$blocksize); + $decrypted = ""; + $i = 0; + foreach($data as $chunk) { + if (\openssl_public_decrypt($chunk,$dchunk,$key, \OPENSSL_PKCS1_PADDING)) { + $decrypted .= $dchunk; + } else { + throw new \ValueError("Error decrypting chunk $i ({$blocksize} bytes)"); + } + $i++; + } + + // Deprecated in PHP 8.0 and up, but included to be compatible with 7.4. + // Wrap in a try/catch in case the function is removed in a later version. + try { + \openssl_pkey_free($key); + } catch (\Exception $x) {} + + return $decrypted; + } + + private static function trim_headers($data) { + // Headers are repeated in this function for easier testing and copy-pasting into other projects. + $START_HEADER = "----- BEGIN ACTIVATION KEY -----"; + $END_HEADER = "----- END ACTIVATION KEY -----"; + + $parts = preg_split("/\r?\n/",\trim($data)); + if (count($parts) > 2) { + $start = -1; + $end = -1; + for($i = 0; $i < count($parts); $i++) { + if ( $parts[$i] == $START_HEADER ) { + $start = $i+1; + } + if ($start > 0 && $parts[$i] == $END_HEADER) { + $end = $i; + } + } + if ($start < 0 || $end < 0 || $end - $start <= 0) { + throw new \ValueError("Invalid activation key wrappers"); + } else { + $keyslice = array_slice($parts, $start, $end - $start); + return implode("\n",$keyslice); + } + } else { + throw new \ValueError("Invalid activation key"); + } + } + + + public static function enabled() { + $status = self::premiumStatus(); + return $status->enabled; + } + + protected static function premiumStatus() { + if (!isset(self::$cachedpremiumstatus)) { + // Initialize default object. + $o = new \stdClass; + $o->enabled = false; + $o->intent = ""; + $o->name = ""; + $o->website = ""; + $o->expires = ""; + $o->expired = false; + $o->issued = ""; + $o->message = \get_string("premium:notregistered","local_treestudyplan"); + + $activationkey = \get_config("local_treestudyplan","premium_key"); + if (strlen($activationkey) > 0) { + $activationkey; + + try { + $keydata = self::trim_headers($activationkey); + $json = self::decrypt($keydata); + $decoded = \json_decode($json,false); + + if (is_object($decoded)) { + + $keys = ["intent","name","website","expires","issued"]; + foreach ( $keys as $k) { + if (isset($decoded->$k)) { + $o->$k = $decoded->$k; + } + } + + // Convert dates to user dates + $now = new \DateTime(); + $issuedate = new \DateTime($o->issued); + $expirydate = new \DateTime(); // Default to now + if ($o->expires == 'never') { + // If expiry date == never + $expirydate->add(new \DateInterval("P1Y")); + } else { + try { + $expirydate = new \DateTime($o->expires); + } catch (\Exception $x) {} + } + if ($o->intent == 'treestudyplan' + && !empty($o->issued) + && self::website_match($o->website) + ) { + if ($expirydate > $now ) { + $o->enabled = true; + $o->expired = false; + } else { + $o->expired = true; + $o->enabled = false; + } + // Format dates localized. + $o->issued = \userdate($issuedate->getTimestamp(),\get_string('strftimedate','langconfig')); + if ($o->expires == "never") { + $o->expires = \get_string("premium:never",'local_treestudyplan'); + } else { + $o->expires = \userdate($expirydate->getTimestamp(),\get_string('strftimedate','langconfig')); + } + } + } + } catch (\ValueError $x) { + $o->status = \get_string("premium:invalidactivationcontent","local_treestudyplan"); + } + + } + self::$cachedpremiumstatus = $o; + + } + return self::$cachedpremiumstatus; + } + + private static function website_match($key) { + global $CFG; + $site = $CFG->wwwroot; + // Add double slashes to key and site if no scheme is set. + // Basically: if no double slashes present before any dots,shashes or @s. + if(!preg_match_all('#^[^./@]*?//#',$key )) { + $key = "//".$key; + } + if(!preg_match_all('#^[^./@]*?//#',$site)) { + $site = "//".$site; + } + // Use parse_url() to split path and host. + $keyurl = (object)\parse_url($key); + $siteurl = (object)\parse_url($site); + + // No match if host is empty on key or site + if (empty($keyurl->host) || empty($siteurl->host)) { + if(empty($keyurl->host)){ + print "\e[91mError: no host in keyurl '{$key}'\n"; + print_r($keyurl); + print "\e[0m"; + } + if(empty($siteurl->host)){ + print "\e[91mError: no host in siteurl '{$site}'\n"; + print_r($siteurl); + print "\e[0m"; + } + return false; + } + + // First match the host part. + $keyparts = array_reverse(explode(".",$keyurl->host)); + $siteparts = array_reverse(explode(".",$siteurl->host)); + + // Trim starting www from both parts, since site.domain and www.site.domain should be treated as the same. + if (($x = array_pop($keyparts)) != "www") {array_push($keyparts,$x);} + if (($x = array_pop($siteparts)) != "www") {array_push($siteparts,$x);} + + for ($i = 0; $i < count($keyparts); $i++) { + // No match if the site does not have a part, but the key does. Unless the key part is * + if (!isset($siteparts[$i]) ) { + if($keyparts[$i] != "*") { + return false; + } else { + $i++; //increment $i by one before break, to make sure the comparison following this loop holds. + break; // Stop comparison. Host part matches. + } + } + + // Now do a proper case insensitive check for matching. + // Uses fnmatch to easily handle shell type wildcards. + if ( ! \fnmatch($keyparts[$i],$siteparts[$i],\FNM_CASEFOLD)) { + return false; + } + } + // Fail if the site has a deeper subdomain than the key, unless the deepest key subdomain is * + if ($keyparts[$i-1] != '*' && count($siteparts) > ($i)) { + return false; + } + + // If we made it here then the host part matches. Now check the path. + // If path is /*, matches all subpaths including / + $keypath = empty($keyurl->path)?"/":$keyurl->path; + $sitepath = empty($siteurl->path)?"/":$siteurl->path; + + // Trim trailing / from both paths before comparison + if (strlen($sitepath) > 1) { + $sitepath = \rtrim($sitepath,"/"); + } + if (strlen($keypath) > 1) { + $keypath = \rtrim($keypath,"/"); + } + + // Do a case insensitive fnmatch on the site so wildcards are matched too. + return \fnmatch($keypath,$sitepath,\FNM_CASEFOLD); + } + + public static function get_premiumstatus_parameters() : \external_function_parameters { + return new \external_function_parameters([]); + } + + /** + * Return value description for webservice function get_premiumstatus + */ + public static function get_premiumstatus_returns() : \external_description { + return new \external_single_structure([ + "enabled" => new \external_value(PARAM_BOOL, 'premium status enabled'), + "website" => new \external_value(PARAM_TEXT, 'premium registration website'), + "name" => new \external_value(PARAM_TEXT, 'premium registration name'), + "expires" => new \external_value(PARAM_TEXT, 'premium registration expiry date'), + "expired" => new \external_value(PARAM_BOOL, 'premium status expired'), + ]); + } + + /** + * Get premium status information for webservice + * @return object + */ + public static function get_premiumstatus() { + $status = self::premiumStatus(); + $keys = [ + "enabled", + "website", + "name", + "expires", + "expired", + ]; + + $result = []; + foreach ( $keys as $param) { + $result[$param] = $status->$param; + } + return $result; + } + + public static function statusdescription() { + $status = self::premiumStatus(); + + $msg = new \stdClass; + $msg->name = $status->name; + $msg->issued = $status->issued; + $msg->expires = $status->expires; + if ($status->website != "*") { + $msg->sitestatus = \get_string("premium:onsite",'local_treestudyplan',$status); + } else { + $msg->sitestatus = ""; + } + + if($status->enabled) { + return \get_string("premium:active",'local_treestudyplan',$msg); + } else if ($status->expired) { + return \get_string("premium:expired",'local_treestudyplan',$msg); + } else { + return \get_string("premium:notregistered",'local_treestudyplan',$msg); + } + } + +} \ No newline at end of file diff --git a/db/services.php b/db/services.php index 37a086e..32705f8 100644 --- a/db/services.php +++ b/db/services.php @@ -710,4 +710,16 @@ $functions = [ 'capabilities' => 'local/treestudyplan:viewuserreports', 'loginrequired' => true, ], + /*************************** + * Premium status functions + ***************************/ + 'local_treestudyplan_premiumstatus' => [ // Web service function name. + 'classname' => '\local_treestudyplan\premium', // Class containing the external function. + 'methodname' => 'get_premiumstatus', // External function name. + 'description' => 'Retrieve premium status info', + 'type' => 'read', // Database rights of the web service function (read, write). + 'ajax' => true, + 'capabilities' => '', + 'loginrequired' => false, + ], ]; diff --git a/lang/en/local_treestudyplan.php b/lang/en/local_treestudyplan.php index 44ab319..e5e759e 100644 --- a/lang/en/local_treestudyplan.php +++ b/lang/en/local_treestudyplan.php @@ -433,4 +433,25 @@ $string["individuals"] = 'Individuals'; $string["error:cannotviewcategory"] = 'Error: You do not have access to view this category or context: {$a}'; $string["error:nostudyplanviewaccess"] = 'Error: You do not have access to view study plans in this category or context: {$a}'; $string["error:nostudyplaneditaccess"] = 'Error: You do not have access to manage study plans in this category or context: {$a}'; -$string["error:nocategoriesvisible"] = 'Error: You have no viewing permissions in any category. Therefore the course list remains empty.'; \ No newline at end of file +$string["error:nocategoriesvisible"] = 'Error: You have no viewing permissions in any category. Therefore the course list remains empty.'; + +$string["premium:never"] = 'never'; +$string["premium:onsite"] = 'for use on site {$a->website}'; +$string["premium:active"] = 'Premium access enabled.
Registered to {$a->name} {$a->sitestatus}
Expires {$a->expires}'; +$string["premium:notregistered"] = 'Premium access disabled '; +$string["premium:invalidactivationcontent"] = 'Premium activation key not recognized'; +$string["premium:expired"] = 'Premium access expired on {$a->expires}
Was registered to {$a->name} {$a->sitestatus}'; +$string["settingspage_premium"] = 'Premium registration'; +$string["setting_premium_heading"] = 'Premium features'; +$string["settingdesc_premium_heading"] = 'To access premium features, you need a registration key. +
Premium features include: +'; +$string["setting_premium_status"] = 'Current premium status'; +$string["setting_premium_key"] = 'Activation key'; +$string["settingdesc_premium_key"] = 'Paste the premium key you received in the box above.'; + +$string["premiumfeature:morestudyplans"] = 'Creating more than 5 studyplans in a single category is a premium feature.'; \ No newline at end of file diff --git a/lang/nl/local_treestudyplan.php b/lang/nl/local_treestudyplan.php index 042ddb1..d4cab91 100644 --- a/lang/nl/local_treestudyplan.php +++ b/lang/nl/local_treestudyplan.php @@ -433,4 +433,25 @@ $string["individuals"] = 'Individueel'; $string["error:cannotviewcategory"] = 'Fout: Je hebt geen rechten om deze category of context te bekijken: {$a}'; $string["error:nostudyplanviewaccess"] = 'Fout: Je hebt geen rechten om studieplannen in deze categorie of context te bekijken: {$a}'; $string["error:nostudyplaneditaccess"] = 'Fout: Je hebt geen rechten om studieplannen in deze categorie of context te beheren: {$a}'; -$string["error:nocategoriesvisible"] = 'Fout: Je kunt geen cursussen in een categorie bekijken. Daarom blijft de cursuslijst leeg'; \ No newline at end of file +$string["error:nocategoriesvisible"] = 'Fout: Je kunt geen cursussen in een categorie bekijken. Daarom blijft de cursuslijst leeg'; + +$string["premium:never"] = 'nooit'; +$string["premium:onsite"] = 'voor site {$a->website}'; +$string["premium:active"] = 'Premium toegang ingeschakeld.
Uitgegeven aan {$a->name} {$a->sitestatus}
Verloopt op{$a->expires}'; +$string["premium:notregistered"] = 'Premium toegang staat uitgeschakeld'; +$string["premium:invalidactivationcontent"] = 'Premium activeringssleutel niet herkend'; +$string["premium:expired"] = 'Premium toegang is verlopen op {$a->expires}
Was uitgegeven aan {$a->name} {$a->sitestatus}'; +$string["settingspage_premium"] = 'Premium registration'; +$string["setting_premium_heading"] = 'Premium features'; +$string["settingdesc_premium_heading"] = 'Voor premium toegang is een activeringssleutel vereist. +
Premium toegang bevat onder andere: +'; +$string["setting_premium_status"] = 'Premium status'; +$string["setting_premium_key"] = 'Activation key'; +$string["settingdesc_premium_key"] = 'Premium activation key'; + +$string["premiumfeature:morestudyplans"] = 'Meer dan 5 studieplannen in één categorie aanmaken kan alleen met premium toegang.'; \ No newline at end of file diff --git a/settings.php b/settings.php index 3e9848f..52cc888 100644 --- a/settings.php +++ b/settings.php @@ -25,6 +25,7 @@ defined('MOODLE_INTERNAL') || die(); use local_treestudyplan\aggregator; +use local_treestudyplan\premium; if ($hassiteconfig) { @@ -329,4 +330,36 @@ if ($hassiteconfig) { get_string('cfg_help', 'local_treestudyplan', null, true), $CFG->wwwroot . '/local/treestudyplan/doc.php/index.htm')); + + /************************************** + * + * Settings page: Cohort sync + * + *************************************/ + + $pagepremium = new admin_settingpage('local_treestudyplan_settings_premium', + get_string('settingspage_premium', 'local_treestudyplan', null, true)); + + // Description heading. + $pagepremium->add(new admin_setting_heading('local_treestudyplan/premium_heading', + get_string('setting_premium_heading', 'local_treestudyplan'), + get_string('settingdesc_premium_heading', 'local_treestudyplan') + )); + + // Description heading. + $pagepremium->add(new admin_setting_description('local_treestudyplan/premium_status', + get_string('setting_premium_status', 'local_treestudyplan'), + premium::statusdescription() . "
 
" // Add empty row at end. + )); + + $pagepremium->add(new admin_setting_configtextarea('local_treestudyplan/premium_key', + get_string('setting_premium_key', 'local_treestudyplan'), + get_string('settingdesc_premium_key', 'local_treestudyplan'), + "", + PARAM_RAW + )); + + // Add settings page2 to the admin settings category. + $ADMIN->add('local_treestudyplan', $pagepremium); + } diff --git a/version.php b/version.php index bb94b9d..10a3f58 100644 --- a/version.php +++ b/version.php @@ -22,7 +22,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494). -$plugin->version = 2024020900; // YYYYMMDDHH (year, month, day, iteration). +$plugin->version = 2024020901; // YYYYMMDDHH (year, month, day, iteration). $plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11). $plugin->release = "1.1.0";