Merge branch 'MDL-64439-master' of https://github.com/ilya-catalyst/moodle
authorJake Dallimore <jake@moodle.com>
Thu, 2 May 2019 06:47:39 +0000 (14:47 +0800)
committerJake Dallimore <jake@moodle.com>
Thu, 2 May 2019 06:47:39 +0000 (14:47 +0800)
1  2 
lib/moodlelib.php

diff --combined lib/moodlelib.php
@@@ -137,17 -137,14 +137,17 @@@ define('PARAM_FILE',   'file')
   *
   * Note that you should not use PARAM_FLOAT for numbers typed in by the user.
   * It does not work for languages that use , as a decimal separator.
 - * Instead, do something like
 - *     $rawvalue = required_param('name', PARAM_RAW);
 - *     // ... other code including require_login, which sets current lang ...
 - *     $realvalue = unformat_float($rawvalue);
 - *     // ... then use $realvalue
 + * Use PARAM_LOCALISEDFLOAT instead.
   */
  define('PARAM_FLOAT',  'float');
  
 +/**
 + * PARAM_LOCALISEDFLOAT - a localised real/floating point number.
 + * This is preferred over PARAM_FLOAT for numbers typed in by the user.
 + * Cleans localised numbers to computer readable numbers; false for invalid numbers.
 + */
 +define('PARAM_LOCALISEDFLOAT',  'localisedfloat');
 +
  /**
   * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
   */
@@@ -846,10 -843,6 +846,10 @@@ function clean_param($param, $type) 
              // Convert to float.
              return (float)$param;
  
 +        case PARAM_LOCALISEDFLOAT:
 +            // Convert to float.
 +            return unformat_float($param, true);
 +
          case PARAM_ALPHA:
              // Remove everything not `a-z`.
              return preg_replace('/[^a-zA-Z]/i', '', $param);
