MDL-61864 tool_policy: user agreement reports

This commit is contained in:
Marina Glancy 2018-04-05 14:00:48 +08:00
parent 3d9571d5f0
commit cf398020d5
30 changed files with 3344 additions and 2 deletions

View File

@ -0,0 +1,65 @@
<?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/>.
/**
* Accept policies on behalf of users (non-JS version)
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../../config.php');
require_once($CFG->dirroot.'/user/editlib.php');
$userids = optional_param_array('userids', null, PARAM_INT);
$versionids = optional_param_array('versionids', null, PARAM_INT);
$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
require_login();
if (isguestuser()) {
print_error('noguest');
}
$context = context_system::instance();
$PAGE->set_context($context);
$PAGE->set_url(new moodle_url('/admin/tool/policy/accept.php'));
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
} else if (count($userids) == 1) {
$userid = reset($userids);
$returnurl = new moodle_url('/admin/tool/policy/user.php', ['userid' => $userid]);
} else {
$returnurl = new moodle_url('/admin/tool/policy/acceptances.php');
}
// Initialise the form, this will also validate users, versions and check permission to accept policies.
$form = new \tool_policy\form\accept_policy(null,
['versionids' => $versionids, 'userids' => $userids, 'showbuttons' => true]);
$form->set_data(['returnurl' => $returnurl]);
if ($form->is_cancelled()) {
redirect($returnurl);
} else if ($form->get_data()) {
$form->process();
redirect($returnurl);
}
$output = $PAGE->get_renderer('tool_policy');
echo $output->header();
echo $output->heading(get_string('consentdetails', 'tool_policy'));
$form->display();
echo $output->footer();

View File

@ -0,0 +1,65 @@
<?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/>.
/**
* View user acceptances to the policies
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../../config.php');
require_once($CFG->libdir.'/adminlib.php');
use core\output\notification;
$policyid = optional_param('policyid', null, PARAM_INT);
$versionid = optional_param('versionid', null, PARAM_INT);
$versionid = optional_param('versionid', null, PARAM_INT);
$filtersapplied = optional_param_array('unified-filters', [], PARAM_NOTAGS);
$acceptancesfilter = new \tool_policy\output\acceptances_filter($policyid, $versionid, $filtersapplied);
$policyid = $acceptancesfilter->get_policy_id_filter();
$versionid = $acceptancesfilter->get_version_id_filter();
// Set up the page as an admin page 'tool_policy_managedocs'.
$urlparams = ($policyid ? ['policyid' => $policyid] : []) + ($versionid ? ['versionid' => $versionid] : []);
admin_externalpage_setup('tool_policy_acceptances', '', $urlparams,
new moodle_url('/admin/tool/policy/acceptances.php'));
$acceptancesfilter->validate_ids();
$output = $PAGE->get_renderer('tool_policy');
if ($acceptancesfilter->get_versions()) {
$acceptances = new \tool_policy\acceptances_table('tool_policy_user_acceptances', $acceptancesfilter, $output);
if ($acceptances->is_downloading()) {
$acceptances->download();
}
}
echo $output->header();
echo $output->heading(get_string('useracceptances', 'tool_policy'));
echo $output->render($acceptancesfilter);
if (!empty($acceptances)) {
$acceptances->display();
} else if ($acceptancesfilter->get_avaliable_policies()) {
// There are no non-guest policies.
echo $output->notification(get_string('selectpolicyandversion', 'tool_policy'), notification::NOTIFY_INFO);
} else {
// There are no non-guest policies.
echo $output->notification(get_string('nopolicies', 'tool_policy'), notification::NOTIFY_INFO);
}
echo $output->footer();

View File

@ -0,0 +1 @@
define(["jquery","core/form-autocomplete","core/str","core/notification"],function(a,b,c,d){var e={UNIFIED_FILTERS:"#unified-filters"},f=function(){var f=[{key:"filterplaceholder",component:"tool_policy"},{key:"nofiltersapplied",component:"tool_policy"}];M.util.js_pending("acceptances_filter_datasource"),c.get_strings(f).done(function(a){var c=a[0],f=a[1];b.enhance(e.UNIFIED_FILTERS,!0,"tool_policy/acceptances_filter_datasource",c,!1,!0,f,!0).then(function(){M.util.js_complete("acceptances_filter_datasource")}).fail(d.exception)}).fail(d.exception);var g=a(e.UNIFIED_FILTERS).val();a(e.UNIFIED_FILTERS).on("change",function(){var b=a(this).val(),c=[],d=[],e=!1;if(a.each(b,function(a,b){var f=b.split(":",2);if(2!==f.length)return d.push(b),!0;var g=f[0],h=f[1];return"undefined"!=typeof c[g]&&(e=!0),c[g]=h,!0}),e){var f=[];for(var h in c)f.push(h+":"+c[h]);f=f.concat(d),a(this).val(f)}g.join(",")!=b.join(",")&&this.form.submit()})},g=function(){return a(e.UNIFIED_FILTERS).closest("form")};return{init:function(){f()},getForm:function(){return g()}}});

View File

@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification"],function(a,b,c){return{list:function(b,c){var d=[],e=a(b),f=a(b).data("originaloptionsjson"),g=e.val();a.each(f,function(b,e){return""!==a.trim(c)&&e.label.toLocaleLowerCase().indexOf(c.toLocaleLowerCase())===-1||(a.inArray(e.value,g)>-1||(d.push(e),!0))});var h=new a.Deferred;return h.resolve(d),h.promise()},processResults:function(b,c){var d=[];return a.each(c,function(a,b){d.push({value:b.value,label:b.label})}),d},transport:function(a,b,d){this.list(a,b).then(d)["catch"](c.exception)}}});

View File

@ -0,0 +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"}],i.prototype.init=function(){var c=a("a[data-action=acceptmodal]");c.on("click",function(b){b.preventDefault();var c=a(b.currentTarget).attr("href"),d=c.slice(c.indexOf("?")+1);this.showFormModal(d)}.bind(this)),c=a("form[data-action=acceptmodal]"),c.on("submit",function(d){if(d.preventDefault(),a(d.currentTarget).find('input[type=checkbox][name="userids[]"]:checked').length){var f=a(d.currentTarget).serialize();this.showFormModal(f,c)}else b.get_strings(this.stringKeys).done(function(a){e.alert("",a[2],a[3])})}.bind(this))},i.prototype.showFormModal=function(a,d){b.get_strings(this.stringKeys).done(function(b){c.create({type:c.types.SAVE_CANCEL,title:b[0],body:""},d).done(function(c){this.modal=c,this.setupFormModal(a,b[1])}.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()},{getInstance:function(a){new i(a)}}});

View File

@ -0,0 +1,144 @@
// 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/>.
/**
* Unified filter page JS module for the course participants page.
*
* @module tool_policy/acceptances_filter
* @package tool_policy
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/form-autocomplete', 'core/str', 'core/notification'],
function($, Autocomplete, Str, Notification) {
/**
* Selectors.
*
* @access private
* @type {{UNIFIED_FILTERS: string}}
*/
var SELECTORS = {
UNIFIED_FILTERS: '#unified-filters'
};
/**
* Init function.
*
* @method init
* @private
*/
var init = function() {
var stringkeys = [{
key: 'filterplaceholder',
component: 'tool_policy'
}, {
key: 'nofiltersapplied',
component: 'tool_policy'
}];
M.util.js_pending('acceptances_filter_datasource');
Str.get_strings(stringkeys).done(function(langstrings) {
var placeholder = langstrings[0];
var noSelectionString = langstrings[1];
Autocomplete.enhance(SELECTORS.UNIFIED_FILTERS, true, 'tool_policy/acceptances_filter_datasource', placeholder,
false, true, noSelectionString, true)
.then(function() {
M.util.js_complete('acceptances_filter_datasource');
return;
})
.fail(Notification.exception);
}).fail(Notification.exception);
var last = $(SELECTORS.UNIFIED_FILTERS).val();
$(SELECTORS.UNIFIED_FILTERS).on('change', function() {
var current = $(this).val();
var listoffilters = [];
var textfilters = [];
var updatedselectedfilters = false;
$.each(current, function(index, catoption) {
var catandoption = catoption.split(':', 2);
if (catandoption.length !== 2) {
textfilters.push(catoption);
return true; // Text search filter.
}
var category = catandoption[0];
var option = catandoption[1];
// The last option (eg. 'Teacher') out of a category (eg. 'Role') in this loop is the one that was last
// selected, so we want to use that if there are multiple options from the same category. Eg. The user
// may have chosen to filter by the 'Student' role, then wanted to filter by the 'Teacher' role - the
// last option in the category to be selected (in this case 'Teacher') will come last, so will overwrite
// 'Student' (after this if). We want to let the JS know that the filters have been updated.
if (typeof listoffilters[category] !== 'undefined') {
updatedselectedfilters = true;
}
listoffilters[category] = option;
return true;
});
// Check if we have something to remove from the list of filters.
if (updatedselectedfilters) {
// Go through and put the list into something we can use to update the list of filters.
var updatefilters = [];
for (var category in listoffilters) {
updatefilters.push(category + ":" + listoffilters[category]);
}
updatefilters = updatefilters.concat(textfilters);
$(this).val(updatefilters);
}
// Prevent form from submitting unnecessarily, eg. on blur when no filter is selected.
if (last.join(',') != current.join(',')) {
this.form.submit();
}
});
};
/**
* Return the unified user filter form.
*
* @method getForm
* @return {DOMElement}
*/
var getForm = function() {
return $(SELECTORS.UNIFIED_FILTERS).closest('form');
};
return /** @alias module:core/form-autocomplete */ {
/**
* Initialise the unified user filter.
*
* @method init
*/
init: function() {
init();
},
/**
* Return the unified user filter form.
*
* @method getForm
* @return {DOMElement}
*/
getForm: function() {
return getForm();
}
};
});

View File

