[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/classes/update/ -> checker.php (source)

   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  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1