Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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  }