MDL-54035 accesslib: Check if the user can switch role before switching
[moodle.git] / admin / tool / policy / classes / output / page_agreedocs.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Provides {@link tool_policy\output\renderer} class.
19  *
20  * @package     tool_policy
21  * @category    output
22  * @copyright   2018 Sara Arjona <sara@moodle.com>
23  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 namespace tool_policy\output;
28 defined('MOODLE_INTERNAL') || die();
30 use context_system;
31 use core\output\notification;
32 use core\session\manager;
33 use core_user;
34 use html_writer;
35 use moodle_url;
36 use renderable;
37 use renderer_base;
38 use single_button;
39 use templatable;
40 use tool_policy\api;
41 use tool_policy\policy_version;
43 /**
44  * Represents a page for showing all the policy documents which a user has to agree to.
45  *
46  * @copyright 2018 Sara Arjona <sara@moodle.com>
47  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48  */
49 class page_agreedocs implements renderable, templatable {
51     /** @var array $policies List of public policies objects with information about the user acceptance. */
52     protected $policies = null;
54     /** @var array $agreedocs List of policy identifiers which the user has agreed using the form. */
55     protected $agreedocs = null;
57     /** @var string $action Form action to identify when user agreeds policies. */
58     protected $action = null;
60     /** @var int User id who wants to accept this page. */
61     protected $behalfid = null;
63     /** @var object User who wants to accept this page. */
64     protected $behalfuser = null;
66     /** @var boolean True if signup user has agreed to all the policies; false otherwise. */
67     protected $signupuserpolicyagreed = false;
69     /** @var array Info or error messages to show. */
70     protected $messages = [];
72     /** @var bool This is an existing user (rather than non-loggedin/guest). */
73     protected $isexistinguser;
75     /**
76      * Prepare the page for rendering.
77      *
78      * @param array $agreedocs Array with the policy identifiers which the user has agreed using the form.
79      * @param int $behalfid The userid to accept the policy versions as (such as child's id).
80      * @param string $action Form action to identify when user agreeds policies.
81      */
82     public function __construct($agreedocs = null, $behalfid = 0, $action = null) {
83         global $USER;
84         $realuser = manager::get_realuser();
86         $this->agreedocs = $agreedocs;
87         if (empty($this->agreedocs)) {
88             $this->agreedocs = [];
89         }
91         $this->action = $action;
93         $this->isexistinguser = isloggedin() && !isguestuser();
94         $behalfid = $behalfid ?: $USER->id;
95         if ($realuser->id != $behalfid) {
96             $this->behalfuser = core_user::get_user($behalfid, '*', MUST_EXIST);
97             $this->behalfid = $this->behalfuser->id;
98         }
100         $this->policies = api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN);
101         if (empty($this->behalfid)) {
102             $userid = $USER->id;
103         } else {
104             $userid = $this->behalfid;
105         }
106         $this->accept_and_revoke_policies();
107         $this->prepare_global_page_access($userid);
108         $this->prepare_user_acceptances($userid);
109     }
111     /**
112      * Accept and revoke the policy versions.
113      * The capabilities for accepting/revoking policies are checked into the api functions.
114      *
115      */
116     protected function accept_and_revoke_policies() {
117         global $USER;
119         if ($this->isexistinguser) {
120             // Existing user.
121             if (!empty($this->action) && confirm_sesskey()) {
122                 // The form has been sent. Update policies acceptances according to $this->agreedocs.
123                 $lang = current_language();
124                 // Accept / revoke policies.
125                 $acceptversionids = array();
126                 foreach ($this->policies as $policy) {
127                     if (in_array($policy->id, $this->agreedocs)) {
128                         // Save policy version doc to accept it.
129                         $acceptversionids[] = $policy->id;
130                     } else {
131                         // Revoke policy doc.
132                         api::revoke_acceptance($policy->id, $this->behalfid);
133                     }
134                 }
135                 // Accept all policy docs saved in $acceptversionids.
136                 api::accept_policies($acceptversionids, $this->behalfid, null, $lang);
137                 // Show a message to let know the user he/she must agree all the policies.
138                 if (count($acceptversionids) != count($this->policies)) {
139                     $message = (object) [
140                         'type' => 'error',
141                         'text' => get_string('mustagreetocontinue', 'tool_policy')
142                     ];
143                 } else {
144                     $message = (object) [
145                         'type' => 'success',
146                         'text' => get_string('acceptancessavedsucessfully', 'tool_policy')
147                     ];
148                 }
149                 $this->messages[] = $message;
150             } else if (!empty($this->policies) && empty($USER->policyagreed)) {
151                 // Inform users they must agree to all policies before continuing.
152                 $message = (object) [
153                     'type' => 'error',
154                     'text' => get_string('mustagreetocontinue', 'tool_policy')
155                 ];
156                 $this->messages[] = $message;
157             }
158         } else {
159             // New user.
160             if (!empty($this->action) && confirm_sesskey()) {
161                 // The form has been sent.
162                 $currentpolicyversionids = [];
163                 foreach ($this->policies as $policy) {
164                     $currentpolicyversionids[] = $policy->id;
165                 }
166                 // If the user has accepted all the policies, add it to the session to let continue with the signup process.
167                 $this->signupuserpolicyagreed = empty(array_diff($currentpolicyversionids, $this->agreedocs));
168                 \cache::make('core', 'presignup')->set('tool_policy_userpolicyagreed',
169                     $this->signupuserpolicyagreed);
170             } else if (empty($this->policies)) {
171                 // There are no policies to agree to. Update the policyagreed value to avoid show empty consent page.
172                 \cache::make('core', 'presignup')->set('tool_policy_userpolicyagreed', 1);
173             }
174             if (!empty($this->policies) && !$this->signupuserpolicyagreed) {
175                 // During the signup process, inform users they must agree to all policies before continuing.
176                 $message = (object) [
177                     'type' => 'error',
178                     'text' => get_string('mustagreetocontinue', 'tool_policy')
179                 ];
180                 $this->messages[] = $message;
181             }
182         }
183     }
185     /**
186      * Before display the consent page, the user has to view all the still-non-accepted policy docs.
187      * This function checks if the non-accepted policy docs have been shown and redirect to them.
188      *
189      * @param int $userid User identifier who wants to access to the consent page.
190      * @param moodle_url $returnurl URL to return after shown the policy docs.
191      */
192     protected function redirect_to_policies($userid, $returnurl = null) {
193         $allpolicies = $this->policies;
194         if ($this->isexistinguser) {
195             $acceptances = api::get_user_acceptances($userid);
196             foreach ($allpolicies as $policy) {
197                 if (api::is_user_version_accepted($userid, $policy->id, $acceptances)) {
198                     // If this version is accepted by the user, remove from the pending policies list.
199                     unset($allpolicies[array_search($policy, $allpolicies)]);
200                 }
201             }
202         }
204         if (!empty($allpolicies)) {
205             $currentpolicyversionids = [];
206             foreach ($allpolicies as $policy) {
207                 $currentpolicyversionids[] = $policy->id;
208             }
210             $cache = \cache::make('core', 'presignup');
211             $cachekey = 'tool_policy_viewedpolicies';
213             $viewedpolicies = $cache->get($cachekey);
214             if (!empty($viewedpolicies)) {
215                 // Get the list of the policies docs which the user haven't viewed during this session.
216                 $pendingpolicies = array_diff($currentpolicyversionids, $viewedpolicies);
217             } else {
218                 $pendingpolicies = $currentpolicyversionids;
219             }
220             if (count($pendingpolicies) > 0) {
221                 // Still is needed to show some policies docs. Save in the session and redirect.
222                 $policyversionid = array_shift($pendingpolicies);
223                 $viewedpolicies[] = $policyversionid;
224                 $cache->set($cachekey, $viewedpolicies);
225                 if (empty($returnurl)) {
226                     $returnurl = new moodle_url('/admin/tool/policy/index.php');
227                 }
228                 $urlparams = ['versionid' => $policyversionid,
229                               'returnurl' => $returnurl,
230                               'numpolicy' => count($currentpolicyversionids) - count($pendingpolicies),
231                               'totalpolicies' => count($currentpolicyversionids),
232                 ];
233                 redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
234             }
235         }
236     }
238     /**
239      * Redirect to signup page if defined or to $CFG->wwwroot if not.
240      */
241     protected function redirect_to_previous_url() {
242         global $SESSION;
244         if ($this->isexistinguser) {
245             // Existing user.
246             if (!empty($SESSION->wantsurl)) {
247                 $returnurl = $SESSION->wantsurl;
248                 unset($SESSION->wantsurl);
249             } else {
250                 $returnurl = new moodle_url('/admin/tool/policy/user.php');
251             }
252         } else {
253             // Non-authenticated user.
254             $issignup = \cache::make('core', 'presignup')->get('tool_policy_issignup');
255             if ($issignup) {
256                 // User came here from signup page - redirect back there.
257                 $returnurl = new moodle_url('/login/signup.php');
258                 \cache::make('core', 'presignup')->set('tool_policy_issignup', false);
259             } else {
260                 // Guests should not be on this page unless it's part of signup - redirect home.
261                 $returnurl = new moodle_url('/');
262             }
263         }
265         redirect($returnurl);
266     }
268     /**
269      * Sets up the global $PAGE and performs the access checks.
270      *
271      * @param int $userid
272      */
273     protected function prepare_global_page_access($userid) {
274         global $PAGE, $SITE, $USER;
276         // Guest users or not logged users (but the users during the signup process) are not allowed to access to this page.
277         $newsignupuser = \cache::make('core', 'presignup')->get('tool_policy_issignup');
278         if (!$this->isexistinguser && !$newsignupuser) {
279             $this->redirect_to_previous_url();
280         }
282         // Check for correct user capabilities.
283         if ($this->isexistinguser) {
284             // For existing users, it's needed to check if they have the capability for accepting policies.
285             api::can_accept_policies($this->behalfid, true);
286         } else {
287             // For new users, the behalfid parameter is ignored.
288             if ($this->behalfid) {
289                 redirect(new moodle_url('/admin/tool/policy/index.php'));
290             }
291         }
293         // If the current user has the $USER->policyagreed = 1 or $userpolicyagreed = 1
294         // redirect to the return page.
295         $hasagreedsignupuser = !$this->isexistinguser && $this->signupuserpolicyagreed;
296         $hasagreedloggeduser = $USER->id == $userid && !empty($USER->policyagreed);
297         if (!is_siteadmin() && ($hasagreedsignupuser || $hasagreedloggeduser)) {
298             $this->redirect_to_previous_url();
299         }
301         $myparams = [];
302         if ($this->isexistinguser && !empty($this->behalfid) && $this->behalfid != $USER->id) {
303             $myparams['userid'] = $this->behalfid;
304         }
305         $myurl = new moodle_url('/admin/tool/policy/index.php', $myparams);
307         // Redirect to policy docs before the consent page.
308         $this->redirect_to_policies($userid, $myurl);
310         // Page setup.
311         $PAGE->set_context(context_system::instance());
312         $PAGE->set_url($myurl);
313         $PAGE->set_heading($SITE->fullname);
314         $PAGE->set_title(get_string('policiesagreements', 'tool_policy'));
315         $PAGE->navbar->add(get_string('policiesagreements', 'tool_policy'), new moodle_url('/admin/tool/policy/index.php'));
316     }
318     /**
319      * Prepare user acceptances.
320      *
321      * @param int $userid
322      */
323     protected function prepare_user_acceptances($userid) {
324         global $USER;
326         // Get all the policy version acceptances for this user.
327         $lang = current_language();
328         foreach ($this->policies as $policy) {
329             // Get a link to display the full policy document.
330             $policy->url = new moodle_url('/admin/tool/policy/view.php',
331                 array('policyid' => $policy->policyid, 'returnurl' => qualified_me()));
332             $policyattributes = array('data-action' => 'view',
333                                       'data-versionid' => $policy->id,
334                                       'data-behalfid' => $this->behalfid);
335             $policymodal = html_writer::link($policy->url, $policy->name, $policyattributes);
337             // Check if this policy version has been agreed or not.
338             if ($this->isexistinguser) {
339                 // Existing user.
340                 $versionagreed = false;
341                 $acceptances = api::get_user_acceptances($userid);
342                 $policy->versionacceptance = api::get_user_version_acceptance($userid, $policy->id, $acceptances);
343                 if (!empty($policy->versionacceptance)) {
344                     // The policy version has ever been agreed. Check if status = 1 to know if still is accepted.
345                     $versionagreed = $policy->versionacceptance->status;
346                     if ($versionagreed) {
347                         if ($policy->versionacceptance->lang != $lang) {
348                             // Add a message because this version has been accepted in a different language than the current one.
349                             $policy->versionlangsagreed = get_string('policyversionacceptedinotherlang', 'tool_policy');
350                         }
351                         if ($policy->versionacceptance->usermodified != $userid && $USER->id == $userid) {
352                             // Add a message because this version has been accepted in behalf of current user.
353                             $policy->versionbehalfsagreed = get_string('policyversionacceptedinbehalf', 'tool_policy');
354                         }
355                     }
356                 }
357             } else {
358                 // New user.
359                 $versionagreed = in_array($policy->id, $this->agreedocs);
360             }
361             $policy->versionagreed = $versionagreed;
362             $policy->policylink = html_writer::link($policy->url, $policy->name);
363             $policy->policymodal = $policymodal;
364         }
365     }
367     /**
368      * Export the page data for the mustache template.
369      *
370      * @param renderer_base $output renderer to be used to render the page elements.
371      * @return \stdClass
372      */
373     public function export_for_template(renderer_base $output) {
374         global $USER;
376         $myparams = [];
377         if ($this->isexistinguser && !empty($this->behalfid) && $this->behalfid != $USER->id) {
378             $myparams['userid'] = $this->behalfid;
379         }
380         $data = (object) [
381             'pluginbaseurl' => (new moodle_url('/admin/tool/policy'))->out(false),
382             'myurl' => (new moodle_url('/admin/tool/policy/index.php', $myparams))->out(false),
383             'sesskey' => sesskey(),
384         ];
386         if (!empty($this->messages)) {
387             foreach ($this->messages as $message) {
388                 switch ($message->type) {
389                     case 'error':
390                         $data->messages[] = $output->notification($message->text, notification::NOTIFY_ERROR);
391                         break;
393                     case 'success':
394                         $data->messages[] = $output->notification($message->text, notification::NOTIFY_SUCCESS);
395                         break;
397                     default:
398                         $data->messages[] = $output->notification($message->text, notification::NOTIFY_INFO);
399                         break;
400                 }
401             }
402         }
404         $data->policies = array_values($this->policies);
406         // If viewing docs in behalf of other user, get his/her full name and profile link.
407         if (!empty($this->behalfuser)) {
408             $userfullname = fullname($this->behalfuser, has_capability('moodle/site:viewfullnames', \context_system::instance()) ||
409                         has_capability('moodle/site:viewfullnames', \context_user::instance($this->behalfid)));
410             $data->behalfuser = html_writer::link(\context_user::instance($this->behalfid)->get_url(), $userfullname);
411         }
413         // User can cancel accepting policies only if it is a part of signup.
414         $data->cancancel = !isloggedin() || isguestuser();
416         return $data;
417     }