Merge pull request #7886 from sarehag/CRM-18114
[civicrm-core.git] / CRM / Utils / VersionCheck.php
index c04e6310a966d2de6e2a921b48e7a940bb16a2ab..9b51ed6e948e0af234ddbf82a781ce3c91c97d06 100644 (file)
  *
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2015
- * $Id: $
- *
  */
 class CRM_Utils_VersionCheck {
   const
-    PINGBACK_URL = 'http://latest.civicrm.org/stable.php?format=json',
-    // timeout for when the connection or the server is slow
-    CHECK_TIMEOUT = 5,
-    // relative to $civicrm_root
-    LOCALFILE_NAME = 'civicrm-version.php',
-    // relative to $config->uploadDir
     CACHEFILE_NAME = 'version-info-cache.json',
-    // cachefile expiry time (in seconds) - one day
-    CACHEFILE_EXPIRE = 86400;
-
-  /**
-   * We only need one instance of this object, so we use the
-   * singleton pattern and cache the instance in this variable
-   *
-   * @var object
-   */
-  static private $_singleton = NULL;
+    // after this length of time we fall back on poor-man's cron (7+ days)
+    CACHEFILE_EXPIRE = 605000;
 
   /**
    * The version of the current (local) installation
@@ -67,18 +51,26 @@ class CRM_Utils_VersionCheck {
   public $localMajorVersion;
 
   /**
-   * User setting to skip updates prior to a certain date
+   * Info about available versions
    *
-   * @var string
+   * @var array
    */
-  public $ignoreDate;
+  public $versionInfo = array();
+
+  /**
+   * @var bool
+   */
+  public $isInfoAvailable;
 
   /**
-   * Info about available versions
-   *
    * @var array
    */
-  public $versionInfo = array();
+  public $cronJob = array();
+
+  /**
+   * @var string
+   */
+  public $pingbackUrl = 'http://latest.civicrm.org/stable.php?format=json';
 
   /**
    * Pingback params
@@ -92,56 +84,52 @@ class CRM_Utils_VersionCheck {
    *
    * @var string
    */
-  protected $cacheFile;
+  public $cacheFile;
 
   /**
    * Class constructor.
    */
   public function __construct() {
-    global $civicrm_root;
-    $config = CRM_Core_Config::singleton();
+    $this->localVersion = CRM_Utils_System::version();
+    $this->localMajorVersion = $this->getMajorVersion($this->localVersion);
+    $this->cacheFile = CRM_Core_Config::singleton()->uploadDir . self::CACHEFILE_NAME;
+  }
 
-    $localFile = $civicrm_root . DIRECTORY_SEPARATOR . self::LOCALFILE_NAME;
-    $this->cacheFile = $config->uploadDir . self::CACHEFILE_NAME;
+  /**
+   * Self-populates version info
+   *
+   * @throws \Exception
+   */
+  public function initialize() {
+    $this->getJob();
 
-    if (file_exists($localFile)) {
-      require_once $localFile;
-    }
-    if (function_exists('civicrmVersion')) {
-      $info = civicrmVersion();
-      $this->localVersion = trim($info['version']);
-      $this->localMajorVersion = $this->getMajorVersion($this->localVersion);
-    }
-    // Populate $versionInfo
-    if (CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'versionCheck', NULL, 1)) {
-      // Use cached data if available and not stale
-      if (!$this->readCacheFile()) {
-        // Collect stats for pingback
-        $this->getSiteStats();
-
-        // Get the latest version and send site info
-        $this->pingBack();
-      }
-      $this->ignoreDate = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'versionCheckIgnoreDate');
+    // Populate remote $versionInfo from cache file
+    $this->isInfoAvailable = $this->readCacheFile();
 
-      // Sort version info in ascending order for easier comparisons
-      ksort($this->versionInfo, SORT_NUMERIC);
+    // Poor-man's cron fallback if scheduled job is enabled but has failed to run
+    $expiryTime = time() - self::CACHEFILE_EXPIRE;
+    if (!empty($this->cronJob['is_active']) &&
+      (!$this->isInfoAvailable || filemtime($this->cacheFile) < $expiryTime)
+    ) {
+      // First try updating the files modification time, for 2 reasons:
+      //  - if the file is not writeable, this saves the trouble of pinging back
+      //  - if the remote server is down, this will prevent an immediate retry
+      if (touch($this->cacheFile) === FALSE) {
+        throw new Exception('File not writable');
+      }
+      $this->fetch();
     }
   }
 
   /**
-   * Static instance provider.
-   *
-   * Method providing static instance of CRM_Utils_VersionCheck,
-   * as in Singleton pattern
+   * Sets $versionInfo
    *
-   * @return CRM_Utils_VersionCheck
+   * @param $info
    */
