Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

   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   * User profile field condition.
  19   *
  20   * @package availability_profile
  21   * @copyright 2014 The Open University
  22   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace availability_profile;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * User profile field condition.
  31   *
  32   * @package availability_profile
  33   * @copyright 2014 The Open University
  34   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class condition extends \core_availability\condition {
  37      /** @var string Operator: field contains value */
  38      const OP_CONTAINS = 'contains';
  39  
  40      /** @var string Operator: field does not contain value */
  41      const OP_DOES_NOT_CONTAIN = 'doesnotcontain';
  42  
  43      /** @var string Operator: field equals value */
  44      const OP_IS_EQUAL_TO = 'isequalto';
  45  
  46      /** @var string Operator: field starts with value */
  47      const OP_STARTS_WITH = 'startswith';
  48  
  49      /** @var string Operator: field ends with value */
  50      const OP_ENDS_WITH = 'endswith';
  51  
  52      /** @var string Operator: field is empty */
  53      const OP_IS_EMPTY = 'isempty';
  54  
  55      /** @var string Operator: field is not empty */
  56      const OP_IS_NOT_EMPTY = 'isnotempty';
  57  
  58      /** @var array|null Array of custom profile fields (static cache within request) */
  59      protected static $customprofilefields = null;
  60  
  61      /** @var string Field name (for standard fields) or '' if custom field */
  62      protected $standardfield = '';
  63  
  64      /** @var int Field name (for custom fields) or '' if standard field */
  65      protected $customfield = '';
  66  
  67      /** @var string Operator type (OP_xx constant) */
  68      protected $operator;
  69  
  70      /** @var string Expected value for field */
  71      protected $value = '';
  72  
  73      /**
  74       * Constructor.
  75       *
  76       * @param \stdClass $structure Data structure from JSON decode
  77       * @throws \coding_exception If invalid data structure.
  78       */
  79      public function __construct($structure) {
  80          // Get operator.
  81          if (isset($structure->op) && in_array($structure->op, array(self::OP_CONTAINS,
  82                  self::OP_DOES_NOT_CONTAIN, self::OP_IS_EQUAL_TO, self::OP_STARTS_WITH,
  83                  self::OP_ENDS_WITH, self::OP_IS_EMPTY, self::OP_IS_NOT_EMPTY), true)) {
  84              $this->operator = $structure->op;
  85          } else {
  86              throw new \coding_exception('Missing or invalid ->op for profile condition');
  87          }
  88  
  89          // For operators other than the empty/not empty ones, require value.
  90          switch($this->operator) {
  91              case self::OP_IS_EMPTY:
  92              case self::OP_IS_NOT_EMPTY:
  93                  if (isset($structure->v)) {
  94                      throw new \coding_exception('Unexpected ->v for non-value operator');
  95                  }
  96                  break;
  97              default:
  98                  if (isset($structure->v) && is_string($structure->v)) {
  99                      $this->value = $structure->v;
 100                  } else {
 101                      throw new \coding_exception('Missing or invalid ->v for profile condition');
 102                  }
 103                  break;
 104          }
 105  
 106          // Get field type.
 107          if (property_exists($structure, 'sf')) {
 108              if (property_exists($structure, 'cf')) {
 109                  throw new \coding_exception('Both ->sf and ->cf for profile condition');
 110              }
 111              if (is_string($structure->sf)) {
 112                  $this->standardfield = $structure->sf;
 113              } else {
 114                  throw new \coding_exception('Invalid ->sf for profile condition');
 115              }
 116          } else if (property_exists($structure, 'cf')) {
 117              if (is_string($structure->cf)) {
 118                  $this->customfield = $structure->cf;
 119              } else {
 120                  throw new \coding_exception('Invalid ->cf for profile condition');
 121              }
 122          } else {
 123              throw new \coding_exception('Missing ->sf or ->cf for profile condition');
 124          }
 125      }
 126  
 127      public function save() {
 128          $result = (object)array('type' => 'profile', 'op' => $this->operator);
 129          if ($this->customfield) {
 130              $result->cf = $this->customfield;
 131          } else {
 132              $result->sf = $this->standardfield;
 133          }
 134          switch($this->operator) {
 135              case self::OP_IS_EMPTY:
 136              case self::OP_IS_NOT_EMPTY:
 137                  break;
 138              default:
 139                  $result->v = $this->value;
 140                  break;
 141          }
 142          return $result;
 143      }
 144  
 145      /**
 146       * Returns a JSON object which corresponds to a condition of this type.
 147       *
 148       * Intended for unit testing, as normally the JSON values are constructed
 149       * by JavaScript code.
 150       *
 151       * @param bool $customfield True if this is a custom field
 152       * @param string $fieldname Field name
 153       * @param string $operator Operator name (OP_xx constant)
 154       * @param string|null $value Value (not required for some operator types)
 155       * @return stdClass Object representing condition
 156       */
 157      public static function get_json($customfield, $fieldname, $operator, $value = null) {
 158          $result = (object)array('type' => 'profile', 'op' => $operator);
 159          if ($customfield) {
 160              $result->cf = $fieldname;
 161          } else {
 162              $result->sf = $fieldname;
 163          }
 164          switch ($operator) {
 165              case self::OP_IS_EMPTY:
 166              case self::OP_IS_NOT_EMPTY:
 167                  break;
 168              default:
 169                  if (is_null($value)) {
 170                      throw new \coding_exception('Operator requires value');
 171                  }
 172                  $result->v = $value;
 173                  break;
 174          }
 175          return $result;
 176      }
 177  
 178      public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
 179          $uservalue = $this->get_cached_user_profile_field($userid);
 180          $allow = self::is_field_condition_met($this->operator, $uservalue, $this->value);
 181          if ($not) {
 182              $allow = !$allow;
 183          }
 184          return $allow;
 185      }
 186  
 187      public function get_description($full, $not, \core_availability\info $info) {
 188          $course = $info->get_course();
 189          // Display the fieldname into current lang.
 190          if ($this->customfield) {
 191              // Is a custom profile field (will use multilang).
 192              $customfields = self::get_custom_profile_fields();
 193              if (array_key_exists($this->customfield, $customfields)) {
 194                  $translatedfieldname = $customfields[$this->customfield]->name;
 195              } else {
 196                  $translatedfieldname = get_string('missing', 'availability_profile',
 197                          $this->customfield);
 198              }
 199          } else {
 200              $standardfields = self::get_standard_profile_fields();
 201              if (array_key_exists($this->standardfield, $standardfields)) {
 202                  $translatedfieldname = $standardfields[$this->standardfield];
 203              } else {
 204                  $translatedfieldname = get_string('missing', 'availability_profile', $this->standardfield);
 205              }
 206          }
 207          $a = new \stdClass();
 208          // Not safe to call format_string here; use the special function to call it later.
 209          $a->field = self::description_format_string($translatedfieldname);
 210          $a->value = s($this->value);
 211          if ($not) {
 212              // When doing NOT strings, we replace the operator with its inverse.
 213              // Some of them don't have inverses, so for those we use a new
 214              // identifier which is only used for this lang string.
 215              switch($this->operator) {
 216                  case self::OP_CONTAINS:
 217                      $opname = self::OP_DOES_NOT_CONTAIN;
 218                      break;
 219                  case self::OP_DOES_NOT_CONTAIN:
 220                      $opname = self::OP_CONTAINS;
 221                      break;
 222                  case self::OP_ENDS_WITH:
 223                      $opname = 'notendswith';
 224                      break;
 225                  case self::OP_IS_EMPTY:
 226                      $opname = self::OP_IS_NOT_EMPTY;
 227                      break;
 228                  case self::OP_IS_EQUAL_TO:
 229                      $opname = 'notisequalto';
 230                      break;
 231                  case self::OP_IS_NOT_EMPTY:
 232                      $opname = self::OP_IS_EMPTY;
 233                      break;
 234                  case self::OP_STARTS_WITH:
 235                      $opname = 'notstartswith';
 236                      break;
 237                  default:
 238                      throw new \coding_exception('Unexpected operator: ' . $this->operator);
 239              }
 240          } else {
 241              $opname = $this->operator;
 242          }
 243          return get_string('requires_' . $opname, 'availability_profile', $a);
 244      }
 245  
 246      protected function get_debug_string() {
 247          if ($this->customfield) {
 248              $out = '*' . $this->customfield;
 249          } else {
 250              $out = $this->standardfield;
 251          }
 252          $out .= ' ' . $this->operator;
 253          switch($this->operator) {
 254              case self::OP_IS_EMPTY:
 255              case self::OP_IS_NOT_EMPTY:
 256                  break;
 257              default:
 258                  $out .= ' ' . $this->value;
 259                  break;
 260          }
 261          return $out;
 262      }
 263  
 264      /**
 265       * Returns true if a field meets the required conditions, false otherwise.
 266       *
 267       * @param string $operator the requirement/condition
 268       * @param string $uservalue the user's value
 269       * @param string $value the value required
 270       * @return boolean True if conditions are met
 271       */
 272      protected static function is_field_condition_met($operator, $uservalue, $value) {
 273          if ($uservalue === false) {
 274              // If the user value is false this is an instant fail.
 275              // All user values come from the database as either data or the default.
 276              // They will always be a string.
 277              return false;
 278          }
 279          $fieldconditionmet = true;
 280          // Just to be doubly sure it is a string.
 281          $uservalue = (string)$uservalue;
 282          switch($operator) {
 283              case self::OP_CONTAINS:
 284                  $pos = strpos($uservalue, $value);
 285                  if ($pos === false) {
 286                      $fieldconditionmet = false;
 287                  }
 288                  break;
 289              case self::OP_DOES_NOT_CONTAIN:
 290                  if (!empty($value)) {
 291                      $pos = strpos($uservalue, $value);
 292                      if ($pos !== false) {
 293                          $fieldconditionmet = false;
 294                      }
 295                  }
 296                  break;
 297              case self::OP_IS_EQUAL_TO:
 298                  if ($value !== $uservalue) {
 299                      $fieldconditionmet = false;
 300                  }
 301                  break;
 302              case self::OP_STARTS_WITH:
 303                  $length = strlen($value);
 304                  if ((substr($uservalue, 0, $length) !== $value)) {
 305                      $fieldconditionmet = false;
 306                  }
 307                  break;
 308              case self::OP_ENDS_WITH:
 309                  $length = strlen($value);
 310                  $start = $length * -1;
 311                  if (substr($uservalue, $start) !== $value) {
 312                      $fieldconditionmet = false;
 313                  }
 314                  break;
 315              case self::OP_IS_EMPTY:
 316                  if (!empty($uservalue)) {
 317                      $fieldconditionmet = false;
 318                  }
 319                  break;
 320              case self::OP_IS_NOT_EMPTY:
 321                  if (empty($uservalue)) {
 322                      $fieldconditionmet = false;
 323                  }
 324                  break;
 325          }
 326          return $fieldconditionmet;
 327      }
 328  
 329      /**
 330       * Return list of standard user profile fields used by the condition
 331       *
 332       * @return string[]
 333       */
 334      public static function get_standard_profile_fields(): array {
 335          return [
 336              'firstname' => \core_user\fields::get_display_name('firstname'),
 337              'lastname' => \core_user\fields::get_display_name('lastname'),
 338              'email' => \core_user\fields::get_display_name('email'),
 339              'city' => \core_user\fields::get_display_name('city'),
 340              'country' => \core_user\fields::get_display_name('country'),
 341              'idnumber' => \core_user\fields::get_display_name('idnumber'),
 342              'institution' => \core_user\fields::get_display_name('institution'),
 343              'department' => \core_user\fields::get_display_name('department'),
 344              'phone1' => \core_user\fields::get_display_name('phone1'),
 345              'phone2' => \core_user\fields::get_display_name('phone2'),
 346              'address' => \core_user\fields::get_display_name('address'),
 347          ];
 348      }
 349  
 350      /**
 351       * Gets data about custom profile fields. Cached statically in current
 352       * request.
 353       *
 354       * This only includes fields which can be tested by the system (those whose
 355       * data is cached in $USER object) - basically doesn't include textarea type
 356       * fields.
 357       *
 358       * @return array Array of records indexed by shortname
 359       */
 360      public static function get_custom_profile_fields() {
 361          global $DB, $CFG;
 362  
 363          if (self::$customprofilefields === null) {
 364              // Get fields and store them indexed by shortname.
 365              require_once($CFG->dirroot . '/user/profile/lib.php');
 366              $fields = profile_get_custom_fields(true);
 367              self::$customprofilefields = array();
 368              foreach ($fields as $field) {
 369                  self::$customprofilefields[$field->shortname] = $field;
 370              }
 371          }
 372          return self::$customprofilefields;
 373      }
 374  
 375      /**
 376       * Wipes the static cache (for use in unit tests).
 377       */
 378      public static function wipe_static_cache() {
 379          self::$customprofilefields = null;
 380      }
 381  
 382      /**
 383       * Return the value for a user's profile field
 384       *
 385       * @param int $userid User ID
 386       * @return string|bool Value, or false if user does not have a value for this field
 387       */
 388      protected function get_cached_user_profile_field($userid) {
 389          global $USER, $DB, $CFG;
 390          $iscurrentuser = $USER->id == $userid;
 391          if (isguestuser($userid) || ($iscurrentuser && !isloggedin())) {
 392              // Must be logged in and can't be the guest.
 393              return false;
 394          }
 395  
 396          // Custom profile fields will be numeric, there are no numeric standard profile fields so this is not a problem.
 397          $iscustomprofilefield = $this->customfield ? true : false;
 398          if ($iscustomprofilefield) {
 399              // As its a custom profile field we need to map the id back to the actual field.
 400              // We'll also preload all of the other custom profile fields just in case and ensure we have the
 401              // default value available as well.
 402              if (!array_key_exists($this->customfield, self::get_custom_profile_fields())) {
 403                  // No such field exists.
 404                  // This shouldn't normally happen but occur if things go wrong when deleting a custom profile field
 405                  // or when restoring a backup of a course with user profile field conditions.
 406                  return false;
 407              }
 408              $field = $this->customfield;
 409          } else {
 410              $field = $this->standardfield;
 411          }
 412  
 413          // If its the current user than most likely we will be able to get this information from $USER.
 414          // If its a regular profile field then it should already be available, if not then we have a mega problem.
 415          // If its a custom profile field then it should be available but may not be. If it is then we use the value
 416          // available, otherwise we load all custom profile fields into a temp object and refer to that.
 417          // Noting its not going be great for performance if we have to use the temp object as it involves loading the
 418          // custom profile field API and classes.
 419          if ($iscurrentuser) {
 420              if (!$iscustomprofilefield) {
 421                  if (property_exists($USER, $field)) {
 422                      return $USER->{$field};
 423                  } else {
 424                      // Unknown user field. This should not happen.
 425                      throw new \coding_exception('Requested user profile field does not exist');
 426                  }
 427              }
 428              // Checking if the custom profile fields are already available.
 429              if (!isset($USER->profile)) {
 430                  // Drat! they're not. We need to use a temp object and load them.
 431                  // We don't use $USER as the profile fields are loaded into the object.
 432                  $user = new \stdClass;
 433                  $user->id = $USER->id;
 434                  // This should ALWAYS be set, but just in case we check.
 435                  require_once($CFG->dirroot . '/user/profile/lib.php');
 436                  profile_load_custom_fields($user);
 437                  if (array_key_exists($field, $user->profile)) {
 438                      return $user->profile[$field];
 439                  }
 440              } else if (array_key_exists($field, $USER->profile)) {
 441                  // Hurrah they're available, this is easy.
 442                  return $USER->profile[$field];
 443              }
 444              // The profile field doesn't exist.
 445              return false;
 446          } else {
 447              // Loading for another user.
 448              if ($iscustomprofilefield) {
 449                  // Fetch the data for the field. Noting we keep this query simple so that Database caching takes care of performance
 450                  // for us (this will likely be hit again).
 451                  // We are able to do this because we've already pre-loaded the custom fields.
 452                  $data = $DB->get_field('user_info_data', 'data', array('userid' => $userid,
 453                          'fieldid' => self::$customprofilefields[$field]->id), IGNORE_MISSING);
 454                  // If we have data return that, otherwise return the default.
 455                  if ($data !== false) {
 456                      return $data;
 457                  } else {
 458                      return self::$customprofilefields[$field]->defaultdata;
 459                  }
 460              } else {
 461                  // Its a standard field, retrieve it from the user.
 462                  return $DB->get_field('user', $field, array('id' => $userid), MUST_EXIST);
 463              }
 464          }
 465          return false;
 466      }
 467  
 468      public function is_applied_to_user_lists() {
 469          // Profile conditions are assumed to be 'permanent', so they affect the
 470          // display of user lists for activities.
 471          return true;
 472      }
 473  
 474      public function filter_user_list(array $users, $not, \core_availability\info $info,
 475              \core_availability\capability_checker $checker) {
 476          global $CFG, $DB;
 477  
 478          // If the array is empty already, just return it.
 479          if (!$users) {
 480              return $users;
 481          }
 482  
 483          // Get all users from the list who match the condition.
 484          list ($sql, $params) = $DB->get_in_or_equal(array_keys($users));
 485  
 486          if ($this->customfield) {
 487              $customfields = self::get_custom_profile_fields();
 488              if (!array_key_exists($this->customfield, $customfields)) {
 489                  // If the field isn't found, nobody matches.
 490                  return array();
 491              }
 492              $customfield = $customfields[$this->customfield];
 493  
 494              // Fetch custom field value for all users.
 495              $values = $DB->get_records_select('user_info_data', 'fieldid = ? AND userid ' . $sql,
 496                      array_merge(array($customfield->id), $params),
 497                      '', 'userid, data');
 498              $valuefield = 'data';
 499              $default = $customfield->defaultdata;
 500          } else {
 501              $standardfields = self::get_standard_profile_fields();
 502              if (!array_key_exists($this->standardfield, $standardfields)) {
 503                  // If the field isn't found, nobody matches.
 504                  return [];
 505              }
 506              $values = $DB->get_records_select('user', 'id ' . $sql, $params,
 507                      '', 'id, '. $this->standardfield);
 508              $valuefield = $this->standardfield;
 509              $default = '';
 510          }
 511  
 512          // Filter the user list.
 513          $result = array();
 514          foreach ($users as $id => $user) {
 515              // Get value for user.
 516              if (array_key_exists($id, $values)) {
 517                  $value = $values[$id]->{$valuefield};
 518              } else {
 519                  $value = $default;
 520              }
 521  
 522              // Check value.
 523              $allow = $this->is_field_condition_met($this->operator, $value, $this->value);
 524              if ($not) {
 525                  $allow = !$allow;
 526              }
 527              if ($allow) {
 528                  $result[$id] = $user;
 529              }
 530          }
 531          return $result;
 532      }
 533  
 534      /**
 535       * Gets SQL to match a field against this condition. The second copy of the
 536       * field is in case you're using variables for the field so that it needs
 537       * to be two different ones.
 538       *
 539       * @param string $field Field name
 540       * @param string $field2 Second copy of field name (default same).
 541       * @param boolean $istext Any of the fields correspond to a TEXT column in database (true) or not (false).
 542       * @return array Array of SQL and parameters
 543       */
 544      private function get_condition_sql($field, $field2 = null, $istext = false) {
 545          global $DB;
 546          if (is_null($field2)) {
 547              $field2 = $field;
 548          }
 549  
 550          $params = array();
 551          switch($this->operator) {
 552              case self::OP_CONTAINS:
 553                  $sql = $DB->sql_like($field, self::unique_sql_parameter(
 554                          $params, '%' . $this->value . '%'));
 555                  break;
 556              case self::OP_DOES_NOT_CONTAIN:
 557                  if (empty($this->value)) {
 558                      // The 'does not contain nothing' expression matches everyone.
 559                      return null;
 560                  }
 561                  $sql = $DB->sql_like($field, self::unique_sql_parameter(
 562                          $params, '%' . $this->value . '%'), true, true, true);
 563                  break;
 564              case self::OP_IS_EQUAL_TO:
 565                  if ($istext) {
 566                      $sql = $DB->sql_compare_text($field) . ' = ' . $DB->sql_compare_text(
 567                              self::unique_sql_parameter($params, $this->value));
 568                  } else {
 569                      $sql = $field . ' = ' . self::unique_sql_parameter(
 570                              $params, $this->value);
 571                  }
 572                  break;
 573              case self::OP_STARTS_WITH:
 574                  $sql = $DB->sql_like($field, self::unique_sql_parameter(
 575                          $params, $this->value . '%'));
 576                  break;
 577              case self::OP_ENDS_WITH:
 578                  $sql = $DB->sql_like($field, self::unique_sql_parameter(
 579                          $params, '%' . $this->value));
 580                  break;
 581              case self::OP_IS_EMPTY:
 582                  // Mimic PHP empty() behaviour for strings, '0' or ''.
 583                  $emptystring = self::unique_sql_parameter($params, '');
 584                  if ($istext) {
 585                      $sql = '(' . $DB->sql_compare_text($field) . " IN ('0', $emptystring) OR $field2 IS NULL)";
 586                  } else {
 587                      $sql = '(' . $field . " IN ('0', $emptystring) OR $field2 IS NULL)";
 588                  }
 589                  break;
 590              case self::OP_IS_NOT_EMPTY:
 591                  $emptystring = self::unique_sql_parameter($params, '');
 592                  if ($istext) {
 593                      $sql = '(' . $DB->sql_compare_text($field) . " NOT IN ('0', $emptystring) AND $field2 IS NOT NULL)";
 594                  } else {
 595                      $sql = '(' . $field . " NOT IN ('0', $emptystring) AND $field2 IS NOT NULL)";
 596                  }
 597                  break;
 598          }
 599          return array($sql, $params);
 600      }
 601  
 602      public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
 603          global $DB;
 604  
 605          // Build suitable SQL depending on custom or standard field.
 606          if ($this->customfield) {
 607              $customfields = self::get_custom_profile_fields();
 608              if (!array_key_exists($this->customfield, $customfields)) {
 609                  // If the field isn't found, nobody matches.
 610                  return array('SELECT id FROM {user} WHERE 0 = 1', array());
 611              }
 612              $customfield = $customfields[$this->customfield];
 613  
 614              $mainparams = array();
 615              $tablesql = "LEFT JOIN {user_info_data} ud ON ud.fieldid = " .
 616                      self::unique_sql_parameter($mainparams, $customfield->id) .
 617                      " AND ud.userid = userids.id";
 618              list ($condition, $conditionparams) = $this->get_condition_sql('ud.data', null, true);
 619              $mainparams = array_merge($mainparams, $conditionparams);
 620  
 621              // If default is true, then allow that too.
 622              if ($this->is_field_condition_met(
 623                      $this->operator, $customfield->defaultdata, $this->value)) {
 624                  $where = "((ud.data IS NOT NULL AND $condition) OR (ud.data IS NULL))";
 625              } else {
 626                  $where = "(ud.data IS NOT NULL AND $condition)";
 627              }
 628          } else {
 629              $standardfields = self::get_standard_profile_fields();
 630              if (!array_key_exists($this->standardfield, $standardfields)) {
 631                  // If the field isn't found, nobody matches.
 632                  return ['SELECT id FROM {user} WHERE 0 = 1', []];
 633              }
 634              $tablesql = "JOIN {user} u ON u.id = userids.id";
 635              list ($where, $mainparams) = $this->get_condition_sql(
 636                      'u.' . $this->standardfield);
 637          }
 638  
 639          // Handle NOT.
 640          if ($not) {
 641              $where = 'NOT (' . $where . ')';
 642          }
 643  
 644          // Get enrolled user SQL and combine with this query.
 645          list ($enrolsql, $enrolparams) =
 646                  get_enrolled_sql($info->get_context(), '', 0, $onlyactive);
 647          $sql = "SELECT userids.id
 648                    FROM ($enrolsql) userids
 649                         $tablesql
 650                   WHERE $where";
 651          $params = array_merge($enrolparams, $mainparams);
 652          return array($sql, $params);
 653      }
 654  }