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