@ -0,0 +1,93 @@
// 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/>.
/**
* Datasource for the tool_policy/acceptances_filter.
*
* This module is compatible with core/form-autocomplete.
*
* @package tool_policy
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {
return /** @alias module:tool_policy/acceptances_filter_datasource */ {
/**
* List filter options.
*
* @param {String} selector The select element selector.
* @param {String} query The query string.
* @return {Promise}
*/
list: function(selector, query) {
var filteredOptions = [];
var el = $(selector);
var originalOptions = $(selector).data('originaloptionsjson');
var selectedFilters = el.val();
$.each(originalOptions, function(index, option) {
// Skip option if it does not contain the query string.
if ($.trim(query) !== '' && option.label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) === -1) {
return true;
}
// Skip filters that have already been selected.
if ($.inArray(option.value, selectedFilters) > -1) {
return true;
}
filteredOptions.push(option);
return true;
});
var deferred = new $.Deferred();
deferred.resolve(filteredOptions);
return deferred.promise();
},
/**
* Process the results for auto complete elements.
*
* @param {String} selector The selector of the auto complete element.
* @param {Array} results An array or results.
* @return {Array} New array of results.
*/
processResults: function(selector, results) {
var options = [];
$.each(results, function(index, data) {
options.push({
value: data.value,
label: data.label
});
});
return options;
},
/**
* Source of data for Ajax element.
*
* @param {String} selector The selector of the auto complete element.
* @param {String} query The query string.
* @param {Function} callback A callback function receiving an array of results.
*/
/* eslint-disable promise/no-callback-in-promise */
transport: function(selector, query, callback) {
this.list(selector, query).then(callback).catch(Notification.exception);
}
};
});

View File

@ -0,0 +1,244 @@
// 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/>.
/**
* Add policy consent modal to the page
*
* @module tool_policy/acceptmodal
* @class AcceptOnBehalf
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/notification', 'core/fragment',
'core/ajax', 'core/yui'],
function($, Str, ModalFactory, ModalEvents, Notification, Fragment, Ajax, Y) {
"use strict";
/**
* Constructor
*
* @param {int} contextid
*
* Each call to init gets it's own instance of this class.
*/
var AcceptOnBehalf = function(contextid) {
this.contextid = contextid;
this.init();
};
/**
* @var {Modal} modal
* @private
*/
AcceptOnBehalf.prototype.modal = null;
/**
* @var {int} contextid
* @private
*/
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'
}
];
/**
* Initialise the class.
*
* @private
*/
AcceptOnBehalf.prototype.init = function() {
// Initialise for links accepting policies for individual users.
var triggers = $('a[data-action=acceptmodal]');
triggers.on('click', function(e) {
e.preventDefault();
var href = $(e.currentTarget).attr('href'),
formData = href.slice(href.indexOf('?') + 1);
this.showFormModal(formData);
}.bind(this));
// Initialise for multiple users acceptance form.
triggers = $('form[data-action=acceptmodal]');
triggers.on('submit', function(e) {
e.preventDefault();
if ($(e.currentTarget).find('input[type=checkbox][name="userids[]"]:checked').length) {
var formData = $(e.currentTarget).serialize();
this.showFormModal(formData, triggers);
} else {
Str.get_strings(this.stringKeys).done(function (strings) {
Notification.alert('', strings[2], strings[3]);
});
}
}.bind(this));
};
/**
* Show modal with a form
*
* @param {String} formData
* @param {object} triggerElement The trigger HTML jQuery object
*/
AcceptOnBehalf.prototype.showFormModal = function(formData, triggerElement) {
// Fetch the title string.
Str.get_strings(this.stringKeys).done(function(strings) {
// Create the modal.
ModalFactory.create({
type: ModalFactory.types.SAVE_CANCEL,
title: strings[0],
body: ''
}, triggerElement).done(function(modal) {
this.modal = modal;
this.setupFormModal(formData, strings[1]);
}.bind(this));
}.bind(this))
.fail(Notification.exception);
};
/**
* Setup form inside a modal
*
* @param {String} formData
* @param {String} saveText
*/
AcceptOnBehalf.prototype.setupFormModal = function(formData, saveText) {
var modal = this.modal;
modal.setLarge();
modal.setSaveButtonText(saveText);
// We want to reset the form every time it is opened.
modal.getRoot().on(ModalEvents.hidden, this.destroy.bind(this));
modal.setBody(this.getBody(formData));
// We catch the modal save event, and use it to submit the form inside the modal.
// Triggering a form submission will give JS validation scripts a chance to check for errors.
modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));
// We also catch the form submit event and use it to submit the form with ajax.
modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
modal.show();
};
/**
* Load the body of the modal (contains the form)
*
* @method getBody
* @private
* @param {String} formData
* @return {Promise}
*/
AcceptOnBehalf.prototype.getBody = function(formData) {
if (typeof formData === "undefined") {
formData = {};
}
// Get the content of the modal.
var params = {jsonformdata: JSON.stringify(formData)};
return Fragment.loadFragment('tool_policy', 'accept_on_behalf', this.contextid, params);
};
/**
* Submit the form inside the modal via AJAX request
*
* @method submitFormAjax
* @private
* @param {Event} e Form submission event.
*/
AcceptOnBehalf.prototype.submitFormAjax = function(e) {
// We don't want to do a real form submission.
e.preventDefault();
// Convert all the form elements values to a serialised string.
var formData = this.modal.getRoot().find('form').serialize();
var requests = Ajax.call([{
methodname: 'tool_policy_submit_accept_on_behalf',
args: {jsonformdata: JSON.stringify(formData)}
}]);
requests[0].done(function(data) {
if (data.validationerrors) {
this.modal.setBody(this.getBody(formData));
} else {
this.close();
}
}.bind(this)).fail(Notification.exception);
};
/**
* This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
*
* @method submitForm
* @param {Event} e Form submission event.
* @private
*/
AcceptOnBehalf.prototype.submitForm = function(e) {
e.preventDefault();
this.modal.getRoot().find('form').submit();
};
/**
* Close the modal
*/
AcceptOnBehalf.prototype.close = function() {
this.destroy();
document.location.reload();
};
/**
* Destroy the modal
*/
AcceptOnBehalf.prototype.destroy = function() {
Y.use('moodle-core-formchangechecker', function() {
M.core_formchangechecker.reset_form_dirty_state();
});
this.modal.destroy();
};
return /** @alias module:tool_policy/acceptmodal */ {
// Public variables and functions.
/**
* Attach event listeners to initialise this module.
*
* @method init
* @param {string} selector The CSS selector used to find nodes that will trigger this module.
* @param {int} contextid The contextid for the course.
* @return {Promise}
*/
getInstance: function(contextid) {
new AcceptOnBehalf(contextid);
}
};
});

View File

