Merge branch 'MDL-49787_email_attachments_symlink_fix' of https://github.com/iamandre...
[moodle.git] / lib / moodlelib.php
index fcf605c..a326558 100644 (file)
@@ -408,7 +408,7 @@ define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
 
 /** True if module has no 'view' page (like label) */
 define('FEATURE_NO_VIEW_LINK', 'viewlink');
-/** True if module supports outcomes */
+/** True (which is default) if the module wants support for setting the ID number for grade calculation purposes. */
 define('FEATURE_IDNUMBER', 'idnumber');
 /** True if module supports groups */
 define('FEATURE_GROUPS', 'groups');
@@ -1032,10 +1032,16 @@ function clean_param($param, $type) {
             // Allow http absolute, root relative and relative URLs within wwwroot.
             $param = clean_param($param, PARAM_URL);
             if (!empty($param)) {
+
+                // Simulate the HTTPS version of the site.
+                $httpswwwroot = str_replace('http://', 'https://', $CFG->wwwroot);
+
                 if (preg_match(':^/:', $param)) {
                     // Root-relative, ok!
-                } else if (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i', $param)) {
+                } else if (preg_match('/^' . preg_quote($CFG->wwwroot, '/') . '/i', $param)) {
                     // Absolute, and matches our wwwroot.
+                } else if (!empty($CFG->loginhttps) && preg_match('/^' . preg_quote($httpswwwroot, '/') . '/i', $param)) {
+                    // Absolute, and matches our httpswwwroot.
                 } else {
                     // Relative - let's make sure there are no tricks.
                     if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?')) {
@@ -2036,23 +2042,15 @@ function get_user_preferences($name = null, $default = null, $user = null) {
  * @return int GMT timestamp
  */
 function make_timestamp($year, $month=1, $day=1, $hour=0, $minute=0, $second=0, $timezone=99, $applydst=true) {
+    $date = new DateTime('now', core_date::get_user_timezone_object($timezone));
+    $date->setDate((int)$year, (int)$month, (int)$day);
+    $date->setTime((int)$hour, (int)$minute, (int)$second);
 
-    // Save input timezone, required for dst offset check.
-    $passedtimezone = $timezone;
-
-    $timezone = get_user_timezone_offset($timezone);
+    $time = $date->getTimestamp();
 
-    if (abs($timezone) > 13) {
-        // Server time.
-        $time = mktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
-    } else {
-        $time = gmmktime((int)$hour, (int)$minute, (int)$second, (int)$month, (int)$day, (int)$year);
-        $time = usertime($time, $timezone);
-
-        // Apply dst for string timezones or if 99 then try dst offset with user's default timezone.
-        if ($applydst && ((99 == $passedtimezone) || !is_numeric($passedtimezone))) {
-            $time -= dst_offset_on($time, $passedtimezone);
-        }
+    // Moodle BC DST stuff.
+    if (!$applydst) {
+        $time += dst_offset_on($time, $timezone);
     }
 
     return $time;
@@ -2176,13 +2174,9 @@ function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour
  * If we are running under Windows convert to Windows encoding and then back to UTF-8
  * (because it's impossible to specify UTF-8 to fetch locale info in Win32).
  *
- * This function does not do any calculation regarding the user preferences and should
- * therefore receive the final date timestamp, format and timezone. Timezone being only used
- * to differentiate the use of server time or not (strftime() against gmstrftime()).
- *
- * @param int $date the timestamp.
+ * @param int $date the timestamp - since Moodle 2.9 this is a real UTC timestamp
  * @param string $format strftime format.
- * @param int|float $tz the numerical timezone, typically returned by {@link get_user_timezone_offset()}.
+ * @param int|float|string $tz the user timezone
  * @return string the formatted date/time.
  * @since Moodle 2.3.3
  */
@@ -2196,23 +2190,18 @@ function date_format_string($date, $format, $tz = 99) {
         $localewincharset = $calendartype->locale_win_charset();
     }
 
-    if (abs($tz) > 13) {
-        if ($localewincharset) {
-            $format = core_text::convert($format, 'utf-8', $localewincharset);
-            $datestring = strftime($format, $date);
-            $datestring = core_text::convert($datestring, $localewincharset, 'utf-8');
-        } else {
-            $datestring = strftime($format, $date);
-        }
-    } else {
-        if ($localewincharset) {
-            $format = core_text::convert($format, 'utf-8', $localewincharset);
-            $datestring = gmstrftime($format, $date);
-            $datestring = core_text::convert($datestring, $localewincharset, 'utf-8');
-        } else {
-            $datestring = gmstrftime($format, $date);
-        }
+    if ($localewincharset) {
+        $format = core_text::convert($format, 'utf-8', $localewincharset);
     }
+
+    date_default_timezone_set(core_date::get_user_timezone($tz));
+    $datestring = strftime($format, $date);
+    core_date::set_default_server_timezone();
+
+    if ($localewincharset) {
+        $datestring = core_text::convert($datestring, $localewincharset, 'utf-8');
+    }
+
     return $datestring;
 }
 
@@ -2222,81 +2211,37 @@ function date_format_string($date, $format, $tz = 99) {
  *
  * @package core
  * @category time
- * @uses HOURSECS
  * @param int $time Timestamp in GMT
- * @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
- *        dst offset is applied {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @param float|int|string $timezone user timezone
  * @return array An array that represents the date in user time
  */
 function usergetdate($time, $timezone=99) {
+    date_default_timezone_set(core_date::get_user_timezone($timezone));
+    $result = getdate($time);
+    core_date::set_default_server_timezone();
 
-    // Save input timezone, required for dst offset check.
-    $passedtimezone = $timezone;
-
-    $timezone = get_user_timezone_offset($timezone);
-
-    if (abs($timezone) > 13) {
-        // Server time.
-        return getdate($time);
-    }
-
-    // Add daylight saving offset for string timezones only, as we can't get dst for
-    // float values. if timezone is 99 (user default timezone), then try update dst.
-    if ($passedtimezone == 99 || !is_numeric($passedtimezone)) {
-        $time += dst_offset_on($time, $passedtimezone);
-    }
-
-    $time += intval((float)$timezone * HOURSECS);
-
-    $datestring = gmstrftime('%B_%A_%j_%Y_%m_%w_%d_%H_%M_%S', $time);
-
-    // Be careful to ensure the returned array matches that produced by getdate() above.
-    list(
-        $getdate['month'],
-        $getdate['weekday'],
-        $getdate['yday'],
-        $getdate['year'],
-        $getdate['mon'],
-        $getdate['wday'],
-        $getdate['mday'],
-        $getdate['hours'],
-        $getdate['minutes'],
-        $getdate['seconds']
-    ) = explode('_', $datestring);
-
-    // Set correct datatype to match with getdate().
-    $getdate['seconds'] = (int)$getdate['seconds'];
-    $getdate['yday'] = (int)$getdate['yday'] - 1; // The function gmstrftime returns 0 through 365.
-    $getdate['year'] = (int)$getdate['year'];
-    $getdate['mon'] = (int)$getdate['mon'];
-    $getdate['wday'] = (int)$getdate['wday'];
-    $getdate['mday'] = (int)$getdate['mday'];
-    $getdate['hours'] = (int)$getdate['hours'];
-    $getdate['minutes'] = (int)$getdate['minutes'];
-    return $getdate;
+    return $result;
 }
 
 /**
  * Given a GMT timestamp (seconds since epoch), offsets it by
  * the timezone.  eg 3pm in India is 3pm GMT - 7 * 3600 seconds
  *
+ * NOTE: this function does not include DST properly,
+ *       you should use the PHP date stuff instead!
+ *
  * @package core
  * @category time
- * @uses HOURSECS
  * @param int $date Timestamp in GMT
- * @param float|int|string $timezone timezone to calculate GMT time offset before
- *        calculating user time, 99 is default user timezone
- *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @param float|int|string $timezone user timezone
  * @return int
  */
 function usertime($date, $timezone=99) {
+    $userdate = new DateTime('@' . $date);
+    $userdate->setTimezone(core_date::get_user_timezone_object($timezone));
+    $dst = dst_offset_on($date, $timezone);
 
-    $timezone = get_user_timezone_offset($timezone);
-
-    if (abs($timezone) > 13) {
-        return $date;
-    }
-    return $date - (int)($timezone * HOURSECS);
+    return $date - $userdate->getOffset() + $dst;
 }
 
 /**
@@ -2306,9 +2251,7 @@ function usertime($date, $timezone=99) {
  * @package core
  * @category time
  * @param int $date Timestamp in GMT
- * @param float|int|string $timezone timezone to calculate GMT time offset before
- *        calculating user midnight time, 99 is default user timezone
- *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @param float|int|string $timezone user timezone
  * @return int Returns a GMT timestamp
  */
 function usergetmidnight($date, $timezone=99) {
@@ -2325,86 +2268,12 @@ function usergetmidnight($date, $timezone=99) {
  *
  * @package core
  * @category time
- * @param float|int|string $timezone timezone to calculate GMT time offset before
- *        calculating user timezone, 99 is default user timezone
- *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @param float|int|string $timezone user timezone
  * @return string
  */
 function usertimezone($timezone=99) {
-
-    $tz = get_user_timezone($timezone);
-
-    if (!is_float($tz)) {
-        return $tz;
-    }
-
-    if (abs($tz) > 13) {
-        // Server time.
-        return get_string('serverlocaltime');
-    }
-
-    if ($tz == intval($tz)) {
-        // Don't show .0 for whole hours.
-        $tz = intval($tz);
-    }
-
-    if ($tz == 0) {
-        return 'UTC';
-    } else if ($tz > 0) {
-        return 'UTC+'.$tz;
-    } else {
-        return 'UTC'.$tz;
-    }
-
-}
-
-/**
- * Returns a float which represents the user's timezone difference from GMT in hours
- * Checks various settings and picks the most dominant of those which have a value
- *
- * @package core
- * @category time
- * @param float|int|string $tz timezone to calculate GMT time offset for user,
- *        99 is default user timezone
- *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
- * @return float
- */
-function get_user_timezone_offset($tz = 99) {
-    $tz = get_user_timezone($tz);
-
-    if (is_float($tz)) {
-        return $tz;
-    } else {
-        $tzrecord = get_timezone_record($tz);
-        if (empty($tzrecord)) {
-            return 99.0;
-        }
-        return (float)$tzrecord->gmtoff / HOURMINS;
-    }
-}
-
-/**
- * Returns an int which represents the systems's timezone difference from GMT in seconds
- *
- * @package core
- * @category time
- * @param float|int|string $tz timezone for which offset is required.
- *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
- * @return int|bool if found, false is timezone 99 or error
- */
-function get_timezone_offset($tz) {
-    if ($tz == 99) {
-        return false;
-    }
-
-    if (is_numeric($tz)) {
-        return intval($tz * 60*60);
-    }
-
-    if (!$tzrecord = get_timezone_record($tz)) {
-        return false;
-    }
-    return intval($tzrecord->gmtoff * 60);
+    $tz = core_date::get_user_timezone($timezone);
+    return core_date::get_localised_timezone($tz);
 }
 
 /**
@@ -2439,198 +2308,6 @@ function get_user_timezone($tz = 99) {
     return is_numeric($tz) ? (float) $tz : $tz;
 }
 
-/**
- * Returns cached timezone record for given $timezonename
- *
- * @package core
- * @param string $timezonename name of the timezone
- * @return stdClass|bool timezonerecord or false
- */
-function get_timezone_record($timezonename) {
-    global $DB;
-    static $cache = null;
-
-    if ($cache === null) {
-        $cache = array();
-    }
-
-    if (isset($cache[$timezonename])) {
-        return $cache[$timezonename];
-    }
-
-    return $cache[$timezonename] = $DB->get_record_sql('SELECT * FROM {timezone}
-                                                        WHERE name = ? ORDER BY year DESC', array($timezonename), IGNORE_MULTIPLE);
-}
-
-/**
- * Build and store the users Daylight Saving Time (DST) table
- *
- * @package core
- * @param int $fromyear Start year for the table, defaults to 1971
- * @param int $toyear End year for the table, defaults to 2035
- * @param int|float|string $strtimezone timezone to check if dst should be applied.
- * @return bool
- */
-function calculate_user_dst_table($fromyear = null, $toyear = null, $strtimezone = null) {
-    global $SESSION, $DB;
-
-    $usertz = get_user_timezone($strtimezone);
-
-    if (is_float($usertz)) {
-        // Trivial timezone, no DST.
-        return false;
-    }
-
-    if (!empty($SESSION->dst_offsettz) && $SESSION->dst_offsettz != $usertz) {
-        // We have pre-calculated values, but the user's effective TZ has changed in the meantime, so reset.
-        unset($SESSION->dst_offsets);
-        unset($SESSION->dst_range);
-    }
-
-    if (!empty($SESSION->dst_offsets) && empty($fromyear) && empty($toyear)) {
-        // Repeat calls which do not request specific year ranges stop here, we have already calculated the table.
-        // This will be the return path most of the time, pretty light computationally.
-        return true;
-    }
-
-    // Reaching here means we either need to extend our table or create it from scratch.
-
-    // Remember which TZ we calculated these changes for.
-    $SESSION->dst_offsettz = $usertz;
-
-    if (empty($SESSION->dst_offsets)) {
-        // If we 're creating from scratch, put the two guard elements in there.
-        $SESSION->dst_offsets = array(1 => null, 0 => null);
-    }
-    if (empty($SESSION->dst_range)) {
-        // If creating from scratch.
-        $from = max((empty($fromyear) ? intval(date('Y')) - 3 : $fromyear), 1971);
-        $to   = min((empty($toyear)   ? intval(date('Y')) + 3 : $toyear),   2035);
-
-        // Fill in the array with the extra years we need to process.
-        $yearstoprocess = array();
-        for ($i = $from; $i <= $to; ++$i) {
-            $yearstoprocess[] = $i;
-        }
-
-        // Take note of which years we have processed for future calls.
-        $SESSION->dst_range = array($from, $to);
-    } else {
-        // If needing to extend the table, do the same.
-        $yearstoprocess = array();
-
-        $from = max((empty($fromyear) ? $SESSION->dst_range[0] : $fromyear), 1971);
-        $to   = min((empty($toyear)   ? $SESSION->dst_range[1] : $toyear),   2035);
-
-        if ($from < $SESSION->dst_range[0]) {
-            // Take note of which years we need to process and then note that we have processed them for future calls.
-            for ($i = $from; $i < $SESSION->dst_range[0]; ++$i) {
-                $yearstoprocess[] = $i;
-            }
-            $SESSION->dst_range[0] = $from;
-        }
-        if ($to > $SESSION->dst_range[1]) {
-            // Take note of which years we need to process and then note that we have processed them for future calls.
-            for ($i = $SESSION->dst_range[1] + 1; $i <= $to; ++$i) {
-                $yearstoprocess[] = $i;
-            }
-            $SESSION->dst_range[1] = $to;
-        }
-    }
-
-    if (empty($yearstoprocess)) {
-        // This means that there was a call requesting a SMALLER range than we have already calculated.
-        return true;
-    }
-
-    // From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
-    // Also, the array is sorted in descending timestamp order!
-
-    // Get DB data.
-
-    static $presetscache = array();
-    if (!isset($presetscache[$usertz])) {
-        $presetscache[$usertz] = $DB->get_records('timezone', array('name' => $usertz),
-            'year DESC', 'year, gmtoff, dstoff, dst_month, dst_startday, dst_weekday, dst_skipweeks, dst_time, std_month, '.
-            'std_startday, std_weekday, std_skipweeks, std_time');
-    }
-    if (empty($presetscache[$usertz])) {
-        return false;
-    }
-
-    // Remove ending guard (first element of the array).
-    reset($SESSION->dst_offsets);
-    unset($SESSION->dst_offsets[key($SESSION->dst_offsets)]);
-
-    // Add all required change timestamps.
-    foreach ($yearstoprocess as $y) {
-        // Find the record which is in effect for the year $y.
-        foreach ($presetscache[$usertz] as $year => $preset) {
-            if ($year <= $y) {
-                break;
-            }
-        }
-
-        $changes = dst_changes_for_year($y, $preset);
-
-        if ($changes === null) {
-            continue;
-        }
-        if ($changes['dst'] != 0) {
-            $SESSION->dst_offsets[$changes['dst']] = $preset->dstoff * MINSECS;
-        }
-        if ($changes['std'] != 0) {
-            $SESSION->dst_offsets[$changes['std']] = 0;
-        }
-    }
-
-    // Put in a guard element at the top.
-    $maxtimestamp = max(array_keys($SESSION->dst_offsets));
-    $SESSION->dst_offsets[($maxtimestamp + DAYSECS)] = null; // DAYSECS is arbitrary, any "small" number will do.
-
-    // Sort again.
-    krsort($SESSION->dst_offsets);
-
-    return true;
-}
-
-/**
- * Calculates the required DST change and returns a Timestamp Array
- *
- * @package core
- * @category time
- * @uses HOURSECS
- * @uses MINSECS
- * @param int|string $year Int or String Year to focus on
- * @param object $timezone Instatiated Timezone object
- * @return array|null Array dst => xx, 0 => xx, std => yy, 1 => yy or null
- */
-function dst_changes_for_year($year, $timezone) {
-
-    if ($timezone->dst_startday == 0 && $timezone->dst_weekday == 0 &&
-        $timezone->std_startday == 0 && $timezone->std_weekday == 0) {
-        return null;
-    }
-
-    $monthdaydst = find_day_in_month($timezone->dst_startday, $timezone->dst_weekday, $timezone->dst_month, $year);
-    $monthdaystd = find_day_in_month($timezone->std_startday, $timezone->std_weekday, $timezone->std_month, $year);
-
-    list($dsthour, $dstmin) = explode(':', $timezone->dst_time);
-    list($stdhour, $stdmin) = explode(':', $timezone->std_time);
-
-    $timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, 99, false);
-    $timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, 99, false);
-
-    // Instead of putting hour and minute in make_timestamp(), we add them afterwards.
-    // This has the advantage of being able to have negative values for hour, i.e. for timezones
-    // where GMT time would be in the PREVIOUS day than the local one on which DST changes.
-
-    $timedst += $dsthour * HOURSECS + $dstmin * MINSECS;
-    $timestd += $stdhour * HOURSECS + $stdmin * MINSECS;
-
-    return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
-}
-
 /**
  * Calculates the Daylight Saving Offset for a given date/time (timestamp)
  * - Note: Daylight saving only works for string timezones and not for float.
@@ -2638,48 +2315,20 @@ function dst_changes_for_year($year, $timezone) {
  * @package core
  * @category time
  * @param int $time must NOT be compensated at all, it has to be a pure timestamp
- * @param int|float|string $strtimezone timezone for which offset is expected, if 99 or null
- *        then user's default timezone is used. {@link http://docs.moodle.org/dev/Time_API#Timezone}
+ * @param int|float|string $strtimezone user timezone
  * @return int
  */
 function dst_offset_on($time, $strtimezone = null) {
-    global $SESSION;
-
-    if (!calculate_user_dst_table(null, null, $strtimezone) || empty($SESSION->dst_offsets)) {
-        return 0;
-    }
-
-    reset($SESSION->dst_offsets);
-    while (list($from, $offset) = each($SESSION->dst_offsets)) {
-        if ($from <= $time) {
-            break;
-        }
-    }
-
-    // This is the normal return path.
-    if ($offset !== null) {
-        return $offset;
-    }
-
-    // Reaching this point means we haven't calculated far enough, do it now:
-    // Calculate extra DST changes if needed and recurse. The recursion always
-    // moves toward the stopping condition, so will always end.
-
-    if ($from == 0) {
-        // We need a year smaller than $SESSION->dst_range[0].
-        if ($SESSION->dst_range[0] == 1971) {
-            return 0;
-        }
-        calculate_user_dst_table($SESSION->dst_range[0] - 5, null, $strtimezone);
-        return dst_offset_on($time, $strtimezone);
-    } else {
-        // We need a year larger than $SESSION->dst_range[1].
-        if ($SESSION->dst_range[1] == 2035) {
-            return 0;
+    $tz = core_date::get_user_timezone($strtimezone);
+    $date = new DateTime('@' . $time);
+    $date->setTimezone(new DateTimeZone($tz));
+    if ($date->format('I') == '1') {
+        if ($tz === 'Australia/Lord_Howe') {
+            return 1800;
         }
-        calculate_user_dst_table(null, $SESSION->dst_range[1] + 5, $strtimezone);
-        return dst_offset_on($time, $strtimezone);
+        return 3600;
     }
+    return 0;
 }
 
 /**
@@ -7106,53 +6755,6 @@ function get_list_of_themes() {
     return $themes;
 }
 
-/**
- * Returns a list of timezones in the current language
- *
- * @return array
- */
-function get_list_of_timezones() {
-    global $DB;
-
-    static $timezones;
-
-    if (!empty($timezones)) {    // This function has been called recently.
-        return $timezones;
-    }
-
-    $timezones = array();
-
-    if ($rawtimezones = $DB->get_records_sql("SELECT MAX(id), name FROM {timezone} GROUP BY name")) {
-        foreach ($rawtimezones as $timezone) {
-            if (!empty($timezone->name)) {
-                if (get_string_manager()->string_exists(strtolower($timezone->name), 'timezones')) {
-                    $timezones[$timezone->name] = get_string(strtolower($timezone->name), 'timezones');
-                } else {
-                    $timezones[$timezone->name] = $timezone->name;
-                }
-                if (substr($timezones[$timezone->name], 0, 1) == '[') {  // No translation found.
-                    $timezones[$timezone->name] = $timezone->name;
-                }
-            }
-        }
-    }
-
-    asort($timezones);
-
-    for ($i = -13; $i <= 13; $i += .5) {
-        $tzstring = 'UTC';
-        if ($i < 0) {
-            $timezones[sprintf("%.1f", $i)] = $tzstring . $i;
-        } else if ($i > 0) {
-            $timezones[sprintf("%.1f", $i)] = $tzstring . '+' . $i;
-        } else {
-            $timezones[sprintf("%.1f", $i)] = $tzstring;
-        }
-    }
-
-    return $timezones;
-}
-
 /**
  * Factory function for emoticon_manager
  *