diff --git a/admin/settings/plugins.php b/admin/settings/plugins.php
index 6abbe555e7b..c55b1f12dc3 100644
--- a/admin/settings/plugins.php
+++ b/admin/settings/plugins.php
@@ -123,43 +123,6 @@ if ($hassiteconfig) {
$temp->add($setting);
$ADMIN->add('authsettings', $temp);
- $options = array(
- 0 => get_string('no'),
- 1 => get_string('yes')
- );
- $url = new moodle_url('/admin/settings.php?section=supportcontact');
- $url = $url->out();
- $setting = new admin_setting_configselect('agedigitalconsentverification',
- new lang_string('agedigitalconsentverification', 'admin'),
- new lang_string('agedigitalconsentverification_desc', 'admin', $url), 0, $options);
- $setting->set_force_ltr(true);
- $temp->add($setting);
-
- $setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
- new lang_string('ageofdigitalconsentmap', 'admin'),
- new lang_string('ageofdigitalconsentmap_desc', 'admin'),
- // See {@link https://gdpr-info.eu/art-8-gdpr/}.
- implode(PHP_EOL, [
- '*, 16',
- 'AT, 14',
- 'CZ, 13',
- 'DE, 14',
- 'DK, 13',
- 'ES, 13',
- 'FI, 15',
- 'GB, 13',
- 'HU, 14',
- 'IE, 13',
- 'LT, 16',
- 'LU, 16',
- 'NL, 16',
- 'PL, 13',
- 'SE, 13',
- ]),
- PARAM_RAW
- );
- $temp->add($setting);
-
$temp = new admin_externalpage('authtestsettings', get_string('testsettings', 'core_auth'), new moodle_url("/auth/test_settings.php"), 'moodle/site:config', true);
$ADMIN->add('authsettings', $temp);
diff --git a/admin/settings/privacy.php b/admin/settings/privacy.php
new file mode 100644
index 00000000000..87f8c52589b
--- /dev/null
+++ b/admin/settings/privacy.php
@@ -0,0 +1,80 @@
+.
+
+/**
+ * Adds privacy and policies links to admin tree.
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+ // Privacy settings.
+ $temp = new admin_settingpage('privacysettings', new lang_string('privacysettings', 'admin'));
+
+ $options = array(
+ 0 => get_string('no'),
+ 1 => get_string('yes')
+ );
+ $url = new moodle_url('/admin/settings.php?section=supportcontact');
+ $url = $url->out();
+ $setting = new admin_setting_configselect('agedigitalconsentverification',
+ new lang_string('agedigitalconsentverification', 'admin'),
+ new lang_string('agedigitalconsentverification_desc', 'admin', $url), 0, $options);
+ $setting->set_force_ltr(true);
+ $temp->add($setting);
+
+ $setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
+ new lang_string('ageofdigitalconsentmap', 'admin'),
+ new lang_string('ageofdigitalconsentmap_desc', 'admin'),
+ // See {@link https://gdpr-info.eu/art-8-gdpr/}.
+ implode(PHP_EOL, [
+ '*, 16',
+ 'AT, 14',
+ 'CZ, 13',
+ 'DE, 14',
+ 'DK, 13',
+ 'ES, 13',
+ 'FI, 15',
+ 'GB, 13',
+ 'HU, 14',
+ 'IE, 13',
+ 'LT, 16',
+ 'LU, 16',
+ 'NL, 16',
+ 'PL, 13',
+ 'SE, 13',
+ ]),
+ PARAM_RAW
+ );
+ $temp->add($setting);
+
+ $ADMIN->add('privacy', $temp);
+
+ // Policy settings.
+ $temp = new admin_settingpage('policysettings', new lang_string('policysettings', 'admin'));
+ $temp->add(new admin_settings_sitepolicy_handler_select('sitepolicyhandler', new lang_string('sitepolicyhandler', 'core_admin'),
+ new lang_string('sitepolicyhandler_desc', 'core_admin')));
+ $temp->add(new admin_setting_configtext('sitepolicy', new lang_string('sitepolicy', 'core_admin'),
+ new lang_string('sitepolicy_help', 'core_admin'), '', PARAM_RAW));
+ $temp->add(new admin_setting_configtext('sitepolicyguest', new lang_string('sitepolicyguest', 'core_admin'),
+ new lang_string('sitepolicyguest_help', 'core_admin'), (isset($CFG->sitepolicy) ? $CFG->sitepolicy : ''), PARAM_RAW));
+
+ $ADMIN->add('privacy', $temp);
+}
diff --git a/admin/settings/security.php b/admin/settings/security.php
index 658a34e9545..a6ac61f7667 100644
--- a/admin/settings/security.php
+++ b/admin/settings/security.php
@@ -55,20 +55,6 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
$temp->add(new admin_setting_configcheckbox('extendedusernamechars', new lang_string('extendedusernamechars', 'admin'), new lang_string('configextendedusernamechars', 'admin'), 0));
- $sitepolicyhandlers = ['' => new lang_string('sitepolicyhandlercore', 'core_admin')];
- foreach (get_plugins_with_function('site_policy_handler') as $ptype => $pnames) {
- foreach ($pnames as $pname => $fname) {
- $sitepolicyhandlers[$ptype.'_'.$pname] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
- ['name' => new lang_string('pluginname', $ptype.'_'.$pname), 'component' => $ptype.'_'.$pname]);
- }
- }
- $temp->add(new admin_setting_configselect('sitepolicyhandler', new lang_string('sitepolicyhandler', 'core_admin'),
- new lang_string('sitepolicyhandler_desc', 'core_admin'), '', $sitepolicyhandlers));
- $temp->add(new admin_setting_configtext('sitepolicy', new lang_string('sitepolicy', 'core_admin'),
- new lang_string('sitepolicy_help', 'core_admin'), '', PARAM_RAW));
- $temp->add(new admin_setting_configtext('sitepolicyguest', new lang_string('sitepolicyguest', 'core_admin'),
- new lang_string('sitepolicyguest_help', 'core_admin'), (isset($CFG->sitepolicy) ? $CFG->sitepolicy : ''), PARAM_RAW));
-
$temp->add(new admin_setting_configcheckbox('extendedusernamechars', new lang_string('extendedusernamechars', 'admin'), new lang_string('configextendedusernamechars', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('keeptagnamecase', new lang_string('keeptagnamecase','admin'),new lang_string('configkeeptagnamecase', 'admin'),'1'));
diff --git a/admin/settings/top.php b/admin/settings/top.php
index 17621cbd671..a049ce9007b 100644
--- a/admin/settings/top.php
+++ b/admin/settings/top.php
@@ -29,6 +29,7 @@ $ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empt
$ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
$ADMIN->add('root', new admin_category('language', new lang_string('language')));
$ADMIN->add('root', new admin_category('modules', new lang_string('plugins', 'admin')));
+$ADMIN->add('root', new admin_category('privacy', new lang_string('privacyandpolicies', 'admin')));
$ADMIN->add('root', new admin_category('security', new lang_string('security','admin')));
$ADMIN->add('root', new admin_category('appearance', new lang_string('appearance','admin')));
$ADMIN->add('root', new admin_category('frontpage', new lang_string('frontpage','admin')));
diff --git a/admin/tool/mobile/classes/api.php b/admin/tool/mobile/classes/api.php
index e70910bfbdc..18d89e5ff37 100644
--- a/admin/tool/mobile/classes/api.php
+++ b/admin/tool/mobile/classes/api.php
@@ -223,7 +223,9 @@ class api {
}
if (empty($section) or $section == 'sitepolicies') {
- $settings->sitepolicy = $CFG->sitepolicy;
+ $manager = new \core_privacy\local\sitepolicy\manager();
+ $settings->sitepolicy = ($sitepolicy = $manager->get_embed_url()) ? $sitepolicy->out(false) : '';
+ $settings->sitepolicyhandler = $CFG->sitepolicyhandler;
$settings->disableuserimages = $CFG->disableuserimages;
}
diff --git a/admin/tool/mobile/tests/externallib_test.php b/admin/tool/mobile/tests/externallib_test.php
index ff1f3c1a829..2a1adb2bf19 100644
--- a/admin/tool/mobile/tests/externallib_test.php
+++ b/admin/tool/mobile/tests/externallib_test.php
@@ -150,6 +150,7 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
array('name' => 'newsitems', 'value' => $SITE->newsitems),
array('name' => 'commentsperpage', 'value' => $CFG->commentsperpage),
array('name' => 'sitepolicy', 'value' => $mysitepolicy),
+ array('name' => 'sitepolicyhandler', 'value' => ''),
array('name' => 'disableuserimages', 'value' => $CFG->disableuserimages),
array('name' => 'mygradesurl', 'value' => user_mygrades_url()->out(false)),
array('name' => 'tool_mobile_forcelogout', 'value' => 0),
diff --git a/auth/email/classes/external.php b/auth/email/classes/external.php
index 76bba9b466f..6a015a9bf9c 100644
--- a/auth/email/classes/external.php
+++ b/auth/email/classes/external.php
@@ -88,8 +88,9 @@ class auth_email_external extends external_api {
if (!empty($CFG->passwordpolicy)) {
$result['passwordpolicy'] = print_password_policy();
}
- if (!empty($CFG->sitepolicy)) {
- $result['sitepolicy'] = $CFG->sitepolicy;
+ $manager = new \core_privacy\local\sitepolicy\manager();
+ if ($sitepolicy = $manager->get_embed_url()) {
+ $result['sitepolicy'] = $sitepolicy->out(false);
}
if (!empty($CFG->defaultcity)) {
$result['defaultcity'] = $CFG->defaultcity;
@@ -287,7 +288,8 @@ class auth_email_external extends external_api {
$data = $params;
$data['email2'] = $data['email'];
// Force policy agreed if a site policy is set. The client is responsible of implementing the interface check.
- if (!empty($CFG->sitepolicy)) {
+ $manager = new \core_privacy\local\sitepolicy\manager();
+ if (!$manager->is_defined()) {
$data['policyagreed'] = 1;
}
unset($data['recaptcharesponse']);
diff --git a/auth/tests/behat/validateagedigitalconsentmap.feature b/auth/tests/behat/validateagedigitalconsentmap.feature
index 00f5165f9c6..9fe1753e427 100644
--- a/auth/tests/behat/validateagedigitalconsentmap.feature
+++ b/auth/tests/behat/validateagedigitalconsentmap.feature
@@ -6,7 +6,7 @@ Feature: Test validation of 'Age of digital consent' setting.
Background:
Given I log in as "admin"
- And I navigate to "Manage authentication" node in "Site administration > Plugins > Authentication"
+ And I navigate to "Privacy settings" node in "Site administration > Privacy and policies"
Scenario: Admin provides valid value for 'Age of digital consent'.
Given I set the field "s__agedigitalconsentmap" to multiline:
diff --git a/auth/upgrade.txt b/auth/upgrade.txt
index fff9fdd86cd..4d8bf95fb64 100644
--- a/auth/upgrade.txt
+++ b/auth/upgrade.txt
@@ -1,6 +1,11 @@
This files describes API changes in /auth/* - plugins,
information provided here is intended especially for developers.
+=== 3.4.2 ===
+
+* Self registration plugins should use core_privacy\local\sitepolicy\manager instead of directly checking
+ $CFG->sitepolicy , especially in custom signup forms. See https://docs.moodle.org/dev/Site_policy_handler
+
=== 3.3 ===
* Authentication plugins have been migrated to use the admin settings API. Plugins should use a settings.php file to
diff --git a/file.php b/file.php
index 61e563245f3..2799c319c89 100644
--- a/file.php
+++ b/file.php
@@ -68,7 +68,7 @@ if ($course->id != SITEID) {
require_login($course, true, null, false);
} else if ($CFG->forcelogin) {
- if (!empty($CFG->sitepolicy)
+ if (empty($CFG->sitepolicyhandler) and !empty($CFG->sitepolicy)
and ($CFG->sitepolicy == $CFG->wwwroot.'/file.php/'.$relativepath
or $CFG->sitepolicy == $CFG->wwwroot.'/file.php?file=/'.$relativepath)) {
//do not require login for policy file
diff --git a/lang/en/admin.php b/lang/en/admin.php
index cbd65007c3b..cd0accb7434 100644
--- a/lang/en/admin.php
+++ b/lang/en/admin.php
@@ -857,6 +857,9 @@ $string['pluginscheckfailed'] = 'Dependencies check failed for {$a->pluginslist}
$string['pluginschecktodo'] = 'You must solve all the plugin requirements before proceeding to install this Moodle version!';
$string['pluginsoverview'] = 'Plugins overview';
$string['pluginsoverviewsee'] = 'See plugins overview page for more details.';
+$string['policysettings'] = 'Policy settings';
+$string['privacyandpolicies'] = 'Privacy and policies';
+$string['privacysettings'] = 'Privacy settings';
$string['profilecategory'] = 'Category';
$string['profilecategoryname'] = 'Category name (must be unique)';
$string['profilecategorynamenotunique'] = 'This category name is already in use';
diff --git a/lib/adminlib.php b/lib/adminlib.php
index 0e0deff7a73..30f756add0f 100644
--- a/lib/adminlib.php
+++ b/lib/adminlib.php
@@ -10626,3 +10626,46 @@ class admin_setting_agedigitalconsentmap extends admin_setting_configtextarea {
return true;
}
}
+
+/**
+ * Selection of plugins that can work as site policy handlers
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2018 Marina Glancy
+ */
+class admin_settings_sitepolicy_handler_select extends admin_setting_configselect {
+
+ /**
+ * Constructor
+ * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
+ * for ones in config_plugins.
+ * @param string $visiblename localised
+ * @param string $description long localised info
+ * @param string $defaultsetting
+ */
+ public function __construct($name, $visiblename, $description, $defaultsetting = '') {
+ parent::__construct($name, $visiblename, $description, $defaultsetting, null);
+ }
+
+ /**
+ * Lazy-load the available choices for the select box
+ */
+ public function load_choices() {
+ if (during_initial_install()) {
+ return false;
+ }
+ if (is_array($this->choices)) {
+ return true;
+ }
+
+ $this->choices = ['' => new lang_string('sitepolicyhandlercore', 'core_admin')];
+ $manager = new \core_privacy\local\sitepolicy\manager();
+ $plugins = $manager->get_all_handlers();
+ foreach ($plugins as $pname => $unused) {
+ $this->choices[$pname] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
+ ['name' => new lang_string('pluginname', $pname), 'component' => $pname]);
+ }
+
+ return true;
+ }
+}
diff --git a/lib/moodlelib.php b/lib/moodlelib.php
index 442541a91ac..4cb3b7c2815 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -2723,40 +2723,16 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
// Check that the user has agreed to a site policy if there is one - do not test in case of admins.
// Do not test if the script explicitly asked for skipping the site policies check.
- // Also do not check during AJAX requests as they may be actually performed on pages where policies are accepted.
- if (!$USER->policyagreed && !is_siteadmin() && !NO_SITEPOLICY_CHECK && !AJAX_SCRIPT) {
- if (!empty($CFG->sitepolicyhandler)) {
- try {
- $handler = component_callback($CFG->sitepolicyhandler, 'site_policy_handler');
- } catch (Exception $e) {
- debugging('Error while trying to execute the site_policy_handler callback!');
- $handler = false;
- }
- if (!empty($handler) && (empty($PAGE->url) || !$PAGE->url->compare(new moodle_url($handler), URL_MATCH_BASE))) {
- if ($preventredirect) {
- throw new moodle_exception('sitepolicynotagreed', 'error', '', $handler);
- }
- if ($setwantsurltome) {
- $SESSION->wantsurl = qualified_me();
- }
- redirect($handler);
- }
- } else if (!empty($CFG->sitepolicy) and !isguestuser()) {
+ if (!$USER->policyagreed && !is_siteadmin() && !NO_SITEPOLICY_CHECK) {
+ $manager = new \core_privacy\local\sitepolicy\manager();
+ if ($policyurl = $manager->get_redirect_url(isguestuser())) {
if ($preventredirect) {
- throw new moodle_exception('sitepolicynotagreed', 'error', '', $CFG->sitepolicy);
+ throw new moodle_exception('sitepolicynotagreed', 'error', '', $policyurl->out());
}
if ($setwantsurltome) {
$SESSION->wantsurl = qualified_me();
}
- redirect($CFG->wwwroot .'/user/policy.php');
- } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
- if ($preventredirect) {
- throw new moodle_exception('sitepolicynotagreed', 'error', '', $CFG->sitepolicyguest);
- }
- if ($setwantsurltome) {
- $SESSION->wantsurl = qualified_me();
- }
- redirect($CFG->wwwroot .'/user/policy.php');
+ redirect($policyurl);
}
}
diff --git a/lib/upgrade.txt b/lib/upgrade.txt
index a214f7e04ca..d1d4f730658 100644
--- a/lib/upgrade.txt
+++ b/lib/upgrade.txt
@@ -3,9 +3,9 @@ information provided here is intended especially for developers.
=== 3.4.2 ===
-* Plugins can provide a new callback 'site_policy_handler' if they implement an alternative mechanisms for site policies
- managements and agreements. Administrators can define which component is to be used for handling site policies and
- agreements.
+* Plugins can define class 'PLUGINNAME\privacy\local\sitepolicy\handler' if they implement an alternative mechanisms for
+ site policies managements and agreements. Administrators can define which component is to be used for handling site
+ policies and agreements. See https://docs.moodle.org/dev/Site_policy_handler
* Scripts can define a constant NO_SITEPOLICY_CHECK and set it to true before requiring the main config.php file. It
will make the require_login() skipping the test for the user's policyagreed status. This is useful for plugins that
act as a site policy handler.
diff --git a/login/signup_form.php b/login/signup_form.php
index 89572dbfae8..ca975aa62b0 100644
--- a/login/signup_form.php
+++ b/login/signup_form.php
@@ -97,13 +97,10 @@ class login_signup_form extends moodleform implements renderable, templatable {
$mform->closeHeaderBefore('recaptcha_element');
}
- if (!empty($CFG->sitepolicy)) {
- $mform->addElement('header', 'policyagreement', get_string('policyagreement'), '');
- $mform->setExpanded('policyagreement');
- $mform->addElement('static', 'policylink', '', ''.get_String('policyagreementclick').'');
- $mform->addElement('checkbox', 'policyagreed', get_string('policyaccept'));
- $mform->addRule('policyagreed', get_string('policyagree'), 'required', null, 'client');
- }
+ // Add "Agree to sitepolicy" controls. By default it is a link to the policy text and a checkbox but
+ // it can be implemented differently in custom sitepolicy handlers.
+ $manager = new \core_privacy\local\sitepolicy\manager();
+ $manager->signup_form($mform);
// buttons
$this->add_action_buttons(true, get_string('createaccount'));
diff --git a/message/output/popup/lib.php b/message/output/popup/lib.php
index 09930848b7e..4c4f910b942 100644
--- a/message/output/popup/lib.php
+++ b/message/output/popup/lib.php
@@ -36,7 +36,8 @@ function message_popup_render_navbar_output(\renderer_base $renderer) {
// Early bail out conditions.
if (!isloggedin() || isguestuser() || user_not_fully_set_up($USER) ||
get_user_preferences('auth_forcepasswordchange') ||
- ($CFG->sitepolicy && !$USER->policyagreed && !is_siteadmin())) {
+ (!$USER->policyagreed && !is_siteadmin() &&
+ ($manager = new \core_privacy\local\sitepolicy\manager()) && $manager->is_defined())) {
return '';
}
diff --git a/privacy/classes/local/sitepolicy/default_handler.php b/privacy/classes/local/sitepolicy/default_handler.php
new file mode 100644
index 00000000000..ac831fd1351
--- /dev/null
+++ b/privacy/classes/local/sitepolicy/default_handler.php
@@ -0,0 +1,86 @@
+.
+
+/**
+ * Default (core) handler for site policies.
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_privacy\local\sitepolicy;
+
+use moodle_url;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Default (core) handler for site policies.
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class default_handler extends handler {
+
+ /**
+ * Checks if the site has site policy defined
+ *
+ * @param bool $forguests
+ * @return bool
+ */
+ public static function is_defined($forguests = false) {
+ global $CFG;
+ if (!$forguests) {
+ return !empty($CFG->sitepolicy);
+ } else {
+ return !empty($CFG->sitepolicyguest);
+ }
+ }
+
+ /**
+ * Returns URL to redirect user to when user needs to agree to site policy
+ *
+ * This is a regular interactive page for web users. It should have normal Moodle header/footers, it should
+ * allow user to view policies and accept them.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null (returns null if site policy is not defined)
+ */
+ public static function get_redirect_url($forguests = false) {
+ return static::is_defined($forguests) ? new moodle_url('/user/policy.php') : null;
+ }
+
+ /**
+ * Returns URL of the site policy that needs to be displayed to the user (inside iframe or to use in WS such as mobile app)
+ *
+ * This page should not have any header/footer, it does not also have any buttons/checkboxes. The caller needs to implement
+ * the "Accept" button and call {@link self::accept()} on completion.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null
+ */
+ public static function get_embed_url($forguests = false) {
+ global $CFG;
+ if ($forguests && !empty($CFG->sitepolicyguest)) {
+ return new moodle_url($CFG->sitepolicyguest);
+ } else if (!$forguests && !empty($CFG->sitepolicy)) {
+ return new moodle_url($CFG->sitepolicy);
+ }
+ return null;
+ }
+}
diff --git a/privacy/classes/local/sitepolicy/handler.php b/privacy/classes/local/sitepolicy/handler.php
new file mode 100644
index 00000000000..1a6e06734c5
--- /dev/null
+++ b/privacy/classes/local/sitepolicy/handler.php
@@ -0,0 +1,113 @@
+.
+
+/**
+ * Base class for site policy handlers.
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_privacy\local\sitepolicy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Base class for site policy handlers.
+ *
+ * If a plugin wants to act as a site policy handler it has to define class
+ * PLUGINNAME\privacy\sitepolicy\handler that extends \core_privacy\sitepolicy\handler
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class handler {
+ /**
+ * Checks if the site has site policy defined
+ *
+ * @param bool $forguests
+ * @return bool
+ */
+ public static function is_defined($forguests = false) {
+ $url = static::get_redirect_url($forguests);
+ return !empty($url);
+ }
+
+ /**
+ * Returns URL to redirect user to when user needs to agree to site policy
+ *
+ * This is a regular interactive page for web users. It should have normal Moodle header/footers, it should
+ * allow user to view policies and accept them.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null (returns null if site policy is not defined)
+ */
+ abstract public static function get_redirect_url($forguests = false);
+
+ /**
+ * Returns URL of the site policy that needs to be displayed to the user (inside iframe or to use in WS such as mobile app)
+ *
+ * This page should not have any header/footer, it does not also have any buttons/checkboxes. The caller needs to implement
+ * the "Accept" button and call {@link self::accept()} on completion.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null
+ */
+ abstract public static function get_embed_url($forguests = false);
+
+ /**
+ * Accept site policy for the current user
+ *
+ * @return bool - false if sitepolicy not defined, user is not logged in or user has already agreed to site policy;
+ * true - if we have successfully marked the user as agreed to the site policy
+ */
+ public static function accept() {
+ global $USER, $DB;
+ if (!isloggedin()) {
+ return false;
+ }
+ if ($USER->policyagreed || !static::is_defined(isguestuser())) {
+ return false;
+ }
+
+ if (!isguestuser()) {
+ // For the guests agreement in stored in session only, for other users - in DB.
+ $DB->set_field('user', 'policyagreed', 1, array('id' => $USER->id));
+ }
+ $USER->policyagreed = 1;
+ return true;
+ }
+
+ /**
+ * Adds "Agree to site policy" checkbox to the signup form.
+ *
+ * Sitepolicy handlers can override the simple checkbox with their own controls.
+ *
+ * @param \MoodleQuickForm $mform
+ */
+ public static function signup_form($mform) {
+ if ($url = static::get_embed_url()) {
+ $mform->addElement('header', 'policyagreement', get_string('policyagreement'), '');
+ $mform->setExpanded('policyagreement');
+ $mform->addElement('static', 'policylink', '', '' . get_string('policyagreementclick') . '');
+ $mform->addElement('checkbox', 'policyagreed', get_string('policyaccept'));
+ $mform->addRule('policyagreed', get_string('policyagree'), 'required', null, 'client');
+ }
+ }
+}
diff --git a/privacy/classes/local/sitepolicy/manager.php b/privacy/classes/local/sitepolicy/manager.php
new file mode 100644
index 00000000000..aa11c3006b6
--- /dev/null
+++ b/privacy/classes/local/sitepolicy/manager.php
@@ -0,0 +1,137 @@
+.
+
+/**
+ * Site policy management class.
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_privacy\local\sitepolicy;
+
+use moodle_url;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Site policy management class.
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class manager {
+
+ /**
+ * Returns the list of plugins that can work as sitepolicy handlers (have class PLUGINNAME\privacy\sitepolicy\handler)
+ * @return array
+ */
+ public function get_all_handlers() {
+ $sitepolicyhandlers = [];
+ foreach (\core_component::get_plugin_types() as $ptype => $unused) {
+ $plugins = \core_component::get_plugin_list_with_class($ptype, 'privacy\local\sitepolicy\handler') +
+ \core_component::get_plugin_list_with_class($ptype, 'privacy_local_sitepolicy_handler');
+ // Allow plugins to have the class either with namespace or without (useful for unittest).
+ foreach ($plugins as $pname => $class) {
+ $sitepolicyhandlers[$pname] = $class;
+ }
+ }
+ return $sitepolicyhandlers;
+ }
+
+ /**
+ * Returns the current site policy handler
+ *
+ * @return handler
+ */
+ public function get_handler_classname() {
+ global $CFG;
+ if (!empty($CFG->sitepolicyhandler)) {
+ $sitepolicyhandlers = $this->get_all_handlers();
+ $classname = $sitepolicyhandlers[$CFG->sitepolicyhandler];
+ return $classname;
+ } else {
+ return default_handler::class;
+ }
+ }
+
+ /**
+ * Checks if the site has site policy defined
+ *
+ * @param bool $forguests
+ * @return bool
+ */
+ public function is_defined($forguests = false) {
+ return component_class_callback($this->get_handler_classname(), 'is_defined', [$forguests]);
+ }
+
+ /**
+ * Returns URL to redirect user to when user needs to agree to site policy
+ *
+ * This is a regular interactive page for web users. It should have normal Moodle header/footers, it should
+ * allow user to view policies and accept them.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null (returns null if site policy is not defined)
+ */
+ public function get_redirect_url($forguests = false) {
+ $url = component_class_callback($this->get_handler_classname(), 'get_redirect_url', [$forguests]);
+ if ($url && !($url instanceof moodle_url)) {
+ $url = new moodle_url($url);
+ }
+ return $url;
+ }
+
+ /**
+ * Returns URL of the site policy that needs to be displayed to the user (inside iframe or to use in WS such as mobile app)
+ *
+ * This page should not have any header/footer, it does not also have any buttons/checkboxes. The caller needs to implement
+ * the "Accept" button and call {@link self::accept()} on completion.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null
+ */
+ public function get_embed_url($forguests = false) {
+ $url = component_class_callback($this->get_handler_classname(), 'get_embed_url', [$forguests]);
+ if ($url && !($url instanceof moodle_url)) {
+ $url = new moodle_url($url);
+ }
+ return $url;
+ }
+
+ /**
+ * Accept site policy for the current user
+ *
+ * @return bool - false if sitepolicy not defined, user is not logged in or user has already agreed to site policy;
+ * true - if we have successfully marked the user as agreed to the site policy
+ */
+ public function accept() {
+ return component_class_callback($this->get_handler_classname(), 'accept', []);
+ }
+
+ /**
+ * Adds "Agree to site policy" checkbox to the signup form.
+ *
+ * Sitepolicy handlers can override the simple checkbox with their own controls.
+ *
+ * @param \MoodleQuickForm $mform
+ */
+ public function signup_form($mform) {
+ component_class_callback($this->get_handler_classname(), 'signup_form', [$mform]);
+ }
+}
diff --git a/privacy/tests/fixtures/mock_sitepolicy_handler.php b/privacy/tests/fixtures/mock_sitepolicy_handler.php
new file mode 100644
index 00000000000..be398950619
--- /dev/null
+++ b/privacy/tests/fixtures/mock_sitepolicy_handler.php
@@ -0,0 +1,77 @@
+.
+
+/**
+ * Mock handler for site policies
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Mock handler for site policies
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mock_sitepolicy_handler extends \core_privacy\local\sitepolicy\handler {
+
+ /**
+ * Returns URL to redirect user to when user needs to agree to site policy
+ *
+ * This is a regular interactive page for web users. It should have normal Moodle header/footers, it should
+ * allow user to view policies and accept them.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null (returns null if site policy is not defined)
+ */
+ public static function get_redirect_url($forguests = false) {
+ return 'http://example.com/policy.php';
+ }
+
+ /**
+ * Returns URL of the site policy that needs to be displayed to the user (inside iframe or to use in WS such as mobile app)
+ *
+ * This page should not have any header/footer, it does not also have any buttons/checkboxes. The caller needs to implement
+ * the "Accept" button and call {@link self::accept()} on completion.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null
+ */
+ public static function get_embed_url($forguests = false) {
+ return 'http://example.com/view.htm';
+ }
+
+ /**
+ * Accept site policy for the current user
+ *
+ * @return bool - false if sitepolicy not defined, user is not logged in or user has already agreed to site policy;
+ * true - if we have successfully marked the user as agreed to the site policy
+ */
+ public static function accept() {
+ global $USER, $DB;
+ // Accepts policy on behalf of the current user. We set it to 2 here to check that this callback was called.
+ $USER->policyagreed = 2;
+ if (!isguestuser()) {
+ $DB->update_record('user', ['policyagreed' => 2, 'id' => $USER->id]);
+ }
+ return true;
+ }
+}
diff --git a/privacy/tests/sitepolicy_test.php b/privacy/tests/sitepolicy_test.php
new file mode 100644
index 00000000000..37dd4af5666
--- /dev/null
+++ b/privacy/tests/sitepolicy_test.php
@@ -0,0 +1,324 @@
+.
+
+/**
+ * Unit Tests for sitepolicy manager
+ *
+ * @package core_privacy
+ * @category test
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+/**
+ * Unit Tests for sitepolicy manager
+ *
+ * @package core_privacy
+ * @category test
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class sitepolicy_test extends advanced_testcase {
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::is_defined()
+ */
+ public function test_is_defined() {
+ global $CFG;
+ $this->resetAfterTest(true);
+
+ $manager = new \core_privacy\local\sitepolicy\manager();
+
+ $this->assertFalse($manager->is_defined(true));
+ $this->assertFalse($manager->is_defined(false));
+
+ $CFG->sitepolicy = 'http://example.com/sitepolicy.html';
+ $this->assertFalse($manager->is_defined(true));
+ $this->assertTrue($manager->is_defined(false));
+
+ $CFG->sitepolicyguest = 'http://example.com/sitepolicyguest.html';
+ $this->assertTrue($manager->is_defined(true));
+ $this->assertTrue($manager->is_defined(false));
+
+ $CFG->sitepolicy = null;
+ $this->assertTrue($manager->is_defined(true));
+ $this->assertFalse($manager->is_defined(false));
+ }
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::get_redirect_url()
+ */
+ public function test_get_redirect_url() {
+ global $CFG;
+ $this->resetAfterTest(true);
+
+ $manager = new \core_privacy\local\sitepolicy\manager();
+
+ $this->assertEquals(null, $manager->get_redirect_url(true));
+ $this->assertEquals(null, $manager->get_redirect_url(false));
+
+ $CFG->sitepolicy = 'http://example.com/sitepolicy.html';
+ $this->assertEquals(null, $manager->get_redirect_url(true));
+ $this->assertEquals($CFG->wwwroot.'/user/policy.php', $manager->get_redirect_url(false)->out(false));
+
+ $CFG->sitepolicyguest = 'http://example.com/sitepolicyguest.html';
+ $this->assertEquals($CFG->wwwroot.'/user/policy.php', $manager->get_redirect_url(true)->out(false));
+ $this->assertEquals($CFG->wwwroot.'/user/policy.php', $manager->get_redirect_url(false)->out(false));
+
+ $CFG->sitepolicy = null;
+ $this->assertEquals($CFG->wwwroot.'/user/policy.php', $manager->get_redirect_url(true)->out(false));
+ $this->assertEquals(null, $manager->get_redirect_url(false));
+ }
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::get_redirect_url()
+ */
+ public function test_get_embed_url() {
+ global $CFG;
+ $this->resetAfterTest(true);
+
+ $manager = new \core_privacy\local\sitepolicy\manager();
+
+ $this->assertEquals(null, $manager->get_embed_url(true));
+ $this->assertEquals(null, $manager->get_embed_url(false));
+
+ $CFG->sitepolicy = 'http://example.com/sitepolicy.html';
+ $this->assertEquals(null, $manager->get_embed_url(true));
+ $this->assertEquals($CFG->sitepolicy, $manager->get_embed_url(false)->out(false));
+
+ $CFG->sitepolicyguest = 'http://example.com/sitepolicyguest.html';
+ $this->assertEquals($CFG->sitepolicyguest, $manager->get_embed_url(true)->out(false));
+ $this->assertEquals($CFG->sitepolicy, $manager->get_embed_url(false)->out(false));
+
+ $CFG->sitepolicy = null;
+ $this->assertEquals($CFG->sitepolicyguest, $manager->get_embed_url(true)->out(false));
+ $this->assertEquals(null, $manager->get_embed_url(false));
+ }
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::get_redirect_url()
+ */
+ public function test_accept() {
+ global $CFG, $USER, $DB;
+ $this->resetAfterTest(true);
+
+ $manager = new \core_privacy\local\sitepolicy\manager();
+
+ // No site policy.
+ $user1 = $this->getDataGenerator()->create_user();
+ $this->setUser($user1);
+ $this->assertFalse($manager->accept());
+ $this->assertEquals(0, $USER->policyagreed);
+
+ // With site policy.
+ $CFG->sitepolicy = 'http://example.com/sitepolicy.html';
+
+ $user2 = $this->getDataGenerator()->create_user();
+ $this->setUser($user2);
+
+ $this->assertEquals(0, $USER->policyagreed);
+ $this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $USER->id]));
+ $this->assertTrue($manager->accept());
+ $this->assertEquals(1, $USER->policyagreed);
+ $this->assertEquals(1, $DB->get_field('user', 'policyagreed', ['id' => $USER->id]));
+ }
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::get_redirect_url() for guests
+ */
+ public function test_accept_guests() {
+ global $CFG, $USER, $DB;
+ $this->resetAfterTest(true);
+
+ $manager = new \core_privacy\local\sitepolicy\manager();
+
+ $this->setGuestUser();
+
+ // No site policy.
+ $this->assertFalse($manager->accept());
+ $this->assertEquals(0, $USER->policyagreed);
+
+ // With site policy.
+ $CFG->sitepolicyguest = 'http://example.com/sitepolicy.html';
+
+ $this->assertEquals(0, $USER->policyagreed);
+ $this->assertTrue($manager->accept());
+ $this->assertEquals(1, $USER->policyagreed);
+ $this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $USER->id]));
+ }
+
+ /**
+ * Helper to spoof the results of the internal function get_all_handlers, allowing mock handler to be tested.
+ *
+ * @return PHPUnit_Framework_MockObject_MockObject
+ */
+ protected function get_mock_manager_with_handler() {
+ global $CFG;
+ require_once($CFG->dirroot.'/privacy/tests/fixtures/mock_sitepolicy_handler.php');
+
+ $mock = $this->getMockBuilder(\core_privacy\local\sitepolicy\manager::class)
+ ->setMethods(['get_all_handlers'])
+ ->getMock();
+ $mock->expects($this->any())
+ ->method('get_all_handlers')
+ ->will($this->returnValue(['testtool_testhandler' => 'mock_sitepolicy_handler']));
+ return $mock;
+ }
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::is_defined() with a handler
+ */
+ public function test_is_defined_with_handler() {
+ global $CFG;
+ $this->resetAfterTest(true);
+ $CFG->sitepolicyhandler = 'testtool_testhandler';
+ $manager = $this->get_mock_manager_with_handler();
+ $this->assertTrue($manager->is_defined(true));
+ $this->assertTrue($manager->is_defined(false));
+ }
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::get_redirect_url() with a handler
+ */
+ public function test_get_redirect_url_with_handler() {
+ global $CFG;
+ $this->resetAfterTest(true);
+
+ $CFG->sitepolicyhandler = 'testtool_testhandler';
+ $manager = $this->get_mock_manager_with_handler();
+
+ $this->assertEquals('http://example.com/policy.php', $manager->get_redirect_url(true)->out(false));
+ $this->assertEquals('http://example.com/policy.php', $manager->get_redirect_url(false)->out(false));
+ }
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::get_redirect_url() with a handler
+ */
+ public function test_get_embed_url_with_handler() {
+ global $CFG;
+ $this->resetAfterTest(true);
+
+ $CFG->sitepolicyhandler = 'testtool_testhandler';
+ $manager = $this->get_mock_manager_with_handler();
+
+ $this->assertEquals('http://example.com/view.htm', $manager->get_embed_url(true)->out(false));
+ $this->assertEquals('http://example.com/view.htm', $manager->get_embed_url(false)->out(false));
+ }
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::get_redirect_url() with a handler
+ */
+ public function test_accept_with_handler() {
+ global $CFG, $USER, $DB;
+ $this->resetAfterTest(true);
+
+ $CFG->sitepolicyhandler = 'testtool_testhandler';
+ $manager = $this->get_mock_manager_with_handler();
+
+ $user2 = $this->getDataGenerator()->create_user();
+ $this->setUser($user2);
+
+ $this->assertEquals(0, $USER->policyagreed);
+ $this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $USER->id]));
+ $this->assertTrue($manager->accept());
+ $this->assertEquals(2, $USER->policyagreed);
+ $this->assertEquals(2, $DB->get_field('user', 'policyagreed', ['id' => $USER->id]));
+ }
+
+ /**
+ * Tests for \core_privacy\local\sitepolicy\manager::get_redirect_url() for guests with a handler
+ */
+ public function test_accept_guests_with_handler() {
+ global $CFG, $USER, $DB;
+ $this->resetAfterTest(true);
+
+ $CFG->sitepolicyhandler = 'testtool_testhandler';
+ $manager = $this->get_mock_manager_with_handler();
+
+ $this->setGuestUser();
+
+ $this->assertEquals(0, $USER->policyagreed);
+ $this->assertTrue($manager->accept());
+ $this->assertEquals(2, $USER->policyagreed);
+ $this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $USER->id]));
+ }
+}
+
+/**
+ * Mock handler for site policies
+ *
+ * @package core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class handler extends \core_privacy\local\sitepolicy\handler {
+
+ /**
+ * Checks if the site has site policy defined
+ *
+ * @param bool $forguests
+ * @return bool
+ */
+ public static function is_defined($forguests = false) {
+ return true;
+ }
+
+ /**
+ * Returns URL to redirect user to when user needs to agree to site policy
+ *
+ * This is a regular interactive page for web users. It should have normal Moodle header/footers, it should
+ * allow user to view policies and accept them.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null (returns null if site policy is not defined)
+ */
+ public static function get_redirect_url($forguests = false) {
+ return 'http://example.com/policy.php';
+ }
+
+ /**
+ * Returns URL of the site policy that needs to be displayed to the user (inside iframe or to use in WS such as mobile app)
+ *
+ * This page should not have any header/footer, it does not also have any buttons/checkboxes. The caller needs to implement
+ * the "Accept" button and call {@link self::accept()} on completion.
+ *
+ * @param bool $forguests
+ * @return moodle_url|null
+ */
+ public static function get_embed_url($forguests = false) {
+ return 'http://example.com/view.htm';
+ }
+
+ /**
+ * Accept site policy for the current user
+ *
+ * @return bool - false if sitepolicy not defined, user is not logged in or user has already agreed to site policy;
+ * true - if we have successfully marked the user as agreed to the site policy
+ */
+ public static function accept() {
+ global $USER, $DB;
+ // Accepts policy on behalf of the current user. We set it to 2 here to check that this callback was called.
+ $USER->policyagreed = 2;
+ if (!isguestuser()) {
+ $DB->update_record('user', ['policyagreed' => 2, 'id' => $USER->id]);
+ }
+ return true;
+ }
+}
diff --git a/user/classes/analytics/indicator/user_profile_set.php b/user/classes/analytics/indicator/user_profile_set.php
index c41921d8c76..20639ac8417 100644
--- a/user/classes/analytics/indicator/user_profile_set.php
+++ b/user/classes/analytics/indicator/user_profile_set.php
@@ -72,7 +72,8 @@ class user_profile_set extends \core_analytics\local\indicator\linear {
// Nothing set results in -1.
$calculatedvalue = self::MIN_VALUE;
- if (!empty($CFG->sitepolicy) && !$user->policyagreed) {
+ $sitepolicymanager = new \core_privacy\local\sitepolicy\manager();
+ if ($sitepolicymanager->is_defined() && !$user->policyagreed) {
return self::MIN_VALUE;
}
diff --git a/user/externallib.php b/user/externallib.php
index f47ab060e88..83da6dcd000 100644
--- a/user/externallib.php
+++ b/user/externallib.php
@@ -1828,15 +1828,8 @@ class core_user_external extends external_api {
}
}
- if (empty($CFG->sitepolicy)) {
- $status = false;
- $warnings[] = array(
- 'item' => 'user',
- 'itemid' => $USER->id,
- 'warningcode' => 'nositepolicy',
- 'message' => 'The site does not have a site policy configured.'
- );
- } else if (!empty($USER->policyagreed)) {
+ $manager = new \core_privacy\local\sitepolicy\manager();
+ if (!empty($USER->policyagreed)) {
$status = false;
$warnings[] = array(
'item' => 'user',
@@ -1844,10 +1837,16 @@ class core_user_external extends external_api {
'warningcode' => 'alreadyagreed',
'message' => 'The user already agreed the site policy.'
);
+ } else if (!$manager->is_defined()) {
+ $status = false;
+ $warnings[] = array(
+ 'item' => 'user',
+ 'itemid' => $USER->id,
+ 'warningcode' => 'nositepolicy',
+ 'message' => 'The site does not have a site policy configured.'
+ );
} else {
- $DB->set_field('user', 'policyagreed', 1, array('id' => $USER->id));
- $USER->policyagreed = 1;
- $status = true;
+ $status = $manager->accept();
}
$result = array();
diff --git a/user/policy.php b/user/policy.php
index d3dc4b5b9e2..c8e42d0824d 100644
--- a/user/policy.php
+++ b/user/policy.php
@@ -38,28 +38,30 @@ if (!isloggedin()) {
require_login();
}
-if (isguestuser()) {
- $sitepolicy = $CFG->sitepolicyguest;
-} else {
- $sitepolicy = $CFG->sitepolicy;
-}
-
if (!empty($SESSION->wantsurl)) {
$return = $SESSION->wantsurl;
} else {
$return = $CFG->wwwroot.'/';
}
+$sitepolicymanager = new \core_privacy\local\sitepolicy\manager();
+if (!empty($CFG->sitepolicyhandler)) {
+ // We are on the wrong page, site policies are managed by somebody else.
+ if ($sitepolicyurl = $sitepolicymanager->get_redirect_url(isguestuser())) {
+ redirect($sitepolicyurl);
+ } else {
+ redirect($return);
+ }
+}
+
+$sitepolicy = $sitepolicymanager->get_embed_url(isguestuser());
if (empty($sitepolicy)) {
// Nothing to agree to, sorry, hopefully we will not get to infinite loop.
redirect($return);
}
if ($agree and confirm_sesskey()) { // User has agreed.
- if (!isguestuser()) { // Don't remember guests.
- $DB->set_field('user', 'policyagreed', 1, array('id' => $USER->id));
- }
- $USER->policyagreed = 1;
+ $sitepolicymanager->accept();
unset($SESSION->wantsurl);
redirect($return);
}