@ -0,0 +1,648 @@
<?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/>.
/**
* View user acceptances to the policies
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy;
use tool_policy\output\acceptances_filter;
use tool_policy\output\renderer;
use tool_policy\output\user_agreement;
use core_user;
use stdClass;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/lib/tablelib.php');
/**
* Class acceptances_table
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class acceptances_table extends \table_sql {
/** @var array */
protected $versionids;
/** @var acceptances_filter */
protected $acceptancesfilter;
/** @var renderer */
protected $output;
/**
* @var string[] The list of countries.
*/
protected $countries;
/** @var bool are there any users that this user can agree on behalf of */
protected $canagreeany = false;
/**
* Constructor.
*
* @param string $uniqueid Table identifier.
* @param acceptances_filter $acceptancesfilter
* @param renderer $output
*/
public function __construct($uniqueid, acceptances_filter $acceptancesfilter, renderer $output) {
global $CFG;
parent::__construct($uniqueid);
$this->set_attribute('id', 'acceptancetable');
$this->acceptancesfilter = $acceptancesfilter;
$this->is_downloading(optional_param('download', 0, PARAM_ALPHA), 'user_acceptances');
$this->baseurl = $acceptancesfilter->get_url();
$this->output = $output;
$this->versionids = [];
$versions = $acceptancesfilter->get_versions();
if (count($versions) > 1) {
foreach ($versions as $version) {
$this->versionids[$version->id] = $version->name;
}
} else {
$version = reset($versions);
$this->versionids[$version->id] = $version->name;
if ($version->status != policy_version::STATUS_ACTIVE) {
$this->versionids[$version->id] .= '<br>' . $version->revision;
}
}
$extrafields = get_extra_user_fields(\context_system::instance());
$userfields = \user_picture::fields('u', $extrafields);
$this->set_sql("$userfields",
"{user} u",
'u.id <> :siteguestid AND u.deleted = 0',
['siteguestid' => $CFG->siteguest]);
if (!$this->is_downloading()) {
$this->add_column_header('select', get_string('select'), false, 'colselect');
}
$this->add_column_header('fullname', get_string('fullnameuser', 'core'));
foreach ($extrafields as $field) {
$this->add_column_header($field, get_user_field_name($field));
}
if (!$this->is_downloading() && !has_capability('tool/policy:acceptbehalf', \context_system::instance())) {
// We will need to check capability to accept on behalf in each user's context, preload users contexts.
$this->sql->fields .= ',' . \context_helper::get_preload_record_columns_sql('ctx');
$this->sql->from .= ' JOIN {context} ctx ON ctx.contextlevel = :usercontextlevel AND ctx.instanceid = u.id';
$this->sql->params['usercontextlevel'] = CONTEXT_USER;
}
if ($this->acceptancesfilter->get_single_version()) {
$this->configure_for_single_version();
} else {
$this->configure_for_multiple_versions();
}
$this->build_sql_for_search_string($extrafields);
$this->build_sql_for_capability_filter();
$this->build_sql_for_roles_filter();
$this->sortable(true, 'firstname');
}
/**
* Remove randomness from the list by always sorting by user id in the end
*
* @return array
*/
public function get_sort_columns() {
$c = parent::get_sort_columns();
$c['u.id'] = SORT_ASC;
return $c;
}
/**
* Allows to add only one column name and header to the table (parent class methods only allow to set all).
*
* @param string $key
* @param string $label
* @param bool $sortable
* @param string $columnclass
*/
protected function add_column_header($key, $label, $sortable = true, $columnclass = '') {
if (empty($this->columns)) {
$this->define_columns([$key]);
$this->define_headers([$label]);
} else {
$this->columns[$key] = count($this->columns);
$this->column_style[$key] = array();
$this->column_class[$key] = $columnclass;
$this->column_suppress[$key] = false;
$this->headers[] = $label;
}
if ($columnclass !== null) {
$this->column_class($key, $columnclass);
}
if (!$sortable) {
$this->no_sorting($key);
}
}
/**
* Helper configuration method.
*/
protected function configure_for_single_version() {
$userfieldsmod = get_all_user_name_fields(true, 'm', null, 'mod');
$v = key($this->versionids);
$this->sql->fields .= ", $userfieldsmod, a{$v}.status AS status{$v}, a{$v}.note, ".
"a{$v}.timemodified, a{$v}.usermodified AS usermodified{$v}";
$join = "JOIN {tool_policy_acceptances} a{$v} ON a{$v}.userid = u.id AND a{$v}.policyversionid=:versionid{$v}";
$filterstatus = $this->acceptancesfilter->get_status_filter();
if ($filterstatus == 1) {
$this->sql->from .= " $join AND a{$v}.status=1";
} 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->params['versionid' . $v] = $v;
if ($filterstatus === 0) {
$this->sql->where .= " AND (a{$v}.status IS NULL OR a{$v}.status = 0)";
}
$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('note', get_string('acceptancenote', 'tool_policy'), false);
}
/**
* Helper configuration method.
*/
protected function configure_for_multiple_versions() {
$this->add_column_header('statusall', get_string('acceptancestatusoverall', 'tool_policy'));
$filterstatus = $this->acceptancesfilter->get_status_filter();
$statusall = [];
foreach ($this->versionids as $v => $versionname) {
$this->sql->fields .= ", a{$v}.status AS status{$v}, a{$v}.usermodified AS usermodified{$v}";
$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 {
$this->sql->from .= " LEFT {$join}";
}
$this->sql->params['versionid' . $v] = $v;
$this->add_column_header('status' . $v, $versionname, true, 'mdl-align');
$statusall[] = "COALESCE(a{$v}.status, 0)";
}
$this->sql->fields .= ",".join('+', $statusall)." AS statusall";
if ($filterstatus === 0) {
$statussql = [];
foreach ($this->versionids as $v => $versionname) {
$statussql[] = "a{$v}.status IS NULL OR a{$v}.status = 0";
}
$this->sql->where .= " AND (u.policyagreed = 0 OR ".join(" OR ", $statussql).")";
}
}
/**
* Download the data.
*/
public function download() {
\core\session\manager::write_close();
$this->out(0, false);
exit;
}
/**
* Get sql to add to where statement.
*
* @return string
*/
public function get_sql_where() {
list($where, $params) = parent::get_sql_where();
$where = preg_replace('/firstname/', 'u.firstname', $where);
$where = preg_replace('/lastname/', 'u.lastname', $where);
return [$where, $params];
}
/**
* Helper SQL query builder.
*
* @param array $userfields
*/
protected function build_sql_for_search_string($userfields) {
global $DB, $USER;
$search = $this->acceptancesfilter->get_search_strings();
if (empty($search)) {
return;
}
$wheres = [];
$params = [];
foreach ($search as $index => $keyword) {
$searchkey1 = 'search' . $index . '1';
$searchkey2 = 'search' . $index . '2';
$searchkey3 = 'search' . $index . '3';
$searchkey4 = 'search' . $index . '4';
$searchkey5 = 'search' . $index . '5';
$searchkey6 = 'search' . $index . '6';
$searchkey7 = 'search' . $index . '7';
$conditions = array();
// Search by fullname.
$fullname = $DB->sql_fullname('u.firstname', 'u.lastname');
$conditions[] = $DB->sql_like($fullname, ':' . $searchkey1, false, false);
// Search by email.
$email = $DB->sql_like('u.email', ':' . $searchkey2, false, false);
if (!in_array('email', $userfields)) {
$maildisplay = 'maildisplay' . $index;
$userid1 = 'userid' . $index . '1';
// Prevent users who hide their email address from being found by others
// who aren't allowed to see hidden email addresses.
$email = "(". $email ." AND (" .
"u.maildisplay <> :$maildisplay " .
"OR u.id = :$userid1". // User can always find himself.
"))";
$params[$maildisplay] = core_user::MAILDISPLAY_HIDE;
$params[$userid1] = $USER->id;
}
$conditions[] = $email;
// Search by idnumber.
$idnumber = $DB->sql_like('u.idnumber', ':' . $searchkey3, false, false);
if (!in_array('idnumber', $userfields)) {
$userid2 = 'userid' . $index . '2';
// Users who aren't allowed to see idnumbers should at most find themselves
// when searching for an idnumber.
$idnumber = "(". $idnumber . " AND u.id = :$userid2)";
$params[$userid2] = $USER->id;
}
$conditions[] = $idnumber;
// Search by middlename.
$middlename = $DB->sql_like('u.middlename', ':' . $searchkey4, false, false);
$conditions[] = $middlename;
// Search by alternatename.
$alternatename = $DB->sql_like('u.alternatename', ':' . $searchkey5, false, false);
$conditions[] = $alternatename;
// Search by firstnamephonetic.
$firstnamephonetic = $DB->sql_like('u.firstnamephonetic', ':' . $searchkey6, false, false);
$conditions[] = $firstnamephonetic;
// Search by lastnamephonetic.
$lastnamephonetic = $DB->sql_like('u.lastnamephonetic', ':' . $searchkey7, false, false);
$conditions[] = $lastnamephonetic;
$wheres[] = "(". implode(" OR ", $conditions) .") ";
$params[$searchkey1] = "%$keyword%";
$params[$searchkey2] = "%$keyword%";
$params[$searchkey3] = "%$keyword%";
$params[$searchkey4] = "%$keyword%";
$params[$searchkey5] = "%$keyword%";
$params[$searchkey6] = "%$keyword%";
$params[$searchkey7] = "%$keyword%";
}
$this->sql->where .= ' AND '.join(' AND ', $wheres);
$this->sql->params += $params;
}
/**
* If there is a filter to find users who can/cannot accept on their own behalf add it to the SQL query
*/
protected function build_sql_for_capability_filter() {
global $CFG;
$hascapability = $this->acceptancesfilter->get_capability_accept_filter();
if ($hascapability === null) {
return;
}
list($neededroles, $forbiddenroles) = get_roles_with_cap_in_context(\context_system::instance(), 'tool/policy:accept');
if (empty($neededroles)) {
// There are no roles that allow to accept agreement on one own's behalf.
$this->sql->where .= $hascapability ? ' AND 1=0' : '';
return;
}
if (empty($forbiddenroles)) {
// There are no roles that prohibit to accept agreement on one own's behalf.
$this->sql->where .= ' AND ' . $this->sql_has_role($neededroles, $hascapability);
return;
}
$defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
if (!empty($neededroles[$defaultuserroleid])) {
// Default role allows to accept agreement. Make sure user has/does not have one of the roles prohibiting it.
$this->sql->where .= ' AND ' . $this->sql_has_role($forbiddenroles, !$hascapability);
return;
}
if ($hascapability) {
// User has at least one role allowing to accept and no roles prohibiting.
$this->sql->where .= ' AND ' . $this->sql_has_role($neededroles);
$this->sql->where .= ' AND ' . $this->sql_has_role($forbiddenroles, false);
} else {
// Option 1: User has one of the roles prohibiting to accept.
$this->sql->where .= ' AND (' . $this->sql_has_role($forbiddenroles);
// Option 2: User has none of the roles allowing to accept.
$this->sql->where .= ' OR ' . $this->sql_has_role($neededroles, false) . ")";
}
}
/**
* Returns SQL snippet for users that have (do not have) one of the given roles in the system context
*
* @param array $roles list of roles indexed by role id
* @param bool $positive true: return users who HAVE roles; false: return users who DO NOT HAVE roles
* @return string
*/
protected function sql_has_role($roles, $positive = true) {
global $CFG;
if (empty($roles)) {
return $positive ? '1=0' : '1=1';
}
$defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
if (!empty($roles[$defaultuserroleid])) {
// No need to query, everybody has the default role.
return $positive ? '1=1' : '1=0';
}
return "u.id " . ($positive ? "" : "NOT") . " IN (
SELECT userid
FROM {role_assignments}
WHERE contextid = " . SYSCONTEXTID . " AND roleid IN (" . implode(',', array_keys($roles)) . ")
)";
}
/**
* If there is a filter by user roles add it to the SQL query.
*/
protected function build_sql_for_roles_filter() {
foreach ($this->acceptancesfilter->get_role_filters() as $roleid) {
$this->sql->where .= ' AND ' . $this->sql_has_role([$roleid => $roleid]);
}
}
/**
* Hook that can be overridden in child classes to wrap a table in a form
* for example. Called only when there is data to display and not
* downloading.
*/
public function wrap_html_start() {
echo \html_writer::start_tag('form',
['action' => new \moodle_url('/admin/tool/policy/accept.php'), 'data-action' => 'acceptmodal']);
echo \html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()]);
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}]",
'value' => $versionid]);
}
}
/**
* Hook that can be overridden in child classes to wrap a table in a form
* for example. Called only when there is data to display and not
* downloading.
*/
public function wrap_html_finish() {
global $PAGE;
if ($this->canagreeany) {
echo \html_writer::empty_tag('input', ['type' => 'submit',
'value' => get_string('consentbulk', 'tool_policy'), 'class' => 'btn btn-primary']);
$PAGE->requires->js_call_amd('tool_policy/acceptmodal', 'getInstance', [\context_system::instance()->id]);
}
echo "</form>\n";
}
/**
* Render the table.
*/
public function display() {
$this->out(100, true);
}
/**
* Call appropriate methods on this table class to perform any processing on values before displaying in table.
* Takes raw data from the database and process it into human readable format, perhaps also adding html linking when
* displaying table as html, adding a div wrap, etc.
*
* See for example col_fullname below which will be called for a column whose name is 'fullname'.
*
* @param array|object $row row of data from db used to make one row of the table.
* @return array one row for the table, added using add_data_keyed method.
*/
public function format_row($row) {
\context_helper::preload_from_record($row);
$row->canaccept = false;
$row->user = \user_picture::unalias($row, [], $this->useridfield);
$row->select = null;
if (!$this->is_downloading()) {
if (has_capability('tool/policy:acceptbehalf', \context_system::instance()) ||
has_capability('tool/policy:acceptbehalf', \context_user::instance($row->id))) {
$row->canaccept = true;
$row->select = \html_writer::empty_tag('input',
['type' => 'checkbox', 'name' => 'userids[]', 'value' => $row->id, 'class' => 'usercheckbox',
'id' => 'selectuser' . $row->id]) .
\html_writer::tag('label', get_string('selectuser', 'tool_policy', $this->username($row->user, false)),
['for' => 'selectuser' . $row->id, 'class' => 'accesshide']);
$this->canagreeany = true;
}
}
return parent::format_row($row);
}
/**
* Get the column fullname value.
*
* @param stdClass $row
* @return string
*/
public function col_fullname($row) {
global $OUTPUT;
$userpic = $this->is_downloading() ? '' : $OUTPUT->user_picture($row->user);
return $userpic . $this->username($row->user, true);
}
/**
* User name with a link to profile
*
* @param stdClass $user
* @param bool $profilelink show link to profile (when we are downloading never show links)
* @return string
*/
protected function username($user, $profilelink = true) {
$canviewfullnames = has_capability('moodle/site:viewfullnames', \context_system::instance()) ||
has_capability('moodle/site:viewfullnames', \context_user::instance($user->id));
$name = fullname($user, $canviewfullnames);
if (!$this->is_downloading() && $profilelink) {
$profileurl = new \moodle_url('/user/profile.php', array('id' => $user->id));
return \html_writer::link($profileurl, $name);
}
return $name;
}
/**
* Helper.
*/
protected function get_return_url() {
$pageurl = $this->baseurl;
if ($this->currpage) {
$pageurl = new \moodle_url($pageurl, [$this->request[TABLE_VAR_PAGE] => $this->currpage]);
}
return $pageurl;
}
/**
* Return agreement status
*
* @param int $versionid either id of an individual version or empty for overall status
* @param stdClass $row
* @return string
*/
protected function status($versionid, $row) {
$onbehalf = false;
$versions = $versionid ? [$versionid => $this->versionids[$versionid]] : $this->versionids; // List of versions.
$accepted = []; // List of versionids that user has accepted.
foreach ($versions as $v => $name) {
if (!empty($row->{'status' . $v})) {
$accepted[] = $v;
$agreedby = $row->{'usermodified' . $v};
if ($agreedby && $agreedby != $row->id) {
$onbehalf = true;
}
}
}
if ($versionid) {
$str = new \lang_string($accepted ? 'yes' : 'no');
} else {
$str = new \lang_string('acceptancecount', 'tool_policy', (object)[
'agreedcount' => count($accepted),
'policiescount' => count($versions)
]);
}
if ($this->is_downloading()) {
return $str->out();
} 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;
}
}
/**
* Get the column timemodified value.
*
* @param stdClass $row
* @return string
*/
public function col_timemodified($row) {
if ($row->timemodified) {
if ($this->is_downloading()) {
// Use timestamp format readable for both machines and humans.
return date_format_string($row->timemodified, '%Y-%m-%d %H:%M:%S %Z');
} else {
// Use localised calendar format.
return userdate($row->timemodified, get_string('strftimedatetime'));
}
} else {
return null;
}
}
/**
* Get the column note value.
*
* @param stdClass $row
* @return string
*/
public function col_note($row) {
if ($this->is_downloading()) {
return $row->note;
} else {
return format_text($row->note, FORMAT_MOODLE);
}
}
/**
* Get the column statusall value.
*
* @param stdClass $row
* @return string
*/
public function col_statusall($row) {
return $this->status(0, $row);
}
/**
* Generate the country column.
*
* @param \stdClass $data
* @return string
*/
public function col_country($data) {
if ($data->country && $this->countries === null) {
$this->countries = get_string_manager()->get_list_of_countries();
}
if (!empty($this->countries[$data->country])) {
return $this->countries[$data->country];
}
return '';
}
/**
* You can override this method in a child class. See the description of
* build_table which calls this method.
*
* @param string $column
* @param stdClass $row
* @return string
*/
public function other_cols($column, $row) {
if (preg_match('/^status([\d]+)$/', $column, $matches)) {
$versionid = $matches[1];
return $this->status($versionid, $row);
}
if (preg_match('/^usermodified([\d]+)$/', $column, $matches)) {
if ($row->$column && $row->$column != $row->id) {
$user = (object)['id' => $row->$column];
username_load_fields_from_object($user, $row, 'mod');
return $this->username($user, true);
}
return ''; // User agreed by themselves.
}
return null;
}
}

