diff --git a/classes/condition.php b/classes/condition.php index 05c640a..a4573b2 100644 --- a/classes/condition.php +++ b/classes/condition.php @@ -44,6 +44,23 @@ class condition extends \core_availability\condition { */ protected $ip_addresses = ''; + /** + * condition constructor. + * + * @param $structure + * + * @throws \coding_exception + */ + public function __construct($structure) { + + if (isset($structure->ip_addresses)) { + // set a number + $this->ip_addresses = $structure->ip_addresses; + } else { + throw new \coding_exception('Missing or invalid ->ip_addresses for ipaddress condition'); + } + } + /** * Determines whether a particular item is currently available * according to this availability condition. @@ -69,10 +86,15 @@ class condition extends \core_availability\condition { * @return bool True if available */ public function is_available($not, info $info, $grabthelot, $userid) { - // Check if the setting is enabled. + + if (empty($this->ip_addresses)) { + return true; + } // Check if ip-address matches - + if (address_in_subnet(getremoteaddr(), trim($this->ip_addresses))) { + return true; + } return false; } @@ -113,7 +135,43 @@ class condition extends \core_availability\condition { * @return string Text representation of parameters */ protected function get_debug_string() { - // TODO: Implement get_debug_string() method. + return !empty($this->ip_addresses) ? 'ip_addresses ON' : 'ip_addresses OFF'; + } + + /** + * Returns a JSON object which corresponds to a condition of this type. + * + * Intended for unit testing, as normally the JSON values are constructed + * by JavaScript code. + * + * @param string $ip_addresses + * + * @return \stdClass Object representing condition + */ + public static function get_json($ip_addresses) { + return (object)[ + 'type' => 'ipaddress', + 'ip_addresses' => $ip_addresses, + ]; + } + + /** + * Check if ip-address is valid + * + * @param $ip_addresses + * + * @return bool + */ + public static function is_valid_ip_addresses($ip_addresses) { + $ip_addresses = implode(',', $ip_addresses); + foreach ($ip_addresses as $ip_address) { + if (!filter_var($ip_address, FILTER_VALIDATE_IP)) { + return false; + } + } + + return true; + } /** @@ -122,6 +180,11 @@ class condition extends \core_availability\condition { * @return \stdClass Structure object (ready to be made into JSON format) */ public function save() { - // TODO: Implement save() method. + + + return (object)[ + 'type' => 'ipaddress', + 'ip_addresses' => $this->ip_addresses, + ]; } } \ No newline at end of file diff --git a/classes/frontend.php b/classes/frontend.php index 9536962..66c7ab2 100644 --- a/classes/frontend.php +++ b/classes/frontend.php @@ -40,7 +40,13 @@ class frontend extends \core_availability\frontend { * @return array */ protected function get_javascript_strings() { - return ['requires_app', 'requires_notapp', 'label_access']; + return [ + 'js:ipaddress', + 'js:turn-on-timestamps', + 'js:turn-off-timestamps', + 'js:enabled', + 'error_ipaddress', + ]; } /** diff --git a/lang/en/availability_ipaddress.php b/lang/en/availability_ipaddress.php index 733e07c..5ed2b2e 100644 --- a/lang/en/availability_ipaddress.php +++ b/lang/en/availability_ipaddress.php @@ -25,4 +25,16 @@ **/ $string['pluginname'] = 'IP address'; $string['title'] = 'IP address'; -$string['description'] = 'Restrict access by ip-address'; \ No newline at end of file +$string['description'] = 'Restrict access by ip-address or subnet'; +$string['require_condition'] = 'Matching ip-address/subnet'; + +// Javascript strings. +$string['js:ipaddress'] = 'Require network address'; +$string['js:turn-on-timestamps'] = ''; +$string['js:turn-off-timestamps'] = ''; +$string['js:enabled'] = ''; + +// Errors. +$string['error_ipaddress'] = 'Incorrect ip-address/subnet format'; + +$string['requiresubnet_help'] = 'Access may be restricted to particular subnets on the LAN or Internet by specifying a comma-separated list of partial or full IP address numbers. This can be useful for an invigilated (proctored) quiz, to ensure that only people in a certain location can access the quiz.'; diff --git a/version.php b/version.php index 1c0d362..7ac4fd0 100644 --- a/version.php +++ b/version.php @@ -27,7 +27,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'availability_ipaddress'; -$plugin->version = 2019051400; +$plugin->version = 2019051401; $plugin->release = 'v3.5.0'; -$plugin->requires = 2018120300; +$plugin->requires = 2016120500; $plugin->maturity = MATURITY_BETA; \ No newline at end of file diff --git a/yui/build/moodle-availability_ipaddress-form/moodle-availability_ipaddress-form-debug.js b/yui/build/moodle-availability_ipaddress-form/moodle-availability_ipaddress-form-debug.js new file mode 100644 index 0000000..655a15e --- /dev/null +++ b/yui/build/moodle-availability_ipaddress-form/moodle-availability_ipaddress-form-debug.js @@ -0,0 +1,168 @@ +YUI.add('moodle-availability_ipaddress-form', function (Y, NAME) { + +/** + * Availability ip-address YUI code + * + * @package availability_ipaddress + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright 2019-05-14 Mfreak.nl | LdesignMedia.nl - Luuk Verhoeven + */ + +/** + * JavaScript for form editing grade conditions. + * + * @module moodle-availability_ipaddress-form + */ +M.availability_ipaddress = M.availability_ipaddress || {}; + +// MIT https://github.com/sindresorhus/ip-regex +// Advanced ip-address regex for validating. +M.availability_ipaddress.v4 = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]' + + '[0-9]|[1-9][0-9]|[0-9])){3}'; +M.availability_ipaddress.v6 = '((?:[0-9a-fA-F]{1,4}:){7}(?:[0-9a-fA-F]{1,4}|:)|(?:[0-9a-fA-F]{1,4}:){6}(?:(?:25[0-5]|2[0-4]' + + '[0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|:[0-9a-fA-F]{1,4}|:)' + + '|(?:[0-9a-fA-F]{1,4}:){5}(?::(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]' + + '|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,2}|:)|(?:[0-9a-fA-F]{1,4}:){4}(?:' + + '(:[0-9a-fA-F]{1,4}){0,1}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]' + + '|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,3}|:)|(?:[0-9a-fA-F]{1,4}:){3}(?:(:[0-9a-fA-F]{1,4}){0,2}:(?:25[0-5]|2[0-4]' + + '[0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|' + + '(:[0-9a-fA-F]{1,4}){1,4}|:)|(?:[0-9a-fA-F]{1,4}:){2}(?:(:[0-9a-fA-F]{1,4}){0,3}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|' + + '[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,5}|:)|' + + '(?:[0-9a-fA-F]{1,4}:){1}(?:(:[0-9a-fA-F]{1,4}){0,4}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.' + + '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,6}|:)|(?::((?::[0-9a-fA-F]{1,4}){0,5}' + + ':(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|' + + '(?::[0-9a-fA-F]{1,4}){1,7}|:)))(%[0-9a-zA-Z]{1,})?'; + +console.log(M.availability_ipaddress.v6); +/** + * @class M.availability_ipaddress.form + * @extends M.core_availability.plugin + */ +M.availability_ipaddress.form = Y.Object(M.core_availability.plugin); + +/** + * Initialises this plugin. + * + * @method initInner + * @param {Array} param Array of objects + */ +M.availability_ipaddress.form.initInner = function(params) { + "use strict"; + console.log('M.availability_ipaddress'); +}; + +/** + * Gets the numeric value of an input field. Supports decimal points (using + * dot or comma). + * + * @method getValue + * @return {Number|String} Value of field as number or string if not valid + */ +M.availability_ipaddress.form.getValue = function(field, node) { + "use strict"; + // Get field value. + var value = node.one('input[name=' + field + ']').get('value'); + + // If it is not a valid positive number, return false. + if (M.availability_ipaddress.validate_ipaddress(value)) { + console.log('Valid ip-address'); + return value; + } + + console.log('getValue failed:', value); + return value; +}; + +/** + * getNode + * @param json + * @returns {*} + */ +M.availability_ipaddress.form.getNode = function(json) { + "use strict"; + var html, node, root, id; + + // Make sure we work with unique id. + id = 'ip_addresses' + M.availability_ipaddress.form.instId; + M.availability_ipaddress.form.instId += 1; + + // Create HTML structure. + html = ''; + html += ''; + html += ''; + node = Y.Node.create('' + html + ''); + + // Set initial values, if specified. + if (json.ip_addresses !== undefined) { + node.one('input[name=ip_addresses]').set('value', json.ip_addresses); + } + + // Add event handlers (first time only). + if (!M.availability_ipaddress.form.addedEvents) { + M.availability_ipaddress.form.addedEvents = true; + root = Y.one('.availability-field'); + root.delegate('valuechange', function() { + // Trigger the updating of the hidden availability data whenever the ipaddress field changes. + M.core_availability.form.update(); + }, '.availability_ipaddress input[name=ip_addresses]'); + } + + return node; +}; + +/** + * validate_ipaddress + * + * @param {string[]} ipaddresses + * @returns {boolean} + */ +M.availability_ipaddress.validate_ipaddress = function(ipaddresses) { + 'use strict'; + + ipaddresses = ipaddresses.split(','); + for (var i in ipaddresses) { + + // Test normal ip format. + if (new RegExp("(?:".concat(M.availability_ipaddress.v4, ")|(?:").concat(M.availability_ipaddress.v6, ")"), "g") + .test(ipaddresses[i])) { + continue; + } + + // Test subnet with a regex. + if (new RegExp("(?:".concat(M.availability_ipaddress.v4 + "\\/(3[0-2]|[12]?[0-9])", ")|(?:") + .concat(M.availability_ipaddress.v6 + "\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])", ")"), "g") + .test(ipaddresses[i])) { + continue; + } + + console.error('Incorrect ip', ipaddresses[i]); + return false; + } + + console.log('Valid ipaddresses', ipaddresses); + return true; +}; + +M.availability_ipaddress.form.fillValue = function(value, node) { + // This function gets passed the node (from above) and a value + // object. Within that object, it must set up the correct values + // to use within the JSON data in the form. Should be compatible + // with the structure used in the __construct and save functions + // within condition.php. + value.ip_addresses = this.getValue('ip_addresses', node); +}; + +M.availability_ipaddress.form.fillErrors = function(errors, node) { + "use strict"; + var value = {}; + this.fillValue(value, node); + console.log('ip_address:', value); + + // Basic ip_addresses checks. + if (M.availability_ipaddress.validate_ipaddress(value.ip_addresses) === false) { + errors.push('availability_ipaddress:error_ipaddress'); + } +}; + +}, '@VERSION@', {"requires": ["base", "node", "event", "moodle-core_availability-form"]}); diff --git a/yui/build/moodle-availability_ipaddress-form/moodle-availability_ipaddress-form-min.js b/yui/build/moodle-availability_ipaddress-form/moodle-availability_ipaddress-form-min.js new file mode 100644 index 0000000..696fdcc --- /dev/null +++ b/yui/build/moodle-availability_ipaddress-form/moodle-availability_ipaddress-form-min.js @@ -0,0 +1 @@ +YUI.add("moodle-availability_ipaddress-form",function(e,t){M.availability_ipaddress=M.availability_ipaddress||{},M.availability_ipaddress.v4="(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}",M.availability_ipaddress.v6="((?:[0-9a-fA-F]{1,4}:){7}(?:[0-9a-fA-F]{1,4}|:)|(?:[0-9a-fA-F]{1,4}:){6}(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|:[0-9a-fA-F]{1,4}|:)|(?:[0-9a-fA-F]{1,4}:){5}(?::(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,2}|:)|(?:[0-9a-fA-F]{1,4}:){4}(?:(:[0-9a-fA-F]{1,4}){0,1}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,3}|:)|(?:[0-9a-fA-F]{1,4}:){3}(?:(:[0-9a-fA-F]{1,4}){0,2}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,4}|:)|(?:[0-9a-fA-F]{1,4}:){2}(?:(:[0-9a-fA-F]{1,4}){0,3}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,5}|:)|(?:[0-9a-fA-F]{1,4}:){1}(?:(:[0-9a-fA-F]{1,4}){0,4}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,6}|:)|(?::((?::[0-9a-fA-F]{1,4}){0,5}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(?::[0-9a-fA-F]{1,4}){1,7}|:)))(%[0-9a-zA-Z]{1,})?",console.log(M.availability_ipaddress.v6),M.availability_ipaddress.form=e.Object(M.core_availability.plugin),M.availability_ipaddress.form.initInner=function(e){"use strict";console.log("M.availability_ipaddress")},M.availability_ipaddress.form.getValue=function(e,t){"use strict";var n=t.one("input[name="+e+"]").get("value");return M.availability_ipaddress.validate_ipaddress(n)?(console.log("Valid ip-address"),n):(console.log("getValue failed:",n),n)},M.availability_ipaddress.form.getNode=function(t){"use strict";var n,r,i,s;return s="ip_addresses"+M.availability_ipaddress.form.instId,M.availability_ipaddress.form.instId+=1,n="",n+='",n+='',r=e.Node.create(''+n+""),t.ip_addresses!==undefined&&r.one("input[name=ip_addresses]").set("value",t.ip_addresses),M.availability_ipaddress.form.addedEvents||(M.availability_ipaddress.form.addedEvents=!0,i=e.one(".availability-field"),i.delegate("valuechange",function(){M.core_availability.form.update()},".availability_ipaddress input[name=ip_addresses]")),r},M.availability_ipaddress.validate_ipaddress=function(e){"use strict";e=e.split(",");for(var t in e){if((new RegExp("(?:".concat(M.availability_ipaddress.v4,")|(?:").concat(M.availability_ipaddress.v6,")"),"g")).test(e[t]))continue;if((new RegExp("(?:".concat(M.availability_ipaddress.v4+"\\/(3[0-2]|[12]?[0-9])",")|(?:").concat(M.availability_ipaddress.v6+"\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])",")"),"g")).test(e[t]))continue;return console.error("Incorrect ip",e[t]),!1}return console.log("Valid ipaddresses",e),!0},M.availability_ipaddress.form.fillValue=function(e,t){e.ip_addresses=this.getValue("ip_addresses",t)},M.availability_ipaddress.form.fillErrors=function(e,t){"use strict";var n={};this.fillValue(n,t),console.log("ip_address:",n),M.availability_ipaddress.validate_ipaddress(n.ip_addresses)===!1&&e.push("availability_ipaddress:error_ipaddress")}},"@VERSION@",{requires:["base","node","event","moodle-core_availability-form"]}); diff --git a/yui/build/moodle-availability_ipaddress-form/moodle-availability_ipaddress-form.js b/yui/build/moodle-availability_ipaddress-form/moodle-availability_ipaddress-form.js new file mode 100644 index 0000000..655a15e --- /dev/null +++ b/yui/build/moodle-availability_ipaddress-form/moodle-availability_ipaddress-form.js @@ -0,0 +1,168 @@ +YUI.add('moodle-availability_ipaddress-form', function (Y, NAME) { + +/** + * Availability ip-address YUI code + * + * @package availability_ipaddress + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright 2019-05-14 Mfreak.nl | LdesignMedia.nl - Luuk Verhoeven + */ + +/** + * JavaScript for form editing grade conditions. + * + * @module moodle-availability_ipaddress-form + */ +M.availability_ipaddress = M.availability_ipaddress || {}; + +// MIT https://github.com/sindresorhus/ip-regex +// Advanced ip-address regex for validating. +M.availability_ipaddress.v4 = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]' + + '[0-9]|[1-9][0-9]|[0-9])){3}'; +M.availability_ipaddress.v6 = '((?:[0-9a-fA-F]{1,4}:){7}(?:[0-9a-fA-F]{1,4}|:)|(?:[0-9a-fA-F]{1,4}:){6}(?:(?:25[0-5]|2[0-4]' + + '[0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|:[0-9a-fA-F]{1,4}|:)' + + '|(?:[0-9a-fA-F]{1,4}:){5}(?::(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]' + + '|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,2}|:)|(?:[0-9a-fA-F]{1,4}:){4}(?:' + + '(:[0-9a-fA-F]{1,4}){0,1}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]' + + '|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,3}|:)|(?:[0-9a-fA-F]{1,4}:){3}(?:(:[0-9a-fA-F]{1,4}){0,2}:(?:25[0-5]|2[0-4]' + + '[0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|' + + '(:[0-9a-fA-F]{1,4}){1,4}|:)|(?:[0-9a-fA-F]{1,4}:){2}(?:(:[0-9a-fA-F]{1,4}){0,3}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|' + + '[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,5}|:)|' + + '(?:[0-9a-fA-F]{1,4}:){1}(?:(:[0-9a-fA-F]{1,4}){0,4}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.' + + '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,6}|:)|(?::((?::[0-9a-fA-F]{1,4}){0,5}' + + ':(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|' + + '(?::[0-9a-fA-F]{1,4}){1,7}|:)))(%[0-9a-zA-Z]{1,})?'; + +console.log(M.availability_ipaddress.v6); +/** + * @class M.availability_ipaddress.form + * @extends M.core_availability.plugin + */ +M.availability_ipaddress.form = Y.Object(M.core_availability.plugin); + +/** + * Initialises this plugin. + * + * @method initInner + * @param {Array} param Array of objects + */ +M.availability_ipaddress.form.initInner = function(params) { + "use strict"; + console.log('M.availability_ipaddress'); +}; + +/** + * Gets the numeric value of an input field. Supports decimal points (using + * dot or comma). + * + * @method getValue + * @return {Number|String} Value of field as number or string if not valid + */ +M.availability_ipaddress.form.getValue = function(field, node) { + "use strict"; + // Get field value. + var value = node.one('input[name=' + field + ']').get('value'); + + // If it is not a valid positive number, return false. + if (M.availability_ipaddress.validate_ipaddress(value)) { + console.log('Valid ip-address'); + return value; + } + + console.log('getValue failed:', value); + return value; +}; + +/** + * getNode + * @param json + * @returns {*} + */ +M.availability_ipaddress.form.getNode = function(json) { + "use strict"; + var html, node, root, id; + + // Make sure we work with unique id. + id = 'ip_addresses' + M.availability_ipaddress.form.instId; + M.availability_ipaddress.form.instId += 1; + + // Create HTML structure. + html = ''; + html += ''; + html += ''; + node = Y.Node.create('' + html + ''); + + // Set initial values, if specified. + if (json.ip_addresses !== undefined) { + node.one('input[name=ip_addresses]').set('value', json.ip_addresses); + } + + // Add event handlers (first time only). + if (!M.availability_ipaddress.form.addedEvents) { + M.availability_ipaddress.form.addedEvents = true; + root = Y.one('.availability-field'); + root.delegate('valuechange', function() { + // Trigger the updating of the hidden availability data whenever the ipaddress field changes. + M.core_availability.form.update(); + }, '.availability_ipaddress input[name=ip_addresses]'); + } + + return node; +}; + +/** + * validate_ipaddress + * + * @param {string[]} ipaddresses + * @returns {boolean} + */ +M.availability_ipaddress.validate_ipaddress = function(ipaddresses) { + 'use strict'; + + ipaddresses = ipaddresses.split(','); + for (var i in ipaddresses) { + + // Test normal ip format. + if (new RegExp("(?:".concat(M.availability_ipaddress.v4, ")|(?:").concat(M.availability_ipaddress.v6, ")"), "g") + .test(ipaddresses[i])) { + continue; + } + + // Test subnet with a regex. + if (new RegExp("(?:".concat(M.availability_ipaddress.v4 + "\\/(3[0-2]|[12]?[0-9])", ")|(?:") + .concat(M.availability_ipaddress.v6 + "\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])", ")"), "g") + .test(ipaddresses[i])) { + continue; + } + + console.error('Incorrect ip', ipaddresses[i]); + return false; + } + + console.log('Valid ipaddresses', ipaddresses); + return true; +}; + +M.availability_ipaddress.form.fillValue = function(value, node) { + // This function gets passed the node (from above) and a value + // object. Within that object, it must set up the correct values + // to use within the JSON data in the form. Should be compatible + // with the structure used in the __construct and save functions + // within condition.php. + value.ip_addresses = this.getValue('ip_addresses', node); +}; + +M.availability_ipaddress.form.fillErrors = function(errors, node) { + "use strict"; + var value = {}; + this.fillValue(value, node); + console.log('ip_address:', value); + + // Basic ip_addresses checks. + if (M.availability_ipaddress.validate_ipaddress(value.ip_addresses) === false) { + errors.push('availability_ipaddress:error_ipaddress'); + } +}; + +}, '@VERSION@', {"requires": ["base", "node", "event", "moodle-core_availability-form"]}); diff --git a/yui/src/form/js/form.js b/yui/src/form/js/form.js index e69de29..12378e7 100644 --- a/yui/src/form/js/form.js +++ b/yui/src/form/js/form.js @@ -0,0 +1,164 @@ +/** + * Availability ip-address YUI code + * + * @package availability_ipaddress + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright 2019-05-14 Mfreak.nl | LdesignMedia.nl - Luuk Verhoeven + */ + +/** + * JavaScript for form editing grade conditions. + * + * @module moodle-availability_ipaddress-form + */ +M.availability_ipaddress = M.availability_ipaddress || {}; + +// MIT https://github.com/sindresorhus/ip-regex +// Advanced ip-address regex for validating. +M.availability_ipaddress.v4 = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9]' + + '[0-9]|[1-9][0-9]|[0-9])){3}'; +M.availability_ipaddress.v6 = '((?:[0-9a-fA-F]{1,4}:){7}(?:[0-9a-fA-F]{1,4}|:)|(?:[0-9a-fA-F]{1,4}:){6}(?:(?:25[0-5]|2[0-4]' + + '[0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|:[0-9a-fA-F]{1,4}|:)' + + '|(?:[0-9a-fA-F]{1,4}:){5}(?::(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]' + + '|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,2}|:)|(?:[0-9a-fA-F]{1,4}:){4}(?:' + + '(:[0-9a-fA-F]{1,4}){0,1}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]' + + '|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,3}|:)|(?:[0-9a-fA-F]{1,4}:){3}(?:(:[0-9a-fA-F]{1,4}){0,2}:(?:25[0-5]|2[0-4]' + + '[0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|' + + '(:[0-9a-fA-F]{1,4}){1,4}|:)|(?:[0-9a-fA-F]{1,4}:){2}(?:(:[0-9a-fA-F]{1,4}){0,3}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|' + + '[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,5}|:)|' + + '(?:[0-9a-fA-F]{1,4}:){1}(?:(:[0-9a-fA-F]{1,4}){0,4}:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.' + + '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(:[0-9a-fA-F]{1,4}){1,6}|:)|(?::((?::[0-9a-fA-F]{1,4}){0,5}' + + ':(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|' + + '(?::[0-9a-fA-F]{1,4}){1,7}|:)))(%[0-9a-zA-Z]{1,})?'; + +console.log(M.availability_ipaddress.v6); +/** + * @class M.availability_ipaddress.form + * @extends M.core_availability.plugin + */ +M.availability_ipaddress.form = Y.Object(M.core_availability.plugin); + +/** + * Initialises this plugin. + * + * @method initInner + * @param {Array} param Array of objects + */ +M.availability_ipaddress.form.initInner = function(params) { + "use strict"; + console.log('M.availability_ipaddress'); +}; + +/** + * Gets the numeric value of an input field. Supports decimal points (using + * dot or comma). + * + * @method getValue + * @return {Number|String} Value of field as number or string if not valid + */ +M.availability_ipaddress.form.getValue = function(field, node) { + "use strict"; + // Get field value. + var value = node.one('input[name=' + field + ']').get('value'); + + // If it is not a valid positive number, return false. + if (M.availability_ipaddress.validate_ipaddress(value)) { + console.log('Valid ip-address'); + return value; + } + + console.log('getValue failed:', value); + return value; +}; + +/** + * getNode + * @param json + * @returns {*} + */ +M.availability_ipaddress.form.getNode = function(json) { + "use strict"; + var html, node, root, id; + + // Make sure we work with unique id. + id = 'ip_addresses' + M.availability_ipaddress.form.instId; + M.availability_ipaddress.form.instId += 1; + + // Create HTML structure. + html = ''; + html += ''; + html += ''; + node = Y.Node.create('' + html + ''); + + // Set initial values, if specified. + if (json.ip_addresses !== undefined) { + node.one('input[name=ip_addresses]').set('value', json.ip_addresses); + } + + // Add event handlers (first time only). + if (!M.availability_ipaddress.form.addedEvents) { + M.availability_ipaddress.form.addedEvents = true; + root = Y.one('.availability-field'); + root.delegate('valuechange', function() { + // Trigger the updating of the hidden availability data whenever the ipaddress field changes. + M.core_availability.form.update(); + }, '.availability_ipaddress input[name=ip_addresses]'); + } + + return node; +}; + +/** + * validate_ipaddress + * + * @param {string[]} ipaddresses + * @returns {boolean} + */ +M.availability_ipaddress.validate_ipaddress = function(ipaddresses) { + 'use strict'; + + ipaddresses = ipaddresses.split(','); + for (var i in ipaddresses) { + + // Test normal ip format. + if (new RegExp("(?:".concat(M.availability_ipaddress.v4, ")|(?:").concat(M.availability_ipaddress.v6, ")"), "g") + .test(ipaddresses[i])) { + continue; + } + + // Test subnet with a regex. + if (new RegExp("(?:".concat(M.availability_ipaddress.v4 + "\\/(3[0-2]|[12]?[0-9])", ")|(?:") + .concat(M.availability_ipaddress.v6 + "\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])", ")"), "g") + .test(ipaddresses[i])) { + continue; + } + + console.error('Incorrect ip', ipaddresses[i]); + return false; + } + + console.log('Valid ipaddresses', ipaddresses); + return true; +}; + +M.availability_ipaddress.form.fillValue = function(value, node) { + // This function gets passed the node (from above) and a value + // object. Within that object, it must set up the correct values + // to use within the JSON data in the form. Should be compatible + // with the structure used in the __construct and save functions + // within condition.php. + value.ip_addresses = this.getValue('ip_addresses', node); +}; + +M.availability_ipaddress.form.fillErrors = function(errors, node) { + "use strict"; + var value = {}; + this.fillValue(value, node); + console.log('ip_address:', value); + + // Basic ip_addresses checks. + if (M.availability_ipaddress.validate_ipaddress(value.ip_addresses) === false) { + errors.push('availability_ipaddress:error_ipaddress'); + } +}; \ No newline at end of file