first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,650 @@
<?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;
}
}
// TODO Does not support custom user profile fields (MDL-70456).
$userfieldsapi = \core_user\fields::for_identity(\context_system::instance(), false)->with_userpic();
$userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
$extrafields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
$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, \core_user\fields::get_display_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->columnsticky[$key] = '';
$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() {
$userfieldsapi = \core_user\fields::for_name();
$userfieldsmod = $userfieldsapi->get_sql('m', false, 'mod', '', false)->selects;
$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 if ($filterstatus == 2) {
$this->sql->from .= " $join AND a{$v}.status=0";
} else {
$this->sql->from .= " LEFT $join";
}
$this->sql->from .= " LEFT JOIN {user} m ON m.id = a{$v}.usermodified AND m.id <> u.id AND a{$v}.status IS NOT NULL";
$this->sql->params['versionid' . $v] = $v;
if ($filterstatus === 0) {
$this->sql->where .= " AND a{$v}.status IS NULL";
}
$this->add_column_header('status' . $v, get_string('response', 'tool_policy'));
$this->add_column_header('timemodified', get_string('responseon', 'tool_policy'));
$this->add_column_header('usermodified' . $v, get_string('responseby', 'tool_policy'));
$this->add_column_header('note', get_string('acceptancenote', 'tool_policy'), false);
}
/**
* 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 if ($filterstatus == 2) {
$this->sql->from .= " {$join} AND a{$v}.status=0";
} else {
$this->sql->from .= " LEFT {$join}";
}
$this->sql->params['versionid' . $v] = $v;
$this->add_column_header('status' . $v, $versionname);
$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";
}
$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')]);
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[]',
'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' => 'hidden', 'name' => 'action', 'value' => 'accept']);
echo \html_writer::empty_tag('input', ['type' => 'submit', 'data-action' => 'acceptmodal',
'value' => get_string('consentbulk', 'tool_policy'), 'class' => 'btn btn-primary mt-1']);
$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.
$declined = [];
foreach ($versions as $v => $name) {
if ($row->{'status' . $v} !== null) {
if (empty($row->{'status' . $v})) {
$declined[] = $v;
} else {
$accepted[] = $v;
}
$agreedby = $row->{'usermodified' . $v};
if ($agreedby && $agreedby != $row->id) {
$onbehalf = true;
}
}
}
$ua = new user_agreement($row->id, $accepted, $declined, $this->get_return_url(), $versions, $onbehalf, $row->canaccept);
if ($this->is_downloading()) {
return $ua->export_for_download();
} else {
return $this->output->render($ua);
}
}
/**
* 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((int) $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 parent::other_cols($column, $row);
}
}
File diff suppressed because it is too large Load Diff
@@ -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;
}
}
@@ -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}'";
}
}
@@ -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}'";
}
}
+189
View File
@@ -0,0 +1,189 @@
<?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/>.
/**
* Class containing the external API functions functions for the Policy tool.
*
* @package tool_policy
* @copyright 2018 Sara Arjona (sara@moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy;
use context_system;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use moodle_exception;
use tool_policy\form\accept_policy;
/**
* Class external.
*
* The external API for the Policy tool.
*
* @copyright 2018 Sara Arjona (sara@moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external extends external_api {
/**
* Parameter description for get_policy_version_parameters().
*
* @return external_function_parameters
*/
public static function get_policy_version_parameters() {
return new external_function_parameters([
'versionid' => new external_value(PARAM_INT, 'The policy version ID', VALUE_REQUIRED),
'behalfid' => new external_value(PARAM_INT, 'The id of user on whose behalf the user is viewing the policy',
VALUE_DEFAULT, 0)
]);
}
/**
* Fetch the details of a policy version.
*
* @param int $versionid The policy version ID.
* @param int $behalfid The id of user on whose behalf the user is viewing the policy.
* @return array
* @throws coding_exception
* @throws dml_exception
* @throws invalid_parameter_exception
* @throws restricted_context_exception
* @throws moodle_exception
*/
public static function get_policy_version($versionid, $behalfid = null) {
global $PAGE;
$result = [];
$warnings = [];
$params = external_api::validate_parameters(self::get_policy_version_parameters(), [
'versionid' => $versionid,
'behalfid' => $behalfid
]);
$versionid = $params['versionid'];
$behalfid = $params['behalfid'];
$context = context_system::instance();
$PAGE->set_context($context);
try {
// Validate if the user has access to the policy version.
$version = api::get_policy_version($versionid);
if (!api::can_user_view_policy_version($version, $behalfid)) {
$warnings[] = [
'item' => $versionid,
'warningcode' => 'errorusercantviewpolicyversion',
'message' => get_string('errorusercantviewpolicyversion', 'tool_policy')
];
} else if (!empty($version)) {
$version = api::get_policy_version($versionid);
$policy['name'] = $version->name;
$policy['versionid'] = $versionid;
list($policy['content'], $notusedformat) = \core_external\util::format_text(
$version->content,
$version->contentformat,
\context_system::instance(),
'tool_policy',
'policydocumentcontent',
$version->id
);
$result['policy'] = $policy;
}
} catch (moodle_exception $e) {
$warnings[] = [
'item' => $versionid,
'warningcode' => 'errorpolicyversionnotfound',
'message' => get_string('errorpolicyversionnotfound', 'tool_policy')
];
}
return [
'result' => $result,
'warnings' => $warnings
];
}
/**
* Parameter description for get_policy_version().
*
* @return \core_external\external_description
*/
public static function get_policy_version_returns() {
return new external_single_structure([
'result' => new external_single_structure([
'policy' => new external_single_structure([
'name' => new external_value(PARAM_RAW, 'The policy version name', VALUE_OPTIONAL),
'versionid' => new external_value(PARAM_INT, 'The policy version id', VALUE_OPTIONAL),
'content' => new external_value(PARAM_RAW, 'The policy version content', VALUE_OPTIONAL)
], 'Policy information', VALUE_OPTIONAL)
]),
'warnings' => new external_warnings()
]);
}
/**
* Describes the parameters for submit_create_group_form webservice.
* @return external_function_parameters
*/
public static function submit_accept_on_behalf_parameters() {
return new external_function_parameters(
array(
'jsonformdata' => new external_value(PARAM_RAW, 'The data from the create group form, encoded as a json array')
)
);
}
/**
* Submit the create group form.
*
* @param string $jsonformdata The data from the form, encoded as a json array.
* @return int new group id.
*/
public static function submit_accept_on_behalf($jsonformdata) {
// We always must pass webservice params through validate_parameters.
$params = self::validate_parameters(self::submit_accept_on_behalf_parameters(),
['jsonformdata' => $jsonformdata]);
self::validate_context(context_system::instance());
$serialiseddata = json_decode($params['jsonformdata']);
$data = array();
parse_str($serialiseddata, $data);
// The last param is the ajax submitted data.
$mform = new accept_policy(null, $data, 'post', '', null, true, $data);
// Do the action.
$mform->process();
return true;
}
/**
* Returns description of method result value.
*
* @return \core_external\external_description
* @since Moodle 3.0
*/
public static function submit_accept_on_behalf_returns() {
return new external_value(PARAM_BOOL, 'success');
}
}
@@ -0,0 +1,173 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_policy\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_multiple_structure;
use core_external\external_format_value;
use core_external\external_value;
use core_external\external_warnings;
use tool_policy\api;
use context_user;
use core_user;
use core_external\util;
/**
* External function for retrieving user policies acceptances.
*
* @package tool_policy
* @copyright 2023 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.4
*/
class get_user_acceptances extends external_api {
/**
* Webservice parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'userid' => new external_value(PARAM_INT, 'The user id we want to retrieve the acceptances.',
VALUE_DEFAULT, 0),
]
);
}
/**
* Returns the acceptance status for all the policies the given user can see.
*
* @param int $userid the user id we want to retrieve the acceptances
* @throws \required_capability_exception
* @return array policies and acceptance status
*/
public static function execute(int $userid = 0): array {
global $USER;
$params = self::validate_parameters(self::execute_parameters(),
[
'userid' => $userid,
]
);
// Do not check for the site policies in validate_context() to avoid the redirect loop.
if (!defined('NO_SITEPOLICY_CHECK')) {
define('NO_SITEPOLICY_CHECK', true);
}
$systemcontext = \context_system::instance();
external_api::validate_context($systemcontext);
if (empty($params['userid']) || $params['userid'] == $USER->id) {
$user = $USER;
} else {
$user = core_user::get_user($params['userid'], '*', MUST_EXIST);
core_user::require_active_user($user);
$usercontext = context_user::instance($user->id);
// Check capability to view acceptances. No capability is needed to view your own acceptances.
if (!has_capability('tool/policy:acceptbehalf', $usercontext)) {
require_capability('tool/policy:viewacceptances', $usercontext);
}
}
$canviewfullnames = has_capability('moodle/site:viewfullnames', $systemcontext);
$userpolicies = api::get_policies_with_acceptances($user->id);
$policies = [];
foreach ($userpolicies as $userpolicy) {
foreach ($userpolicy->versions as $version) {
$policy = (array) clone $version;
unset($policy['acceptance']); // This might return NULL and break the WS response.
$policy['versionid'] = $version->id;
$policy['name'] = util::format_string($version->name, $systemcontext);
$policy['revision'] = util::format_string($version->revision, $systemcontext);
[$policy['summary'], $policy['summaryformat']] = util::format_text($version->summary,
$version->summaryformat, $systemcontext);
[$policy['content'], $policy['contentformat']] = util::format_text($version->content,
$version->contentformat, $systemcontext);
if (!empty($version->acceptance)) {
$policy['acceptance'] = (array) $version->acceptance;
if ($version->acceptance->usermodified && $version->acceptance->usermodified != $user->id) {
// Get the full name of who accepted on behalf.
$usermodified = (object)['id' => $version->acceptance->usermodified];
username_load_fields_from_object($usermodified, $version->acceptance, 'mod');
$override = $canviewfullnames || has_capability('moodle/site:viewfullnames', context_user::instance($version->acceptance->usermodified));
$policy['acceptance']['modfullname'] = fullname($usermodified, $override);
}
if (!empty($version->acceptance->note)) {
[$policy['acceptance']['note']] = util::format_text($version->acceptance->note, FORMAT_MOODLE, $systemcontext);
}
}
// Return permission for actions for the current policy and user.
$policy['canaccept'] = api::can_accept_policies([$version->id], $user->id);
$policy['candecline'] = api::can_decline_policies([$version->id], $user->id);
$policy['canrevoke'] = api::can_revoke_policies([$version->id], $user->id);
$policies[] = $policy;
}
}
$return = [
'policies' => $policies,
'warnings' => [],
];
return $return;
}
/**
* Webservice returns.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'policies' => new external_multiple_structure(
new external_single_structure([
'policyid' => new external_value(PARAM_INT, 'The policy id.'),
'versionid' => new external_value(PARAM_INT, 'The policy version id.'),
'agreementstyle' => new external_value(PARAM_INT, 'The policy agreement style. 0: consent page, 1: own page.'),
'optional' => new external_value(PARAM_INT, 'Whether the policy is optional. 0: compulsory, 1: optional'),
'revision' => new external_value(PARAM_TEXT, 'The policy revision.'),
'status' => new external_value(PARAM_INT, 'The policy status. 0: draft, 1: active, 2: archived.'),
'name' => new external_value(PARAM_TEXT, 'The policy name'),
'summary' => new external_value(PARAM_RAW, 'The policy summary.', VALUE_OPTIONAL),
'summaryformat' => new external_format_value('summary'),
'content' => new external_value(PARAM_RAW, 'The policy content.', VALUE_OPTIONAL),
'contentformat' => new external_format_value('content'),
'acceptance' => new external_single_structure([
'status' => new external_value(PARAM_INT, 'The acceptance status. 0: declined, 1: accepted.'),
'lang' => new external_value(PARAM_LANG, 'The policy lang.'),
'timemodified' => new external_value(PARAM_INT, 'The time the acceptance was set.'),
'usermodified' => new external_value(PARAM_INT, 'The user who accepted.'),
'note' => new external_value(PARAM_TEXT, 'The policy note/remarks.', VALUE_OPTIONAL),
'modfullname' => new external_value(PARAM_NOTAGS, 'The fullname who accepted on behalf.', VALUE_OPTIONAL),
], 'Acceptance status for the given user.', VALUE_OPTIONAL),
'canaccept' => new external_value(PARAM_BOOL, 'Whether the policy can be accepted.'),
'candecline' => new external_value(PARAM_BOOL, 'Whether the policy can be declined.'),
'canrevoke' => new external_value(PARAM_BOOL, 'Whether the policy can be revoked.'),
]), 'Policies and acceptance status for the given user.', VALUE_OPTIONAL
),
'warnings' => new external_warnings(),
]);
}
}
@@ -0,0 +1,151 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_policy\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_multiple_structure;
use core_external\external_value;
use core_external\external_warnings;
use tool_policy\api;
use tool_policy\policy_version;
use core_user;
/**
* External function for setting user policies acceptances.
*
* @package tool_policy
* @copyright 2023 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.4
*/
class set_acceptances_status extends external_api {
/**
* Webservice parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'policies' => new external_multiple_structure(
new external_single_structure([
'versionid' => new external_value(PARAM_INT, 'The policy version id.'),
'status' => new external_value(PARAM_INT, 'The policy acceptance status. 0: decline, 1: accept.'),
'note' => new external_value(PARAM_NOTAGS,
'Any comments added by a user when giving consent on behalf of another user.', VALUE_OPTIONAL, null),
]), 'Policies acceptances for the given user.'
),
'userid' => new external_value(PARAM_INT,
'The user id we want to set the acceptances. Default is the current user.', VALUE_DEFAULT, 0
),
]
);
}
/**
* Set the acceptance status (accept or decline only) for the indicated policies for the given user.
*
* @param array $policies the policies to set the acceptance status
* @param int $userid the user id we want to retrieve the acceptances
* @throws \moodle_exception
* @return array policies and acceptance status
*/
public static function execute(array $policies, int $userid = 0): array {
global $USER;
$params = self::validate_parameters(self::execute_parameters(),
[
'policies' => $policies,
'userid' => $userid,
]
);
// Do not check for the site policies in validate_context() to avoid the redirect loop.
if (!defined('NO_SITEPOLICY_CHECK')) {
define('NO_SITEPOLICY_CHECK', true);
}
$systemcontext = \context_system::instance();
external_api::validate_context($systemcontext);
if (empty($params['userid']) || $params['userid'] == $USER->id) {
$user = $USER;
} else {
$user = core_user::get_user($params['userid'], '*', MUST_EXIST);
core_user::require_active_user($user);
}
// Split acceptances.
$requestedpolicies = $agreepolicies = $declinepolicies = $notes = [];
foreach ($params['policies'] as $policy) {
$requestedpolicies[$policy['versionid']] = $policy['status'];
if ($USER->id != $user->id) {
// Notes are only allowed when setting acceptances on behalf of another user.
$notes[$policy['versionid']] = $policy['note'] ?? null;
}
}
// Retrieve all policies and their acceptances.
$allpolicies = api::get_policies_with_acceptances($user->id);
foreach ($allpolicies as $policy) {
foreach ($policy->versions as $version) {
if (isset($requestedpolicies[$version->id])) {
if ($requestedpolicies[$version->id] === 1) {
$agreepolicies[] = $version->id;
} else if ($requestedpolicies[$version->id] === 0) {
$declinepolicies[] = $version->id;
}
}
}
}
// Permissions check.
api::can_accept_policies($agreepolicies, $user->id, true);
api::can_decline_policies($declinepolicies, $user->id, true);
// Good to go.
foreach ($agreepolicies as $policyversionid) {
api::accept_policies($policyversionid, $user->id, $notes[$policyversionid] ?? null);
}
foreach ($declinepolicies as $policyversionid) {
api::decline_policies($policyversionid, $user->id, $notes[$policyversionid] ?? null);
}
$return = [
'policyagreed' => (int) $user->policyagreed, // Final policy agreement status for $user.
'warnings' => [],
];
return $return;
}
/**
* Webservice returns.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'policyagreed' => new external_value(PARAM_INT,
'Whether the user has provided acceptance to all current site policies. 1 if yes, 0 if not'),
'warnings' => new external_warnings(),
]);
}
}
@@ -0,0 +1,201 @@
<?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 or revoking 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, $USER;
$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');
}
$action = $this->_customdata['action'];
$userids = clean_param_array($this->_customdata['userids'], PARAM_INT);
$versionids = clean_param_array($this->_customdata['versionids'], PARAM_INT);
$usernames = $this->validate_and_get_users($versionids, $userids, $action);
$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);
$useracceptancelabel = (count($usernames) > 1) ? get_string('acceptanceusers', 'tool_policy') :
get_string('user');
$mform->addElement('static', 'user', $useracceptancelabel, join(', ', $usernames));
$policyacceptancelabel = (count($versionnames) > 1) ? get_string('acceptancepolicies', 'tool_policy') :
get_string('policydochdrpolicy', 'tool_policy');
$mform->addElement('static', 'policy', $policyacceptancelabel, join(', ', $versionnames));
if ($action === 'revoke') {
$mform->addElement('static', 'ack', '', get_string('revokeacknowledgement', 'tool_policy'));
$mform->addElement('hidden', 'action', 'revoke');
} else if ($action === 'accept') {
$mform->addElement('static', 'ack', '', get_string('acceptanceacknowledgement', 'tool_policy'));
$mform->addElement('hidden', 'action', 'accept');
} else if ($action === 'decline') {
$mform->addElement('static', 'ack', '', get_string('declineacknowledgement', 'tool_policy'));
$mform->addElement('hidden', 'action', 'decline');
} else {
throw new \moodle_exception('invalidaccessparameter');
}
$mform->setType('action', PARAM_ALPHA);
if (count($usernames) == 1 && isset($usernames[$USER->id])) {
// No need to display the acknowledgement if the users are giving/revoking acceptance on their own.
$mform->removeElement('ack');
}
$mform->addElement('textarea', 'note', get_string('acceptancenote', 'tool_policy'));
$mform->setType('note', PARAM_NOTAGS);
if (!empty($this->_customdata['showbuttons'])) {
if ($action === 'revoke') {
$this->add_action_buttons(true, get_string('irevokethepolicy', 'tool_policy'));
} else if ($action === 'accept') {
$this->add_action_buttons(true, get_string('iagreetothepolicy', 'tool_policy'));
} else if ($action === 'decline') {
$this->add_action_buttons(true, get_string('declinethepolicy', 'tool_policy'));
}
}
$PAGE->requires->js_call_amd('tool_policy/policyactions', 'init', ['[data-action="view"]']);
}
/**
* Validate userids and return usernames
*
* @param array $versionids int[] List of policy version ids to process.
* @param array $userids
* @param string $action accept|decline|revoke
* @return array (userid=>username)
*/
protected function validate_and_get_users($versionids, $userids, $action) {
global $DB;
$usernames = [];
list($sql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params['usercontextlevel'] = CONTEXT_USER;
$userfieldsapi = \core_user\fields::for_name();
$users = $DB->get_records_sql("SELECT u.id" . $userfieldsapi->get_sql('u')->selects . ", " .
\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);
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');
}
\context_helper::preload_from_record($user);
if ($action === 'revoke') {
api::can_revoke_policies($versionids, $userid, true);
} else if ($action === 'accept') {
api::can_accept_policies($versionids, $userid, true);
} else if ($action === 'decline') {
api::can_decline_policies($versionids, $userid, true);
}
$usernames[$userid] = fullname($user);
}
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) {
if ($data->action === 'revoke') {
foreach ($data->versionids as $versionid) {
\tool_policy\api::revoke_acceptance($versionid, $userid, $data->note);
}
} else if ($data->action === 'accept') {
\tool_policy\api::accept_policies($data->versionids, $userid, $data->note);
} else if ($data->action === 'decline') {
\tool_policy\api::decline_policies($data->versionids, $userid, $data->note);
}
}
}
}
}
@@ -0,0 +1,168 @@
<?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\policydoc} class.
*
* @package tool_policy
* @category output
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\form;
use context_system;
use html_writer;
use moodleform;
use tool_policy\api;
use tool_policy\policy_version;
defined('MOODLE_INTERNAL') || die();
/**
* Defines the form for editing a policy document version.
*
* @copyright 2018 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class policydoc extends moodleform {
/**
* Defines the form fields.
*/
public function definition() {
$mform = $this->_form;
$formdata = $this->_customdata['formdata'];
$mform->addElement('text', 'name', get_string('policydocname', 'tool_policy'), ['maxlength' => 1333]);
$mform->settype('name', PARAM_TEXT);
$mform->addRule('name', null, 'required', null, 'client');
$mform->addRule('name', get_string('maximumchars', '', 1333), 'maxlength', 1333, 'client');
$options = [];
foreach ([policy_version::TYPE_SITE,
policy_version::TYPE_PRIVACY,
policy_version::TYPE_THIRD_PARTY,
policy_version::TYPE_OTHER] as $type) {
$options[$type] = get_string('policydoctype'.$type, 'tool_policy');
}
$mform->addElement('select', 'type', get_string('policydoctype', 'tool_policy'), $options);
$options = [];
foreach ([policy_version::AUDIENCE_ALL,
policy_version::AUDIENCE_LOGGEDIN,
policy_version::AUDIENCE_GUESTS] as $audience) {
$options[$audience] = get_string('policydocaudience'.$audience, 'tool_policy');
}
$mform->addElement('select', 'audience', get_string('policydocaudience', 'tool_policy'), $options);
if (empty($formdata->id)) {
$default = userdate(time(), get_string('strftimedate', 'core_langconfig'));
} else {
$default = userdate($formdata->timecreated, get_string('strftimedate', 'core_langconfig'));
}
$mform->addElement('text', 'revision', get_string('policydocrevision', 'tool_policy'),
['maxlength' => 1333, 'placeholder' => $default]);
$mform->settype('revision', PARAM_TEXT);
$mform->addRule('revision', get_string('maximumchars', '', 1333), 'maxlength', 1333, 'client');
$mform->addElement('editor', 'summary_editor', get_string('policydocsummary', 'tool_policy'), ['rows' => 7],
api::policy_summary_field_options());
$mform->addRule('summary_editor', null, 'required', null, 'client');
$mform->addElement('editor', 'content_editor', get_string('policydoccontent', 'tool_policy'), null,
api::policy_content_field_options());
$mform->addRule('content_editor', null, 'required', null, 'client');
$mform->addElement('selectyesno', 'agreementstyle', get_string('policypriorityagreement', 'tool_policy'));
$mform->addElement('selectyesno', 'optional', get_string('policydocoptional', 'tool_policy'));
if (!$formdata->id || $formdata->status == policy_version::STATUS_DRAFT) {
// Creating a new version or editing a draft/archived version.
$mform->addElement('hidden', 'minorchange');
$mform->setType('minorchange', PARAM_INT);
$statusgrp = [
$mform->createElement('radio', 'status', '', get_string('status'.policy_version::STATUS_ACTIVE, 'tool_policy'),
policy_version::STATUS_ACTIVE),
$mform->createElement('radio', 'status', '', get_string('status'.policy_version::STATUS_DRAFT, 'tool_policy'),
policy_version::STATUS_DRAFT),
$mform->createElement('static', 'statusinfo', '', html_writer::div(get_string('statusinfo', 'tool_policy'),
'muted text-muted')),
];
$mform->addGroup($statusgrp, null, get_string('status', 'tool_policy'), ['<br>'], false);
} else {
// Editing an active version.
$mform->addElement('hidden', 'status', policy_version::STATUS_ACTIVE);
$mform->setType('status', PARAM_INT);
$statusgrp = [
$mform->createElement('checkbox', 'minorchange', '', get_string('minorchange', 'tool_policy')),
$mform->createElement('static', 'minorchangeinfo', '',
html_writer::div(get_string('minorchangeinfo', 'tool_policy'), 'muted text-muted')),
];
$mform->addGroup($statusgrp, null, get_string('status', 'tool_policy'), ['<br>'], false);
}
// Add "Save" button and, optionally, "Save as draft".
$buttonarray = [];
$buttonarray[] = $mform->createElement('submit', 'save', get_string('save', 'tool_policy'));
if ($formdata->id && $formdata->status == policy_version::STATUS_ACTIVE) {
$buttonarray[] = $mform->createElement('submit', 'saveasdraft', get_string('saveasdraft', 'tool_policy'));
}
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
$this->set_data($formdata);
}
/**
* Form validation
*
* @param array $data array of ("fieldname"=>value) of submitted data
* @param array $files array of uploaded files "element_name"=>tmp_file_path
* @return array of "element_name"=>"error_description" if there are errors,
* or an empty array if everything is OK (true allowed for backwards compatibility too).
*/
public function validation($data, $files) {
$errors = parent::validation($data, $files);
if (!empty($data['minorchange']) && !empty($data['saveasdraft'])) {
// If minorchange is checked and "save as draft" is pressed - return error.
$errors['minorchange'] = get_string('errorsaveasdraft', 'tool_policy');
}
return $errors;
}
/**
* Return submitted data if properly submitted or returns NULL if validation fails or
* if there is no submitted data.
*
* @return object submitted data; NULL if not valid or not submitted or cancelled
*/
public function get_data() {
if ($data = parent::get_data()) {
if (!empty($data->saveasdraft)) {
$data->status = policy_version::STATUS_DRAFT;
}
}
return $data;
}
}
@@ -0,0 +1,89 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace tool_policy;
use core\hook\output\before_standard_footer_html_generation;
use core\hook\output\before_standard_top_of_body_html_generation;
use html_writer;
use moodle_url;
/**
* Allows the plugin to add any elements to the footer.
*
* @package tool_policy
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class hook_callbacks {
/**
* Add the guest consent form to the top of the body.
*
* @param before_standard_top_of_body_html_generation $hook
*/
public static function before_standard_top_of_body_html_generation(before_standard_top_of_body_html_generation $hook): void {
global $CFG, $PAGE, $USER;
if (empty($CFG->sitepolicyhandler)) {
return;
}
if ($CFG->sitepolicyhandler !== 'tool_policy') {
return;
}
if (!empty($USER->policyagreed)) {
return;
}
if (!isguestuser() && isloggedin()) {
return;
}
$output = $PAGE->get_renderer('tool_policy');
try {
$page = new \tool_policy\output\guestconsent();
$hook->add_html($output->render($page));
} catch (\dml_read_exception $e) {
// During upgrades, the new plugin code with new SQL could be in place but the DB not upgraded yet.
return;
}
}
/**
* Add the user policy settings link to the footer.
*
* @param before_standard_footer_html_generation $hook
*/
public static function before_standard_footer_html_generation(before_standard_footer_html_generation $hook): void {
global $CFG, $PAGE;
if (empty($CFG->sitepolicyhandler) || $CFG->sitepolicyhandler !== 'tool_policy') {
return;
}
$policies = api::get_current_versions_ids();
if (!empty($policies)) {
$url = new moodle_url('/admin/tool/policy/viewall.php', ['returnurl' => $PAGE->url]);
$hook->add_html(
html_writer::div(
html_writer::link($url, get_string('userpolicysettings', 'tool_policy')),
'policiesfooter',
),
);
}
}
}
@@ -0,0 +1,142 @@
<?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
* @param string|moodle_url $returnurl
*/
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);
$versionids = [];
$canviewfullnames = has_capability('moodle/site:viewfullnames', \context_system::instance());
foreach ($policies as $policy) {
foreach ($policy->versions as $version) {
$versionids[$version->id] = $version->id;
unset($version->summary);
unset($version->content);
$version->iscurrent = ($version->status == policy_version::STATUS_ACTIVE);
$version->isoptional = ($version->optional == policy_version::AGREEMENT_OPTIONAL);
$version->name = $version->name;
$version->revision = $version->revision;
$returnurl = new moodle_url('/admin/tool/policy/user.php', ['userid' => $this->userid]);
$version->viewurl = (new moodle_url('/admin/tool/policy/view.php', [
'policyid' => $policy->id,
'versionid' => $version->id,
'returnurl' => $returnurl->out(false),
]))->out(false);
if ($version->acceptance !== null) {
$acceptance = $version->acceptance;
$version->timeaccepted = userdate($acceptance->timemodified, get_string('strftimedatetime'));
$onbehalf = $acceptance->usermodified && $acceptance->usermodified != $this->userid;
if ($version->acceptance->status == 1) {
$version->agreement = new user_agreement($this->userid, [$version->id], [], $returnurl,
[$version->id => $version->name], $onbehalf);
} else {
$version->agreement = new user_agreement($this->userid, [], [$version->id], $returnurl,
[$version->id => $version->name], $onbehalf);
}
if ($onbehalf) {
$usermodified = (object)['id' => $acceptance->usermodified];
username_load_fields_from_object($usermodified, $acceptance, 'mod');
$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);
$data->canrevoke = \tool_policy\api::can_revoke_policies(array_keys($versionids), $this->userid);
return $data;
}
}
@@ -0,0 +1,464 @@
<?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:
case self::FILTER_CAPABILITY_ACCEPT:
case self::FILTER_STATUS:
$value = (int)$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.':2' => get_string('filterstatusdeclined', 'tool_policy'),
self::FILTER_STATUS.':1' => get_string('filterstatusyes', 'tool_policy'),
self::FILTER_STATUS.':0' => get_string('filterstatuspending', '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;
}
}
@@ -0,0 +1,74 @@
<?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\renderer} class.
*
* @package tool_policy
* @category output
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @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 templatable;
use tool_policy\api;
use tool_policy\policy_version;
/**
* Renderer for the policies plugin.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class guestconsent implements renderable, templatable {
/**
* 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) {
global $PAGE;
$data = (object) [];
$data->pluginbaseurl = (new moodle_url('/admin/tool/policy'))->out(true);
if (strpos(qualified_me(), '/tool/policy/view.php') === false) {
// Current page is not a policy doc, so returnurl parameter will be it.
$data->returnurl = qualified_me();
} else {
// If current page is also a policy doc to view, get previous returnurl parameter to avoid error.
$returnurl = $PAGE->url->get_param('returnurl');
if (isset($returnurl)) {
$data->returnurl = $returnurl;
}
}
$data->returnurl = urlencode($data->returnurl);
$policies = api::list_current_versions(policy_version::AUDIENCE_GUESTS);
$data->policies = array_values($policies);
$data->haspolicies = !empty($policies);
return $data;
}
}
@@ -0,0 +1,502 @@
<?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\renderer} class.
*
* @package tool_policy
* @category output
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\output;
defined('MOODLE_INTERNAL') || die();
use context_system;
use core\output\notification;
use core\session\manager;
use core_user;
use html_writer;
use moodle_url;
use renderable;
use renderer_base;
use single_button;
use templatable;
use tool_policy\api;
use tool_policy\policy_version;
/**
* Represents a page for showing all the policy documents which a user has to agree to.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class page_agreedocs implements renderable, templatable {
/** @var array $policies List of public policies objects with information about the user acceptance. */
protected $policies = null;
/** @var array List of policy version ids that were displayed to the user to agree with. */
protected $listdocs = null;
/** @var array $agreedocs List of policy identifiers which the user has agreed using the form. */
protected $agreedocs = null;
/** @var array $declinedocs List of policy identifiers that the user declined. */
protected $declinedocs = null;
/** @var string $action Form action to identify when user agreeds policies. */
protected $action = null;
/** @var int User id who wants to accept this page. */
protected $behalfid = null;
/** @var object User who wants to accept this page. */
protected $behalfuser = null;
/** @var boolean True if signup user has agreed to all the policies; false otherwise. */
protected $signupuserpolicyagreed = false;
/** @var array Info or error messages to show. */
protected $messages = [];
/** @var bool This is an existing user (rather than non-loggedin/guest). */
protected $isexistinguser;
/**
* Prepare the page for rendering.
*
* @param array $listdocs List of policy version ids that were displayed to the user to agree with.
* @param array $agreedocs List of policy version ids that the user actually agreed with.
* @param array $declinedocs List of policy version ids that the user declined.
* @param int $behalfid The userid to accept the policy versions as (such as child's id).
* @param string $action Form action to identify when user agreeds policies.
*/
public function __construct(array $listdocs, array $agreedocs = [], array $declinedocs = [], $behalfid = 0, $action = null) {
global $USER;
$realuser = manager::get_realuser();
$this->listdocs = $listdocs;
$this->agreedocs = $agreedocs;
$this->declinedocs = $declinedocs;
$this->action = $action;
$this->isexistinguser = isloggedin() && !isguestuser();
$behalfid = $behalfid ?: $USER->id;
if ($realuser->id != $behalfid) {
$this->behalfuser = core_user::get_user($behalfid, '*', MUST_EXIST);
$this->behalfid = $this->behalfuser->id;
}
$this->policies = api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN);
if (!$this->isexistinguser) {
// During the signup, show compulsory policies only.
foreach ($this->policies as $ix => $policyversion) {
if ($policyversion->optional == policy_version::AGREEMENT_OPTIONAL) {
unset($this->policies[$ix]);
}
}
$this->policies = array_values($this->policies);
}
if (empty($this->behalfid)) {
$userid = $USER->id;
} else {
$userid = $this->behalfid;
}
$this->accept_and_revoke_policies();
$this->prepare_global_page_access($userid);
$this->prepare_user_acceptances($userid);
}
/**
* Accept and revoke the policy versions.
* The capabilities for accepting/revoking policies are checked into the api functions.
*
*/
protected function accept_and_revoke_policies() {
global $USER;
if ($this->isexistinguser) {
// Existing user.
if (!empty($this->action) && confirm_sesskey()) {
// The form has been sent, update policies acceptances.
$lang = current_language();
// Accept / revoke policies.
$acceptversionids = [];
$declineversionids = [];
foreach ($this->policies as $policy) {
if (in_array($policy->id, $this->listdocs)) {
if (in_array($policy->id, $this->agreedocs)) {
$acceptversionids[] = $policy->id;
} else if (in_array($policy->id, $this->declinedocs)) {
$declineversionids[] = $policy->id;
} else {
// If the policy was displayed but not answered, revoke the eventually given acceptance.
api::revoke_acceptance($policy->id, $this->behalfid);
}
}
}
api::accept_policies($acceptversionids, $this->behalfid, null, $lang);
api::decline_policies($declineversionids, $this->behalfid, null, $lang);
// Show a message to let know the user he/she must agree all the policies.
if ((count($acceptversionids) + count($declineversionids)) != count($this->policies)) {
$message = (object) [
'type' => 'error',
'text' => get_string('mustagreetocontinue', 'tool_policy')
];
} else {
$message = (object) [
'type' => 'success',
'text' => get_string('acceptancessavedsucessfully', 'tool_policy')
];
}
$this->messages[] = $message;
} else if (!empty($this->policies) && empty($USER->policyagreed)) {
// Inform users they must agree to all policies before continuing.
$message = (object) [
'type' => 'error',
'text' => get_string('mustagreetocontinue', 'tool_policy')
];
$this->messages[] = $message;
}
} else {
// New user.
if (!empty($this->action) && confirm_sesskey()) {
$currentpolicyversionids = [];
$presignupcache = \cache::make('core', 'presignup');
$acceptances = $presignupcache->get('tool_policy_policyversionidsagreed');
if (!$acceptances) {
$acceptances = [];
}
foreach ($this->policies as $policy) {
$currentpolicyversionids[] = $policy->id;
if (in_array($policy->id, $this->listdocs)) {
if (in_array($policy->id, $this->agreedocs)) {
$acceptances[] = $policy->id;
} else {
$acceptances = array_values(array_diff($acceptances, [$policy->id]));
}
}
}
// If the user has accepted all the policies, add it to the session to let continue with the signup process.
$this->signupuserpolicyagreed = empty(array_diff($currentpolicyversionids, $acceptances));
$presignupcache->set('tool_policy_userpolicyagreed', $this->signupuserpolicyagreed);
$presignupcache->set('tool_policy_policyversionidsagreed', $acceptances);
} else if (empty($this->policies)) {
// There are no policies to agree to. Update the policyagreed value to avoid show empty consent page.
\cache::make('core', 'presignup')->set('tool_policy_userpolicyagreed', 1);
}
if (!empty($this->policies) && !$this->signupuserpolicyagreed) {
// During the signup process, inform users they must agree to all policies before continuing.
$message = (object) [
'type' => 'error',
'text' => get_string('mustagreetocontinue', 'tool_policy')
];
$this->messages[] = $message;
}
}
}
/**
* Before display the consent page, the user has to view all the still-non-accepted policy docs.
* This function checks if the non-accepted policy docs have been shown and redirect to them.
*
* @param int $userid User identifier who wants to access to the consent page.
* @param moodle_url $returnurl URL to return after shown the policy docs.
*/
protected function redirect_to_policies($userid, $returnurl = null) {
// Make a list of all policies that the user has not answered yet.
$allpolicies = $this->policies;
if ($this->isexistinguser) {
$acceptances = api::get_user_acceptances($userid);
foreach ($allpolicies as $ix => $policy) {
$isaccepted = api::is_user_version_accepted($userid, $policy->id, $acceptances);
if ($isaccepted) {
// The user has accepted this policy, do not show it again.
unset($allpolicies[$ix]);
} else if ($isaccepted === false && $policy->optional == policy_version::AGREEMENT_OPTIONAL) {
// The user declined this policy but the agreement was optional, do not show it.
unset($allpolicies[$ix]);
} else {
// The user has not answered the policy yet, or the agreement is compulsory. Show it.
continue;
}
}
} else {
$presignupcache = \cache::make('core', 'presignup');
$acceptances = $presignupcache->get('tool_policy_policyversionidsagreed');
if ($acceptances) {
foreach ($allpolicies as $ix => $policy) {
if (in_array($policy->id, $acceptances)) {
unset($allpolicies[$ix]);
}
}
}
}
if (!empty($allpolicies)) {
// Check if some of the to-be-accepted policies should be agreed on their own page.
foreach ($allpolicies as $policy) {
if ($policy->agreementstyle == policy_version::AGREEMENTSTYLE_OWNPAGE) {
if (empty($returnurl)) {
$returnurl = (new moodle_url('/admin/tool/policy/index.php'))->out_as_local_url(false);
}
$urlparams = ['versionid' => $policy->id, 'returnurl' => $returnurl];
redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
}
}
$currentpolicyversionids = [];
foreach ($allpolicies as $policy) {
$currentpolicyversionids[] = $policy->id;
}
$cache = \cache::make('core', 'presignup');
$cachekey = 'tool_policy_viewedpolicies';
$viewedpolicies = $cache->get($cachekey) ?: [];
if (!empty($viewedpolicies)) {
// Get the list of the policies docs which the user haven't viewed during this session.
$pendingpolicies = array_diff($currentpolicyversionids, $viewedpolicies);
} else {
$pendingpolicies = $currentpolicyversionids;
}
if (count($pendingpolicies) > 0) {
// Still is needed to show some policies docs. Save in the session and redirect.
$policyversionid = array_shift($pendingpolicies);
$viewedpolicies[] = $policyversionid;
$cache->set($cachekey, $viewedpolicies);
if (empty($returnurl)) {
$returnurl = new moodle_url('/admin/tool/policy/index.php');
}
$urlparams = ['versionid' => $policyversionid,
'returnurl' => $returnurl,
'numpolicy' => count($currentpolicyversionids) - count($pendingpolicies),
'totalpolicies' => count($currentpolicyversionids),
];
redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
}
} else {
// Update the policyagreed for the user to avoid infinite loop because there are no policies to-be-accepted.
api::update_policyagreed($userid);
$this->redirect_to_previous_url();
}
}
/**
* Redirect to signup page if defined or to $CFG->wwwroot if not.
*/
protected function redirect_to_previous_url() {
global $SESSION;
if ($this->isexistinguser) {
// Existing user.
if (!empty($SESSION->wantsurl)) {
$returnurl = $SESSION->wantsurl;
unset($SESSION->wantsurl);
} else {
$returnurl = new moodle_url('/admin/tool/policy/user.php');
}
} else {
// Non-authenticated user.
$issignup = \cache::make('core', 'presignup')->get('tool_policy_issignup');
if ($issignup) {
// User came here from signup page - redirect back there.
$returnurl = new moodle_url('/login/signup.php');
\cache::make('core', 'presignup')->set('tool_policy_issignup', false);
} else {
// Guests should not be on this page unless it's part of signup - redirect home.
$returnurl = new moodle_url('/');
}
}
redirect($returnurl);
}
/**
* Sets up the global $PAGE and performs the access checks.
*
* @param int $userid
*/
protected function prepare_global_page_access($userid) {
global $PAGE, $SITE, $USER;
// Guest users or not logged users (but the users during the signup process) are not allowed to access to this page.
$newsignupuser = \cache::make('core', 'presignup')->get('tool_policy_issignup');
if (!$this->isexistinguser && !$newsignupuser) {
$this->redirect_to_previous_url();
}
// Check for correct user capabilities.
if ($this->isexistinguser) {
// For existing users, it's needed to check if they have the capability for accepting policies.
api::can_accept_policies($this->listdocs, $this->behalfid, true);
} else {
// For new users, the behalfid parameter is ignored.
if ($this->behalfid) {
redirect(new moodle_url('/admin/tool/policy/index.php'));
}
}
// If the current user has the $USER->policyagreed = 1 or $userpolicyagreed = 1
// redirect to the return page.
$hasagreedsignupuser = !$this->isexistinguser && $this->signupuserpolicyagreed;
$hasagreedloggeduser = $USER->id == $userid && !empty($USER->policyagreed);
if (!is_siteadmin() && ($hasagreedsignupuser || $hasagreedloggeduser)) {
$this->redirect_to_previous_url();
}
$myparams = [];
if ($this->isexistinguser && !empty($this->behalfid) && $this->behalfid != $USER->id) {
$myparams['userid'] = $this->behalfid;
}
$myurl = new moodle_url('/admin/tool/policy/index.php', $myparams);
// Redirect to policy docs before the consent page.
$this->redirect_to_policies($userid, $myurl);
// Page setup.
$PAGE->set_context(context_system::instance());
$PAGE->set_url($myurl);
$PAGE->set_heading($SITE->fullname);
$PAGE->set_title(get_string('policiesagreements', 'tool_policy'));
$PAGE->navbar->add(get_string('policiesagreements', 'tool_policy'), new moodle_url('/admin/tool/policy/index.php'));
}
/**
* Prepare user acceptances.
*
* @param int $userid
*/
protected function prepare_user_acceptances($userid) {
global $USER;
// Get all the policy version acceptances for this user.
$lang = current_language();
foreach ($this->policies as $policy) {
// Get a link to display the full policy document.
$policy->url = new moodle_url('/admin/tool/policy/view.php',
array('policyid' => $policy->policyid, 'returnurl' => qualified_me()));
$policyattributes = array('data-action' => 'view',
'data-versionid' => $policy->id,
'data-behalfid' => $this->behalfid);
$policymodal = html_writer::link($policy->url, $policy->name, $policyattributes);
// Check if this policy version has been agreed or not.
if ($this->isexistinguser) {
// Existing user.
$versionagreed = false;
$versiondeclined = false;
$acceptances = api::get_user_acceptances($userid);
$policy->versionacceptance = api::get_user_version_acceptance($userid, $policy->id, $acceptances);
if (!empty($policy->versionacceptance)) {
// The policy version has ever been replied to before. Check if status = 1 to know if still is accepted.
if ($policy->versionacceptance->status) {
$versionagreed = true;
} else {
$versiondeclined = true;
}
if ($versionagreed) {
if ($policy->versionacceptance->lang != $lang) {
// Add a message because this version has been accepted in a different language than the current one.
$policy->versionlangsagreed = get_string('policyversionacceptedinotherlang', 'tool_policy');
}
$usermodified = $policy->versionacceptance->usermodified;
if ($usermodified && $usermodified != $userid && $USER->id == $userid) {
// Add a message because this version has been accepted on behalf of current user.
$policy->versionbehalfsagreed = get_string('policyversionacceptedinbehalf', 'tool_policy');
}
}
}
} else {
// New user.
$versionagreed = in_array($policy->id, $this->agreedocs);
$versiondeclined = false;
}
$policy->versionagreed = $versionagreed;
$policy->versiondeclined = $versiondeclined;
$policy->policylink = html_writer::link($policy->url, $policy->name);
$policy->policymodal = $policymodal;
}
}
/**
* 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) {
global $USER;
$myparams = [];
if ($this->isexistinguser && !empty($this->behalfid) && $this->behalfid != $USER->id) {
$myparams['userid'] = $this->behalfid;
}
$data = (object) [
'pluginbaseurl' => (new moodle_url('/admin/tool/policy'))->out(false),
'myurl' => (new moodle_url('/admin/tool/policy/index.php', $myparams))->out(false),
'sesskey' => sesskey(),
];
if (!empty($this->messages)) {
foreach ($this->messages as $message) {
switch ($message->type) {
case 'error':
$data->messages[] = $output->notification($message->text, notification::NOTIFY_ERROR);
break;
case 'success':
$data->messages[] = $output->notification($message->text, notification::NOTIFY_SUCCESS);
break;
default:
$data->messages[] = $output->notification($message->text, notification::NOTIFY_INFO);
break;
}
}
}
// Filter out policies already shown on their own page, keep just policies to be shown here on the consent page.
$data->policies = array_values(array_filter($this->policies, function ($policy) {
return $policy->agreementstyle == policy_version::AGREEMENTSTYLE_CONSENTPAGE;
}));
// If viewing docs in behalf of other user, get his/her full name and profile link.
if (!empty($this->behalfuser)) {
$userfullname = fullname($this->behalfuser, has_capability('moodle/site:viewfullnames', \context_system::instance()) ||
has_capability('moodle/site:viewfullnames', \context_user::instance($this->behalfid)));
$data->behalfuser = html_writer::link(\context_user::instance($this->behalfid)->get_url(), $userfullname);
}
// User can cancel accepting policies only if it is a part of signup.
$data->cancancel = !isloggedin() || isguestuser();
return $data;
}
}
@@ -0,0 +1,264 @@
<?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\page_managedocs_list} class.
*
* @package tool_policy
* @category output
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\output;
use html_writer;
use tool_policy\api;
defined('MOODLE_INTERNAL') || die();
use action_menu;
use action_menu_link;
use moodle_url;
use pix_icon;
use renderable;
use renderer_base;
use single_button;
use templatable;
use tool_policy\policy_version;
/**
* Represents a management page with the list of policy documents.
*
* The page displays all policy documents in their sort order, together with draft future versions.
*
* @copyright 2018 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class page_managedocs_list implements renderable, templatable {
/** @var int */
protected $policyid = null;
/** @var moodle_url */
protected $returnurl = null;
/**
* page_managedocs_list constructor.
* @param int $policyid when specified only archived versions of this policy will be displayed.
*/
public function __construct($policyid = null) {
$this->policyid = $policyid;
$this->returnurl = new moodle_url('/admin/tool/policy/managedocs.php');
if ($this->policyid) {
$this->returnurl->param('archived', $this->policyid);
}
}
/**
* 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->pluginbaseurl = (new moodle_url('/admin/tool/policy'))->out(false);
$data->canmanage = has_capability('tool/policy:managedocs', \context_system::instance());
$data->canaddnew = $data->canmanage && !$this->policyid;
$data->canviewacceptances = has_capability('tool/policy:viewacceptances', \context_system::instance());
$data->title = get_string('policiesagreements', 'tool_policy');
$data->policies = [];
if ($this->policyid) {
// We are only interested in the archived versions of the given policy.
$data->backurl = (new moodle_url('/admin/tool/policy/managedocs.php'))->out(false);
$policy = api::list_policies([$this->policyid], true)[0];
if ($firstversion = $policy->currentversion ?: (reset($policy->draftversions) ?: reset($policy->archivedversions))) {
$data->title = get_string('previousversions', 'tool_policy', format_string($firstversion->name));
}
foreach ($policy->archivedversions as $i => $version) {
$data->versions[] = $this->export_version_for_template($output, $policy, $version,
false, false, false);
}
return $data;
}
// List all policies. Display current and all draft versions of each policy in this list.
// If none found, then show only one archived version.
$policies = api::list_policies(null, true);
foreach ($policies as $i => $policy) {
if (empty($policy->currentversion) && empty($policy->draftversions)) {
// There is no current and no draft versions, display the first archived version.
$firstpolicy = array_shift($policy->archivedversions);
$data->versions[] = $this->export_version_for_template($output, $policy, $firstpolicy,
false, $i > 0, $i < count($policies) - 1);
}
if (!empty($policy->currentversion)) {
// Current version of the policy.
$data->versions[] = $this->export_version_for_template($output, $policy, $policy->currentversion,
false, $i > 0, $i < count($policies) - 1);
} else if ($policy->draftversions) {
// There is no current version, display the first draft version as the current.
$firstpolicy = array_shift($policy->draftversions);
$data->versions[] = $this->export_version_for_template($output, $policy, $firstpolicy,
false, $i > 0, $i < count($policies) - 1);
}
foreach ($policy->draftversions as $draft) {
// Show all [other] draft policies indented.
$data->versions[] = $this->export_version_for_template($output, $policy, $draft,
true, false, false);
}
}
return $data;
}
/**
* Exports one version for the list of policies
*
* @param \renderer_base $output
* @param \stdClass $policy
* @param \stdClass $version
* @param bool $isindented display indented (normally drafts of the current version)
* @param bool $moveup can move up
* @param bool $movedown can move down
* @return \stdClass
*/
protected function export_version_for_template($output, $policy, $version, $isindented, $moveup, $movedown) {
$status = $version->status;
$version->statustext = get_string('status' . $status, 'tool_policy');
if ($status == policy_version::STATUS_ACTIVE) {
$version->statustext = html_writer::span($version->statustext, 'badge bg-success text-white');
} else if ($status == policy_version::STATUS_DRAFT) {
$version->statustext = html_writer::span($version->statustext, 'badge bg-warning text-dark');
} else {
$version->statustext = html_writer::span($version->statustext, 'label');
}
if ($version->optional == policy_version::AGREEMENT_OPTIONAL) {
$version->optionaltext = get_string('policydocoptionalyes', 'tool_policy');
} else {
$version->optionaltext = get_string('policydocoptionalno', 'tool_policy');
}
$version->indented = $isindented;
$editbaseurl = new moodle_url('/admin/tool/policy/editpolicydoc.php', [
'sesskey' => sesskey(),
'policyid' => $policy->id,
'returnurl' => $this->returnurl->out_as_local_url(false),
]);
$viewurl = new moodle_url('/admin/tool/policy/view.php', [
'policyid' => $policy->id,
'versionid' => $version->id,
'manage' => 1,
'returnurl' => $this->returnurl->out_as_local_url(false),
]);
$actionmenu = new action_menu();
$actionmenu->set_menu_trigger(get_string('actions', 'tool_policy'));
$actionmenu->prioritise = true;
if ($moveup) {
$actionmenu->add(new action_menu_link(
new moodle_url($editbaseurl, ['moveup' => $policy->id]),
new pix_icon('t/up', get_string('moveup', 'tool_policy')),
get_string('moveup', 'tool_policy'),
true
));
}
if ($movedown) {
$actionmenu->add(new action_menu_link(
new moodle_url($editbaseurl, ['movedown' => $policy->id]),
new pix_icon('t/down', get_string('movedown', 'tool_policy')),
get_string('movedown', 'tool_policy'),
true
));
}
$actionmenu->add(new action_menu_link(
$viewurl,
null,
get_string('view'),
false
));
if ($status != policy_version::STATUS_ARCHIVED) {
$actionmenu->add(new action_menu_link(
new moodle_url($editbaseurl, ['versionid' => $version->id]),
null,
get_string('edit'),
false
));
}
if ($status == policy_version::STATUS_ACTIVE) {
$actionmenu->add(new action_menu_link(
new moodle_url($editbaseurl, ['inactivate' => $policy->id]),
null,
get_string('inactivate', 'tool_policy'),
false,
['data-action' => 'inactivate']
));
}
if ($status == policy_version::STATUS_DRAFT) {
$actionmenu->add(new action_menu_link(
new moodle_url($editbaseurl, ['makecurrent' => $version->id]),
null,
get_string('activate', 'tool_policy'),
false,
['data-action' => 'makecurrent']
));
}
if (api::can_delete_version($version)) {
$actionmenu->add(new action_menu_link(
new moodle_url($editbaseurl, ['delete' => $version->id]),
null,
get_string('delete'),
false,
['data-action' => 'delete']
));
}
if ($status == policy_version::STATUS_ARCHIVED) {
$actionmenu->add(new action_menu_link(
new moodle_url($editbaseurl, ['versionid' => $version->id]),
null,
get_string('settodraft', 'tool_policy'),
false
));
}
if (!$this->policyid && !$isindented && $policy->archivedversions &&
($status != policy_version::STATUS_ARCHIVED || count($policy->archivedversions) > 1)) {
$actionmenu->add(new action_menu_link(
new moodle_url('/admin/tool/policy/managedocs.php', ['archived' => $policy->id]),
null,
get_string('viewarchived', 'tool_policy'),
false
));
}
$version->actionmenu = $actionmenu->export_for_template($output);
return $version;
}
}
@@ -0,0 +1,172 @@
<?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\renderer} class.
*
* @package tool_policy
* @category output
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\output;
use core\session\manager;
use moodle_exception;
defined('MOODLE_INTERNAL') || die();
use context_system;
use core_user;
use html_writer;
use moodle_url;
use renderable;
use renderer_base;
use templatable;
use tool_policy\api;
use tool_policy\policy_version;
/**
* Represents a page for showing the error messages.
*
* This is used when a user has no permission to agree to policies or accept policies on behalf of defined behalfid.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class page_nopermission implements renderable, templatable {
/** @var int User id who wants to view this page. */
protected $behalfid = null;
/** @var object User who wants to accept this page. */
protected $behalfuser = null;
/** @var bool True if user has permission to accept policy documents; false otherwise. */
protected $haspermissionagreedocs = true;
/** @var array $policies List of public policies objects. */
protected $policies = null;
/**
* Prepare the page for rendering.
*
* @param array $versionids int[] List of policy version ids that were checked.
* @param int $behalfid The userid to consent policies as (such as child's id).
*/
public function __construct(array $versionids, $behalfid) {
global $USER;
$behalfid = $behalfid ?: $USER->id;
$realuser = manager::get_realuser();
if ($realuser->id != $behalfid) {
$this->behalfuser = core_user::get_user($behalfid, '*', MUST_EXIST);
$this->behalfid = $this->behalfuser->id;
}
if (!empty($USER->id)) {
// For existing users, it's needed to check if they have the capability for accepting policies.
$this->haspermissionagreedocs = api::can_accept_policies($versionids, $this->behalfid);
}
$this->policies = api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN);
if (empty($this->policies) && !empty($USER->id)) {
// Existing user without policies to agree to.
$currentuser = (!empty($this->behalfuser)) ? $this->behalfuser : $USER;
if (!$currentuser->policyagreed) {
// If there are no policies to agreed, change $user->policyagreed to true.
api::update_policyagreed($currentuser);
}
}
$this->prepare_global_page_access();
}
/**
* Sets up the global $PAGE and performs the access checks.
*/
protected function prepare_global_page_access() {
global $PAGE, $SITE, $USER;
$myurl = new moodle_url('/admin/tool/policy/index.php', [
'behalfid' => $this->behalfid,
]);
if (isguestuser() || empty($USER->id) || !$USER->policyagreed) {
// Disable notifications for new users, guests or users who haven't agreed to the policies.
$PAGE->set_popup_notification_allowed(false);
}
$PAGE->set_context(context_system::instance());
$PAGE->set_pagelayout('standard');
$PAGE->set_url($myurl);
$PAGE->set_heading($SITE->fullname);
$PAGE->set_title(get_string('policiesagreements', 'tool_policy'));
$PAGE->navbar->add(get_string('policiesagreements', 'tool_policy'), new moodle_url('/admin/tool/policy/index.php'));
}
/**
* 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) {
global $OUTPUT;
$data = (object) [
'pluginbaseurl' => (new moodle_url('/admin/tool/policy'))->out(false),
'haspermissionagreedocs' => $this->haspermissionagreedocs,
'supportemail' => $OUTPUT->supportemail(['class' => 'font-weight-bold'])
];
// Get the messages to display.
$messagetitle = null;
$messagedesc = null;
if (!$this->haspermissionagreedocs) {
if (!empty($this->behalfuser)) {
// If viewing docs in behalf of other user, get his/her full name and profile link.
$userfullname = fullname($this->behalfuser, has_capability('moodle/site:viewfullnames', \context_system::instance())
|| has_capability('moodle/site:viewfullnames', \context_user::instance($this->behalfid)));
$data->behalfuser = html_writer::link(\context_user::instance($this->behalfid)->get_url(), $userfullname);
$messagetitle = get_string('nopermissiontoagreedocsbehalf', 'tool_policy');
$messagedesc = get_string('nopermissiontoagreedocsbehalf_desc', 'tool_policy', $data->behalfuser);
} else {
$messagetitle = get_string('nopermissiontoagreedocs', 'tool_policy');
$messagedesc = get_string('nopermissiontoagreedocs_desc', 'tool_policy');
}
}
$data->messagetitle = $messagetitle;
$data->messagedesc = $messagedesc;
// Add policies list.
$policieslinks = array();
foreach ($this->policies as $policyversion) {
// Get a link to display the full policy document.
$policyurl = new moodle_url('/admin/tool/policy/view.php',
array('policyid' => $policyversion->policyid, 'returnurl' => qualified_me()));
$policyattributes = array('data-action' => 'view',
'data-versionid' => $policyversion->id,
'data-behalfid' => $this->behalfid);
$policieslinks[] = html_writer::link($policyurl, $policyversion->name, $policyattributes);
}
$data->policies = $policieslinks;
return $data;
}
}
@@ -0,0 +1,122 @@
<?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\renderer} class.
*
* @package tool_policy
* @category output
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\output;
use moodle_exception;
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->libdir/filelib.php");
use context_system;
use moodle_url;
use renderable;
use renderer_base;
use single_button;
use templatable;
use tool_policy\api;
use tool_policy\policy_version;
/**
* Represents a page for showing all the policy documents with a current version.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class page_viewalldoc implements renderable, templatable {
/** @var ?moodle_url Return url */
private $returnurl = null;
/** @var array List current (active) policy versions. */
private array $policies = [];
/**
* Prepare the page for rendering.
*
*/
public function __construct($returnurl) {
if (!empty($returnurl)) {
$this->returnurl = new moodle_url($returnurl);
}
$this->prepare_global_page_access();
$this->prepare_policies();
}
/**
* Loads the policy versions to display on the page.
*
*/
protected function prepare_policies() {
$this->policies = api::list_current_versions();
}
/**
* Sets up the global $PAGE and performs the access checks.
*/
protected function prepare_global_page_access() {
global $PAGE, $SITE, $USER;
$myurl = new moodle_url('/admin/tool/policy/viewall.php', []);
// Disable notifications for new users, guests or users who haven't agreed to the policies.
if (isguestuser() || empty($USER->id) || !$USER->policyagreed) {
$PAGE->set_popup_notification_allowed(false);
}
$PAGE->set_context(context_system::instance());
$PAGE->set_pagelayout('popup');
$PAGE->set_url($myurl);
$PAGE->set_heading($SITE->fullname);
$PAGE->set_title(get_string('policiesagreements', 'tool_policy'));
}
/**
* 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) [
'pluginbaseurl' => (new moodle_url('/admin/tool/policy'))->out(false),
];
$data->policies = array_values($this->policies);
if (!empty($this->returnurl)) {
$data->returnurl = $this->returnurl;
}
array_walk($data->policies, function($item, $key) {
$item->policytypestr = get_string('policydoctype'.$item->type, 'tool_policy');
$item->policyaudiencestr = get_string('policydocaudience'.$item->audience, 'tool_policy');
});
return $data;
}
}
@@ -0,0 +1,200 @@
<?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\renderer} class.
*
* @package tool_policy
* @category output
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\output;
use moodle_exception;
defined('MOODLE_INTERNAL') || die();
use context_system;
use moodle_url;
use renderable;
use renderer_base;
use single_button;
use templatable;
use tool_policy\api;
use tool_policy\policy_version;
/**
* Represents a page for showing the given policy document version.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class page_viewdoc implements renderable, templatable {
/** @var stdClass Exported {@link \tool_policy\policy_version_exporter} to display on this page. */
protected $policy;
/** @var string Return URL. */
protected $returnurl = null;
/** @var int User id who wants to view this page. */
protected $behalfid = null;
/** @var bool View the policy as a part of the management UI. */
protected $manage;
/** @var int Position of the current policy with respect to the total of policy docs to display. */
protected $numpolicy = 0;
/** @var int Total number of policy documents which the user has to agree to. */
protected $totalpolicies = 0;
/**
* Prepare the page for rendering.
*
* @param int $policyid The policy id for this page.
* @param int $versionid The version id to show. Empty tries to load the current one.
* @param string $returnurl URL of a page to continue after reading the policy text.
* @param int $behalfid The userid to view this policy version as (such as child's id).
* @param bool $manage View the policy as a part of the management UI.
* @param int $numpolicy Position of the current policy with respect to the total of policy docs to display.
* @param int $totalpolicies Total number of policy documents which the user has to agree to.
*/
public function __construct($policyid, $versionid, $returnurl, $behalfid, $manage, $numpolicy = 0, $totalpolicies = 0) {
$this->returnurl = $returnurl;
$this->behalfid = $behalfid;
$this->manage = $manage;
$this->numpolicy = $numpolicy;
$this->totalpolicies = $totalpolicies;
$this->prepare_policy($policyid, $versionid);
$this->prepare_global_page_access();
}
/**
* Loads the policy version to display on the page.
*
* @param int $policyid The policy id for this page.
* @param int $versionid The version id to show. Empty tries to load the current one.
*/
protected function prepare_policy($policyid, $versionid) {
if ($versionid) {
$this->policy = api::get_policy_version($versionid);
} else {
$this->policy = array_reduce(api::list_current_versions(), function ($carry, $current) use ($policyid) {
if ($current->policyid == $policyid) {
return $current;
}
return $carry;
});
}
if (empty($this->policy)) {
throw new \moodle_exception('errorpolicyversionnotfound', 'tool_policy');
}
}
/**
* Sets up the global $PAGE and performs the access checks.
*/
protected function prepare_global_page_access() {
global $CFG, $PAGE, $SITE, $USER;
$myurl = new moodle_url('/admin/tool/policy/view.php', [
'policyid' => $this->policy->policyid,
'versionid' => $this->policy->id,
'returnurl' => $this->returnurl,
'behalfid' => $this->behalfid,
'manage' => $this->manage,
'numpolicy' => $this->numpolicy,
'totalpolicies' => $this->totalpolicies,
]);
if ($this->manage) {
require_once($CFG->libdir.'/adminlib.php');
admin_externalpage_setup('tool_policy_managedocs', '', null, $myurl);
require_capability('tool/policy:managedocs', context_system::instance());
$PAGE->navbar->add(format_string($this->policy->name),
new moodle_url('/admin/tool/policy/managedocs.php', ['id' => $this->policy->policyid]));
} else {
if ($this->policy->status != policy_version::STATUS_ACTIVE) {
require_login();
} else if (isguestuser() || empty($USER->id) || !$USER->policyagreed) {
// Disable notifications for new users, guests or users who haven't agreed to the policies.
$PAGE->set_popup_notification_allowed(false);
}
$PAGE->set_url($myurl);
$PAGE->set_heading($SITE->fullname);
$PAGE->set_title(get_string('policiesagreements', 'tool_policy'));
$PAGE->navbar->add(get_string('policiesagreements', 'tool_policy'), new moodle_url('/admin/tool/policy/index.php'));
$PAGE->navbar->add(format_string($this->policy->name));
}
if (!api::can_user_view_policy_version($this->policy, $this->behalfid)) {
throw new moodle_exception('accessdenied', 'tool_policy');
}
}
/**
* 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) {
global $USER;
$data = (object) [
'pluginbaseurl' => (new moodle_url('/admin/tool/policy'))->out(false),
'returnurl' => $this->returnurl ? (new moodle_url($this->returnurl))->out(false) : null,
'numpolicy' => $this->numpolicy ? : null,
'totalpolicies' => $this->totalpolicies ? : null,
];
if ($this->manage && $this->policy->status != policy_version::STATUS_ARCHIVED) {
$paramsurl = ['policyid' => $this->policy->policyid, 'versionid' => $this->policy->id];
$data->editurl = (new moodle_url('/admin/tool/policy/editpolicydoc.php', $paramsurl))->out(false);
}
if ($this->policy->agreementstyle == policy_version::AGREEMENTSTYLE_OWNPAGE) {
if (!api::is_user_version_accepted($USER->id, $this->policy->id)) {
unset($data->returnurl);
$data->accepturl = (new moodle_url('/admin/tool/policy/index.php', [
'listdoc[]' => $this->policy->id,
'status'.$this->policy->id => 1,
'submit' => 'accept',
'sesskey' => sesskey(),
]))->out(false);
if ($this->policy->optional == policy_version::AGREEMENT_OPTIONAL) {
$data->declineurl = (new moodle_url('/admin/tool/policy/index.php', [
'listdoc[]' => $this->policy->id,
'status'.$this->policy->id => 0,
'submit' => 'decline',
'sesskey' => sesskey(),
]))->out(false);
}
}
}
$data->policy = clone($this->policy);
return $data;
}
}
@@ -0,0 +1,71 @@
<?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\renderer} class.
*
* @package tool_policy
* @category output
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\output;
defined('MOODLE_INTERNAL') || die();
use core\output\mustache_template_finder;
use plugin_renderer_base;
use renderable;
use Exception;
/**
* Renderer for the policies plugin.
*
* @copyright 2018 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Overrides the parent so that templatable widgets are handled even without their explicit render method.
*
* @param renderable $widget
* @return string
*/
public function render(renderable $widget) {
$namespacedclassname = get_class($widget);
$plainclassname = preg_replace('/^.*\\\/', '', $namespacedclassname);
$rendermethod = 'render_'.$plainclassname;
if (method_exists($this, $rendermethod)) {
// Explicit rendering method exists, fall back to the default behaviour.
return parent::render($widget);
}
$interfaces = class_implements($namespacedclassname);
if (isset($interfaces['templatable'])) {
// Default implementation of template-based rendering.
$data = $widget->export_for_template($this);
return parent::render_from_template('tool_policy/'.$plainclassname, $data);
} else {
return parent::render($widget);
}
}
}
@@ -0,0 +1,345 @@
<?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 array */
protected $declined;
/** @var bool */
protected $canaccept;
/** @var bool */
protected $canrevoke;
/**
* user_agreement constructor
*
* @param int $userid
* @param array $accepted list of ids of accepted versions
* @param array $declined list of ids of declined versions
* @param moodle_url $pageurl
* @param array $versions list of versions (id=>name)
* @param bool $onbehalf whether at least one version was accepted by somebody else on behalf of the user
* @param bool $canaccept does the current user have permission to accept/decline the policy on behalf of user $userid
* @param bool $canrevoke does the current user have permission to revoke the policy on behalf of user $userid
*/
public function __construct($userid, array $accepted, array $declined, moodle_url $pageurl, $versions, $onbehalf = false,
$canaccept = null, $canrevoke = null) {
// Make sure that all ids in $accepted and $declined are present in $versions.
if (array_diff(array_merge($accepted, $declined), array_keys($versions))) {
throw new \coding_exception('Policy version ids mismatch');
}
$this->userid = $userid;
$this->onbehalf = $onbehalf;
$this->pageurl = $pageurl;
$this->versions = $versions;
$this->accepted = $accepted;
$this->declined = $declined;
$this->canaccept = $canaccept;
if (count($this->accepted) < count($this->versions) && $canaccept === null) {
$this->canaccept = \tool_policy\api::can_accept_policies(array_keys($this->versions), $this->userid);
}
if (count($this->accepted) > 0 && $canrevoke === null) {
$this->canrevoke = \tool_policy\api::can_revoke_policies(array_keys($this->versions), $this->userid);
}
}
/**
* Export data to be rendered.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(\renderer_base $output) {
$data = (object)[
'statusicon' => '',
'statustext' => '',
'statuslink' => '',
'actions' => [],
];
if (count($this->versions) == 1) {
// We represent one particular policy's agreement status.
$versionname = reset($this->versions);
$versionid = key($this->versions);
$actionaccept = (object)[
'text' => get_string('useracceptanceactionaccept', 'tool_policy'),
'title' => get_string('useracceptanceactionacceptone', 'tool_policy', $versionname),
'data' => 'acceptmodal',
'url' => (new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'versionids[]' => $versionid,
'action' => 'accept',
'returnurl' => $this->pageurl->out_as_local_url(false),
]))->out(false),
];
$actionrevoke = (object)[
'text' => get_string('useracceptanceactionrevoke', 'tool_policy'),
'title' => get_string('useracceptanceactionrevokeone', 'tool_policy', $versionname),
'data' => 'acceptmodal',
'url' => (new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'versionids[]' => $versionid,
'action' => 'revoke',
'returnurl' => $this->pageurl->out_as_local_url(false),
]))->out(false),
];
$actiondecline = (object)[
'text' => get_string('useracceptanceactiondecline', 'tool_policy'),
'title' => get_string('useracceptanceactiondeclineone', 'tool_policy', $versionname),
'data' => 'acceptmodal',
'url' => (new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'versionids[]' => $versionid,
'action' => 'decline',
'returnurl' => $this->pageurl->out_as_local_url(false),
]))->out(false),
];
if ($this->accepted) {
$data->statusicon = 'agreed';
if ($this->onbehalf) {
$data->statustext = get_string('acceptancestatusacceptedbehalf', 'tool_policy');
} else {
$data->statustext = get_string('acceptancestatusaccepted', 'tool_policy');
}
if ($this->canrevoke) {
$data->actions[] = $actionrevoke;
}
} else if ($this->declined) {
$data->statusicon = 'declined';
if ($this->onbehalf) {
$data->statustext = get_string('acceptancestatusdeclinedbehalf', 'tool_policy');
} else {
$data->statustext = get_string('acceptancestatusdeclined', 'tool_policy');
}
if ($this->canaccept) {
$data->actions[] = $actionaccept;
}
} else {
$data->statusicon = 'pending';
$data->statustext = get_string('acceptancestatuspending', 'tool_policy');
if ($this->canaccept) {
$data->actions[] = $actionaccept;
$data->actions[] = $actiondecline;
}
}
} else if (count($this->versions) > 1) {
// We represent the summary status for multiple policies.
$data->actions[] = (object)[
'text' => get_string('useracceptanceactiondetails', 'tool_policy'),
'url' => (new \moodle_url('/admin/tool/policy/user.php', [
'userid' => $this->userid,
'returnurl' => $this->pageurl->out_as_local_url(false),
]))->out(false),
];
// Prepare the action link to accept all pending policies.
$accepturl = new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'action' => 'accept',
'returnurl' => $this->pageurl->out_as_local_url(false),
]);
foreach (array_diff(array_keys($this->versions), $this->accepted, $this->declined) as $ix => $versionid) {
$accepturl->param('versionids['.$ix.']', $versionid);
}
$actionaccept = (object)[
'text' => get_string('useracceptanceactionaccept', 'tool_policy'),
'title' => get_string('useracceptanceactionacceptpending', 'tool_policy'),
'data' => 'acceptmodal',
'url' => $accepturl->out(false),
];
// Prepare the action link to revoke all agreed policies.
$revokeurl = new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'action' => 'revoke',
'returnurl' => $this->pageurl->out_as_local_url(false),
]);
foreach ($this->accepted as $ix => $versionid) {
$revokeurl->param('versionids['.$ix.']', $versionid);
}
$actionrevoke = (object)[
'text' => get_string('useracceptanceactionrevoke', 'tool_policy'),
'title' => get_string('useracceptanceactionrevokeall', 'tool_policy'),
'data' => 'acceptmodal',
'url' => $revokeurl->out(false),
];
// Prepare the action link to decline all pending policies.
$declineurl = new \moodle_url('/admin/tool/policy/accept.php', [
'userids[]' => $this->userid,
'action' => 'decline',
'returnurl' => $this->pageurl->out_as_local_url(false),
]);
foreach (array_diff(array_keys($this->versions), $this->accepted, $this->declined) as $ix => $versionid) {
$declineurl->param('versionids['.$ix.']', $versionid);
}
$actiondecline = (object)[
'text' => get_string('useracceptanceactiondecline', 'tool_policy'),
'title' => get_string('useracceptanceactiondeclinepending', 'tool_policy'),
'data' => 'acceptmodal',
'url' => $declineurl->out(false),
];
$countversions = count($this->versions);
$countaccepted = count($this->accepted);
$countdeclined = count($this->declined);
if ($countaccepted == $countversions) {
// All policies accepted.
$data->statusicon = 'agreed';
$data->statustext = get_string('acceptancestatusaccepted', 'tool_policy');
if ($this->canrevoke) {
$data->actions[] = $actionrevoke;
}
} else if ($countdeclined == $countversions) {
// All policies declined.
$data->statusicon = 'declined';
$data->statustext = get_string('acceptancestatusdeclined', 'tool_policy');
} else if ($countaccepted + $countdeclined == $countversions) {
// All policies responded, only some of them accepted.
$data->statusicon = 'partial';
$data->statustext = get_string('acceptancestatuspartial', 'tool_policy');
if ($this->accepted && $this->canrevoke) {
$data->actions[] = $actionrevoke;
}
} else {
// Some policies are pending.
$data->statusicon = 'pending';
$data->statustext = get_string('acceptancestatuspending', 'tool_policy');
if ($this->canaccept) {
$data->actions[] = $actionaccept;
$data->actions[] = $actiondecline;
}
}
}
return $data;
}
/**
* Describe the status with a plain text for downloading purposes.
*
* @return string
*/
public function export_for_download() {
if (count($this->versions) == 1) {
if ($this->accepted) {
if ($this->onbehalf) {
return get_string('acceptancestatusacceptedbehalf', 'tool_policy');
} else {
return get_string('acceptancestatusaccepted', 'tool_policy');
}
} else if ($this->declined) {
if ($this->onbehalf) {
return get_string('acceptancestatusdeclinedbehalf', 'tool_policy');
} else {
return get_string('acceptancestatusdeclined', 'tool_policy');
}
} else {
return get_string('acceptancestatuspending', 'tool_policy');
}
} else if (count($this->versions) > 1) {
if (count($this->accepted) == count($this->versions)) {
return get_string('acceptancestatusaccepted', 'tool_policy');
} else if (count($this->declined) == count($this->versions)) {
return get_string('acceptancestatusdeclined', 'tool_policy');
} else if (count($this->accepted) > 0 || count($this->declined) > 0) {
return get_string('acceptancestatuspartial', 'tool_policy');
} else {
return get_string('acceptancestatuspending', 'tool_policy');
}
}
}
}
@@ -0,0 +1,124 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link tool_policy\policy_exporter} class.
*
* @package tool_policy
* @copyright 2018 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
/**
* Exporter of a policy document model.
*
* @copyright 2018 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class policy_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'id' => [
'type' => PARAM_INT,
],
'sortorder' => [
'type' => PARAM_INT,
'default' => 999,
],
'currentversionid' => [
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'versions' => 'tool_policy\policy_version_exporter[]',
];
}
/**
* Return the list of additional, generated dynamically from the given properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'currentversion' => [
'type' => policy_version_exporter::read_properties_definition(),
'null' => NULL_ALLOWED,
],
'draftversions' => [
'type' => policy_version_exporter::read_properties_definition(),
'multiple' => true,
],
'archivedversions' => [
'type' => policy_version_exporter::read_properties_definition(),
'multiple' => true,
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$othervalues = [
'currentversion' => null,
'draftversions' => [],
'archivedversions' => [],
];
foreach ($this->related['versions'] as $exporter) {
$data = $exporter->export($output);
if ($data->id == $this->data->currentversionid) {
$othervalues['currentversion'] = $data;
} else if ($data->archived) {
$othervalues['archivedversions'][] = $data;
} else {
$othervalues['draftversions'][] = $data;
}
}
return $othervalues;
}
}
@@ -0,0 +1,193 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link tool_policy\policy_version} persistent.
*
* @package tool_policy
* @copyright 2018 Sara Arjona (sara@moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy;
defined('MOODLE_INTERNAL') || die();
use core\persistent;
/**
* Persistent model representing a single policy document version.
*
* @copyright 2018 Sara Arjona (sara@moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class policy_version extends persistent {
/** @var string Table name this persistent is mapped to. */
const TABLE = 'tool_policy_versions';
/** @var int Site policy document. */
const TYPE_SITE = 0;
/** @var int Privacy policy document. */
const TYPE_PRIVACY = 1;
/** @var int Third party policy document. */
const TYPE_THIRD_PARTY = 2;
/** @var int Other policy document. */
const TYPE_OTHER = 99;
/** @var int Policy applies to all users. */
const AUDIENCE_ALL = 0;
/** @var int Policy applies to logged in users only. */
const AUDIENCE_LOGGEDIN = 1;
/** @var int Policy applies to guests only. */
const AUDIENCE_GUESTS = 2;
/** @var int Policy version is a draft. */
const STATUS_DRAFT = 0;
/** @var int Policy version is the active one. */
const STATUS_ACTIVE = 1;
/** @var int Policy version has been archived. */
const STATUS_ARCHIVED = 2;
/** @var int Policy to be accepted together with others on the consent page. */
const AGREEMENTSTYLE_CONSENTPAGE = 0;
/** @var int Policy to be accepted on its own page before reaching the consent page. */
const AGREEMENTSTYLE_OWNPAGE = 1;
/** @var int Users must agree to the policy in order to use the site. */
const AGREEMENT_COMPULSORY = 0;
/** @var int Users may or may not agree to the policy. */
const AGREEMENT_OPTIONAL = 1;
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return [
'name' => [
'type' => PARAM_TEXT,
'default' => '',
],
'type' => [
'type' => PARAM_INT,
'choices' => [
self::TYPE_SITE,
self::TYPE_PRIVACY,
self::TYPE_THIRD_PARTY,
self::TYPE_OTHER,
],
'default' => self::TYPE_SITE,
],
'audience' => [
'type' => PARAM_INT,
'choices' => [
self::AUDIENCE_ALL,
self::AUDIENCE_LOGGEDIN,
self::AUDIENCE_GUESTS,
],
'default' => self::AUDIENCE_ALL,
],
'archived' => [
'type' => PARAM_BOOL,
'default' => false,
],
'policyid' => [
'type' => PARAM_INT,
],
'agreementstyle' => [
'type' => PARAM_INT,
'choices' => [
self::AGREEMENTSTYLE_CONSENTPAGE,
self::AGREEMENTSTYLE_OWNPAGE,
],
'default' => self::AGREEMENTSTYLE_CONSENTPAGE,
],
'optional' => [
'type' => PARAM_INT,
'choices' => [
self::AGREEMENT_OPTIONAL,
self::AGREEMENT_COMPULSORY,
],
'default' => self::AGREEMENT_COMPULSORY,
],
'revision' => [
'type' => PARAM_TEXT,
'default' => '',
],
'summary' => [
'type' => PARAM_RAW,
'default' => '',
],
'summaryformat' => [
'type' => PARAM_INT,
'default' => FORMAT_HTML,
'choices' => [
FORMAT_PLAIN,
FORMAT_HTML,
FORMAT_MOODLE,
FORMAT_MARKDOWN,
],
],
'content' => [
'type' => PARAM_RAW,
'default' => '',
],
'contentformat' => [
'type' => PARAM_INT,
'default' => FORMAT_HTML,
'choices' => [
FORMAT_PLAIN,
FORMAT_HTML,
FORMAT_MOODLE,
FORMAT_MARKDOWN,
],
],
];
}
/**
* Hook to execute after an update.
*
* @param bool $result Whether or not the update was successful (but it always is)
*/
protected function after_update($result) {
$optcache = \cache::make('tool_policy', 'policy_optional');
$optcache->delete($this->raw_get('id'));
}
/**
* Hook to execute after an update.
*
* @param bool $result Whether or not the update was successful (but it always is)
*/
protected function after_delete($result) {
$optcache = \cache::make('tool_policy', 'policy_optional');
$optcache->delete($this->raw_get('id'));
}
}
@@ -0,0 +1,163 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link tool_policy\policy_version_exporter} class.
*
* @package tool_policy
* @copyright 2018 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
use tool_policy\api;
/**
* Exporter of a single policy document version.
*
* Note we cannot use the persistent_exporter as our super class because we want to add some properties not present in
* the persistent (e.g. acceptancescount).
*
* @copyright 2018 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class policy_version_exporter extends exporter {
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return policy_version::properties_definition() + [
'acceptancescount' => [
'type' => PARAM_INT,
'default' => 0,
],
'status' => [
'type' => PARAM_INT,
],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'context' => 'context',
];
}
/**
* Return the list of additional (calculated and readonly) properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
// Human readable type of the policy document version.
'typetext' => [
'type' => PARAM_TEXT,
],
// Human readable audience of the policy document audience.
'audiencetext' => [
'type' => PARAM_TEXT,
],
// Detailed information about the number of policy acceptances.
'acceptancescounttext' => [
'type' => PARAM_TEXT,
],
// Link to view acceptances.
'acceptancescounturl' => [
'type' => PARAM_LOCALURL,
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$othervalues = [
'typetext' => get_string('policydoctype'.$this->data->type, 'tool_policy'),
'audiencetext' => get_string('policydocaudience'.$this->data->audience, 'tool_policy'),
];
if (!isset($this->data->acceptancescount) || $this->data->status == policy_version::STATUS_DRAFT) {
// Return "N/A" for acceptances count.
$othervalues['acceptancescounttext'] = get_string('useracceptancecountna', 'tool_policy');
$othervalues['acceptancescounturl'] = null;
return $othervalues;
}
$acceptancescount = empty($this->data->acceptancescount) ? 0 : $this->data->acceptancescount;
$acceptancesexpected = api::count_total_users();
$a = [
'agreedcount' => $acceptancescount,
'userscount' => $acceptancesexpected,
'percent' => min(100, round($acceptancescount * 100 / max($acceptancesexpected, 1))),
];
$othervalues['acceptancescounttext'] = get_string('useracceptancecount', 'tool_policy', $a);
$acceptancesurl = new \moodle_url('/admin/tool/policy/acceptances.php', ['policyid' => $this->data->policyid]);
if ($this->data->status != policy_version::STATUS_ACTIVE) {
$acceptancesurl->param('versionid', $this->data->id);
}
$othervalues['acceptancescounturl'] = $acceptancesurl->out(false);
return $othervalues;
}
/**
* Get the formatting parameters for the summary field.
*
* @return array
*/
protected function get_format_parameters_for_summary() {
return [
'component' => 'tool_policy',
'filearea' => 'policydocumentsummary',
'itemid' => $this->data->id
];
}
/**
* Get the formatting parameters for the content field.
*
* @return array
*/
protected function get_format_parameters_for_content() {
return [
'component' => 'tool_policy',
'filearea' => 'policydocumentcontent',
'itemid' => $this->data->id
];
}
}
@@ -0,0 +1,129 @@
<?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/>.
/**
* Site policy handler class.
*
* @package tool_policy
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\privacy\local\sitepolicy;
defined('MOODLE_INTERNAL') || die();
use tool_policy\api;
use tool_policy\policy_version;
/**
* Class implementation for a site policy handler.
*
* @package tool_policy
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class handler extends \core_privacy\local\sitepolicy\handler {
/**
* Returns URL to redirect user to when user needs to agree to site policy
*
* This is a regular interactive page for web users. It should have normal Moodle header/footers, it should
* allow user to view policies and accept them.
*
* @param bool $forguests
* @return moodle_url|null (returns null if site policy is not defined)
*/
public static function get_redirect_url($forguests = false) {
// There is no redirect for guests, policies are shown in the popup, only return redirect url for the logged in users.
if (!$forguests && api::get_current_versions_ids(policy_version::AUDIENCE_LOGGEDIN)) {
return new \moodle_url('/admin/tool/policy/index.php');
}
return null;
}
/**
* Returns URL of the site policy that needs to be displayed to the user (inside iframe or to use in WS such as mobile app)
*
* This page should not have any header/footer, it does not also have any buttons/checkboxes. The caller needs to implement
* the "Accept" button and call {@link self::accept()} on completion.
*
* @param bool $forguests
* @return moodle_url|null
*/
public static function get_embed_url($forguests = false) {
if (api::get_current_versions_ids($forguests ? policy_version::AUDIENCE_GUESTS : policy_version::AUDIENCE_LOGGEDIN)) {
return new \moodle_url('/admin/tool/policy/viewall.php');
}
return null;
}
/**
* Accept site policy for the current user
*
* @return bool - false if sitepolicy not defined, user is not logged in or user has already agreed to site policy;
* true - if we have successfully marked the user as agreed to the site policy
*/
public static function accept() {
global $USER, $DB;
if (!isloggedin()) {
return false;
}
if ($USER->policyagreed) {
return false;
}
if (isguestuser()) {
// For guests, agreement is stored in the session only.
$USER->policyagreed = 1;
return true;
}
// Find all compulsory policies and mark them as accepted.
$compulsory = [];
foreach (api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN) as $policyversion) {
if ($policyversion->optional == policy_version::AGREEMENT_COMPULSORY) {
$compulsory[] = $policyversion->id;
}
}
if ($compulsory) {
api::accept_policies($compulsory);
}
// Mark policies as agreed.
$DB->set_field('user', 'policyagreed', 1, array('id' => $USER->id));
$USER->policyagreed = 1;
return true;
}
/**
* Adds "Agree to site policy" checkbox to the signup form.
*
* @param \MoodleQuickForm $mform
*/
public static function signup_form($mform) {
if (static::is_defined()) {
// This plugin displays policies to the user who is signing up before the signup form is shown.
// By the time user has access to signup form they have already agreed to all compulsory policies.
$mform->addElement('hidden', 'policyagreed', 1);
$mform->setType('policyagreed', PARAM_INT);
}
}
}
@@ -0,0 +1,356 @@
<?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/>.
/**
* Privacy Subsystem implementation for tool_policy.
*
* @package tool_policy
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\moodle_content_writer;
use core_privacy\local\request\userlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
/**
* Implementation of the privacy subsystem plugin provider for the policy tool.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// This tool stores user data.
\core_privacy\local\metadata\provider,
// This plugin is capable of determining which users have data within it.
\core_privacy\local\request\core_userlist_provider,
// This tool may provide access to and deletion of user data.
\core_privacy\local\request\plugin\provider {
/**
* Return the fields which contain personal data.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table(
'tool_policy_acceptances',
[
'policyversionid' => 'privacy:metadata:acceptances:policyversionid',
'userid' => 'privacy:metadata:acceptances:userid',
'status' => 'privacy:metadata:acceptances:status',
'lang' => 'privacy:metadata:acceptances:lang',
'usermodified' => 'privacy:metadata:acceptances:usermodified',
'timecreated' => 'privacy:metadata:acceptances:timecreated',
'timemodified' => 'privacy:metadata:acceptances:timemodified',
'note' => 'privacy:metadata:acceptances:note',
],
'privacy:metadata:acceptances'
);
$collection->add_database_table(
'tool_policy_versions',
[
'name' => 'privacy:metadata:versions:name',
'type' => 'privacy:metadata:versions:type',
'audience' => 'privacy:metadata:versions:audience',
'archived' => 'privacy:metadata:versions:archived',
'usermodified' => 'privacy:metadata:versions:usermodified',
'timecreated' => 'privacy:metadata:versions:timecreated',
'timemodified' => 'privacy:metadata:versions:timemodified',
'policyid' => 'privacy:metadata:versions:policyid',
'revision' => 'privacy:metadata:versions:revision',
'summary' => 'privacy:metadata:versions:summary',
'summaryformat' => 'privacy:metadata:versions:summaryformat',
'content' => 'privacy:metadata:versions:content',
'contentformat' => 'privacy:metadata:versions:contentformat',
],
'privacy:metadata:versions'
);
$collection->add_subsystem_link('core_files', [], 'privacy:metadata:subsystem:corefiles');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The userid.
* @return contextlist The list of contexts containing user info for the user.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$contextlist = new contextlist();
// Policies a user has modified.
$sql = "SELECT c.id
FROM {context} c
JOIN {tool_policy_versions} v ON v.usermodified = :userid
WHERE c.contextlevel = :contextlevel";
$params = [
'contextlevel' => CONTEXT_SYSTEM,
'userid' => $userid,
];
$contextlist->add_from_sql($sql, $params);
// Policies a user has accepted.
$sql = "SELECT c.id
FROM {context} c
JOIN {tool_policy_acceptances} a ON c.instanceid = a.userid
WHERE
c.contextlevel = :contextlevel
AND (
a.userid = :userid OR a.usermodified = :usermodified
)";
$params = [
'contextlevel' => CONTEXT_USER,
'userid' => $userid,
'usermodified' => $userid,
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
// Users that have modified any policies, if fetching for system context.
if (is_a($context, \context_system::class)) {
$sql = "SELECT v.usermodified AS userid
FROM {tool_policy_versions} v";
$userlist->add_from_sql('userid', $sql, []);
}
// Users that have accepted any policies, if fetching for user context.
if (is_a($context, \context_user::class)) {
$sql = "SELECT a.userid, a.usermodified
FROM {tool_policy_acceptances} a
WHERE a.userid = :instanceid";
$params = ['instanceid' => $context->instanceid];
$userlist->add_from_sql('userid', $sql, $params);
$userlist->add_from_sql('usermodified', $sql, $params);
}
}
/**
* Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
*
* @param approved_contextlist $contextlist A list of contexts approved for export.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
// Export user agreements.
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel == CONTEXT_USER) {
static::export_policy_agreements_for_context($context);
} else if ($context->contextlevel == CONTEXT_SYSTEM) {
static::export_authored_policies($contextlist->get_user());
}
}
}
/**
* Delete all data for all users in the specified context.
*
* We never delete user agreements to the policies because they are part of privacy data.
* We never delete policy versions because they are part of privacy data.
*
* @param \context $context The context to delete in.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* We never delete user agreements to the policies because they are part of privacy data.
* We never delete policy versions because they are part of privacy data.
*
* @param approved_contextlist $contextlist A list of contexts approved for deletion.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
}
/**
* Delete multiple users within a single context.
*
* We never delete user agreements to the policies because they are part of privacy data.
* We never delete policy versions because they are part of privacy data.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
}
/**
* Export all policy agreements relating to the specified user context.
*
* @param \context_user $context The context to export
*/
protected static function export_policy_agreements_for_context(\context_user $context) {
global $DB;
$sysctx = \context_system::instance();
$fs = get_file_storage();
$agreementsql = "
SELECT
a.id AS agreementid, a.userid, a.timemodified, a.note, a.status,
a.policyversionid AS versionid, a.usermodified, a.timecreated,
v.id, v.archived, v.name, v.revision,
v.summary, v.summaryformat,
v.content, v.contentformat,
p.currentversionid
FROM {tool_policy_acceptances} a
JOIN {tool_policy_versions} v ON v.id = a.policyversionid
JOIN {tool_policy} p ON v.policyid = p.id
WHERE a.userid = :userid OR a.usermodified = :usermodified";
// Fetch all agreements related to this user.
$agreements = $DB->get_recordset_sql($agreementsql, [
'userid' => $context->instanceid,
'usermodified' => $context->instanceid,
]);
$basecontext = [
get_string('privacyandpolicies', 'admin'),
get_string('useracceptances', 'tool_policy'),
];
foreach ($agreements as $agreement) {
$subcontext = array_merge($basecontext, [get_string('policynamedversion', 'tool_policy', $agreement)]);
$summary = writer::with_context($context)->rewrite_pluginfile_urls(
$subcontext,
'tool_policy',
'policydocumentsummary',
$agreement->versionid,
$agreement->summary
);
$content = writer::with_context($context)->rewrite_pluginfile_urls(
$subcontext,
'tool_policy',
'policydocumentcontent',
$agreement->versionid,
$agreement->content
);
$agreementcontent = (object) [
'name' => $agreement->name,
'revision' => $agreement->revision,
'isactive' => transform::yesno($agreement->versionid == $agreement->currentversionid),
'isagreed' => transform::yesno($agreement->status),
'agreedby' => transform::user($agreement->usermodified),
'timecreated' => transform::datetime($agreement->timecreated),
'timemodified' => transform::datetime($agreement->timemodified),
'note' => $agreement->note,
'summary' => format_text($summary, $agreement->summaryformat),
'content' => format_text($content, $agreement->contentformat),
];
writer::with_context($context)->export_data($subcontext, $agreementcontent);
// Manually export the files as they reside in the system context so we can't use
// the write's helper methods.
foreach ($fs->get_area_files($sysctx->id, 'tool_policy', 'policydocumentsummary', $agreement->versionid) as $file) {
writer::with_context($context)->export_file($subcontext, $file);
}
foreach ($fs->get_area_files($sysctx->id, 'tool_policy', 'policydocumentcontent', $agreement->versionid) as $file) {
writer::with_context($context)->export_file($subcontext, $file);
}
}
$agreements->close();
}
/**
* Export all policy agreements that the user authored.
*
* @param stdClass $user The user who has created the policies to export.
*/
protected static function export_authored_policies(\stdClass $user) {
global $DB;
// Authored policies are exported against the system.
$context = \context_system::instance();
$basecontext = [
get_string('policydocuments', 'tool_policy'),
];
$sql = "SELECT v.id,
v.name,
v.revision,
v.summary,
v.content,
v.archived,
v.usermodified,
v.timecreated,
v.timemodified,
p.currentversionid
FROM {tool_policy_versions} v
JOIN {tool_policy} p ON p.id = v.policyid
WHERE v.usermodified = :userid";
$versions = $DB->get_recordset_sql($sql, ['userid' => $user->id]);
foreach ($versions as $version) {
$subcontext = array_merge($basecontext, [get_string('policynamedversion', 'tool_policy', $version)]);
$versioncontent = (object) [
'name' => $version->name,
'revision' => $version->revision,
'summary' => writer::with_context($context)->rewrite_pluginfile_urls(
$subcontext,
'tool_policy',
'policydocumentsummary',
$version->id,
$version->summary
),
'content' => writer::with_context($context)->rewrite_pluginfile_urls(
$subcontext,
'tool_policy',
'policydocumentcontent',
$version->id,
$version->content
),
'isactive' => transform::yesno($version->id == $version->currentversionid),
'isarchived' => transform::yesno($version->archived),
'createdbyme' => transform::yesno($version->usermodified == $user->id),
'timecreated' => transform::datetime($version->timecreated),
'timemodified' => transform::datetime($version->timemodified),
];
writer::with_context($context)
->export_data($subcontext, $versioncontent)
->export_area_files($subcontext, 'tool_policy', 'policydocumentsummary', $version->id)
->export_area_files($subcontext, 'tool_policy', 'policydocumentcontent', $version->id);
}
$versions->close();
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link \tool_policy\test\helper} class.
*
* @package tool_policy
* @category test
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_policy\test;
use tool_policy\api;
use tool_policy\policy_version;
defined('MOODLE_INTERNAL') || die();
/**
* Provides some helper methods for unit-tests.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Helper method that creates a new policy for testing
*
* @param array $params
* @return policy_version
*/
public static function add_policy($params = []) {
static $counter = 0;
$counter++;
$defaults = [
'name' => 'Policy '.$counter,
'summary_editor' => ['text' => "P$counter summary", 'format' => FORMAT_HTML, 'itemid' => 0],
'content_editor' => ['text' => "P$counter content", 'format' => FORMAT_HTML, 'itemid' => 0],
];
$params = (array)$params + $defaults;
$formdata = api::form_policydoc_data(new policy_version(0));
foreach ($params as $key => $value) {
$formdata->$key = $value;
}
return api::form_policydoc_add($formdata);
}
/**
* Helper method that prepare a policy document with some versions.
*
* @param int $numversions The number of policy versions to create.
* @return array Array with all the policy versions created.
*/
public static function create_versions($numversions = 2) {
// Prepare a policy document with some versions.
$policy = self::add_policy([
'name' => 'Test policy',
'revision' => 'v1',
]);
for ($i = 2; $i <= $numversions; $i++) {
$formdata = api::form_policydoc_data($policy);
$formdata->revision = 'v'.$i;
api::form_policydoc_update_new($formdata);
}
$list = api::list_policies($policy->get('policyid'));
return $list[0]->draftversions;
}
}