View File

@ -0,0 +1,117 @@
<?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 {@link tool_policy\event\acceptance_base} class.
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\event;
use core\event\base;
defined('MOODLE_INTERNAL') || die();
/**
* Base class for acceptance_created and acceptance_updated events.
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class acceptance_base extends base {
/**
* Initialise the event.
*/
protected function init() {
$this->data['objecttable'] = 'tool_policy_acceptances';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Create event from record.
*
* @param stdClass $record
* @return acceptance_created
*/
public static function create_from_record($record) {
$event = static::create([
'objectid' => $record->id,
'relateduserid' => $record->userid,
'context' => \context_user::instance($record->userid),
'other' => [
'policyversionid' => $record->policyversionid,
'note' => $record->note,
'status' => $record->status,
],
]);
$event->add_record_snapshot($event->objecttable, $record);
return $event;
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/admin/tool/policy/acceptance.php', array('userid' => $this->relateduserid,
'versionid' => $this->other['policyversionid']));
}
/**
* Get the object ID mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return array('db' => 'tool_policy', 'restore' => \core\event\base::NOT_MAPPED);
}
/**
* Custom validation.
*
* @throws \coding_exception
*/
protected function validate_data() {
parent::validate_data();
if (empty($this->other['policyversionid'])) {
throw new \coding_exception('The \'policyversionid\' value must be set');
}
if (!isset($this->other['status'])) {
throw new \coding_exception('The \'status\' value must be set');
}
if (empty($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
}
/**
* No mapping required for this event because this event is not backed up.
*
* @return bool
*/
public static function get_other_mapping() {
return false;
}
}

View File

@ -0,0 +1,73 @@
<?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 {@link tool_policy\event\acceptance_created} class.
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\event;
use core\event\base;
defined('MOODLE_INTERNAL') || die();
/**
* Event acceptance_created
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class acceptance_created extends acceptance_base {
/**
* Initialise the event.
*/
protected function init() {
parent::init();
$this->data['crud'] = 'c';
}
/**
* Returns event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_acceptance_created', 'tool_policy');
}
/**
* Get the event description.
*
* @return string
*/
public function get_description() {
if ($this->other['status'] == 1) {
$action = 'added consent to';
} else if ($this->other['status'] == -1) {
$action = 'revoked consent to';
} else {
$action = 'created an empty consent record for';
}
return "The user with id '{$this->userid}' $action the policy with revision {$this->other['policyversionid']} ".
"for the user with id '{$this->relateduserid}'";
}
}

View File

@ -0,0 +1,73 @@
<?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 {@link tool_policy\event\acceptance_updated} class.
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\event;
use core\event\base;
defined('MOODLE_INTERNAL') || die();
/**
* Event acceptance_updated
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class acceptance_updated extends acceptance_base {
/**
* Initialise the event.
*/
protected function init() {
parent::init();
$this->data['crud'] = 'u';
}
/**
* Returns event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_acceptance_updated', 'tool_policy');
}
/**
* Get the event description.
*
* @return string
*/
public function get_description() {
if ($this->other['status'] == 1) {
$action = 'added consent to';
} else if ($this->other['status'] == -1) {
$action = 'revoked consent to';
} else {
$action = 'updated consent to';
}
return "The user with id '{$this->userid}' $action the policy with revision {$this->other['policyversionid']} ".
"for the user with id '{$this->relateduserid}'";
}
}

View File

