mirror of
https://github.com/LdesignMedia/moodle-availability_ipaddress.git
synced 2026-10-22 19:18:06 +02:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06a5f9c8c9 | ||
|
|
150067f43d | ||
|
|
b1f0bc2f09 | ||
|
|
9269cb7f76 | ||
|
|
2210e85ca0 | ||
|
|
6b24f4a2b9 | ||
|
|
00941bbc54 | ||
|
|
cf5409707f | ||
|
|
d730ed97fd | ||
|
|
474f91ccbc | ||
|
|
d65fd40621 | ||
|
|
4632008dfb | ||
|
|
c91646df81 | ||
|
|
d2c231e657 | ||
|
|
700820480d | ||
|
|
e91befdf10 | ||
|
|
3f5f4ee6cb | ||
|
|
cccebb6c15 | ||
|
|
af275d1699 | ||
|
|
5dce3ed488 | ||
|
|
e74ed2ae69 | ||
|
|
15ba2b295b | ||
|
|
fe3a5b2302 | ||
|
|
26669e73f1 | ||
|
|
e7e5cbe91d |
No files matched your search
+1
-1
@@ -1 +1 @@
|
|||||||
github: [MFreakNL]
|
github: [LdesignMedia]
|
||||||
Whitespace-only changes.
@@ -0,0 +1,48 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 5.3.2 (2026101500)
|
||||||
|
- Fixed IP address validation being too permissive https://github.com/LdesignMedia/moodle-availability_ipaddress/issues/2
|
||||||
|
- Invalid entries like `2001:db8::1garbage` or `192.168.1.0/24garbage` are no longer accepted. Moodle silently ignored them, so a "must not" restriction could let blocked users in.
|
||||||
|
- Only formats Moodle can match are accepted. Rejected: IPv6 written with an IPv4 part (`::ffff:192.168.1.1`), an IPv6 prefix without mask (`2001:db8::`, use `2001:db8::/32`), masks with leading zeros (`/024`) and some compressed IPv6 forms Moodle can't parse (`1::2:3:4:5:6:7`).
|
||||||
|
- Predefined IP ranges are now validated with the same rules (subnet mask range, range end).
|
||||||
|
- Spaces after commas are now allowed in the restriction field, e.g. `192.168.1.1, 10.0.0.1`.
|
||||||
|
- Added support for IPv6 ranges in the last group, e.g. `2001:db8::1-ff`.
|
||||||
|
- Note: existing restrictions and predefined ranges are not changed. If one contains an invalid entry, it now shows a validation error the next time it is edited. Entries that were already invalid never matched.
|
||||||
|
|
||||||
|
## 5.3.1 (2026101400)
|
||||||
|
- Solved CI tests https://github.com/LdesignMedia/moodle-availability_ipaddress/issues/20 | Thanks @minhduchoang195
|
||||||
|
|
||||||
|
## 5.3.0 (2026092300)
|
||||||
|
- Tested and verified on Moodle 5.3
|
||||||
|
|
||||||
|
## 5.2.0 (2026031802)
|
||||||
|
- Tested and verified on Moodle 5.2
|
||||||
|
|
||||||
|
## 5.1.1 (2026031800)
|
||||||
|
* Fixed PHP 7.3 compatibility issue that broke Moodle 4.0.x servers ([#13](https://github.com/LdesignMedia/moodle-availability_ipaddress/issues/13))
|
||||||
|
* Removed typed properties (PHP 7.4+) to support PHP 7.3+
|
||||||
|
* Lowered environment.xml PHP requirement from 7.4 to 7.3
|
||||||
|
|
||||||
|
## 5.1.0 (2026030300)
|
||||||
|
- Tested and verified on Moodle 5.1
|
||||||
|
|
||||||
|
## 5.0.2 (2025080401)
|
||||||
|
- Added support for pre-configuring IP ranges by admins
|
||||||
|
|
||||||
|
## 5.0.1 (2025052200)
|
||||||
|
- Tested on Moodle 5.0
|
||||||
|
|
||||||
|
## 4.5.0 (2025040400)
|
||||||
|
- Tested on Moodle 4.5
|
||||||
|
|
||||||
|
## 4.4.0 (2024072000)
|
||||||
|
- Tested on Moodle 4.4
|
||||||
|
|
||||||
|
## 4.0.3 (2022052801)
|
||||||
|
- Test in Moodle 4.0 @[hamzatamyachte](https://github.com/hamzatamyachte)
|
||||||
|
|
||||||
|
## 4.0.2 (2022052800)
|
||||||
|
- Fixed the [issue 6](https://github.com/ldesignmediaNL/moodle-availability_ipaddress/issues/6) @[hamzatamyachte](https://github.com/hamzatamyachte)
|
||||||
|
|
||||||
|
## 3.9.0 (2022021100)
|
||||||
|
- Thanks for adding ip-range support @[juacas](https://github.com/juacas)
|
||||||
@@ -1,30 +1,40 @@
|
|||||||
## Moodle - availability ip address plugin
|
## Moodle - availability ip address plugin
|
||||||
|
|
||||||
|
[](https://github.com/LdesignMedia/moodle-availability_ipaddress/actions/workflows/ci.yml)
|
||||||
|
|
||||||
Enhance activity security by restricting access based on IP address. This plugin allows you to control the availability of any chosen
|
Enhance activity security by restricting access based on IP address. This plugin allows you to control the availability of any chosen
|
||||||
activity, making it accessible only to users from specified IP addresses.
|
activity, making it accessible only to users from specified IP addresses.
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||

|
<img src="https://ldesignmedia.nl/themes/ldesignmedia/assets/images/logo/logo.svg" alt="ldesignmedia" height="70px">
|
||||||
|
|
||||||
* Author: Luuk Verhoeven, [ldesignmedia.nl](https://ldesignmedia.nl/)
|
* Author: Luuk Verhoeven, [ldesignmedia.nl](https://ldesignmedia.nl/)
|
||||||
|
* Author: Vincent Cornelis, [ldesignmedia.nl](https://ldesignmedia.nl/)
|
||||||
* Min. required: Moodle 4.0
|
* Min. required: Moodle 4.0
|
||||||
* Supports PHP: 7.4
|
* Supports PHP: 7.3+
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## List of features
|
## List of features
|
||||||
- Supports comma separate list of ip-addresses
|
- Supports comma separate list of ip-addresses
|
||||||
- Subnet support, eg 192.168.1.0/24
|
- Subnet support, eg 192.168.1.0/24
|
||||||
- Inline ip-address validation
|
- Inline ip-address validation
|
||||||
- Turning on/off with eye icon, without lossing the input value.
|
- Turning on/off with eye icon, without losing the input value.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
1. Copy this plugin to the `availability\condition\ipaddress` folder on the server
|
1. Copy this plugin to the `availability\condition\ipaddress` folder on the server
|
||||||
@@ -41,9 +51,6 @@ activity, making it accessible only to users from specified IP addresses.
|
|||||||
1. The users with matching ip addresses can view the activity.
|
1. The users with matching ip addresses can view the activity.
|
||||||
5. Save the activity
|
5. Save the activity
|
||||||
|
|
||||||
## TODO
|
|
||||||
- Behat tests ip validation
|
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
If you discover any security related issues, please email [[email protected]](mailto:[email protected]) instead of using the issue tracker.
|
If you discover any security related issues, please email [[email protected]](mailto:[email protected]) instead of using the issue tracker.
|
||||||
@@ -56,10 +63,6 @@ The GNU GENERAL PUBLIC LICENSE. Please see [License File](LICENSE) for more info
|
|||||||
|
|
||||||
Contributions are welcome and will be fully credited. We accept contributions via Pull Requests on Github.
|
Contributions are welcome and will be fully credited. We accept contributions via Pull Requests on Github.
|
||||||
|
|
||||||
## Changelog
|
### Changelog
|
||||||
|
|
||||||
- 2025040400 Tested on Moodle 4.5
|
See [CHANGELOG.md](CHANGELOG.md) for a full list of changes.
|
||||||
- 2024072000 Tested on Moodle 4.4
|
|
||||||
- 2022021100 Thanks for adding ip-range support @[juacas](https://github.com/juacas)
|
|
||||||
- 2022052800 Fixed the [issue 6](https://github.com/ldesignmediaNL/moodle-availability_ipaddress/issues/6) @[hamzatamyachte](https://github.com/hamzatamyachte)
|
|
||||||
- 2022052801 Test in Moodle 4.0 @[hamzatamyachte](https://github.com/hamzatamyachte)
|
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
At `Ldesign Media`, we take the security of our software very seriously. This policy outlines the steps we take to ensure the security of our software and how you can help us keep it secure.
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
To ensure the best security for our users, we only provide support for the latest version of our software.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you discover a security vulnerability in our software, we kindly ask that you report it to us as soon as possible by sending an email to `[email protected]`. Please do not use the issue tracker for reporting security vulnerabilities.
|
||||||
|
We will make every effort to investigate and resolve the vulnerability in a timely manner.
|
||||||
|
|
||||||
|
When reporting a vulnerability, please include the following information (as much as you can provide) to help us better understand the nature and scope of the issue:
|
||||||
|
|
||||||
|
- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||||
|
- Full paths of source file(s) related to the manifestation of the issue
|
||||||
|
- The location of the affected source code (tag/branch/commit or direct URL)
|
||||||
|
- Any special configuration required to reproduce the issue
|
||||||
|
- Step-by-step instructions to reproduce the issue
|
||||||
|
- Proof-of-concept or exploit code (if possible)
|
||||||
|
- Impact of the issue, including how an attacker might exploit the issue
|
||||||
|
|
||||||
|
This information will help us triage your report more quickly.
|
||||||
|
|
||||||
|
## Rewards
|
||||||
|
|
||||||
|
We do not currently have a bug bounty program, but we are grateful for any responsible disclosures of security vulnerabilities. Your efforts help make our software safer for everyone.
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
When a security vulnerability has been discovered and addressed, we will provide updates to the community as soon as possible to keep everyone informed.
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
We would like to extend our gratitude to the security researchers who have helped us improve the security of our software by responsibly reporting security vulnerabilities to us. Your contributions are invaluable to maintaining a safe and secure environment for our users.
|
||||||
+65
-10
@@ -36,27 +36,42 @@ use core_availability\info;
|
|||||||
* @copyright 2019-05-14 Mfreak.nl | LdesignMedia.nl - Luuk Verhoeven
|
* @copyright 2019-05-14 Mfreak.nl | LdesignMedia.nl - Luuk Verhoeven
|
||||||
*/
|
*/
|
||||||
class condition extends \core_availability\condition {
|
class condition extends \core_availability\condition {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Manual provided IP addresses.
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected string $ipaddresses = '';
|
protected $ipaddresses = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Predefined IP address ranges.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $predefinedranges = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* condition constructor.
|
* condition constructor.
|
||||||
*
|
*
|
||||||
* @param \stdClass $structure
|
* @param \stdClass $structure
|
||||||
*/
|
*/
|
||||||
public function __construct($structure) {
|
public function __construct(\stdClass $structure) {
|
||||||
if (isset($structure->ipaddresses)) {
|
if (isset($structure->ipaddresses)) {
|
||||||
$this->ipaddresses = $structure->ipaddresses;
|
$this->ipaddresses = $structure->ipaddresses;
|
||||||
}
|
}
|
||||||
|
if (isset($structure->predefined_ranges)) {
|
||||||
|
$this->predefinedranges = $structure->predefined_ranges;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether a particular item is currently available
|
* Determines whether a particular item is currently available
|
||||||
* according to this availability condition.
|
* according to this availability condition.
|
||||||
*
|
*
|
||||||
|
* Note: Cannot add type declarations for $not, $grabthelot, and $userid parameters
|
||||||
|
* as the parent core_availability\condition::is_available() method doesn't have them,
|
||||||
|
* and PHP requires compatibility with parent method signatures when overriding.
|
||||||
|
*
|
||||||
* If implementations require a course or modinfo, they should use
|
* If implementations require a course or modinfo, they should use
|
||||||
* the get methods in $info.
|
* the get methods in $info.
|
||||||
*
|
*
|
||||||
@@ -78,14 +93,43 @@ class condition extends \core_availability\condition {
|
|||||||
* @return bool True if available
|
* @return bool True if available
|
||||||
*/
|
*/
|
||||||
public function is_available($not, info $info, $grabthelot, $userid): bool {
|
public function is_available($not, info $info, $grabthelot, $userid): bool {
|
||||||
|
global $DB;
|
||||||
|
|
||||||
if (empty($this->ipaddresses)) {
|
// Collect all IP addresses to check.
|
||||||
|
$allipaddresses = [];
|
||||||
|
|
||||||
|
// Add custom IP addresses.
|
||||||
|
if (!empty($this->ipaddresses)) {
|
||||||
|
$allipaddresses[] = trim($this->ipaddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add predefined ranges.
|
||||||
|
if (!empty($this->predefinedranges)) {
|
||||||
|
$ranges = $DB->get_records_list(
|
||||||
|
'availability_ipaddress_pre',
|
||||||
|
'id',
|
||||||
|
$this->predefinedranges,
|
||||||
|
'',
|
||||||
|
'ipaddresses'
|
||||||
|
);
|
||||||
|
foreach ($ranges as $range) {
|
||||||
|
if (!empty($range->ipaddresses)) {
|
||||||
|
$allipaddresses[] = trim($range->ipaddresses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no IP addresses are configured, the condition passes.
|
||||||
|
if (empty($allipaddresses)) {
|
||||||
return !$not;
|
return !$not;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if ip-address matches.
|
// Check if user's IP matches any of the allowed addresses.
|
||||||
if (address_in_subnet(getremoteaddr(), trim($this->ipaddresses))) {
|
$userip = getremoteaddr();
|
||||||
return !$not;
|
foreach ($allipaddresses as $iplist) {
|
||||||
|
if (address_in_subnet($userip, $iplist)) {
|
||||||
|
return !$not;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $not;
|
return $not;
|
||||||
@@ -97,6 +141,9 @@ class condition extends \core_availability\condition {
|
|||||||
* students if the activity is not available to them, and for staff to see
|
* students if the activity is not available to them, and for staff to see
|
||||||
* what conditions are.
|
* what conditions are.
|
||||||
*
|
*
|
||||||
|
* Note: Cannot add type declarations for $full and $not parameters as the parent
|
||||||
|
* core_availability\condition::get_description() method doesn't have them.
|
||||||
|
*
|
||||||
* The $full parameter can be used to distinguish between 'staff' cases
|
* The $full parameter can be used to distinguish between 'staff' cases
|
||||||
* (when displaying all information about the activity) and 'student' cases
|
* (when displaying all information about the activity) and 'student' cases
|
||||||
* (when displaying only conditions they don't meet).
|
* (when displaying only conditions they don't meet).
|
||||||
@@ -115,7 +162,10 @@ class condition extends \core_availability\condition {
|
|||||||
* @return string Information string (for admin) about all restrictions on this item
|
* @return string Information string (for admin) about all restrictions on this item
|
||||||
*/
|
*/
|
||||||
public function get_description($full, $not, info $info): string {
|
public function get_description($full, $not, info $info): string {
|
||||||
return get_string('require_condition', 'availability_ipaddress', getremoteaddr());
|
|
||||||
|
$desc = $not ? 'require_condition_not' : 'require_condition';
|
||||||
|
|
||||||
|
return get_string($desc, 'availability_ipaddress', getremoteaddr());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,10 +201,15 @@ class condition extends \core_availability\condition {
|
|||||||
* @return \stdClass Structure object (ready to be made into JSON format)
|
* @return \stdClass Structure object (ready to be made into JSON format)
|
||||||
*/
|
*/
|
||||||
public function save(): \stdClass {
|
public function save(): \stdClass {
|
||||||
return (object) [
|
$result = (object) [
|
||||||
'type' => 'ipaddress',
|
'type' => 'ipaddress',
|
||||||
'ipaddresses' => $this->ipaddresses,
|
'ipaddresses' => $this->ipaddresses,
|
||||||
];
|
];
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!empty($this->predefinedranges)) {
|
||||||
|
$result->predefined_ranges = $this->predefinedranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form for managing predefined IP ranges.
|
||||||
|
*
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 04/08/2025 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @author Vincent Cornelis
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace availability_ipaddress\form;
|
||||||
|
|
||||||
|
use availability_ipaddress\helper;
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
global $CFG;
|
||||||
|
require_once($CFG->libdir . '/formslib.php');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form for managing predefined IP ranges.
|
||||||
|
*
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 04/08/2025 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @author Vincent Cornelis
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class range_form extends \moodleform {
|
||||||
|
/**
|
||||||
|
* Define the form.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function definition(): void {
|
||||||
|
$mform = $this->_form;
|
||||||
|
$id = $this->_customdata['id'] ?? 0;
|
||||||
|
|
||||||
|
// Hidden id field.
|
||||||
|
$mform->addElement('hidden', 'id');
|
||||||
|
$mform->setType('id', PARAM_INT);
|
||||||
|
$mform->setDefault('id', $id);
|
||||||
|
|
||||||
|
// Name field.
|
||||||
|
$mform->addElement('text', 'name', get_string('range_name', 'availability_ipaddress'));
|
||||||
|
$mform->setType('name', PARAM_TEXT);
|
||||||
|
$mform->addRule('name', null, 'required', null, 'client');
|
||||||
|
$mform->addHelpButton('name', 'range_name', 'availability_ipaddress');
|
||||||
|
|
||||||
|
// IP addresses field.
|
||||||
|
$mform->addElement(
|
||||||
|
'text',
|
||||||
|
'ipaddresses',
|
||||||
|
get_string('ipaddresses', 'availability_ipaddress'),
|
||||||
|
['size' => 100]
|
||||||
|
);
|
||||||
|
$mform->setType('ipaddresses', PARAM_TEXT);
|
||||||
|
$mform->addRule('ipaddresses', null, 'required', null, 'client');
|
||||||
|
$mform->addHelpButton('ipaddresses', 'ipaddresses_help', 'availability_ipaddress');
|
||||||
|
|
||||||
|
// Description field.
|
||||||
|
$mform->addElement(
|
||||||
|
'textarea',
|
||||||
|
'description',
|
||||||
|
get_string('description'),
|
||||||
|
['rows' => 3, 'cols' => 60]
|
||||||
|
);
|
||||||
|
$mform->setType('description', PARAM_TEXT);
|
||||||
|
|
||||||
|
// Enabled field.
|
||||||
|
$mform->addElement('advcheckbox', 'enabled', get_string('enabled', 'availability_ipaddress'));
|
||||||
|
$mform->setDefault('enabled', 1);
|
||||||
|
|
||||||
|
// Action buttons.
|
||||||
|
$this->add_action_buttons();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the form data.
|
||||||
|
*
|
||||||
|
* Note: Parameter type declarations cannot be added here as the parent
|
||||||
|
* moodleform::validation() method doesn't have them, and PHP requires
|
||||||
|
* compatibility with parent method signatures when overriding.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @param array $files
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validation($data, $files) {
|
||||||
|
$errors = parent::validation($data, $files);
|
||||||
|
|
||||||
|
// Validate IP addresses, the field is required so an empty value is invalid as well.
|
||||||
|
if (!helper::is_valid_ipaddress_list($data['ipaddresses'] ?? '')) {
|
||||||
|
$errors['ipaddresses'] = get_string('error_ipaddress', 'availability_ipaddress');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
-1
@@ -34,7 +34,6 @@ namespace availability_ipaddress;
|
|||||||
* @copyright 2019-05-14 Mfreak.nl | LdesignMedia.nl - Luuk Verhoeven
|
* @copyright 2019-05-14 Mfreak.nl | LdesignMedia.nl - Luuk Verhoeven
|
||||||
*/
|
*/
|
||||||
class frontend extends \core_availability\frontend {
|
class frontend extends \core_availability\frontend {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get_javascript_strings
|
* get_javascript_strings
|
||||||
*
|
*
|
||||||
@@ -44,7 +43,41 @@ class frontend extends \core_availability\frontend {
|
|||||||
return [
|
return [
|
||||||
'js:ipaddress',
|
'js:ipaddress',
|
||||||
'error_ipaddress',
|
'error_ipaddress',
|
||||||
|
'predefined_ranges',
|
||||||
|
'custom_ipaddress',
|
||||||
|
'use_predefined',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get additional parameters for the JavaScript module.
|
||||||
|
*
|
||||||
|
* Note: Cannot add type declaration for $course parameter as the parent
|
||||||
|
* core_availability\frontend::get_javascript_init_params() method doesn't
|
||||||
|
* have it, and PHP requires compatibility with parent method signatures.
|
||||||
|
*
|
||||||
|
* @param \stdClass $course Course object
|
||||||
|
* @param \cm_info|null $cm Course module
|
||||||
|
* @param \section_info|null $section Section
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_javascript_init_params($course, ?\cm_info $cm = null, ?\section_info $section = null): array {
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
// Get enabled predefined IP ranges.
|
||||||
|
$ranges = $DB->get_records('availability_ipaddress_pre', ['enabled' => 1], 'name', 'id, name, ipaddresses');
|
||||||
|
|
||||||
|
// Format for JavaScript.
|
||||||
|
$rangedata = [];
|
||||||
|
foreach ($ranges as $range) {
|
||||||
|
$rangedata[] = [
|
||||||
|
'id' => $range->id,
|
||||||
|
'name' => format_string($range->name),
|
||||||
|
'ipaddresses' => $range->ipaddresses,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$rangedata];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions for availability_ipaddress.
|
||||||
|
*
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 04/08/2025 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @author Vincent Cornelis
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace availability_ipaddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for availability_ipaddress.
|
||||||
|
*
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 04/08/2025 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @author Vincent Cornelis
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class helper {
|
||||||
|
/**
|
||||||
|
* Check if a predefined range is in use.
|
||||||
|
*
|
||||||
|
* @param int $rangeid The ID of the range to check.
|
||||||
|
*
|
||||||
|
* @return array Array with 'inuse' boolean and 'count' of uses.
|
||||||
|
*/
|
||||||
|
public static function is_range_in_use(int $rangeid): array {
|
||||||
|
$activities = [];
|
||||||
|
|
||||||
|
// Check course modules.
|
||||||
|
$moduleactivities = self::check_range_in_modules($rangeid);
|
||||||
|
$activities = array_merge($activities, $moduleactivities);
|
||||||
|
|
||||||
|
// Check sections.
|
||||||
|
$sectionactivities = self::check_range_in_sections($rangeid);
|
||||||
|
$activities = array_merge($activities, $sectionactivities);
|
||||||
|
|
||||||
|
$count = count($activities);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'inuse' => ($count > 0),
|
||||||
|
'count' => $count,
|
||||||
|
'activities' => $activities,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a range is used in course modules.
|
||||||
|
*
|
||||||
|
* @param int $rangeid The ID of the range to check.
|
||||||
|
*
|
||||||
|
* @return array Array of activities using the range.
|
||||||
|
*/
|
||||||
|
private static function check_range_in_modules(int $rangeid): array {
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
$activities = [];
|
||||||
|
|
||||||
|
$sql = "SELECT cm.id, cm.course, cm.availability, cm.module, cm.instance,
|
||||||
|
c.fullname as coursename, m.name as modname
|
||||||
|
FROM {course_modules} cm
|
||||||
|
JOIN {course} c ON c.id = cm.course
|
||||||
|
JOIN {modules} m ON m.id = cm.module
|
||||||
|
WHERE cm.availability IS NOT NULL AND cm.availability != ''";
|
||||||
|
|
||||||
|
$modules = $DB->get_records_sql($sql);
|
||||||
|
|
||||||
|
foreach ($modules as $module) {
|
||||||
|
$availability = json_decode($module->availability);
|
||||||
|
if (!$availability || !self::check_availability_tree($availability, $rangeid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$activityname = self::get_module_name($module);
|
||||||
|
$activities[] = [
|
||||||
|
'coursename' => $module->coursename,
|
||||||
|
'cmid' => $module->id,
|
||||||
|
'name' => $activityname,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of a module.
|
||||||
|
*
|
||||||
|
* @param \stdClass $module The module record.
|
||||||
|
*
|
||||||
|
* @return string The module name.
|
||||||
|
*/
|
||||||
|
private static function get_module_name(\stdClass $module): string {
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($DB->get_manager()->table_exists($module->modname)) {
|
||||||
|
$activity = $DB->get_record($module->modname, ['id' => $module->instance], 'name', IGNORE_MISSING);
|
||||||
|
if ($activity && !empty($activity->name)) {
|
||||||
|
return $activity->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Table doesn't exist or other database error - fall through to default.
|
||||||
|
debugging('Error getting module name: ' . $e->getMessage(), DEBUG_DEVELOPER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_string('modulename', $module->modname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a range is used in course sections.
|
||||||
|
*
|
||||||
|
* @param int $rangeid The ID of the range to check.
|
||||||
|
*
|
||||||
|
* @return array Array of sections using the range.
|
||||||
|
*/
|
||||||
|
private static function check_range_in_sections(int $rangeid): array {
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
$activities = [];
|
||||||
|
|
||||||
|
$sql = "SELECT cs.id, cs.course, cs.availability, cs.name, cs.section, c.fullname as coursename
|
||||||
|
FROM {course_sections} cs
|
||||||
|
JOIN {course} c ON c.id = cs.course
|
||||||
|
WHERE cs.availability IS NOT NULL AND cs.availability != ''";
|
||||||
|
|
||||||
|
$sections = $DB->get_records_sql($sql);
|
||||||
|
|
||||||
|
foreach ($sections as $section) {
|
||||||
|
$availability = json_decode($section->availability);
|
||||||
|
if (!$availability || !self::check_availability_tree($availability, $rangeid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sectionname = $section->name ?: get_string('section') . ' ' . $section->section;
|
||||||
|
$activities[] = [
|
||||||
|
'coursename' => $section->coursename,
|
||||||
|
'cmid' => 0,
|
||||||
|
'name' => $sectionname,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively check availability tree for range usage.
|
||||||
|
*
|
||||||
|
* @param \stdClass $availability The availability tree.
|
||||||
|
* @param int $rangeid The range ID to look for.
|
||||||
|
*
|
||||||
|
* @return bool True if range is found in tree.
|
||||||
|
*/
|
||||||
|
private static function check_availability_tree(\stdClass $availability, int $rangeid): bool {
|
||||||
|
|
||||||
|
// Check if this is an IP address condition.
|
||||||
|
if (isset($availability->type) && $availability->type === 'ipaddress') {
|
||||||
|
if (isset($availability->predefined_ranges) && is_array($availability->predefined_ranges)) {
|
||||||
|
return in_array($rangeid, $availability->predefined_ranges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check nested conditions (for groups).
|
||||||
|
if (isset($availability->c) && is_array($availability->c)) {
|
||||||
|
foreach ($availability->c as $condition) {
|
||||||
|
if (self::check_availability_tree($condition, $rangeid)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get usage details for a range as HTML.
|
||||||
|
*
|
||||||
|
* @param int $rangeid The range ID.
|
||||||
|
*
|
||||||
|
* @return string HTML string with usage details.
|
||||||
|
*/
|
||||||
|
public static function get_range_usage_html(int $rangeid): string {
|
||||||
|
$usage = self::is_range_in_use($rangeid);
|
||||||
|
|
||||||
|
if (!$usage['inuse']) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = \html_writer::tag('p', get_string('range_in_use_count', 'availability_ipaddress', $usage['count']));
|
||||||
|
|
||||||
|
if (!empty($usage['activities'])) {
|
||||||
|
$items = [];
|
||||||
|
foreach (array_slice($usage['activities'], 0, 5) as $activity) {
|
||||||
|
$items[] = $activity['coursename'] . ': ' . $activity['name'];
|
||||||
|
}
|
||||||
|
$html .= \html_writer::alist($items);
|
||||||
|
|
||||||
|
if (count($usage['activities']) > 5) {
|
||||||
|
$more = count($usage['activities']) - 5;
|
||||||
|
$html .= \html_writer::tag('p', get_string('and_x_more', 'availability_ipaddress', $more));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a predefined range from all availability restrictions.
|
||||||
|
*
|
||||||
|
* @param int $rangeid The ID of the range to remove.
|
||||||
|
*
|
||||||
|
* @return int Number of restrictions updated.
|
||||||
|
*/
|
||||||
|
public static function remove_range_from_restrictions(int $rangeid): int {
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
$updatecount = 0;
|
||||||
|
|
||||||
|
// Update course modules.
|
||||||
|
$sql = "SELECT cm.id, cm.availability
|
||||||
|
FROM {course_modules} cm
|
||||||
|
WHERE cm.availability IS NOT NULL AND cm.availability != ''";
|
||||||
|
|
||||||
|
$modules = $DB->get_records_sql($sql);
|
||||||
|
|
||||||
|
foreach ($modules as $module) {
|
||||||
|
$availability = json_decode($module->availability);
|
||||||
|
if ($availability && self::remove_range_from_tree($availability, $rangeid)) {
|
||||||
|
$module->availability = json_encode($availability);
|
||||||
|
$DB->update_record('course_modules', $module);
|
||||||
|
$updatecount++;
|
||||||
|
|
||||||
|
// Rebuild course cache.
|
||||||
|
$course = $DB->get_record('course', ['id' => $DB->get_field('course_modules', 'course', ['id' => $module->id])]);
|
||||||
|
if ($course) {
|
||||||
|
rebuild_course_cache($course->id, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update course sections.
|
||||||
|
$sql = "SELECT cs.id, cs.course, cs.availability
|
||||||
|
FROM {course_sections} cs
|
||||||
|
WHERE cs.availability IS NOT NULL AND cs.availability != ''";
|
||||||
|
|
||||||
|
$sections = $DB->get_records_sql($sql);
|
||||||
|
|
||||||
|
foreach ($sections as $section) {
|
||||||
|
$availability = json_decode($section->availability);
|
||||||
|
if ($availability && self::remove_range_from_tree($availability, $rangeid)) {
|
||||||
|
$section->availability = json_encode($availability);
|
||||||
|
$DB->update_record('course_sections', $section);
|
||||||
|
$updatecount++;
|
||||||
|
|
||||||
|
// Rebuild course cache.
|
||||||
|
rebuild_course_cache($section->course, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $updatecount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively remove range from availability tree.
|
||||||
|
*
|
||||||
|
* @param \stdClass $availability The availability tree.
|
||||||
|
* @param int $rangeid The range ID to remove.
|
||||||
|
*
|
||||||
|
* @return bool True if tree was modified.
|
||||||
|
*/
|
||||||
|
private static function remove_range_from_tree(\stdClass $availability, int $rangeid): bool {
|
||||||
|
$modified = false;
|
||||||
|
|
||||||
|
// Process IP address conditions.
|
||||||
|
if (self::is_ipaddress_condition($availability)) {
|
||||||
|
$modified = self::remove_range_from_condition($availability, $rangeid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process nested conditions.
|
||||||
|
return self::process_nested_conditions($availability, $rangeid) || $modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the availability item is an IP address condition.
|
||||||
|
*
|
||||||
|
* @param \stdClass $availability The availability item.
|
||||||
|
*
|
||||||
|
* @return bool True if it's an IP address condition.
|
||||||
|
*/
|
||||||
|
private static function is_ipaddress_condition(\stdClass $availability): bool {
|
||||||
|
return isset($availability->type) && $availability->type === 'ipaddress';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove range from an IP address condition.
|
||||||
|
*
|
||||||
|
* @param \stdClass $availability The availability condition.
|
||||||
|
* @param int $rangeid The range ID to remove.
|
||||||
|
*
|
||||||
|
* @return bool True if the condition was modified.
|
||||||
|
*/
|
||||||
|
private static function remove_range_from_condition(\stdClass $availability, int $rangeid): bool {
|
||||||
|
if (!isset($availability->predefined_ranges) || !is_array($availability->predefined_ranges)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = array_search($rangeid, $availability->predefined_ranges);
|
||||||
|
if ($key === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the range from the array.
|
||||||
|
array_splice($availability->predefined_ranges, $key, 1);
|
||||||
|
|
||||||
|
// If no ranges left, remove the predefined_ranges property.
|
||||||
|
if (empty($availability->predefined_ranges)) {
|
||||||
|
unset($availability->predefined_ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process nested conditions in availability tree.
|
||||||
|
*
|
||||||
|
* @param \stdClass $availability The availability item.
|
||||||
|
* @param int $rangeid The range ID to remove.
|
||||||
|
*
|
||||||
|
* @return bool True if any nested condition was modified.
|
||||||
|
*/
|
||||||
|
private static function process_nested_conditions(\stdClass $availability, int $rangeid): bool {
|
||||||
|
if (!isset($availability->c) || !is_array($availability->c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$modified = false;
|
||||||
|
foreach ($availability->c as $condition) {
|
||||||
|
if (self::remove_range_from_tree($condition, $rangeid)) {
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a comma separated list of IP addresses.
|
||||||
|
*
|
||||||
|
* @param string $ipaddresses Comma separated list of IP addresses.
|
||||||
|
*
|
||||||
|
* @return bool True if every entry is valid.
|
||||||
|
*/
|
||||||
|
public static function is_valid_ipaddress_list(string $ipaddresses): bool {
|
||||||
|
foreach (explode(',', $ipaddresses) as $entry) {
|
||||||
|
if (!self::is_valid_ipaddress_entry(trim($entry))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a single IP address entry.
|
||||||
|
*
|
||||||
|
* Only formats that address_in_subnet() understands are accepted, anything else
|
||||||
|
* would be silently ignored when the restriction is checked.
|
||||||
|
*
|
||||||
|
* - Address: 192.168.1.1 or 2001:db8::1
|
||||||
|
* - Subnet: 192.168.1.0/24 or 2001:db8::/32
|
||||||
|
* - Range in the last group: 192.168.1.1-255 or 2001:db8::1-ff
|
||||||
|
*
|
||||||
|
* @param string $entry A single IP address entry.
|
||||||
|
*
|
||||||
|
* @return bool True if the entry is valid.
|
||||||
|
*/
|
||||||
|
public static function is_valid_ipaddress_entry(string $entry): bool {
|
||||||
|
if (strpos($entry, '/') !== false) {
|
||||||
|
$valid = self::is_valid_subnet($entry);
|
||||||
|
} else if (strpos($entry, '-') !== false) {
|
||||||
|
$valid = self::is_valid_range($entry);
|
||||||
|
} else {
|
||||||
|
// An address ending in ":" is treated as an incomplete prefix by address_in_subnet(), which fails to parse it.
|
||||||
|
$valid = self::is_ipv4($entry) || (self::is_ipv6($entry) && substr($entry, -1) !== ':');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety net: the entry must match its own start address, so address_in_subnet() is able to parse it.
|
||||||
|
return $valid && address_in_subnet(preg_split('/[\/-]/', $entry)[0], $entry, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a value is a valid IPv4 address.
|
||||||
|
*
|
||||||
|
* @param string $value The value to check.
|
||||||
|
*
|
||||||
|
* @return bool True if the value is a valid IPv4 address.
|
||||||
|
*/
|
||||||
|
private static function is_ipv4(string $value): bool {
|
||||||
|
return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a value is a valid IPv6 address that cleanremoteaddr() can parse.
|
||||||
|
*
|
||||||
|
* cleanremoteaddr() doesn't handle embedded IPv4 notation reliably and only expands "::"
|
||||||
|
* when it replaces at least two groups, or three at the start or end of the address.
|
||||||
|
*
|
||||||
|
* @param string $value The value to check.
|
||||||
|
*
|
||||||
|
* @return bool True if the value is a valid IPv6 address.
|
||||||
|
*/
|
||||||
|
private static function is_ipv6(string $value): bool {
|
||||||
|
if (strpos($value, '.') !== false || filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($value, '::') === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = count(array_filter(explode(':', $value), 'strlen'));
|
||||||
|
if (strpos($value, '::') === 0 || substr($value, -2) === '::') {
|
||||||
|
return $groups <= 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groups <= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a subnet in CIDR notation, e.g. 192.168.1.0/24 or 2001:db8::/32.
|
||||||
|
*
|
||||||
|
* @param string $entry The subnet.
|
||||||
|
*
|
||||||
|
* @return bool True if the subnet is valid.
|
||||||
|
*/
|
||||||
|
private static function is_valid_subnet(string $entry): bool {
|
||||||
|
$parts = explode('/', $entry);
|
||||||
|
|
||||||
|
// Moodle's is_number() rejects a mask with leading zeros.
|
||||||
|
if (count($parts) !== 2 || !preg_match('/^(0|[1-9]\d{0,2})$/D', $parts[1])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$address, $mask] = $parts;
|
||||||
|
if (self::is_ipv4($address)) {
|
||||||
|
return (int) $mask <= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::is_ipv6($address) && (int) $mask <= 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a range in the last group of the address, e.g. 192.168.1.1-255 or 2001:db8::1-ff.
|
||||||
|
*
|
||||||
|
* The end of the range may not be lower than the start, it would never match.
|
||||||
|
*
|
||||||
|
* @param string $entry The range.
|
||||||
|
*
|
||||||
|
* @return bool True if the range is valid.
|
||||||
|
*/
|
||||||
|
private static function is_valid_range(string $entry): bool {
|
||||||
|
$parts = explode('-', $entry);
|
||||||
|
if (count($parts) !== 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$start, $end] = $parts;
|
||||||
|
if (self::is_ipv4($start)) {
|
||||||
|
$startgroup = (int) substr(strrchr($start, '.'), 1);
|
||||||
|
|
||||||
|
return preg_match('/^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/D', $end) === 1 && (int) $end >= $startgroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::is_ipv6($start)) {
|
||||||
|
$startgroup = hexdec(substr(strrchr($start, ':'), 1) ?: '0');
|
||||||
|
|
||||||
|
return preg_match('/^[0-9a-f]{1,4}$/iD', $end) === 1 && hexdec($end) >= $startgroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,6 @@ namespace availability_ipaddress\privacy;
|
|||||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
*/
|
*/
|
||||||
class provider implements \core_privacy\local\metadata\null_provider {
|
class provider implements \core_privacy\local\metadata\null_provider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the language string identifier with the component's language
|
* Get the language string identifier with the component's language
|
||||||
* file to explain why this plugin stores no data.
|
* file to explain why this plugin stores no data.
|
||||||
@@ -43,5 +42,4 @@ class provider implements \core_privacy\local\metadata\null_provider {
|
|||||||
public static function get_reason(): string {
|
public static function get_reason(): string {
|
||||||
return 'privacy:metadata';
|
return 'privacy:metadata';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table class for displaying IP address ranges.
|
||||||
|
*
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 04/08/2025 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @author Vincent Cornelis
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace availability_ipaddress\table;
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
global $CFG;
|
||||||
|
require_once($CFG->libdir . '/tablelib.php');
|
||||||
|
|
||||||
|
use table_sql;
|
||||||
|
use html_writer;
|
||||||
|
use moodle_url;
|
||||||
|
use pix_icon;
|
||||||
|
use confirm_action;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table class for IP address ranges.
|
||||||
|
*
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 04/08/2025 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @author Vincent Cornelis
|
||||||
|
*/
|
||||||
|
class ipranges_table extends table_sql {
|
||||||
|
/**
|
||||||
|
* @var moodle_url The base URL for the page.
|
||||||
|
*/
|
||||||
|
public $baseurl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $uniqueid Unique ID for the table.
|
||||||
|
* @param moodle_url $baseurl The base URL for the page.
|
||||||
|
*/
|
||||||
|
public function __construct(string $uniqueid, moodle_url $baseurl) {
|
||||||
|
parent::__construct($uniqueid);
|
||||||
|
$this->baseurl = $baseurl;
|
||||||
|
|
||||||
|
// Define columns and headers.
|
||||||
|
$columns = ['name', 'description', 'ipaddresses', 'enabled', 'actions'];
|
||||||
|
$headers = [
|
||||||
|
get_string('name'),
|
||||||
|
get_string('description'),
|
||||||
|
get_string('ipaddresses', 'availability_ipaddress'),
|
||||||
|
get_string('enabled', 'availability_ipaddress'),
|
||||||
|
get_string('actions'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->define_columns($columns);
|
||||||
|
$this->define_headers($headers);
|
||||||
|
|
||||||
|
// Set attributes.
|
||||||
|
$this->set_attribute('class', 'generaltable');
|
||||||
|
$this->sortable(true, 'name', SORT_ASC);
|
||||||
|
$this->no_sorting('description');
|
||||||
|
$this->no_sorting('actions');
|
||||||
|
$this->collapsible(false);
|
||||||
|
$this->pageable(true);
|
||||||
|
$this->is_downloadable(false);
|
||||||
|
|
||||||
|
$this->define_baseurl($baseurl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the SQL query.
|
||||||
|
*
|
||||||
|
* @param int $pagesize Number of records per page.
|
||||||
|
* @param bool $useinitialsbar Whether to use the initials bar.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set_sql_data(int $pagesize = 30, bool $useinitialsbar = false): void {
|
||||||
|
$fields = 'id, name, description, ipaddresses, enabled, timecreated, timemodified';
|
||||||
|
$from = '{availability_ipaddress_pre}';
|
||||||
|
$where = '1=1';
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
$this->set_sql($fields, $from, $where, $params);
|
||||||
|
$this->set_count_sql("SELECT COUNT(*) FROM {availability_ipaddress_pre}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column for name.
|
||||||
|
*
|
||||||
|
* @param \stdClass $range The range record.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_name(\stdClass $range): string {
|
||||||
|
return format_string($range->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column for description.
|
||||||
|
*
|
||||||
|
* @param \stdClass $range The range record.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_description(\stdClass $range): string {
|
||||||
|
if (empty($range->description)) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
$description = format_string($range->description);
|
||||||
|
// Truncate if too long.
|
||||||
|
if (strlen($description) > 100) {
|
||||||
|
$truncated = substr($description, 0, 97) . '...';
|
||||||
|
|
||||||
|
return html_writer::tag('span', $truncated, ['title' => $description]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column for IP addresses.
|
||||||
|
*
|
||||||
|
* @param \stdClass $range The range record.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_ipaddresses(\stdClass $range): string {
|
||||||
|
$ips = s($range->ipaddresses);
|
||||||
|
// Truncate if too long and add tooltip.
|
||||||
|
if (strlen($ips) > 50) {
|
||||||
|
$truncated = substr($ips, 0, 47) . '...';
|
||||||
|
|
||||||
|
return html_writer::tag(
|
||||||
|
'span',
|
||||||
|
html_writer::tag('code', $truncated),
|
||||||
|
['title' => $ips]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html_writer::tag('code', $ips);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column for enabled status.
|
||||||
|
*
|
||||||
|
* @param \stdClass $range The range record.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_enabled(\stdClass $range): string {
|
||||||
|
return $range->enabled ? get_string('yes') : get_string('no');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column for actions.
|
||||||
|
*
|
||||||
|
* @param \stdClass $range The range record.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_actions(\stdClass $range): string {
|
||||||
|
global $OUTPUT;
|
||||||
|
|
||||||
|
$actions = [];
|
||||||
|
|
||||||
|
// Edit action.
|
||||||
|
$editurl = new moodle_url($this->baseurl, ['action' => 'edit', 'id' => $range->id]);
|
||||||
|
$actions[] = $OUTPUT->action_icon(
|
||||||
|
$editurl,
|
||||||
|
new pix_icon('t/edit', get_string('edit'))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Toggle action.
|
||||||
|
$toggleurl = new moodle_url($this->baseurl, ['action' => 'toggle', 'id' => $range->id, 'sesskey' => sesskey()]);
|
||||||
|
$toggleicon = $range->enabled ? 't/hide' : 't/show';
|
||||||
|
$togglestring = $range->enabled ? get_string('disable') : get_string('enable');
|
||||||
|
|
||||||
|
// Check if range is in use and add confirmation if disabling.
|
||||||
|
if ($range->enabled) {
|
||||||
|
$usage = \availability_ipaddress\helper::is_range_in_use($range->id);
|
||||||
|
if ($usage['inuse']) {
|
||||||
|
// Create confirmation message with usage details.
|
||||||
|
$message = \availability_ipaddress\helper::get_range_usage_html($range->id);
|
||||||
|
$message .= \html_writer::tag(
|
||||||
|
'p',
|
||||||
|
get_string('confirm_disable_range', 'availability_ipaddress'),
|
||||||
|
['class' => 'font-weight-bold']
|
||||||
|
);
|
||||||
|
$actions[] = $OUTPUT->action_icon(
|
||||||
|
$toggleurl,
|
||||||
|
new pix_icon($toggleicon, $togglestring),
|
||||||
|
new confirm_action($message)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$actions[] = $OUTPUT->action_icon($toggleurl, new pix_icon($toggleicon, $togglestring));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Enabling doesn't need confirmation.
|
||||||
|
$actions[] = $OUTPUT->action_icon($toggleurl, new pix_icon($toggleicon, $togglestring));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete action.
|
||||||
|
$deleteurl = new moodle_url($this->baseurl, [
|
||||||
|
'action' => 'delete',
|
||||||
|
'id' => $range->id,
|
||||||
|
'sesskey' => sesskey(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check if range is in use and add usage info to confirmation.
|
||||||
|
$usage = \availability_ipaddress\helper::is_range_in_use($range->id);
|
||||||
|
if ($usage['inuse']) {
|
||||||
|
$message = \availability_ipaddress\helper::get_range_usage_html($range->id);
|
||||||
|
$message .= \html_writer::tag(
|
||||||
|
'p',
|
||||||
|
get_string('confirm_delete_range', 'availability_ipaddress'),
|
||||||
|
['class' => 'font-weight-bold']
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$message = get_string('confirm_delete_range', 'availability_ipaddress');
|
||||||
|
}
|
||||||
|
|
||||||
|
$actions[] = $OUTPUT->action_icon(
|
||||||
|
$deleteurl,
|
||||||
|
new pix_icon('t/delete', get_string('delete')),
|
||||||
|
new confirm_action($message)
|
||||||
|
);
|
||||||
|
|
||||||
|
return implode(' ', $actions);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<XMLDB PATH="availability/condition/ipaddress/db" VERSION="20240104" COMMENT="XMLDB file for availability_ipaddress plugin"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
|
||||||
|
>
|
||||||
|
<TABLES>
|
||||||
|
<TABLE NAME="availability_ipaddress_pre" COMMENT="Stores predefined IP address ranges for availability_ipaddress">
|
||||||
|
<FIELDS>
|
||||||
|
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||||
|
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" COMMENT="Display name for the IP range" SEQUENCE="false"/>
|
||||||
|
<FIELD NAME="ipaddresses" TYPE="text" NOTNULL="true" COMMENT="Comma-separated list of IP addresses/ranges" SEQUENCE="false"/>
|
||||||
|
<FIELD NAME="description" TYPE="text" NOTNULL="false" COMMENT="Optional description of this IP range" SEQUENCE="false"/>
|
||||||
|
<FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" COMMENT="Whether this range is available for selection" SEQUENCE="false"/>
|
||||||
|
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||||
|
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||||
|
</FIELDS>
|
||||||
|
<KEYS>
|
||||||
|
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||||
|
</KEYS>
|
||||||
|
<INDEXES>
|
||||||
|
<INDEX NAME="enabled" UNIQUE="false" FIELDS="enabled"/>
|
||||||
|
</INDEXES>
|
||||||
|
</TABLE>
|
||||||
|
</TABLES>
|
||||||
|
</XMLDB>
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade script for availability_ipaddress.
|
||||||
|
*
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 04/08/2025 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @author Vincent Cornelis
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade function.
|
||||||
|
*
|
||||||
|
* @param int $oldversion The old version of the plugin
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function xmldb_availability_ipaddress_upgrade(int $oldversion): bool {
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
$dbman = $DB->get_manager();
|
||||||
|
|
||||||
|
if ($oldversion < 2025070400) {
|
||||||
|
// Define table availability_ipaddress_pre to be created.
|
||||||
|
$table = new xmldb_table('availability_ipaddress_pre');
|
||||||
|
|
||||||
|
// Adding fields to table availability_ipaddress_pre.
|
||||||
|
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||||
|
$table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||||
|
$table->add_field('ipaddresses', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
|
||||||
|
$table->add_field('description', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||||
|
$table->add_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1');
|
||||||
|
$table->add_field('sortorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||||
|
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||||
|
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||||
|
|
||||||
|
// Adding keys to table availability_ipaddress_pre.
|
||||||
|
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||||
|
|
||||||
|
// Adding indexes to table availability_ipaddress_pre.
|
||||||
|
$table->add_index('enabled_sortorder', XMLDB_INDEX_NOTUNIQUE, ['enabled', 'sortorder']);
|
||||||
|
|
||||||
|
// Conditionally launch create table for availability_ipaddress_pre.
|
||||||
|
if (!$dbman->table_exists($table)) {
|
||||||
|
$dbman->create_table($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Availability_ipaddress savepoint reached.
|
||||||
|
upgrade_plugin_savepoint(true, 2025070400, 'availability', 'ipaddress');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($oldversion < 2025080401) {
|
||||||
|
// Remove sortorder field from availability_ipaddress_pre table.
|
||||||
|
$table = new xmldb_table('availability_ipaddress_pre');
|
||||||
|
|
||||||
|
// Drop the index that includes sortorder.
|
||||||
|
$index = new xmldb_index('enabled_sortorder', XMLDB_INDEX_NOTUNIQUE, ['enabled', 'sortorder']);
|
||||||
|
if ($dbman->index_exists($table, $index)) {
|
||||||
|
$dbman->drop_index($table, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the sortorder field.
|
||||||
|
$field = new xmldb_field('sortorder');
|
||||||
|
if ($dbman->field_exists($table, $field)) {
|
||||||
|
$dbman->drop_field($table, $field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new index for enabled only.
|
||||||
|
$index = new xmldb_index('enabled', XMLDB_INDEX_NOTUNIQUE, ['enabled']);
|
||||||
|
if (!$dbman->index_exists($table, $index)) {
|
||||||
|
$dbman->add_index($table, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Availability_ipaddress savepoint reached.
|
||||||
|
upgrade_plugin_savepoint(true, 2025080401, 'availability', 'ipaddress');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<COMPATIBILITY_MATRIX>
|
<COMPATIBILITY_MATRIX>
|
||||||
<PLUGIN name="availability_ipaddress">
|
<PLUGIN name="availability_ipaddress">
|
||||||
<PHP version="7.4" level="required"/>
|
<PHP version="7.3" level="required"/>
|
||||||
</PLUGIN>
|
</PLUGIN>
|
||||||
</COMPATIBILITY_MATRIX>
|
</COMPATIBILITY_MATRIX>
|
||||||
@@ -24,10 +24,15 @@
|
|||||||
* @author Luuk Verhoeven
|
* @author Luuk Verhoeven
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
// We like comments and our own sorting.
|
||||||
|
// phpcs:disable moodle.Files.LangFilesOrdering.UnexpectedComment
|
||||||
|
// phpcs:disable moodle.Files.LangFilesOrdering.IncorrectOrder
|
||||||
|
|
||||||
$string['pluginname'] = 'IP address';
|
$string['pluginname'] = 'IP address';
|
||||||
$string['description'] = 'Restrict access by ip-address or subnet';
|
$string['description'] = 'Restrict access by ip-address or subnet';
|
||||||
$string['title'] = 'IP address';
|
$string['title'] = 'IP address';
|
||||||
$string['require_condition'] = 'Matching ip-address/subnet. (Your IP:{$a})';
|
$string['require_condition'] = 'ip-address/subnet is allowed (Your IP:{$a})';
|
||||||
|
$string['require_condition_not'] = 'ip-address/subnet is not blocked (Your IP:{$a})';
|
||||||
|
|
||||||
// Errors.
|
// Errors.
|
||||||
$string['error_ipaddress'] = 'Incorrect ip-address/subnet format';
|
$string['error_ipaddress'] = 'Incorrect ip-address/subnet format';
|
||||||
@@ -37,3 +42,36 @@ $string['js:ipaddress'] = 'Require network address';
|
|||||||
|
|
||||||
// Privacy provider.
|
// Privacy provider.
|
||||||
$string['privacy:metadata'] = 'The restriction by activity ipaddress plugin does not store any personal data.';
|
$string['privacy:metadata'] = 'The restriction by activity ipaddress plugin does not store any personal data.';
|
||||||
|
|
||||||
|
// Predefined ranges.
|
||||||
|
$string['setting:manage_predefined_ranges'] = 'IP address - Manage predefined IP ranges';
|
||||||
|
$string['manage_predefined_ranges'] = 'Manage predefined IP ranges';
|
||||||
|
$string['predefined_ranges'] = 'Predefined IP ranges';
|
||||||
|
$string['custom_ipaddress'] = 'Custom IP address(es)';
|
||||||
|
$string['use_predefined'] = 'Use predefined IP addresses';
|
||||||
|
$string['range_name'] = 'Range name';
|
||||||
|
$string['range_name_help'] = 'A descriptive name for this IP range, e.g., "Campus Network" or "Library Computers"';
|
||||||
|
$string['ipaddresses'] = 'IP addresses';
|
||||||
|
$string['ipaddresses_help'] = 'Enter IP addresses separated by commas. Supports single IPs (192.168.1.1), ranges (192.168.1.1-255), and subnets (192.168.1.0/24)';
|
||||||
|
$string['ipaddresses_help_help'] = '<p>Enter one or more IP addresses or ranges, separated by commas.</p>
|
||||||
|
<p><strong>Examples:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Single IP:</strong> <code>192.168.1.1</code> or <code>2001:db8::1</code></li>
|
||||||
|
<li><strong>IP range:</strong> <code>192.168.1.1-255</code> or <code>2001:db8::1-ff</code></li>
|
||||||
|
<li><strong>Subnet:</strong> <code>192.168.1.0/24</code> or <code>2001:db8::/32</code></li>
|
||||||
|
<li><strong>Multiple:</strong> <code>192.168.1.1, 10.0.0.0/8, 172.16.0.1-255</code></li>
|
||||||
|
</ul>
|
||||||
|
<p>A range only changes the last part of the address. Use a subnet for an IPv6 network prefix, e.g. <code>2001:db8::/32</code> instead of <code>2001:db8::</code>.</p>';
|
||||||
|
$string['enabled'] = 'Enabled';
|
||||||
|
$string['sortorder'] = 'Sort order';
|
||||||
|
$string['existing_ranges'] = 'Existing IP ranges';
|
||||||
|
$string['range_created'] = 'IP range created successfully';
|
||||||
|
$string['range_updated'] = 'IP range updated successfully';
|
||||||
|
$string['range_deleted'] = 'IP range deleted successfully';
|
||||||
|
$string['confirm_delete_range'] = 'Deleting this IP range will remove it from all restrictions where it is used. Are you sure you want to permanently delete it?';
|
||||||
|
$string['range_in_use_count'] = 'This IP range is currently used in {$a} restriction(s).';
|
||||||
|
$string['and_x_more'] = '... and {$a} more.';
|
||||||
|
$string['confirm_disable_range'] = 'This IP range is currently in use. Disabling it will remove it from all restrictions where it is used. Are you sure you want to continue?';
|
||||||
|
$string['range_in_use_title'] = 'IP Range In Use';
|
||||||
|
$string['range_disabled_and_removed'] = 'IP range disabled and removed from {$a} restriction(s).';
|
||||||
|
$string['range_deleted_and_removed'] = 'IP range deleted and removed from {$a} restriction(s).';
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage predefined IP address ranges.
|
||||||
|
*
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 04/08/2025 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @author Vincent Cornelis
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/../../../config.php');
|
||||||
|
require_once($CFG->libdir . '/adminlib.php');
|
||||||
|
|
||||||
|
admin_externalpage_setup('availability_ipaddress_ranges');
|
||||||
|
|
||||||
|
$action = optional_param('action', '', PARAM_ALPHA);
|
||||||
|
$id = optional_param('id', 0, PARAM_INT);
|
||||||
|
|
||||||
|
$PAGE->set_url('/availability/condition/ipaddress/manage_ranges.php');
|
||||||
|
$PAGE->set_title(get_string('manage_predefined_ranges', 'availability_ipaddress'));
|
||||||
|
$PAGE->set_heading(get_string('manage_predefined_ranges', 'availability_ipaddress'));
|
||||||
|
|
||||||
|
// Handle actions.
|
||||||
|
if ($action === 'delete' && confirm_sesskey()) {
|
||||||
|
// Remove from all restrictions before deleting.
|
||||||
|
$removed = \availability_ipaddress\helper::remove_range_from_restrictions($id);
|
||||||
|
|
||||||
|
$DB->delete_records('availability_ipaddress_pre', ['id' => $id]);
|
||||||
|
|
||||||
|
if ($removed > 0) {
|
||||||
|
redirect(
|
||||||
|
$PAGE->url,
|
||||||
|
get_string('range_deleted_and_removed', 'availability_ipaddress', $removed),
|
||||||
|
null,
|
||||||
|
\core\output\notification::NOTIFY_SUCCESS
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
redirect(
|
||||||
|
$PAGE->url,
|
||||||
|
get_string('range_deleted', 'availability_ipaddress'),
|
||||||
|
null,
|
||||||
|
\core\output\notification::NOTIFY_SUCCESS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'toggle' && confirm_sesskey()) {
|
||||||
|
$record = $DB->get_record('availability_ipaddress_pre', ['id' => $id], '*', MUST_EXIST);
|
||||||
|
|
||||||
|
$record->enabled = !$record->enabled;
|
||||||
|
$record->timemodified = time();
|
||||||
|
$DB->update_record('availability_ipaddress_pre', $record);
|
||||||
|
|
||||||
|
// If we just disabled the range, remove it from all restrictions.
|
||||||
|
if (!$record->enabled) {
|
||||||
|
$removed = \availability_ipaddress\helper::remove_range_from_restrictions($id);
|
||||||
|
if ($removed > 0) {
|
||||||
|
redirect(
|
||||||
|
$PAGE->url,
|
||||||
|
get_string('range_disabled_and_removed', 'availability_ipaddress', $removed),
|
||||||
|
null,
|
||||||
|
\core\output\notification::NOTIFY_SUCCESS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect($PAGE->url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle form submission for adding/editing.
|
||||||
|
if ($action === 'add' || $action === 'edit') {
|
||||||
|
$formurl = new moodle_url($PAGE->url, ['action' => $action, 'id' => $id]);
|
||||||
|
$form = new \availability_ipaddress\form\range_form($formurl, ['id' => $id]);
|
||||||
|
|
||||||
|
if ($form->is_cancelled()) {
|
||||||
|
redirect($PAGE->url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data = $form->get_data()) {
|
||||||
|
if ($data->id) {
|
||||||
|
// Update existing.
|
||||||
|
$data->timemodified = time();
|
||||||
|
$DB->update_record('availability_ipaddress_pre', $data);
|
||||||
|
redirect(
|
||||||
|
$PAGE->url,
|
||||||
|
get_string('range_updated', 'availability_ipaddress'),
|
||||||
|
null,
|
||||||
|
\core\output\notification::NOTIFY_SUCCESS
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Create new.
|
||||||
|
$data->timecreated = time();
|
||||||
|
$data->timemodified = time();
|
||||||
|
$DB->insert_record('availability_ipaddress_pre', $data);
|
||||||
|
redirect(
|
||||||
|
$PAGE->url,
|
||||||
|
get_string('range_created', 'availability_ipaddress'),
|
||||||
|
null,
|
||||||
|
\core\output\notification::NOTIFY_SUCCESS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load data for editing.
|
||||||
|
if ($action === 'edit' && $id) {
|
||||||
|
$record = $DB->get_record('availability_ipaddress_pre', ['id' => $id], '*', MUST_EXIST);
|
||||||
|
$form->set_data($record);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $OUTPUT->header();
|
||||||
|
$form->display();
|
||||||
|
echo $OUTPUT->footer();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display page.
|
||||||
|
echo $OUTPUT->header();
|
||||||
|
|
||||||
|
// Add new button.
|
||||||
|
echo $OUTPUT->single_button(new moodle_url($PAGE->url, ['action' => 'add']), get_string('add'), 'get');
|
||||||
|
|
||||||
|
// Create and display table.
|
||||||
|
$table = new \availability_ipaddress\table\ipranges_table('availability-ipaddress-ranges', $PAGE->url);
|
||||||
|
$table->set_sql_data(30);
|
||||||
|
$table->out(30, true);
|
||||||
|
|
||||||
|
echo $OUTPUT->footer();
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings for availability_ipaddress.
|
||||||
|
*
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 04/08/2025 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @author Vincent Cornelis
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
global $ADMIN;
|
||||||
|
|
||||||
|
if ($hassiteconfig) {
|
||||||
|
// Add external page for managing IP ranges.
|
||||||
|
$ADMIN->add(
|
||||||
|
'availabilitysettings',
|
||||||
|
new admin_externalpage(
|
||||||
|
'availability_ipaddress_ranges',
|
||||||
|
get_string('setting:manage_predefined_ranges', 'availability_ipaddress'),
|
||||||
|
new moodle_url('/availability/condition/ipaddress/manage_ranges.php'),
|
||||||
|
'moodle/site:config'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the visible name of auto generated settings page to empty string,
|
||||||
|
// to avoid showing it in the settings tree, as we only add the external page.
|
||||||
|
$settings->visiblename = '';
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
namespace availability_ipaddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the IP address validation in the helper class.
|
||||||
|
*
|
||||||
|
* @package availability_ipaddress
|
||||||
|
* @copyright 2026 LdesignMedia.nl - Luuk Verhoeven
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
* @covers \availability_ipaddress\helper
|
||||||
|
*/
|
||||||
|
final class helper_test extends \basic_testcase {
|
||||||
|
/**
|
||||||
|
* Data provider for test_is_valid_ipaddress_list.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function ipaddress_list_provider(): array {
|
||||||
|
return [
|
||||||
|
'IPv4 address' => ['192.168.1.1', true],
|
||||||
|
'IPv4 subnet' => ['192.168.1.0/24', true],
|
||||||
|
'IPv4 subnet all addresses' => ['0.0.0.0/0', true],
|
||||||
|
'IPv4 range' => ['192.168.1.10-20', true],
|
||||||
|
'IPv4 range single address' => ['192.168.1.5-5', true],
|
||||||
|
'IPv6 address' => ['2001:db8::1', true],
|
||||||
|
'IPv6 loopback' => ['::1', true],
|
||||||
|
'IPv6 full address' => ['2001:db8:0:0:0:0:0:1', true],
|
||||||
|
'IPv6 compressed in the middle' => ['1:2:3::4:5:6', true],
|
||||||
|
'IPv6 compressed at the start' => ['::1:2:3:4:5', true],
|
||||||
|
'IPv6 subnet' => ['2001:db8::/32', true],
|
||||||
|
'IPv6 subnet all addresses' => ['::/0', true],
|
||||||
|
'IPv6 range' => ['2001:db8::1-ff', true],
|
||||||
|
'IPv6 range from compressed start' => ['2001:db8::-ffff', true],
|
||||||
|
'List' => ['192.168.1.1,10.0.0.0/8,2001:db8::/32', true],
|
||||||
|
'List with spaces' => [' 192.168.1.1 , 10.0.0.1 ', true],
|
||||||
|
'IPv6 with trailing text' => ['2001:db8::1garbage', false],
|
||||||
|
'IPv6 with zone id' => ['fe80::1%eth0', false],
|
||||||
|
'IPv6 subnet mask too large' => ['2001:db8::/129', false],
|
||||||
|
'IPv6 address ending in ::' => ['2001:db8::', false],
|
||||||
|
'IPv6 unspecified address' => ['::', false],
|
||||||
|
'IPv6 :: replacing one group' => ['1::2:3:4:5:6:7', false],
|
||||||
|
'IPv6 :: at the start replacing two groups' => ['::1:2:3:4:5:6', false],
|
||||||
|
'IPv6 subnet :: at the end replacing two groups' => ['2001:db8:1:2:3:4::/112', false],
|
||||||
|
'IPv6 with embedded IPv4' => ['::ffff:192.168.1.1', false],
|
||||||
|
'IPv6 subnet with embedded IPv4' => ['::ffff:192.168.1.0/120', false],
|
||||||
|
'IPv4 subnet with trailing text' => ['192.168.1.0/24garbage', false],
|
||||||
|
'IPv4 subnet mask too large' => ['192.168.1.0/33', false],
|
||||||
|
'IPv4 subnet mask with leading zero' => ['192.168.1.0/024', false],
|
||||||
|
'IPv4 subnet negative mask' => ['192.168.1.0/-5', false],
|
||||||
|
'IPv4 subnet exponent mask' => ['192.168.1.0/1e3', false],
|
||||||
|
'IPv4 subnet trailing newline' => ["192.168.1.0/24\n", true],
|
||||||
|
'IPv4 with non-breaking space' => ["192.168.1.1\u{a0}", false],
|
||||||
|
'IPv4 subnet double mask' => ['192.168.1.0/24/8', false],
|
||||||
|
'IPv4 range with text' => ['192.168.1.1-garbage', false],
|
||||||
|
'IPv4 range end too large' => ['192.168.1.1-256', false],
|
||||||
|
'IPv4 range end below start' => ['192.168.1.20-10', false],
|
||||||
|
'IPv4 range double dash' => ['192.168.1.1-2-3', false],
|
||||||
|
'IPv6 range end below start' => ['2001:db8::ff-1', false],
|
||||||
|
'Invalid octet' => ['999.1.1.1', false],
|
||||||
|
'Leading zero' => ['192.168.01.1', false],
|
||||||
|
'Text' => ['abc', false],
|
||||||
|
'Text with subnet' => ['hello/gm', false],
|
||||||
|
'Empty' => ['', false],
|
||||||
|
'Zero' => ['0', false],
|
||||||
|
'Trailing comma' => ['192.168.1.1,', false],
|
||||||
|
'List with one invalid entry' => ['192.168.1.1, 2001:db8::1garbage', false],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test validation of a comma separated list of IP addresses.
|
||||||
|
*
|
||||||
|
* @dataProvider ipaddress_list_provider
|
||||||
|
*
|
||||||
|
* @param string $ipaddresses
|
||||||
|
* @param bool $expected
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_is_valid_ipaddress_list(string $ipaddresses, bool $expected): void {
|
||||||
|
$this->assertSame($expected, helper::is_valid_ipaddress_list($ipaddresses));
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -27,8 +27,8 @@
|
|||||||
defined('MOODLE_INTERNAL') || die();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
$plugin->component = 'availability_ipaddress';
|
$plugin->component = 'availability_ipaddress';
|
||||||
$plugin->version = 2025040401;
|
$plugin->version = 2026101500;
|
||||||
$plugin->release = '4.5.1';
|
$plugin->release = '5.3.2';
|
||||||
$plugin->requires = 2016120500;
|
$plugin->requires = 2016120500;
|
||||||
$plugin->maturity = MATURITY_STABLE;
|
$plugin->maturity = MATURITY_STABLE;
|
||||||
$plugin->supported = [400, 405];
|
$plugin->supported = [400, 503];
|
||||||
+205
-54
@@ -15,11 +15,12 @@ YUI.add('moodle-availability_ipaddress-form', function (Y, NAME) {
|
|||||||
*/
|
*/
|
||||||
M.availability_ipaddress = M.availability_ipaddress || {};
|
M.availability_ipaddress = M.availability_ipaddress || {};
|
||||||
|
|
||||||
|
// A single IPv4 octet (0-255) without leading zeros.
|
||||||
|
M.availability_ipaddress.octet = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)';
|
||||||
|
|
||||||
// MIT https://github.com/sindresorhus/ip-regex
|
// MIT https://github.com/sindresorhus/ip-regex
|
||||||
// Advanced ip-address regex for validating.
|
// IPv6 address, without anchors so it can be combined.
|
||||||
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]' +
|
M.availability_ipaddress.v6 = "((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]" +
|
||||||
'[0-9]|[1-9][0-9]|[0-9])){3}';
|
|
||||||
M.availability_ipaddress.v6 = "^((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]" +
|
|
||||||
"\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}" +
|
"\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}" +
|
||||||
"(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4})" +
|
"(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4})" +
|
||||||
"{1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(:[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]" +
|
"{1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(:[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]" +
|
||||||
@@ -29,7 +30,7 @@ M.availability_ipaddress.v6 = "^((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|
|
|||||||
"\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(:[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]" +
|
"\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(:[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]" +
|
||||||
"|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,6}|:)|(?::" +
|
"|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,6}|:)|(?::" +
|
||||||
"((?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}" +
|
"((?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}" +
|
||||||
"|(?::[a-fA-F\\d]{1,4}){1,7}|:)))(%[0-9a-zA-Z]{1,})?";
|
"|(?::[a-fA-F\\d]{1,4}){1,7}|:)))";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class M.availability_ipaddress.form
|
* @class M.availability_ipaddress.form
|
||||||
@@ -43,9 +44,11 @@ M.availability_ipaddress.form = Y.Object(M.core_availability.plugin);
|
|||||||
* @method initInner
|
* @method initInner
|
||||||
* @param {Array} param Array of objects
|
* @param {Array} param Array of objects
|
||||||
*/
|
*/
|
||||||
M.availability_ipaddress.form.initInner = function() {
|
M.availability_ipaddress.form.initInner = function(param) {
|
||||||
"use strict";
|
"use strict";
|
||||||
Y.log('M.availability_ipaddress 1.10');
|
Y.log('M.availability_ipaddress 1.10');
|
||||||
|
// Store predefined ranges from backend.
|
||||||
|
this.predefinedRanges = param || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,24 +83,68 @@ M.availability_ipaddress.form.getValue = function(field, node) {
|
|||||||
*/
|
*/
|
||||||
M.availability_ipaddress.form.getNode = function(json) {
|
M.availability_ipaddress.form.getNode = function(json) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var html, node, root, id;
|
var html, node, root, id, selectId, i, range;
|
||||||
|
|
||||||
// Make sure we work with unique id.
|
// Make sure we work with unique id.
|
||||||
id = 'ipaddresses' + M.availability_ipaddress.form.instId;
|
id = 'ipaddresses' + M.availability_ipaddress.form.instId;
|
||||||
|
selectId = 'predefined' + M.availability_ipaddress.form.instId;
|
||||||
M.availability_ipaddress.form.instId += 1;
|
M.availability_ipaddress.form.instId += 1;
|
||||||
|
|
||||||
// Create HTML structure.
|
// Create HTML structure.
|
||||||
html = '';
|
html = '<div class="availability-ipaddress-container">';
|
||||||
html += '<span class="availability-group"><label for="' + id + '"><span class="p-r-1">' +
|
|
||||||
M.util.get_string('title', 'availability_ipaddress') + ' </span></label>';
|
// Add predefined ranges if available.
|
||||||
html += '<input type="text" placeholder="192.168.178.1,231.54.211.0/20,231.3.56.211" name="ipaddresses" id="' + id + '">';
|
if (this.predefinedRanges && this.predefinedRanges.length > 0) {
|
||||||
node = Y.Node.create('<span class="form-inline">' + html + '</span>');
|
html += '<div class="availability-group">';
|
||||||
|
html += '<label><span class="p-r-1">' +
|
||||||
|
M.util.get_string('use_predefined', 'availability_ipaddress') + '</span></label>';
|
||||||
|
html += '<select name="predefined_ranges" id="' + selectId +
|
||||||
|
'" multiple="multiple" class="form-control" style="min-height: 100px;">';
|
||||||
|
|
||||||
|
for (i = 0; i < this.predefinedRanges.length; i++) {
|
||||||
|
range = this.predefinedRanges[i];
|
||||||
|
html += '<option value="' + range.id + '" data-ipaddresses="' + Y.Escape.html(range.ipaddresses) + '">';
|
||||||
|
html += Y.Escape.html(range.name);
|
||||||
|
html += '</option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</select>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="availability-group" style="margin-top: 10px;">';
|
||||||
|
html += '<label for="' + id + '"><span class="p-r-1">' +
|
||||||
|
M.util.get_string('custom_ipaddress', 'availability_ipaddress') + '</span></label>';
|
||||||
|
} else {
|
||||||
|
html += '<div class="availability-group">';
|
||||||
|
html += '<label for="' + id + '"><span class="p-r-1">' +
|
||||||
|
M.util.get_string('title', 'availability_ipaddress') + '</span></label>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '<input type="text" placeholder="192.168.178.1,231.54.211.0/20,231.3.56.211" name="ipaddresses" id="' +
|
||||||
|
id + '" class="form-control">';
|
||||||
|
html += '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
node = Y.Node.create('<div>' + html + '</div>');
|
||||||
|
|
||||||
// Set initial values, if specified.
|
// Set initial values, if specified.
|
||||||
if (json.ipaddresses !== undefined) {
|
if (json.ipaddresses !== undefined) {
|
||||||
node.one('input[name=ipaddresses]').set('value', json.ipaddresses);
|
node.one('input[name=ipaddresses]').set('value', json.ipaddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set selected predefined ranges if specified.
|
||||||
|
if (json.predefined_ranges !== undefined && this.predefinedRanges && this.predefinedRanges.length > 0) {
|
||||||
|
var select = node.one('select[name=predefined_ranges]');
|
||||||
|
if (select) {
|
||||||
|
json.predefined_ranges.forEach(function(rangeId) {
|
||||||
|
var option = select.one('option[value="' + rangeId + '"]');
|
||||||
|
if (option) {
|
||||||
|
option.set('selected', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add event handlers (first time only).
|
// Add event handlers (first time only).
|
||||||
if (!M.availability_ipaddress.form.addedEvents) {
|
if (!M.availability_ipaddress.form.addedEvents) {
|
||||||
M.availability_ipaddress.form.addedEvents = true;
|
M.availability_ipaddress.form.addedEvents = true;
|
||||||
@@ -106,62 +153,152 @@ M.availability_ipaddress.form.getNode = function(json) {
|
|||||||
// Trigger the updating of the hidden availability data whenever the ipaddress field changes.
|
// Trigger the updating of the hidden availability data whenever the ipaddress field changes.
|
||||||
M.core_availability.form.update();
|
M.core_availability.form.update();
|
||||||
}, '.availability_ipaddress input[name=ipaddresses]');
|
}, '.availability_ipaddress input[name=ipaddresses]');
|
||||||
|
|
||||||
|
root.delegate('change', function() {
|
||||||
|
// Trigger the updating when predefined ranges are selected.
|
||||||
|
M.core_availability.form.update();
|
||||||
|
}, '.availability_ipaddress select[name=predefined_ranges]');
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate ipaddresses
|
* Check if a value is a valid IPv4 address.
|
||||||
*
|
*
|
||||||
* @param {string[]} ipaddresses
|
* @param {string} value
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.isIpv4 = function(value) {
|
||||||
|
'use strict';
|
||||||
|
var octet = M.availability_ipaddress.octet;
|
||||||
|
return new RegExp('^' + octet + '(?:\\.' + octet + '){3}$').test(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a value is a valid IPv6 address that Moodle's cleanremoteaddr() can parse.
|
||||||
|
*
|
||||||
|
* cleanremoteaddr() doesn't handle embedded IPv4 notation reliably and only expands "::"
|
||||||
|
* when it replaces at least two groups, or three at the start or end of the address.
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.isIpv6 = function(value) {
|
||||||
|
'use strict';
|
||||||
|
var groups;
|
||||||
|
|
||||||
|
if (value.indexOf('.') !== -1 || !new RegExp('^' + M.availability_ipaddress.v6 + '$').test(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.indexOf('::') === -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
groups = value.split(':').filter(function(group) {
|
||||||
|
return group !== '';
|
||||||
|
}).length;
|
||||||
|
if (value.indexOf('::') === 0 || value.slice(-2) === '::') {
|
||||||
|
return groups <= 5;
|
||||||
|
}
|
||||||
|
return groups <= 6;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim the same characters as PHP's trim(), which address_in_subnet() uses.
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.trim = function(value) {
|
||||||
|
'use strict';
|
||||||
|
var chars = ' \t\n\r\x00\x0B',
|
||||||
|
start = 0,
|
||||||
|
end = value.length;
|
||||||
|
|
||||||
|
while (start < end && chars.indexOf(value.charAt(start)) !== -1) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
while (end > start && chars.indexOf(value.charAt(end - 1)) !== -1) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
return value.substring(start, end);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a single entry. Only formats that Moodle's address_in_subnet() understands are
|
||||||
|
* accepted, anything else would be silently ignored when the restriction is checked.
|
||||||
|
*
|
||||||
|
* - Address: 192.168.1.1 or 2001:db8::1
|
||||||
|
* - Subnet: 192.168.1.0/24 or 2001:db8::/32
|
||||||
|
* - Range in the last group: 192.168.1.1-255 or 2001:db8::1-ff
|
||||||
|
*
|
||||||
|
* @param {string} entry
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.validateEntry = function(entry) {
|
||||||
|
'use strict';
|
||||||
|
var parts, mask, lastgroup;
|
||||||
|
|
||||||
|
if (entry.indexOf('/') !== -1) {
|
||||||
|
parts = entry.split('/');
|
||||||
|
// Moodle's is_number() rejects a mask with leading zeros.
|
||||||
|
if (parts.length !== 2 || !/^(0|[1-9]\d{0,2})$/.test(parts[1])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mask = parseInt(parts[1], 10);
|
||||||
|
if (M.availability_ipaddress.isIpv4(parts[0])) {
|
||||||
|
return mask <= 32;
|
||||||
|
}
|
||||||
|
return M.availability_ipaddress.isIpv6(parts[0]) && mask <= 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.indexOf('-') !== -1) {
|
||||||
|
parts = entry.split('-');
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The end of the range may not be lower than the start, it would never match.
|
||||||
|
if (M.availability_ipaddress.isIpv4(parts[0])) {
|
||||||
|
return new RegExp('^' + M.availability_ipaddress.octet + '$').test(parts[1]) &&
|
||||||
|
parseInt(parts[1], 10) >= parseInt(parts[0].split('.')[3], 10);
|
||||||
|
}
|
||||||
|
if (M.availability_ipaddress.isIpv6(parts[0])) {
|
||||||
|
lastgroup = parts[0].split(':').pop() || '0';
|
||||||
|
return /^[a-fA-F\d]{1,4}$/.test(parts[1]) && parseInt(parts[1], 16) >= parseInt(lastgroup, 16);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An address ending in ":" is treated as an incomplete prefix by address_in_subnet(), which fails to parse it.
|
||||||
|
return M.availability_ipaddress.isIpv4(entry) || (M.availability_ipaddress.isIpv6(entry) && entry.slice(-1) !== ':');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a comma separated list of ipaddresses.
|
||||||
|
*
|
||||||
|
* @param {string} ipaddresses
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
M.availability_ipaddress.validateIpaddress = function(ipaddresses) {
|
M.availability_ipaddress.validateIpaddress = function(ipaddresses) {
|
||||||
'use strict';
|
'use strict';
|
||||||
Y.log(ipaddresses);
|
var entries, i;
|
||||||
ipaddresses = ipaddresses.split(',');
|
|
||||||
for (var i in ipaddresses) {
|
|
||||||
|
|
||||||
// Test normal ip format.
|
// Return true for empty string - it's valid to have no custom IPs.
|
||||||
// Strict ipv4 check.
|
if (!ipaddresses || M.availability_ipaddress.trim(ipaddresses) === '') {
|
||||||
if (new RegExp(/^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/gm)
|
return true;
|
||||||
.test(ipaddresses[i])) {
|
}
|
||||||
Y.log('Correct ipv4');
|
|
||||||
continue;
|
entries = ipaddresses.split(',');
|
||||||
|
for (i = 0; i < entries.length; i++) {
|
||||||
|
// Whitespace around an entry is allowed, address_in_subnet() trims it as well.
|
||||||
|
if (!M.availability_ipaddress.validateEntry(M.availability_ipaddress.trim(entries[i]))) {
|
||||||
|
Y.log('Incorrect ip-address: ' + entries[i]);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipv4Regex = new RegExp(
|
|
||||||
'^(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)' +
|
|
||||||
'(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}-' +
|
|
||||||
'([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$',
|
|
||||||
'gm'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ipv4Regex.test(ipaddresses[i])) {
|
|
||||||
Y.log('Correct ipv4 range.');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new RegExp(M.availability_ipaddress.v6)
|
|
||||||
.test(ipaddresses[i])) {
|
|
||||||
Y.log('Correct ipv6');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test subnet with a regex.
|
|
||||||
if (new RegExp("^(?:".concat(M.availability_ipaddress.v4 + "\\/(3[0-2]|[12]?[0-9])|(1\\*)", ")|(?:")
|
|
||||||
.concat(M.availability_ipaddress.v6 + "\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])", ")?\\/gm"))
|
|
||||||
.test(ipaddresses[i])) {
|
|
||||||
Y.log('Correct subnet');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Y.log('Incorrect ip');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Y.log('Valid ipaddresses');
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -178,6 +315,20 @@ M.availability_ipaddress.form.fillValue = function(value, node) {
|
|||||||
// with the structure used in the __construct and save functions
|
// with the structure used in the __construct and save functions
|
||||||
// within condition.php.
|
// within condition.php.
|
||||||
value.ipaddresses = this.getValue('ipaddresses', node);
|
value.ipaddresses = this.getValue('ipaddresses', node);
|
||||||
|
|
||||||
|
// Get selected predefined ranges.
|
||||||
|
var select = node.one('select[name=predefined_ranges]');
|
||||||
|
if (select) {
|
||||||
|
var selectedRanges = [];
|
||||||
|
select.get('options').each(function(option) {
|
||||||
|
if (option.get('selected')) {
|
||||||
|
selectedRanges.push(parseInt(option.get('value')));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (selectedRanges.length > 0) {
|
||||||
|
value.predefined_ranges = selectedRanges;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -190,7 +341,7 @@ M.availability_ipaddress.form.fillErrors = function(errors, node) {
|
|||||||
var value = {};
|
var value = {};
|
||||||
this.fillValue(value, node);
|
this.fillValue(value, node);
|
||||||
|
|
||||||
// Basic ipaddresses checks.
|
// Basic ipaddresses checks - an empty value is valid.
|
||||||
if (M.availability_ipaddress.validateIpaddress(value.ipaddresses) === false) {
|
if (M.availability_ipaddress.validateIpaddress(value.ipaddresses) === false) {
|
||||||
errors.push('availability_ipaddress:error_ipaddress');
|
errors.push('availability_ipaddress:error_ipaddress');
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
YUI.add("moodle-availability_ipaddress-form",function(e,d){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="^((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(:[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,3}|:)|(?:[a-fA-F\\d]{1,4}:){3}(?:(:[a-fA-F\\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,4}|:)|(?:[a-fA-F\\d]{1,4}:){2}(?:(:[a-fA-F\\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(:[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,6}|:)|(?::((?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:)))(%[0-9a-zA-Z]{1,})?",M.availability_ipaddress.form=e.Object(M.core_availability.plugin),M.availability_ipaddress.form.initInner=function(){},M.availability_ipaddress.form.getValue=function(d,a){"use strict";d=a.one("input[name="+d+"]").get("value");return M.availability_ipaddress.validateIpaddress(d),d},M.availability_ipaddress.form.getNode=function(d){"use strict";var a,i="ipaddresses"+M.availability_ipaddress.form.instId;return M.availability_ipaddress.form.instId+=1,a="",a+='<span class="availability-group"><label for="'+i+'"><span class="p-r-1">'+M.util.get_string("title","availability_ipaddress")+" </span></label>",i=e.Node.create('<span class="form-inline">'+(a+='<input type="text" placeholder="192.168.178.1,231.54.211.0/20,231.3.56.211" name="ipaddresses" id="'+i+'">')+"</span>"),d.ipaddresses!==undefined&&i.one("input[name=ipaddresses]").set("value",d.ipaddresses),M.availability_ipaddress.form.addedEvents||(M.availability_ipaddress.form.addedEvents=!0,e.one(".availability-field").delegate("valuechange",function(){M.core_availability.form.update()},".availability_ipaddress input[name=ipaddresses]")),i},M.availability_ipaddress.validateIpaddress=function(d){"use strict";for(var a in d=d.split(","))if(!(new RegExp(/^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/gm).test(d[a])||new RegExp("^(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}-([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$","gm").test(d[a])||new RegExp(M.availability_ipaddress.v6).test(d[a])||new RegExp("^(?:".concat(M.availability_ipaddress.v4+"\\/(3[0-2]|[12]?[0-9])|(1\\*)",")|(?:").concat(M.availability_ipaddress.v6+"\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])",")?\\/gm")).test(d[a])))return!1;return!0},M.availability_ipaddress.form.fillValue=function(d,a){d.ipaddresses=this.getValue("ipaddresses",a)},M.availability_ipaddress.form.fillErrors=function(d,a){"use strict";var i={};this.fillValue(i,a),!1===M.availability_ipaddress.validateIpaddress(i.ipaddresses)&&d.push("availability_ipaddress:error_ipaddress")}},"@VERSION@",{requires:["base","node","event","moodle-core_availability-form"]});
|
YUI.add("moodle-availability_ipaddress-form",function(r,i){M.availability_ipaddress=M.availability_ipaddress||{},M.availability_ipaddress.octet="(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)",M.availability_ipaddress.v6="((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(:[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,3}|:)|(?:[a-fA-F\\d]{1,4}:){3}(?:(:[a-fA-F\\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,4}|:)|(?:[a-fA-F\\d]{1,4}:){2}(?:(:[a-fA-F\\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(:[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,6}|:)|(?::((?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:)))",M.availability_ipaddress.form=r.Object(M.core_availability.plugin),M.availability_ipaddress.form.initInner=function(i){"use strict";this.predefinedRanges=i||[]},M.availability_ipaddress.form.getValue=function(i,a){"use strict";i=a.one("input[name="+i+"]").get("value");return M.availability_ipaddress.validateIpaddress(i),i},M.availability_ipaddress.form.getNode=function(i){"use strict";var a,e,d,s,t="ipaddresses"+M.availability_ipaddress.form.instId,l="predefined"+M.availability_ipaddress.form.instId;if(M.availability_ipaddress.form.instId+=1,a='<div class="availability-ipaddress-container">',this.predefinedRanges&&0<this.predefinedRanges.length){for(a=(a+='<div class="availability-group">')+('<label><span class="p-r-1">'+M.util.get_string("use_predefined","availability_ipaddress")+"</span></label>")+('<select name="predefined_ranges" id="'+l+'" multiple="multiple" class="form-control" style="min-height: 100px;">'),e=0;e<this.predefinedRanges.length;e++)a=(a+='<option value="'+(d=this.predefinedRanges[e]).id+'" data-ipaddresses="'+r.Escape.html(d.ipaddresses)+'">')+r.Escape.html(d.name)+"</option>";a=(a=(a+="</select>")+"</div>"+'<div class="availability-group" style="margin-top: 10px;">')+('<label for="'+t+'"><span class="p-r-1">'+M.util.get_string("custom_ipaddress","availability_ipaddress")+"</span></label>")}else a=(a+='<div class="availability-group">')+'<label for="'+t+'"><span class="p-r-1">'+M.util.get_string("title","availability_ipaddress")+"</span></label>";return l=r.Node.create("<div>"+(a=(a+='<input type="text" placeholder="192.168.178.1,231.54.211.0/20,231.3.56.211" name="ipaddresses" id="'+t+'" class="form-control">')+"</div>"+"</div>")+"</div>"),i.ipaddresses!==undefined&&l.one("input[name=ipaddresses]").set("value",i.ipaddresses),i.predefined_ranges!==undefined&&this.predefinedRanges&&0<this.predefinedRanges.length&&(s=l.one("select[name=predefined_ranges]"))&&i.predefined_ranges.forEach(function(i){i=s.one('option[value="'+i+'"]');i&&i.set("selected",!0)}),M.availability_ipaddress.form.addedEvents||(M.availability_ipaddress.form.addedEvents=!0,(t=r.one(".availability-field")).delegate("valuechange",function(){M.core_availability.form.update()},".availability_ipaddress input[name=ipaddresses]"),t.delegate("change",function(){M.core_availability.form.update()},".availability_ipaddress select[name=predefined_ranges]")),l},M.availability_ipaddress.isIpv4=function(i){"use strict";var a=M.availability_ipaddress.octet;return new RegExp("^"+a+"(?:\\."+a+"){3}$").test(i)},M.availability_ipaddress.isIpv6=function(i){"use strict";var a;return!(-1!==i.indexOf(".")||!new RegExp("^"+M.availability_ipaddress.v6+"$").test(i))&&(-1===i.indexOf("::")||(a=i.split(":").filter(function(i){return""!==i}).length,0===i.indexOf("::")||"::"===i.slice(-2)?a<=5:a<=6))},M.availability_ipaddress.trim=function(i){"use strict";for(var a=" \t\n\r\0\x0B",e=0,d=i.length;e<d&&-1!==a.indexOf(i.charAt(e));)e++;for(;e<d&&-1!==a.indexOf(i.charAt(d-1));)d--;return i.substring(e,d)},M.availability_ipaddress.validateEntry=function(i){"use strict";var a,e;return-1!==i.indexOf("/")?!(2!==(a=i.split("/")).length||!/^(0|[1-9]\d{0,2})$/.test(a[1]))&&(e=parseInt(a[1],10),M.availability_ipaddress.isIpv4(a[0])?e<=32:M.availability_ipaddress.isIpv6(a[0])&&e<=128):-1!==i.indexOf("-")?2===(a=i.split("-")).length&&(M.availability_ipaddress.isIpv4(a[0])?new RegExp("^"+M.availability_ipaddress.octet+"$").test(a[1])&&parseInt(a[1],10)>=parseInt(a[0].split(".")[3],10):!!M.availability_ipaddress.isIpv6(a[0])&&(e=a[0].split(":").pop()||"0",/^[a-fA-F\d]{1,4}$/Line truncated
|
||||||
+204
-47
@@ -15,11 +15,12 @@ YUI.add('moodle-availability_ipaddress-form', function (Y, NAME) {
|
|||||||
*/
|
*/
|
||||||
M.availability_ipaddress = M.availability_ipaddress || {};
|
M.availability_ipaddress = M.availability_ipaddress || {};
|
||||||
|
|
||||||
|
// A single IPv4 octet (0-255) without leading zeros.
|
||||||
|
M.availability_ipaddress.octet = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)';
|
||||||
|
|
||||||
// MIT https://github.com/sindresorhus/ip-regex
|
// MIT https://github.com/sindresorhus/ip-regex
|
||||||
// Advanced ip-address regex for validating.
|
// IPv6 address, without anchors so it can be combined.
|
||||||
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]' +
|
M.availability_ipaddress.v6 = "((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]" +
|
||||||
'[0-9]|[1-9][0-9]|[0-9])){3}';
|
|
||||||
M.availability_ipaddress.v6 = "^((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]" +
|
|
||||||
"\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}" +
|
"\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}" +
|
||||||
"(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4})" +
|
"(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4})" +
|
||||||
"{1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(:[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]" +
|
"{1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(:[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]" +
|
||||||
@@ -29,7 +30,7 @@ M.availability_ipaddress.v6 = "^((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|
|
|||||||
"\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(:[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]" +
|
"\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(:[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]" +
|
||||||
"|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,6}|:)|(?::" +
|
"|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,6}|:)|(?::" +
|
||||||
"((?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}" +
|
"((?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}" +
|
||||||
"|(?::[a-fA-F\\d]{1,4}){1,7}|:)))(%[0-9a-zA-Z]{1,})?";
|
"|(?::[a-fA-F\\d]{1,4}){1,7}|:)))";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class M.availability_ipaddress.form
|
* @class M.availability_ipaddress.form
|
||||||
@@ -43,8 +44,10 @@ M.availability_ipaddress.form = Y.Object(M.core_availability.plugin);
|
|||||||
* @method initInner
|
* @method initInner
|
||||||
* @param {Array} param Array of objects
|
* @param {Array} param Array of objects
|
||||||
*/
|
*/
|
||||||
M.availability_ipaddress.form.initInner = function() {
|
M.availability_ipaddress.form.initInner = function(param) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
// Store predefined ranges from backend.
|
||||||
|
this.predefinedRanges = param || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,24 +80,68 @@ M.availability_ipaddress.form.getValue = function(field, node) {
|
|||||||
*/
|
*/
|
||||||
M.availability_ipaddress.form.getNode = function(json) {
|
M.availability_ipaddress.form.getNode = function(json) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var html, node, root, id;
|
var html, node, root, id, selectId, i, range;
|
||||||
|
|
||||||
// Make sure we work with unique id.
|
// Make sure we work with unique id.
|
||||||
id = 'ipaddresses' + M.availability_ipaddress.form.instId;
|
id = 'ipaddresses' + M.availability_ipaddress.form.instId;
|
||||||
|
selectId = 'predefined' + M.availability_ipaddress.form.instId;
|
||||||
M.availability_ipaddress.form.instId += 1;
|
M.availability_ipaddress.form.instId += 1;
|
||||||
|
|
||||||
// Create HTML structure.
|
// Create HTML structure.
|
||||||
html = '';
|
html = '<div class="availability-ipaddress-container">';
|
||||||
html += '<span class="availability-group"><label for="' + id + '"><span class="p-r-1">' +
|
|
||||||
M.util.get_string('title', 'availability_ipaddress') + ' </span></label>';
|
// Add predefined ranges if available.
|
||||||
html += '<input type="text" placeholder="192.168.178.1,231.54.211.0/20,231.3.56.211" name="ipaddresses" id="' + id + '">';
|
if (this.predefinedRanges && this.predefinedRanges.length > 0) {
|
||||||
node = Y.Node.create('<span class="form-inline">' + html + '</span>');
|
html += '<div class="availability-group">';
|
||||||
|
html += '<label><span class="p-r-1">' +
|
||||||
|
M.util.get_string('use_predefined', 'availability_ipaddress') + '</span></label>';
|
||||||
|
html += '<select name="predefined_ranges" id="' + selectId +
|
||||||
|
'" multiple="multiple" class="form-control" style="min-height: 100px;">';
|
||||||
|
|
||||||
|
for (i = 0; i < this.predefinedRanges.length; i++) {
|
||||||
|
range = this.predefinedRanges[i];
|
||||||
|
html += '<option value="' + range.id + '" data-ipaddresses="' + Y.Escape.html(range.ipaddresses) + '">';
|
||||||
|
html += Y.Escape.html(range.name);
|
||||||
|
html += '</option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</select>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="availability-group" style="margin-top: 10px;">';
|
||||||
|
html += '<label for="' + id + '"><span class="p-r-1">' +
|
||||||
|
M.util.get_string('custom_ipaddress', 'availability_ipaddress') + '</span></label>';
|
||||||
|
} else {
|
||||||
|
html += '<div class="availability-group">';
|
||||||
|
html += '<label for="' + id + '"><span class="p-r-1">' +
|
||||||
|
M.util.get_string('title', 'availability_ipaddress') + '</span></label>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '<input type="text" placeholder="192.168.178.1,231.54.211.0/20,231.3.56.211" name="ipaddresses" id="' +
|
||||||
|
id + '" class="form-control">';
|
||||||
|
html += '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
node = Y.Node.create('<div>' + html + '</div>');
|
||||||
|
|
||||||
// Set initial values, if specified.
|
// Set initial values, if specified.
|
||||||
if (json.ipaddresses !== undefined) {
|
if (json.ipaddresses !== undefined) {
|
||||||
node.one('input[name=ipaddresses]').set('value', json.ipaddresses);
|
node.one('input[name=ipaddresses]').set('value', json.ipaddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set selected predefined ranges if specified.
|
||||||
|
if (json.predefined_ranges !== undefined && this.predefinedRanges && this.predefinedRanges.length > 0) {
|
||||||
|
var select = node.one('select[name=predefined_ranges]');
|
||||||
|
if (select) {
|
||||||
|
json.predefined_ranges.forEach(function(rangeId) {
|
||||||
|
var option = select.one('option[value="' + rangeId + '"]');
|
||||||
|
if (option) {
|
||||||
|
option.set('selected', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add event handlers (first time only).
|
// Add event handlers (first time only).
|
||||||
if (!M.availability_ipaddress.form.addedEvents) {
|
if (!M.availability_ipaddress.form.addedEvents) {
|
||||||
M.availability_ipaddress.form.addedEvents = true;
|
M.availability_ipaddress.form.addedEvents = true;
|
||||||
@@ -103,53 +150,149 @@ M.availability_ipaddress.form.getNode = function(json) {
|
|||||||
// Trigger the updating of the hidden availability data whenever the ipaddress field changes.
|
// Trigger the updating of the hidden availability data whenever the ipaddress field changes.
|
||||||
M.core_availability.form.update();
|
M.core_availability.form.update();
|
||||||
}, '.availability_ipaddress input[name=ipaddresses]');
|
}, '.availability_ipaddress input[name=ipaddresses]');
|
||||||
|
|
||||||
|
root.delegate('change', function() {
|
||||||
|
// Trigger the updating when predefined ranges are selected.
|
||||||
|
M.core_availability.form.update();
|
||||||
|
}, '.availability_ipaddress select[name=predefined_ranges]');
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate ipaddresses
|
* Check if a value is a valid IPv4 address.
|
||||||
*
|
*
|
||||||
* @param {string[]} ipaddresses
|
* @param {string} value
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.isIpv4 = function(value) {
|
||||||
|
'use strict';
|
||||||
|
var octet = M.availability_ipaddress.octet;
|
||||||
|
return new RegExp('^' + octet + '(?:\\.' + octet + '){3}$').test(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a value is a valid IPv6 address that Moodle's cleanremoteaddr() can parse.
|
||||||
|
*
|
||||||
|
* cleanremoteaddr() doesn't handle embedded IPv4 notation reliably and only expands "::"
|
||||||
|
* when it replaces at least two groups, or three at the start or end of the address.
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.isIpv6 = function(value) {
|
||||||
|
'use strict';
|
||||||
|
var groups;
|
||||||
|
|
||||||
|
if (value.indexOf('.') !== -1 || !new RegExp('^' + M.availability_ipaddress.v6 + '$').test(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.indexOf('::') === -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
groups = value.split(':').filter(function(group) {
|
||||||
|
return group !== '';
|
||||||
|
}).length;
|
||||||
|
if (value.indexOf('::') === 0 || value.slice(-2) === '::') {
|
||||||
|
return groups <= 5;
|
||||||
|
}
|
||||||
|
return groups <= 6;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim the same characters as PHP's trim(), which address_in_subnet() uses.
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.trim = function(value) {
|
||||||
|
'use strict';
|
||||||
|
var chars = ' \t\n\r\x00\x0B',
|
||||||
|
start = 0,
|
||||||
|
end = value.length;
|
||||||
|
|
||||||
|
while (start < end && chars.indexOf(value.charAt(start)) !== -1) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
while (end > start && chars.indexOf(value.charAt(end - 1)) !== -1) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
return value.substring(start, end);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a single entry. Only formats that Moodle's address_in_subnet() understands are
|
||||||
|
* accepted, anything else would be silently ignored when the restriction is checked.
|
||||||
|
*
|
||||||
|
* - Address: 192.168.1.1 or 2001:db8::1
|
||||||
|
* - Subnet: 192.168.1.0/24 or 2001:db8::/32
|
||||||
|
* - Range in the last group: 192.168.1.1-255 or 2001:db8::1-ff
|
||||||
|
*
|
||||||
|
* @param {string} entry
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.validateEntry = function(entry) {
|
||||||
|
'use strict';
|
||||||
|
var parts, mask, lastgroup;
|
||||||
|
|
||||||
|
if (entry.indexOf('/') !== -1) {
|
||||||
|
parts = entry.split('/');
|
||||||
|
// Moodle's is_number() rejects a mask with leading zeros.
|
||||||
|
if (parts.length !== 2 || !/^(0|[1-9]\d{0,2})$/.test(parts[1])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mask = parseInt(parts[1], 10);
|
||||||
|
if (M.availability_ipaddress.isIpv4(parts[0])) {
|
||||||
|
return mask <= 32;
|
||||||
|
}
|
||||||
|
return M.availability_ipaddress.isIpv6(parts[0]) && mask <= 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.indexOf('-') !== -1) {
|
||||||
|
parts = entry.split('-');
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The end of the range may not be lower than the start, it would never match.
|
||||||
|
if (M.availability_ipaddress.isIpv4(parts[0])) {
|
||||||
|
return new RegExp('^' + M.availability_ipaddress.octet + '$').test(parts[1]) &&
|
||||||
|
parseInt(parts[1], 10) >= parseInt(parts[0].split('.')[3], 10);
|
||||||
|
}
|
||||||
|
if (M.availability_ipaddress.isIpv6(parts[0])) {
|
||||||
|
lastgroup = parts[0].split(':').pop() || '0';
|
||||||
|
return /^[a-fA-F\d]{1,4}$/.test(parts[1]) && parseInt(parts[1], 16) >= parseInt(lastgroup, 16);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An address ending in ":" is treated as an incomplete prefix by address_in_subnet(), which fails to parse it.
|
||||||
|
return M.availability_ipaddress.isIpv4(entry) || (M.availability_ipaddress.isIpv6(entry) && entry.slice(-1) !== ':');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a comma separated list of ipaddresses.
|
||||||
|
*
|
||||||
|
* @param {string} ipaddresses
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
M.availability_ipaddress.validateIpaddress = function(ipaddresses) {
|
M.availability_ipaddress.validateIpaddress = function(ipaddresses) {
|
||||||
'use strict';
|
'use strict';
|
||||||
ipaddresses = ipaddresses.split(',');
|
var entries, i;
|
||||||
for (var i in ipaddresses) {
|
|
||||||
|
|
||||||
// Test normal ip format.
|
// Return true for empty string - it's valid to have no custom IPs.
|
||||||
// Strict ipv4 check.
|
if (!ipaddresses || M.availability_ipaddress.trim(ipaddresses) === '') {
|
||||||
if (new RegExp(/^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/gm)
|
return true;
|
||||||
.test(ipaddresses[i])) {
|
}
|
||||||
continue;
|
|
||||||
|
entries = ipaddresses.split(',');
|
||||||
|
for (i = 0; i < entries.length; i++) {
|
||||||
|
// Whitespace around an entry is allowed, address_in_subnet() trims it as well.
|
||||||
|
if (!M.availability_ipaddress.validateEntry(M.availability_ipaddress.trim(entries[i]))) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipv4Regex = new RegExp(
|
|
||||||
'^(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)' +
|
|
||||||
'(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}-' +
|
|
||||||
'([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$',
|
|
||||||
'gm'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ipv4Regex.test(ipaddresses[i])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new RegExp(M.availability_ipaddress.v6)
|
|
||||||
.test(ipaddresses[i])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test subnet with a regex.
|
|
||||||
if (new RegExp("^(?:".concat(M.availability_ipaddress.v4 + "\\/(3[0-2]|[12]?[0-9])|(1\\*)", ")|(?:")
|
|
||||||
.concat(M.availability_ipaddress.v6 + "\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])", ")?\\/gm"))
|
|
||||||
.test(ipaddresses[i])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -168,6 +311,20 @@ M.availability_ipaddress.form.fillValue = function(value, node) {
|
|||||||
// with the structure used in the __construct and save functions
|
// with the structure used in the __construct and save functions
|
||||||
// within condition.php.
|
// within condition.php.
|
||||||
value.ipaddresses = this.getValue('ipaddresses', node);
|
value.ipaddresses = this.getValue('ipaddresses', node);
|
||||||
|
|
||||||
|
// Get selected predefined ranges.
|
||||||
|
var select = node.one('select[name=predefined_ranges]');
|
||||||
|
if (select) {
|
||||||
|
var selectedRanges = [];
|
||||||
|
select.get('options').each(function(option) {
|
||||||
|
if (option.get('selected')) {
|
||||||
|
selectedRanges.push(parseInt(option.get('value')));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (selectedRanges.length > 0) {
|
||||||
|
value.predefined_ranges = selectedRanges;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,7 +337,7 @@ M.availability_ipaddress.form.fillErrors = function(errors, node) {
|
|||||||
var value = {};
|
var value = {};
|
||||||
this.fillValue(value, node);
|
this.fillValue(value, node);
|
||||||
|
|
||||||
// Basic ipaddresses checks.
|
// Basic ipaddresses checks - an empty value is valid.
|
||||||
if (M.availability_ipaddress.validateIpaddress(value.ipaddresses) === false) {
|
if (M.availability_ipaddress.validateIpaddress(value.ipaddresses) === false) {
|
||||||
errors.push('availability_ipaddress:error_ipaddress');
|
errors.push('availability_ipaddress:error_ipaddress');
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+205
-54
@@ -13,11 +13,12 @@
|
|||||||
*/
|
*/
|
||||||
M.availability_ipaddress = M.availability_ipaddress || {};
|
M.availability_ipaddress = M.availability_ipaddress || {};
|
||||||
|
|
||||||
|
// A single IPv4 octet (0-255) without leading zeros.
|
||||||
|
M.availability_ipaddress.octet = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)';
|
||||||
|
|
||||||
// MIT https://github.com/sindresorhus/ip-regex
|
// MIT https://github.com/sindresorhus/ip-regex
|
||||||
// Advanced ip-address regex for validating.
|
// IPv6 address, without anchors so it can be combined.
|
||||||
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]' +
|
M.availability_ipaddress.v6 = "((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]" +
|
||||||
'[0-9]|[1-9][0-9]|[0-9])){3}';
|
|
||||||
M.availability_ipaddress.v6 = "^((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]" +
|
|
||||||
"\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}" +
|
"\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}" +
|
||||||
"(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4})" +
|
"(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4})" +
|
||||||
"{1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(:[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]" +
|
"{1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(:[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]" +
|
||||||
@@ -27,7 +28,7 @@ M.availability_ipaddress.v6 = "^((?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|
|
|||||||
"\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(:[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]" +
|
"\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(:[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]" +
|
||||||
"|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,6}|:)|(?::" +
|
"|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(:[a-fA-F\\d]{1,4}){1,6}|:)|(?::" +
|
||||||
"((?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}" +
|
"((?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}" +
|
||||||
"|(?::[a-fA-F\\d]{1,4}){1,7}|:)))(%[0-9a-zA-Z]{1,})?";
|
"|(?::[a-fA-F\\d]{1,4}){1,7}|:)))";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class M.availability_ipaddress.form
|
* @class M.availability_ipaddress.form
|
||||||
@@ -41,9 +42,11 @@ M.availability_ipaddress.form = Y.Object(M.core_availability.plugin);
|
|||||||
* @method initInner
|
* @method initInner
|
||||||
* @param {Array} param Array of objects
|
* @param {Array} param Array of objects
|
||||||
*/
|
*/
|
||||||
M.availability_ipaddress.form.initInner = function() {
|
M.availability_ipaddress.form.initInner = function(param) {
|
||||||
"use strict";
|
"use strict";
|
||||||
Y.log('M.availability_ipaddress 1.10');
|
Y.log('M.availability_ipaddress 1.10');
|
||||||
|
// Store predefined ranges from backend.
|
||||||
|
this.predefinedRanges = param || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,24 +81,68 @@ M.availability_ipaddress.form.getValue = function(field, node) {
|
|||||||
*/
|
*/
|
||||||
M.availability_ipaddress.form.getNode = function(json) {
|
M.availability_ipaddress.form.getNode = function(json) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var html, node, root, id;
|
var html, node, root, id, selectId, i, range;
|
||||||
|
|
||||||
// Make sure we work with unique id.
|
// Make sure we work with unique id.
|
||||||
id = 'ipaddresses' + M.availability_ipaddress.form.instId;
|
id = 'ipaddresses' + M.availability_ipaddress.form.instId;
|
||||||
|
selectId = 'predefined' + M.availability_ipaddress.form.instId;
|
||||||
M.availability_ipaddress.form.instId += 1;
|
M.availability_ipaddress.form.instId += 1;
|
||||||
|
|
||||||
// Create HTML structure.
|
// Create HTML structure.
|
||||||
html = '';
|
html = '<div class="availability-ipaddress-container">';
|
||||||
html += '<span class="availability-group"><label for="' + id + '"><span class="p-r-1">' +
|
|
||||||
M.util.get_string('title', 'availability_ipaddress') + ' </span></label>';
|
// Add predefined ranges if available.
|
||||||
html += '<input type="text" placeholder="192.168.178.1,231.54.211.0/20,231.3.56.211" name="ipaddresses" id="' + id + '">';
|
if (this.predefinedRanges && this.predefinedRanges.length > 0) {
|
||||||
node = Y.Node.create('<span class="form-inline">' + html + '</span>');
|
html += '<div class="availability-group">';
|
||||||
|
html += '<label><span class="p-r-1">' +
|
||||||
|
M.util.get_string('use_predefined', 'availability_ipaddress') + '</span></label>';
|
||||||
|
html += '<select name="predefined_ranges" id="' + selectId +
|
||||||
|
'" multiple="multiple" class="form-control" style="min-height: 100px;">';
|
||||||
|
|
||||||
|
for (i = 0; i < this.predefinedRanges.length; i++) {
|
||||||
|
range = this.predefinedRanges[i];
|
||||||
|
html += '<option value="' + range.id + '" data-ipaddresses="' + Y.Escape.html(range.ipaddresses) + '">';
|
||||||
|
html += Y.Escape.html(range.name);
|
||||||
|
html += '</option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</select>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="availability-group" style="margin-top: 10px;">';
|
||||||
|
html += '<label for="' + id + '"><span class="p-r-1">' +
|
||||||
|
M.util.get_string('custom_ipaddress', 'availability_ipaddress') + '</span></label>';
|
||||||
|
} else {
|
||||||
|
html += '<div class="availability-group">';
|
||||||
|
html += '<label for="' + id + '"><span class="p-r-1">' +
|
||||||
|
M.util.get_string('title', 'availability_ipaddress') + '</span></label>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '<input type="text" placeholder="192.168.178.1,231.54.211.0/20,231.3.56.211" name="ipaddresses" id="' +
|
||||||
|
id + '" class="form-control">';
|
||||||
|
html += '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
node = Y.Node.create('<div>' + html + '</div>');
|
||||||
|
|
||||||
// Set initial values, if specified.
|
// Set initial values, if specified.
|
||||||
if (json.ipaddresses !== undefined) {
|
if (json.ipaddresses !== undefined) {
|
||||||
node.one('input[name=ipaddresses]').set('value', json.ipaddresses);
|
node.one('input[name=ipaddresses]').set('value', json.ipaddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set selected predefined ranges if specified.
|
||||||
|
if (json.predefined_ranges !== undefined && this.predefinedRanges && this.predefinedRanges.length > 0) {
|
||||||
|
var select = node.one('select[name=predefined_ranges]');
|
||||||
|
if (select) {
|
||||||
|
json.predefined_ranges.forEach(function(rangeId) {
|
||||||
|
var option = select.one('option[value="' + rangeId + '"]');
|
||||||
|
if (option) {
|
||||||
|
option.set('selected', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add event handlers (first time only).
|
// Add event handlers (first time only).
|
||||||
if (!M.availability_ipaddress.form.addedEvents) {
|
if (!M.availability_ipaddress.form.addedEvents) {
|
||||||
M.availability_ipaddress.form.addedEvents = true;
|
M.availability_ipaddress.form.addedEvents = true;
|
||||||
@@ -104,62 +151,152 @@ M.availability_ipaddress.form.getNode = function(json) {
|
|||||||
// Trigger the updating of the hidden availability data whenever the ipaddress field changes.
|
// Trigger the updating of the hidden availability data whenever the ipaddress field changes.
|
||||||
M.core_availability.form.update();
|
M.core_availability.form.update();
|
||||||
}, '.availability_ipaddress input[name=ipaddresses]');
|
}, '.availability_ipaddress input[name=ipaddresses]');
|
||||||
|
|
||||||
|
root.delegate('change', function() {
|
||||||
|
// Trigger the updating when predefined ranges are selected.
|
||||||
|
M.core_availability.form.update();
|
||||||
|
}, '.availability_ipaddress select[name=predefined_ranges]');
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate ipaddresses
|
* Check if a value is a valid IPv4 address.
|
||||||
*
|
*
|
||||||
* @param {string[]} ipaddresses
|
* @param {string} value
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.isIpv4 = function(value) {
|
||||||
|
'use strict';
|
||||||
|
var octet = M.availability_ipaddress.octet;
|
||||||
|
return new RegExp('^' + octet + '(?:\\.' + octet + '){3}$').test(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a value is a valid IPv6 address that Moodle's cleanremoteaddr() can parse.
|
||||||
|
*
|
||||||
|
* cleanremoteaddr() doesn't handle embedded IPv4 notation reliably and only expands "::"
|
||||||
|
* when it replaces at least two groups, or three at the start or end of the address.
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.isIpv6 = function(value) {
|
||||||
|
'use strict';
|
||||||
|
var groups;
|
||||||
|
|
||||||
|
if (value.indexOf('.') !== -1 || !new RegExp('^' + M.availability_ipaddress.v6 + '$').test(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.indexOf('::') === -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
groups = value.split(':').filter(function(group) {
|
||||||
|
return group !== '';
|
||||||
|
}).length;
|
||||||
|
if (value.indexOf('::') === 0 || value.slice(-2) === '::') {
|
||||||
|
return groups <= 5;
|
||||||
|
}
|
||||||
|
return groups <= 6;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim the same characters as PHP's trim(), which address_in_subnet() uses.
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.trim = function(value) {
|
||||||
|
'use strict';
|
||||||
|
var chars = ' \t\n\r\x00\x0B',
|
||||||
|
start = 0,
|
||||||
|
end = value.length;
|
||||||
|
|
||||||
|
while (start < end && chars.indexOf(value.charAt(start)) !== -1) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
while (end > start && chars.indexOf(value.charAt(end - 1)) !== -1) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
return value.substring(start, end);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a single entry. Only formats that Moodle's address_in_subnet() understands are
|
||||||
|
* accepted, anything else would be silently ignored when the restriction is checked.
|
||||||
|
*
|
||||||
|
* - Address: 192.168.1.1 or 2001:db8::1
|
||||||
|
* - Subnet: 192.168.1.0/24 or 2001:db8::/32
|
||||||
|
* - Range in the last group: 192.168.1.1-255 or 2001:db8::1-ff
|
||||||
|
*
|
||||||
|
* @param {string} entry
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
M.availability_ipaddress.validateEntry = function(entry) {
|
||||||
|
'use strict';
|
||||||
|
var parts, mask, lastgroup;
|
||||||
|
|
||||||
|
if (entry.indexOf('/') !== -1) {
|
||||||
|
parts = entry.split('/');
|
||||||
|
// Moodle's is_number() rejects a mask with leading zeros.
|
||||||
|
if (parts.length !== 2 || !/^(0|[1-9]\d{0,2})$/.test(parts[1])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mask = parseInt(parts[1], 10);
|
||||||
|
if (M.availability_ipaddress.isIpv4(parts[0])) {
|
||||||
|
return mask <= 32;
|
||||||
|
}
|
||||||
|
return M.availability_ipaddress.isIpv6(parts[0]) && mask <= 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.indexOf('-') !== -1) {
|
||||||
|
parts = entry.split('-');
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The end of the range may not be lower than the start, it would never match.
|
||||||
|
if (M.availability_ipaddress.isIpv4(parts[0])) {
|
||||||
|
return new RegExp('^' + M.availability_ipaddress.octet + '$').test(parts[1]) &&
|
||||||
|
parseInt(parts[1], 10) >= parseInt(parts[0].split('.')[3], 10);
|
||||||
|
}
|
||||||
|
if (M.availability_ipaddress.isIpv6(parts[0])) {
|
||||||
|
lastgroup = parts[0].split(':').pop() || '0';
|
||||||
|
return /^[a-fA-F\d]{1,4}$/.test(parts[1]) && parseInt(parts[1], 16) >= parseInt(lastgroup, 16);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An address ending in ":" is treated as an incomplete prefix by address_in_subnet(), which fails to parse it.
|
||||||
|
return M.availability_ipaddress.isIpv4(entry) || (M.availability_ipaddress.isIpv6(entry) && entry.slice(-1) !== ':');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a comma separated list of ipaddresses.
|
||||||
|
*
|
||||||
|
* @param {string} ipaddresses
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
M.availability_ipaddress.validateIpaddress = function(ipaddresses) {
|
M.availability_ipaddress.validateIpaddress = function(ipaddresses) {
|
||||||
'use strict';
|
'use strict';
|
||||||
Y.log(ipaddresses);
|
var entries, i;
|
||||||
ipaddresses = ipaddresses.split(',');
|
|
||||||
for (var i in ipaddresses) {
|
|
||||||
|
|
||||||
// Test normal ip format.
|
// Return true for empty string - it's valid to have no custom IPs.
|
||||||
// Strict ipv4 check.
|
if (!ipaddresses || M.availability_ipaddress.trim(ipaddresses) === '') {
|
||||||
if (new RegExp(/^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/gm)
|
return true;
|
||||||
.test(ipaddresses[i])) {
|
}
|
||||||
Y.log('Correct ipv4');
|
|
||||||
continue;
|
entries = ipaddresses.split(',');
|
||||||
|
for (i = 0; i < entries.length; i++) {
|
||||||
|
// Whitespace around an entry is allowed, address_in_subnet() trims it as well.
|
||||||
|
if (!M.availability_ipaddress.validateEntry(M.availability_ipaddress.trim(entries[i]))) {
|
||||||
|
Y.log('Incorrect ip-address: ' + entries[i]);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipv4Regex = new RegExp(
|
|
||||||
'^(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)' +
|
|
||||||
'(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}-' +
|
|
||||||
'([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$',
|
|
||||||
'gm'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ipv4Regex.test(ipaddresses[i])) {
|
|
||||||
Y.log('Correct ipv4 range.');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new RegExp(M.availability_ipaddress.v6)
|
|
||||||
.test(ipaddresses[i])) {
|
|
||||||
Y.log('Correct ipv6');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test subnet with a regex.
|
|
||||||
if (new RegExp("^(?:".concat(M.availability_ipaddress.v4 + "\\/(3[0-2]|[12]?[0-9])|(1\\*)", ")|(?:")
|
|
||||||
.concat(M.availability_ipaddress.v6 + "\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])", ")?\\/gm"))
|
|
||||||
.test(ipaddresses[i])) {
|
|
||||||
Y.log('Correct subnet');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Y.log('Incorrect ip');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Y.log('Valid ipaddresses');
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,6 +313,20 @@ M.availability_ipaddress.form.fillValue = function(value, node) {
|
|||||||
// with the structure used in the __construct and save functions
|
// with the structure used in the __construct and save functions
|
||||||
// within condition.php.
|
// within condition.php.
|
||||||
value.ipaddresses = this.getValue('ipaddresses', node);
|
value.ipaddresses = this.getValue('ipaddresses', node);
|
||||||
|
|
||||||
|
// Get selected predefined ranges.
|
||||||
|
var select = node.one('select[name=predefined_ranges]');
|
||||||
|
if (select) {
|
||||||
|
var selectedRanges = [];
|
||||||
|
select.get('options').each(function(option) {
|
||||||
|
if (option.get('selected')) {
|
||||||
|
selectedRanges.push(parseInt(option.get('value')));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (selectedRanges.length > 0) {
|
||||||
|
value.predefined_ranges = selectedRanges;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -188,7 +339,7 @@ M.availability_ipaddress.form.fillErrors = function(errors, node) {
|
|||||||
var value = {};
|
var value = {};
|
||||||
this.fillValue(value, node);
|
this.fillValue(value, node);
|
||||||
|
|
||||||
// Basic ipaddresses checks.
|
// Basic ipaddresses checks - an empty value is valid.
|
||||||
if (M.availability_ipaddress.validateIpaddress(value.ipaddresses) === false) {
|
if (M.availability_ipaddress.validateIpaddress(value.ipaddresses) === false) {
|
||||||
errors.push('availability_ipaddress:error_ipaddress');
|
errors.push('availability_ipaddress:error_ipaddress');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user