3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2015
35 class CRM_Utils_VersionCheck
{
37 PINGBACK_URL
= 'http://latest.civicrm.org/stable.php?format=json',
38 // timeout for when the connection or the server is slow
40 // relative to $civicrm_root
41 LOCALFILE_NAME
= 'civicrm-version.php',
42 // relative to $config->uploadDir
43 CACHEFILE_NAME
= 'version-info-cache.json',
44 // cachefile expiry time (in seconds) - one day
45 CACHEFILE_EXPIRE
= 86400;
48 * We only need one instance of this object, so we use the
49 * singleton pattern and cache the instance in this variable
53 static private $_singleton = NULL;
56 * The version of the current (local) installation
60 public $localVersion = NULL;
63 * The major version (branch name) of the local version
67 public $localMajorVersion;
70 * User setting to skip updates prior to a certain date
77 * Info about available versions
81 public $versionInfo = array();
88 protected $stats = array();
100 public function __construct() {
101 global $civicrm_root;
102 $config = CRM_Core_Config
::singleton();
104 $localFile = $civicrm_root . DIRECTORY_SEPARATOR
. self
::LOCALFILE_NAME
;
105 $this->cacheFile
= $config->uploadDir
. self
::CACHEFILE_NAME
;
107 if (file_exists($localFile)) {
108 require_once $localFile;
110 if (function_exists('civicrmVersion')) {
111 $info = civicrmVersion();
112 $this->localVersion
= trim($info['version']);
113 $this->localMajorVersion
= $this->getMajorVersion($this->localVersion
);
115 // Populate $versionInfo
116 if (CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'versionCheck', NULL, 1)) {
117 // Use cached data if available and not stale
118 if (!$this->readCacheFile()) {
119 // Collect stats for pingback
120 $this->getSiteStats();
122 // Get the latest version and send site info
125 $this->ignoreDate
= CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'versionCheckIgnoreDate');
127 // Sort version info in ascending order for easier comparisons
128 ksort($this->versionInfo
, SORT_NUMERIC
);
133 * Static instance provider.
135 * Method providing static instance of CRM_Utils_VersionCheck,
136 * as in Singleton pattern
138 * @return CRM_Utils_VersionCheck
140 public static function &singleton() {
141 if (!isset(self
::$_singleton)) {
142 self
::$_singleton = new CRM_Utils_VersionCheck();
144 return self
::$_singleton;
148 * Finds the release info for a minor version.
149 * @param string $version
152 public function getReleaseInfo($version) {
153 $majorVersion = $this->getMajorVersion($version);
154 if (isset($this->versionInfo
[$majorVersion])) {
155 foreach ($this->versionInfo
[$majorVersion]['releases'] as $info) {
156 if ($info['version'] == $version) {
165 * @param $minorVersion
168 public function getMajorVersion($minorVersion) {
169 if (!$minorVersion) {
172 list($a, $b) = explode('.', $minorVersion);
179 public function isSecurityUpdateAvailable() {
180 $thisVersion = $this->getReleaseInfo($this->localVersion
);
181 $localVersionDate = CRM_Utils_Array
::value('date', $thisVersion, 0);
183 foreach ($this->versionInfo
as $majorVersionNumber => $majorVersion) {
184 foreach ($majorVersion['releases'] as $release) {
185 if (!empty($release['security']) && $release['date'] > $localVersionDate
186 && version_compare($this->localVersion
, $release['version']) < 0
188 if ((!$this->ignoreDate ||
$this->ignoreDate
< $release['date'])
189 && (!$this->isThisReleaseTheLTS() ||
$majorVersionNumber === $this->localMajorVersion
)
199 * Is this the LTS release.
201 * This function is only really being used in 4.6 & is a bit heavy on the enotice
202 * handling for test reasons
204 public function isThisReleaseTheLTS() {
205 if (empty($this->versionInfo
)) {
208 if (empty($this->versionInfo
[$this->localMajorVersion
])) {
211 if ($this->versionInfo
[$this->localMajorVersion
]['status'] === 'lts') {
218 * Get the latest version number if it's newer than the local one
220 * @return string|null
221 * Returns version number of the latest release if it is greater than the local version
223 public function isNewerVersionAvailable() {
224 $newerVersion = NULL;
225 if ($this->versionInfo
&& $this->localVersion
) {
226 foreach ($this->versionInfo
as $majorVersionNumber => $majorVersion) {
227 $release = $this->checkBranchForNewVersion($majorVersion);
229 // If we have a release with the same majorVersion as local, return it
230 if ($majorVersionNumber == $this->localMajorVersion
) {
233 // Search outside the local majorVersion (excluding non-stable)
234 elseif ($majorVersion['status'] != 'testing') {
235 // We found a new release but don't return yet, keep searching newer majorVersions
236 $newerVersion = $release;
241 return $newerVersion;
245 * @param $majorVersion
246 * @return null|string
248 private function checkBranchForNewVersion($majorVersion) {
249 $newerVersion = NULL;
250 if (!empty($majorVersion['releases'])) {
251 foreach ($majorVersion['releases'] as $release) {
252 if (version_compare($this->localVersion
, $release['version']) < 0) {
253 if (!$this->ignoreDate ||
$this->ignoreDate
< $release['date']) {
254 $newerVersion = $release['version'];
259 return $newerVersion;
263 * Alert the site admin of new versions of CiviCRM.
264 * Show the message once a day
266 public function versionAlert() {
267 $versionAlertSetting = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'versionAlert', NULL, 1);
268 $securityAlertSetting = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'securityUpdateAlert', NULL, 3);
269 $settingsUrl = CRM_Utils_System
::url('civicrm/admin/setting/misc', 'reset=1', FALSE, NULL, FALSE, FALSE, TRUE);
270 if (CRM_Core_Permission
::check('administer CiviCRM') && $securityAlertSetting > 1 && $this->isSecurityUpdateAvailable()) {
271 $session = CRM_Core_Session
::singleton();
272 if ($session->timer('version_alert', 24 * 60 * 60)) {
273 $msg = ts('This version of CiviCRM requires a security update.') .
275 <li><a href="https://civicrm.org/advisory">' . ts('Read advisory') . '</a></li>
276 <li><a href="https://civicrm.org/download">' . ts('Download now') . '</a></li>
277 <li><a class="crm-setVersionCheckIgnoreDate" href="' . $settingsUrl . '">' . ts('Suppress this message') . '</a></li>
279 $session->setStatus($msg, ts('Security Alert'), 'alert');
280 CRM_Core_Resources
::singleton()
281 ->addScriptFile('civicrm', 'templates/CRM/Admin/Form/Setting/versionCheckOptions.js');
284 elseif (CRM_Core_Permission
::check('administer CiviCRM') && $versionAlertSetting > 1) {
285 $newerVersion = $this->isNewerVersionAvailable();
287 $session = CRM_Core_Session
::singleton();
288 if ($session->timer('version_alert', 24 * 60 * 60)) {
289 $msg = ts('A newer version of CiviCRM is available: %1', array(1 => $newerVersion)) .
291 <li><a href="https://civicrm.org/download">' . ts('Download now') . '</a></li>
292 <li><a class="crm-setVersionCheckIgnoreDate" href="' . $settingsUrl . '">' . ts('Suppress this message') . '</a></li>
294 $session->setStatus($msg, ts('Update Available'), 'info');
295 CRM_Core_Resources
::singleton()
296 ->addScriptFile('civicrm', 'templates/CRM/Admin/Form/Setting/versionCheckOptions.js');
303 * Collect info about the site to be sent as pingback data.
305 private function getSiteStats() {
306 $config = CRM_Core_Config
::singleton();
307 $siteKey = md5(defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY
: '');
309 // Calorie-free pingback for alphas
310 $this->stats
= array('version' => $this->localVersion
);
312 // Non-alpha versions get the full treatment
313 if ($this->localVersion
&& !strpos($this->localVersion
, 'alpha')) {
314 $this->stats +
= array(
315 'hash' => md5($siteKey . $config->userFrameworkBaseURL
),
316 'uf' => $config->userFramework
,
317 'lang' => $config->lcMessages
,
318 'co' => $config->defaultContactCountry
,
319 'ufv' => $config->userFrameworkVersion
,
320 'PHP' => phpversion(),
321 'MySQL' => CRM_CORE_DAO
::singleValueQuery('SELECT VERSION()'),
322 'communityMessagesUrl' => CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'communityMessagesUrl', NULL, '*default*'),
324 $this->getPayProcStats();
325 $this->getEntityStats();
326 $this->getExtensionStats();
331 * Get active payment processor types.
333 private function getPayProcStats() {
334 $dao = new CRM_Financial_DAO_PaymentProcessor();
339 // Get title and id for all processor types
340 $ppTypeNames = CRM_Core_PseudoConstant
::paymentProcessorType();
342 while ($dao->fetch()) {
343 $ppTypes[] = $ppTypeNames[$dao->payment_processor_type_id
];
345 // add the .-separated list of the processor types
346 $this->stats
['PPTypes'] = implode(',', array_unique($ppTypes));
350 * Fetch counts from entity tables.
351 * Add info to the 'entities' array
353 private function getEntityStats() {
355 'CRM_Activity_DAO_Activity' => 'is_test = 0',
356 'CRM_Case_DAO_Case' => 'is_deleted = 0',
357 'CRM_Contact_DAO_Contact' => 'is_deleted = 0',
358 'CRM_Contact_DAO_Relationship' => NULL,
359 'CRM_Campaign_DAO_Campaign' => NULL,
360 'CRM_Contribute_DAO_Contribution' => 'is_test = 0',
361 'CRM_Contribute_DAO_ContributionPage' => 'is_active = 1',
362 'CRM_Contribute_DAO_ContributionProduct' => NULL,
363 'CRM_Contribute_DAO_Widget' => 'is_active = 1',
364 'CRM_Core_DAO_Discount' => NULL,
365 'CRM_Price_DAO_PriceSetEntity' => NULL,
366 'CRM_Core_DAO_UFGroup' => 'is_active = 1',
367 'CRM_Event_DAO_Event' => 'is_active = 1',
368 'CRM_Event_DAO_Participant' => 'is_test = 0',
369 'CRM_Friend_DAO_Friend' => 'is_active = 1',
370 'CRM_Grant_DAO_Grant' => NULL,
371 'CRM_Mailing_DAO_Mailing' => 'is_completed = 1',
372 'CRM_Member_DAO_Membership' => 'is_test = 0',
373 'CRM_Member_DAO_MembershipBlock' => 'is_active = 1',
374 'CRM_Pledge_DAO_Pledge' => 'is_test = 0',
375 'CRM_Pledge_DAO_PledgeBlock' => NULL,
377 foreach ($tables as $daoName => $where) {
378 $dao = new $daoName();
380 $dao->whereAdd($where);
382 $short_name = substr($daoName, strrpos($daoName, '_') +
1);
383 $this->stats
['entities'][] = array(
384 'name' => $short_name,
385 'size' => $dao->count(),
391 * Fetch stats about enabled components/extensions
392 * Add info to the 'extensions' array
394 private function getExtensionStats() {
396 $config = CRM_Core_Config
::singleton();
397 foreach ($config->enableComponents
as $comp) {
398 $this->stats
['extensions'][] = array(
399 'name' => 'org.civicrm.component.' . strtolower($comp),
401 'version' => $this->stats
['version'],
404 // Contrib extensions
405 $mapper = CRM_Extension_System
::singleton()->getMapper();
406 $dao = new CRM_Core_DAO_Extension();
408 while ($dao->fetch()) {
409 $info = $mapper->keyToInfo($dao->full_name
);
410 $this->stats
['extensions'][] = array(
411 'name' => $dao->full_name
,
412 'enabled' => $dao->is_active
,
413 'version' => isset($info->version
) ?
$info->version
: NULL,
419 * Send the request to civicrm.org
420 * Set timeout and suppress errors
421 * Store results in the cache file
423 private function pingBack() {
424 ini_set('default_socket_timeout', self
::CHECK_TIMEOUT
);
428 'header' => 'Content-type: application/x-www-form-urlencoded',
429 'content' => http_build_query($this->stats
),
432 $ctx = stream_context_create($params);
433 $rawJson = @file_get_contents
(self
::PINGBACK_URL
, FALSE, $ctx);
434 $versionInfo = $rawJson ?
json_decode($rawJson, TRUE) : NULL;
435 // If we couldn't fetch or parse the data $versionInfo will be NULL
436 // Otherwise it will be an array and we'll cache it.
437 // Note the array may be empty e.g. in the case of a pre-alpha with no releases
438 if ($versionInfo !== NULL) {
439 $this->writeCacheFile($rawJson);
440 $this->versionInfo
= $versionInfo;
442 ini_restore('default_socket_timeout');
448 private function readCacheFile() {
449 $expiryTime = time() - self
::CACHEFILE_EXPIRE
;
451 // if there's a cachefile and it's not stale, use it
452 if (file_exists($this->cacheFile
) && (filemtime($this->cacheFile
) > $expiryTime)) {
453 $this->versionInfo
= (array) json_decode(file_get_contents($this->cacheFile
), TRUE);
460 * Save version info to file.
461 * @param string $contents
463 private function writeCacheFile($contents) {
464 $fp = @fopen
($this->cacheFile
, 'w');
466 if (CRM_Core_Permission
::check('administer CiviCRM')) {
467 CRM_Core_Session
::setStatus(
468 ts('Unable to write file') . ": $this->cacheFile<br />" . ts('Please check your system file permissions.'),
469 ts('File Error'), 'error');
473 fwrite($fp, $contents);