@ -0,0 +1,161 @@
<?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 {@link tool_policy\form\accept_policy} class.
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\form;
use tool_policy\api;
use tool_policy\policy_version;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* Represents the form for accepting a policy.
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class accept_policy extends \moodleform {
/**
* Defines the form fields.
*/
public function definition() {
global $PAGE;
$mform = $this->_form;
if (empty($this->_customdata['userids']) || !is_array($this->_customdata['userids'])) {
throw new \moodle_exception('missingparam', 'error', '', 'userids');
}
if (empty($this->_customdata['versionids']) || !is_array($this->_customdata['versionids'])) {
throw new \moodle_exception('missingparam', '', '', 'versionids');
}
$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);
$versionnames = $this->validate_and_get_versions($versionids);
foreach ($usernames as $userid => $name) {
$mform->addElement('hidden', 'userids['.$userid.']', $userid);
$mform->setType('userids['.$userid.']', PARAM_INT);
}
foreach ($versionnames as $versionid => $name) {
$mform->addElement('hidden', 'versionids['.$versionid.']', $versionid);
$mform->setType('versionids['.$versionid.']', PARAM_INT);
}
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_LOCALURL);
$mform->addElement('static', 'user', get_string('acceptanceusers', 'tool_policy'), join(', ', $usernames));
$mform->addElement('static', 'policy', get_string('acceptancepolicies', 'tool_policy'),
join(', ', $versionnames));
$mform->addElement('static', 'ack', '', get_string('acceptanceacknowledgement', 'tool_policy'));
$mform->addElement('textarea', 'note', get_string('acceptancenote', 'tool_policy'));
$mform->setType('note', PARAM_NOTAGS);
if (!empty($this->_customdata['showbuttons'])) {
$this->add_action_buttons(true, get_string('iagreetothepolicy', 'tool_policy'));
}
$PAGE->requires->js_call_amd('tool_policy/policyactions', 'init');
}
/**
* Validate userids and return usernames
*
* @param array $userids
* @return array (userid=>username)
*/
protected function validate_and_get_users($userids) {
global $DB, $USER;
$usernames = [];
list($sql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params['usercontextlevel'] = CONTEXT_USER;
$users = $DB->get_records_sql("SELECT u.id, " . get_all_user_name_fields(true, 'u') . ", " .
\context_helper::get_preload_record_columns_sql('ctx') .
" FROM {user} u JOIN {context} ctx ON ctx.contextlevel=:usercontextlevel AND ctx.instanceid = u.id
WHERE u.id " . $sql, $params);
$acceptany = has_capability('tool/policy:acceptbehalf', \context_system::instance());
foreach ($userids as $userid) {
if (!isset($users[$userid])) {
throw new \dml_missing_record_exception('user', 'id=?', [$userid]);
}
$user = $users[$userid];
if (isguestuser($user)) {
throw new \moodle_exception('noguest');
}
if ($userid == $USER->id) {
require_capability('tool/policy:accept', \context_system::instance());
} else if (!$acceptany) {
\context_helper::preload_from_record($user);
require_capability('tool/policy:acceptbehalf', \context_user::instance($userid));
}
$usernames[$userid] = fullname($user);
}
return $usernames;
}
/**
* Validate versionids and return their names
*
* @param array $versionids
* @return array (versionid=>name)
*/
protected function validate_and_get_versions($versionids) {
$versionnames = [];
$policies = api::list_policies();
foreach ($versionids as $versionid) {
$version = api::get_policy_version($versionid, $policies);
if ($version->audience == policy_version::AUDIENCE_GUESTS) {
throw new \moodle_exception('errorpolicyversionnotfound', 'tool_policy');
}
$url = new \moodle_url('/admin/tool/policy/view.php', ['versionid' => $version->id]);
$policyname = $version->name;
if ($version->status != policy_version::STATUS_ACTIVE) {
$policyname .= ' ' . $version->revision;
}
$versionnames[$version->id] = \html_writer::link($url, $policyname,
['data-action' => 'view', 'data-versionid' => $version->id]);
}
return $versionnames;
}
/**
* Process form submission
*/
public function process() {
if ($data = $this->get_data()) {
foreach ($data->userids as $userid) {
\tool_policy\api::accept_policies($data->versionids, $userid, $data->note);
}
}
}
}

View File

@ -0,0 +1,132 @@
<?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 {@link tool_policy\output\acceptances} class.
*
* @package tool_policy
* @category output
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\output;
use tool_policy\api;
defined('MOODLE_INTERNAL') || die();
use moodle_url;
use renderable;
use renderer_base;
use single_button;
use templatable;
use tool_policy\policy_version;
/**
* List of users and their acceptances
*
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class acceptances implements renderable, templatable {
/** @var id */
protected $userid;
/** @var moodle_url */
protected $returnurl;
/**
* Contructor.
*
* @param int $userid
*/
public function __construct($userid, $returnurl = null) {
$this->userid = $userid;
$this->returnurl = $returnurl ? (new moodle_url($returnurl))->out(false) : null;
}
/**
* Export the page data for the mustache template.
*
* @param renderer_base $output renderer to be used to render the page elements.
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
$data = (object)[];
$data->hasonbehalfagreements = false;
$data->pluginbaseurl = (new moodle_url('/admin/tool/policy'))->out(false);
$data->returnurl = $this->returnurl;
// 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);
$canviewfullnames = has_capability('moodle/site:viewfullnames', \context_system::instance());
foreach ($policies as $policy) {
foreach ($policy->versions as $version) {
unset($version->summary);
unset($version->content);
$version->iscurrent = ($version->status == policy_version::STATUS_ACTIVE);
$version->name = $version->name;
$version->revision = $version->revision;
$returnurl = new moodle_url('/admin/tool/policy/user.php', ['userid' => $this->userid]);
$version->viewurl = (new moodle_url('/admin/tool/policy/view.php', [
'policyid' => $policy->id,
'versionid' => $version->id,
'returnurl' => $returnurl->out(false),
]))->out(false);
if (!empty($version->acceptance->status)) {
$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 ($onbehalf) {
$usermodified = (object)['id' => $acceptance->usermodified];
username_load_fields_from_object($usermodified, $acceptance, 'mod');
$profileurl = new \moodle_url('/user/profile.php', array('id' => $usermodified->id));
$version->acceptedby = \html_writer::link($profileurl, fullname($usermodified, $canviewfullnames ||
has_capability('moodle/site:viewfullnames', \context_user::instance($acceptance->usermodified))));
$data->hasonbehalfagreements = true;
}
$version->note = format_text($acceptance->note);
} else if ($version->iscurrent) {
$version->agreement = new user_agreement($this->userid, [], $returnurl, [$version->id => $version->name]);
}
if (isset($version->agreement)) {
$version->agreement = $version->agreement->export_for_template($output);
}
}
if ($policy->versions[0]->status != policy_version::STATUS_ACTIVE) {
// Add an empty "currentversion" on top.
$policy->versions = [0 => (object)[]] + $policy->versions;
}
$policy->versioncount = count($policy->versions);
$policy->versions = array_values($policy->versions);
$policy->versions[0]->isfirst = 1;
$policy->versions[0]->hasarchived = (count($policy->versions) > 1);
}
$data->policies = array_values($policies);
return $data;
}
}

View File

@ -0,0 +1,465 @@
<?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 {@link tool_policy\output\acceptances_filter} class.
*
* @package tool_policy
* @category output
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\output;
use tool_policy\api;
use tool_policy\policy_version;
defined('MOODLE_INTERNAL') || die();
/**
* Implements the widget allowing to filter the acceptance records.
*
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class acceptances_filter implements \templatable, \renderable {
/** @var array $filtersapplied The list of selected filter options. */
protected $filtersapplied;
/** @var string $searchstring */
protected $searchstrings;
/** @var array list of available versions */
protected $versions = null;
/** @var array list of available roles for the filter */
protected $roles;
/** @var array cached list of all available policies, to retrieve use {@link self::get_avaliable_policies()} */
protected $policies;
/** @var int */
const FILTER_SEARCH_STRING = 0;
/** @var int */
const FILTER_POLICYID = 1;
/** @var int */
const FILTER_VERSIONID = 2;
/** @var int */
const FILTER_CAPABILITY_ACCEPT = 3;
/** @var int */
const FILTER_STATUS = 4;
/** @var int */
const FILTER_ROLE = 5;
/**
* Constructor.
*
* @param array $policyid Specified policy id
* @param array $versionid Specified version id
* @param array $filtersapplied The list of selected filter option values.
*/
public function __construct($policyid, $versionid, $filtersapplied) {
$this->filtersapplied = [];
$this->roles = get_assignable_roles(\context_system::instance());
if ($policyid) {
$this->add_filter(self::FILTER_POLICYID, $policyid);
}
if ($versionid) {
$this->add_filter(self::FILTER_VERSIONID, $versionid);
}
foreach ($filtersapplied as $filter) {
if (preg_match('/^([1-9]\d*):(\d+)$/', $filter, $parts)) {
// This is a pre-set filter (policy, version, status, etc.).
$allowmultiple = false;
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];
break;
case self::FILTER_ROLE:
$value = (int)$parts[2];
if (!array_key_exists($value, $this->roles)) {
continue 2;
}
$allowmultiple = true;
break;
default:
// Unrecognised filter.
continue 2;
}
$this->add_filter((int)$parts[1], $value, $allowmultiple);
} else if (trim($filter) !== '') {
// This is a search string.
$this->add_filter(self::FILTER_SEARCH_STRING, trim($filter), true);
}
}
}
/**
* Adds an applied filter
*
* @param mixed $key
* @param mixed $value
* @param bool $allowmultiple
*/
protected function add_filter($key, $value, $allowmultiple = false) {
if ($allowmultiple || empty($this->get_filter_values($key))) {
$this->filtersapplied[] = [$key, $value];
}
}
/**
* Is there a filter by policy
*
* @return null|int null if there is no filter, otherwise the policy id
*/
public function get_policy_id_filter() {
return $this->get_filter_value(self::FILTER_POLICYID);
}
/**
* Is there a filter by version
*
* @return null|int null if there is no filter, otherwise the version id
*/
public function get_version_id_filter() {
return $this->get_filter_value(self::FILTER_VERSIONID);
}
/**
* Are there filters by search strings
*
* @return string[] array of string filters
*/
public function get_search_strings() {
return $this->get_filter_values(self::FILTER_SEARCH_STRING);
}
/**
* Is there a filter by status (agreed/not agreed).
*
* @return null|0|1 null if there is no filter, 0/1 if there is a filter by status
*/
public function get_status_filter() {
return $this->get_filter_value(self::FILTER_STATUS);
}
/**
* Are there filters by role
*
* @return array list of role ids
*/
public function get_role_filters() {
return $this->get_filter_values(self::FILTER_ROLE);
}
/**
* Is there a filter by capability (can accept/cannot accept).
*
* @return null|0|1 null if there is no filter, 0/1 if there is a filter by capability
*/
public function get_capability_accept_filter() {
return $this->get_filter_value(self::FILTER_CAPABILITY_ACCEPT);
}
/**
* Get all values of the applied filter
*
* @param string $filtername
* @return array
*/
protected function get_filter_values($filtername) {
$values = [];
foreach ($this->filtersapplied as $filter) {
if ($filter[0] == $filtername) {
$values[] = $filter[1];
}
}
return $values;
}
/**
* Get one value of the applied filter
*
* @param string $filtername
* @param string $default
* @return mixed
*/
protected function get_filter_value($filtername, $default = null) {
if ($values = $this->get_filter_values($filtername)) {
$value = reset($values);
return $value;
}
return $default;
}
/**
* Returns all policies that have versions with possible acceptances (excl. drafts and guest-only versions)
*
* @return array|null
*/
public function get_avaliable_policies() {
if ($this->policies === null) {
$this->policies = [];
foreach (\tool_policy\api::list_policies() as $policy) {
// Make a list of all versions that are not draft and are not guest-only.
$policy->versions = [];
if ($policy->currentversion && $policy->currentversion->audience != policy_version::AUDIENCE_GUESTS) {
$policy->versions[$policy->currentversion->id] = $policy->currentversion;
} else {
$policy->currentversion = null;
}
foreach ($policy->archivedversions as $version) {
if ($version->audience != policy_version::AUDIENCE_GUESTS) {
$policy->versions[$version->id] = $version;
}
}
if ($policy->versions) {
$this->policies[$policy->id] = $policy;
}
}
}
return $this->policies;
}
/**
* List of policies that match current filters
*
* @return array of versions to display indexed by versionid
*/
public function get_versions() {
if ($this->versions === null) {
$policyid = $this->get_policy_id_filter();
$versionid = $this->get_version_id_filter();
$this->versions = [];
foreach ($this->get_avaliable_policies() as $policy) {
if ($policyid && $policy->id != $policyid) {
continue;
}
if ($versionid) {
if (array_key_exists($versionid, $policy->versions)) {
$this->versions[$versionid] = $policy->versions[$versionid];
break; // No need to keep searching.
}
} else if ($policy->currentversion) {
$this->versions[$policy->currentversion->id] = $policy->currentversion;
}
}
}
return $this->versions;
}
/**
* Validates if policyid and versionid are valid (if specified)
*/
public function validate_ids() {
$policyid = $this->get_policy_id_filter();
$versionid = $this->get_version_id_filter();
if ($policyid || $versionid) {
$found = array_filter($this->get_avaliable_policies(), function($policy) use ($policyid, $versionid) {
return (!$policyid || $policy->id == $policyid) &&
(!$versionid || array_key_exists($versionid, $policy->versions));
});
if (!$found) {
// Throw exception that policy/version is not found.
throw new \moodle_exception('errorpolicyversionnotfound', 'tool_policy');
}
}
}
/**
* If policyid or versionid is specified return one single policy that needs to be shown
*
* If neither policyid nor versionid is specified this method returns null.
*
* When versionid is specified this method will always return an object (this is validated in {@link self::validate_ids()}
* When only policyid is specified this method either returns the current version of the policy or null if there is
* no current version (for example, it is an old policy).
*
* @return mixed|null
*/
public function get_single_version() {
if ($this->get_version_id_filter() || $this->get_policy_id_filter()) {
$versions = $this->get_versions();
return reset($versions);
}
return null;
}
/**
* Returns URL of the acceptances page with all current filters applied
*
* @return \moodle_url
*/
public function get_url() {
$urlparams = [];
if ($policyid = $this->get_policy_id_filter()) {
$urlparams['policyid'] = $policyid;
}
if ($versionid = $this->get_version_id_filter()) {
$urlparams['versionid'] = $versionid;
}
$i = 0;
foreach ($this->filtersapplied as $filter) {
if ($filter[0] != self::FILTER_POLICYID && $filter[0] != self::FILTER_VERSIONID) {
if ($filter[0] == self::FILTER_SEARCH_STRING) {
$urlparams['unified-filters['.($i++).']'] = $filter[1];
} else {
$urlparams['unified-filters['.($i++).']'] = join(':', $filter);
}
}
}
return new \moodle_url('/admin/tool/policy/acceptances.php', $urlparams);
}
/**
* Creates an option name for the smart select for the version
*
* @param \stdClass $version
* @return string
*/
protected function get_version_option_for_filter($version) {
if ($version->status == policy_version::STATUS_ACTIVE) {
$a = (object)[
'name' => format_string($version->revision),
'status' => get_string('status'.policy_version::STATUS_ACTIVE, 'tool_policy'),
];
return get_string('filterrevisionstatus', 'tool_policy', $a);
} else {
return get_string('filterrevision', 'tool_policy', $version->revision);
}
}
/**
* Build list of filters available for this page
*
* @return array [$availablefilters, $selectedoptions]
*/
protected function build_available_filters() {
$selectedoptions = [];
$availablefilters = [];
$versionid = $this->get_version_id_filter();
$policyid = $versionid ? $this->get_single_version()->policyid : $this->get_policy_id_filter();
// Policies.
$policies = $this->get_avaliable_policies();
if ($policyid) {
// If policy is selected, display only the current policy in the selector.
$selectedoptions[] = $key = self::FILTER_POLICYID . ':' . $policyid;
$version = $versionid ? $policies[$policyid]->versions[$versionid] : reset($policies[$policyid]->versions);
$availablefilters[$key] = get_string('filterpolicy', 'tool_policy', $version->name);
} else {
// If no policy/version is selected display the list of all policies.
foreach ($policies as $policy) {
$firstversion = reset($policy->versions);
$key = self::FILTER_POLICYID . ':' . $policy->id;
$availablefilters[$key] = get_string('filterpolicy', 'tool_policy', $firstversion->name);
}
}
// Versions.
if ($versionid) {
$singleversion = $this->get_single_version();
$selectedoptions[] = $key = self::FILTER_VERSIONID . ':' . $singleversion->id;
$availablefilters[$key] = $this->get_version_option_for_filter($singleversion);
} else if ($policyid) {
foreach ($policies[$policyid]->versions as $version) {
$key = self::FILTER_VERSIONID . ':' . $version->id;
$availablefilters[$key] = $this->get_version_option_for_filter($version);
}
}
// Permissions.
$permissions = [
self::FILTER_CAPABILITY_ACCEPT . ':1' => get_string('filtercapabilityyes', 'tool_policy'),
self::FILTER_CAPABILITY_ACCEPT . ':0' => get_string('filtercapabilityno', 'tool_policy'),
];
if (($currentpermission = $this->get_capability_accept_filter()) !== null) {
$selectedoptions[] = $key = self::FILTER_CAPABILITY_ACCEPT . ':' . $currentpermission;
$permissions = array_intersect_key($permissions, [$key => true]);
}
$availablefilters += $permissions;
// Status.
$statuses = [
self::FILTER_STATUS.':1' => get_string('filterstatusyes', 'tool_policy'),
self::FILTER_STATUS.':0' => get_string('filterstatusno', 'tool_policy'),
];
if (($currentstatus = $this->get_status_filter()) !== null) {
$selectedoptions[] = $key = self::FILTER_STATUS . ':' . $currentstatus;
$statuses = array_intersect_key($statuses, [$key => true]);
}
$availablefilters += $statuses;
// Roles.
$currentroles = $this->get_role_filters();
foreach ($this->roles as $roleid => $rolename) {
$key = self::FILTER_ROLE . ':' . $roleid;
$availablefilters[$key] = get_string('filterrole', 'tool_policy', $rolename);
if (in_array($roleid, $currentroles)) {
$selectedoptions[] = $key;
}
}
// Search string.
foreach ($this->get_search_strings() as $str) {
$selectedoptions[] = $str;
$availablefilters[$str] = $str;
}
return [$availablefilters, $selectedoptions];
}
/**
* Function to export the renderer data in a format that is suitable for a mustache template.
*
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
* @return \stdClass|array
*/
public function export_for_template(\renderer_base $output) {
$data = new \stdClass();
$data->action = (new \moodle_url('/admin/tool/policy/acceptances.php'))->out(false);
$data->filteroptions = [];
$originalfilteroptions = [];
list($avilablefilters, $selectedoptions) = $this->build_available_filters();
foreach ($avilablefilters as $value => $label) {
$selected = in_array($value, $selectedoptions);
$filteroption = (object)[
'value' => $value,
'label' => $label
];
$originalfilteroptions[] = $filteroption;
$filteroption->selected = $selected;
$data->filteroptions[] = $filteroption;
}
$data->originaloptionsjson = json_encode($originalfilteroptions);
return $data;
}
}

