Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   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          $context = \context_course::instance($course->id);
 208          $a = new \stdClass();
 209          $a->field = format_string($translatedfieldname, true, array('context' => $context));
 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' => get_user_field_name('firstname'),
 337              'lastname' => get_user_field_name('lastname'),
 338              'email' => get_user_field_name('email'),
 339              'city' => get_user_field_name('city'),
 340              'country' => get_user_field_name('country'),
 341              'url' => get_user_field_name('url'),
 342              'icq' => get_user_field_name('icq'),
 343              'skype' => get_user_field_name('skype'),
 344              'aim' => get_user_field_name('aim'),
 345              'yahoo' => get_user_field_name('yahoo'),
 346              'msn' => get_user_field_name('msn'),
 347              'idnumber' => get_user_field_name('idnumber'),
 348              'institution' => get_user_field_name('institution'),
 349              'department' => get_user_field_name('department'),
 350              'phone1' => get_user_field_name('phone1'),
 351              'phone2' => get_user_field_name('phone2'),
 352              'address' => get_user_field_name('address')
 353          ];
 354      }
 355  
 356      /**
 357       * Gets data about custom profile fields. Cached statically in current
 358       * request.
 359       *
 360       * This only includes fields which can be tested by the system (those whose
 361       * data is cached in $USER object) - basically doesn't include textarea type
 362       * fields.
 363       *
 364       * @return array Array of records indexed by shortname
 365       */
 366      public static function get_custom_profile_fields() {
 367          global $DB, $CFG;
 368  
 369          if (self::$customprofilefields === null) {
 370              // Get fields and store them indexed by shortname.
 371              require_once($CFG->dirroot . '/user/profile/lib.php');
 372              $fields = profile_get_custom_fields(true);
 373              self::$customprofilefields = array();
 374              foreach ($fields as $field) {
 375                  self::$customprofilefields[$field->shortname] = $field;
 376              }
 377          }
 378          return self::$customprofilefields;
 379      }
 380  
 381      /**
 382       * Wipes the static cache (for use in unit tests).
 383       */
 384      public static function wipe_static_cache() {
 385          self::$customprofilefields = null;
 386      }
 387  
 388      /**
 389       * Return the value for a user's profile field
 390       *
 391       * @param int $userid User ID
 392       * @return string|bool Value, or false if user does not have a value for this field
 393       */
 394      protected function get_cached_user_profile_field($userid) {
 395          global $USER, $DB, $CFG;
 396          $iscurrentuser = $USER->id == $userid;
 397          if (isguestuser($userid) || ($iscurrentuser && !isloggedin())) {
 398              // Must be logged in and can't be the guest.
 399              return false;
 400          }
 401  
 402          // Custom profile fields will be numeric, there are no numeric standard profile fields so this is not a problem.
 403          $iscustomprofilefield = $this->customfield ? true : false;
 404          if ($iscustomprofilefield) {
 405              // As its a custom profile field we need to map the id back to the actual field.
 406              // We'll also preload all of the other custom profile fields just in case and ensure we have the
 407              // default value available as well.
 408              if (!array_key_exists($this->customfield, self::get_custom_profile_fields())) {
 409                  // No such field exists.
 410                  // This shouldn't normally happen but occur if things go wrong when deleting a custom profile field
 411                  // or when restoring a backup of a course with user profile field conditions.
 412                  return false;
 413              }
 414              $field = $this->customfield;
 415          } else {
 416              $field = $this->standardfield;
 417          }
 418  
 419          // If its the current user than most likely we will be able to get this information from $USER.
 420          // If its a regular profile field then it should already be available, if not then we have a mega problem.
 421          // If its a custom profile field then it should be available but may not be. If it is then we use the value
 422          // available, otherwise we load all custom profile fields into a temp object and refer to that.
 423          // Noting its not going be great for performance if we have to use the temp object as it involves loading the
 424          // custom profile field API and classes.
 425          if ($iscurrentuser) {
 426              if (!$iscustomprofilefield) {
 427                  if (property_exists($USER, $field)) {
 428                      return $USER->{$field};
 429                  } else {
 430                      // Unknown user field. This should not happen.
 431                      throw new \coding_exception('Requested user profile field does not exist');
 432                  }
 433              }
 434              // Checking if the custom profile fields are already available.
 435              if (!isset($USER->profile)) {
 436                  // Drat! they're not. We need to use a temp object and load them.
 437                  // We don't use $USER as the profile fields are loaded into the object.
 438                  $user = new \stdClass;
 439                  $user->id = $USER->id;
 440                  // This should ALWAYS be set, but just in case we check.
 441                  require_once($CFG->dirroot . '/user/profile/lib.php');
 442                  profile_load_custom_fields($user);
 443                  if (array_key_exists($field, $user->profile)) {
 444                      return $user->profile[$field];
 445                  }
 446              } else if (array_key_exists($field, $USER->profile)) {
 447                  // Hurrah they're available, this is easy.
 448                  return $USER->profile[$field];
 449              }
 450              // The profile field doesn't exist.
 451              return false;
 452          } else {
 453              // Loading for another user.
 454              if ($iscustomprofilefield) {
 455                  // Fetch the data for the field. Noting we keep this query simple so that Database caching takes care of performance
 456                  // for us (this will likely be hit again).
 457                  // We are able to do this because we've already pre-loaded the custom fields.
 458                  $data = $DB->get_field('user_info_data', 'data', array('userid' => $userid,
 459                          'fieldid' => self::$customprofilefields[$field]->id), IGNORE_MISSING);
 460                  // If we have data return that, otherwise return the default.
 461                  if ($data !== false) {
 462                      return $data;
 463                  } else {
 464                      return self::$customprofilefields[$field]->defaultdata;
 465                  }
 466              } else {
 467                  // Its a standard field, retrieve it from the user.
 468                  return $DB->get_field('user', $field, array('id' => $userid), MUST_EXIST);
 469              }
 470          }
 471          return false;
 472      }
 473  
 474      public function is_applied_to_user_lists() {
 475          // Profile conditions are assumed to be 'permanent', so they affect the
 476          // display of user lists for activities.
 477          return true;
 478      }
 479  
 480      public function filter_user_list(array $users, $not, \core_availability\info $info,
 481              \core_availability\capability_checker $checker) {
 482          global $CFG, $DB;
 483  
 484          // If the array is empty already, just return it.
 485          if (!$users) {
 486              return $users;
 487          }
 488  
 489          // Get all users from the list who match the condition.
 490          list ($sql, $params) = $DB->get_in_or_equal(array_keys($users));
 491  
 492          if ($this->customfield) {
 493              $customfields = self::get_custom_profile_fields();
 494              if (!array_key_exists($this->customfield, $customfields)) {
 495                  // If the field isn't found, nobody matches.
 496                  return array();
 497              }
 498              $customfield = $customfields[$this->customfield];
 499  
 500              // Fetch custom field value for all users.
 501              $values = $DB->get_records_select('user_info_data', 'fieldid = ? AND userid ' . $sql,
 502                      array_merge(array($customfield->id), $params),
 503                      '', 'userid, data');
 504              $valuefield = 'data';
 505              $default = $customfield->defaultdata;
 506          } else {
 507              $standardfields = self::get_standard_profile_fields();
 508              if (!array_key_exists($this->standardfield, $standardfields)) {
 509                  // If the field isn't found, nobody matches.
 510                  return [];
 511              }
 512              $values = $DB->get_records_select('user', 'id ' . $sql, $params,
 513                      '', 'id, '. $this->standardfield);
 514              $valuefield = $this->standardfield;
 515              $default = '';
 516          }
 517  
 518          // Filter the user list.
 519          $result = array();
 520          foreach ($users as $id => $user) {
 521              // Get value for user.
 522              if (array_key_exists($id, $values)) {
 523                  $value = $values[$id]->{$valuefield};
 524              } else {
 525                  $value = $default;
 526              }
 527  
 528              // Check value.
 529              $allow = $this->is_field_condition_met($this->operator, $value, $this->value);
 530              if ($not) {
 531                  $allow = !$allow;
 532              }
 533              if ($allow) {
 534                  $result[$id] = $user;
 535              }
 536          }
 537          return $result;
 538      }
 539  
 540      /**
 541       * Gets SQL to match a field against this condition. The second copy of the
 542       * field is in case you're using variables for the field so that it needs
 543       * to be two different ones.
 544       *
 545       * @param string $field Field name
 546       * @param string $field2 Second copy of field name (default same).
 547       * @param boolean $istext Any of the fields correspond to a TEXT column in database (true) or not (false).
 548       * @return array Array of SQL and parameters
 549       */
 550      private function get_condition_sql($field, $field2 = null, $istext = false) {
 551          global $DB;
 552          if (is_null($field2)) {
 553              $field2 = $field;
 554          }
 555  
 556          $params = array();
 557          switch($this->operator) {
 558              case self::OP_CONTAINS:
 559                  $sql = $DB->sql_like($field, self::unique_sql_parameter(
 560                          $params, '%' . $this->value . '%'));
 561                  break;
 562              case self::OP_DOES_NOT_CONTAIN:
 563                  if (empty($this->value)) {
 564                      // The 'does not contain nothing' expression matches everyone.
 565                      return null;
 566                  }
 567                  $sql = $DB->sql_like($field, self::unique_sql_parameter(
 568                          $params, '%' . $this->value . '%'), true, true, true);
 569                  break;
 570              case self::OP_IS_EQUAL_TO:
 571                  if ($istext) {
 572                      $sql = $DB->sql_compare_text($field) . ' = ' . $DB->sql_compare_text(
 573                              self::unique_sql_parameter($params, $this->value));
 574                  } else {
 575                      $sql = $field . ' = ' . self::unique_sql_parameter(
 576                              $params, $this->value);
 577                  }
 578                  break;
 579              case self::OP_STARTS_WITH:
 580                  $sql = $DB->sql_like($field, self::unique_sql_parameter(
 581                          $params, $this->value . '%'));
 582                  break;
 583              case self::OP_ENDS_WITH:
 584                  $sql = $DB->sql_like($field, self::unique_sql_parameter(
 585                          $params, '%' . $this->value));
 586                  break;
 587              case self::OP_IS_EMPTY:
 588                  // Mimic PHP empty() behaviour for strings, '0' or ''.
 589                  $emptystring = self::unique_sql_parameter($params, '');
 590                  if ($istext) {
 591                      $sql = '(' . $DB->sql_compare_text($field) . " IN ('0', $emptystring) OR $field2 IS NULL)";
 592                  } else {
 593                      $sql = '(' . $field . " IN ('0', $emptystring) OR $field2 IS NULL)";
 594                  }
 595                  break;
 596              case self::OP_IS_NOT_EMPTY:
 597                  $emptystring = self::unique_sql_parameter($params, '');
 598                  if ($istext) {
 599                      $sql = '(' . $DB->sql_compare_text($field) . " NOT IN ('0', $emptystring) AND $field2 IS NOT NULL)";
 600                  } else {
 601                      $sql = '(' . $field . " NOT IN ('0', $emptystring) AND $field2 IS NOT NULL)";
 602                  }
 603                  break;
 604          }
 605          return array($sql, $params);
 606      }
 607  
 608      public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
 609          global $DB;
 610  
 611          // Build suitable SQL depending on custom or standard field.
 612          if ($this->customfield) {
 613              $customfields = self::get_custom_profile_fields();
 614              if (!array_key_exists($this->customfield, $customfields)) {
 615                  // If the field isn't found, nobody matches.
 616                  return array('SELECT id FROM {user} WHERE 0 = 1', array());
 617              }
 618              $customfield = $customfields[$this->customfield];
 619  
 620              $mainparams = array();
 621              $tablesql = "LEFT JOIN {user_info_data} ud ON ud.fieldid = " .
 622                      self::unique_sql_parameter($mainparams, $customfield->id) .
 623                      " AND ud.userid = userids.id";
 624              list ($condition, $conditionparams) = $this->get_condition_sql('ud.data', null, true);
 625              $mainparams = array_merge($mainparams, $conditionparams);
 626  
 627              // If default is true, then allow that too.
 628              if ($this->is_field_condition_met(
 629                      $this->operator, $customfield->defaultdata, $this->value)) {
 630                  $where = "((ud.data IS NOT NULL AND $condition) OR (ud.data IS NULL))";
 631              } else {
 632                  $where = "(ud.data IS NOT NULL AND $condition)";
 633              }
 634          } else {
 635              $standardfields = self::get_standard_profile_fields();
 636              if (!array_key_exists($this->standardfield, $standardfields)) {
 637                  // If the field isn't found, nobody matches.
 638                  return ['SELECT id FROM {user} WHERE 0 = 1', []];
 639              }
 640              $tablesql = "JOIN {user} u ON u.id = userids.id";
 641              list ($where, $mainparams) = $this->get_condition_sql(
 642                      'u.' . $this->standardfield);
 643          }
 644  
 645          // Handle NOT.
 646          if ($not) {
 647              $where = 'NOT (' . $where . ')';
 648          }
 649  
 650          // Get enrolled user SQL and combine with this query.
 651          list ($enrolsql, $enrolparams) =
 652                  get_enrolled_sql($info->get_context(), '', 0, $onlyactive);
 653          $sql = "SELECT userids.id
 654                    FROM ($enrolsql) userids
 655                         $tablesql
 656                   WHERE $where";
 657          $params = array_merge($enrolparams, $mainparams);
 658          return array($sql, $params);
 659      }
 660  }