Merge branch 'MDL-62309-master-optionalpolicies' of git://github.com/mudrd8mz/moodle

This commit is contained in:
David Monllao 2018-10-22 16:28:41 +02:00
commit 14f70eda28
46 changed files with 1815 additions and 499 deletions

View File

@ -39,6 +39,10 @@ $context = context_system::instance();
$PAGE->set_context($context);
$PAGE->set_url(new moodle_url('/admin/tool/policy/accept.php'));
if (!in_array($action, ['accept', 'decline', 'revoke'])) {
throw new moodle_exception('invalidaccessparameter');
}
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
} else if (count($userids) == 1) {
@ -59,14 +63,8 @@ if ($form->is_cancelled()) {
redirect($returnurl);
}
if ($action == 'revoke') {
$title = get_string('revokedetails', 'tool_policy');
} else {
$title = get_string('consentdetails', 'tool_policy');
}
$output = $PAGE->get_renderer('tool_policy');
echo $output->header();
echo $output->heading($title);
echo $output->heading(get_string('statusformtitle'.$action, 'tool_policy'));
$form->display();
echo $output->footer();

View File

@ -1 +1 @@
define(["jquery","core/str","core/modal_factory","core/modal_events","core/notification","core/fragment","core/ajax","core/yui"],function(a,b,c,d,e,f,g,h){"use strict";var i=function(a){this.contextid=a,this.init()};return i.prototype.modal=null,i.prototype.contextid=-1,i.prototype.stringKeys=[{key:"consentdetails",component:"tool_policy"},{key:"iagreetothepolicy",component:"tool_policy"},{key:"selectusersforconsent",component:"tool_policy"},{key:"ok"},{key:"revokedetails",component:"tool_policy"},{key:"irevokethepolicy",component:"tool_policy"}],i.prototype.currentTrigger=null,i.prototype.triggers={SINGLE:"a[data-action=acceptmodal]",BULK:"input[data-action=acceptmodal]"},i.prototype.init=function(){a(this.triggers.SINGLE).on("click",function(b){b.preventDefault(),this.currentTrigger=a(b.currentTarget);var c=a(b.currentTarget).attr("href"),d=c.slice(c.indexOf("?")+1);this.showFormModal(d)}.bind(this)),a(this.triggers.BULK).on("click",function(c){c.preventDefault(),this.currentTrigger=a(c.currentTarget);var d=a(c.currentTarget).closest("form");if(d.find('input[type=checkbox][name="userids[]"]:checked').length){var f=d.serialize();this.showFormModal(f)}else b.get_strings(this.stringKeys).done(function(a){e.alert("",a[2],a[3])})}.bind(this))},i.prototype.showFormModal=function(a){for(var d,f=a.split("&"),g=0;g<f.length;g++){var h=f[g].split("=");"action"==h[0]&&(d=h[1])}b.get_strings(this.stringKeys).done(function(b){var e,f;"revoke"==d?(e=b[4],f=b[5]):(e=b[0],f=b[1]),c.create({type:c.types.SAVE_CANCEL,title:e,body:""}).done(function(b){this.modal=b,this.setupFormModal(a,f)}.bind(this))}.bind(this)).fail(e.exception)},i.prototype.setupFormModal=function(a,b){var c=this.modal;c.setLarge(),c.setSaveButtonText(b),c.getRoot().on(d.hidden,this.destroy.bind(this)),c.setBody(this.getBody(a)),c.getRoot().on(d.save,this.submitForm.bind(this)),c.getRoot().on("submit","form",this.submitFormAjax.bind(this)),c.show()},i.prototype.getBody=function(a){"undefined"==typeof a&&(a={});var b={jsonformdata:JSON.stringify(a)};return f.loadFragment("tool_policy","accept_on_behalf",this.contextid,b)},i.prototype.submitFormAjax=function(a){a.preventDefault();var b=this.modal.getRoot().find("form").serialize(),c=g.call([{methodname:"tool_policy_submit_accept_on_behalf",args:{jsonformdata:JSON.stringify(b)}}]);c[0].done(function(a){a.validationerrors?this.modal.setBody(this.getBody(b)):this.close()}.bind(this)).fail(e.exception)},i.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},i.prototype.close=function(){this.destroy(),document.location.reload()},i.prototype.destroy=function(){h.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()}),this.modal.destroy(),this.currentTrigger.focus()},{getInstance:function(a){return new i(a)}}});
define(["jquery","core/str","core/modal_factory","core/modal_events","core/notification","core/fragment","core/ajax","core/yui"],function(a,b,c,d,e,f,g,h){"use strict";var i=function(a){this.contextid=a,this.init()};return i.prototype.modal=null,i.prototype.contextid=-1,i.prototype.currentTrigger=null,i.prototype.triggers={SINGLE:"a[data-action=acceptmodal]",BULK:"input[data-action=acceptmodal]"},i.prototype.init=function(){a(this.triggers.SINGLE).on("click",function(b){b.preventDefault(),this.currentTrigger=a(b.currentTarget);var c=a(b.currentTarget).attr("href"),d=c.slice(c.indexOf("?")+1);this.showFormModal(d)}.bind(this)),a(this.triggers.BULK).on("click",function(c){c.preventDefault(),this.currentTrigger=a(c.currentTarget);var d=a(c.currentTarget).closest("form");if(d.find('input[type=checkbox][name="userids[]"]:checked').length){var f=d.serialize();this.showFormModal(f)}else b.get_strings([{key:"notice"},{key:"selectusersforconsent",component:"tool_policy"},{key:"ok"}]).then(function(a){e.alert(a[0],a[1],a[2])}).fail(e.exception)}.bind(this))},i.prototype.showFormModal=function(a){for(var d,f=a.split("&"),g=0;g<f.length;g++){var h=f[g].split("=");"action"==h[0]&&(d=h[1])}b.get_strings([{key:"statusformtitleaccept",component:"tool_policy"},{key:"iagreetothepolicy",component:"tool_policy"},{key:"statusformtitlerevoke",component:"tool_policy"},{key:"irevokethepolicy",component:"tool_policy"},{key:"statusformtitledecline",component:"tool_policy"},{key:"declinethepolicy",component:"tool_policy"}]).then(function(b){var e,f;return"accept"==d?(e=b[0],f=b[1]):"revoke"==d?(e=b[2],f=b[3]):"decline"==d&&(e=b[4],f=b[5]),c.create({type:c.types.SAVE_CANCEL,title:e,body:""}).done(function(b){this.modal=b,this.setupFormModal(a,f)}.bind(this))}.bind(this))["catch"](e.exception)},i.prototype.setupFormModal=function(a,b){var c=this.modal;c.setLarge(),c.setSaveButtonText(b),c.getRoot().on(d.hidden,this.destroy.bind(this)),c.setBody(this.getBody(a)),c.getRoot().on(d.save,this.submitForm.bind(this)),c.getRoot().on("submit","form",this.submitFormAjax.bind(this)),c.show()},i.prototype.getBody=function(a){"undefined"==typeof a&&(a={});var b={jsonformdata:JSON.stringify(a)};return f.loadFragment("tool_policy","accept_on_behalf",this.contextid,b)},i.prototype.submitFormAjax=function(a){a.preventDefault();var b=this.modal.getRoot().find("form").serialize(),c=g.call([{methodname:"tool_policy_submit_accept_on_behalf",args:{jsonformdata:JSON.stringify(b)}}]);c[0].done(function(a){a.validationerrors?this.modal.setBody(this.getBody(b)):this.close()}.bind(this)).fail(e.exception)},i.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},i.prototype.close=function(){this.destroy(),document.location.reload()},i.prototype.destroy=function(){h.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()}),this.modal.destroy(),this.currentTrigger.focus()},{getInstance:function(a){return new i(a)}}});

View File