View File

@ -0,0 +1,114 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides {@link tool_policy\output\user_agreement} class.
*
* @package tool_policy
* @category output
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\output;
defined('MOODLE_INTERNAL') || die();
use moodle_url;
use renderable;
use renderer_base;
use single_button;
use templatable;
/**
* List of users and their acceptances
*
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_agreement implements \templatable, \renderable {
/** @var int */
protected $userid;
/** @var bool */
protected $onbehalf;
/** @var moodle_url */
protected $pageurl;
/** @var array */
protected $versions;
/** @var array */
protected $accepted;
/** @var bool */
protected $canaccept;
/**
* user_agreement constructor
*
* @param int $userid
* @param array $accepted list of ids of accepted 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
*/
public function __construct($userid, $accepted, moodle_url $pageurl, $versions, $onbehalf = false, $canaccept = null) {
$this->userid = $userid;
$this->onbehalf = $onbehalf;
$this->pageurl = $pageurl;
$this->versions = $versions;
$this->accepted = $accepted;
$this->canaccept = $canaccept;
if (count($this->accepted) < count($this->versions) && $canaccept === null) {
$this->canaccept = (has_capability('tool/policy:acceptbehalf', \context_system::instance()) ||
has_capability('tool/policy:acceptbehalf', \context_user::instance($this->userid)));
}
}
/**
* Export data to be rendered.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(\renderer_base $output) {
$data = [
'status' => count($this->accepted) == count($this->versions),
'onbehalf' => $this->onbehalf,
'canaccept' => $this->canaccept,
];
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;
}
$linkparams['returnurl'] = $this->pageurl->out_as_local_url(false);
$link = new \moodle_url('/admin/tool/policy/accept.php', $linkparams);
$data['acceptlink'] = $link->out(false);
$data['acceptmodaldata'] = $link->get_query_string(false); // TODO not needed?
}
$data['singleversion'] = count($this->versions) == 1;
if ($data['singleversion']) {
$firstversion = reset($this->versions);
$data['versionname'] = $firstversion;
}
return $data;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

View File

@ -0,0 +1,3 @@
<!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="-1.6 -0.5 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M12.8 2.7L10.1 0S8.5 1.5 6.4 4C4.3 1.5 2.7 0 2.7 0L0 2.7S1.9 4 4.6 6.4C3 8.7 1.3 11.6 0 14.9c2.2-2.7 4.4-5 6.4-6.9 2 1.9 4.2 4.2 6.4 6.9-1.3-3.3-3-6.2-4.6-8.6 2.7-2.3 4.6-3.6 4.6-3.6z" fill="#FF403C"/></svg>

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

View File

@ -0,0 +1,3 @@
<!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="#9C3"/></svg>

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

View File

@ -0,0 +1,3 @@
<!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>

After

Width:  |  Height:  |  Size: 448 B

View File

@ -0,0 +1,149 @@
{{!
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/>.
}}
{{!
@template tool_policy/acceptances
Template for the user acceptances page.
Classes required for JS:
-
Data attributes required for JS:
-
Context variables required for this template:
* policies
Example context (json):
{
"hasonbehalfagreements": true,
"policies": [
{
"versions": [
{
"isfirst": true,
"policyid": 1,
"viewurl": "/",
"name": "Terms &amp; conditions",
"revision": "2.0",
"hasarchived": true,
"timeaccepted": "1 Mar 2018",
"agreement": {
"onbehalf": false,
"status": false,
"canaccept": true,
"acceptlink": "#"
}
},
{
"isfirst": false,
"policyid": 1,
"viewurl": "/",
"name": "Terms &amp; conditions",
"revision": "1.0-beta",
"acceptedby": "<a href=\"#\">Mary Smith</a>",
"note": "Based on parent's agreement via email",
"hasarchived": false,
"timeaccepted": "15 Feb 2018",
"agreement": {
"onbehalf": true,
"status": true,
"canaccept": false
}
}
]
}
]
}
}}
<table class="generaltable fullwidth">
<thead>
<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>
{{#hasonbehalfagreements}}
<th>{{#str}} agreedby, tool_policy {{/str}}</th>
<th>{{#str}} acceptancenote, tool_policy {{/str}}</th>
{{/hasonbehalfagreements}}
<th></th>
</tr>
</thead>
<tbody>
{{#policies}}
{{#versions}}
<tr {{^isfirst}}class="archived{{policyid}}" style="display:none"{{/isfirst}}>
<td>
{{^isfirst}}
<div style="float:left">
{{#pix}} level, tool_policy {{/pix}}
</div>
{{/isfirst}}
<div {{^isfirst}}style="margin-left: 24px" {{/isfirst}}>
<div><a href="{{viewurl}}">{{{name}}}</a></div>
</div>
</td>
<td>
<a href="{{viewurl}}">{{{revision}}}</a>
{{#iscurrent}}<span class="label label-success">{{#str}} status1, tool_policy {{/str}}{{/iscurrent}}
</td>
<td>
{{#agreement}}
{{>tool_policy/user_agreement}}
{{/agreement}}
</td>
<td>{{timeaccepted}}</td>
{{#hasonbehalfagreements}}
<td>{{{acceptedby}}}</td>
<td>{{{note}}}</td>
{{/hasonbehalfagreements}}
<td>
{{#hasarchived}}<a class="showarchived" data-target=".archived{{policyid}}" data-status="hidden" href="#">
<div class="toggleoff" style="display:none">{{#pix}}t/less, moodle, {{#str}}detailedless, moodle{{/str}}{{/pix}}</div>
<div class="toggleon">{{#pix}}t/more, moodle, {{#str}}detailedmore, moodle{{/str}}{{/pix}}</div>
</a>{{/hasarchived}}
</td>
</tr>
{{/versions}}
{{/policies}}
</tbody>
</table>
{{#returnurl}}
<div><a role="button" href="{{returnurl}}" class="btn btn-primary">{{#str}} back {{/str}}</a></div>
{{/returnurl}}
{{#js}}
require(['jquery'], function($) {
$('body').on('click', '.showarchived', function(e) {
e.preventDefault();
var target = $(this).attr('data-target'),
status = $(this).attr('data-status');
if (status === 'hidden') {
$(target).show();
$(this).attr('data-status', 'shown');
$(this).find('.toggleoff').show();
$(this).find('.toggleon').hide();
} else {
$(target).hide();
$(this).attr('data-status', 'hidden');
$(this).find('.toggleon').show();
$(this).find('.toggleoff').hide();
}
});
});
{{/js}}

View File

@ -0,0 +1,67 @@
{{!
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/>.
}}
{{!
@template tool_policy/acceptances_filter
Template for the unified filter element.
Context variables required for this template:
* action string - The action URL for the form.
* filteroptions - Array of filter options.
* value string - The option value.
* label string - The option label.
* selected boolean - Whether the option is selected
Example context (json):
{
"action": "/admin/tool/policy/acceptances.php",
"filteroptions": [
{
"value": "1",
"label": "Option 1"
},
{
"value": "2",
"label": "Option 2",
"selected": true
},
{
"value": "3",
"label": "Option 3",
"selected": true
},
{
"value": "4",
"label": "Option 4"
}
]
}
}}
<form method="post" action="{{action}}" class="m-b-1" role="search">
<label for="unified-filters" class="sr-only">{{#str}}filters , tool_policy{{/str}}</label>
<select name="unified-filters[]" id="unified-filters" multiple="multiple" data-originaloptionsjson="{{originaloptionsjson}}" class="form-autocomplete-original-select">
{{#filteroptions}}
<option value="{{value}}" {{#selected}}selected="selected"{{/selected}}>{{{label}}}</option>
{{/filteroptions}}
</select>
<input type="hidden" name="unified-filter-submitted" value="1">
</form>
{{#js}}
require(['tool_policy/acceptances_filter'], function(Filter) {
Filter.init();
});
{{/js}}

View File

@ -0,0 +1,65 @@
{{!
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/>.
}}
{{!
@template tool_policy/user_agreement
Template for the user agreement icon.
Classes required for JS:
-
Data attributes required for JS:
-
Context variables required for this template:
* status
* onbehalf
* canaccept
* acceptlink
Example context (json):
{
"status": false,
"onbehalf": false,
"canaccept": true,
"acceptlink": "/",
"singleversion": false,
"versionname": ""
}
}}
{{#status}}
{{#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}}
{{/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}}

View File

@ -0,0 +1,230 @@
@tool @tool_policy
Feature: Viewing acceptances reports and accepting on behalf of other users
In order to manage user acceptances
As a manager
I need to be able to view acceptances and accept on behalf of other users
Background:
Given the following config values are set as admin:
| sitepolicyhandler | tool_policy |
And the following policies exist:
| Name | Revision | Content | Summary | Status |
| This site policy | | full text2 | short text2 | active |
| This privacy policy | | full text3 | short text3 | draft |
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: View acceptances made by users on their own, single policy
When I log in as "user1"
Then I should see "This site policy"
And I should not see "Course overview"
And I press "Next"
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
And I should see "Course overview"
And I log out
And I log in as "manager"
And I press "Next"
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
And I navigate to "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 "Not agreed" "icon" 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"
And I set the following system permissions of "Manager" role:
| capability | permission |
| tool/policy:acceptbehalf | Allow |
And I log out
When I log in as "manager"
And I press "Next"
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
And I navigate to "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 "Not agreed" "link" in the "User One" "table_row"
Then I should see "Consent details"
And I should see "User One"
And I should see "This site policy"
And I should see "I acknowledge that consents to these policies have been acquired"
And I set the field "Remarks" to "Consent received from a parent"
And I press "I agree to the policy"
And "Agreed on behalf of" "icon" 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 "Not agreed" "icon" should exist in the "User Two" "table_row"
@javascript
Scenario: Agree on behalf of another user as a manager, single policy, javascript on
Given I log in as "admin"
And I set the following system permissions of "Manager" role:
| capability | permission |
| tool/policy:acceptbehalf | Allow |
And I log out
When I log in as "manager"
And I press "Next"
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
And I navigate to "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 "Not agreed" "link" in the "User One" "table_row"
Then I should see "Consent details"
And I should see "User One"
And I should see "This site policy"
And I should see "I acknowledge that consents to these policies have been acquired"
And I set the field "Remarks" to "Consent received from a parent"
And I press "I agree to the policy"
And "Agreed on behalf of" "icon" 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 "Not agreed" "icon" 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"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "This privacy policy" "table_row"
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
And I press "Continue"
And I log out
When I log in as "user1"
Then I should see "This site policy"
And I press "Next"
And I should see "This privacy policy"
And I press "Next"
And 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 "1"
And I press "Next"
And I should see "Course overview"
And I log out
And I log in as "manager"
And I press "Next"
And I press "Next"
And 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 "1"
And I press "Next"
And I navigate to "Privacy and policies > User agreements" in site administration
And "Agreed" "icon" should exist in the "User One" "table_row"
And "Not agreed" "icon" should not exist in the "User One" "table_row"
And "Agreed" "icon" should exist in the "Max Manager" "table_row"
And "Not agreed" "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 I am on site homepage
And I navigate to "Privacy and policies > User agreements" in site administration
And I click on "0 of 2" "link" in the "User Two" "table_row"
And "Not agreed" "icon" should exist in the "This site policy" "table_row"
And "Not agreed" "icon" 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"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "This privacy policy" "table_row"
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
And I press "Continue"
And I set the following system permissions of "Manager" role:
| capability | permission |
| tool/policy:acceptbehalf | Allow |
And I log out
When I log in as "manager"
And I press "Next"
And I press "Next"
And 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 "1"
And I press "Next"
And I navigate to "Privacy and policies > User agreements" in site administration
And I click on "Not agreed, click to agree to \"This site policy\"" "link" in the "User One" "table_row"
Then I should see "Consent details"
And I should see "User One"
And I should see "This site policy"
And I should see "I acknowledge that consents to these policies have been acquired"
And I set the field "Remarks" to "Consent received from a parent"
And I press "I agree to the policy"
And "Agreed on behalf of" "icon" should exist in the "User One" "table_row"
And "Not agreed, click to agree to \"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 "Agreed on behalf of" "icon" 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 "Not agreed" "icon" should exist in the "This privacy policy" "table_row"
@javascript
Scenario: Agree on behalf of another user as a manager, multiple policies, javascript on
Given I log in as "admin"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "This privacy policy" "table_row"
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
And I press "Activate"
And I set the following system permissions of "Manager" role:
| capability | permission |
| tool/policy:acceptbehalf | Allow |
And I log out
When I log in as "manager"
And I press "Next"
And I press "Next"
And 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 "1"
And I press "Next"
And I navigate to "Privacy and policies > User agreements" in site administration
And I click on "Not agreed, click to agree to \"This site policy\"" "link" in the "User One" "table_row"
Then I should see "Consent details"
And I should see "User One"
And I should see "This site policy"
And I should see "I acknowledge that consents to these policies have been acquired"
And I set the field "Remarks" to "Consent received from a parent"
And I press "I agree to the policy"
And "Agreed on behalf of" "icon" should exist in the "User One" "table_row"
And "Not agreed, click to agree to \"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 "Agreed on behalf of" "icon" 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 "Not agreed" "icon" 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"
And I press "Next"
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
When I follow "Profile" in the user menu
# 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"
# 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
Given I log in as "admin"
And I set the following system permissions of "Manager" role:
| capability | permission |
| tool/policy:acceptbehalf | Allow |
And I log out
And I log in as "manager"
And I press "Next"
And I set the field "I agree to the This site policy" to "1"
And I press "Next"
# User can see agreements link in other user profiles because has the capability for accepting on behalf of them.
When I am on "Course1" course homepage
And I navigate to course participants
And I follow "User Two"
Then I should see "Policies and agreements"

View File

@ -0,0 +1,111 @@
<?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/>.
/**
* Step definition for tool_policy
*
* @package tool_policy
* @category test
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode;
/**
* Step definition for tool_policy
*
* @package tool_policy
* @category test
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_tool_policy extends behat_base {
/**
* Click on an entry in the edit menu.
*
* @Given /^the following policies exist:$/
*
* @param TableNode $data
*/
public function the_following_policies_exist(TableNode $data) {
global $CFG;
if (empty($CFG->sitepolicyhandler) || $CFG->sitepolicyhandler !== 'tool_policy') {
throw new Exception('Site policy handler is not set to "tool_policy"');
}
$fields = [
'name', // Policy name (required).
'revision', // Revision name.
'policy', // Any policy identifier, can be used to generate multiple versions of the same policy.
'status', // Version status: 'draft', 'active', 'archived'. By default 'active'.
'audience', // Audience: 'guest', 'all', 'loggedin' (by default 'all').
'type', // 0 (default) - Site policy, 1 - Privacy policy, 2 - Third party policy, 99 - Other .
'content',
'summary',
];
// Associative array "policy identifier" => id in the database .
$policies = [];
foreach ($data->getHash() as $elementdata) {
$data = (object)[
'audience' => \tool_policy\policy_version::AUDIENCE_ALL,
'archived' => 0,
'type' => 0
];
$elementdata = array_change_key_case($elementdata, CASE_LOWER);
foreach ($elementdata as $key => $value) {
if ($key === 'policy') {
if (array_key_exists($value, $policies)) {
$data->policyid = $policies[$value];
}
} else if ($key === 'status') {
$data->archived = ($value === 'archived');
} else if ($key === 'audience') {
if ($value === 'guest') {
$data->audience = \tool_policy\policy_version::AUDIENCE_GUESTS;
} else if ($value === 'loggedin') {
$data->audience = \tool_policy\policy_version::AUDIENCE_LOGGEDIN;
}
} else if (($key === 'summary' || $key === 'content') && !empty($value)) {
$data->{$key.'_editor'} = ['text' => $value, 'format' => FORMAT_MOODLE];
} else if (in_array($key, $fields) && $value !== '') {
$data->$key = $value;
}
}
if (empty($data->name) || empty($data->content_editor) || empty($data->summary_editor)) {
throw new Exception('Policy is missing at least one of the required fields: name, content, summary');
}
if (!empty($data->policyid)) {
$version = tool_policy\api::form_policydoc_update_new($data);
} else {
$version = \tool_policy\api::form_policydoc_add($data);
}
if (!empty($elementdata['policy'])) {
$policies[$elementdata['policy']] = $version->get('policyid');
}
if (empty($elementdata['status']) || $elementdata['status'] === 'active') {
\tool_policy\api::make_current($version->get('id'));
}
}
}
}

