3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
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
33 class CRM_Utils_VersionCheck
{
35 PINGBACK_URL
= 'http://latest.civicrm.org/stable.php?format=json',
36 // timeout for when the connection or the server is slow
38 // relative to $civicrm_root
39 LOCALFILE_NAME
= 'civicrm-version.php',
40 // relative to $config->uploadDir
41 CACHEFILE_NAME
= 'version-info-cache.json',
42 // cachefile expiry time (in seconds) - one day
43 CACHEFILE_EXPIRE
= 86400;
46 * We only need one instance of this object, so we use the
47 * singleton pattern and cache the instance in this variable
51 static private $_singleton = NULL;
54 * The version of the current (local) installation
58 public $localVersion = NULL;
61 * The major version (branch name) of the local version
65 public $localMajorVersion;
68 * Info about available versions
72 public $versionInfo = array();
79 protected $stats = array();
91 public function __construct() {
93 $config = CRM_Core_Config
::singleton();
95 $localFile = $civicrm_root . DIRECTORY_SEPARATOR
. self
::LOCALFILE_NAME
;
96 $this->cacheFile
= $config->uploadDir
. self
::CACHEFILE_NAME
;
98 if (file_exists($localFile)) {
99 require_once $localFile;
101 if (function_exists('civicrmVersion')) {
102 $info = civicrmVersion();
103 $this->localVersion
= trim($info['version']);
104 $this->localMajorVersion
= $this->getMajorVersion($this->localVersion
);
106 // Populate $versionInfo
107 if (Civi
::settings()->get('versionCheck')) {
108 // Use cached data if available and not stale
109 if (!$this->readCacheFile()) {
110 // Collect stats for pingback
111 $this->getSiteStats();
113 // Get the latest version and send site info
117 // Sort version info in ascending order for easier comparisons
118 ksort($this->versionInfo
, SORT_NUMERIC
);
123 * Static instance provider.
125 * Method providing static instance of CRM_Utils_VersionCheck,
126 * as in Singleton pattern
128 * @return CRM_Utils_VersionCheck
130 public static function &singleton() {
131 if (!isset(self
::$_singleton)) {
132 self
::$_singleton = new CRM_Utils_VersionCheck();
134 return self
::$_singleton;
138 * Finds the release info for a minor version.
139 * @param string $version
142 public function getReleaseInfo($version) {
143 $majorVersion = $this->getMajorVersion($version);
144 if (isset($this->versionInfo
[$majorVersion])) {
145 foreach ($this->versionInfo
[$majorVersion]['releases'] as $info) {
146 if ($info['version'] == $version) {
155 * @param $minorVersion
158 public function getMajorVersion($minorVersion) {
159 if (!$minorVersion) {
162 list($a, $b) = explode('.', $minorVersion);
168 * Get the latest version number if it's newer than the local one
171 * Returns version number of the latest release if it is greater than the local version,
172 * along with the type of upgrade (regular/security) needed and the status of the major
175 public function isNewerVersionAvailable() {
182 if ($this->versionInfo
&& $this->localVersion
) {
183 if (isset($this->versionInfo
[$this->localMajorVersion
])) {
184 switch (CRM_Utils_Array
::value('status', $this->versionInfo
[$this->localMajorVersion
])) {
188 // look for latest version in this major version
189 $releases = $this->checkBranchForNewVersion($this->versionInfo
[$this->localMajorVersion
]);
190 if ($releases['newest']) {
191 $return['version'] = $releases['newest'];
193 // check for intervening security releases
194 $return['upgrade'] = ($releases['security']) ?
'security' : 'regular';
200 // look for latest version ever
201 foreach ($this->versionInfo
as $majorVersionNumber => $majorVersion) {
202 if ($majorVersionNumber < $this->localMajorVersion ||
$majorVersion['status'] == 'testing') {
205 $releases = $this->checkBranchForNewVersion($this->versionInfo
[$majorVersionNumber]);
207 if ($releases['newest']) {
208 $return['version'] = $releases['newest'];
210 // check for intervening security releases
211 $return['upgrade'] = ($releases['security'] ||
$return['upgrade'] == 'security') ?
'security' : 'regular';
215 $return['status'] = $this->versionInfo
[$this->localMajorVersion
]['status'];
218 // Figure if the version is really old or really new
221 foreach ($this->versionInfo
as $majorVersionNumber => $majorVersion) {
222 $wayOld = ($this->localMajorVersion
< $majorVersionNumber);
226 $releases = $this->checkBranchForNewVersion($majorVersion);
229 'version' => $releases['newest'],
230 'upgrade' => 'security',
241 * @param $majorVersion
242 * @return null|string
244 private function checkBranchForNewVersion($majorVersion) {
245 $newerVersion = array(
249 if (!empty($majorVersion['releases'])) {
250 foreach ($majorVersion['releases'] as $release) {
251 if (version_compare($this->localVersion
, $release['version']) < 0) {
252 $newerVersion['newest'] = $release['version'];
253 if (CRM_Utils_Array
::value('security', $release)) {
254 $newerVersion['security'] = $release['version'];
259 return $newerVersion;
263 * Collect info about the site to be sent as pingback data.
265 private function getSiteStats() {
266 $config = CRM_Core_Config
::singleton();
267 $siteKey = md5(defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY
: '');
269 // Calorie-free pingback for alphas
270 $this->stats
= array('version' => $this->localVersion
);
272 // Non-alpha versions get the full treatment
273 if ($this->localVersion
&& !strpos($this->localVersion
, 'alpha')) {
274 $this->stats +
= array(
275 'hash' => md5($siteKey . $config->userFrameworkBaseURL
),
276 'uf' => $config->userFramework
,
277 'lang' => $config->lcMessages
,
278 'co' => $config->defaultContactCountry
,
279 'ufv' => $config->userSystem
->getVersion(),
280 'PHP' => phpversion(),
281 'MySQL' => CRM_CORE_DAO
::singleValueQuery('SELECT VERSION()'),
282 'communityMessagesUrl' => Civi
::settings()->get('communityMessagesUrl'),
284 $this->getPayProcStats();
285 $this->getEntityStats();
286 $this->getExtensionStats();
291 * Get active payment processor types.
293 private function getPayProcStats() {
294 $dao = new CRM_Financial_DAO_PaymentProcessor();
299 // Get title and id for all processor types
300 $ppTypeNames = CRM_Core_PseudoConstant
::paymentProcessorType();
302 while ($dao->fetch()) {
303 $ppTypes[] = $ppTypeNames[$dao->payment_processor_type_id
];
305 // add the .-separated list of the processor types
306 $this->stats
['PPTypes'] = implode(',', array_unique($ppTypes));
310 * Fetch counts from entity tables.
311 * Add info to the 'entities' array
313 private function getEntityStats() {
315 'CRM_Activity_DAO_Activity' => 'is_test = 0',
316 'CRM_Case_DAO_Case' => 'is_deleted = 0',
317 'CRM_Contact_DAO_Contact' => 'is_deleted = 0',
318 'CRM_Contact_DAO_Relationship' => NULL,
319 'CRM_Campaign_DAO_Campaign' => NULL,
320 'CRM_Contribute_DAO_Contribution' => 'is_test = 0',
321 'CRM_Contribute_DAO_ContributionPage' => 'is_active = 1',
322 'CRM_Contribute_DAO_ContributionProduct' => NULL,
323 'CRM_Contribute_DAO_Widget' => 'is_active = 1',
324 'CRM_Core_DAO_Discount' => NULL,
325 'CRM_Price_DAO_PriceSetEntity' => NULL,
326 'CRM_Core_DAO_UFGroup' => 'is_active = 1',
327 'CRM_Event_DAO_Event' => 'is_active = 1',
328 'CRM_Event_DAO_Participant' => 'is_test = 0',
329 'CRM_Friend_DAO_Friend' => 'is_active = 1',
330 'CRM_Grant_DAO_Grant' => NULL,
331 'CRM_Mailing_DAO_Mailing' => 'is_completed = 1',
332 'CRM_Member_DAO_Membership' => 'is_test = 0',
333 'CRM_Member_DAO_MembershipBlock' => 'is_active = 1',
334 'CRM_Pledge_DAO_Pledge' => 'is_test = 0',
335 'CRM_Pledge_DAO_PledgeBlock' => NULL,
337 foreach ($tables as $daoName => $where) {
338 $dao = new $daoName();
340 $dao->whereAdd($where);
342 $short_name = substr($daoName, strrpos($daoName, '_') +
1);
343 $this->stats
['entities'][] = array(
344 'name' => $short_name,
345 'size' => $dao->count(),
351 * Fetch stats about enabled components/extensions
352 * Add info to the 'extensions' array
354 private function getExtensionStats() {
356 $config = CRM_Core_Config
::singleton();
357 foreach ($config->enableComponents
as $comp) {
358 $this->stats
['extensions'][] = array(
359 'name' => 'org.civicrm.component.' . strtolower($comp),
361 'version' => $this->stats
['version'],
364 // Contrib extensions
365 $mapper = CRM_Extension_System
::singleton()->getMapper();
366 $dao = new CRM_Core_DAO_Extension();
368 while ($dao->fetch()) {
369 $info = $mapper->keyToInfo($dao->full_name
);
370 $this->stats
['extensions'][] = array(
371 'name' => $dao->full_name
,
372 'enabled' => $dao->is_active
,
373 'version' => isset($info->version
) ?
$info->version
: NULL,
379 * Send the request to civicrm.org
380 * Set timeout and suppress errors
381 * Store results in the cache file
383 private function pingBack() {
384 ini_set('default_socket_timeout', self
::CHECK_TIMEOUT
);
388 'header' => 'Content-type: application/x-www-form-urlencoded',
389 'content' => http_build_query($this->stats
),
392 $ctx = stream_context_create($params);
393 $rawJson = @file_get_contents
(self
::PINGBACK_URL
, FALSE, $ctx);
394 $versionInfo = $rawJson ?
json_decode($rawJson, TRUE) : NULL;
395 // If we couldn't fetch or parse the data $versionInfo will be NULL
396 // Otherwise it will be an array and we'll cache it.
397 // Note the array may be empty e.g. in the case of a pre-alpha with no releases
398 if ($versionInfo !== NULL) {
399 $this->writeCacheFile($rawJson);
400 $this->versionInfo
= $versionInfo;
402 ini_restore('default_socket_timeout');
408 private function readCacheFile() {
409 $expiryTime = time() - self
::CACHEFILE_EXPIRE
;
411 // if there's a cachefile and it's not stale, use it
412 if (file_exists($this->cacheFile
) && (filemtime($this->cacheFile
) > $expiryTime)) {
413 $this->versionInfo
= (array) json_decode(file_get_contents($this->cacheFile
), TRUE);
420 * Save version info to file.
421 * @param string $contents
423 private function writeCacheFile($contents) {
424 $fp = @fopen
($this->cacheFile
, 'w');
426 if (CRM_Core_Permission
::check('administer CiviCRM')) {
427 CRM_Core_Session
::setStatus(
428 ts('Unable to write file') . ": $this->cacheFile<br />" . ts('Please check your system file permissions.'),
429 ts('File Error'), 'error');
433 fwrite($fp, $contents);