@@@ -2699,6 -2692,10 +2699,10 @@@ function require_login($courseorid = nu
                  $authplugin = get_auth_plugin($authname);
                  $authplugin->pre_loginpage_hook();
                  if (isloggedin()) {
+                     if ($cm) {
+                         $modinfo = get_fast_modinfo($course);
+                         $cm = $modinfo->get_cm($cm->id);
+                     }
                      break;
                  }
              }
          $CFG->forceclean = true;
      }
  
 +    $afterlogins = get_plugins_with_function('after_require_login', 'lib.php');
 +
      // Do not bother admins with any formalities, except for activities pending deletion.
      if (is_siteadmin() && !($cm && $cm->deletioninprogress)) {
          // Set the global $COURSE.
          if (!WS_SERVER && !AJAX_SCRIPT) {
              user_accesstime_log($course->id);
          }
 +
 +        foreach ($afterlogins as $plugintype => $plugins) {
 +            foreach ($plugins as $pluginfunction) {
 +                $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 +            }
 +        }
          return;
      }
  
                      $USER->enrol['enrolled'][$course->id] = $until;
                      $access = true;
  
 -                } else {
 +                } else if (core_course_category::can_view_course_info($course)) {
                      $params = array('courseid' => $course->id, 'status' => ENROL_INSTANCE_ENABLED);
                      $instances = $DB->get_records('enrol', $params, 'sortorder, id ASC');
                      $enrols = enrol_get_plugins(true);
                              }
                          }
                      }
 +                } else {
 +                    // User is not enrolled and is not allowed to browse courses here.
 +                    if ($preventredirect) {
 +                        throw new require_login_exception('Course is not available');
 +                    }
 +                    $PAGE->set_context(null);
 +                    // We need to override the navigation URL as the course won't have been added to the navigation and thus
 +                    // the navigation will mess up when trying to find it.
 +                    navigation_node::override_active_url(new moodle_url('/'));
 +                    notice(get_string('coursehidden'), $CFG->wwwroot .'/');
                  }
              }
          }
          $PAGE->set_course($course);
      }
  
 +    foreach ($afterlogins as $plugintype => $plugins) {
 +        foreach ($plugins as $pluginfunction) {
 +            $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 +        }
 +    }
 +
      // Finally access granted, update lastaccess times.
      // Do not update access time for webservice or ajax requests.
      if (!WS_SERVER && !AJAX_SCRIPT) {
@@@ -4798,11 -4771,9 +4802,11 @@@ function update_internal_user_password(
   * @param string $field The user field to be checked for a given value.
   * @param string $value The value to match for $field.
   * @param int $mnethostid
 + * @param bool $throwexception If true, it will throw an exception when there's no record found or when there are multiple records
 + *                              found. Otherwise, it will just return false.
   * @return mixed False, or A {@link $USER} object.
   */
 -function get_complete_user_data($field, $value, $mnethostid = null) {
 +function get_complete_user_data($field, $value, $mnethostid = null, $throwexception = false) {
      global $CFG, $DB;
  
      if (!$field || !$value) {
      $field = core_text::strtolower($field);
  
      // List of case insensitive fields.
 -    $caseinsensitivefields = ['username'];
 +    $caseinsensitivefields = ['username', 'email'];
  
      // Build the WHERE clause for an SQL query.
      $params = array('fieldval' => $value);
      }
  
      // Get all the basic user data.
 -    if (! $user = $DB->get_record_select('user', $constraints, $params)) {
 -        return false;
 +    try {
 +        // Make sure that there's only a single record that matches our query.
 +        // For example, when fetching by email, multiple records might match the query as there's no guarantee that email addresses
 +        // are unique. Therefore we can't reliably tell whether the user profile data that we're fetching is the correct one.
 +        $user = $DB->get_record_select('user', $constraints, $params, '*', MUST_EXIST);
 +    } catch (dml_exception $exception) {
 +        if ($throwexception) {
 +            throw $exception;
 +        } else {
 +            // Return false when no records or multiple records were found.
 +            return false;
 +        }
      }
  
      // Get various settings and preferences.
@@@ -6090,7 -6051,6 +6094,7 @@@ function email_to_user($user, $from, $s
          'siteshortname' => $SITE->shortname,
          'sitewwwroot' => $CFG->wwwroot,
          'subject' => $subject,
 +        'prefix' => $CFG->emailsubjectprefix,
          'to' => $user->email,
          'toname' => fullname($user),
          'from' => $mail->From,
@@@ -6450,14 -6410,17 +6454,14 @@@ function send_password_change_confirmat
  }
  
  /**
 - * Sends an email containinginformation on how to change your password.
 + * Sends an email containing information on how to change your password.
   *
   * @param stdClass $user A {@link $USER} object
   * @return bool Returns true if mail was sent OK and false if there was an error.
   */
  function send_password_change_info($user) {
 -    global $CFG;
 -
      $site = get_site();
      $supportuser = core_user::get_support_user();
 -    $systemcontext = context_system::instance();
  
      $data = new stdClass();
      $data->firstname = $user->firstname;
      $data->sitename  = format_string($site->fullname);
      $data->admin     = generate_email_signoff();
  
 -    $userauth = get_auth_plugin($user->auth);
 -
 -    if (!is_enabled_auth($user->auth) or $user->auth == 'nologin') {
 +    if (!is_enabled_auth($user->auth)) {
          $message = get_string('emailpasswordchangeinfodisabled', '', $data);
          $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
          // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
          return email_to_user($user, $supportuser, $subject, $message);
      }
  
 -    if ($userauth->can_change_password() and $userauth->change_password_url()) {
 -        // We have some external url for password changing.
 -        $data->link .= $userauth->change_password_url();
 -
 -    } else {
 -        // No way to change password, sorry.
 -        $data->link = '';
 -    }
 -
 -    if (!empty($data->link) and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
 -        $message = get_string('emailpasswordchangeinfo', '', $data);
 -        $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
 -    } else {
 -        $message = get_string('emailpasswordchangeinfofail', '', $data);
 -        $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
 -    }
 +    $userauth = get_auth_plugin($user->auth);
 +    ['subject' => $subject, 'message' => $message] = $userauth->get_password_change_info($user);
  
      // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
      return email_to_user($user, $supportuser, $subject, $message);
 -
  }
  
  /**
@@@ -7083,20 -7063,11 +7087,20 @@@ function get_string_manager($forcereloa
      if ($singleton === null) {
          if (empty($CFG->early_install_lang)) {
  
 +            $transaliases = array();
              if (empty($CFG->langlist)) {
                   $translist = array();
              } else {
                  $translist = explode(',', $CFG->langlist);
                  $translist = array_map('trim', $translist);
 +                // Each language in the $CFG->langlist can has an "alias" that would substitute the default language name.
 +                foreach ($translist as $i => $value) {
 +                    $parts = preg_split('/\s*\|\s*/', $value, 2);
 +                    if (count($parts) == 2) {
 +                        $transaliases[$parts[0]] = $parts[1];
 +                        $translist[$i] = $parts[0];
 +                    }
 +                }
              }
  
              if (!empty($CFG->config_php_settings['customstringmanager'])) {
                      $implements = class_implements($classname);
  
                      if (isset($implements['core_string_manager'])) {
 -                        $singleton = new $classname($CFG->langotherroot, $CFG->langlocalroot, $translist);
 +                        $singleton = new $classname($CFG->langotherroot, $CFG->langlocalroot, $translist, $transaliases);
                          return $singleton;
  
                      } else {
                  }
              }
  
 -            $singleton = new core_string_manager_standard($CFG->langotherroot, $CFG->langlocalroot, $translist);
 +            $singleton = new core_string_manager_standard($CFG->langotherroot, $CFG->langlocalroot, $translist, $transaliases);
  
          } else {
              $singleton = new core_string_manager_install();
@@@ -7381,12 -7352,10 +7385,12 @@@ class emoticon_manager 
      /**
       * Returns the currently enabled emoticons
       *
 +     * @param boolean $selectable - If true, only return emoticons that should be selectable from a list.
       * @return array of emoticon objects
       */
 -    public function get_emoticons() {
 +    public function get_emoticons($selectable = false) {
          global $CFG;
 +        $notselectable = ['martin', 'egg'];
  
          if (empty($CFG->emoticons)) {
              return array();
              debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
              return array();
          }
 +        if ($selectable) {
 +            foreach ($emoticons as $index => $emote) {
 +                if (in_array($emote->altidentifier, $notselectable)) {
 +                    // Skip this one.
 +                    unset($emoticons[$index]);
 +                }
 +            }
 +        }
  
          return $emoticons;
      }
@@@ -9328,7 -9289,7 +9332,7 @@@ function get_performance_info() 
          // Attempt to avoid devs debugging peformance issues, when its caused by css building and so on.
          $info['html'] .= '<p><strong>Warning: Theme designer mode is enabled.</strong></p>';
      }
 -    $info['html'] .= '<ul class="list-unstyled m-l-1 row">';         // Holds userfriendly HTML representation.
 +    $info['html'] .= '<ul class="list-unstyled ml-1 row">';         // Holds userfriendly HTML representation.
  
      $info['realtime'] = microtime_diff($PERF->starttime, microtime());
  
          $info['txt']  .= 'memory_peak: '.$info['memory_peak'].'B (' . display_size($info['memory_peak']).') ';
      }
  
 -    $info['html'] .= '</ul><ul class="list-unstyled m-l-1 row">';
 +    $info['html'] .= '</ul><ul class="list-unstyled ml-1 row">';
      $inc = get_included_files();
      $info['includecount'] = count($inc);
      $info['html'] .= '<li class="included col-sm-4">Included '.$info['includecount'].' files</li> ';
  
      $info['html'] .= '</ul>';
      if ($stats = cache_helper::get_stats()) {
 -        $html = '<ul class="cachesused list-unstyled m-l-1 row">';
 +        $html = '<ul class="cachesused list-unstyled ml-1 row">';
          $html .= '<li class="cache-stats-heading font-weight-bold">Caches used (hits/misses/sets)</li>';
 -        $html .= '</ul><ul class="cachesused list-unstyled m-l-1">';
 +        $html .= '</ul><ul class="cachesused list-unstyled ml-1">';
          $text = 'Caches used (hits/misses/sets): ';
          $hits = 0;
          $misses = 0;
                      $mode = ' <span title="request cache">[r]</span>';
                      break;
              }
 -            $html .= '<ul class="cache-definition-stats list-unstyled m-l-1 m-b-1 cache-mode-'.$modeclass.' card d-inline-block">';
 +            $html .= '<ul class="cache-definition-stats list-unstyled ml-1 mb-1 cache-mode-'.$modeclass.' card d-inline-block">';
              $html .= '<li class="cache-definition-stats-heading p-t-1 card-header bg-dark bg-inverse font-weight-bold">' .
                  $definition . $mode.'</li>';
              $text .= "$definition {";
@@@ -10326,28 -10287,3 +10330,28 @@@ function get_callable_name($callable) 
          return $name;
      }
  }
 +
 +/**
 + * Tries to guess if $CFG->wwwroot is publicly accessible or not.
 + * Never put your faith on this function and rely on its accuracy as there might be false positives.
 + * It just performs some simple checks, and mainly is used for places where we want to hide some options
 + * such as site registration when $CFG->wwwroot is not publicly accessible.
 + * Good thing is there is no false negative.
 + *
 + * @return bool
 + */
 +function site_is_public() {
 +    global $CFG;
 +
 +    $host = parse_url($CFG->wwwroot, PHP_URL_HOST);
 +
 +    if ($host === 'localhost' || preg_match('|^127\.\d+\.\d+\.\d+$|', $host)) {
 +        $ispublic = false;
 +    } else if (\core\ip_utils::is_ip_address($host) && !ip_is_public($host)) {
 +        $ispublic = false;
 +    } else {
 +        $ispublic = true;
 +    }
 +
 +    return $ispublic;
 +}