View File

@ -0,0 +1,261 @@
@tool @tool_policy
Feature: Manage policies
In order to manage policies
As a manager
I need to be able to create and edit site policies
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 | |
Scenario: Create new policy and save as draft
When I log in as "manager"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I follow "New policy"
And I set the following fields to these values:
| Name | Policy1 |
| Version | v1 |
| Summary | Policy summary |
| Full policy | Full text |
And the field "Type" matches value "Site policy"
And the field "User consent" matches value "All users"
And the field "status" matches value "0"
And "Draft" "field" should exist
And "Active" "field" should exist
And "Minor change" "field" should not exist
And I should not see "Minor change"
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 |
And I log out
Scenario: Create new policy and save as active
When I log in as "admin"
# TODO MDL-61844 change to manager!
And I navigate to "Privacy and policies > Manage policies" in site administration
And I follow "New policy"
And I set the following fields to these values:
| Name | Policy1 |
| Version | v1 |
| Summary | Policy summary |
| Full policy | Full text |
| 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%) |
And I log out
Scenario: Edit active policy and save as minor change
Given the following policies exist:
| Name | Revision | Content | Summary | Status |
| Policy1 | v1 | full text2 | short text2 | active |
And I log in as "manager"
And I press "Next"
And I set the field "I agree to the Policy1" to "1"
And I press "Next"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "Policy1" "table_row"
And I click on "Edit" "link" in the "Policy1" "table_row"
And "Draft" "field" should not exist
And "Active" "field" should not exist
And "Minor change" "field" should exist
And "Save as draft" "button" should exist
And I set the field "Version" to "v1 amended"
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%) |
And I log out
Scenario: Edit active policy and save as draft
Given the following policies exist:
| Name | Revision | Content | Summary | Status |
| Policy1 | v1 | full text2 | short text2 | active |
And I log in as "manager"
And I press "Next"
And I set the field "I agree to the Policy1" to "1"
And I press "Next"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "Policy1" "table_row"
And I click on "Edit" "link" in the "Policy1" "table_row"
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 |
And I log out
Scenario: Edit active policy and save as new active version
Given the following policies exist:
| Name | Revision | Content | Summary | Status |
| Policy1 | v1 | full text2 | short text2 | active |
And I log in as "manager"
And I press "Next"
And I set the field "I agree to the Policy1" to "1"
And I press "Next"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "Policy1" "table_row"
And I click on "Edit" "link" in the "Policy1" "table_row"
And I set the field "Name" to "Policy2"
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%) |
And I should not see "Policy1"
And I should not see "v1"
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 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%) |
And I log out
Scenario: Edit draft policy and save as draft
Given the following policies exist:
| Name | Revision | Content | Summary | Status |
| Policy1 | v1 | full text2 | short text2 | draft |
And I log in as "manager"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "Policy1" "table_row"
And I click on "Edit" "link" in the "Policy1" "table_row"
And I set the field "Version" to "v2"
And "Draft" "field" should exist
And "Active" "field" should exist
And "Minor change" "field" should not exist
And I should not see "Minor change"
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 |
And I should not see "v1"
And I open the action menu in "Policy1" "table_row"
And "View previous versions" "link" should not exist
And I log out
Scenario: Edit draft policy and save as active
Given the following policies exist:
| Name | Revision | Content | Summary | Status |
| Policy1 | v1 | full text2 | short text2 | draft |
And I log in as "admin"
# TODO MDL-61844 change to manager!
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "Policy1" "table_row"
And I click on "Edit" "link" in the "Policy1" "table_row"
And I set the field "Version" to "v2"
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%) |
And I should not see "v1"
And I open the action menu in "Policy1" "table_row"
And "View previous versions" "link" should not exist
And I log out
Scenario: Activate draft policy
Given the following policies exist:
| Name | Revision | Content | Summary | Status |
| Policy1 | v1 | full text2 | short text2 | draft |
And I log in as "admin"
# TODO MDL-61844 change to manager!
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "Policy1" "table_row"
And I click on "Set status to \"Active\"" "link" in the "Policy1" "table_row"
Then I should see "All users will be required to accept 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%) |
And I open the action menu in "Policy1" "table_row"
And "View previous versions" "link" should not exist
And I log out
Scenario: Edit archived policy and save as draft
Given the following policies exist:
| Name | Revision | Content | Summary | Status |
| Policy1 | v1 | full text2 | short text2 | active |
And I log in as "manager"
And I press "Next"
And I set the field "I agree to the Policy1" to "1"
And I press "Next"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "Policy1" "table_row"
And I click on "Set status to \"Inactive\"" "link" in the "Policy1" "table_row"
Then I should see "The policy will not apply until some version is made the current one"
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%) |
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"
And I set the field "Name" to "Policy2"
And the field "status" matches value "0"
And "Draft" "field" should exist
And "Active" "field" should exist
And "Minor change" "field" should not exist
And I should not see "Minor change"
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 |
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%) |
And I should not see "v2"
And I log out
Scenario: Edit archived policy and save as active
Given the following policies exist:
| Name | Revision | Content | Summary | Status |
| Policy1 | v1 | full text2 | short text2 | active |
And I log in as "manager"
And I press "Next"
And I set the field "I agree to the Policy1" to "1"
And I press "Next"
And I navigate to "Privacy and policies > Manage policies" in site administration
And I open the action menu in "Policy1" "table_row"
And I click on "Set status to \"Inactive\"" "link" in the "Policy1" "table_row"
And I press "Continue"
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"
And I set the field "Name" to "Policy2"
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%) |
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%) |
And I should not see "v2"
And I log out