@ -52,36 +52,6 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/n
*/
AcceptOnBehalf.prototype.contextid = -1;
/**
* @var {Array} strings
* @private
*/
AcceptOnBehalf.prototype.stringKeys = [
{
key: 'consentdetails',
component: 'tool_policy'
},
{
key: 'iagreetothepolicy',
component: 'tool_policy'
},
{
key: 'selectusersforconsent',
component: 'tool_policy'
},
{
key: 'ok'
},
{
key: 'revokedetails',
component: 'tool_policy'
},
{
key: 'irevokethepolicy',
component: 'tool_policy'
}
];
/**
* @var {object} currentTrigger The triggered HTML jQuery object
* @private
@ -121,9 +91,14 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/n
var formData = form.serialize();
this.showFormModal(formData);
} else {
Str.get_strings(this.stringKeys).done(function(strings) {
Notification.alert('', strings[2], strings[3]);
});
Str.get_strings([
{key: 'notice'},
{key: 'selectusersforconsent', component: 'tool_policy'},
{key: 'ok'}
]).then(function(strings) {
Notification.alert(strings[0], strings[1], strings[2]);
return;
}).fail(Notification.exception);
}
}.bind(this));
};
@ -143,18 +118,28 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/n
}
}
// Fetch the title string.
Str.get_strings(this.stringKeys).done(function(strings) {
Str.get_strings([
{key: 'statusformtitleaccept', component: 'tool_policy'},
{key: 'iagreetothepolicy', component: 'tool_policy'},
{key: 'statusformtitlerevoke', component: 'tool_policy'},
{key: 'irevokethepolicy', component: 'tool_policy'},
{key: 'statusformtitledecline', component: 'tool_policy'},
{key: 'declinethepolicy', component: 'tool_policy'}
]).then(function(strings) {
var title;
var saveText;
if (action == 'revoke') {
title = strings[4];
saveText = strings[5];
} else {
if (action == 'accept') {
title = strings[0];
saveText = strings[1];
} else if (action == 'revoke') {
title = strings[2];
saveText = strings[3];
} else if (action == 'decline') {
title = strings[4];
saveText = strings[5];
}
// Create the modal.
ModalFactory.create({
return ModalFactory.create({
type: ModalFactory.types.SAVE_CANCEL,
title: title,
body: ''
@ -163,7 +148,7 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/n
this.setupFormModal(formData, saveText);
}.bind(this));
}.bind(this))
.fail(Notification.exception);
.catch(Notification.exception);
};
/**

View File

@ -177,21 +177,23 @@ class acceptances_table extends \table_sql {
$filterstatus = $this->acceptancesfilter->get_status_filter();
if ($filterstatus == 1) {
$this->sql->from .= " $join AND a{$v}.status=1";
} else if ($filterstatus == 2) {
$this->sql->from .= " $join AND a{$v}.status=0";
} else {
$this->sql->from .= " LEFT $join";
}
$this->sql->from .= " LEFT JOIN {user} m ON m.id = a{$v}.usermodified AND m.id <> u.id AND a{$v}.status = 1";
$this->sql->from .= " LEFT JOIN {user} m ON m.id = a{$v}.usermodified AND m.id <> u.id AND a{$v}.status IS NOT NULL";
$this->sql->params['versionid' . $v] = $v;
if ($filterstatus === 0) {
$this->sql->where .= " AND (a{$v}.status IS NULL OR a{$v}.status = 0)";
$this->sql->where .= " AND a{$v}.status IS NULL";
}
$this->add_column_header('status' . $v, get_string('agreed', 'tool_policy'), true, 'mdl-align');
$this->add_column_header('timemodified', get_string('agreedon', 'tool_policy'));
$this->add_column_header('usermodified' . $v, get_string('agreedby', 'tool_policy'));
$this->add_column_header('status' . $v, get_string('response', 'tool_policy'));
$this->add_column_header('timemodified', get_string('responseon', 'tool_policy'));
$this->add_column_header('usermodified' . $v, get_string('responseby', 'tool_policy'));
$this->add_column_header('note', get_string('acceptancenote', 'tool_policy'), false);
}
@ -207,11 +209,13 @@ class acceptances_table extends \table_sql {
$join = "JOIN {tool_policy_acceptances} a{$v} ON a{$v}.userid = u.id AND a{$v}.policyversionid=:versionid{$v}";
if ($filterstatus == 1) {
$this->sql->from .= " {$join} AND a{$v}.status=1";
} else if ($filterstatus == 2) {
$this->sql->from .= " {$join} AND a{$v}.status=0";
} else {
$this->sql->from .= " LEFT {$join}";
}
$this->sql->params['versionid' . $v] = $v;
$this->add_column_header('status' . $v, $versionname, true, 'mdl-align');
$this->add_column_header('status' . $v, $versionname);
$statusall[] = "COALESCE(a{$v}.status, 0)";
}
$this->sql->fields .= ",".join('+', $statusall)." AS statusall";
@ -219,7 +223,7 @@ class acceptances_table extends \table_sql {
if ($filterstatus === 0) {
$statussql = [];
foreach ($this->versionids as $v => $versionname) {
$statussql[] = "a{$v}.status IS NULL OR a{$v}.status = 0";
$statussql[] = "a{$v}.status IS NULL";
}
$this->sql->where .= " AND (u.policyagreed = 0 OR ".join(" OR ", $statussql).")";
}
@ -420,7 +424,7 @@ class acceptances_table extends \table_sql {
echo \html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'returnurl',
'value' => $this->get_return_url()]);
foreach (array_keys($this->versionids) as $versionid) {
echo \html_writer::empty_tag('input', ['type' => 'hidden', 'name' => "versionids[{$versionid}]",
echo \html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'versionids[]',
'value' => $versionid]);
}
}
@ -433,6 +437,7 @@ class acceptances_table extends \table_sql {
public function wrap_html_finish() {
global $PAGE;
if ($this->canagreeany) {
echo \html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'action', 'value' => 'accept']);
echo \html_writer::empty_tag('input', ['type' => 'submit', 'data-action' => 'acceptmodal',
'value' => get_string('consentbulk', 'tool_policy'), 'class' => 'btn btn-primary m-t-1']);
$PAGE->requires->js_call_amd('tool_policy/acceptmodal', 'getInstance', [\context_system::instance()->id]);
@ -529,10 +534,15 @@ class acceptances_table extends \table_sql {
$onbehalf = false;
$versions = $versionid ? [$versionid => $this->versionids[$versionid]] : $this->versionids; // List of versions.
$accepted = []; // List of versionids that user has accepted.
$declined = [];
foreach ($versions as $v => $name) {
if (!empty($row->{'status' . $v})) {
$accepted[] = $v;
if ($row->{'status' . $v} !== null) {
if (empty($row->{'status' . $v})) {
$declined[] = $v;
} else {
$accepted[] = $v;
}
$agreedby = $row->{'usermodified' . $v};
if ($agreedby && $agreedby != $row->id) {
$onbehalf = true;
@ -540,25 +550,13 @@ class acceptances_table extends \table_sql {
}
}
if ($versionid) {
$str = new \lang_string($accepted ? 'yes' : 'no');
} else {
$str = new \lang_string('acceptancecount', 'tool_policy', (object)[
'agreedcount' => count($accepted),
'policiescount' => count($versions)
]);
}
$ua = new user_agreement($row->id, $accepted, $declined, $this->get_return_url(), $versions, $onbehalf, $row->canaccept);
if ($this->is_downloading()) {
return $str->out();
return $ua->export_for_download();
} else {
$s = $this->output->render(new user_agreement($row->id, $accepted, $this->get_return_url(),
$versions, $onbehalf, $row->canaccept));
if (!$versionid) {
$s .= '<br>' . \html_writer::link(new \moodle_url('/admin/tool/policy/user.php',
['userid' => $row->id, 'returnurl' => $this->get_return_url()]), $str);
}
return $s;
return $this->output->render($ua);
}
}

View File

@ -131,6 +131,8 @@ class api {
$policies = [];
$versions = [];
$optcache = \cache::make('tool_policy', 'policy_optional');
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $r) {
@ -149,6 +151,8 @@ class api {
}
$versions[$r->id][$versiondata->id] = $versiondata;
$optcache->set($versiondata->id, $versiondata->optional);
}
$rs->close();
@ -309,8 +313,8 @@ class api {
return true;
}
// Users have access to all the policies they have ever accepted.
if (static::is_user_version_accepted($userid, $policy->id)) {
// Users have access to all the policies they have ever accepted/declined.
if (static::is_user_version_accepted($userid, $policy->id) !== null) {
return true;
}
@ -719,20 +723,22 @@ class api {
}
/**
* Returns version acceptance for this user.
* Did the user accept the given policy version?
*
* @param int $userid User identifier.
* @param int $versionid Policy version identifier.
* @param array|null $acceptances Iist of policy version acceptances indexed by versionid.
* @return bool True if this user has accepted this policy version; false otherwise.
* @param array|null $acceptances Pre-loaded list of policy version acceptances indexed by versionid.
* @return bool|null True/false if this user accepted/declined the policy; null otherwise.
*/
public static function is_user_version_accepted($userid, $versionid, $acceptances = null) {
$acceptance = static::get_user_version_acceptance($userid, $versionid, $acceptances);
if (!empty($acceptance)) {
return $acceptance->status;
return (bool) $acceptance->status;
}
return false;
return null;
}
/**
@ -754,14 +760,14 @@ class api {
if (isset($acceptances[$policy->currentversion->id])) {
$policy->currentversion->acceptance = $acceptances[$policy->currentversion->id];
} else {
$policy->currentversion->acceptance = 0;
$policy->currentversion->acceptance = null;
}
$versions[] = $policy->currentversion;
}
foreach ($policy->archivedversions as $version) {
if ($version->audience != policy_version::AUDIENCE_GUESTS
&& static::can_user_view_policy_version($version, $userid)) {
$version->acceptance = isset($acceptances[$version->id]) ? $acceptances[$version->id] : 0;
$version->acceptance = isset($acceptances[$version->id]) ? $acceptances[$version->id] : null;
$versions[] = $version;
}
}
@ -774,14 +780,19 @@ class api {
}
/**
* Checks if user can accept policies for themselves or on behalf of another user
* Check if given policies can be accepted by the current user (eventually on behalf of the other user)
*
* @param int $userid
* @param bool $throwexception
* Currently, the version ids are not relevant and the check is based on permissions only. In the future, additional
* conditions can be added (such as policies applying to certain users only).
*
* @param array $versionids int[] List of policy version ids to check
* @param int $userid Accepting policies on this user's behalf (defaults to accepting on self)
* @param bool $throwexception Throw exception instead of returning false
* @return bool
*/
public static function can_accept_policies($userid = null, $throwexception = false) {
public static function can_accept_policies(array $versionids, $userid = null, $throwexception = false) {
global $USER;
if (!isloggedin() || isguestuser()) {
if ($throwexception) {
throw new \moodle_exception('noguest');
@ -789,6 +800,7 @@ class api {
return false;
}
}
if (!$userid) {
$userid = $USER->id;
}
@ -814,15 +826,48 @@ class api {
}
/**
* Checks if user can revoke policies for themselves or on behalf of another user
* Check if given policies can be declined by the current user (eventually on behalf of the other user)
*
* @param int $userid
* @param bool $throwexception
* Only optional policies can be declined. Otherwise, the permissions are same as for accepting policies.
*
* @param array $versionids int[] List of policy version ids to check
* @param int $userid Declining policies on this user's behalf (defaults to declining by self)
* @param bool $throwexception Throw exception instead of returning false
* @return bool
*/
public static function can_revoke_policies($userid = null, $throwexception = false) {
public static function can_decline_policies(array $versionids, $userid = null, $throwexception = false) {
foreach ($versionids as $versionid) {
if (static::get_agreement_optional($versionid) == policy_version::AGREEMENT_COMPULSORY) {
// Compulsory policies can't be declined (that is what makes them compulsory).
if ($throwexception) {
throw new \moodle_exception('errorpolicyversioncompulsory', 'tool_policy');
} else {
return false;
}
}
}
return static::can_accept_policies($versionids, $userid, $throwexception);
}
/**
* Check if acceptances to given policies can be revoked by the current user (eventually on behalf of the other user)
*
* Revoking optional policies is controlled by the same rules as declining them. Compulsory policies can be revoked
* only by users with the permission to accept policies on other's behalf. The reasoning behind this is to make sure
* the user communicates with the site's privacy officer and is well aware of all consequences of the decision (such
* as losing right to access the site).
*
* @param array $versionids int[] List of policy version ids to check
* @param int $userid Revoking policies on this user's behalf (defaults to revoking by self)
* @param bool $throwexception Throw exception instead of returning false
* @return bool
*/
public static function can_revoke_policies(array $versionids, $userid = null, $throwexception = false) {
global $USER;
// Guests' acceptance is not stored so there is nothing to revoke.
if (!isloggedin() || isguestuser()) {
if ($throwexception) {
throw new \moodle_exception('noguest');
@ -830,32 +875,84 @@ class api {
return false;
}
}
if (!$userid) {
$userid = $USER->id;
// Sort policies into two sets according the optional flag.
$compulsory = [];
$optional = [];
foreach ($versionids as $versionid) {
$agreementoptional = static::get_agreement_optional($versionid);
if ($agreementoptional == policy_version::AGREEMENT_COMPULSORY) {
$compulsory[] = $versionid;
} else if ($agreementoptional == policy_version::AGREEMENT_OPTIONAL) {
$optional[] = $versionid;
} else {
throw new \coding_exception('Unexpected optional flag value');
}
}
// At the moment, current users can't revoke their own policies.
// Check capability to revoke on behalf as the real user.
$realuser = manager::get_realuser();
$usercontext = \context_user::instance($userid);
if ($throwexception) {
require_capability('tool/policy:acceptbehalf', $usercontext, $realuser);
return;
} else {
return has_capability('tool/policy:acceptbehalf', $usercontext, $realuser);
// Check if the user can revoke the optional policies from the list.
if ($optional) {
if (!static::can_decline_policies($optional, $userid, $throwexception)) {
return false;
}
}
// Check if the user can revoke the compulsory policies from the list.
if ($compulsory) {
if (!$userid) {
$userid = $USER->id;
}
$realuser = manager::get_realuser();
$usercontext = \context_user::instance($userid);
if ($throwexception) {
require_capability('tool/policy:acceptbehalf', $usercontext, $realuser);
return;
} else {
return has_capability('tool/policy:acceptbehalf', $usercontext, $realuser);
}
}
return true;
}
/**
* Accepts the current revisions of all policies that the user has not yet accepted
* Mark the given policy versions as accepted by the user.
*
* @param array|int $policyversionid
* @param int|null $userid
* @param string|null $note
* @param string|null $lang
* @param array|int $policyversionid Policy version id(s) to set acceptance status for.
* @param int|null $userid Id of the user accepting the policy version, defaults to the current one.
* @param string|null $note Note to be recorded.
* @param string|null $lang Language in which the policy was shown, defaults to the current one.
*/
public static function accept_policies($policyversionid, $userid = null, $note = null, $lang = null) {
static::set_acceptances_status($policyversionid, $userid, $note, $lang, 1);
}
/**
* Mark the given policy versions as declined by the user.
*
* @param array|int $policyversionid Policy version id(s) to set acceptance status for.
* @param int|null $userid Id of the user accepting the policy version, defaults to the current one.
* @param string|null $note Note to be recorded.
* @param string|null $lang Language in which the policy was shown, defaults to the current one.
*/
public static function decline_policies($policyversionid, $userid = null, $note = null, $lang = null) {
static::set_acceptances_status($policyversionid, $userid, $note, $lang, 0);
}
/**
* Mark the given policy versions as accepted or declined by the user.
*
* @param array|int $policyversionid Policy version id(s) to set acceptance status for.
* @param int|null $userid Id of the user accepting the policy version, defaults to the current one.
* @param string|null $note Note to be recorded.
* @param string|null $lang Language in which the policy was shown, defaults to the current one.
* @param int $status The acceptance status, defaults to 1 = accepted
*/
protected static function set_acceptances_status($policyversionid, $userid = null, $note = null, $lang = null, $status = 1) {
global $DB, $USER;
// Validate arguments and capabilities.
if (empty($policyversionid)) {
return;
@ -865,18 +962,22 @@ class api {
if (!$userid) {
$userid = $USER->id;
}
self::can_accept_policies($userid, true);
self::can_accept_policies([$policyversionid], $userid, true);
// Retrieve the list of policy versions that need agreement (do not update existing agreements).
list($sql, $params) = $DB->get_in_or_equal($policyversionid, SQL_PARAMS_NAMED);
$sql = "SELECT v.id AS versionid, a.*
FROM {tool_policy_versions} v
LEFT JOIN {tool_policy_acceptances} a ON a.userid = :userid AND a.policyversionid = v.id
WHERE (a.id IS NULL or a.status <> 1) AND v.id " . $sql;
$needacceptance = $DB->get_records_sql($sql, ['userid' => $userid] + $params);
LEFT JOIN {tool_policy_acceptances} a ON a.userid = :userid AND a.policyversionid = v.id
WHERE v.id $sql AND (a.id IS NULL OR a.status <> :status)";
$needacceptance = $DB->get_records_sql($sql, $params + [
'userid' => $userid,
'status' => $status,
]);
$realuser = manager::get_realuser();
$updatedata = ['status' => 1, 'lang' => $lang ?: current_language(),
$updatedata = ['status' => $status, 'lang' => $lang ?: current_language(),
'timemodified' => time(), 'usermodified' => $realuser->id, 'note' => $note];
foreach ($needacceptance as $versionid => $currentacceptance) {
unset($currentacceptance->versionid);
@ -911,23 +1012,30 @@ class api {
$user = $DB->get_record('user', ['id' => $user], 'id, policyagreed');
}
$sql = "SELECT d.id, a.status
$sql = "SELECT d.id, v.optional, a.status
FROM {tool_policy} d
INNER JOIN {tool_policy_versions} v ON v.policyid = d.id AND v.id = d.currentversionid
LEFT JOIN {tool_policy_acceptances} a ON a.userid = :userid AND a.policyversionid = v.id
WHERE (v.audience = :audience OR v.audience = :audienceall)";
INNER JOIN {tool_policy_versions} v ON v.policyid = d.id AND v.id = d.currentversionid
LEFT JOIN {tool_policy_acceptances} a ON a.userid = :userid AND a.policyversionid = v.id
WHERE (v.audience = :audience OR v.audience = :audienceall)";
$params = [
'audience' => policy_version::AUDIENCE_LOGGEDIN,
'audienceall' => policy_version::AUDIENCE_ALL,
'userid' => $user->id
];
$policies = $DB->get_records_sql_menu($sql, $params);
$acceptedpolicies = array_filter($policies);
$policyagreed = (count($policies) == count($acceptedpolicies)) ? 1 : 0;
if ($user->policyagreed != $policyagreed) {
$user->policyagreed = $policyagreed;
$DB->set_field('user', 'policyagreed', $policyagreed, ['id' => $user->id]);
$allresponded = true;
foreach ($DB->get_records_sql($sql, $params) as $policyacceptance) {
if ($policyacceptance->optional == policy_version::AGREEMENT_COMPULSORY && empty($policyacceptance->status)) {
$allresponded = false;
} else if ($policyacceptance->optional == policy_version::AGREEMENT_OPTIONAL && $policyacceptance->status === null) {
$allresponded = false;
}
}
if ($user->policyagreed != $allresponded) {
$user->policyagreed = $allresponded;
$DB->set_field('user', 'policyagreed', $allresponded, ['id' => $user->id]);
}
}
@ -943,7 +1051,7 @@ class api {
if (!$userid) {
$userid = $USER->id;
}
self::can_accept_policies($userid, true);
self::can_accept_policies([$policyversionid], $userid, true);
if ($currentacceptance = $DB->get_record('tool_policy_acceptances',
['policyversionid' => $policyversionid, 'userid' => $userid])) {
@ -963,7 +1071,7 @@ class api {
* @param \core\event\user_created $event
*/
public static function create_acceptances_user_created(\core\event\user_created $event) {
global $CFG, $DB;
global $USER, $CFG, $DB;
// Do nothing if not set as the site policies handler.
if (empty($CFG->sitepolicyhandler) || $CFG->sitepolicyhandler !== 'tool_policy') {
@ -977,28 +1085,59 @@ class api {
if (!$user->policyagreed) {
return;
}
// Remove the presignup cache after the user account is created.
// Cleanup our bits in the presignup cache (we can not rely on them at this stage any more anyway).
$cache = \cache::make('core', 'presignup');
$cache->delete('tool_policy_userpolicyagreed');
$cache->delete('tool_policy_viewedpolicies');
$cache->delete('tool_policy_policyversionidsagreed');
// Get all active policies.
$currentpolicyversions = static::get_current_versions_ids(policy_version::AUDIENCE_LOGGEDIN);
// Save active policies as accepted by the user.
if (!empty($currentpolicyversions)) {
// Mark all compulsory policies as implicitly accepted during the signup.
if ($policyversions = static::list_current_versions(policy_version::AUDIENCE_LOGGEDIN)) {
$acceptances = array();
foreach ($currentpolicyversions as $policy) {
$now = time();
foreach ($policyversions as $policyversion) {
if ($policyversion->optional == policy_version::AGREEMENT_OPTIONAL) {
continue;
}
$acceptances[] = array(
'policyversionid' => $policy,
'policyversionid' => $policyversion->id,
'userid' => $userid,
'status' => 1,
'lang' => $lang,
'usermodified' => 0,
'timecreated' => time(),
'timemodified' => time()
'usermodified' => isset($USER->id) ? $USER->id : 0,
'timecreated' => $now,
'timemodified' => $now,
);
}
$DB->insert_records('tool_policy_acceptances', $acceptances);
}
static::update_policyagreed($userid);
}
/**
* Returns the value of the optional flag for the given policy version.
*
* Optimised for being called multiple times by making use of a request cache. The cache is normally populated as a
* side effect of calling {@link self::list_policies()} and in most cases should be warm enough for hits.
*
* @param int $versionid
* @return int policy_version::AGREEMENT_COMPULSORY | policy_version::AGREEMENT_OPTIONAL
*/
public static function get_agreement_optional($versionid) {
global $DB;
$optcache = \cache::make('tool_policy', 'policy_optional');
$hit = $optcache->get($versionid);
if ($hit === false) {
$flags = $DB->get_records_menu('tool_policy_versions', null, '', 'id, optional');
$optcache->set_many($flags);
$hit = $flags[$versionid];
}
return $hit;
}
}

View File

@ -44,7 +44,7 @@ class accept_policy extends \moodleform {
* Defines the form fields.
*/
public function definition() {
global $PAGE;
global $PAGE, $USER;
$mform = $this->_form;
if (empty($this->_customdata['userids']) || !is_array($this->_customdata['userids'])) {
@ -53,10 +53,10 @@ class accept_policy extends \moodleform {
if (empty($this->_customdata['versionids']) || !is_array($this->_customdata['versionids'])) {
throw new \moodle_exception('missingparam', '', '', 'versionids');
}
$revoke = (!empty($this->_customdata['action']) && $this->_customdata['action'] == 'revoke');
$action = $this->_customdata['action'];
$userids = clean_param_array($this->_customdata['userids'], PARAM_INT);
$versionids = clean_param_array($this->_customdata['versionids'], PARAM_INT);
$usernames = $this->validate_and_get_users($userids, $revoke);
$usernames = $this->validate_and_get_users($versionids, $userids, $action);
$versionnames = $this->validate_and_get_versions($versionids);
foreach ($usernames as $userid => $name) {
@ -78,22 +78,36 @@ class accept_policy extends \moodleform {
get_string('policydochdrpolicy', 'tool_policy');
$mform->addElement('static', 'policy', $policyacceptancelabel, join(', ', $versionnames));
if ($revoke) {
if ($action === 'revoke') {
$mform->addElement('static', 'ack', '', get_string('revokeacknowledgement', 'tool_policy'));
$mform->addElement('hidden', 'action', 'revoke');
$mform->setType('action', PARAM_ALPHA);
} else {
} else if ($action === 'accept') {
$mform->addElement('static', 'ack', '', get_string('acceptanceacknowledgement', 'tool_policy'));
$mform->addElement('hidden', 'action', 'accept');
} else if ($action === 'decline') {
$mform->addElement('static', 'ack', '', get_string('declineacknowledgement', 'tool_policy'));
$mform->addElement('hidden', 'action', 'decline');
} else {
throw new \moodle_exception('invalidaccessparameter');
}
$mform->setType('action', PARAM_ALPHA);
if (count($usernames) == 1 && isset($usernames[$USER->id])) {
// No need to display the acknowledgement if the users are giving/revoking acceptance on their own.
$mform->removeElement('ack');
}
$mform->addElement('textarea', 'note', get_string('acceptancenote', 'tool_policy'));
$mform->setType('note', PARAM_NOTAGS);
if (!empty($this->_customdata['showbuttons'])) {
if ($revoke) {
if ($action === 'revoke') {
$this->add_action_buttons(true, get_string('irevokethepolicy', 'tool_policy'));
} else {
} else if ($action === 'accept') {
$this->add_action_buttons(true, get_string('iagreetothepolicy', 'tool_policy'));
} else if ($action === 'decline') {
$this->add_action_buttons(true, get_string('declinethepolicy', 'tool_policy'));
}
}
@ -103,12 +117,14 @@ class accept_policy extends \moodleform {
/**
* Validate userids and return usernames
*
* @param array $versionids int[] List of policy version ids to process.
* @param array $userids
* @param boolean $revoke True if policies will be revoked; false when policies will be accepted.
* @param string $action accept|decline|revoke
* @return array (userid=>username)
*/
protected function validate_and_get_users($userids, $revoke = false) {
protected function validate_and_get_users($versionids, $userids, $action) {
global $DB;
$usernames = [];
list($sql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params['usercontextlevel'] = CONTEXT_USER;
@ -126,10 +142,12 @@ class accept_policy extends \moodleform {
throw new \moodle_exception('noguest');
}
\context_helper::preload_from_record($user);
if ($revoke) {
api::can_revoke_policies($userid, true);
} else {
api::can_accept_policies($userid, true);
if ($action === 'revoke') {
api::can_revoke_policies($versionids, $userid, true);
} else if ($action === 'accept') {
api::can_accept_policies($versionids, $userid, true);
} else if ($action === 'decline') {
api::can_decline_policies($versionids, $userid, true);
}
$usernames[$userid] = fullname($user);
}
@ -166,14 +184,15 @@ class accept_policy extends \moodleform {
*/
public function process() {
if ($data = $this->get_data()) {
$revoke = (!empty($data->action) && $data->action == 'revoke');
foreach ($data->userids as $userid) {
if ($revoke) {
if ($data->action === 'revoke') {
foreach ($data->versionids as $versionid) {
\tool_policy\api::revoke_acceptance($versionid, $userid, $data->note);
}
} else {
} else if ($data->action === 'accept') {
\tool_policy\api::accept_policies($data->versionids, $userid, $data->note);
} else if ($data->action === 'decline') {
\tool_policy\api::decline_policies($data->versionids, $userid, $data->note);
}
}
}

View File

@ -91,6 +91,8 @@ class policydoc extends moodleform {
$mform->addElement('selectyesno', 'agreementstyle', get_string('policypriorityagreement', 'tool_policy'));
$mform->addElement('selectyesno', 'optional', get_string('policydocoptional', 'tool_policy'));
if (!$formdata->id || $formdata->status == policy_version::STATUS_DRAFT) {
// Creating a new version or editing a draft/archived version.
$mform->addElement('hidden', 'minorchange');

View File

@ -50,9 +50,6 @@ class acceptances implements renderable, templatable {
/** @var moodle_url */
protected $returnurl;
/** @var bool */
protected $canrevoke;
/**
* Contructor.
*
@ -62,7 +59,6 @@ class acceptances implements renderable, templatable {
public function __construct($userid, $returnurl = null) {
$this->userid = $userid;
$this->returnurl = $returnurl ? (new moodle_url($returnurl))->out(false) : null;
$this->canrevoke = \tool_policy\api::can_revoke_policies($this->userid);
}
/**
@ -76,19 +72,20 @@ class acceptances implements renderable, templatable {
$data->hasonbehalfagreements = false;
$data->pluginbaseurl = (new moodle_url('/admin/tool/policy'))->out(false);
$data->returnurl = $this->returnurl;
$data->canrevoke = $this->canrevoke;
// Get the list of policies and versions that current user is able to see
// and the respective acceptance records for the selected user.
$policies = api::get_policies_with_acceptances($this->userid);
$versionids = [];
$canviewfullnames = has_capability('moodle/site:viewfullnames', \context_system::instance());
foreach ($policies as $policy) {
foreach ($policy->versions as $version) {
$versionids[$version->id] = $version->id;
unset($version->summary);
unset($version->content);
$version->iscurrent = ($version->status == policy_version::STATUS_ACTIVE);
$version->isoptional = ($version->optional == policy_version::AGREEMENT_OPTIONAL);
$version->name = $version->name;
$version->revision = $version->revision;
$returnurl = new moodle_url('/admin/tool/policy/user.php', ['userid' => $this->userid]);
@ -98,12 +95,17 @@ class acceptances implements renderable, templatable {
'returnurl' => $returnurl->out(false),
]))->out(false);
if (!empty($version->acceptance->status)) {
if ($version->acceptance !== null) {
$acceptance = $version->acceptance;
$version->timeaccepted = userdate($acceptance->timemodified, get_string('strftimedatetime'));
$onbehalf = $acceptance->usermodified && $acceptance->usermodified != $this->userid;
$version->agreement = new user_agreement($this->userid, [$version->id], $returnurl,
[$version->id => $version->name], $onbehalf);
if ($version->acceptance->status == 1) {
$version->agreement = new user_agreement($this->userid, [$version->id], [], $returnurl,
[$version->id => $version->name], $onbehalf);
} else {
$version->agreement = new user_agreement($this->userid, [], [$version->id], $returnurl,
[$version->id => $version->name], $onbehalf);
}
if ($onbehalf) {
$usermodified = (object)['id' => $acceptance->usermodified];
username_load_fields_from_object($usermodified, $acceptance, 'mod');
@ -114,7 +116,7 @@ class acceptances implements renderable, templatable {
}
$version->note = format_text($acceptance->note);
} else if ($version->iscurrent) {
$version->agreement = new user_agreement($this->userid, [], $returnurl, [$version->id => $version->name]);
$version->agreement = new user_agreement($this->userid, [], [], $returnurl, [$version->id => $version->name]);
}
if (isset($version->agreement)) {
$version->agreement = $version->agreement->export_for_template($output);
@ -133,6 +135,8 @@ class acceptances implements renderable, templatable {
}
$data->policies = array_values($policies);
$data->canrevoke = \tool_policy\api::can_revoke_policies(array_keys($versionids), $this->userid);
return $data;
}
}

View File

@ -94,11 +94,9 @@ class acceptances_filter implements \templatable, \renderable {
switch ((int)$parts[1]) {
case self::FILTER_POLICYID:
case self::FILTER_VERSIONID:
$value = (int)$parts[2];
break;
case self::FILTER_CAPABILITY_ACCEPT:
case self::FILTER_STATUS:
$value = (int)(bool)$parts[2];
$value = (int)$parts[2];
break;
case self::FILTER_ROLE:
$value = (int)$parts[2];
@ -408,8 +406,9 @@ class acceptances_filter implements \templatable, \renderable {
// Status.
$statuses = [
self::FILTER_STATUS.':2' => get_string('filterstatusdeclined', 'tool_policy'),
self::FILTER_STATUS.':1' => get_string('filterstatusyes', 'tool_policy'),
self::FILTER_STATUS.':0' => get_string('filterstatusno', 'tool_policy'),
self::FILTER_STATUS.':0' => get_string('filterstatuspending', 'tool_policy'),
];
if (($currentstatus = $this->get_status_filter()) !== null) {
$selectedoptions[] = $key = self::FILTER_STATUS . ':' . $currentstatus;

View File

@ -57,6 +57,9 @@ class page_agreedocs implements renderable, templatable {
/** @var array $agreedocs List of policy identifiers which the user has agreed using the form. */
protected $agreedocs = null;
/** @var array $declinedocs List of policy identifiers that the user declined. */
protected $declinedocs = null;
/** @var string $action Form action to identify when user agreeds policies. */
protected $action = null;
@ -80,15 +83,17 @@ class page_agreedocs implements renderable, templatable {
*
* @param array $listdocs List of policy version ids that were displayed to the user to agree with.
* @param array $agreedocs List of policy version ids that the user actually agreed with.
* @param array $declinedocs List of policy version ids that the user declined.
* @param int $behalfid The userid to accept the policy versions as (such as child's id).
* @param string $action Form action to identify when user agreeds policies.
*/
public function __construct(array $listdocs, array $agreedocs = [], $behalfid = 0, $action = null) {
public function __construct(array $listdocs, array $agreedocs = [], array $declinedocs = [], $behalfid = 0, $action = null) {
global $USER;
$realuser = manager::get_realuser();
$this->listdocs = $listdocs;
$this->agreedocs = $agreedocs;
$this->declinedocs = $declinedocs;
$this->action = $action;
$this->isexistinguser = isloggedin() && !isguestuser();
@ -99,11 +104,23 @@ class page_agreedocs implements renderable, templatable {
}
$this->policies = api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN);
if (!$this->isexistinguser) {
// During the signup, show compulsory policies only.
foreach ($this->policies as $ix => $policyversion) {
if ($policyversion->optional == policy_version::AGREEMENT_OPTIONAL) {
unset($this->policies[$ix]);
}
}
$this->policies = array_values($this->policies);
}
if (empty($this->behalfid)) {
$userid = $USER->id;
} else {
$userid = $this->behalfid;
}
$this->accept_and_revoke_policies();
$this->prepare_global_page_access($userid);
$this->prepare_user_acceptances($userid);
@ -120,25 +137,30 @@ class page_agreedocs implements renderable, templatable {
if ($this->isexistinguser) {
// Existing user.
if (!empty($this->action) && confirm_sesskey()) {
// The form has been sent. Update policies acceptances according to $this->agreedocs.
// The form has been sent, update policies acceptances.
$lang = current_language();
// Accept / revoke policies.
$acceptversionids = array();
$acceptversionids = [];
$declineversionids = [];
foreach ($this->policies as $policy) {
if (in_array($policy->id, $this->listdocs)) {
if (in_array($policy->id, $this->agreedocs)) {
// Save policy version doc to accept it.
$acceptversionids[] = $policy->id;
} else if (in_array($policy->id, $this->declinedocs)) {
$declineversionids[] = $policy->id;
} else {
// If the policy was displayed but not agreed, revoke the eventually given acceptance.
// If the policy was displayed but not answered, revoke the eventually given acceptance.
api::revoke_acceptance($policy->id, $this->behalfid);
}
}
}
// Accept all policy docs saved in $acceptversionids.
api::accept_policies($acceptversionids, $this->behalfid, null, $lang);
api::decline_policies($declineversionids, $this->behalfid, null, $lang);
// Show a message to let know the user he/she must agree all the policies.
if (count($acceptversionids) != count($this->policies)) {
if ((count($acceptversionids) + count($declineversionids)) != count($this->policies)) {
$message = (object) [
'type' => 'error',
'text' => get_string('mustagreetocontinue', 'tool_policy')
@ -205,16 +227,25 @@ class page_agreedocs implements renderable, templatable {
*/
protected function redirect_to_policies($userid, $returnurl = null) {
// Make a list of all policies that the user has not accepted yet.
// Make a list of all policies that the user has not answered yet.
$allpolicies = $this->policies;
if ($this->isexistinguser) {
$acceptances = api::get_user_acceptances($userid);
foreach ($allpolicies as $ix => $policy) {
if (api::is_user_version_accepted($userid, $policy->id, $acceptances)) {
$isaccepted = api::is_user_version_accepted($userid, $policy->id, $acceptances);
if ($isaccepted) {
// The user has accepted this policy, do not show it again.
unset($allpolicies[$ix]);
} else if ($isaccepted === false && $policy->optional == policy_version::AGREEMENT_OPTIONAL) {
// The user declined this policy but the agreement was optional, do not show it.
unset($allpolicies[$ix]);
} else {
// The user has not answered the policy yet, or the agreement is compulsory. Show it.
continue;
}
}
} else {
$presignupcache = \cache::make('core', 'presignup');
$acceptances = $presignupcache->get('tool_policy_policyversionidsagreed');
@ -321,7 +352,7 @@ class page_agreedocs implements renderable, templatable {
// Check for correct user capabilities.
if ($this->isexistinguser) {
// For existing users, it's needed to check if they have the capability for accepting policies.
api::can_accept_policies($this->behalfid, true);
api::can_accept_policies($this->listdocs, $this->behalfid, true);
} else {
// For new users, the behalfid parameter is ignored.
if ($this->behalfid) {
@ -377,18 +408,24 @@ class page_agreedocs implements renderable, templatable {
if ($this->isexistinguser) {
// Existing user.
$versionagreed = false;
$versiondeclined = false;
$acceptances = api::get_user_acceptances($userid);
$policy->versionacceptance = api::get_user_version_acceptance($userid, $policy->id, $acceptances);
if (!empty($policy->versionacceptance)) {
// The policy version has ever been agreed. Check if status = 1 to know if still is accepted.
$versionagreed = $policy->versionacceptance->status;
// The policy version has ever been replied to before. Check if status = 1 to know if still is accepted.
if ($policy->versionacceptance->status) {
$versionagreed = true;
} else {
$versiondeclined = true;
}
if ($versionagreed) {
if ($policy->versionacceptance->lang != $lang) {
// Add a message because this version has been accepted in a different language than the current one.
$policy->versionlangsagreed = get_string('policyversionacceptedinotherlang', 'tool_policy');
}
if ($policy->versionacceptance->usermodified != $userid && $USER->id == $userid) {
// Add a message because this version has been accepted in behalf of current user.
$usermodified = $policy->versionacceptance->usermodified;
if ($usermodified && $usermodified != $userid && $USER->id == $userid) {
// Add a message because this version has been accepted on behalf of current user.
$policy->versionbehalfsagreed = get_string('policyversionacceptedinbehalf', 'tool_policy');
}
}
@ -396,8 +433,10 @@ class page_agreedocs implements renderable, templatable {
} else {
// New user.
$versionagreed = in_array($policy->id, $this->agreedocs);
$versiondeclined = false;
}
$policy->versionagreed = $versionagreed;
$policy->versiondeclined = $versiondeclined;
$policy->policylink = html_writer::link($policy->url, $policy->name);
$policy->policymodal = $policymodal;
}

View File

@ -159,6 +159,12 @@ class page_managedocs_list implements renderable, templatable {
$version->statustext = html_writer::span($version->statustext, 'label');
}
if ($version->optional == policy_version::AGREEMENT_OPTIONAL) {
$version->optionaltext = get_string('policydocoptionalyes', 'tool_policy');
} else {
$version->optionaltext = get_string('policydocoptionalno', 'tool_policy');
}
$version->indented = $isindented;
$editbaseurl = new moodle_url('/admin/tool/policy/editpolicydoc.php', [

View File

@ -65,9 +65,10 @@ class page_nopermission implements renderable, templatable {
/**
* Prepare the page for rendering.
*
* @param array $versionids int[] List of policy version ids that were checked.
* @param int $behalfid The userid to consent policies as (such as child's id).
*/
public function __construct($behalfid) {
public function __construct(array $versionids, $behalfid) {
global $USER;
$behalfid = $behalfid ?: $USER->id;
@ -79,7 +80,7 @@ class page_nopermission implements renderable, templatable {
if (!empty($USER->id)) {
// For existing users, it's needed to check if they have the capability for accepting policies.
$this->haspermissionagreedocs = api::can_accept_policies($this->behalfid);
$this->haspermissionagreedocs = api::can_accept_policies($versionids, $this->behalfid);
}
$this->policies = api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN);

View File

@ -169,10 +169,18 @@ class page_viewdoc implements renderable, templatable {
unset($data->returnurl);
$data->accepturl = (new moodle_url('/admin/tool/policy/index.php', [
'listdoc[]' => $this->policy->id,
'agreedoc[]' => $this->policy->id,
'status'.$this->policy->id => 1,
'submit' => 'accept',
'sesskey' => sesskey(),
]))->out(false);
if ($this->policy->optional == policy_version::AGREEMENT_OPTIONAL) {
$data->declineurl = (new moodle_url('/admin/tool/policy/index.php', [
'listdoc[]' => $this->policy->id,
'status'.$this->policy->id => 0,
'submit' => 'decline',
'sesskey' => sesskey(),
]))->out(false);
}
}
}

View File

@ -56,6 +56,9 @@ class user_agreement implements \templatable, \renderable {
/** @var array */
protected $accepted;
/** @var array */
protected $declined;
/** @var bool */
protected $canaccept;
@ -67,25 +70,35 @@ class user_agreement implements \templatable, \renderable {
*
* @param int $userid
* @param array $accepted list of ids of accepted versions
* @param array $declined list of ids of declined versions
* @param moodle_url $pageurl
* @param array $versions list of versions (id=>name)
* @param bool $onbehalf whether at least one version was accepted by somebody else on behalf of the user
* @param bool $canaccept does the current user have permission to accept the policy on behalf of user $userid
* @param bool $canaccept does the current user have permission to accept/decline the policy on behalf of user $userid
* @param bool $canrevoke does the current user have permission to revoke the policy on behalf of user $userid
*/
public function __construct($userid, $accepted, moodle_url $pageurl, $versions, $onbehalf = false,
public function __construct($userid, array $accepted, array $declined, moodle_url $pageurl, $versions, $onbehalf = false,
$canaccept = null, $canrevoke = null) {
// Make sure that all ids in $accepted and $declined are present in $versions.
if (array_diff(array_merge($accepted, $declined), array_keys($versions))) {
throw new \coding_exception('Policy version ids mismatch');
}
$this->userid = $userid;
$this->onbehalf = $onbehalf;
$this->pageurl = $pageurl;
$this->versions = $versions;
$this->accepted = $accepted;
$this->declined = $declined;
$this->canaccept = $canaccept;
if (count($this->accepted) < count($this->versions) && $canaccept === null) {
$this->canaccept = \tool_policy\api::can_accept_policies($this->userid);
$this->canaccept = \tool_policy\api::can_accept_policies(array_keys($this->versions), $this->userid);
}
if (count($this->accepted) == count($this->versions) && $canrevoke === null) {
$this->canrevoke = \tool_policy\api::can_revoke_policies($this->userid);
if (count($this->accepted) > 0 && $canrevoke === null) {
$this->canrevoke = \tool_policy\api::can_revoke_policies(array_keys($this->versions), $this->userid);
}
}
@ -96,35 +109,237 @@ class user_agreement implements \templatable, \renderable {
* @return stdClass
*/
public function export_for_template(\renderer_base $output) {
$data = [
'status' => count($this->accepted) == count($this->versions),
'onbehalf' => $this->onbehalf,
'canaccept' => $this->canaccept,
'canrevoke' => $this->canrevoke,
$data = (object)[
'statusicon' => '',
'statustext' => '',
'statuslink' => '',
'actions' => [],
];
if (!$data['status'] && $this->canaccept) {
$linkparams = ['userids[0]' => $this->userid];
foreach (array_diff(array_keys($this->versions), $this->accepted) as $versionid) {
$linkparams["versionids[{$versionid}]"] = $versionid;
if (count($this->versions) == 1) {
// We represent one particular policy's agreement status.
$versionname = reset($this->versions);
$versionid = key($this->versions);
$actionaccept = (object)[
'text' => get_string('useracceptanceactionaccept', 'tool_policy'),
'title' => get_string('useracceptanceactionacceptone', 'tool_policy', $versionname),
'data' => 'acceptmodal',
'url' => (new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'versionids[]' => $versionid,
'action' => 'accept',
'returnurl' => $this->pageurl->out_as_local_url(false),
]))->out(false),
];
$actionrevoke = (object)[
'text' => get_string('useracceptanceactionrevoke', 'tool_policy'),
'title' => get_string('useracceptanceactionrevokeone', 'tool_policy', $versionname),
'data' => 'acceptmodal',
'url' => (new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'versionids[]' => $versionid,
'action' => 'revoke',
'returnurl' => $this->pageurl->out_as_local_url(false),
]))->out(false),
];
$actiondecline = (object)[
'text' => get_string('useracceptanceactiondecline', 'tool_policy'),
'title' => get_string('useracceptanceactiondeclineone', 'tool_policy', $versionname),
'data' => 'acceptmodal',
'url' => (new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'versionids[]' => $versionid,
'action' => 'decline',
'returnurl' => $this->pageurl->out_as_local_url(false),
]))->out(false),
];
if ($this->accepted) {
$data->statusicon = 'agreed';
if ($this->onbehalf) {
$data->statustext = get_string('acceptancestatusacceptedbehalf', 'tool_policy');
} else {
$data->statustext = get_string('acceptancestatusaccepted', 'tool_policy');
}
if ($this->canrevoke) {
$data->actions[] = $actionrevoke;
}
} else if ($this->declined) {
$data->statusicon = 'declined';
if ($this->onbehalf) {
$data->statustext = get_string('acceptancestatusdeclinedbehalf', 'tool_policy');
} else {
$data->statustext = get_string('acceptancestatusdeclined', 'tool_policy');
}
if ($this->canaccept) {
$data->actions[] = $actionaccept;
}
} else {
$data->statusicon = 'pending';
$data->statustext = get_string('acceptancestatuspending', 'tool_policy');
if ($this->canaccept) {
$data->actions[] = $actionaccept;
$data->actions[] = $actiondecline;
}
}
$linkparams['returnurl'] = $this->pageurl->out_as_local_url(false);
$link = new \moodle_url('/admin/tool/policy/accept.php', $linkparams);
$data['acceptlink'] = $link->out(false);
} else if ($data['status'] && $this->canrevoke) {
$linkparams = ['userids[0]' => $this->userid];
foreach (array_keys($this->versions) as $versionid) {
$linkparams["versionids[{$versionid}]"] = $versionid;
} else if (count($this->versions) > 1) {
// We represent the summary status for multiple policies.
$data->actions[] = (object)[
'text' => get_string('useracceptanceactiondetails', 'tool_policy'),
'url' => (new \moodle_url('/admin/tool/policy/user.php', [
'userid' => $this->userid,
'returnurl' => $this->pageurl->out_as_local_url(false),
]))->out(false),
];
// Prepare the action link to accept all pending policies.
$accepturl = new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'action' => 'accept',
'returnurl' => $this->pageurl->out_as_local_url(false),
]);
foreach (array_diff(array_keys($this->versions), $this->accepted, $this->declined) as $ix => $versionid) {
$accepturl->param('versionids['.$ix.']', $versionid);
}
$actionaccept = (object)[
'text' => get_string('useracceptanceactionaccept', 'tool_policy'),
'title' => get_string('useracceptanceactionacceptpending', 'tool_policy'),
'data' => 'acceptmodal',
'url' => $accepturl->out(false),
];
// Prepare the action link to revoke all agreed policies.
$revokeurl = new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'action' => 'revoke',
'returnurl' => $this->pageurl->out_as_local_url(false),
]);
foreach ($this->accepted as $ix => $versionid) {
$revokeurl->param('versionids['.$ix.']', $versionid);
}
$actionrevoke = (object)[
'text' => get_string('useracceptanceactionrevoke', 'tool_policy'),
'title' => get_string('useracceptanceactionrevokeall', 'tool_policy'),
'data' => 'acceptmodal',
'url' => $revokeurl->out(false),
];
// Prepare the action link to decline all pending policies.
$declineurl = new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'action' => 'decline',
'returnurl' => $this->pageurl->out_as_local_url(false),
]);
foreach (array_diff(array_keys($this->versions), $this->accepted, $this->declined) as $ix => $versionid) {
$declineurl->param('versionids['.$ix.']', $versionid);
}
$actiondecline = (object)[
'text' => get_string('useracceptanceactiondecline', 'tool_policy'),
'title' => get_string('useracceptanceactiondeclinepending', 'tool_policy'),
'data' => 'acceptmodal',
'url' => $declineurl->out(false),
];
$countversions = count($this->versions);
$countaccepted = count($this->accepted);
$countdeclined = count($this->declined);
if ($countaccepted == $countversions) {
// All policies accepted.
$data->statusicon = 'agreed';
$data->statustext = get_string('acceptancestatusaccepted', 'tool_policy');
if ($this->canrevoke) {
$data->actions[] = $actionrevoke;
}
} else if ($countdeclined == $countversions) {
// All policies declined.
$data->statusicon = 'declined';
$data->statustext = get_string('acceptancestatusdeclined', 'tool_policy');
} else if ($countaccepted + $countdeclined == $countversions) {
// All policies responded, only some of them accepted.
$data->statusicon = 'partial';
$data->statustext = get_string('acceptancestatuspartial', 'tool_policy');
if ($this->accepted && $this->canrevoke) {
$data->actions[] = $actionrevoke;
}
} else {
// Some policies are pending.
$data->statusicon = 'pending';
$data->statustext = get_string('acceptancestatuspending', 'tool_policy');
if ($this->canaccept) {
$data->actions[] = $actionaccept;
$data->actions[] = $actiondecline;
}
}
$linkparams['returnurl'] = $this->pageurl->out_as_local_url(false);
$linkparams['action'] = 'revoke';
$link = new \moodle_url('/admin/tool/policy/accept.php', $linkparams);
$data['revokelink'] = $link->out(false);
}
$data['singleversion'] = count($this->versions) == 1;
if ($data['singleversion']) {
$firstversion = reset($this->versions);
$data['versionname'] = $firstversion;
}
return $data;
}
}
/**
* Describe the status with a plain text for downloading purposes.
*
* @return string
*/
public function export_for_download() {
if (count($this->versions) == 1) {
if ($this->accepted) {
if ($this->onbehalf) {
return get_string('acceptancestatusacceptedbehalf', 'tool_policy');
} else {
return get_string('acceptancestatusaccepted', 'tool_policy');
}
} else if ($this->declined) {
if ($this->onbehalf) {
return get_string('acceptancestatusdeclinedbehalf', 'tool_policy');
} else {
return get_string('acceptancestatusdeclined', 'tool_policy');
}
} else {
return get_string('acceptancestatuspending', 'tool_policy');
}
} else if (count($this->versions) > 1) {
if (count($this->accepted) == count($this->versions)) {
return get_string('acceptancestatusaccepted', 'tool_policy');
} else if (count($this->declined) == count($this->versions)) {
return get_string('acceptancestatusdeclined', 'tool_policy');
} else if (count($this->accepted) > 0 || count($this->declined) > 0) {
return get_string('acceptancestatuspartial', 'tool_policy');
} else {
return get_string('acceptancestatuspending', 'tool_policy');
}
}
}
}

View File

@ -75,6 +75,12 @@ class policy_version extends persistent {
/** @var int Policy to be accepted on its own page before reaching the consent page. */
const AGREEMENTSTYLE_OWNPAGE = 1;
/** @var int Users must agree to the policy in order to use the site. */
const AGREEMENT_COMPULSORY = 0;
/** @var int Users may or may not agree to the policy. */
const AGREEMENT_OPTIONAL = 1;
/**
* Return the definition of the properties of this model.
*
@ -120,6 +126,14 @@ class policy_version extends persistent {
],
'default' => self::AGREEMENTSTYLE_CONSENTPAGE,
],
'optional' => [
'type' => PARAM_INT,
'choices' => [
self::AGREEMENT_OPTIONAL,
self::AGREEMENT_COMPULSORY,
],
'default' => self::AGREEMENT_COMPULSORY,
],
'revision' => [
'type' => PARAM_TEXT,
'default' => '',
@ -154,4 +168,26 @@ class policy_version extends persistent {
],
];
}
/**
* Hook to execute after an update.
*
* @param bool $result Whether or not the update was successful (but it always is)
*/
protected function after_update($result) {
$optcache = \cache::make('tool_policy', 'policy_optional');
$optcache->delete($this->raw_get('id'));
}
/**
* Hook to execute after an update.
*
* @param bool $result Whether or not the update was successful (but it always is)
*/
protected function after_delete($result) {
$optcache = \cache::make('tool_policy', 'policy_optional');
$optcache->delete($this->raw_get('id'));
}
}

View File

@ -79,26 +79,37 @@ class handler extends \core_privacy\local\sitepolicy\handler {
*/
public static function accept() {
global $USER, $DB;
if (!isloggedin()) {
return false;
}
if ($USER->policyagreed) {
return false;
}
if (!isguestuser()) {
// Accepts all policies with a current version for logged users on behalf of the current user.
if (!$versions = api::get_current_versions_ids(policy_version::AUDIENCE_LOGGEDIN)) {
return false;
}
api::accept_policies(array_values($versions));
if (isguestuser()) {
// For guests, agreement is stored in the session only.
$USER->policyagreed = 1;
return true;
}
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));
// Find all compulsory policies and mark them as accepted.
$compulsory = [];
foreach (api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN) as $policyversion) {
if ($policyversion->optional == policy_version::AGREEMENT_COMPULSORY) {
$compulsory[] = $policyversion->id;
}
}
if ($compulsory) {
api::accept_policies($compulsory);
}
// Mark policies as agreed.
$DB->set_field('user', 'policyagreed', 1, array('id' => $USER->id));
$USER->policyagreed = 1;
return true;
}
@ -110,7 +121,7 @@ class handler extends \core_privacy\local\sitepolicy\handler {
public static function signup_form($mform) {
if (static::is_defined()) {
// This plugin displays policies to the user who is signing up before the signup form is shown.
// By the time user has access to signup form they have already agreed to the policies.
// By the time user has access to signup form they have already agreed to all compulsory policies.
$mform->addElement('hidden', 'policyagreed', 1);
$mform->setType('policyagreed', PARAM_INT);
}

View File

@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - https://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/>.
/**
* Provides the {@link \tool_policy\test\helper} class.
*
* @package tool_policy
* @category test
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\test;
use tool_policy\api;
use tool_policy\policy_version;
defined('MOODLE_INTERNAL') || die();
/**
* Provides some helper methods for unit-tests.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Helper method that creates a new policy for testing
*
* @param array $params
* @return policy_version
*/
public static function add_policy($params = []) {
static $counter = 0;
$counter++;
$defaults = [
'name' => 'Policy '.$counter,
'summary_editor' => ['text' => "P$counter summary", 'format' => FORMAT_HTML, 'itemid' => 0],
'content_editor' => ['text' => "P$counter content", 'format' => FORMAT_HTML, 'itemid' => 0],
];
$params = (array)$params + $defaults;
$formdata = api::form_policydoc_data(new policy_version(0));
foreach ($params as $key => $value) {
$formdata->$key = $value;
}
return api::form_policydoc_add($formdata);
}
/**
* Helper method that prepare a policy document with some versions.
*
* @param int $numversions The number of policy versions to create.
* @return array Array with all the policy versions created.
*/
public static function create_versions($numversions = 2) {
// Prepare a policy document with some versions.
$policy = self::add_policy([
'name' => 'Test policy',
'revision' => 'v1',
]);
for ($i = 2; $i <= $numversions; $i++) {
$formdata = api::form_policydoc_data($policy);
$formdata->revision = 'v'.$i;
api::form_policydoc_update_new($formdata);
}
$list = api::list_policies($policy->get('policyid'));
return $list[0]->draftversions;
}
}

View File

@ -0,0 +1,32 @@
<?php
// This file is part of Moodle - https://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/>.
/**
* Defined caches used internally by the plugin.
*
* @package tool_policy
* @category cache
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$definitions = [
'policy_optional' => [
'mode' => cache_store::MODE_REQUEST,
],
];

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="admin/tool/policy/db" VERSION="20180829" COMMENT="The plugin allows to manage various policy documents that users have to accept to use the site."
<XMLDB PATH="admin/tool/policy/db" VERSION="20180918" COMMENT="The plugin allows to manage various policy documents that users have to accept to use the site."
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
>
@ -27,6 +27,7 @@
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp of when the policy version was last modified."/>
<FIELD NAME="policyid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the policy document we are version of."/>
<FIELD NAME="agreementstyle" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="How this agreement should flow: 0 - on the consent page, 1 - on a separate page before reaching the consent page."/>
<FIELD NAME="optional" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="0 - the policy must be accepted to use the site, 1 - accepting the policy is optional"/>
<FIELD NAME="revision" TYPE="char" LENGTH="1333" NOTNULL="true" SEQUENCE="false" COMMENT="Human readable version of the policy document"/>
<FIELD NAME="summary" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Policy text summary"/>
<FIELD NAME="summaryformat" TYPE="int" LENGTH="3" NOTNULL="true" SEQUENCE="false" COMMENT="Format of the summary field"/>

View File

@ -48,6 +48,17 @@ function xmldb_tool_policy_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2018082900, 'tool', 'policy');
}
if ($oldversion < 2018091800) {
// Add field "optional" to the table "tool_policy_versions".
$table = new xmldb_table('tool_policy_versions');
$field = new xmldb_field('optional', XMLDB_TYPE_INTEGER, '3', null, XMLDB_NOTNULL, null, '0', 'agreementstyle');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_plugin_savepoint(true, 2018091800, 'tool', 'policy');
}
return true;
}

View File

@ -19,7 +19,7 @@
*
* Script parameters:
* listdoc=<array> List of policy version ids that were displayed to the user to accept.
* agreedoc=<array> List of policy version ids that were accepted by the user.
* statusXX=<int> Acceptance status to be set for the policy version with id XX.
* behalfid=<id> The user id to view the policy version as (such as child's id).
*
* @package tool_policy
@ -39,35 +39,50 @@ require(__DIR__.'/../../../config.php');
$submit = optional_param('submit', null, PARAM_NOTAGS);
$cancel = optional_param('cancel', null, PARAM_NOTAGS);
$listdocs = optional_param_array('listdoc', [], PARAM_INT);
$agreedocs = optional_param_array('agreedoc', [], PARAM_INT);
$behalfid = optional_param('userid', null, PARAM_INT);
$agreedocs = [];
$declinedocs = [];
foreach ($listdocs as $pvid) {
$status = optional_param('status'.$pvid, null, PARAM_INT);
if ($status === 1) {
$agreedocs[] = $pvid;
} else if ($status === 0) {
$declinedocs[] = $pvid;
}
}
$listdocs = array_values(array_unique($listdocs));
$agreedocs = array_values(array_unique($agreedocs));
$declinedocs = array_values(array_unique($declinedocs));
$PAGE->set_context(context_system::instance());
$PAGE->set_pagelayout('standard');
$PAGE->set_url('/admin/tool/policy/index.php');
$PAGE->set_popup_notification_allowed(false);
if (array_diff($agreedocs, $listdocs)) {
if (array_diff($agreedocs, $listdocs) || array_diff($declinedocs, $listdocs)) {
throw new moodle_exception('invalidaccessparameter');
}
if (isloggedin() && !isguestuser()) {
// Existing user.
$haspermissionagreedocs = api::can_accept_policies($behalfid);
$haspermissionagreedocs = api::can_accept_policies($listdocs, $behalfid);
} else {
// New user.
$haspermissionagreedocs = true;
}
if (!$haspermissionagreedocs) {
$outputpage = new \tool_policy\output\page_nopermission($behalfid);
$outputpage = new \tool_policy\output\page_nopermission($listdocs, $behalfid);
} else if ($cancel) {
redirect(new moodle_url('/'));
} else {
if (!$behalfid && \core\session\manager::is_loggedinas()) {
$behalfid = $USER->id;
}
$outputpage = new \tool_policy\output\page_agreedocs($listdocs, $agreedocs, $behalfid, $submit);
$outputpage = new \tool_policy\output\page_agreedocs($listdocs, $agreedocs, $declinedocs, $behalfid, $submit);
}
$output = $PAGE->get_renderer('tool_policy');

View File

@ -26,40 +26,36 @@
defined('MOODLE_INTERNAL') || die();
$string['acceptanceacknowledgement'] = 'I acknowledge that I have received a request to give consent on behalf of the above user(s).';
$string['acceptancecount'] = '{$a->agreedcount} of {$a->policiescount}';
$string['acceptancenote'] = 'Remarks';
$string['acceptancepolicies'] = 'Policies';
$string['acceptancessavedsucessfully'] = 'The agreements have been saved successfully.';
$string['acceptancestatusaccepted'] = 'Accepted';
$string['acceptancestatusacceptedbehalf'] = 'Accepted on user\'s behalf';
$string['acceptancestatusdeclined'] = 'Declined';
$string['acceptancestatusdeclinedbehalf'] = 'Declined on user\'s behalf';
$string['acceptancestatusoverall'] = 'Overall';
$string['acceptancestatuspartial'] = 'Partially accepted';
$string['acceptancestatuspending'] = 'Pending';
$string['acceptanceusers'] = 'Users';
$string['actions'] = 'Actions';
$string['activate'] = 'Set status to "Active"';
$string['activating'] = 'Activating a policy';
$string['activateconfirm'] = '<p>You are about to activate policy <em>\'{$a->name}\'</em> and make the version <em>\'{$a->revision}\'</em> the current one.</p><p>All users will be required to agree to this new policy version to be able to use the site.</p>';
$string['activateconfirmyes'] = 'Activate';
$string['agreed'] = 'Agreed';
$string['agreedby'] = 'Agreed by';
$string['agreedno'] = 'Consent not given';
$string['agreednowithlink'] = 'Consent not given; click to give consent on behalf of user for {$a}';
$string['agreednowithlinkall'] = 'Consent not given; click to give consent on behalf of user for all policies';
$string['agreedon'] = 'Agreed on';
$string['agreedyes'] = 'Agreed';
$string['agreedyesonbehalf'] = 'Consent given on behalf of user';
$string['agreedyesonbehalfwithlink'] = 'Consent given on behalf of user; click to withdraw user consent for {$a}';
$string['agreedyesonbehalfwithlinkall'] = 'Consent given on behalf of user; click to withdraw user consent for all policies';
$string['agreedyeswithlink'] = 'Consent given; click to withdraw user consent for {$a}';
$string['agreedyeswithlinkall'] = 'Consent given; click to withdraw user consent for all policies';
$string['agreepolicies'] = 'Please agree to the following policies';
$string['backtoprevious'] = 'Go back to previous page';
$string['backtotop'] = 'Back to top';
$string['cachedef_policy_optional'] = 'Cache of the optional/compulsory flag for policy versions';
$string['consentbulk'] = 'Consent';
$string['consentdetails'] = 'Give consent on behalf of user(s)';
$string['consentpagetitle'] = 'Consent';
$string['contactdpo'] = 'For any questions about the policies please contact the privacy officer.';
$string['dataproc'] = 'Personal data processing';
$string['declineacknowledgement'] = 'I acknowledge that I have received a request to decline consent on behalf of the above user(s).';
$string['declinethepolicy'] = 'Decline user consent';
$string['deleting'] = 'Deleting a version';
$string['deleteconfirm'] = '<p>Are you sure you want to delete policy <em>\'{$a->name}\'</em>?</p><p>This operation can not be undone.</p>';
$string['editingpolicydocument'] = 'Editing policy';
$string['errorpolicyversioncompulsory'] = 'Compulsory policies cannot be declined!';
$string['errorpolicyversionnotfound'] = 'There isn\'t any policy version with this identifier.';
$string['errorsaveasdraft'] = 'Minor change can not be saved as draft';
$string['errorusercantviewpolicyversion'] = 'The user doesn\'t have access to this policy version.';
@ -71,13 +67,15 @@ $string['filterrevision'] = 'Version: {$a}';
$string['filterrevisionstatus'] = 'Version: {$a->name} ({$a->status})';
$string['filterrole'] = 'Role: {$a}';
$string['filters'] = 'Filters';
$string['filterstatusno'] = 'Status: Not agreed';
$string['filterstatusdeclined'] = 'Status: Declined';
$string['filterstatuspending'] = 'Status: Pending';
$string['filterstatusyes'] = 'Status: Agreed';
$string['filterplaceholder'] = 'Search keyword or select filter';
$string['filterpolicy'] = 'Policy: {$a}';
$string['guestconsent:continue'] = 'Continue';
$string['guestconsentmessage'] = 'If you continue browsing this website, you agree to our policies:';
$string['iagree'] = 'I agree to the {$a}';
$string['idontagree'] = 'No thanks, I decline {$a}';
$string['iagreetothepolicy'] = 'Give consent';
$string['inactivate'] = 'Set status to "Inactive"';
$string['inactivating'] = 'Inactivating a policy';
@ -91,7 +89,7 @@ $string['minorchangeinfo'] = 'A minor change does not alter the meaning of the p
$string['managepolicies'] = 'Manage policies';
$string['movedown'] = 'Move down';
$string['moveup'] = 'Move up';
$string['mustagreetocontinue'] = 'Before continuing you must agree to all these policies.';
$string['mustagreetocontinue'] = 'Before continuing you need to acknowledge all these policies.';
$string['newpolicy'] = 'New policy';
$string['newversion'] = 'New version';
$string['noactivepolicies'] = 'There are no policies with an active version.';
@ -119,6 +117,9 @@ $string['policydoccontent'] = 'Full policy';
$string['policydochdrpolicy'] = 'Policy';
$string['policydochdrversion'] = 'Document version';
$string['policydocname'] = 'Name';
$string['policydocoptional'] = 'Agreement optional';
$string['policydocoptionalyes'] = 'Optional';
$string['policydocoptionalno'] = 'Compulsory';
$string['policydocrevision'] = 'Version';
$string['policydocsummary'] = 'Summary';
$string['policydocsummary_help'] = 'This text should provide a summary of the policy, potentially in a simplified and easily accessible form, using clear and plain language.';
@ -160,18 +161,33 @@ $string['privacy:metadata:versions:contentformat'] = 'The format of the content
$string['privacysettings'] = 'Privacy settings';
$string['readpolicy'] = 'Please read our {$a}';
$string['refertofullpolicytext'] = 'Please refer to the full {$a} if you would like to review the text.';
$string['response'] = 'Response';
$string['responseby'] = 'Respondent';
$string['responseon'] = 'Date';
$string['revokeacknowledgement'] = 'I acknowledge that I have received a request to withdraw consent on behalf of the above user(s).';
$string['revokedetails'] = 'Withdraw user consent';
$string['save'] = 'Save';
$string['saveasdraft'] = 'Save as draft';
$string['selectuser'] = 'Select user {$a}';
$string['selectusersforconsent'] = 'Select users to give consent on behalf of';
$string['selectusersforconsent'] = 'Select users to give consent on behalf of.';
$string['settodraft'] = 'Create a new draft';
$string['status'] = 'Policy status';
$string['statusformtitleaccept'] = 'Accepting policy';
$string['statusformtitledecline'] = 'Declining policy';
$string['statusformtitlerevoke'] = 'Withdrawing policy';
$string['statusinfo'] = 'A policy with \'Active\' status requires users to give their consent, either when they first log in, or in the case of existing users when they next log in.';
$string['status0'] = 'Draft';
$string['status1'] = 'Active';
$string['status2'] = 'Inactive';
$string['useracceptanceactionaccept'] = 'Accept';
$string['useracceptanceactionacceptone'] = 'Accept {$a}';
$string['useracceptanceactionacceptpending'] = 'Accept pending policies';
$string['useracceptanceactiondecline'] = 'Decline';
$string['useracceptanceactiondeclineone'] = 'Decline {$a}';
$string['useracceptanceactiondeclinepending'] = 'Decline pending policies';
$string['useracceptanceactiondetails'] = 'Details';
$string['useracceptanceactionrevoke'] = 'Withdraw';
$string['useracceptanceactionrevokeall'] = 'Withdraw accepted policies';
$string['useracceptanceactionrevokeone'] = 'Withdraw acceptance of {$a}';
$string['useracceptancecount'] = '{$a->agreedcount} of {$a->userscount} ({$a->percent}%)';
$string['useracceptancecountna'] = 'N/A';
$string['useracceptances'] = 'User agreements';

View File

@ -204,9 +204,10 @@ function tool_policy_pluginfile($course, $cm, $context, $filearea, $args, $force
*/
function tool_policy_get_fontawesome_icon_map() {
return [
'tool_policy:agreedno' => 'fa-times text-danger',
'tool_policy:agreedyes' => 'fa-check text-success',
'tool_policy:agreedyesonbehalf' => 'fa-check text-info',
'tool_policy:agreed' => 'fa-check text-success',
'tool_policy:declined' => 'fa-times text-danger',
'tool_policy:pending' => 'fa-clock-o text-warning',
'tool_policy:partial' => 'fa-exclamation-triangle text-warning',
'tool_policy:level' => 'fa-level-up fa-rotate-90 text-muted',
];
}

View File

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 234 B

View File

Before

Width:  |  Height:  |  Size: 445 B

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

View File

@ -1,3 +0,0 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-0.1 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M6.4 11.1c-2-2.5-3.7-4-3.7-4L0 9.8C5 13.1 8.1 16 8.1 16s.2-.7.6-1.8c.9-2.7 3.2-8.1 7.1-14.2-4.6 3.7-7.7 8.2-9.4 11.1z" fill="#FFB844"/></svg>

Before

Width:  |  Height:  |  Size: 448 B

View File

Before

Width:  |  Height:  |  Size: 258 B

After

Width:  |  Height:  |  Size: 258 B

View File

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 16 16"
preserveAspectRatio="xMinYMid meet"
overflow="visible"
version="1.1"
id="svg956"
sodipodi:docname="warning.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
inkscape:export-filename="/home/mudrd8mz/www/html/mdk/m36/admin/tool/policy/pix/warning.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<metadata
id="metadata962">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs960" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1624"
inkscape:window-height="911"
id="namedview958"
showgrid="false"
inkscape:zoom="14.75"
inkscape:cx="-0.06779661"
inkscape:cy="7.3559322"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="svg956" />
<path
d="M7 .7L.2 14.1c-.5 1 0 1.8 1.1 1.8h13.2c1.1 0 1.6-.8 1.1-1.8L8.8.7C8.3-.2 7.5-.2 7 .7zm2.4 12.7c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5.7-1.5 1.5-1.5 1.5.6 1.5 1.5zm-.2-7.5v4c0 .5-.5 1-1 1h-.6c-.5 0-1-.5-1-1v-4c0-.6.5-1 1-1h.6c.6 0 1 .4 1 1z"
fill="#999"
id="path954"
style="fill:#ffb844;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 16 16"
preserveAspectRatio="xMinYMid meet"
overflow="visible"
version="1.1"
id="svg5917"
sodipodi:docname="agreedpending.svg"
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
<metadata
id="metadata5923">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs5921" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1026"
id="namedview5919"
showgrid="false"
inkscape:zoom="14.75"
inkscape:cx="-0.06779661"
inkscape:cy="7.3559322"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="svg5917" />
<path
d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 14c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm4-6c0 .6-.4 1-1 1H8c-.6 0-1-.4-1-1V4c0-.6.4-1 1-1s1 .4 1 1v3h2c.6 0 1 .4 1 1z"
fill="#999"
id="path5915"
style="fill:#ffb844;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -43,6 +43,8 @@
"revision": "2.0",
"hasarchived": true,
"timeaccepted": "1 Mar 2018",
"iscurrent": true,
"isoptional": false,
"agreement": {
"onbehalf": false,
"status": false,
@ -60,6 +62,8 @@
"note": "Based on parent's agreement via email",
"hasarchived": false,
"timeaccepted": "15 Feb 2018",
"iscurrent": true,
"isoptional": false,
"agreement": {
"onbehalf": true,
"status": true,
@ -80,10 +84,10 @@
<tr>
<th>{{#str}} policydocname, tool_policy {{/str}}</th>
<th>{{#str}} policydocrevision, tool_policy {{/str}}</th>
<th>{{#str}} agreed, tool_policy {{/str}}</th>
<th>{{#str}} agreedon, tool_policy {{/str}}</th>
<th>{{#str}} response, tool_policy {{/str}}</th>
<th>{{#str}} responseon, tool_policy {{/str}}</th>
{{#hasonbehalfagreements}}
<th>{{#str}} agreedby, tool_policy {{/str}}</th>
<th>{{#str}} responseby, tool_policy {{/str}}</th>
<th>{{#str}} acceptancenote, tool_policy {{/str}}</th>
{{/hasonbehalfagreements}}
<th></th>
@ -106,7 +110,8 @@
</td>
<td>
<a href="{{viewurl}}">{{{revision}}}</a>
{{#iscurrent}}<span class="label label-success">{{#str}} status1, tool_policy {{/str}}{{/iscurrent}}
{{#iscurrent}}<span class="label label-success">{{#str}} status1, tool_policy {{/str}}</span>{{/iscurrent}}
{{#isoptional}}<span class="label label-info">{{#str}} policydocoptionalyes, tool_policy {{/str}}</span>{{/isoptional}}
</td>
<td>
{{>tool_policy/user_agreement}}

View File

@ -80,6 +80,8 @@
{{#policies}}
<input value="{{id}}" name="listdoc[]" type="hidden">
<div class="agreedoc-policy clearfix m-t-2 m-b-1">
<h3>{{{name}}}</h3>
<div class="agreedoc-content">
@ -90,11 +92,27 @@
{{# str }}refertofullpolicytext, tool_policy, {{{policymodal}}} {{/ str }}
</div>
<div class="agreedoc-form m-t-1">
{{#optional}}
<div class="agreedoc-radios">
<div class="agreedoc-radios-1">
<label>
<input type="radio" name="status{{id}}" value="1" {{#versionagreed}}checked="{{.}}"{{/versionagreed}}>
{{# str }}iagree, tool_policy, {{{name}}} {{/ str }}
</label>
</div>
<div class="agreedoc-radios-0">
<label>
<input type="radio" name="status{{id}}" value="0" {{#versiondeclined}}checked="{{.}}"{{/versiondeclined}}>
{{# str }}idontagree, tool_policy, {{{name}}} {{/ str }}
</label>
</div>
</div>
{{/optional}}
{{^optional}}
<div class="agreedoc-checkbox">
<label>
<input value="{{id}}" name="listdoc[]" type="hidden" >
<input value="{{id}}" name="agreedoc[]" {{#versionagreed}}checked="{{.}}"{{/versionagreed}} type="checkbox">
<strong>{{# str }}iagree, tool_policy, {{{name}}} {{/ str }}</strong>
<input value="1" name="status{{id}}" {{#versionagreed}}checked="{{.}}"{{/versionagreed}} type="checkbox">
{{# str }}iagree, tool_policy, {{{name}}} {{/ str }}
<i class="icon fa fa-exclamation-circle text-danger fa-fw" title="{{# str }} required {{/ str }}" ></i>
</label>
</div>
@ -106,6 +124,7 @@
<li><small>{{{.}}}</small></li>
{{/versionbehalfsagreed}}
</ul>
{{/optional}}
</div>
</div>
</div>

View File

@ -45,6 +45,7 @@
"typetext": "Site policy",
"audiencetext": "All users",
"statustext": "Active",
"optionaltext": "Optional",
"revision": "1.0",
"timemodified": 1521531208,
"acceptancescounturl": "#",
@ -56,6 +57,7 @@
"typetext": "Site policy",
"audiencetext": "All users",
"statustext": "Draft",
"optionaltext": "Compulsory",
"revision": "2.0",
"timemodified": 1521531208,
"acceptancescounttext": "N/A"
@ -85,7 +87,6 @@
<th scope="col">{{#str}} policydocname, tool_policy {{/str}}</th>
<th scope="col">{{#str}} status, tool_policy {{/str}}</th>
<th scope="col">{{#str}} policydocrevision, tool_policy {{/str}}</th>
<th scope="col">{{#str}} lastmodified, core {{/str}}</th>
{{#canviewacceptances}}
<th scope="col">{{#str}} usersaccepted, tool_policy {{/str}}</th>
{{/canviewacceptances}}
@ -105,7 +106,7 @@
{{/indented}}
<div {{#indented}}style="margin-left: 24px" {{/indented}}>
<div>{{{name}}}</div>
<div class="text-muted, muted"><small>{{{typetext}}}, {{{audiencetext}}}</small></div>
<div class="text-muted, muted"><small>{{{typetext}}}, {{{audiencetext}}}, {{{optionaltext}}}</small></div>
</div>
</td>
<td>
@ -113,9 +114,13 @@
</td>
<td>
{{revision}}
</td>
<td>
{{#userdate}} {{timemodified}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}
<div class="text-muted, muted">
<small>
<time title="{{#str}} lastmodified, core {{/str}}" datetime="{{#userdate}} {{timemodified}}, %Y-%m-%dT%T%z {{/userdate}}">
{{#userdate}} {{timemodified}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}
</time>
</small>
</div>
</td>
{{#canviewacceptances}}
<td>

View File

@ -92,6 +92,9 @@
{{#accepturl}}
<a role="button" href="{{accepturl}}" class="btn btn-primary">{{#str}} iagree, tool_policy, {{{policy.name}}} {{/str}}</a>
{{/accepturl}}
{{#declineurl}}
<a role="button" href="{{declineurl}}" class="btn btn-link">{{#str}} idontagree, tool_policy, {{{policy.name}}} {{/str}}</a>
{{/declineurl}}
<div class="pull-right">
<a href="#top">

View File

@ -26,67 +26,30 @@
-
Context variables required for this template:
* status
* onbehalf
* canaccept
* acceptlink
* statusicon
* statustext
* actions
- url
- data
- text
- title
Example context (json):
{
"status": false,
"onbehalf": false,
"canaccept": true,
"canrevoke": true,
"acceptlink": "/",
"revokelink": "/",
"singleversion": false,
"versionname": ""
"statusicon": "accepted",
"statustext": "Accepted",
"actions": {
"url": "/admin/tool/policy/accept.php?param=value",
"data": "acceptmodal",
"text": "Withdraw",
"title": "Withdraw acceptance of Site policy"
}
}
}}
{{#status}}
{{#canrevoke}}
{{#singleversion}}
{{#onbehalf}}
<a href="{{revokelink}}" data-action="acceptmodal">{{#pix}}agreedyesonbehalf, tool_policy,
{{#str}} agreedyesonbehalfwithlink, tool_policy, {{{versionname}}} {{/str}}{{/pix}}</a>
{{/onbehalf}}
{{^onbehalf}}
<a href="{{revokelink}}" data-action="acceptmodal">{{#pix}}agreedyes, tool_policy,
{{#str}} agreedyeswithlink, tool_policy, {{{versionname}}} {{/str}}{{/pix}}</a>
{{/onbehalf}}
{{/singleversion}}
{{^singleversion}}
{{#onbehalf}}
<a href="{{revokelink}}" data-action="acceptmodal">{{#pix}}agreedyesonbehalf, tool_policy,
{{#str}} agreedyesonbehalfwithlinkall, tool_policy {{/str}}{{/pix}}</a>
{{/onbehalf}}
{{^onbehalf}}
<a href="{{revokelink}}" data-action="acceptmodal">{{#pix}}agreedyes, tool_policy,
{{#str}} agreedyeswithlinkall, tool_policy {{/str}}{{/pix}}</a>
{{/onbehalf}}
{{/singleversion}}
{{/canrevoke}}
{{^canrevoke}}
{{#onbehalf}}
<span>{{#pix}}agreedyesonbehalf, tool_policy, {{#str}} agreedyesonbehalf, tool_policy {{/str}}{{/pix}}</span>
{{/onbehalf}}
{{^onbehalf}}
<span>{{#pix}}agreedyes, tool_policy, {{#str}} agreedyes, tool_policy {{/str}}{{/pix}}</span>
{{/onbehalf}}
{{/canrevoke}}
{{/status}}
{{^status}}
{{#canaccept}}
{{#singleversion}}
<a href="{{acceptlink}}" data-action="acceptmodal">{{#pix}}agreedno, tool_policy, {{#str}} agreednowithlink, tool_policy, {{{versionname}}} {{/str}}{{/pix}}</a>
{{/singleversion}}
{{^singleversion}}
<a href="{{acceptlink}}" data-action="acceptmodal">{{#pix}}agreedno, tool_policy, {{#str}} agreednowithlinkall, tool_policy {{/str}}{{/pix}}</a>
{{/singleversion}}
{{/canaccept}}
{{^canaccept}}
<span>{{#pix}}agreedno, tool_policy, {{#str}} agreedno, tool_policy {{/str}}{{/pix}}</span>
{{/canaccept}}
{{/status}}
<span class="tool_policy-user_agreement-icon">{{#pix}} {{statusicon}}, tool_policy{{/pix}}</span>
<span class="tool_policy-user_agreement-status">{{statustext}}</span>
<span class="tool_policy-user_agreement-actions">
{{#actions}}
<a href="{{url}}" title="{{title}}" data-action="{{data}}"><small>{{text}}</small></a>
{{/actions}}
</span>

View File

@ -25,6 +25,7 @@
use tool_policy\api;
use tool_policy\policy_version;
use tool_policy\test\helper;
defined('MOODLE_INTERNAL') || die();
@ -207,9 +208,9 @@ class tool_policy_api_testcase extends advanced_testcase {
$this->resetAfterTest();
$this->setAdminUser();
$policy1 = $this->add_policy(['audience' => policy_version::AUDIENCE_LOGGEDIN]);
$policy2 = $this->add_policy(['audience' => policy_version::AUDIENCE_GUESTS]);
$policy3 = $this->add_policy();
$policy1 = helper::add_policy(['audience' => policy_version::AUDIENCE_LOGGEDIN]);
$policy2 = helper::add_policy(['audience' => policy_version::AUDIENCE_GUESTS]);
$policy3 = helper::add_policy();
api::make_current($policy1->get('id'));
api::make_current($policy2->get('id'));
@ -242,54 +243,6 @@ class tool_policy_api_testcase extends advanced_testcase {
$policy3->get('policyid') => $policy3->get('id')], $ids);
}
/**
* Helper method that creates a new policy for testing
*
* @param array $params
* @return policy_version
*/
protected function add_policy($params = []) {
static $counter = 0;
$counter++;
$defaults = [
'name' => 'Policy '.$counter,
'summary_editor' => ['text' => "P$counter summary", 'format' => FORMAT_HTML, 'itemid' => 0],
'content_editor' => ['text' => "P$counter content", 'format' => FORMAT_HTML, 'itemid' => 0],
];
$params = (array)$params + $defaults;
$formdata = api::form_policydoc_data(new policy_version(0));
foreach ($params as $key => $value) {
$formdata->$key = $value;
}
return api::form_policydoc_add($formdata);
}
/**
* Helper method that prepare a policy document with some versions.
*
* @param int $numversions The number of policy versions to create.
* @return array Array with all the policy versions created.
*/
protected function create_versions($numversions = 2) {
// Prepare a policy document with some versions.
$policy = self::add_policy([
'name' => 'Test policy',
'revision' => 'v1',
]);
for ($i = 2; $i <= $numversions; $i++) {
$formdata = api::form_policydoc_data($policy);
$formdata->revision = 'v'.$i;
api::form_policydoc_update_new($formdata);
}
$list = api::list_policies($policy->get('policyid'));
return $list[0]->draftversions;
}
/**
* Test behaviour of the {@link api::can_user_view_policy_version()} method.
*/
@ -326,7 +279,7 @@ class tool_policy_api_testcase extends advanced_testcase {
accesslib_clear_all_caches_for_unit_testing();
// Prepare a policy document with some versions.
list($policy1, $policy2, $policy3) = $this->create_versions(3);
list($policy1, $policy2, $policy3) = helper::create_versions(3);
// Normally users do not have access to policy drafts.
$this->assertFalse(api::can_user_view_policy_version($policy1, null, $child->id));
@ -368,6 +321,164 @@ class tool_policy_api_testcase extends advanced_testcase {
$this->assertTrue(api::can_user_view_policy_version($policy3, null, $parent->id));
}
/**
* Test behaviour of the {@link api::can_accept_policies()} method.
*/
public function test_can_accept_policies() {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
$user = $this->getDataGenerator()->create_user();
$child = $this->getDataGenerator()->create_user();
$parent = $this->getDataGenerator()->create_user();
$officer = $this->getDataGenerator()->create_user();
$manager = $this->getDataGenerator()->create_user();
$syscontext = context_system::instance();
$childcontext = context_user::instance($child->id);
$roleminorid = create_role('Digital minor', 'digiminor', 'Not old enough to accept site policies themselves');
$roleparentid = create_role('Parent', 'parent', 'Can accept policies on behalf of their child');
$roleofficerid = create_role('Policy officer', 'policyofficer', 'Can see all acceptances but can\'t edit policy documents');
$rolemanagerid = create_role('Policy manager', 'policymanager', 'Can manage policy documents');
assign_capability('tool/policy:accept', CAP_PROHIBIT, $roleminorid, $syscontext->id);
assign_capability('tool/policy:acceptbehalf', CAP_ALLOW, $roleparentid, $syscontext->id);
assign_capability('tool/policy:acceptbehalf', CAP_ALLOW, $roleofficerid, $syscontext->id);
assign_capability('tool/policy:viewacceptances', CAP_ALLOW, $roleofficerid, $syscontext->id);
assign_capability('tool/policy:acceptbehalf', CAP_ALLOW, $rolemanagerid, $syscontext->id);
assign_capability('tool/policy:managedocs', CAP_ALLOW, $rolemanagerid, $syscontext->id);
role_assign($roleminorid, $child->id, $syscontext->id);
role_assign($roleparentid, $parent->id, $childcontext->id);
role_assign($roleofficerid, $officer->id, $syscontext->id);
role_assign($rolemanagerid, $manager->id, $syscontext->id);
accesslib_clear_all_caches_for_unit_testing();
$policy1 = helper::add_policy(['optional' => policy_version::AGREEMENT_COMPULSORY])->to_record();
$policy2 = helper::add_policy(['optional' => policy_version::AGREEMENT_COMPULSORY])->to_record();
$policy3 = helper::add_policy(['optional' => policy_version::AGREEMENT_OPTIONAL])->to_record();
$policy4 = helper::add_policy(['optional' => policy_version::AGREEMENT_OPTIONAL])->to_record();
$mixed = [$policy1->id, $policy2->id, $policy3->id, $policy4->id];
$compulsory = [$policy1->id, $policy2->id];
$optional = [$policy3->id, $policy4->id];
// Normally users can accept all policies.
$this->setUser($user);
$this->assertTrue(api::can_accept_policies($mixed));
$this->assertTrue(api::can_accept_policies($compulsory));
$this->assertTrue(api::can_accept_policies($optional));
// Digital minors can be set to not be able to accept policies themselves.
$this->setUser($child);
$this->assertFalse(api::can_accept_policies($mixed));
$this->assertFalse(api::can_accept_policies($compulsory));
$this->assertFalse(api::can_accept_policies($optional));
// The parent can accept optional policies on child's behalf.
$this->setUser($parent);
$this->assertTrue(api::can_accept_policies($mixed, $child->id));
$this->assertTrue(api::can_accept_policies($compulsory, $child->id));
$this->assertTrue(api::can_accept_policies($optional, $child->id));
// Officers and managers can accept on other user's behalf.
$this->setUser($officer);
$this->assertTrue(api::can_accept_policies($mixed, $parent->id));
$this->assertTrue(api::can_accept_policies($compulsory, $parent->id));
$this->assertTrue(api::can_accept_policies($optional, $parent->id));
$this->setUser($manager);
$this->assertTrue(api::can_accept_policies($mixed, $parent->id));
$this->assertTrue(api::can_accept_policies($compulsory, $parent->id));
$this->assertTrue(api::can_accept_policies($optional, $parent->id));
}
/**
* Test behaviour of the {@link api::can_decline_policies()} method.
*/
public function test_can_decline_policies() {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
$user = $this->getDataGenerator()->create_user();
$child = $this->getDataGenerator()->create_user();
$parent = $this->getDataGenerator()->create_user();
$officer = $this->getDataGenerator()->create_user();
$manager = $this->getDataGenerator()->create_user();
$syscontext = context_system::instance();
$childcontext = context_user::instance($child->id);
$roleminorid = create_role('Digital minor', 'digiminor', 'Not old enough to accept site policies themselves');
$roleparentid = create_role('Parent', 'parent', 'Can accept policies on behalf of their child');
$roleofficerid = create_role('Policy officer', 'policyofficer', 'Can see all acceptances but can\'t edit policy documents');
$rolemanagerid = create_role('Policy manager', 'policymanager', 'Can manage policy documents');
assign_capability('tool/policy:accept', CAP_PROHIBIT, $roleminorid, $syscontext->id);
assign_capability('tool/policy:acceptbehalf', CAP_ALLOW, $roleparentid, $syscontext->id);
assign_capability('tool/policy:acceptbehalf', CAP_ALLOW, $roleofficerid, $syscontext->id);
assign_capability('tool/policy:viewacceptances', CAP_ALLOW, $roleofficerid, $syscontext->id);
assign_capability('tool/policy:acceptbehalf', CAP_ALLOW, $rolemanagerid, $syscontext->id);
assign_capability('tool/policy:managedocs', CAP_ALLOW, $rolemanagerid, $syscontext->id);
role_assign($roleminorid, $child->id, $syscontext->id);
role_assign($roleparentid, $parent->id, $childcontext->id);
role_assign($roleofficerid, $officer->id, $syscontext->id);
role_assign($rolemanagerid, $manager->id, $syscontext->id);
accesslib_clear_all_caches_for_unit_testing();
$policy1 = helper::add_policy(['optional' => policy_version::AGREEMENT_COMPULSORY])->to_record();
$policy2 = helper::add_policy(['optional' => policy_version::AGREEMENT_COMPULSORY])->to_record();
$policy3 = helper::add_policy(['optional' => policy_version::AGREEMENT_OPTIONAL])->to_record();
$policy4 = helper::add_policy(['optional' => policy_version::AGREEMENT_OPTIONAL])->to_record();
$mixed = [$policy1->id, $policy2->id, $policy3->id, $policy4->id];
$compulsory = [$policy1->id, $policy2->id];
$optional = [$policy3->id, $policy4->id];
// Normally users can decline only optional policies.
$this->setUser($user);
$this->assertFalse(api::can_decline_policies($mixed));
$this->assertFalse(api::can_decline_policies($compulsory));
$this->assertTrue(api::can_decline_policies($optional));
// If they can't accept them, they can't decline them too.
$this->setUser($child);
$this->assertFalse(api::can_decline_policies($mixed));
$this->assertFalse(api::can_decline_policies($compulsory));
$this->assertFalse(api::can_decline_policies($optional));
// The parent can decline optional policies on child's behalf.
$this->setUser($parent);
$this->assertFalse(api::can_decline_policies($mixed, $child->id));
$this->assertFalse(api::can_decline_policies($compulsory, $child->id));
$this->assertTrue(api::can_decline_policies($optional, $child->id));
// Even officers or managers cannot decline compulsory policies.
$this->setUser($officer);
$this->assertFalse(api::can_decline_policies($mixed));
$this->assertFalse(api::can_decline_policies($compulsory));
$this->assertTrue(api::can_decline_policies($optional));
$this->assertFalse(api::can_decline_policies($mixed, $child->id));
$this->assertFalse(api::can_decline_policies($compulsory, $child->id));
$this->assertTrue(api::can_decline_policies($optional, $child->id));
$this->setUser($manager);
$this->assertFalse(api::can_decline_policies($mixed));
$this->assertFalse(api::can_decline_policies($compulsory));
$this->assertTrue(api::can_decline_policies($optional));
$this->assertFalse(api::can_decline_policies($mixed, $child->id));
$this->assertFalse(api::can_decline_policies($compulsory, $child->id));
$this->assertTrue(api::can_decline_policies($optional, $child->id));
}
/**
* Test behaviour of the {@link api::can_revoke_policies()} method.
*/
@ -406,32 +517,48 @@ class tool_policy_api_testcase extends advanced_testcase {
accesslib_clear_all_caches_for_unit_testing();
// Prepare a policy document with some versions.
list($policy1, $policy2, $policy3) = $this->create_versions(3);
$policy1 = helper::add_policy(['optional' => policy_version::AGREEMENT_COMPULSORY])->to_record();
$policy2 = helper::add_policy(['optional' => policy_version::AGREEMENT_OPTIONAL])->to_record();
$versionids = [$policy1->id, $policy2->id];
// Guests cannot revoke anything.
$this->setGuestUser();
$this->assertFalse(api::can_revoke_policies($versionids));
// Normally users do not have access to revoke policies.
$this->setUser($user);
$this->assertFalse(api::can_revoke_policies($user->id));
$this->assertFalse(api::can_revoke_policies($versionids, $user->id));
$this->setUser($child);
$this->assertFalse(api::can_revoke_policies($child->id));
$this->assertFalse(api::can_revoke_policies($versionids, $child->id));
// The parent can revoke the policy on behalf of her child (but not her own policies).
// Optional policies can be revoked if the user can accept them.
$this->setUser($user);
$this->assertTrue(api::can_revoke_policies([$policy2->id]));
$this->assertTrue(api::can_revoke_policies([$policy2->id], $user->id));
$this->setUser($child);
$this->assertFalse(api::can_revoke_policies([$policy2->id]));
$this->assertFalse(api::can_revoke_policies([$policy2->id], $child->id));
// The parent can revoke the policy on behalf of her child (but not her own policies, unless they are optional).
$this->setUser($parent);
$this->assertFalse(api::can_revoke_policies($parent->id));
$this->assertTrue(api::can_revoke_policies($child->id));
$this->assertFalse(api::can_revoke_policies($versionids, $parent->id));
$this->assertTrue(api::can_revoke_policies($versionids, $child->id));
$this->assertTrue(api::can_revoke_policies([$policy2->id]));
$this->assertTrue(api::can_revoke_policies([$policy2->id], $child->id));
// Officers and managers can revoke everything.
$this->setUser($officer);
$this->assertTrue(api::can_revoke_policies($officer->id));
$this->assertTrue(api::can_revoke_policies($child->id));
$this->assertTrue(api::can_revoke_policies($parent->id));
$this->assertTrue(api::can_revoke_policies($manager->id));
$this->assertTrue(api::can_revoke_policies($versionids, $officer->id));
$this->assertTrue(api::can_revoke_policies($versionids, $child->id));
$this->assertTrue(api::can_revoke_policies($versionids, $parent->id));
$this->assertTrue(api::can_revoke_policies($versionids, $manager->id));
$this->setUser($manager);
$this->assertTrue(api::can_revoke_policies($manager->id));
$this->assertTrue(api::can_revoke_policies($child->id));
$this->assertTrue(api::can_revoke_policies($parent->id));
$this->assertTrue(api::can_revoke_policies($officer->id));
$this->assertTrue(api::can_revoke_policies($versionids, $manager->id));
$this->assertTrue(api::can_revoke_policies($versionids, $child->id));
$this->assertTrue(api::can_revoke_policies($versionids, $parent->id));
$this->assertTrue(api::can_revoke_policies($versionids, $officer->id));
}
/**
@ -467,19 +594,26 @@ class tool_policy_api_testcase extends advanced_testcase {
$this->resetAfterTest();
$this->setAdminUser();
$policy1 = $this->add_policy()->to_record();
$policy1 = helper::add_policy()->to_record();
api::make_current($policy1->id);
$policy2 = $this->add_policy()->to_record();
$policy2 = helper::add_policy()->to_record();
api::make_current($policy2->id);
$policy3 = helper::add_policy(['optional' => true])->to_record();
api::make_current($policy3->id);
// Accept policy on behalf of somebody else.
$user1 = $this->getDataGenerator()->create_user();
$this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $user1->id]));
// Accepting just compulsory policies is not enough, we want to hear explicitly about the optional one, too.
api::accept_policies([$policy1->id, $policy2->id], $user1->id);
$this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $user1->id]));
// Optional policy does not need to be accepted, but it must be answered explicitly.
api::decline_policies([$policy3->id], $user1->id);
$this->assertEquals(1, $DB->get_field('user', 'policyagreed', ['id' => $user1->id]));
// Now revoke.
// Revoke previous agreement to a compulsory policy.
api::revoke_acceptance($policy1->id, $user1->id);
$this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $user1->id]));
@ -493,6 +627,12 @@ class tool_policy_api_testcase extends advanced_testcase {
$this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $user2->id]));
api::accept_policies([$policy2->id]);
$this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $user2->id]));
api::decline_policies([$policy3->id]);
$this->assertEquals(1, $DB->get_field('user', 'policyagreed', ['id' => $user2->id]));
api::accept_policies([$policy3->id]);
$this->assertEquals(1, $DB->get_field('user', 'policyagreed', ['id' => $user2->id]));
}
@ -507,14 +647,14 @@ class tool_policy_api_testcase extends advanced_testcase {
$user1 = $this->getDataGenerator()->create_user();
// Introducing a new policy.
list($policy1v1, $policy1v2) = $this->create_versions(2);
list($policy1v1, $policy1v2) = helper::create_versions(2);
api::make_current($policy1v1->id);
$this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $user1->id]));
api::accept_policies([$policy1v1->id], $user1->id);
$this->assertEquals(1, $DB->get_field('user', 'policyagreed', ['id' => $user1->id]));
// Introducing another policy.
$policy2v1 = $this->add_policy()->to_record();
$policy2v1 = helper::add_policy()->to_record();
api::make_current($policy2v1->id);
$this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $user1->id]));
api::accept_policies([$policy2v1->id], $user1->id);
@ -599,7 +739,7 @@ class tool_policy_api_testcase extends advanced_testcase {
$CFG->sitepolicyhandler = 'tool_policy';
$policy = $this->add_policy()->to_record();
$policy = helper::add_policy()->to_record();
api::make_current($policy->id);
// User has not accepted any policies.
@ -632,4 +772,44 @@ class tool_policy_api_testcase extends advanced_testcase {
require_login(null, false, null, false, true);
}
/**
* Test the three-state logic of the value returned by {@link api::is_user_version_accepted()}.
*/
public function test_is_user_version_accepted() {
$preloadedacceptances = [
4 => (object) [
'policyversionid' => 4,
'mainuserid' => 13,
'status' => 1,
],
6 => (object) [
'policyversionid' => 6,
'mainuserid' => 13,
'status' => 0,
],
];
$this->assertTrue(api::is_user_version_accepted(13, 4, $preloadedacceptances));
$this->assertFalse(api::is_user_version_accepted(13, 6, $preloadedacceptances));
$this->assertNull(api::is_user_version_accepted(13, 5, $preloadedacceptances));
}
/**
* Test the functionality of {@link api::get_agreement_optional()}.
*/
public function test_get_agreement_optional() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$policy1 = helper::add_policy(['optional' => policy_version::AGREEMENT_OPTIONAL])->to_record();
api::make_current($policy1->id);
$policy2 = helper::add_policy(['optional' => policy_version::AGREEMENT_COMPULSORY])->to_record();
api::make_current($policy2->id);
$this->assertEquals(api::get_agreement_optional($policy1->id), policy_version::AGREEMENT_OPTIONAL);
$this->assertEquals(api::get_agreement_optional($policy2->id), policy_version::AGREEMENT_COMPULSORY);
}
}

View File

@ -41,9 +41,9 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
And I navigate to "Users > Privacy and policies > User agreements" in site administration
And "Agreed" "icon" should exist in the "User One" "table_row"
And "Agreed" "icon" should exist in the "Max Manager" "table_row"
And "Consent not given" "icon" should exist in the "User Two" "table_row"
And "Accepted" "text" should exist in the "User One" "table_row"
And "Accepted" "text" should exist in the "Max Manager" "table_row"
And "Pending" "text" should exist in the "User Two" "table_row"
Scenario: Agree on behalf of another user as a manager, single policy, javascript off
Given I log in as "admin"
@ -57,17 +57,17 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I press "Next"
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
And I click on "Consent not given" "link" in the "User One" "table_row"
Then I should see "Give consent"
And I click on "Accept This site policy" "link" in the "User One" "table_row"
Then I should see "Accepting policy"
And I should see "User One"
And I should see "This site policy"
And I should see "I acknowledge that I have received a request to give consent on behalf of the above user(s)."
And I set the field "Remarks" to "Consent received from a parent"
And I press "Give consent"
And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
And "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
And "Max Manager" "link" should exist in the "User One" "table_row"
And "Consent received from a parent" "text" should exist in the "User One" "table_row"
And "Consent not given" "icon" should exist in the "User Two" "table_row"
And "Pending" "text" should exist in the "User Two" "table_row"
@javascript
Scenario: Agree on behalf of another user as a manager, single policy, javascript on
@ -83,17 +83,17 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I should not see "Next"
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
And I click on "Consent not given" "link" in the "User One" "table_row"
And I click on "Accept This site policy" "link" in the "User One" "table_row"
Then I should see "Give consent"
And I should see "User One"
And I should see "This site policy"
And I should see "I acknowledge that I have received a request to give consent on behalf of the above user(s)."
And I set the field "Remarks" to "Consent received from a parent"
And I press "Give consent"
And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
And "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
And "Max Manager" "link" should exist in the "User One" "table_row"
And "Consent received from a parent" "text" should exist in the "User One" "table_row"
And "Consent not given" "icon" should exist in the "User Two" "table_row"
And "Pending" "text" should exist in the "User Two" "table_row"
Scenario: View acceptances made by users on their own, multiple policies
Given I log in as "admin"
@ -119,19 +119,19 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I set the field "I agree to the This privacy policy" to "1"
And I press "Next"
And I navigate to "Users > Privacy and policies > User agreements" in site administration
And "Agreed" "icon" should exist in the "User One" "table_row"
And "Consent not given" "icon" should not exist in the "User One" "table_row"
And "Agreed" "icon" should exist in the "Max Manager" "table_row"
And "Consent not given" "icon" should exist in the "User Two" "table_row"
And "Agreed" "icon" should not exist in the "User Two" "table_row"
And I click on "2 of 2" "link" in the "User One" "table_row"
And "Agreed" "icon" should exist in the "This site policy" "table_row"
And "Agreed" "icon" should exist in the "This privacy policy" "table_row"
And "Accepted" "text" should exist in the "User One" "table_row"
And "Pending" "text" should not exist in the "User One" "table_row"
And "Accepted" "text" should exist in the "Max Manager" "table_row"
And "Pending" "text" should exist in the "User Two" "table_row"
And "Accepted" "text" should not exist in the "User Two" "table_row"
And I click on "Details" "link" in the "User One" "table_row"
And "Accepted" "text" should exist in the "This site policy" "table_row"
And "Accepted" "text" should exist in the "This privacy policy" "table_row"
And I am on site homepage
And I navigate to "Users > Privacy and policies > User agreements" in site administration
And I click on "0 of 2" "link" in the "User Two" "table_row"
And "Consent not given" "icon" should exist in the "This site policy" "table_row"
And "Consent not given" "icon" should exist in the "This privacy policy" "table_row"
And I click on "Details" "link" in the "User Two" "table_row"
And "Pending" "text" should exist in the "This site policy" "table_row"
And "Pending" "text" should exist in the "This privacy policy" "table_row"
Scenario: Agree on behalf of another user as a manager, multiple policies, javascript off
Given I log in as "admin"
@ -150,20 +150,20 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I set the field "I agree to the This privacy policy" to "1"
And I press "Next"
And I navigate to "Users > Privacy and policies > User agreements" in site administration
And I click on "Consent not given; click to give consent on behalf of user for This site policy" "link" in the "User One" "table_row"
Then I should see "Give consent"
And I click on "Accept This site policy" "link" in the "User One" "table_row"
Then I should see "Accepting policy"
And I should see "User One"
And I should see "This site policy"
And I should see "I acknowledge that I have received a request to give consent on behalf of the above user(s)."
And I set the field "Remarks" to "Consent received from a parent"
And I press "Give consent"
And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
And "Consent not given; click to give consent on behalf of user for This privacy policy" "icon" should exist in the "User One" "table_row"
And I click on "1 of 2" "link" in the "User One" "table_row"
And "Consent given on behalf of user" "icon" should exist in the "This site policy" "table_row"
And "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
And "Pending" "text" should exist in the "User One" "table_row"
And I click on "Details" "link" in the "User One" "table_row"
And "Accepted on user's behalf" "text" should exist in the "This site policy" "table_row"
And "Max Manager" "link" should exist in the "This site policy" "table_row"
And "Consent received from a parent" "text" should exist in the "This site policy" "table_row"
And "Consent not given" "icon" should exist in the "This privacy policy" "table_row"
And "Pending" "text" should exist in the "This privacy policy" "table_row"
@javascript
Scenario: Agree on behalf of another user as a manager, multiple policies, javascript on
@ -183,20 +183,20 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I set the field "I agree to the This privacy policy" to "1"
And I press "Next"
And I navigate to "Users > Privacy and policies > User agreements" in site administration
And I click on "Consent not given; click to give consent on behalf of user for This site policy" "link" in the "User One" "table_row"
And I click on "Accept This site policy" "link" in the "User One" "table_row"
Then I should see "Give consent"
And I should see "User One"
And I should see "This site policy"
And I should see "I acknowledge that I have received a request to give consent on behalf of the above user(s)."
And I set the field "Remarks" to "Consent received from a parent"
And I press "Give consent"
And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
And "Consent not given; click to give consent on behalf of user for This privacy policy" "icon" should exist in the "User One" "table_row"
And I click on "1 of 2" "link" in the "User One" "table_row"
And "Consent given on behalf of user" "icon" should exist in the "This site policy" "table_row"
And "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
And "Pending" "text" should exist in the "User One" "table_row"
And I click on "Details" "link" in the "User One" "table_row"
And "Accepted on user's behalf" "text" should exist in the "This site policy" "table_row"
And "Max Manager" "link" should exist in the "This site policy" "table_row"
And "Consent received from a parent" "text" should exist in the "This site policy" "table_row"
And "Consent not given" "icon" should exist in the "This privacy policy" "table_row"
And "Pending" "text" should exist in the "This privacy policy" "table_row"
Scenario: Policies and agreements profile link visible for current user
Given I log in as "user1"
@ -207,14 +207,14 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
# User can see his own agreements link in the profile.
Then I should see "Policies and agreements"
And I follow "Policies and agreements"
And "Agreed" "icon" should exist in the "This site policy" "table_row"
And "Accepted" "text" should exist in the "This site policy" "table_row"
# User can't see agreements link in other user profiles.
And I am on "Course1" course homepage
And I navigate to course participants
And I follow "User Two"
And I should not see "Policies and agreements"
Scenario: Policies and agreements profile link visible also for users who can access on behaf of others
Scenario: Policies and agreements profile link visible also for users who can access on behalf of others
Given I log in as "admin"
And I set the following system permissions of "Manager" role:
| capability | permission |
@ -248,18 +248,18 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I press "Continue"
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
And I click on "Consent not given" "link" in the "User One" "table_row"
Then I should see "Give consent"
And I click on "Accept This site policy" "link" in the "User One" "table_row"
Then I should see "Accepting policy"
And I should see "User One"
And I should see "This site policy"
And I should see "I acknowledge that I have received a request to give consent on behalf of the above user(s)."
And I set the field "Remarks" to "Consent received from a parent"
And I press "Give consent"
And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
And "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
And "Max Manager" "link" should not exist in the "User One" "table_row"
And "Admin User" "link" should exist in the "User One" "table_row"
And "Consent received from a parent" "text" should exist in the "User One" "table_row"
And "Consent not given" "icon" should exist in the "User Two" "table_row"
And "Pending" "text" should exist in the "User Two" "table_row"
@javascript
Scenario: Bulk agree on behalf of another users as a manager, multiple policies, javascript on
@ -281,16 +281,14 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
And I navigate to "Users > Privacy and policies > User agreements" in site administration
And I click on "Select" "checkbox" in the "User One" "table_row"
And I press "Consent"
And I should see "Give consent on behalf of user(s)"
And I should see "Accepting policy"
And I should see "One"
And I press "Cancel"
And I should not see "Give consent on behalf of user(s)"
And I should not see "Accepting policy"
And I click on "Select" "checkbox" in the "User Two" "table_row"
And I press "Consent"
And I should see "Give consent on behalf of user(s)"
And I should see "Accepting policy"
And I should see "User One, User Two"
When I press "Give consent"
Then "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
And I should see "2 of 2" in the "User One" "table_row"
And "Consent given on behalf of user" "icon" should exist in the "User Two" "table_row"
And I should see "2 of 2" in the "User Two" "table_row"
Then "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
And "Accepted on user's behalf" "text" should exist in the "User Two" "table_row"

View File

@ -52,6 +52,7 @@ class behat_tool_policy extends behat_base {
* - Summary: Policy summary text.
* - Content: Policy full text.
* - Agreement style (agreementstyle): 0 - On the consent page, 1 - On its own page
* - Agreement optional (optional): 0 - Compulsory policy, 1 - Optional policy
*
* @param TableNode $data
*/
@ -71,6 +72,7 @@ class behat_tool_policy extends behat_base {
'content',
'summary',
'agreementstyle',
'optional',
];
// Associative array "policy identifier" => id in the database .

View File

@ -113,7 +113,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I follow "Profile" in the user menu
# User can see his own agreements in the profile.
And I follow "Policies and agreements"
And "Agreed" "icon" should exist in the "This site policy" "table_row"
And "Accepted" "text" should exist in the "This site policy" "table_row"
And I log out
Scenario: Accept policy on sign up, multiple policies
@ -172,8 +172,8 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I follow "Profile" in the user menu
# User can see his own agreements in the profile.
And I follow "Policies and agreements"
And "Agreed" "icon" should exist in the "This site policy" "table_row"
And "Agreed" "icon" should exist in the "This privacy policy" "table_row"
And "Accepted" "text" should exist in the "This site policy" "table_row"
And "Accepted" "text" should exist in the "This privacy policy" "table_row"
And I should not see "This guests policy"
And I log out
@ -225,7 +225,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I follow "Profile" in the user menu
# User can see his own agreements in the profile.
And I follow "Policies and agreements"
And "Agreed" "icon" should exist in the "This site policy" "table_row"
And "Accepted" "text" should exist in the "This site policy" "table_row"
And I log out
Scenario: Accept policy on sign up, do not accept all policies
@ -252,12 +252,12 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the field "I agree to the This privacy policy" to "0"
And I press "Next"
Then I should see "Please agree to the following policies"
And I should see "Before continuing you must agree to all these policies."
And I should see "Before continuing you need to acknowledge all these policies."
# Confirm that a notification is displayed if only some policies are accepted.
When I set the field "I agree to the This site policy" to "1"
And I set the field "I agree to the This privacy policy" to "0"
Then I should see "Please agree to the following policies"
And I should see "Before continuing you must agree to all these policies."
And I should see "Before continuing you need to acknowledge all these policies."
Scenario: Accept policy on login, do not accept all policies
Given the following config values are set as admin:
@ -284,12 +284,12 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the field "I agree to the This privacy policy" to "0"
And I press "Next"
Then I should see "Please agree to the following policies"
And I should see "Before continuing you must agree to all these policies."
And I should see "Before continuing you need to acknowledge all these policies."
# Confirm that a notification is displayed if only some policies are accepted.
When I set the field "I agree to the This site policy" to "1"
And I set the field "I agree to the This privacy policy" to "0"
Then I should see "Please agree to the following policies"
And I should see "Before continuing you must agree to all these policies."
And I should see "Before continuing you need to acknowledge all these policies."
# Confirm that user can not browse the site (edit their profile).
When I follow "Profile" in the user menu
Then I should see "Please agree to the following policies"
@ -319,7 +319,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I set the field "I agree to the This privacy policy" to "1"
And I press "Next"
Then I should not see "Please agree to the following policies"
And I should not see "Before continuing you must agree to all these policies."
And I should not see "Before continuing you need to acknowledge all these policies."
# Confirm that user can login and browse the site (edit their profile).
When I open my profile in edit mode
Then the field "First name" matches value "User"
@ -661,7 +661,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I follow "Profile" in the user menu
# User can see his own agreements in the profile.
And I follow "Policies and agreements"
And "Agreed" "icon" should exist in the "This site policy" "table_row"
And "Accepted" "text" should exist in the "This site policy" "table_row"
And I log out
Scenario: Accepting policies on sign up, multiple policies with different style of giving ageement.
@ -730,10 +730,10 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I follow "Profile" in the user menu
# User can see his own agreements in the profile.
And I follow "Policies and agreements"
And "Agreed" "icon" should exist in the "Privacy policy" "table_row"
And "Agreed" "icon" should exist in the "Cookies policy" "table_row"
And "Agreed" "icon" should exist in the "Terms of Service" "table_row"
And "Agreed" "icon" should exist in the "Digital maturity declaration" "table_row"
And "Accepted" "text" should exist in the "Privacy policy" "table_row"
And "Accepted" "text" should exist in the "Cookies policy" "table_row"
And "Accepted" "text" should exist in the "Terms of Service" "table_row"
And "Accepted" "text" should exist in the "Digital maturity declaration" "table_row"
And I log out
Scenario: Accepting policies on login, multiple policies with different style of giving ageement.
@ -788,10 +788,10 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I follow "Profile" in the user menu
# User can see his own agreements in the profile.
And I follow "Policies and agreements"
And "Agreed" "icon" should exist in the "Privacy policy" "table_row"
And "Agreed" "icon" should exist in the "Cookies policy" "table_row"
And "Agreed" "icon" should exist in the "Terms of Service" "table_row"
And "Agreed" "icon" should exist in the "Digital maturity declaration" "table_row"
And "Accepted" "text" should exist in the "Privacy policy" "table_row"
And "Accepted" "text" should exist in the "Cookies policy" "table_row"
And "Accepted" "text" should exist in the "Terms of Service" "table_row"
And "Accepted" "text" should exist in the "Digital maturity declaration" "table_row"
And I log out
Scenario: Accepting policies on login, all and loggedin policies to be accepted on their own page.
@ -822,9 +822,9 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I press "I agree to the Terms of Service"
And I follow "Profile" in the user menu
And I follow "Policies and agreements"
And "Agreed" "icon" should exist in the "Privacy policy" "table_row"
And "Agreed" "icon" should exist in the "Terms of Service" "table_row"
And "Agreed" "icon" should exist in the "Digital maturity declaration" "table_row"
And "Accepted" "text" should exist in the "Privacy policy" "table_row"
And "Accepted" "text" should exist in the "Terms of Service" "table_row"
And "Accepted" "text" should exist in the "Digital maturity declaration" "table_row"
And "Cookies policy" "table_row" should not exist
And I log out
@ -872,8 +872,8 @@ Feature: User must accept policy managed by this plugin when logging in and sign
And I follow "Profile" in the user menu
# User can see his own agreements in the profile.
And I follow "Policies and agreements"
And "Agreed" "icon" should exist in the "Digital maturity declaration" "table_row"
And "Agreed" "icon" should exist in the "Cookies policy" "table_row"
And "Accepted" "text" should exist in the "Digital maturity declaration" "table_row"
And "Accepted" "text" should exist in the "Cookies policy" "table_row"
And "Privacy policy" "table_row" should not exist
And "Terms of Service" "table_row" should not exist
And I log out

View File

@ -35,8 +35,8 @@ Feature: Manage policies
And "Save as draft" "button" should not exist
And I press "Save"
Then the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Draft | v1 | N/A |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Draft | v1 | N/A |
And I log out
Scenario: Create new policy and save as active
@ -51,8 +51,8 @@ Feature: Manage policies
| Active | 1 |
And I press "Save"
Then the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Active | v1 | 0 of 4 (0%) |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Active | v1 | 0 of 4 (0%) |
And I log out
Scenario: Edit active policy and save as minor change
@ -74,8 +74,8 @@ Feature: Manage policies
And I set the field "Minor change" to "1"
And I press "Save"
Then the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Active | v1 amended | 1 of 4 (25%) |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Active | v1 amended | 1 of 4 (25%) |
And I log out
Scenario: Edit active policy and save as draft
@ -92,9 +92,9 @@ Feature: Manage policies
And I set the field "Version" to "v2"
And I press "Save as draft"
Then the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Active | v1 | 1 of 4 (25%) |
| Policy1 Site policy, All users | Draft | v2 | N/A |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Active | v1 | 1 of 4 (25%) |
| Policy1 Site policy, All users, Compulsory | Draft | v2 | N/A |
And I log out
Scenario: Edit active policy and save as new active version
@ -112,8 +112,8 @@ Feature: Manage policies
And I set the field "Version" to "v2"
And I press "Save"
Then the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy2 Site policy, All users | Active | v2 | 0 of 4 (0%) |
| Name | Policy status | Version | Agreements |
| Policy2 Site policy, All users, Compulsory | Active | v2 | 0 of 4 (0%) |
And I should not see "Policy1"
And I should not see "v1"
And I open the action menu in "Policy2" "table_row"
@ -121,8 +121,8 @@ Feature: Manage policies
And I should see "Policy2 previous versions"
And I should not see "v2"
Then the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Inactive | v1 | 1 of 4 (25%) |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Inactive | v1 | 1 of 4 (25%) |
And I log out
Scenario: Edit draft policy and save as draft
@ -141,8 +141,8 @@ Feature: Manage policies
And "Save as draft" "button" should not exist
And I press "Save"
Then the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Draft | v2 | N/A |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Draft | v2 | N/A |
And I should not see "v1"
And I open the action menu in "Policy1" "table_row"
And "View previous versions" "link" should not exist
@ -160,8 +160,8 @@ Feature: Manage policies
And I set the field "Active" to "1"
And I press "Save"
Then the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Active | v2 | 0 of 4 (0%) |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Active | v2 | 0 of 4 (0%) |
And I should not see "v1"
And I open the action menu in "Policy1" "table_row"
And "View previous versions" "link" should not exist
@ -178,8 +178,8 @@ Feature: Manage policies
Then I should see "All users will be required to agree to this new policy version to be able to use the site."
And I press "Continue"
And the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Active | v1 | 0 of 4 (0%) |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Active | v1 | 0 of 4 (0%) |
And I open the action menu in "Policy1" "table_row"
And "View previous versions" "link" should not exist
And I log out
@ -198,8 +198,8 @@ Feature: Manage policies
Then I should see "You are about to inactivate policy"
And I press "Continue"
And the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Inactive | v1 | 1 of 4 (25%) |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Inactive | v1 | 1 of 4 (25%) |
And I open the action menu in "Policy1" "table_row"
And I click on "Create a new draft" "link" in the "Policy1" "table_row"
And I set the field "Version" to "v2"
@ -212,16 +212,16 @@ Feature: Manage policies
And "Save as draft" "button" should not exist
And I press "Save"
And the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy2 Site policy, All users | Draft | v2 | N/A |
| Name | Policy status | Version | Agreements |
| Policy2 Site policy, All users, Compulsory | Draft | v2 | N/A |
And I should not see "v1"
And I should not see "Policy1"
And I open the action menu in "Policy2" "table_row"
And I click on "View previous versions" "link" in the "Policy2" "table_row"
And I should see "Policy2 previous versions"
And the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Inactive | v1 | 1 of 4 (25%) |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Inactive | v1 | 1 of 4 (25%) |
And I should not see "v2"
And I log out
@ -244,16 +244,16 @@ Feature: Manage policies
And I set the field "Active" to "1"
And I press "Save"
And the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy2 Site policy, All users | Active | v2 | 0 of 4 (0%) |
| Name | Policy status | Version | Agreements |
| Policy2 Site policy, All users, Compulsory | Active | v2 | 0 of 4 (0%) |
And I should not see "v1"
And I should not see "Policy1"
And I open the action menu in "Policy2" "table_row"
And I click on "View previous versions" "link" in the "Policy2" "table_row"
And I should see "Policy2 previous versions"
And the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users | Inactive | v1 | 1 of 4 (25%) |
| Name | Policy status | Version | Agreements |
| Policy1 Site policy, All users, Compulsory | Inactive | v1 | 1 of 4 (25%) |
And I should not see "v2"
And I log out

View File

@ -0,0 +1,264 @@
@tool @tool_policy
Feature: Optional policies
In order to exercise my privacy rights
As a user
I should be able to decline policy statements and withdraw my previously given consent to them
Background:
Given the following config values are set as admin:
| sitepolicyhandler | tool_policy |
And the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | One | one@example.com |
| user2 | User | Two | two@example.com |
| manager | Max | Manager | man@example.com |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| manager | manager | System | |
And the following "courses" exist:
| fullname | shortname |
| Course1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| user1 | C1 | student |
| user2 | C1 | student |
Scenario: Configuring a policy as optional
Given I log in as "manager"
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
And I follow "New policy"
# Policies are compulsory by default.
And the field "Agreement optional" matches value "No"
# Optional status can be set when creating a new policy.
And I set the following fields to these values:
| Name | ConsentPageOptional1 |
| Version | v1 |
| Summary | Policy summary |
| Full policy | Full text |
| Active | 1 |
| Show policy before showing other policies | No |
| Agreement optional | Yes |
When I press "Save"
Then the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version |
| ConsentPageOptional1 Site policy, All users, Optional | Active | v1 |
# Optional status can be edited.
And I open the action menu in "ConsentPageOptional1" "table_row"
And I click on "Edit" "link" in the "ConsentPageOptional1" "table_row"
And I set the field "Agreement optional" to "No"
And I set the field "Minor change" to "1"
And I press "Save"
And the following should exist in the "tool-policy-managedocs-wrapper" table:
| Name | Policy status | Version |
| ConsentPageOptional1 Site policy, All users, Compulsory | Active | v1 |
Scenario: Compulsory policies must be accepted prior signup, optional policies just after it
Given the following config values are set as admin:
| registerauth | email |
| passwordpolicy | 0 |
And the following policies exist:
| Name | Content | Summary | Agreementstyle | Optional |
| ConsentPageOptional1 | full text1 | short text1 | 0 | 1 |
| ConsentPageOptional2 | full text2 | short text2 | 0 | 1 |
| ConsentPageCompulsory1 | full text3 | short text3 | 0 | 0 |
| OwnPageCompulsory1 | full text4 | short text4 | 1 | 0 |
| OwnPageOptional1 | full text5 | short text5 | 1 | 1 |
And I am on site homepage
And I follow "Log in"
And I press "Create new account"
# Compulsory policies displayed on own page are shown first and must be agreed.
And I should see "OwnPageCompulsory1" in the "region-main" "region"
And I should see "short text4" in the "region-main" "region"
And I should see "full text4" in the "region-main" "region"
And I press "I agree to the OwnPageCompulsory1"
# Compulsory policies displayed on the consent page are shown next and must be agreed.
And I should see "ConsentPageCompulsory1"
And I should see "short text3" in the "region-main" "region"
And I should see "full text3" in the "region-main" "region"
And I press "Next"
And I should see "Please agree to the following policies"
And I set the field "I agree to the ConsentPageCompulsory1" to "1"
And I press "Next"
# The signup form can be submitted and a new account created.
And I set the following fields to these values:
| Username | user3 |
| Password | user3 |
| Email address | user3@address.invalid |
| Email (again) | user3@address.invalid |
| First name | User3 |
| Surname | L3 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user3@address.invalid"
And I confirm email for "user3"
And I should see "Thanks, User3 L3"
And I should see "Your registration has been confirmed"
When I press "Continue"
# After confirming the new account, the user is logged in and asked to accept or decline the optional policies.
# First come policies displayed on their own page.
Then I should see "OwnPageOptional1"
And I should see "short text5" in the "region-main" "region"
And I should see "full text5" in the "region-main" "region"
And I press "No thanks, I decline OwnPageOptional1"
# Then come policies displayed on the consent page.
And I should see "ConsentPageOptional1" in the "region-main" "region"
And I should see "short text1" in the "region-main" "region"
And I should see "full text1" in the "region-main" "region"
And I press "Next"
And I should see "ConsentPageOptional2" in the "region-main" "region"
And I should see "short text2" in the "region-main" "region"
And I should see "full text2" in the "region-main" "region"
And I press "Next"
And I should see "Please agree to the following policies"
And I set the field "I agree to the ConsentPageOptional1" to "1"
And I set the field "No thanks, I decline ConsentPageOptional2" to "0"
And I press "Next"
# Accepted and declined policies are shown in the profile.
And I follow "Profile" in the user menu
And I follow "Policies and agreements"
And "Accepted" "text" should exist in the "ConsentPageCompulsory1" "table_row"
And "Accepted" "text" should exist in the "ConsentPageOptional1" "table_row"
And "Accepted" "text" should exist in the "OwnPageCompulsory1" "table_row"
And "Declined" "text" should exist in the "OwnPageOptional1" "table_row"
And "Declined" "text" should exist in the "ConsentPageOptional2" "table_row"
Scenario: When a new optional policy is added, users are asked to accept/decline it on their next login
Given the following policies exist:
| Name | Content | Summary | Agreementstyle | Optional |
| ConsentPageOptional1 | full text1 | short text1 | 0 | 1 |
| OwnPageOptional1 | full text5 | short text5 | 1 | 1 |
When I log in as "user1"
# First come policies displayed on their own page.
Then I should see "OwnPageOptional1"
And I should see "short text5" in the "region-main" "region"
And I should see "full text5" in the "region-main" "region"
And I press "I agree to the OwnPageOptional1"
# Then come policies displayed on the consent page.
And I should see "ConsentPageOptional1" in the "region-main" "region"
And I should see "short text1" in the "region-main" "region"
And I should see "full text1" in the "region-main" "region"
And I press "Next"
And I should see "Please agree to the following policies"
And I set the field "No thanks, I decline ConsentPageOptional1" to "0"
And I press "Next"
# Accepted and declined policies are shown in the profile.
And I follow "Profile" in the user menu
And I follow "Policies and agreements"
And "Accepted" "text" should exist in the "OwnPageOptional1" "table_row"
And "Declined" "text" should exist in the "ConsentPageOptional1" "table_row"
Scenario: Users can withdraw an accepted optional policy and re-accept it again (js off)
Given the following policies exist:
| Name | Content | Summary | Agreementstyle | Optional |
| OwnPageOptional1 | full text1 | short text1 | 1 | 1 |
And I log in as "user1"
And I press "I agree to the OwnPageOptional1"
And I follow "Profile" in the user menu
And I follow "Policies and agreements"
And "Accepted" "text" should exist in the "OwnPageOptional1" "table_row"
And "Withdraw" "link" should exist in the "OwnPageOptional1" "table_row"
When I click on "Withdraw acceptance of OwnPageOptional1" "link" in the "OwnPageOptional1" "table_row"
Then I should see "Withdrawing policy"
And I should see "User One"
And I should see "OwnPageOptional1"
And I press "Withdraw user consent"
And "Declined" "text" should exist in the "OwnPageOptional1" "table_row"
And "Accept" "link" should exist in the "OwnPageOptional1" "table_row"
And I click on "Accept OwnPageOptional1" "link" in the "OwnPageOptional1" "table_row"
And I should see "Accepting policy"
And I should see "User One"
And I should see "OwnPageOptional1"
And I press "Give consent"
And "Accepted" "text" should exist in the "OwnPageOptional1" "table_row"
@javascript
Scenario: Users can withdraw an accepted optional policy and re-accept it again (js on)
Given the following policies exist:
| Name | Content | Summary | Agreementstyle | Optional |
| OwnPageOptional1 | full text1 | short text1 | 1 | 1 |
And I log in as "user1"
And I press "I agree to the OwnPageOptional1"
And I follow "Profile" in the user menu
And I follow "Policies and agreements"
And "Accepted" "text" should exist in the "OwnPageOptional1" "table_row"
And "Withdraw" "link" should exist in the "OwnPageOptional1" "table_row"
When I click on "Withdraw acceptance of OwnPageOptional1" "link" in the "OwnPageOptional1" "table_row"
Then I should see "Withdrawing policy"
And I should see "User One"
And I should see "OwnPageOptional1"
And I press "Withdraw user consent"
And "Declined" "text" should exist in the "OwnPageOptional1" "table_row"
And "Accept" "link" should exist in the "OwnPageOptional1" "table_row"
And I click on "Accept OwnPageOptional1" "link" in the "OwnPageOptional1" "table_row"
And I should see "Accepting policy"
And I should see "User One"
And I should see "OwnPageOptional1"
And I press "Give consent"
And "Accepted" "text" should exist in the "OwnPageOptional1" "table_row"
Scenario: Managers can see accepted, declined and pending acceptances of optional policies
Given the following policies exist:
| Name | Content | Summary | Agreementstyle | Optional |
| OwnPageOptional1 | full text1 | short text1 | 1 | 1 |
| OwnPageOptional2 | full text2 | short text2 | 1 | 1 |
And I log in as "user1"
And I press "I agree to the OwnPageOptional1"
And I press "No thanks, I decline OwnPageOptional2"
And I log out
And I log in as "manager"
And I press "I agree to the OwnPageOptional1"
And I press "I agree to the OwnPageOptional2"
When I navigate to "Users > Privacy and policies > User agreements" in site administration
# User One has accepted just some policies.
Then "Partially accepted" "text" should exist in the "User One" "table_row"
And "Details" "link" should exist in the "User One" "table_row"
# User Two did not have a chance to respond to the new policies yet.
And "Pending" "text" should exist in the "User Two" "table_row"
And "Details" "link" should exist in the "User Two" "table_row"
# Max Manager accepted all and can also change status of own acceptances.
And "Accepted" "text" should exist in the "Max Manager" "table_row"
And "Details" "link" should exist in the "Max Manager" "table_row"
And "Withdraw accepted policies" "link" should exist in the "Max Manager" "table_row"
And "Withdraw acceptance of OwnPageOptional1" "link" should exist in the "Max Manager" "table_row"
And "Withdraw acceptance of OwnPageOptional2" "link" should exist in the "Max Manager" "table_row"
Scenario: Administrators can see accepted, declined and pending acceptances of optional policies and also change them on behalf of other users
Given the following policies exist:
| Name | Content | Summary | Agreementstyle | Optional |
| OwnPageOptional1 | full text1 | short text1 | 1 | 1 |
| OwnPageOptional2 | full text2 | short text2 | 1 | 1 |
And I log in as "user1"
And I press "I agree to the OwnPageOptional1"
And I press "No thanks, I decline OwnPageOptional2"
And I log out
And I log in as "admin"
When I navigate to "Users > Privacy and policies > User agreements" in site administration
# User One has accepted just some policies.
Then "Partially accepted" "text" should exist in the "User One" "table_row"
And "Details" "link" should exist in the "User One" "table_row"
And "Withdraw acceptance of OwnPageOptional1" "link" should exist in the "User One" "table_row"
And "Accept OwnPageOptional2" "link" should exist in the "User One" "table_row"
# User Two did not have a chance to respond to the new policies yet.
And "Pending" "text" should exist in the "User Two" "table_row"
And "Accept pending policies" "link" should exist in the "User Two" "table_row"
And "Decline pending policies" "link" should exist in the "User Two" "table_row"
And "Accept OwnPageOptional1" "link" should exist in the "User Two" "table_row"
And "Decline OwnPageOptional1" "link" should exist in the "User Two" "table_row"
And "Accept OwnPageOptional2" "link" should exist in the "User Two" "table_row"
And "Decline OwnPageOptional2" "link" should exist in the "User Two" "table_row"
# Accept all policies on Max Manager's behalf.
And I click on "Accept pending policies" "link" in the "Max Manager" "table_row"
And I press "Give consent"
And "Accepted" "text" should exist in the "Max Manager" "table_row"
# Decline all policies on User Two's behalf.
And I click on "Decline pending policies" "link" in the "User Two" "table_row"
And I press "Decline user consent"
And "Declined on user's behalf" "text" should exist in the "User Two" "table_row"
And "Accepted" "text" should not exist in the "User Two" "table_row"
And "Pending" "text" should not exist in the "User Two" "table_row"
# Accept policy on User One's behalf.
And I click on "Accept OwnPageOptional2" "link" in the "User One" "table_row"
And I press "Give consent"
And "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
And "Declined" "text" should not exist in the "User One" "table_row"
And "Pending" "text" should not exist in the "User One" "table_row"

View File

@ -0,0 +1,137 @@
<?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/>.
/**
* Provides the {@link tool_policy_sitepolicy_handler_testcase} class.
*
* @package tool_policy
* @category test
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_policy\api;
use tool_policy\policy_version;
use tool_policy\privacy\local\sitepolicy\handler;
use tool_policy\test\helper;
defined('MOODLE_INTERNAL') || die();
global $CFG;
/**
* Unit tests for the {@link \tool_policy\privacy\local\sitepolicy\handler} class.
*
* @copyright 2018 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_policy_sitepolicy_handler_testcase extends advanced_testcase {
/**
* Test behaviour of the {@link \tool_policy\privacy\local\sitepolicy\handler::get_redirect_url()} method.
*/
public function test_get_redirect_url() {
$this->resetAfterTest();
$this->setAdminUser();
// No redirect for guests.
$this->assertNull(handler::get_redirect_url(true));
// No redirect if there is no policy.
$this->assertNull(handler::get_redirect_url());
// No redirect if no policy for logged in users.
$policy1 = helper::add_policy(['audience' => policy_version::AUDIENCE_GUESTS])->to_record();
api::make_current($policy1->id);
$this->assertNull(handler::get_redirect_url());
// URL only when there is actually some policy to show.
$policy2 = helper::add_policy(['audience' => policy_version::AUDIENCE_LOGGEDIN])->to_record();
api::make_current($policy2->id);
$this->assertInstanceOf('moodle_url', handler::get_redirect_url());
}
/**
* Test behaviour of the {@link \tool_policy\privacy\local\sitepolicy\handler::get_embed_url()} method.
*/
public function test_get_embed_url() {
$this->resetAfterTest();
$this->setAdminUser();
// No embed if there is no policy.
$this->assertNull(handler::get_embed_url());
$this->assertNull(handler::get_embed_url(true));
$policy1 = helper::add_policy(['audience' => policy_version::AUDIENCE_GUESTS])->to_record();
api::make_current($policy1->id);
// Policy exists for guests only.
$this->assertNull(handler::get_embed_url());
$this->assertInstanceOf('moodle_url', handler::get_embed_url(true));
$policy2 = helper::add_policy(['audience' => policy_version::AUDIENCE_LOGGEDIN])->to_record();
api::make_current($policy2->id);
// Some policy exists for all users.
$this->assertInstanceOf('moodle_url', handler::get_embed_url());
$this->assertInstanceOf('moodle_url', handler::get_embed_url(true));
}
/**
* Test behaviour of the {@link \tool_policy\privacy\local\sitepolicy\handler::accept()} method.
*/
public function test_accept() {
global $DB, $USER;
$this->resetAfterTest();
// False if not logged in.
$this->setUser(0);
$this->assertFalse(handler::accept());
// Guests accept policies implicitly by continuing to use the site.
$this->setGuestUser();
$this->assertTrue(handler::accept());
// Create one compulsory and one optional policy.
$this->setAdminUser();
$policy1 = helper::add_policy(['optional' => policy_version::AGREEMENT_COMPULSORY])->to_record();
api::make_current($policy1->id);
$policy2 = helper::add_policy(['optional' => policy_version::AGREEMENT_OPTIONAL])->to_record();
api::make_current($policy2->id);
$user1 = $this->getDataGenerator()->create_user();
$this->assertEquals(0, $DB->get_field('user', 'policyagreed', ['id' => $user1->id]));
$this->assertEmpty($DB->get_records('tool_policy_acceptances', ['userid' => $user1->id]));
$this->setUser($user1->id);
$this->assertEquals(0, $USER->policyagreed);
// Only the compulsory policy is marked as accepted when accepting via the handler.
$this->assertTrue(handler::accept());
$this->assertEquals(1, $DB->get_field('user', 'policyagreed', ['id' => $user1->id]));
$this->assertEquals(1, $USER->policyagreed);
$this->assertEquals(1, $DB->count_records('tool_policy_acceptances', ['userid' => $user1->id]));
$this->assertTrue($DB->record_exists('tool_policy_acceptances', ['userid' => $user1->id,
'policyversionid' => $policy1->id]));
}
/**
* Test presence of the {@link \tool_policy\privacy\local\sitepolicy\handler::signup_form()} method.
*/
public function test_signup_form() {
$this->assertTrue(method_exists('\tool_policy\privacy\local\sitepolicy\handler', 'signup_form'));
}
}

View File

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2018082900; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2018100100; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2018050800; // Requires this Moodle version.
$plugin->component = 'tool_policy'; // Full name of the plugin (used for diagnostics).