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