View File

@ -0,0 +1,53 @@
<?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/>.
/**
* View user acceptances to the policies
*
* @package tool_policy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../../config.php');
require_once($CFG->dirroot.'/user/editlib.php');
$userid = optional_param('userid', null, PARAM_INT);
$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
require_login();
$userid = $userid ?: $USER->id;
if (isguestuser() || isguestuser($userid)) {
print_error('noguest');
}
$context = context_user::instance($userid);
if ($userid != $USER->id) {
// Check capability to view acceptances. No capability is needed to view your own acceptances.
if (!has_capability('tool/policy:acceptbehalf', $context)) {
require_capability('tool/policy:viewacceptances', $context);
}
}
$PAGE->set_context($context);
$PAGE->set_url(new moodle_url('/admin/tool/policy/user.php', ['userid' => $userid]));
$output = $PAGE->get_renderer('tool_policy');
echo $output->header();
echo $output->heading(get_string('policiesagreements', 'tool_policy'));
$acceptances = new \tool_policy\output\acceptances($userid, $returnurl);
echo $output->render($acceptances);
$PAGE->requires->js_call_amd('tool_policy/acceptmodal', 'getInstance', [context_system::instance()->id]);
echo $output->footer();

View File

@ -1907,8 +1907,8 @@ class core_plugin_manager {
'analytics', 'assignmentupgrade', 'availabilityconditions', 'behat', 'capability', 'cohortroles', 'customlang',
'dbtransfer', 'filetypes', 'generator', 'health', 'httpsreplace', 'innodb', 'installaddon',
'langimport', 'log', 'lp', 'lpimportcsv', 'lpmigrate', 'messageinbound', 'mobile', 'multilangupgrade',
'monitor', 'oauth2', 'phpunit', 'profiling', 'recyclebin', 'replace', 'spamcleaner', 'task', 'templatelibrary',
'uploadcourse', 'uploaduser', 'unsuproles', 'usertours', 'xmldb'
'monitor', 'oauth2', 'phpunit', 'policy', 'profiling', 'recyclebin', 'replace', 'spamcleaner', 'task',
'templatelibrary', 'uploadcourse', 'uploaduser', 'unsuproles', 'usertours', 'xmldb'
),
'webservice' => array(