| 1 | efrain | 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 |  * Defines classes used for updates.
 | 
        
           |  |  | 19 |  *
 | 
        
           |  |  | 20 |  * @package    core
 | 
        
           |  |  | 21 |  * @copyright  2011 David Mudrak <david@moodle.com>
 | 
        
           |  |  | 22 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 23 |  */
 | 
        
           |  |  | 24 | namespace core\update;
 | 
        
           |  |  | 25 |   | 
        
           |  |  | 26 | use html_writer, coding_exception, core_component;
 | 
        
           |  |  | 27 |   | 
        
           |  |  | 28 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 29 |   | 
        
           |  |  | 30 | /**
 | 
        
           |  |  | 31 |  * Singleton class that handles checking for available updates
 | 
        
           |  |  | 32 |  */
 | 
        
           |  |  | 33 | class checker {
 | 
        
           |  |  | 34 |   | 
        
           |  |  | 35 |     /** @var \core\update\checker holds the singleton instance */
 | 
        
           |  |  | 36 |     protected static $singletoninstance;
 | 
        
           |  |  | 37 |     /** @var null|int the timestamp of when the most recent response was fetched */
 | 
        
           |  |  | 38 |     protected $recentfetch = null;
 | 
        
           |  |  | 39 |     /** @var null|array the recent response from the update notification provider */
 | 
        
           |  |  | 40 |     protected $recentresponse = null;
 | 
        
           |  |  | 41 |     /** @var null|string the numerical version of the local Moodle code */
 | 
        
           |  |  | 42 |     protected $currentversion = null;
 | 
        
           |  |  | 43 |     /** @var null|string the release info of the local Moodle code */
 | 
        
           |  |  | 44 |     protected $currentrelease = null;
 | 
        
           |  |  | 45 |     /** @var null|string branch of the local Moodle code */
 | 
        
           |  |  | 46 |     protected $currentbranch = null;
 | 
        
           |  |  | 47 |     /** @var array of (string)frankestyle => (string)version list of additional plugins deployed at this site */
 | 
        
           |  |  | 48 |     protected $currentplugins = array();
 | 
        
           |  |  | 49 |   | 
        
           |  |  | 50 |     /**
 | 
        
           |  |  | 51 |      * Direct initiation not allowed, use the factory method {@link self::instance()}
 | 
        
           |  |  | 52 |      */
 | 
        
           |  |  | 53 |     protected function __construct() {
 | 
        
           |  |  | 54 |     }
 | 
        
           |  |  | 55 |   | 
        
           |  |  | 56 |     /**
 | 
        
           |  |  | 57 |      * Sorry, this is singleton
 | 
        
           |  |  | 58 |      */
 | 
        
           |  |  | 59 |     protected function __clone() {
 | 
        
           |  |  | 60 |     }
 | 
        
           |  |  | 61 |   | 
        
           |  |  | 62 |     /**
 | 
        
           |  |  | 63 |      * Factory method for this class
 | 
        
           |  |  | 64 |      *
 | 
        
           |  |  | 65 |      * @return \core\update\checker the singleton instance
 | 
        
           |  |  | 66 |      */
 | 
        
           |  |  | 67 |     public static function instance() {
 | 
        
           |  |  | 68 |         if (is_null(self::$singletoninstance)) {
 | 
        
           |  |  | 69 |             self::$singletoninstance = new self();
 | 
        
           |  |  | 70 |         }
 | 
        
           |  |  | 71 |         return self::$singletoninstance;
 | 
        
           |  |  | 72 |     }
 | 
        
           |  |  | 73 |   | 
        
           |  |  | 74 |     /**
 | 
        
           |  |  | 75 |      * Reset any caches
 | 
        
           |  |  | 76 |      * @param bool $phpunitreset
 | 
        
           |  |  | 77 |      */
 | 
        
           |  |  | 78 |     public static function reset_caches($phpunitreset = false) {
 | 
        
           |  |  | 79 |         if ($phpunitreset) {
 | 
        
           |  |  | 80 |             self::$singletoninstance = null;
 | 
        
           |  |  | 81 |         }
 | 
        
           |  |  | 82 |     }
 | 
        
           |  |  | 83 |   | 
        
           |  |  | 84 |     /**
 | 
        
           |  |  | 85 |      * Is checking for available updates enabled?
 | 
        
           |  |  | 86 |      *
 | 
        
           |  |  | 87 |      * The feature is enabled unless it is prohibited via config.php.
 | 
        
           |  |  | 88 |      * If enabled, the button for manual checking for available updates is
 | 
        
           |  |  | 89 |      * displayed at admin screens. To perform scheduled checks for updates
 | 
        
           |  |  | 90 |      * automatically, the admin setting $CFG->updateautocheck has to be enabled.
 | 
        
           |  |  | 91 |      *
 | 
        
           |  |  | 92 |      * @return bool
 | 
        
           |  |  | 93 |      */
 | 
        
           |  |  | 94 |     public function enabled() {
 | 
        
           |  |  | 95 |         global $CFG;
 | 
        
           |  |  | 96 |   | 
        
           |  |  | 97 |         return empty($CFG->disableupdatenotifications);
 | 
        
           |  |  | 98 |     }
 | 
        
           |  |  | 99 |   | 
        
           |  |  | 100 |     /**
 | 
        
           |  |  | 101 |      * Returns the timestamp of the last execution of {@link fetch()}
 | 
        
           |  |  | 102 |      *
 | 
        
           |  |  | 103 |      * @return int|null null if it has never been executed or we don't known
 | 
        
           |  |  | 104 |      */
 | 
        
           |  |  | 105 |     public function get_last_timefetched() {
 | 
        
           |  |  | 106 |   | 
        
           |  |  | 107 |         $this->restore_response();
 | 
        
           |  |  | 108 |   | 
        
           |  |  | 109 |         if (!empty($this->recentfetch)) {
 | 
        
           |  |  | 110 |             return $this->recentfetch;
 | 
        
           |  |  | 111 |   | 
        
           |  |  | 112 |         } else {
 | 
        
           |  |  | 113 |             return null;
 | 
        
           |  |  | 114 |         }
 | 
        
           |  |  | 115 |     }
 | 
        
           |  |  | 116 |   | 
        
           |  |  | 117 |     /**
 | 
        
           |  |  | 118 |      * Fetches the available update status from the remote site
 | 
        
           |  |  | 119 |      *
 | 
        
           |  |  | 120 |      * @throws checker_exception
 | 
        
           |  |  | 121 |      */
 | 
        
           |  |  | 122 |     public function fetch() {
 | 
        
           |  |  | 123 |   | 
        
           |  |  | 124 |         $response = $this->get_response();
 | 
        
           |  |  | 125 |         $this->validate_response($response);
 | 
        
           |  |  | 126 |         $this->store_response($response);
 | 
        
           |  |  | 127 |   | 
        
           |  |  | 128 |         // We need to reset plugin manager's caches - the currently existing
 | 
        
           |  |  | 129 |         // singleton is not aware of eventually available updates we just fetched.
 | 
        
           |  |  | 130 |         \core_plugin_manager::reset_caches();
 | 
        
           |  |  | 131 |     }
 | 
        
           |  |  | 132 |   | 
        
           |  |  | 133 |     /**
 | 
        
           |  |  | 134 |      * Returns the available update information for the given component
 | 
        
           |  |  | 135 |      *
 | 
        
           |  |  | 136 |      * This method returns null if the most recent response does not contain any information
 | 
        
           |  |  | 137 |      * about it. The returned structure is an array of available updates for the given
 | 
        
           |  |  | 138 |      * component. Each update info is an object with at least one property called
 | 
        
           |  |  | 139 |      * 'version'. Other possible properties are 'release', 'maturity', 'url' and 'downloadurl'.
 | 
        
           |  |  | 140 |      *
 | 
        
           |  |  | 141 |      * For the 'core' component, the method returns real updates only (those with higher version).
 | 
        
           |  |  | 142 |      * For all other components, the list of all known remote updates is returned and the caller
 | 
        
           |  |  | 143 |      * (usually the {@link core_plugin_manager}) is supposed to make the actual comparison of versions.
 | 
        
           |  |  | 144 |      *
 | 
        
           |  |  | 145 |      * @param string $component frankenstyle
 | 
        
           |  |  | 146 |      * @param array $options with supported keys 'minmaturity' and/or 'notifybuilds'
 | 
        
           |  |  | 147 |      * @return null|array null or array of \core\update\info objects
 | 
        
           |  |  | 148 |      */
 | 
        
           |  |  | 149 |     public function get_update_info($component, array $options = array()) {
 | 
        
           |  |  | 150 |   | 
        
           |  |  | 151 |         if (!isset($options['minmaturity'])) {
 | 
        
           |  |  | 152 |             $options['minmaturity'] = 0;
 | 
        
           |  |  | 153 |         }
 | 
        
           |  |  | 154 |   | 
        
           |  |  | 155 |         if (!isset($options['notifybuilds'])) {
 | 
        
           |  |  | 156 |             $options['notifybuilds'] = false;
 | 
        
           |  |  | 157 |         }
 | 
        
           |  |  | 158 |   | 
        
           |  |  | 159 |         if ($component === 'core') {
 | 
        
           |  |  | 160 |             $this->load_current_environment();
 | 
        
           |  |  | 161 |         }
 | 
        
           |  |  | 162 |   | 
        
           |  |  | 163 |         $this->restore_response();
 | 
        
           |  |  | 164 |   | 
        
           |  |  | 165 |         if (empty($this->recentresponse['updates'][$component])) {
 | 
        
           |  |  | 166 |             return null;
 | 
        
           |  |  | 167 |         }
 | 
        
           |  |  | 168 |   | 
        
           |  |  | 169 |         $updates = array();
 | 
        
           |  |  | 170 |         foreach ($this->recentresponse['updates'][$component] as $info) {
 | 
        
           |  |  | 171 |             $update = new info($component, $info);
 | 
        
           |  |  | 172 |             if (isset($update->maturity) and ($update->maturity < $options['minmaturity'])) {
 | 
        
           |  |  | 173 |                 continue;
 | 
        
           |  |  | 174 |             }
 | 
        
           |  |  | 175 |             if ($component === 'core') {
 | 
        
           |  |  | 176 |                 if ($update->version <= $this->currentversion) {
 | 
        
           |  |  | 177 |                     continue;
 | 
        
           |  |  | 178 |                 }
 | 
        
           |  |  | 179 |                 if (empty($options['notifybuilds']) and $this->is_same_release($update->release)) {
 | 
        
           |  |  | 180 |                     continue;
 | 
        
           |  |  | 181 |                 }
 | 
        
           |  |  | 182 |             }
 | 
        
           |  |  | 183 |             $updates[] = $update;
 | 
        
           |  |  | 184 |         }
 | 
        
           |  |  | 185 |   | 
        
           |  |  | 186 |         if (empty($updates)) {
 | 
        
           |  |  | 187 |             return null;
 | 
        
           |  |  | 188 |         }
 | 
        
           |  |  | 189 |   | 
        
           |  |  | 190 |         return $updates;
 | 
        
           |  |  | 191 |     }
 | 
        
           |  |  | 192 |   | 
        
           |  |  | 193 |     /**
 | 
        
           |  |  | 194 |      * The method being run via cron.php
 | 
        
           |  |  | 195 |      */
 | 
        
           |  |  | 196 |     public function cron() {
 | 
        
           |  |  | 197 |         global $CFG;
 | 
        
           |  |  | 198 |   | 
        
           |  |  | 199 |         if (!$this->enabled() or !$this->cron_autocheck_enabled()) {
 | 
        
           |  |  | 200 |             $this->cron_mtrace('Automatic check for available updates not enabled, skipping.');
 | 
        
           |  |  | 201 |             return;
 | 
        
           |  |  | 202 |         }
 | 
        
           |  |  | 203 |   | 
        
           |  |  | 204 |         $now = $this->cron_current_timestamp();
 | 
        
           |  |  | 205 |   | 
        
           |  |  | 206 |         if ($this->cron_has_fresh_fetch($now)) {
 | 
        
           |  |  | 207 |             $this->cron_mtrace('Recently fetched info about available updates is still fresh enough, skipping.');
 | 
        
           |  |  | 208 |             return;
 | 
        
           |  |  | 209 |         }
 | 
        
           |  |  | 210 |   | 
        
           |  |  | 211 |         if ($this->cron_has_outdated_fetch($now)) {
 | 
        
           |  |  | 212 |             $this->cron_mtrace('Outdated or missing info about available updates, forced fetching ... ', '');
 | 
        
           |  |  | 213 |             $this->cron_execute();
 | 
        
           |  |  | 214 |             return;
 | 
        
           |  |  | 215 |         }
 | 
        
           |  |  | 216 |   | 
        
           |  |  | 217 |         $offset = $this->cron_execution_offset();
 | 
        
           |  |  | 218 |         $start = mktime(1, 0, 0, date('n', $now), date('j', $now), date('Y', $now)); // 01:00 AM today local time
 | 
        
           |  |  | 219 |         if ($now > $start + $offset) {
 | 
        
           |  |  | 220 |             $this->cron_mtrace('Regular daily check for available updates ... ', '');
 | 
        
           |  |  | 221 |             $this->cron_execute();
 | 
        
           |  |  | 222 |             return;
 | 
        
           |  |  | 223 |         }
 | 
        
           |  |  | 224 |     }
 | 
        
           |  |  | 225 |   | 
        
           |  |  | 226 |     /* === End of public API === */
 | 
        
           |  |  | 227 |   | 
        
           |  |  | 228 |     /**
 | 
        
           |  |  | 229 |      * Makes cURL request to get data from the remote site
 | 
        
           |  |  | 230 |      *
 | 
        
           |  |  | 231 |      * @return string raw request result
 | 
        
           |  |  | 232 |      * @throws checker_exception
 | 
        
           |  |  | 233 |      */
 | 
        
           |  |  | 234 |     protected function get_response() {
 | 
        
           |  |  | 235 |         global $CFG;
 | 
        
           |  |  | 236 |         require_once($CFG->libdir.'/filelib.php');
 | 
        
           |  |  | 237 |   | 
        
           |  |  | 238 |         $curl = new \curl(array('proxy' => true));
 | 
        
           |  |  | 239 |         $response = $curl->post($this->prepare_request_url(), $this->prepare_request_params(), $this->prepare_request_options());
 | 
        
           |  |  | 240 |         $curlerrno = $curl->get_errno();
 | 
        
           |  |  | 241 |         if (!empty($curlerrno)) {
 | 
        
           |  |  | 242 |             throw new checker_exception('err_response_curl', 'cURL error '.$curlerrno.': '.$curl->error);
 | 
        
           |  |  | 243 |         }
 | 
        
           |  |  | 244 |         $curlinfo = $curl->get_info();
 | 
        
           |  |  | 245 |         if ($curlinfo['http_code'] != 200) {
 | 
        
           |  |  | 246 |             throw new checker_exception('err_response_http_code', $curlinfo['http_code']);
 | 
        
           |  |  | 247 |         }
 | 
        
           |  |  | 248 |         return $response;
 | 
        
           |  |  | 249 |     }
 | 
        
           |  |  | 250 |   | 
        
           |  |  | 251 |     /**
 | 
        
           |  |  | 252 |      * Makes sure the response is valid, has correct API format etc.
 | 
        
           |  |  | 253 |      *
 | 
        
           |  |  | 254 |      * @param string $response raw response as returned by the {@link self::get_response()}
 | 
        
           |  |  | 255 |      * @throws checker_exception
 | 
        
           |  |  | 256 |      */
 | 
        
           |  |  | 257 |     protected function validate_response($response) {
 | 
        
           |  |  | 258 |   | 
        
           |  |  | 259 |         $response = $this->decode_response($response);
 | 
        
           |  |  | 260 |   | 
        
           |  |  | 261 |         if (empty($response)) {
 | 
        
           |  |  | 262 |             throw new checker_exception('err_response_empty');
 | 
        
           |  |  | 263 |         }
 | 
        
           |  |  | 264 |   | 
        
           |  |  | 265 |         if (empty($response['status']) or $response['status'] !== 'OK') {
 | 
        
           |  |  | 266 |             throw new checker_exception('err_response_status', $response['status']);
 | 
        
           |  |  | 267 |         }
 | 
        
           |  |  | 268 |   | 
        
           |  |  | 269 |         if (empty($response['apiver']) or $response['apiver'] !== '1.3') {
 | 
        
           |  |  | 270 |             throw new checker_exception('err_response_format_version', $response['apiver']);
 | 
        
           |  |  | 271 |         }
 | 
        
           |  |  | 272 |   | 
        
           |  |  | 273 |         if (empty($response['forbranch']) or $response['forbranch'] !== moodle_major_version(true)) {
 | 
        
           |  |  | 274 |             throw new checker_exception('err_response_target_version', $response['forbranch']);
 | 
        
           |  |  | 275 |         }
 | 
        
           |  |  | 276 |     }
 | 
        
           |  |  | 277 |   | 
        
           |  |  | 278 |     /**
 | 
        
           |  |  | 279 |      * Decodes the raw string response from the update notifications provider
 | 
        
           |  |  | 280 |      *
 | 
        
           |  |  | 281 |      * @param string $response as returned by {@link self::get_response()}
 | 
        
           |  |  | 282 |      * @return array decoded response structure
 | 
        
           |  |  | 283 |      */
 | 
        
           |  |  | 284 |     protected function decode_response($response) {
 | 
        
           |  |  | 285 |         return json_decode($response, true);
 | 
        
           |  |  | 286 |     }
 | 
        
           |  |  | 287 |   | 
        
           |  |  | 288 |     /**
 | 
        
           |  |  | 289 |      * Stores the valid fetched response for later usage
 | 
        
           |  |  | 290 |      *
 | 
        
           |  |  | 291 |      * This implementation uses the config_plugins table as the permanent storage.
 | 
        
           |  |  | 292 |      *
 | 
        
           |  |  | 293 |      * @param string $response raw valid data returned by {@link self::get_response()}
 | 
        
           |  |  | 294 |      */
 | 
        
           |  |  | 295 |     protected function store_response($response) {
 | 
        
           |  |  | 296 |   | 
        
           |  |  | 297 |         set_config('recentfetch', time(), 'core_plugin');
 | 
        
           |  |  | 298 |         set_config('recentresponse', $response, 'core_plugin');
 | 
        
           |  |  | 299 |   | 
        
           |  |  | 300 |         if (defined('CACHE_DISABLE_ALL') and CACHE_DISABLE_ALL) {
 | 
        
           |  |  | 301 |             // Very nasty hack to work around cache coherency issues on admin/index.php?cache=0 page,
 | 
        
           |  |  | 302 |             // we definitely need to keep caches in sync when writing into DB at all times!
 | 
        
           |  |  | 303 |             \cache_helper::purge_all(true);
 | 
        
           |  |  | 304 |         }
 | 
        
           |  |  | 305 |   | 
        
           |  |  | 306 |         $this->restore_response(true);
 | 
        
           |  |  | 307 |     }
 | 
        
           |  |  | 308 |   | 
        
           |  |  | 309 |     /**
 | 
        
           |  |  | 310 |      * Loads the most recent raw response record we have fetched
 | 
        
           |  |  | 311 |      *
 | 
        
           |  |  | 312 |      * After this method is called, $this->recentresponse is set to an array. If the
 | 
        
           |  |  | 313 |      * array is empty, then either no data have been fetched yet or the fetched data
 | 
        
           |  |  | 314 |      * do not have expected format (and thence they are ignored and a debugging
 | 
        
           |  |  | 315 |      * message is displayed).
 | 
        
           |  |  | 316 |      *
 | 
        
           |  |  | 317 |      * This implementation uses the config_plugins table as the permanent storage.
 | 
        
           |  |  | 318 |      *
 | 
        
           |  |  | 319 |      * @param bool $forcereload reload even if it was already loaded
 | 
        
           |  |  | 320 |      */
 | 
        
           |  |  | 321 |     protected function restore_response($forcereload = false) {
 | 
        
           |  |  | 322 |   | 
        
           |  |  | 323 |         if (!$forcereload and !is_null($this->recentresponse)) {
 | 
        
           |  |  | 324 |             // We already have it, nothing to do.
 | 
        
           |  |  | 325 |             return;
 | 
        
           |  |  | 326 |         }
 | 
        
           |  |  | 327 |   | 
        
           |  |  | 328 |         $config = get_config('core_plugin');
 | 
        
           |  |  | 329 |   | 
        
           |  |  | 330 |         if (!empty($config->recentresponse) and !empty($config->recentfetch)) {
 | 
        
           |  |  | 331 |             try {
 | 
        
           |  |  | 332 |                 $this->validate_response($config->recentresponse);
 | 
        
           |  |  | 333 |                 $this->recentfetch = $config->recentfetch;
 | 
        
           |  |  | 334 |                 $this->recentresponse = $this->decode_response($config->recentresponse);
 | 
        
           |  |  | 335 |             } catch (checker_exception $e) {
 | 
        
           |  |  | 336 |                 // The server response is not valid. Behave as if no data were fetched yet.
 | 
        
           |  |  | 337 |                 // This may happen when the most recent update info (cached locally) has been
 | 
        
           |  |  | 338 |                 // fetched with the previous branch of Moodle (like during an upgrade from 2.x
 | 
        
           |  |  | 339 |                 // to 2.y) or when the API of the response has changed.
 | 
        
           |  |  | 340 |                 $this->recentresponse = array();
 | 
        
           |  |  | 341 |             }
 | 
        
           |  |  | 342 |   | 
        
           |  |  | 343 |         } else {
 | 
        
           |  |  | 344 |             $this->recentresponse = array();
 | 
        
           |  |  | 345 |         }
 | 
        
           |  |  | 346 |     }
 | 
        
           |  |  | 347 |   | 
        
           |  |  | 348 |     /**
 | 
        
           |  |  | 349 |      * Compares two raw {@link $recentresponse} records and returns the list of changed updates
 | 
        
           |  |  | 350 |      *
 | 
        
           |  |  | 351 |      * This method is used to populate potential update info to be sent to site admins.
 | 
        
           |  |  | 352 |      *
 | 
        
           |  |  | 353 |      * @param array $old
 | 
        
           |  |  | 354 |      * @param array $new
 | 
        
           |  |  | 355 |      * @throws checker_exception
 | 
        
           |  |  | 356 |      * @return array parts of $new['updates'] that have changed
 | 
        
           |  |  | 357 |      */
 | 
        
           |  |  | 358 |     protected function compare_responses(array $old, array $new) {
 | 
        
           |  |  | 359 |   | 
        
           |  |  | 360 |         if (empty($new)) {
 | 
        
           |  |  | 361 |             return array();
 | 
        
           |  |  | 362 |         }
 | 
        
           |  |  | 363 |   | 
        
           |  |  | 364 |         if (!array_key_exists('updates', $new)) {
 | 
        
           |  |  | 365 |             throw new checker_exception('err_response_format');
 | 
        
           |  |  | 366 |         }
 | 
        
           |  |  | 367 |   | 
        
           |  |  | 368 |         if (empty($old)) {
 | 
        
           |  |  | 369 |             return $new['updates'];
 | 
        
           |  |  | 370 |         }
 | 
        
           |  |  | 371 |   | 
        
           |  |  | 372 |         if (!array_key_exists('updates', $old)) {
 | 
        
           |  |  | 373 |             throw new checker_exception('err_response_format');
 | 
        
           |  |  | 374 |         }
 | 
        
           |  |  | 375 |   | 
        
           |  |  | 376 |         $changes = array();
 | 
        
           |  |  | 377 |   | 
        
           |  |  | 378 |         foreach ($new['updates'] as $newcomponent => $newcomponentupdates) {
 | 
        
           |  |  | 379 |             if (empty($old['updates'][$newcomponent])) {
 | 
        
           |  |  | 380 |                 $changes[$newcomponent] = $newcomponentupdates;
 | 
        
           |  |  | 381 |                 continue;
 | 
        
           |  |  | 382 |             }
 | 
        
           |  |  | 383 |             foreach ($newcomponentupdates as $newcomponentupdate) {
 | 
        
           |  |  | 384 |                 $inold = false;
 | 
        
           |  |  | 385 |                 foreach ($old['updates'][$newcomponent] as $oldcomponentupdate) {
 | 
        
           |  |  | 386 |                     if ($newcomponentupdate['version'] == $oldcomponentupdate['version']) {
 | 
        
           |  |  | 387 |                         $inold = true;
 | 
        
           |  |  | 388 |                     }
 | 
        
           |  |  | 389 |                 }
 | 
        
           |  |  | 390 |                 if (!$inold) {
 | 
        
           |  |  | 391 |                     if (!isset($changes[$newcomponent])) {
 | 
        
           |  |  | 392 |                         $changes[$newcomponent] = array();
 | 
        
           |  |  | 393 |                     }
 | 
        
           |  |  | 394 |                     $changes[$newcomponent][] = $newcomponentupdate;
 | 
        
           |  |  | 395 |                 }
 | 
        
           |  |  | 396 |             }
 | 
        
           |  |  | 397 |         }
 | 
        
           |  |  | 398 |   | 
        
           |  |  | 399 |         return $changes;
 | 
        
           |  |  | 400 |     }
 | 
        
           |  |  | 401 |   | 
        
           |  |  | 402 |     /**
 | 
        
           |  |  | 403 |      * Returns the URL to send update requests to
 | 
        
           |  |  | 404 |      *
 | 
        
           |  |  | 405 |      * During the development or testing, you can set $CFG->alternativeupdateproviderurl
 | 
        
           |  |  | 406 |      * to a custom URL that will be used. Otherwise the standard URL will be returned.
 | 
        
           |  |  | 407 |      *
 | 
        
           |  |  | 408 |      * @return string URL
 | 
        
           |  |  | 409 |      */
 | 
        
           |  |  | 410 |     protected function prepare_request_url() {
 | 
        
           |  |  | 411 |         global $CFG;
 | 
        
           |  |  | 412 |   | 
        
           |  |  | 413 |         if (!empty($CFG->config_php_settings['alternativeupdateproviderurl'])) {
 | 
        
           |  |  | 414 |             return $CFG->config_php_settings['alternativeupdateproviderurl'];
 | 
        
           |  |  | 415 |         } else {
 | 
        
           |  |  | 416 |             return 'https://download.moodle.org/api/1.3/updates.php';
 | 
        
           |  |  | 417 |         }
 | 
        
           |  |  | 418 |     }
 | 
        
           |  |  | 419 |   | 
        
           |  |  | 420 |     /**
 | 
        
           |  |  | 421 |      * Sets the properties currentversion, currentrelease, currentbranch and currentplugins
 | 
        
           |  |  | 422 |      *
 | 
        
           |  |  | 423 |      * @param bool $forcereload
 | 
        
           |  |  | 424 |      */
 | 
        
           |  |  | 425 |     protected function load_current_environment($forcereload=false) {
 | 
        
           |  |  | 426 |         global $CFG;
 | 
        
           |  |  | 427 |   | 
        
           |  |  | 428 |         if (!is_null($this->currentversion) and !$forcereload) {
 | 
        
           |  |  | 429 |             // Nothing to do.
 | 
        
           |  |  | 430 |             return;
 | 
        
           |  |  | 431 |         }
 | 
        
           |  |  | 432 |   | 
        
           |  |  | 433 |         $version = null;
 | 
        
           |  |  | 434 |         $release = null;
 | 
        
           |  |  | 435 |   | 
        
           |  |  | 436 |         require($CFG->dirroot.'/version.php');
 | 
        
           |  |  | 437 |         $this->currentversion = $version;
 | 
        
           |  |  | 438 |         $this->currentrelease = $release;
 | 
        
           |  |  | 439 |         $this->currentbranch = moodle_major_version(true);
 | 
        
           |  |  | 440 |   | 
        
           |  |  | 441 |         $pluginman = \core_plugin_manager::instance();
 | 
        
           |  |  | 442 |         foreach ($pluginman->get_plugins() as $type => $plugins) {
 | 
        
           |  |  | 443 |             // Iterate over installed plugins and determine which are non-standard and eligible for update checks. Note that we
 | 
        
           |  |  | 444 |             // disregard empty component names here, to ensure we only request valid data from the update site (in the case of an
 | 
        
           |  |  | 445 |             // improperly removed plugin containing sub-plugins, we would get an empty value here for each sub-plugin).
 | 
        
           |  |  | 446 |             foreach ($plugins as $plugin) {
 | 
        
           |  |  | 447 |                 if ($plugin->component !== '' && !$plugin->is_standard()) {
 | 
        
           |  |  | 448 |                     $this->currentplugins[$plugin->component] = $plugin->versiondisk;
 | 
        
           |  |  | 449 |                 }
 | 
        
           |  |  | 450 |             }
 | 
        
           |  |  | 451 |         }
 | 
        
           |  |  | 452 |     }
 | 
        
           |  |  | 453 |   | 
        
           |  |  | 454 |     /**
 | 
        
           |  |  | 455 |      * Returns the list of HTTP params to be sent to the updates provider URL
 | 
        
           |  |  | 456 |      *
 | 
        
           |  |  | 457 |      * @return array of (string)param => (string)value
 | 
        
           |  |  | 458 |      */
 | 
        
           |  |  | 459 |     protected function prepare_request_params() {
 | 
        
           |  |  | 460 |         global $CFG;
 | 
        
           |  |  | 461 |   | 
        
           |  |  | 462 |         $this->load_current_environment();
 | 
        
           |  |  | 463 |         $this->restore_response();
 | 
        
           |  |  | 464 |   | 
        
           |  |  | 465 |         $params = array();
 | 
        
           |  |  | 466 |         $params['format'] = 'json';
 | 
        
           |  |  | 467 |   | 
        
           |  |  | 468 |         if (isset($this->recentresponse['ticket'])) {
 | 
        
           |  |  | 469 |             $params['ticket'] = $this->recentresponse['ticket'];
 | 
        
           |  |  | 470 |         }
 | 
        
           |  |  | 471 |   | 
        
           |  |  | 472 |         if (isset($this->currentversion)) {
 | 
        
           |  |  | 473 |             $params['version'] = $this->currentversion;
 | 
        
           |  |  | 474 |         } else {
 | 
        
           |  |  | 475 |             throw new coding_exception('Main Moodle version must be already known here');
 | 
        
           |  |  | 476 |         }
 | 
        
           |  |  | 477 |   | 
        
           |  |  | 478 |         if (isset($this->currentbranch)) {
 | 
        
           |  |  | 479 |             $params['branch'] = $this->currentbranch;
 | 
        
           |  |  | 480 |         } else {
 | 
        
           |  |  | 481 |             throw new coding_exception('Moodle release must be already known here');
 | 
        
           |  |  | 482 |         }
 | 
        
           |  |  | 483 |   | 
        
           |  |  | 484 |         $plugins = array();
 | 
        
           |  |  | 485 |         foreach ($this->currentplugins as $plugin => $version) {
 | 
        
           |  |  | 486 |             $plugins[] = $plugin.'@'.$version;
 | 
        
           |  |  | 487 |         }
 | 
        
           |  |  | 488 |         if (!empty($plugins)) {
 | 
        
           |  |  | 489 |             $params['plugins'] = implode(',', $plugins);
 | 
        
           |  |  | 490 |         }
 | 
        
           |  |  | 491 |   | 
        
           |  |  | 492 |         return $params;
 | 
        
           |  |  | 493 |     }
 | 
        
           |  |  | 494 |   | 
        
           |  |  | 495 |     /**
 | 
        
           |  |  | 496 |      * Returns the list of cURL options to use when fetching available updates data
 | 
        
           |  |  | 497 |      *
 | 
        
           |  |  | 498 |      * @return array of (string)param => (string)value
 | 
        
           |  |  | 499 |      */
 | 
        
           |  |  | 500 |     protected function prepare_request_options() {
 | 
        
           |  |  | 501 |         $options = array(
 | 
        
           |  |  | 502 |             'CURLOPT_SSL_VERIFYHOST' => 2,      // This is the default in {@link curl} class but just in case.
 | 
        
           |  |  | 503 |             'CURLOPT_SSL_VERIFYPEER' => true,
 | 
        
           |  |  | 504 |         );
 | 
        
           |  |  | 505 |   | 
        
           |  |  | 506 |         return $options;
 | 
        
           |  |  | 507 |     }
 | 
        
           |  |  | 508 |   | 
        
           |  |  | 509 |     /**
 | 
        
           |  |  | 510 |      * Returns the current timestamp
 | 
        
           |  |  | 511 |      *
 | 
        
           |  |  | 512 |      * @return int the timestamp
 | 
        
           |  |  | 513 |      */
 | 
        
           |  |  | 514 |     protected function cron_current_timestamp() {
 | 
        
           |  |  | 515 |         return time();
 | 
        
           |  |  | 516 |     }
 | 
        
           |  |  | 517 |   | 
        
           |  |  | 518 |     /**
 | 
        
           |  |  | 519 |      * Output cron debugging info
 | 
        
           |  |  | 520 |      *
 | 
        
           |  |  | 521 |      * @see mtrace()
 | 
        
           |  |  | 522 |      * @param string $msg output message
 | 
        
           |  |  | 523 |      * @param string $eol end of line
 | 
        
           |  |  | 524 |      */
 | 
        
           |  |  | 525 |     protected function cron_mtrace($msg, $eol = PHP_EOL) {
 | 
        
           |  |  | 526 |         mtrace($msg, $eol);
 | 
        
           |  |  | 527 |     }
 | 
        
           |  |  | 528 |   | 
        
           |  |  | 529 |     /**
 | 
        
           |  |  | 530 |      * Decide if the autocheck feature is disabled in the server setting
 | 
        
           |  |  | 531 |      *
 | 
        
           |  |  | 532 |      * @return bool true if autocheck enabled, false if disabled
 | 
        
           |  |  | 533 |      */
 | 
        
           |  |  | 534 |     protected function cron_autocheck_enabled() {
 | 
        
           |  |  | 535 |         global $CFG;
 | 
        
           |  |  | 536 |   | 
        
           |  |  | 537 |         if (empty($CFG->updateautocheck)) {
 | 
        
           |  |  | 538 |             return false;
 | 
        
           |  |  | 539 |         } else {
 | 
        
           |  |  | 540 |             return true;
 | 
        
           |  |  | 541 |         }
 | 
        
           |  |  | 542 |     }
 | 
        
           |  |  | 543 |   | 
        
           |  |  | 544 |     /**
 | 
        
           |  |  | 545 |      * Decide if the recently fetched data are still fresh enough
 | 
        
           |  |  | 546 |      *
 | 
        
           |  |  | 547 |      * @param int $now current timestamp
 | 
        
           |  |  | 548 |      * @return bool true if no need to re-fetch, false otherwise
 | 
        
           |  |  | 549 |      */
 | 
        
           |  |  | 550 |     protected function cron_has_fresh_fetch($now) {
 | 
        
           |  |  | 551 |         $recent = $this->get_last_timefetched();
 | 
        
           |  |  | 552 |   | 
        
           |  |  | 553 |         if (empty($recent)) {
 | 
        
           |  |  | 554 |             return false;
 | 
        
           |  |  | 555 |         }
 | 
        
           |  |  | 556 |   | 
        
           |  |  | 557 |         if ($now < $recent) {
 | 
        
           |  |  | 558 |             $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
 | 
        
           |  |  | 559 |             return true;
 | 
        
           |  |  | 560 |         }
 | 
        
           |  |  | 561 |   | 
        
           |  |  | 562 |         if ($now - $recent > 24 * HOURSECS) {
 | 
        
           |  |  | 563 |             return false;
 | 
        
           |  |  | 564 |         }
 | 
        
           |  |  | 565 |   | 
        
           |  |  | 566 |         return true;
 | 
        
           |  |  | 567 |     }
 | 
        
           |  |  | 568 |   | 
        
           |  |  | 569 |     /**
 | 
        
           |  |  | 570 |      * Decide if the fetch is outadated or even missing
 | 
        
           |  |  | 571 |      *
 | 
        
           |  |  | 572 |      * @param int $now current timestamp
 | 
        
           |  |  | 573 |      * @return bool false if no need to re-fetch, true otherwise
 | 
        
           |  |  | 574 |      */
 | 
        
           |  |  | 575 |     protected function cron_has_outdated_fetch($now) {
 | 
        
           |  |  | 576 |         $recent = $this->get_last_timefetched();
 | 
        
           |  |  | 577 |   | 
        
           |  |  | 578 |         if (empty($recent)) {
 | 
        
           |  |  | 579 |             return true;
 | 
        
           |  |  | 580 |         }
 | 
        
           |  |  | 581 |   | 
        
           |  |  | 582 |         if ($now < $recent) {
 | 
        
           |  |  | 583 |             $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
 | 
        
           |  |  | 584 |             return false;
 | 
        
           |  |  | 585 |         }
 | 
        
           |  |  | 586 |   | 
        
           |  |  | 587 |         if ($now - $recent > 48 * HOURSECS) {
 | 
        
           |  |  | 588 |             return true;
 | 
        
           |  |  | 589 |         }
 | 
        
           |  |  | 590 |   | 
        
           |  |  | 591 |         return false;
 | 
        
           |  |  | 592 |     }
 | 
        
           |  |  | 593 |   | 
        
           |  |  | 594 |     /**
 | 
        
           |  |  | 595 |      * Returns the cron execution offset for this site
 | 
        
           |  |  | 596 |      *
 | 
        
           |  |  | 597 |      * The main {@link self::cron()} is supposed to run every night in some random time
 | 
        
           |  |  | 598 |      * between 01:00 and 06:00 AM (local time). The exact moment is defined by so called
 | 
        
           |  |  | 599 |      * execution offset, that is the amount of time after 01:00 AM. The offset value is
 | 
        
           |  |  | 600 |      * initially generated randomly and then used consistently at the site. This way, the
 | 
        
           |  |  | 601 |      * regular checks against the download.moodle.org server are spread in time.
 | 
        
           |  |  | 602 |      *
 | 
        
           |  |  | 603 |      * @return int the offset number of seconds from range 1 sec to 5 hours
 | 
        
           |  |  | 604 |      */
 | 
        
           |  |  | 605 |     protected function cron_execution_offset() {
 | 
        
           |  |  | 606 |         global $CFG;
 | 
        
           |  |  | 607 |   | 
        
           |  |  | 608 |         if (empty($CFG->updatecronoffset)) {
 | 
        
           |  |  | 609 |             set_config('updatecronoffset', rand(1, 5 * HOURSECS));
 | 
        
           |  |  | 610 |         }
 | 
        
           |  |  | 611 |   | 
        
           |  |  | 612 |         return $CFG->updatecronoffset;
 | 
        
           |  |  | 613 |     }
 | 
        
           |  |  | 614 |   | 
        
           |  |  | 615 |     /**
 | 
        
           |  |  | 616 |      * Fetch available updates info and eventually send notification to site admins
 | 
        
           |  |  | 617 |      */
 | 
        
           |  |  | 618 |     protected function cron_execute() {
 | 
        
           |  |  | 619 |   | 
        
           |  |  | 620 |         try {
 | 
        
           |  |  | 621 |             $this->restore_response();
 | 
        
           |  |  | 622 |             $previous = $this->recentresponse;
 | 
        
           |  |  | 623 |             $this->fetch();
 | 
        
           |  |  | 624 |             $this->restore_response(true);
 | 
        
           |  |  | 625 |             $current = $this->recentresponse;
 | 
        
           |  |  | 626 |             $changes = $this->compare_responses($previous, $current);
 | 
        
           |  |  | 627 |             $notifications = $this->cron_notifications($changes);
 | 
        
           |  |  | 628 |             $this->cron_notify($notifications);
 | 
        
           |  |  | 629 |             $this->cron_mtrace('done');
 | 
        
           |  |  | 630 |         } catch (checker_exception $e) {
 | 
        
           |  |  | 631 |             $this->cron_mtrace('FAILED!');
 | 
        
           |  |  | 632 |         }
 | 
        
           |  |  | 633 |     }
 | 
        
           |  |  | 634 |   | 
        
           |  |  | 635 |     /**
 | 
        
           |  |  | 636 |      * Given the list of changes in available updates, pick those to send to site admins
 | 
        
           |  |  | 637 |      *
 | 
        
           |  |  | 638 |      * @param array $changes as returned by {@link self::compare_responses()}
 | 
        
           |  |  | 639 |      * @return array of \core\update\info objects to send to site admins
 | 
        
           |  |  | 640 |      */
 | 
        
           |  |  | 641 |     protected function cron_notifications(array $changes) {
 | 
        
           |  |  | 642 |         global $CFG;
 | 
        
           |  |  | 643 |   | 
        
           |  |  | 644 |         if (empty($changes)) {
 | 
        
           |  |  | 645 |             return array();
 | 
        
           |  |  | 646 |         }
 | 
        
           |  |  | 647 |   | 
        
           |  |  | 648 |         $notifications = array();
 | 
        
           |  |  | 649 |         $pluginman = \core_plugin_manager::instance();
 | 
        
           |  |  | 650 |         $plugins = $pluginman->get_plugins();
 | 
        
           |  |  | 651 |   | 
        
           |  |  | 652 |         foreach ($changes as $component => $componentchanges) {
 | 
        
           |  |  | 653 |             if (empty($componentchanges)) {
 | 
        
           |  |  | 654 |                 continue;
 | 
        
           |  |  | 655 |             }
 | 
        
           |  |  | 656 |             $componentupdates = $this->get_update_info($component,
 | 
        
           |  |  | 657 |                 array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds));
 | 
        
           |  |  | 658 |             if (empty($componentupdates)) {
 | 
        
           |  |  | 659 |                 continue;
 | 
        
           |  |  | 660 |             }
 | 
        
           |  |  | 661 |             // Notify only about those $componentchanges that are present in $componentupdates
 | 
        
           |  |  | 662 |             // to respect the preferences.
 | 
        
           |  |  | 663 |             foreach ($componentchanges as $componentchange) {
 | 
        
           |  |  | 664 |                 foreach ($componentupdates as $componentupdate) {
 | 
        
           |  |  | 665 |                     if ($componentupdate->version == $componentchange['version']) {
 | 
        
           |  |  | 666 |                         if ($component == 'core') {
 | 
        
           |  |  | 667 |                             // In case of 'core', we already know that the $componentupdate
 | 
        
           |  |  | 668 |                             // is a real update with higher version ({@see self::get_update_info()}).
 | 
        
           |  |  | 669 |                             // We just perform additional check for the release property as there
 | 
        
           |  |  | 670 |                             // can be two Moodle releases having the same version (e.g. 2.4.0 and 2.5dev shortly
 | 
        
           |  |  | 671 |                             // after the release). We can do that because we have the release info
 | 
        
           |  |  | 672 |                             // always available for the core.
 | 
        
           |  |  | 673 |                             if ((string)$componentupdate->release === (string)$componentchange['release']) {
 | 
        
           |  |  | 674 |                                 $notifications[] = $componentupdate;
 | 
        
           |  |  | 675 |                             }
 | 
        
           |  |  | 676 |                         } else {
 | 
        
           |  |  | 677 |                             // Use the core_plugin_manager to check if the detected $componentchange
 | 
        
           |  |  | 678 |                             // is a real update with higher version. That is, the $componentchange
 | 
        
           |  |  | 679 |                             // is present in the array of {@link \core\update\info} objects
 | 
        
           |  |  | 680 |                             // returned by the plugin's available_updates() method.
 | 
        
           |  |  | 681 |                             list($plugintype, $pluginname) = core_component::normalize_component($component);
 | 
        
           |  |  | 682 |                             if (!empty($plugins[$plugintype][$pluginname])) {
 | 
        
           |  |  | 683 |                                 $availableupdates = $plugins[$plugintype][$pluginname]->available_updates();
 | 
        
           |  |  | 684 |                                 if (!empty($availableupdates)) {
 | 
        
           |  |  | 685 |                                     foreach ($availableupdates as $availableupdate) {
 | 
        
           |  |  | 686 |                                         if ($availableupdate->version == $componentchange['version']) {
 | 
        
           |  |  | 687 |                                             $notifications[] = $componentupdate;
 | 
        
           |  |  | 688 |                                         }
 | 
        
           |  |  | 689 |                                     }
 | 
        
           |  |  | 690 |                                 }
 | 
        
           |  |  | 691 |                             }
 | 
        
           |  |  | 692 |                         }
 | 
        
           |  |  | 693 |                     }
 | 
        
           |  |  | 694 |                 }
 | 
        
           |  |  | 695 |             }
 | 
        
           |  |  | 696 |         }
 | 
        
           |  |  | 697 |   | 
        
           |  |  | 698 |         return $notifications;
 | 
        
           |  |  | 699 |     }
 | 
        
           |  |  | 700 |   | 
        
           |  |  | 701 |     /**
 | 
        
           |  |  | 702 |      * Sends the given notifications to site admins via messaging API
 | 
        
           |  |  | 703 |      *
 | 
        
           |  |  | 704 |      * @param array $notifications array of \core\update\info objects to send
 | 
        
           |  |  | 705 |      */
 | 
        
           |  |  | 706 |     protected function cron_notify(array $notifications) {
 | 
        
           |  |  | 707 |         global $CFG;
 | 
        
           |  |  | 708 |   | 
        
           |  |  | 709 |         if (empty($notifications)) {
 | 
        
           |  |  | 710 |             $this->cron_mtrace('nothing to notify about. ', '');
 | 
        
           |  |  | 711 |             return;
 | 
        
           |  |  | 712 |         }
 | 
        
           |  |  | 713 |   | 
        
           |  |  | 714 |         $admins = get_admins();
 | 
        
           |  |  | 715 |   | 
        
           |  |  | 716 |         if (empty($admins)) {
 | 
        
           |  |  | 717 |             return;
 | 
        
           |  |  | 718 |         }
 | 
        
           |  |  | 719 |   | 
        
           |  |  | 720 |         $this->cron_mtrace('sending notifications ... ', '');
 | 
        
           |  |  | 721 |   | 
        
           |  |  | 722 |         $text = get_string('updatenotifications', 'core_admin') . PHP_EOL;
 | 
        
           |  |  | 723 |         $html = html_writer::tag('h1', get_string('updatenotifications', 'core_admin')) . PHP_EOL;
 | 
        
           |  |  | 724 |   | 
        
           |  |  | 725 |         $coreupdates = array();
 | 
        
           |  |  | 726 |         $pluginupdates = array();
 | 
        
           |  |  | 727 |   | 
        
           |  |  | 728 |         foreach ($notifications as $notification) {
 | 
        
           |  |  | 729 |             if ($notification->component == 'core') {
 | 
        
           |  |  | 730 |                 $coreupdates[] = $notification;
 | 
        
           |  |  | 731 |             } else {
 | 
        
           |  |  | 732 |                 $pluginupdates[] = $notification;
 | 
        
           |  |  | 733 |             }
 | 
        
           |  |  | 734 |         }
 | 
        
           |  |  | 735 |   | 
        
           |  |  | 736 |         if (!empty($coreupdates)) {
 | 
        
           |  |  | 737 |             $text .= PHP_EOL . get_string('updateavailable', 'core_admin') . PHP_EOL;
 | 
        
           |  |  | 738 |             $html .= html_writer::tag('h2', get_string('updateavailable', 'core_admin')) . PHP_EOL;
 | 
        
           |  |  | 739 |             $html .= html_writer::start_tag('ul') . PHP_EOL;
 | 
        
           |  |  | 740 |             foreach ($coreupdates as $coreupdate) {
 | 
        
           |  |  | 741 |                 $html .= html_writer::start_tag('li');
 | 
        
           |  |  | 742 |                 if (isset($coreupdate->release)) {
 | 
        
           |  |  | 743 |                     $text .= get_string('updateavailable_release', 'core_admin', $coreupdate->release);
 | 
        
           |  |  | 744 |                     $html .= html_writer::tag('strong', get_string('updateavailable_release', 'core_admin', $coreupdate->release));
 | 
        
           |  |  | 745 |                 }
 | 
        
           |  |  | 746 |                 if (isset($coreupdate->version)) {
 | 
        
           |  |  | 747 |                     $text .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version);
 | 
        
           |  |  | 748 |                     $html .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version);
 | 
        
           |  |  | 749 |                 }
 | 
        
           |  |  | 750 |                 if (isset($coreupdate->maturity)) {
 | 
        
           |  |  | 751 |                     $text .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')';
 | 
        
           |  |  | 752 |                     $html .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')';
 | 
        
           |  |  | 753 |                 }
 | 
        
           |  |  | 754 |                 $text .= PHP_EOL;
 | 
        
           |  |  | 755 |                 $html .= html_writer::end_tag('li') . PHP_EOL;
 | 
        
           |  |  | 756 |             }
 | 
        
           |  |  | 757 |             $text .= PHP_EOL;
 | 
        
           |  |  | 758 |             $html .= html_writer::end_tag('ul') . PHP_EOL;
 | 
        
           |  |  | 759 |   | 
        
           |  |  | 760 |             $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/index.php');
 | 
        
           |  |  | 761 |             $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL;
 | 
        
           |  |  | 762 |             $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/index.php', $CFG->wwwroot.'/'.$CFG->admin.'/index.php'));
 | 
        
           |  |  | 763 |             $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL;
 | 
        
           |  |  | 764 |   | 
        
           |  |  | 765 |             $text .= PHP_EOL . get_string('updateavailablerecommendation', 'core_admin') . PHP_EOL;
 | 
        
           |  |  | 766 |             $html .= html_writer::tag('p', get_string('updateavailablerecommendation', 'core_admin')) . PHP_EOL;
 | 
        
           |  |  | 767 |         }
 | 
        
           |  |  | 768 |   | 
        
           |  |  | 769 |         if (!empty($pluginupdates)) {
 | 
        
           |  |  | 770 |             $text .= PHP_EOL . get_string('updateavailableforplugin', 'core_admin') . PHP_EOL;
 | 
        
           |  |  | 771 |             $html .= html_writer::tag('h2', get_string('updateavailableforplugin', 'core_admin')) . PHP_EOL;
 | 
        
           |  |  | 772 |   | 
        
           |  |  | 773 |             $html .= html_writer::start_tag('ul') . PHP_EOL;
 | 
        
           |  |  | 774 |             foreach ($pluginupdates as $pluginupdate) {
 | 
        
           |  |  | 775 |                 $html .= html_writer::start_tag('li');
 | 
        
           |  |  | 776 |                 $text .= get_string('pluginname', $pluginupdate->component);
 | 
        
           |  |  | 777 |                 $html .= html_writer::tag('strong', get_string('pluginname', $pluginupdate->component));
 | 
        
           |  |  | 778 |   | 
        
           |  |  | 779 |                 $text .= ' ('.$pluginupdate->component.')';
 | 
        
           |  |  | 780 |                 $html .= ' ('.$pluginupdate->component.')';
 | 
        
           |  |  | 781 |   | 
        
           |  |  | 782 |                 $text .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version);
 | 
        
           |  |  | 783 |                 $html .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version);
 | 
        
           |  |  | 784 |   | 
        
           |  |  | 785 |                 $text .= PHP_EOL;
 | 
        
           |  |  | 786 |                 $html .= html_writer::end_tag('li') . PHP_EOL;
 | 
        
           |  |  | 787 |             }
 | 
        
           |  |  | 788 |             $text .= PHP_EOL;
 | 
        
           |  |  | 789 |             $html .= html_writer::end_tag('ul') . PHP_EOL;
 | 
        
           |  |  | 790 |   | 
        
           |  |  | 791 |             $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php');
 | 
        
           |  |  | 792 |             $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL;
 | 
        
           |  |  | 793 |             $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/plugins.php', $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php'));
 | 
        
           |  |  | 794 |             $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL;
 | 
        
           |  |  | 795 |         }
 | 
        
           |  |  | 796 |   | 
        
           |  |  | 797 |         $a = array('siteurl' => $CFG->wwwroot);
 | 
        
           |  |  | 798 |         $text .= PHP_EOL . get_string('updatenotificationfooter', 'core_admin', $a) . PHP_EOL;
 | 
        
           |  |  | 799 |         $a = array('siteurl' => html_writer::link($CFG->wwwroot, $CFG->wwwroot));
 | 
        
           |  |  | 800 |         $html .= html_writer::tag('footer', html_writer::tag('p', get_string('updatenotificationfooter', 'core_admin', $a),
 | 
        
           |  |  | 801 |             array('style' => 'font-size:smaller; color:#333;')));
 | 
        
           |  |  | 802 |   | 
        
           |  |  | 803 |         foreach ($admins as $admin) {
 | 
        
           |  |  | 804 |             $message = new \core\message\message();
 | 
        
           |  |  | 805 |             $message->courseid          = SITEID;
 | 
        
           |  |  | 806 |             $message->component         = 'moodle';
 | 
        
           |  |  | 807 |             $message->name              = 'availableupdate';
 | 
        
           |  |  | 808 |             $message->userfrom          = get_admin();
 | 
        
           |  |  | 809 |             $message->userto            = $admin;
 | 
        
           |  |  | 810 |             $message->subject           = get_string('updatenotificationsubject', 'core_admin', array('siteurl' => $CFG->wwwroot));
 | 
        
           |  |  | 811 |             $message->fullmessage       = $text;
 | 
        
           |  |  | 812 |             $message->fullmessageformat = FORMAT_PLAIN;
 | 
        
           |  |  | 813 |             $message->fullmessagehtml   = $html;
 | 
        
           |  |  | 814 |             $message->smallmessage      = get_string('updatenotifications', 'core_admin');
 | 
        
           |  |  | 815 |             $message->notification      = 1;
 | 
        
           |  |  | 816 |             message_send($message);
 | 
        
           |  |  | 817 |         }
 | 
        
           |  |  | 818 |     }
 | 
        
           |  |  | 819 |   | 
        
           |  |  | 820 |     /**
 | 
        
           |  |  | 821 |      * Compare two release labels and decide if they are the same
 | 
        
           |  |  | 822 |      *
 | 
        
           |  |  | 823 |      * @param string $remote release info of the available update
 | 
        
           |  |  | 824 |      * @param null|string $local release info of the local code, defaults to $release defined in version.php
 | 
        
           |  |  | 825 |      * @return boolean true if the releases declare the same minor+major version
 | 
        
           |  |  | 826 |      */
 | 
        
           |  |  | 827 |     protected function is_same_release($remote, $local=null) {
 | 
        
           |  |  | 828 |   | 
        
           |  |  | 829 |         if (is_null($local)) {
 | 
        
           |  |  | 830 |             $this->load_current_environment();
 | 
        
           |  |  | 831 |             $local = $this->currentrelease;
 | 
        
           |  |  | 832 |         }
 | 
        
           |  |  | 833 |   | 
        
           |  |  | 834 |         $pattern = '/^([0-9\.\+]+)([^(]*)/';
 | 
        
           |  |  | 835 |   | 
        
           |  |  | 836 |         preg_match($pattern, $remote, $remotematches);
 | 
        
           |  |  | 837 |         preg_match($pattern, $local, $localmatches);
 | 
        
           |  |  | 838 |   | 
        
           |  |  | 839 |         $remotematches[1] = str_replace('+', '', $remotematches[1]);
 | 
        
           |  |  | 840 |         $localmatches[1] = str_replace('+', '', $localmatches[1]);
 | 
        
           |  |  | 841 |   | 
        
           |  |  | 842 |         if ($remotematches[1] === $localmatches[1] and rtrim($remotematches[2]) === rtrim($localmatches[2])) {
 | 
        
           |  |  | 843 |             return true;
 | 
        
           |  |  | 844 |         } else {
 | 
        
           |  |  | 845 |             return false;
 | 
        
           |  |  | 846 |         }
 | 
        
           |  |  | 847 |     }
 | 
        
           |  |  | 848 | }
 |