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.
/auth/db/ -> auth.php (source)

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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   * Authentication Plugin: External Database Authentication
  19   *
  20   * Checks against an external database.
  21   *
  22   * @package    auth_db
  23   * @author     Martin Dougiamas
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  require_once($CFG->libdir.'/authlib.php');
  30  
  31  /**
  32   * External database authentication plugin.
  33   */
  34  class auth_plugin_db extends auth_plugin_base {
  35  
  36      /**
  37       * Constructor.
  38       */
  39      function __construct() {
  40          global $CFG;
  41          require_once($CFG->libdir.'/adodb/adodb.inc.php');
  42  
  43          $this->authtype = 'db';
  44          $this->config = get_config('auth_db');
  45          $this->errorlogtag = '[AUTH DB] ';
  46          if (empty($this->config->extencoding)) {
  47              $this->config->extencoding = 'utf-8';
  48          }
  49      }
  50  
  51      /**
  52       * Returns true if the username and password work and false if they are
  53       * wrong or don't exist.
  54       *
  55       * @param string $username The username
  56       * @param string $password The password
  57       * @return bool Authentication success or failure.
  58       */
  59      function user_login($username, $password) {
  60          global $CFG, $DB;
  61  
  62          if ($this->is_configured() === false) {
  63              debugging(get_string('auth_notconfigured', 'auth', $this->authtype));
  64              return false;
  65          }
  66  
  67          $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
  68          $extpassword = core_text::convert($password, 'utf-8', $this->config->extencoding);
  69  
  70          if ($this->is_internal()) {
  71              // Lookup username externally, but resolve
  72              // password locally -- to support backend that
  73              // don't track passwords.
  74  
  75              if (isset($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_KEEP) {
  76                  // No need to connect to external database in this case because users are never removed and we verify password locally.
  77                  if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
  78                      return validate_internal_user_password($user, $password);
  79                  } else {
  80                      return false;
  81                  }
  82              }
  83  
  84              $authdb = $this->db_init();
  85  
  86              $rs = $authdb->Execute("SELECT *
  87                                        FROM {$this->config->table}
  88                                       WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
  89              if (!$rs) {
  90                  $authdb->Close();
  91                  debugging(get_string('auth_dbcantconnect','auth_db'));
  92                  return false;
  93              }
  94  
  95              if (!$rs->EOF) {
  96                  $rs->Close();
  97                  $authdb->Close();
  98                  // User exists externally - check username/password internally.
  99                  if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
 100                      return validate_internal_user_password($user, $password);
 101                  }
 102              } else {
 103                  $rs->Close();
 104                  $authdb->Close();
 105                  // User does not exist externally.
 106                  return false;
 107              }
 108  
 109          } else {
 110              // Normal case: use external db for both usernames and passwords.
 111  
 112              $authdb = $this->db_init();
 113  
 114              $rs = $authdb->Execute("SELECT {$this->config->fieldpass}
 115                                        FROM {$this->config->table}
 116                                       WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
 117              if (!$rs) {
 118                  $authdb->Close();
 119                  debugging(get_string('auth_dbcantconnect','auth_db'));
 120                  return false;
 121              }
 122  
 123              if ($rs->EOF) {
 124                  $authdb->Close();
 125                  return false;
 126              }
 127  
 128              $fields = array_change_key_case($rs->fields, CASE_LOWER);
 129              $fromdb = $fields[strtolower($this->config->fieldpass)];
 130              $rs->Close();
 131              $authdb->Close();
 132  
 133              if ($this->config->passtype === 'plaintext') {
 134                  return ($fromdb === $extpassword);
 135              } else if ($this->config->passtype === 'md5') {
 136                  return (strtolower($fromdb) === md5($extpassword));
 137              } else if ($this->config->passtype === 'sha1') {
 138                  return (strtolower($fromdb) === sha1($extpassword));
 139              } else if ($this->config->passtype === 'saltedcrypt') {
 140                  return password_verify($extpassword, $fromdb);
 141              } else {
 142                  return false;
 143              }
 144  
 145          }
 146      }
 147  
 148      /**
 149       * Connect to external database.
 150       *
 151       * @return ADOConnection
 152       * @throws moodle_exception
 153       */
 154      function db_init() {
 155          if ($this->is_configured() === false) {
 156              throw new moodle_exception('auth_dbcantconnect', 'auth_db');
 157          }
 158  
 159          // Connect to the external database (forcing new connection).
 160          $authdb = ADONewConnection($this->config->type);
 161          if (!empty($this->config->debugauthdb)) {
 162              $authdb->debug = true;
 163              ob_start(); //Start output buffer to allow later use of the page headers.
 164          }
 165          $authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true);
 166          $authdb->SetFetchMode(ADODB_FETCH_ASSOC);
 167          if (!empty($this->config->setupsql)) {
 168              $authdb->Execute($this->config->setupsql);
 169          }
 170  
 171          return $authdb;
 172      }
 173  
 174      /**
 175       * Returns user attribute mappings between moodle and the external database.
 176       *
 177       * @return array
 178       */
 179      function db_attributes() {
 180          $moodleattributes = array();
 181          // If we have custom fields then merge them with user fields.
 182          $customfields = $this->get_custom_user_profile_fields();
 183          if (!empty($customfields) && !empty($this->userfields)) {
 184              $userfields = array_merge($this->userfields, $customfields);
 185          } else {
 186              $userfields = $this->userfields;
 187          }
 188  
 189          foreach ($userfields as $field) {
 190              if (!empty($this->config->{"field_map_$field"})) {
 191                  $moodleattributes[$field] = $this->config->{"field_map_$field"};
 192              }
 193          }
 194          $moodleattributes['username'] = $this->config->fielduser;
 195          return $moodleattributes;
 196      }
 197  
 198      /**
 199       * Reads any other information for a user from external database,
 200       * then returns it in an array.
 201       *
 202       * @param string $username
 203       * @return array
 204       */
 205      function get_userinfo($username) {
 206          global $CFG;
 207  
 208          $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
 209  
 210          $authdb = $this->db_init();
 211  
 212          // Array to map local fieldnames we want, to external fieldnames.
 213          $selectfields = $this->db_attributes();
 214  
 215          $result = array();
 216          // If at least one field is mapped from external db, get that mapped data.
 217          if ($selectfields) {
 218              $select = array();
 219              $fieldcount = 0;
 220              foreach ($selectfields as $localname=>$externalname) {
 221                  // Without aliasing, multiple occurrences of the same external
 222                  // name can coalesce in only occurrence in the result.
 223                  $select[] = "$externalname AS F".$fieldcount;
 224                  $fieldcount++;
 225              }
 226              $select = implode(', ', $select);
 227              $sql = "SELECT $select
 228                        FROM {$this->config->table}
 229                       WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'";
 230  
 231              if ($rs = $authdb->Execute($sql)) {
 232                  if (!$rs->EOF) {
 233                      $fields = $rs->FetchRow();
 234                      // Convert the associative array to an array of its values so we don't have to worry about the case of its keys.
 235                      $fields = array_values($fields);
 236                      foreach (array_keys($selectfields) as $index => $localname) {
 237                          $value = $fields[$index];
 238                          $result[$localname] = core_text::convert($value, $this->config->extencoding, 'utf-8');
 239                       }
 240                   }
 241                   $rs->Close();
 242              }
 243          }
 244          $authdb->Close();
 245          return $result;
 246      }
 247  
 248      /**
 249       * Change a user's password.
 250       *
 251       * @param  stdClass  $user      User table object
 252       * @param  string  $newpassword Plaintext password
 253       * @return bool                 True on success
 254       */
 255      function user_update_password($user, $newpassword) {
 256          global $DB;
 257  
 258          if ($this->is_internal()) {
 259              $puser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
 260              // This will also update the stored hash to the latest algorithm
 261              // if the existing hash is using an out-of-date algorithm (or the
 262              // legacy md5 algorithm).
 263              if (update_internal_user_password($puser, $newpassword)) {
 264                  $user->password = $puser->password;
 265                  return true;
 266              } else {
 267                  return false;
 268              }
 269          } else {
 270              // We should have never been called!
 271              return false;
 272          }
 273      }
 274  
 275      /**
 276       * Synchronizes user from external db to moodle user table.
 277       *
 278       * Sync should be done by using idnumber attribute, not username.
 279       * You need to pass firstsync parameter to function to fill in
 280       * idnumbers if they don't exists in moodle user table.
 281       *
 282       * Syncing users removes (disables) users that don't exists anymore in external db.
 283       * Creates new users and updates coursecreator status of users.
 284       *
 285       * This implementation is simpler but less scalable than the one found in the LDAP module.
 286       *
 287       * @param progress_trace $trace
 288       * @param bool $do_updates  Optional: set to true to force an update of existing accounts
 289       * @return int 0 means success, 1 means failure
 290       */
 291      function sync_users(progress_trace $trace, $do_updates=false) {
 292          global $CFG, $DB;
 293  
 294          require_once($CFG->dirroot . '/user/lib.php');
 295  
 296          // List external users.
 297          $userlist = $this->get_userlist();
 298  
 299          // Delete obsolete internal users.
 300          if (!empty($this->config->removeuser)) {
 301  
 302              $suspendselect = "";
 303              if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
 304                  $suspendselect = "AND u.suspended = 0";
 305              }
 306  
 307              // Find obsolete users.
 308              if (count($userlist)) {
 309                  $removeusers = array();
 310                  $params['authtype'] = $this->authtype;
 311                  $sql = "SELECT u.id, u.username
 312                            FROM {user} u
 313                           WHERE u.auth=:authtype
 314                             AND u.deleted=0
 315                             AND u.mnethostid=:mnethostid
 316                             $suspendselect";
 317                  $params['mnethostid'] = $CFG->mnet_localhost_id;
 318                  $internalusersrs = $DB->get_recordset_sql($sql, $params);
 319  
 320                  $usernamelist = array_flip($userlist);
 321                  foreach ($internalusersrs as $internaluser) {
 322                      if (!array_key_exists($internaluser->username, $usernamelist)) {
 323                          $removeusers[] = $internaluser;
 324                      }
 325                  }
 326                  $internalusersrs->close();
 327              } else {
 328                  $sql = "SELECT u.id, u.username
 329                            FROM {user} u
 330                           WHERE u.auth=:authtype AND u.deleted=0 AND u.mnethostid=:mnethostid $suspendselect";
 331                  $params = array();
 332                  $params['authtype'] = $this->authtype;
 333                  $params['mnethostid'] = $CFG->mnet_localhost_id;
 334                  $removeusers = $DB->get_records_sql($sql, $params);
 335              }
 336  
 337              if (!empty($removeusers)) {
 338                  $trace->output(get_string('auth_dbuserstoremove', 'auth_db', count($removeusers)));
 339  
 340                  foreach ($removeusers as $user) {
 341                      if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
 342                          delete_user($user);
 343                          $trace->output(get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
 344                      } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
 345                          $updateuser = new stdClass();
 346                          $updateuser->id   = $user->id;
 347                          $updateuser->suspended = 1;
 348                          user_update_user($updateuser, false);
 349                          $trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
 350                      }
 351                  }
 352              }
 353              unset($removeusers);
 354          }
 355  
 356          if (!count($userlist)) {
 357              // Exit right here, nothing else to do.
 358              $trace->finished();
 359              return 0;
 360          }
 361  
 362          // Update existing accounts.
 363          if ($do_updates) {
 364              // Narrow down what fields we need to update.
 365              $all_keys = array_keys(get_object_vars($this->config));
 366              $updatekeys = array();
 367              foreach ($all_keys as $key) {
 368                  if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
 369                      if ($this->config->{$key} === 'onlogin') {
 370                          array_push($updatekeys, $match[1]); // The actual key name.
 371                      }
 372                  }
 373              }
 374              unset($all_keys); unset($key);
 375  
 376              // Only go ahead if we actually have fields to update locally.
 377              if (!empty($updatekeys)) {
 378                  $update_users = array();
 379                  // All the drivers can cope with chunks of 10,000. See line 4491 of lib/dml/tests/dml_est.php
 380                  $userlistchunks = array_chunk($userlist , 10000);
 381                  foreach($userlistchunks as $userlistchunk) {
 382                      list($in_sql, $params) = $DB->get_in_or_equal($userlistchunk, SQL_PARAMS_NAMED, 'u', true);
 383                      $params['authtype'] = $this->authtype;
 384                      $params['mnethostid'] = $CFG->mnet_localhost_id;
 385                      $sql = "SELECT u.id, u.username, u.suspended
 386                            FROM {user} u
 387                           WHERE u.auth = :authtype AND u.deleted = 0 AND u.mnethostid = :mnethostid AND u.username {$in_sql}";
 388                      $update_users = $update_users + $DB->get_records_sql($sql, $params);
 389                  }
 390  
 391                  if ($update_users) {
 392                      $trace->output("User entries to update: ".count($update_users));
 393  
 394                      foreach ($update_users as $user) {
 395                          if ($this->update_user_record($user->username, $updatekeys, false, (bool) $user->suspended)) {
 396                              $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
 397                          } else {
 398                              $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))." - ".get_string('skipped'), 1);
 399                          }
 400                      }
 401                      unset($update_users);
 402                  }
 403              }
 404          }
 405  
 406  
 407          // Create missing accounts.
 408          // NOTE: this is very memory intensive and generally inefficient.
 409          $suspendselect = "";
 410          if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
 411              $suspendselect = "AND u.suspended = 0";
 412          }
 413          $sql = "SELECT u.id, u.username
 414                    FROM {user} u
 415                   WHERE u.auth=:authtype AND u.deleted='0' AND mnethostid=:mnethostid $suspendselect";
 416  
 417          $users = $DB->get_records_sql($sql, array('authtype'=>$this->authtype, 'mnethostid'=>$CFG->mnet_localhost_id));
 418  
 419          // Simplify down to usernames.
 420          $usernames = array();
 421          if (!empty($users)) {
 422              foreach ($users as $user) {
 423                  array_push($usernames, $user->username);
 424              }
 425              unset($users);
 426          }
 427  
 428          $add_users = array_diff($userlist, $usernames);
 429          unset($usernames);
 430  
 431          if (!empty($add_users)) {
 432              $trace->output(get_string('auth_dbuserstoadd','auth_db',count($add_users)));
 433              // Do not use transactions around this foreach, we want to skip problematic users, not revert everything.
 434              foreach($add_users as $user) {
 435                  $username = $user;
 436                  if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
 437                      if ($olduser = $DB->get_record('user', array('username' => $username, 'deleted' => 0, 'suspended' => 1,
 438                              'mnethostid' => $CFG->mnet_localhost_id, 'auth' => $this->authtype))) {
 439                          $updateuser = new stdClass();
 440                          $updateuser->id = $olduser->id;
 441                          $updateuser->suspended = 0;
 442                          user_update_user($updateuser);
 443                          $trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name' => $username,
 444                              'id' => $olduser->id)), 1);
 445                          continue;
 446                      }
 447                  }
 448  
 449                  // Do not try to undelete users here, instead select suspending if you ever expect users will reappear.
 450  
 451                  // Prep a few params.
 452                  $user = $this->get_userinfo_asobj($user);
 453                  $user->username   = $username;
 454                  $user->confirmed  = 1;
 455                  $user->auth       = $this->authtype;
 456                  $user->mnethostid = $CFG->mnet_localhost_id;
 457  
 458                  if ($collision = $DB->get_record_select('user', "username = :username AND mnethostid = :mnethostid AND auth <> :auth", array('username'=>$user->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype), 'id,username,auth')) {
 459                      $trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)), 1);
 460                      continue;
 461                  }
 462                  try {
 463                      $id = user_create_user($user, false, false); // It is truly a new user.
 464                      $trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
 465                  } catch (moodle_exception $e) {
 466                      $trace->output(get_string('auth_dbinsertusererror', 'auth_db', $user->username), 1);
 467                      continue;
 468                  }
 469                  // If relevant, tag for password generation.
 470                  if ($this->is_internal()) {
 471                      set_user_preference('auth_forcepasswordchange', 1, $id);
 472                      set_user_preference('create_password',          1, $id);
 473                  }
 474  
 475                  // Save custom profile fields here.
 476                  require_once($CFG->dirroot . '/user/profile/lib.php');
 477                  $user->id = $id;
 478                  profile_save_data($user);
 479  
 480                  // Make sure user context is present.
 481                  context_user::instance($id);
 482  
 483                  \core\event\user_created::create_from_userid($id)->trigger();
 484              }
 485              unset($add_users);
 486          }
 487          $trace->finished();
 488          return 0;
 489      }
 490  
 491      function user_exists($username) {
 492  
 493          // Init result value.
 494          $result = false;
 495  
 496          $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
 497  
 498          $authdb = $this->db_init();
 499  
 500          $rs = $authdb->Execute("SELECT *
 501                                    FROM {$this->config->table}
 502                                   WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ");
 503  
 504          if (!$rs) {
 505              throw new \moodle_exception('auth_dbcantconnect', 'auth_db');
 506          } else if (!$rs->EOF) {
 507              // User exists externally.
 508              $result = true;
 509          }
 510  
 511          $authdb->Close();
 512          return $result;
 513      }
 514  
 515  
 516      function get_userlist() {
 517  
 518          // Init result value.
 519          $result = array();
 520  
 521          $authdb = $this->db_init();
 522  
 523          // Fetch userlist.
 524          $rs = $authdb->Execute("SELECT {$this->config->fielduser}
 525                                    FROM {$this->config->table} ");
 526  
 527          if (!$rs) {
 528              throw new \moodle_exception('auth_dbcantconnect', 'auth_db');
 529          } else if (!$rs->EOF) {
 530              while ($rec = $rs->FetchRow()) {
 531                  $rec = array_change_key_case((array)$rec, CASE_LOWER);
 532                  array_push($result, $rec[strtolower($this->config->fielduser)]);
 533              }
 534          }
 535  
 536          $authdb->Close();
 537          return $result;
 538      }
 539  
 540      /**
 541       * Reads user information from DB and return it in an object.
 542       *
 543       * @param string $username username
 544       * @return array
 545       */
 546      function get_userinfo_asobj($username) {
 547          $user_array = truncate_userinfo($this->get_userinfo($username));
 548          $user = new stdClass();
 549          foreach($user_array as $key=>$value) {
 550              $user->{$key} = $value;
 551          }
 552          return $user;
 553      }
 554  
 555      /**
 556       * Called when the user record is updated.
 557       * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
 558       * compares information saved modified information to external db.
 559       *
 560       * @param stdClass $olduser     Userobject before modifications
 561       * @param stdClass $newuser     Userobject new modified userobject
 562       * @return boolean result
 563       *
 564       */
 565      function user_update($olduser, $newuser) {
 566          if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
 567              error_log("ERROR:User renaming not allowed in ext db");
 568              return false;
 569          }
 570  
 571          if (isset($olduser->auth) and $olduser->auth != $this->authtype) {
 572              return true; // Just change auth and skip update.
 573          }
 574  
 575          $curruser = $this->get_userinfo($olduser->username);
 576          if (empty($curruser)) {
 577              error_log("ERROR:User $olduser->username found in ext db");
 578              return false;
 579          }
 580  
 581          $extusername = core_text::convert($olduser->username, 'utf-8', $this->config->extencoding);
 582  
 583          $authdb = $this->db_init();
 584  
 585          $update = array();
 586          foreach($curruser as $key=>$value) {
 587              if ($key == 'username') {
 588                  continue; // Skip this.
 589              }
 590              if (empty($this->config->{"field_updateremote_$key"})) {
 591                  continue; // Remote update not requested.
 592              }
 593              if (!isset($newuser->$key)) {
 594                  continue;
 595              }
 596              $nuvalue = $newuser->$key;
 597              // Support for textarea fields.
 598              if (isset($nuvalue['text'])) {
 599                  $nuvalue = $nuvalue['text'];
 600              }
 601              if ($nuvalue != $value) {
 602                  $update[] = $this->config->{"field_map_$key"}."='".$this->ext_addslashes(core_text::convert($nuvalue, 'utf-8', $this->config->extencoding))."'";
 603              }
 604          }
 605          if (!empty($update)) {
 606              $sql = "UPDATE {$this->config->table}
 607                         SET ".implode(',', $update)."
 608                       WHERE {$this->config->fielduser} = ?";
 609              if (!$authdb->Execute($sql, array($this->ext_addslashes($extusername)))) {
 610                  throw new \moodle_exception('auth_dbupdateerror', 'auth_db');
 611              }
 612          }
 613          $authdb->Close();
 614          return true;
 615      }
 616  
 617      function prevent_local_passwords() {
 618          return !$this->is_internal();
 619      }
 620  
 621      /**
 622       * Returns true if this authentication plugin is "internal".
 623       *
 624       * Internal plugins use password hashes from Moodle user table for authentication.
 625       *
 626       * @return bool
 627       */
 628      function is_internal() {
 629          if (!isset($this->config->passtype)) {
 630              return true;
 631          }
 632          return ($this->config->passtype === 'internal');
 633      }
 634  
 635      /**
 636       * Returns false if this plugin is enabled but not configured.
 637       *
 638       * @return bool
 639       */
 640      public function is_configured() {
 641          if (!empty($this->config->type)) {
 642              return true;
 643          }
 644          return false;
 645      }
 646  
 647      /**
 648       * Indicates if moodle should automatically update internal user
 649       * records with data from external sources using the information
 650       * from auth_plugin_base::get_userinfo().
 651       *
 652       * @return bool true means automatically copy data from ext to user table
 653       */
 654      function is_synchronised_with_external() {
 655          return true;
 656      }
 657  
 658      /**
 659       * Returns true if this authentication plugin can change the user's
 660       * password.
 661       *
 662       * @return bool
 663       */
 664      function can_change_password() {
 665          return ($this->is_internal() or !empty($this->config->changepasswordurl));
 666      }
 667  
 668      /**
 669       * Returns the URL for changing the user's pw, or empty if the default can
 670       * be used.
 671       *
 672       * @return moodle_url
 673       */
 674      function change_password_url() {
 675          if ($this->is_internal() || empty($this->config->changepasswordurl)) {
 676              // Standard form.
 677              return null;
 678          } else {
 679              // Use admin defined custom url.
 680              return new moodle_url($this->config->changepasswordurl);
 681          }
 682      }
 683  
 684      /**
 685       * Returns true if plugin allows resetting of internal password.
 686       *
 687       * @return bool
 688       */
 689      function can_reset_password() {
 690          return $this->is_internal();
 691      }
 692  
 693      /**
 694       * Add slashes, we can not use placeholders or system functions.
 695       *
 696       * @param string $text
 697       * @return string
 698       */
 699      function ext_addslashes($text) {
 700          if (empty($this->config->sybasequoting)) {
 701              $text = str_replace('\\', '\\\\', $text);
 702              $text = str_replace(array('\'', '"', "\0"), array('\\\'', '\\"', '\\0'), $text);
 703          } else {
 704              $text = str_replace("'", "''", $text);
 705          }
 706          return $text;
 707      }
 708  
 709      /**
 710       * Test if settings are ok, print info to output.
 711       * @private
 712       */
 713      public function test_settings() {
 714          global $CFG, $OUTPUT;
 715  
 716          // NOTE: this is not localised intentionally, admins are supposed to understand English at least a bit...
 717  
 718          raise_memory_limit(MEMORY_HUGE);
 719  
 720          if (empty($this->config->table)) {
 721              echo $OUTPUT->notification(get_string('auth_dbnoexttable', 'auth_db'), 'notifyproblem');
 722              return;
 723          }
 724  
 725          if (empty($this->config->fielduser)) {
 726              echo $OUTPUT->notification(get_string('auth_dbnouserfield', 'auth_db'), 'notifyproblem');
 727              return;
 728          }
 729  
 730          $olddebug = $CFG->debug;
 731          $olddisplay = ini_get('display_errors');
 732          ini_set('display_errors', '1');
 733          $CFG->debug = DEBUG_DEVELOPER;
 734          $olddebugauthdb = $this->config->debugauthdb;
 735          $this->config->debugauthdb = 1;
 736          error_reporting($CFG->debug);
 737  
 738          $adodb = $this->db_init();
 739  
 740          if (!$adodb or !$adodb->IsConnected()) {
 741              $this->config->debugauthdb = $olddebugauthdb;
 742              $CFG->debug = $olddebug;
 743              ini_set('display_errors', $olddisplay);
 744              error_reporting($CFG->debug);
 745              ob_end_flush();
 746  
 747              echo $OUTPUT->notification(get_string('auth_dbcannotconnect', 'auth_db'), 'notifyproblem');
 748              return;
 749          }
 750  
 751          $rs = $adodb->Execute("SELECT *
 752                                   FROM {$this->config->table}
 753                                  WHERE {$this->config->fielduser} <> 'random_unlikely_username'"); // Any unlikely name is ok here.
 754  
 755          if (!$rs) {
 756              echo $OUTPUT->notification(get_string('auth_dbcannotreadtable', 'auth_db'), 'notifyproblem');
 757  
 758          } else if ($rs->EOF) {
 759              echo $OUTPUT->notification(get_string('auth_dbtableempty', 'auth_db'), 'notifyproblem');
 760              $rs->close();
 761  
 762          } else {
 763              $fields_obj = $rs->FetchObj();
 764              $columns = array_keys((array)$fields_obj);
 765  
 766              echo $OUTPUT->notification(get_string('auth_dbcolumnlist', 'auth_db', implode(', ', $columns)), 'notifysuccess');
 767              $rs->close();
 768          }
 769  
 770          $adodb->Close();
 771  
 772          $this->config->debugauthdb = $olddebugauthdb;
 773          $CFG->debug = $olddebug;
 774          ini_set('display_errors', $olddisplay);
 775          error_reporting($CFG->debug);
 776          ob_end_flush();
 777      }
 778  
 779      /**
 780       * Clean the user data that comes from an external database.
 781       * @deprecated since 3.1, please use core_user::clean_data() instead.
 782       * @param array $user the user data to be validated against properties definition.
 783       * @return stdClass $user the cleaned user data.
 784       */
 785      public function clean_data($user) {
 786          debugging('The method clean_data() has been deprecated, please use core_user::clean_data() instead.',
 787              DEBUG_DEVELOPER);
 788          return core_user::clean_data($user);
 789      }
 790  }