-  public static function &singleton() {
-    if (!isset(self::$_singleton)) {
-      self::$_singleton = new CRM_Utils_VersionCheck();
-    }
-    return self::$_singleton;
+  public function setVersionInfo($info) {
+    $this->versionInfo = (array) $info;
+    // Sort version info in ascending order for easier comparisons
+    ksort($this->versionInfo, SORT_NUMERIC);
   }
 
   /**
@@ -247,6 +235,14 @@ class CRM_Utils_VersionCheck {
     return $return;
   }
 
+  /**
+   * Called by version_check cron job
+   */
+  public function fetch() {
+    $this->getSiteStats();
+    $this->pingBack();
+  }
+
   /**
    * @param $majorVersion
    * @return null|string
@@ -259,11 +255,9 @@ class CRM_Utils_VersionCheck {
     if (!empty($majorVersion['releases'])) {
       foreach ($majorVersion['releases'] as $release) {
         if (version_compare($this->localVersion, $release['version']) < 0) {
-          if (!$this->ignoreDate || $this->ignoreDate < $release['date']) {
-            $newerVersion['newest'] = $release['version'];
-            if (CRM_Utils_Array::value('security', $release)) {
-              $newerVersion['security'] = $release['version'];
-            }
+          $newerVersion['newest'] = $release['version'];
+          if (CRM_Utils_Array::value('security', $release)) {
+            $newerVersion['security'] = $release['version'];
           }
         }
       }
@@ -389,11 +383,9 @@ class CRM_Utils_VersionCheck {
 
   /**
    * Send the request to civicrm.org
-   * Set timeout and suppress errors
    * Store results in the cache file
    */
   private function pingBack() {
-    ini_set('default_socket_timeout', self::CHECK_TIMEOUT);
     $params = array(
       'http' => array(
         'method' => 'POST',
@@ -402,27 +394,24 @@ class CRM_Utils_VersionCheck {
       ),
     );
     $ctx = stream_context_create($params);
-    $rawJson = @file_get_contents(self::PINGBACK_URL, FALSE, $ctx);
+    $rawJson = file_get_contents($this->pingbackUrl, FALSE, $ctx);
     $versionInfo = $rawJson ? json_decode($rawJson, TRUE) : NULL;
     // If we couldn't fetch or parse the data $versionInfo will be NULL
     // Otherwise it will be an array and we'll cache it.
     // Note the array may be empty e.g. in the case of a pre-alpha with no releases
-    if ($versionInfo !== NULL) {
+    $this->isInfoAvailable = $versionInfo !== NULL;
+    if ($this->isInfoAvailable) {
       $this->writeCacheFile($rawJson);
-      $this->versionInfo = $versionInfo;
+      $this->setVersionInfo($versionInfo);
     }
-    ini_restore('default_socket_timeout');
   }
 
   /**
    * @return bool
    */
   private function readCacheFile() {
-    $expiryTime = time() - self::CACHEFILE_EXPIRE;
-
-    // if there's a cachefile and it's not stale, use it
-    if (file_exists($this->cacheFile) && (filemtime($this->cacheFile) > $expiryTime)) {
-      $this->versionInfo = (array) json_decode(file_get_contents($this->cacheFile), TRUE);
+    if (file_exists($this->cacheFile)) {
+      $this->setVersionInfo(json_decode(file_get_contents($this->cacheFile), TRUE));
       return TRUE;
     }
     return FALSE;
@@ -431,19 +420,24 @@ class CRM_Utils_VersionCheck {
   /**
    * Save version info to file.
    * @param string $contents
+   * @throws \Exception
    */
   private function writeCacheFile($contents) {
-    $fp = @fopen($this->cacheFile, 'w');
-    if (!$fp) {
-      if (CRM_Core_Permission::check('administer CiviCRM')) {
-        CRM_Core_Session::setStatus(
-          ts('Unable to write file') . ": $this->cacheFile<br />" . ts('Please check your system file permissions.'),
-          ts('File Error'), 'error');
-      }
-      return;
+    if (file_put_contents($this->cacheFile, $contents) === FALSE) {
+      throw new Exception('File not writable');
     }
-    fwrite($fp, $contents);
-    fclose($fp);
+  }
+
+  /**
+   * Lookup version_check scheduled job
+   */
+  private function getJob() {
+    $jobs = civicrm_api3('Job', 'get', array(
+      'sequential' => 1,
+      'api_action' => "version_check",
+      'api_entity' => "job",
+    ));
+    $this->cronJob = CRM_Utils_Array::value(0, $jobs['values'], array());
   }
 
 }