Merge branch 'MDL-54035_master-fix' of git://github.com/dmonllao/moodle
[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 List of policy version ids that were displayed to the user to agree with. */
55     protected $listdocs = null;
57     /** @var array $agreedocs List of policy identifiers which the user has agreed using the form. */
58     protected $agreedocs = null;
60     /** @var string $action Form action to identify when user agreeds policies. */
61     protected $action = null;
63     /** @var int User id who wants to accept this page. */
64     protected $behalfid = null;
66     /** @var object User who wants to accept this page. */
67     protected $behalfuser = null;
69     /** @var boolean True if signup user has agreed to all the policies; false otherwise. */
70     protected $signupuserpolicyagreed = false;
72     /** @var array Info or error messages to show. */
73     protected $messages = [];
75     /** @var bool This is an existing user (rather than non-loggedin/guest). */
76     protected $isexistinguser;
78     /**
79      * Prepare the page for rendering.
80      *
81      * @param array $listdocs List of policy version ids that were displayed to the user to agree with.
82      * @param array $agreedocs List of policy version ids that the user actually agreed with.
83      * @param int $behalfid The userid to accept the policy versions as (such as child's id).
84      * @param string $action Form action to identify when user agreeds policies.
85      */
86     public function __construct(array $listdocs, array $agreedocs = [], $behalfid = 0, $action = null) {
87         global $USER;
88         $realuser = manager::get_realuser();
90         $this->listdocs = $listdocs;
91         $this->agreedocs = $agreedocs;
92         $this->action = $action;
93         $this->isexistinguser = isloggedin() && !isguestuser();
95         $behalfid = $behalfid ?: $USER->id;
96         if ($realuser->id != $behalfid) {
97             $this->behalfuser = core_user::get_user($behalfid, '*', MUST_EXIST);
98             $this->behalfid = $this->behalfuser->id;
99         }
101         $this->policies = api::list_current_versions(policy_version::AUDIENCE_LOGGEDIN);
102         if (empty($this->behalfid)) {
103             $userid = $USER->id;
104         } else {
105             $userid = $this->behalfid;
106         }
107         $this->accept_and_revoke_policies();
108         $this->prepare_global_page_access($userid);
109         $this->prepare_user_acceptances($userid);
110     }
112     /**
113      * Accept and revoke the policy versions.
114      * The capabilities for accepting/revoking policies are checked into the api functions.
115      *
116      */
117     protected function accept_and_revoke_policies() {
118         global $USER;
120         if ($this->isexistinguser) {
121             // Existing user.
122             if (!empty($this->action) && confirm_sesskey()) {
123                 // The form has been sent. Update policies acceptances according to $this->agreedocs.
124                 $lang = current_language();
125                 // Accept / revoke policies.
126                 $acceptversionids = array();
127                 foreach ($this->policies as $policy) {
128                     if (in_array($policy->id, $this->listdocs)) {
129                         if (in_array($policy->id, $this->agreedocs)) {
130                             // Save policy version doc to accept it.
131                             $acceptversionids[] = $policy->id;
132                         } else {
133                             // If the policy was displayed but not agreed, revoke the eventually given acceptance.
134                             api::revoke_acceptance($policy->id, $this->behalfid);
135                         }
136                     }
137                 }
138                 // Accept all policy docs saved in $acceptversionids.
139                 api::accept_policies($acceptversionids, $this->behalfid, null, $lang);
140                 // Show a message to let know the user he/she must agree all the policies.
141                 if (count($acceptversionids) != count($this->policies)) {
142                     $message = (object) [
143                         'type' => 'error',
144                         'text' => get_string('mustagreetocontinue', 'tool_policy')
145                     ];
146                 } else {
147                     $message = (object) [
148                         'type' => 'success',
149                         'text' => get_string('acceptancessavedsucessfully', 'tool_policy')
150                     ];
151                 }
152                 $this->messages[] = $message;
153             } else if (!empty($this->policies) && empty($USER->policyagreed)) {
154                 // Inform users they must agree to all policies before continuing.
155                 $message = (object) [
156                     'type' => 'error',
157                     'text' => get_string('mustagreetocontinue', 'tool_policy')
158                 ];
159                 $this->messages[] = $message;
160             }
161         } else {
162             // New user.
163             if (!empty($this->action) && confirm_sesskey()) {
164                 $currentpolicyversionids = [];
165                 $presignupcache = \cache::make('core', 'presignup');
166                 $acceptances = $presignupcache->get('tool_policy_policyversionidsagreed');
167                 if (!$acceptances) {
168                     $acceptances = [];
169                 }
170                 foreach ($this->policies as $policy) {
171                     $currentpolicyversionids[] = $policy->id;
172                     if (in_array($policy->id, $this->listdocs)) {
173                         if (in_array($policy->id, $this->agreedocs)) {
174                             $acceptances[] = $policy->id;
175                         } else {
176                             $acceptances = array_values(array_diff($acceptances, [$policy->id]));
177                         }
178                     }
179                 }
180                 // If the user has accepted all the policies, add it to the session to let continue with the signup process.
181                 $this->signupuserpolicyagreed = empty(array_diff($currentpolicyversionids, $acceptances));
182                 $presignupcache->set('tool_policy_userpolicyagreed', $this->signupuserpolicyagreed);
183                 $presignupcache->set('tool_policy_policyversionidsagreed', $acceptances);
184             } else if (empty($this->policies)) {
185                 // There are no policies to agree to. Update the policyagreed value to avoid show empty consent page.
186                 \cache::make('core', 'presignup')->set('tool_policy_userpolicyagreed', 1);
187             }
188             if (!empty($this->policies) && !$this->signupuserpolicyagreed) {
189                 // During the signup process, inform users they must agree to all policies before continuing.
190                 $message = (object) [
191                     'type' => 'error',
192                     'text' => get_string('mustagreetocontinue', 'tool_policy')
193                 ];
194                 $this->messages[] = $message;
195             }
196         }
197     }
199     /**
200      * Before display the consent page, the user has to view all the still-non-accepted policy docs.
201      * This function checks if the non-accepted policy docs have been shown and redirect to them.
202      *
203      * @param int $userid User identifier who wants to access to the consent page.
204      * @param moodle_url $returnurl URL to return after shown the policy docs.
205      */
206     protected function redirect_to_policies($userid, $returnurl = null) {
208         // Make a list of all policies that the user has not accepted yet.
209         $allpolicies = $this->policies;
211         if ($this->isexistinguser) {
212             $acceptances = api::get_user_acceptances($userid);
213             foreach ($allpolicies as $ix => $policy) {
214                 if (api::is_user_version_accepted($userid, $policy->id, $acceptances)) {
215                     unset($allpolicies[$ix]);
216                 }
217             }
218         } else {
219             $presignupcache = \cache::make('core', 'presignup');
220             $acceptances = $presignupcache->get('tool_policy_policyversionidsagreed');
221             if ($acceptances) {
222                 foreach ($allpolicies as $ix => $policy) {
223                     if (in_array($policy->id, $acceptances)) {
224                         unset($allpolicies[$ix]);
225                     }
226                 }
227             }
228         }
230         if (!empty($allpolicies)) {
231             // Check if some of the to-be-accepted policies should be agreed on their own page.
232             foreach ($allpolicies as $policy) {
233                 if ($policy->agreementstyle == policy_version::AGREEMENTSTYLE_OWNPAGE) {
234                     if (empty($returnurl)) {
235                         $returnurl = (new moodle_url('/admin/tool/policy/index.php'))->out_as_local_url(false);
236                     }
237                     $urlparams = ['versionid' => $policy->id, 'returnurl' => $returnurl];
238                     redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
239                 }
240             }
242             $currentpolicyversionids = [];
243             foreach ($allpolicies as $policy) {
244                 $currentpolicyversionids[] = $policy->id;
245             }
247             $cache = \cache::make('core', 'presignup');
248             $cachekey = 'tool_policy_viewedpolicies';
250             $viewedpolicies = $cache->get($cachekey);
251             if (!empty($viewedpolicies)) {
252                 // Get the list of the policies docs which the user haven't viewed during this session.
253                 $pendingpolicies = array_diff($currentpolicyversionids, $viewedpolicies);
254             } else {
255                 $pendingpolicies = $currentpolicyversionids;
256             }
257             if (count($pendingpolicies) > 0) {
258                 // Still is needed to show some policies docs. Save in the session and redirect.
259                 $policyversionid = array_shift($pendingpolicies);
260                 $viewedpolicies[] = $policyversionid;
261                 $cache->set($cachekey, $viewedpolicies);
262                 if (empty($returnurl)) {
263                     $returnurl = new moodle_url('/admin/tool/policy/index.php');
264                 }
265                 $urlparams = ['versionid' => $policyversionid,
266                               'returnurl' => $returnurl,
267                               'numpolicy' => count($currentpolicyversionids) - count($pendingpolicies),
268                               'totalpolicies' => count($currentpolicyversionids),
269                 ];
270                 redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
271             }
272         } else {
273             $this->redirect_to_previous_url();
274         }
275     }
277     /**
278      * Redirect to signup page if defined or to $CFG->wwwroot if not.
279      */
280     protected function redirect_to_previous_url() {
281         global $SESSION;
283         if ($this->isexistinguser) {
284             // Existing user.
285             if (!empty($SESSION->wantsurl)) {
286                 $returnurl = $SESSION->wantsurl;
287                 unset($SESSION->wantsurl);
288             } else {
289                 $returnurl = new moodle_url('/admin/tool/policy/user.php');
290             }
291         } else {
292             // Non-authenticated user.
293             $issignup = \cache::make('core', 'presignup')->get('tool_policy_issignup');
294             if ($issignup) {
295                 // User came here from signup page - redirect back there.
296                 $returnurl = new moodle_url('/login/signup.php');
297                 \cache::make('core', 'presignup')->set('tool_policy_issignup', false);
298             } else {
299                 // Guests should not be on this page unless it's part of signup - redirect home.
300                 $returnurl = new moodle_url('/');
301             }
302         }
304         redirect($returnurl);
305     }
307     /**
308      * Sets up the global $PAGE and performs the access checks.
309      *
310      * @param int $userid
311      */
312     protected function prepare_global_page_access($userid) {
313         global $PAGE, $SITE, $USER;
315         // Guest users or not logged users (but the users during the signup process) are not allowed to access to this page.
316         $newsignupuser = \cache::make('core', 'presignup')->get('tool_policy_issignup');
317         if (!$this->isexistinguser && !$newsignupuser) {
318             $this->redirect_to_previous_url();
319         }
321         // Check for correct user capabilities.
322         if ($this->isexistinguser) {
323             // For existing users, it's needed to check if they have the capability for accepting policies.
324             api::can_accept_policies($this->behalfid, true);
325         } else {
326             // For new users, the behalfid parameter is ignored.
327             if ($this->behalfid) {
328                 redirect(new moodle_url('/admin/tool/policy/index.php'));
329             }
330         }
332         // If the current user has the $USER->policyagreed = 1 or $userpolicyagreed = 1
333         // redirect to the return page.
334         $hasagreedsignupuser = !$this->isexistinguser && $this->signupuserpolicyagreed;
335         $hasagreedloggeduser = $USER->id == $userid && !empty($USER->policyagreed);
336         if (!is_siteadmin() && ($hasagreedsignupuser || $hasagreedloggeduser)) {
337             $this->redirect_to_previous_url();
338         }
340         $myparams = [];
341         if ($this->isexistinguser && !empty($this->behalfid) && $this->behalfid != $USER->id) {
342             $myparams['userid'] = $this->behalfid;
343         }
344         $myurl = new moodle_url('/admin/tool/policy/index.php', $myparams);
346         // Redirect to policy docs before the consent page.
347         $this->redirect_to_policies($userid, $myurl);
349         // Page setup.
350         $PAGE->set_context(context_system::instance());
351         $PAGE->set_url($myurl);
352         $PAGE->set_heading($SITE->fullname);
353         $PAGE->set_title(get_string('policiesagreements', 'tool_policy'));
354         $PAGE->navbar->add(get_string('policiesagreements', 'tool_policy'), new moodle_url('/admin/tool/policy/index.php'));
355     }
357     /**
358      * Prepare user acceptances.
359      *
360      * @param int $userid
361      */
362     protected function prepare_user_acceptances($userid) {
363         global $USER;
365         // Get all the policy version acceptances for this user.
366         $lang = current_language();
367         foreach ($this->policies as $policy) {
368             // Get a link to display the full policy document.
369             $policy->url = new moodle_url('/admin/tool/policy/view.php',
370                 array('policyid' => $policy->policyid, 'returnurl' => qualified_me()));
371             $policyattributes = array('data-action' => 'view',
372                                       'data-versionid' => $policy->id,
373                                       'data-behalfid' => $this->behalfid);
374             $policymodal = html_writer::link($policy->url, $policy->name, $policyattributes);
376             // Check if this policy version has been agreed or not.
377             if ($this->isexistinguser) {
378                 // Existing user.
379                 $versionagreed = false;
380                 $acceptances = api::get_user_acceptances($userid);
381                 $policy->versionacceptance = api::get_user_version_acceptance($userid, $policy->id, $acceptances);
382                 if (!empty($policy->versionacceptance)) {
383                     // The policy version has ever been agreed. Check if status = 1 to know if still is accepted.
384                     $versionagreed = $policy->versionacceptance->status;
385                     if ($versionagreed) {
386                         if ($policy->versionacceptance->lang != $lang) {
387                             // Add a message because this version has been accepted in a different language than the current one.
388                             $policy->versionlangsagreed = get_string('policyversionacceptedinotherlang', 'tool_policy');
389                         }
390                         if ($policy->versionacceptance->usermodified != $userid && $USER->id == $userid) {
391                             // Add a message because this version has been accepted in behalf of current user.
392                             $policy->versionbehalfsagreed = get_string('policyversionacceptedinbehalf', 'tool_policy');
393                         }
394                     }
395                 }
396             } else {
397                 // New user.
398                 $versionagreed = in_array($policy->id, $this->agreedocs);
399             }
400             $policy->versionagreed = $versionagreed;
401             $policy->policylink = html_writer::link($policy->url, $policy->name);
402             $policy->policymodal = $policymodal;
403         }
404     }
406     /**
407      * Export the page data for the mustache template.
408      *
409      * @param renderer_base $output renderer to be used to render the page elements.
410      * @return \stdClass
411      */
412     public function export_for_template(renderer_base $output) {
413         global $USER;
415         $myparams = [];
416         if ($this->isexistinguser && !empty($this->behalfid) && $this->behalfid != $USER->id) {
417             $myparams['userid'] = $this->behalfid;
418         }
419         $data = (object) [
420             'pluginbaseurl' => (new moodle_url('/admin/tool/policy'))->out(false),
421             'myurl' => (new moodle_url('/admin/tool/policy/index.php', $myparams))->out(false),
422             'sesskey' => sesskey(),
423         ];
425         if (!empty($this->messages)) {
426             foreach ($this->messages as $message) {
427                 switch ($message->type) {
428                     case 'error':
429                         $data->messages[] = $output->notification($message->text, notification::NOTIFY_ERROR);
430                         break;
432                     case 'success':
433                         $data->messages[] = $output->notification($message->text, notification::NOTIFY_SUCCESS);
434                         break;
436                     default:
437                         $data->messages[] = $output->notification($message->text, notification::NOTIFY_INFO);
438                         break;
439                 }
440             }
441         }
443         // Filter out policies already shown on their own page, keep just policies to be shown here on the consent page.
444         $data->policies = array_values(array_filter($this->policies, function ($policy) {
445             return $policy->agreementstyle == policy_version::AGREEMENTSTYLE_CONSENTPAGE;
446         }));
448         // If viewing docs in behalf of other user, get his/her full name and profile link.
449         if (!empty($this->behalfuser)) {
450             $userfullname = fullname($this->behalfuser, has_capability('moodle/site:viewfullnames', \context_system::instance()) ||
451                         has_capability('moodle/site:viewfullnames', \context_user::instance($this->behalfid)));
452             $data->behalfuser = html_writer::link(\context_user::instance($this->behalfid)->get_url(), $userfullname);
453         }
455         // User can cancel accepting policies only if it is a part of signup.
456         $data->cancancel = !isloggedin() || isguestuser();
458         return $data;
459     }