MDL-62418 tool_policy: Redirect always to home when agreed all policies
[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     /**
73      * Prepare the page for rendering.
74      *
75      * @param array $agreedocs Array with the policy identifiers which the user has agreed using the form.
76      * @param int $behalfid The userid to accept the policy versions as (such as child's id).
77      * @param string $action Form action to identify when user agreeds policies.
78      */
79     public function __construct($agreedocs = null, $behalfid = 0, $action = null) {
80         global $USER;
81         $realuser = manager::get_realuser();
83         $this->agreedocs = $agreedocs;
84         if (empty($this->agreedocs)) {
85             $this->agreedocs = [];
86         }
88         $this->action = $action;
90         $behalfid = $behalfid ?: $USER->id;
91         if ($realuser->id != $behalfid) {
92             $this->behalfuser = core_user::get_user($behalfid, '*', MUST_EXIST);
93             $this->behalfid = $this->behalfuser->id;
94         }
96         $this->policies = api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN);
97         if (empty($this->behalfid)) {
98             $userid = $USER->id;
99         } else {
100             $userid = $this->behalfid;
101         }
102         $this->accept_and_revoke_policies();
103         $this->prepare_global_page_access($userid);
104         $this->prepare_user_acceptances($userid);
105     }
107     /**
108      * Accept and revoke the policy versions.
109      * The capabilities for accepting/revoking policies are checked into the api functions.
110      *
111      */
112     protected function accept_and_revoke_policies() {
113         global $USER;
115         if (!empty($USER->id)) {
116             // Existing user.
117             if (!empty($this->action) && confirm_sesskey()) {
118                 // The form has been sent. Update policies acceptances according to $this->agreedocs.
119                 $lang = current_language();
120                 // Accept / revoke policies.
121                 $acceptversionids = array();
122                 foreach ($this->policies as $policy) {
123                     if (in_array($policy->id, $this->agreedocs)) {
124                         // Save policy version doc to accept it.
125                         $acceptversionids[] = $policy->id;
126                     } else {
127                         // Revoke policy doc.
128                         api::revoke_acceptance($policy->id, $this->behalfid);
129                     }
130                 }
131                 // Accept all policy docs saved in $acceptversionids.
132                 api::accept_policies($acceptversionids, $this->behalfid, null, $lang);
133                 // Show a message to let know the user he/she must agree all the policies.
134                 if (count($acceptversionids) != count($this->policies)) {
135                     $message = (object) [
136                         'type' => 'error',
137                         'text' => get_string('mustagreetocontinue', 'tool_policy')
138                     ];
139                 } else {
140                     $message = (object) [
141                         'type' => 'success',
142                         'text' => get_string('acceptancessavedsucessfully', 'tool_policy')
143                     ];
144                 }
145                 $this->messages[] = $message;
146             } else if (!empty($this->policies) && empty($USER->policyagreed)) {
147                 // Inform users they must agree to all policies before continuing.
148                 $message = (object) [
149                     'type' => 'error',
150                     'text' => get_string('mustagreetocontinue', 'tool_policy')
151                 ];
152                 $this->messages[] = $message;
153             }
154         } else {
155             // New user.
156             if (!empty($this->action) && confirm_sesskey()) {
157                 // The form has been sent.
158                 $currentpolicyversionids = [];
159                 foreach ($this->policies as $policy) {
160                     $currentpolicyversionids[] = $policy->id;
161                 }
162                 // If the user has accepted all the policies, add it to the session to let continue with the signup process.
163                 $this->signupuserpolicyagreed = empty(array_diff($currentpolicyversionids, $this->agreedocs));
164                 \cache::make('core', 'presignup')->set('tool_policy_userpolicyagreed',
165                     $this->signupuserpolicyagreed);
166             } else if (empty($this->policies)) {
167                 // There are no policies to agree to. Update the policyagreed value to avoid show empty consent page.
168                 \cache::make('core', 'presignup')->set('tool_policy_userpolicyagreed', 1);
169             }
170             if (!empty($this->policies) && !$this->signupuserpolicyagreed) {
171                 // During the signup process, inform users they must agree to all policies before continuing.
172                 $message = (object) [
173                     'type' => 'error',
174                     'text' => get_string('mustagreetocontinue', 'tool_policy')
175                 ];
176                 $this->messages[] = $message;
177             }
178         }
179     }
181     /**
182      * Before display the consent page, the user has to view all the still-non-accepted policy docs.
183      * This function checks if the non-accepted policy docs have been shown and redirect to them.
184      *
185      * @param array $userid User identifier who wants to access to the consent page.
186      * @param url $returnurl URL to return after shown the policy docs.
187      */
188     protected function redirect_to_policies($userid, $returnurl = null) {
189         global $USER;
191         $acceptances = api::get_user_acceptances($userid);
192         $allpolicies = $this->policies;
193         if (!empty($userid)) {
194             foreach ($allpolicies as $policy) {
195                 if (api::is_user_version_accepted($userid, $policy->id, $acceptances)) {
196                     // If this version is accepted by the user, remove from the pending policies list.
197                     unset($allpolicies[array_search($policy, $allpolicies)]);
198                 }
199             }
200         }
202         if (!empty($allpolicies)) {
203             $currentpolicyversionids = [];
204             foreach ($allpolicies as $policy) {
205                 $currentpolicyversionids[] = $policy->id;
206             }
208             $cache = \cache::make('core', 'presignup');
209             $cachekey = 'tool_policy_viewedpolicies';
211             $viewedpolicies = $cache->get($cachekey);
212             if (!empty($viewedpolicies)) {
213                 // Get the list of the policies docs which the user haven't viewed during this session.
214                 $pendingpolicies = array_diff($currentpolicyversionids, $viewedpolicies);
215             } else {
216                 $pendingpolicies = $currentpolicyversionids;
217             }
218             if (count($pendingpolicies) > 0) {
219                 // Still is needed to show some policies docs. Save in the session and redirect.
220                 $policyversionid = array_shift($pendingpolicies);
221                 $viewedpolicies[] = $policyversionid;
222                 $cache->set($cachekey, $viewedpolicies);
223                 if (empty($returnurl)) {
224                     $returnurl = new moodle_url('/admin/tool/policy/index.php');
225                 }
226                 $urlparams = ['versionid' => $policyversionid,
227                               'returnurl' => $returnurl,
228                               'numpolicy' => count($currentpolicyversionids) - count($pendingpolicies),
229                               'totalpolicies' => count($currentpolicyversionids),
230                 ];
231                 redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
232             }
233         }
234     }
236     /**
237      * Redirect to $SESSION->wantsurl if defined or to $CFG->wwwroot if not.
238      */
239     protected function redirect_to_previous_url() {
240         global $SESSION;
242         if (!empty($SESSION->wantsurl)) {
243             $returnurl = $SESSION->wantsurl;
244             unset($SESSION->wantsurl);
245         } else {
246             $returnurl = (new moodle_url('/admin/tool/policy/user.php'))->out();
247         }
249         redirect($returnurl);
250     }
252     /**
253      * Sets up the global $PAGE and performs the access checks.
254      *
255      * @param int $userid
256      */
257     protected function prepare_global_page_access($userid) {
258         global $PAGE, $SESSION, $SITE, $USER;
260         // Guest users or not logged users (but the users during the signup process) are not allowed to access to this page.
261         $newsignupuser = !empty($SESSION->wantsurl) && strpos($SESSION->wantsurl, 'login/signup.php') !== false;
262         if (isguestuser() || (empty($USER->id) && !$newsignupuser)) {
263             $this->redirect_to_previous_url();
264         }
266         // Check for correct user capabilities.
267         if (!empty($USER->id)) {
268             // For existing users, it's needed to check if they have the capability for accepting policies.
269             api::can_accept_policies($this->behalfid, true);
270         } else {
271             // For new users, the behalfid parameter is ignored.
272             if ($this->behalfid != $USER->id) {
273                 redirect(new moodle_url('/admin/tool/policy/index.php'));
274             }
275         }
277         // If the current user has the $USER->policyagreed = 1 or $userpolicyagreed = 1
278         // and $SESSION->wantsurl is defined, redirect to the return page.
279         $hasagreedsignupuser = empty($USER->id) && $this->signupuserpolicyagreed;
280         $hasagreedloggeduser = $USER->id == $userid && !empty($USER->policyagreed);
281         if (!is_siteadmin() && ($hasagreedsignupuser || $hasagreedloggeduser)) {
282             $this->redirect_to_previous_url();
283         }
285         $myparams = [];
286         if (!empty($USER->id) && !empty($this->behalfid) && $this->behalfid != $USER->id) {
287             $myparams['userid'] = $this->behalfid;
288         }
289         $myurl = new moodle_url('/admin/tool/policy/index.php', $myparams);
291         // Redirect to policy docs before the consent page.
292         $this->redirect_to_policies($userid, $myurl);
294         // Page setup.
295         $PAGE->set_context(context_system::instance());
296         $PAGE->set_url($myurl);
297         $PAGE->set_heading($SITE->fullname);
298         $PAGE->set_title(get_string('policiesagreements', 'tool_policy'));
299         $PAGE->navbar->add(get_string('policiesagreements', 'tool_policy'), new moodle_url('/admin/tool/policy/index.php'));
300     }
302     /**
303      * Prepare user acceptances.
304      *
305      * @param int $userid
306      */
307     protected function prepare_user_acceptances($userid) {
308         global $USER;
310         // Get all the policy version acceptances for this user.
311         $acceptances = api::get_user_acceptances($userid);
312         $lang = current_language();
313         foreach ($this->policies as $policy) {
314             // Get a link to display the full policy document.
315             $policy->url = new moodle_url('/admin/tool/policy/view.php',
316                 array('policyid' => $policy->policyid, 'returnurl' => qualified_me()));
317             $policyattributes = array('data-action' => 'view',
318                                       'data-versionid' => $policy->id,
319                                       'data-behalfid' => $this->behalfid);
320             $policymodal = html_writer::link($policy->url, $policy->name, $policyattributes);
322             // Check if this policy version has been agreed or not.
323             if (!empty($userid)) {
324                 // Existing user.
325                 $versionagreed = false;
326                 $policy->versionacceptance = api::get_user_version_acceptance($userid, $policy->id, $acceptances);
327                 if (!empty($policy->versionacceptance)) {
328                     // The policy version has ever been agreed. Check if status = 1 to know if still is accepted.
329                     $versionagreed = $policy->versionacceptance->status;
330                     if ($versionagreed) {
331                         if ($policy->versionacceptance->lang != $lang) {
332                             // Add a message because this version has been accepted in a different language than the current one.
333                             $policy->versionlangsagreed = get_string('policyversionacceptedinotherlang', 'tool_policy');
334                         }
335                         if ($policy->versionacceptance->usermodified != $userid && $USER->id == $userid) {
336                             // Add a message because this version has been accepted in behalf of current user.
337                             $policy->versionbehalfsagreed = get_string('policyversionacceptedinbehalf', 'tool_policy');
338                         }
339                     }
340                 }
341             } else {
342                 // New user.
343                 $versionagreed = in_array($policy->id, $this->agreedocs);
344             }
345             $policy->versionagreed = $versionagreed;
346             $policy->policylink = html_writer::link($policy->url, $policy->name);
347             $policy->policymodal = $policymodal;
348         }
349     }
351     /**
352      * Export the page data for the mustache template.
353      *
354      * @param renderer_base $output renderer to be used to render the page elements.
355      * @return stdClass
356      */
357     public function export_for_template(renderer_base $output) {
358         global $USER;
360         $myparams = [];
361         if (!empty($USER->id) && !empty($this->behalfid) && $this->behalfid != $USER->id) {
362             $myparams['userid'] = $this->behalfid;
363         }
364         $data = (object) [
365             'pluginbaseurl' => (new moodle_url('/admin/tool/policy'))->out(false),
366             'myurl' => (new moodle_url('/admin/tool/policy/index.php', $myparams))->out(false),
367             'sesskey' => sesskey(),
368         ];
370         if (!empty($this->messages)) {
371             foreach ($this->messages as $message) {
372                 switch ($message->type) {
373                     case 'error':
374                         $data->messages[] = $output->notification($message->text, notification::NOTIFY_ERROR);
375                         break;
377                     case 'success':
378                         $data->messages[] = $output->notification($message->text, notification::NOTIFY_SUCCESS);
379                         break;
381                     default:
382                         $data->messages[] = $output->notification($message->text, notification::NOTIFY_INFO);
383                         break;
384                 }
385             }
386         }
388         $data->policies = array_values($this->policies);
390         // If viewing docs in behalf of other user, get his/her full name and profile link.
391         if (!empty($this->behalfuser)) {
392             $userfullname = fullname($this->behalfuser, has_capability('moodle/site:viewfullnames', \context_system::instance()) ||
393                         has_capability('moodle/site:viewfullnames', \context_user::instance($this->behalfid)));
394             $data->behalfuser = html_writer::link(\context_user::instance($this->behalfid)->get_url(), $userfullname);
395         }
397         // User can cancel accepting policies only if it is a part of signup.
398         $data->cancancel = !isloggedin() || isguestuser();
400         return $data;
401     }