3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
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-2016
33 class CRM_Utils_VersionCheck
{
35 CACHEFILE_NAME
= 'version-info-cache.json',
36 // after this length of time we fall back on poor-man's cron (7+ days)
37 CACHEFILE_EXPIRE
= 605000;
40 * The version of the current (local) installation
44 public $localVersion = NULL;
47 * The major version (branch name) of the local version
51 public $localMajorVersion;
54 * Info about available versions
58 public $versionInfo = array();
63 public $isInfoAvailable;
68 public $cronJob = array();
73 public $pingbackUrl = 'http://latest.civicrm.org/stable.php?format=json';
80 protected $stats = array();
92 public function __construct() {
93 $this->localVersion
= CRM_Utils_System
::version();
94 $this->localMajorVersion
= $this->getMajorVersion($this->localVersion
);
95 $this->cacheFile
= CRM_Core_Config
::singleton()->uploadDir
. self
::CACHEFILE_NAME
;
99 * Self-populates version info
103 public function initialize() {
106 // Populate remote $versionInfo from cache file
107 $this->isInfoAvailable
= $this->readCacheFile();
109 // Poor-man's cron fallback if scheduled job is enabled but has failed to run
110 $expiryTime = time() - self
::CACHEFILE_EXPIRE
;
111 if (!empty($this->cronJob
['is_active']) &&
112 (!$this->isInfoAvailable ||
filemtime($this->cacheFile
) < $expiryTime)
114 // First try updating the files modification time, for 2 reasons:
115 // - if the file is not writeable, this saves the trouble of pinging back
116 // - if the remote server is down, this will prevent an immediate retry
117 if (touch($this->cacheFile
) === FALSE) {
118 throw new Exception('File not writable');
129 public function setVersionInfo($info) {
130 $this->versionInfo
= (array) $info;
131 // Sort version info in ascending order for easier comparisons
132 ksort($this->versionInfo
, SORT_NUMERIC
);
136 * Finds the release info for a minor version.
137 * @param string $version
140 public function getReleaseInfo($version) {
141 $majorVersion = $this->getMajorVersion($version);
142 if (isset($this->versionInfo
[$majorVersion])) {
143 foreach ($this->versionInfo
[$majorVersion]['releases'] as $info) {
144 if ($info['version'] == $version) {
153 * @param $minorVersion
156 public function getMajorVersion($minorVersion) {
157 if (!$minorVersion) {
160 list($a, $b) = explode('.', $minorVersion);
166 * Get the latest version number if it's newer than the local one
169 * Returns version number of the latest release if it is greater than the local version,
170 * along with the type of upgrade (regular/security) needed and the status of the major
173 public function isNewerVersionAvailable() {
180 if ($this->versionInfo
&& $this->localVersion
) {
181 if (isset($this->versionInfo
[$this->localMajorVersion
])) {
182 switch (CRM_Utils_Array
::value('status', $this->versionInfo
[$this->localMajorVersion
])) {
186 // look for latest version in this major version
187 $releases = $this->checkBranchForNewVersion($this->versionInfo
[$this->localMajorVersion
]);
188 if ($releases['newest']) {
189 $return['version'] = $releases['newest'];
191 // check for intervening security releases
192 $return['upgrade'] = ($releases['security']) ?
'security' : 'regular';
198 // look for latest version ever
199 foreach ($this->versionInfo
as $majorVersionNumber => $majorVersion) {
200 if ($majorVersionNumber < $this->localMajorVersion ||
$majorVersion['status'] == 'testing') {
203 $releases = $this->checkBranchForNewVersion($this->versionInfo
[$majorVersionNumber]);
205 if ($releases['newest']) {
206 $return['version'] = $releases['newest'];
208 // check for intervening security releases
209 $return['upgrade'] = ($releases['security'] ||
$return['upgrade'] == 'security') ?
'security' : 'regular';
213 $return['status'] = $this->versionInfo
[$this->localMajorVersion
]['status'];
216 // Figure if the version is really old or really new
219 foreach ($this->versionInfo
as $majorVersionNumber => $majorVersion) {
220 $wayOld = ($this->localMajorVersion
< $majorVersionNumber);
224 $releases = $this->checkBranchForNewVersion($majorVersion);
227 'version' => $releases['newest'],
228 'upgrade' => 'security',
239 * Called by version_check cron job
241 public function fetch() {
242 $this->getSiteStats();
247 * @param $majorVersion
248 * @return null|string
250 private function checkBranchForNewVersion($majorVersion) {
251 $newerVersion = array(
255 if (!empty($majorVersion['releases'])) {
256 foreach ($majorVersion['releases'] as $release) {
257 if (version_compare($this->localVersion
, $release['version']) < 0) {
258 $newerVersion['newest'] = $release['version'];
259 if (CRM_Utils_Array
::value('security', $release)) {
260 $newerVersion['security'] = $release['version'];
265 return $newerVersion;
269 * Collect info about the site to be sent as pingback data.
271 private function getSiteStats() {
272 $config = CRM_Core_Config
::singleton();
273 $siteKey = md5(defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY
: '');
275 // Calorie-free pingback for alphas
276 $this->stats
= array('version' => $this->localVersion
);
278 // Non-alpha versions get the full treatment
279 if ($this->localVersion
&& !strpos($this->localVersion
, 'alpha')) {
280 $this->stats +
= array(
281 'hash' => md5($siteKey . $config->userFrameworkBaseURL
),
282 'uf' => $config->userFramework
,
283 'lang' => $config->lcMessages
,
284 'co' => $config->defaultContactCountry
,
285 'ufv' => $config->userSystem
->getVersion(),
286 'PHP' => phpversion(),
287 'MySQL' => CRM_CORE_DAO
::singleValueQuery('SELECT VERSION()'),
288 'communityMessagesUrl' => Civi
::settings()->get('communityMessagesUrl'),
290 $this->getPayProcStats();
291 $this->getEntityStats();
292 $this->getExtensionStats();
297 * Get active payment processor types.
299 private function getPayProcStats() {
300 $dao = new CRM_Financial_DAO_PaymentProcessor();
305 // Get title and id for all processor types
306 $ppTypeNames = CRM_Core_PseudoConstant
::paymentProcessorType();
308 while ($dao->fetch()) {
309 $ppTypes[] = $ppTypeNames[$dao->payment_processor_type_id
];
311 // add the .-separated list of the processor types
312 $this->stats
['PPTypes'] = implode(',', array_unique($ppTypes));
316 * Fetch counts from entity tables.
317 * Add info to the 'entities' array
319 private function getEntityStats() {
321 'CRM_Activity_DAO_Activity' => 'is_test = 0',
322 'CRM_Case_DAO_Case' => 'is_deleted = 0',
323 'CRM_Contact_DAO_Contact' => 'is_deleted = 0',
324 'CRM_Contact_DAO_Relationship' => NULL,
325 'CRM_Campaign_DAO_Campaign' => NULL,
326 'CRM_Contribute_DAO_Contribution' => 'is_test = 0',
327 'CRM_Contribute_DAO_ContributionPage' => 'is_active = 1',
328 'CRM_Contribute_DAO_ContributionProduct' => NULL,
329 'CRM_Contribute_DAO_Widget' => 'is_active = 1',
330 'CRM_Core_DAO_Discount' => NULL,
331 'CRM_Price_DAO_PriceSetEntity' => NULL,
332 'CRM_Core_DAO_UFGroup' => 'is_active = 1',
333 'CRM_Event_DAO_Event' => 'is_active = 1',
334 'CRM_Event_DAO_Participant' => 'is_test = 0',
335 'CRM_Friend_DAO_Friend' => 'is_active = 1',
336 'CRM_Grant_DAO_Grant' => NULL,
337 'CRM_Mailing_DAO_Mailing' => 'is_completed = 1',
338 'CRM_Member_DAO_Membership' => 'is_test = 0',
339 'CRM_Member_DAO_MembershipBlock' => 'is_active = 1',
340 'CRM_Pledge_DAO_Pledge' => 'is_test = 0',
341 'CRM_Pledge_DAO_PledgeBlock' => NULL,
343 foreach ($tables as $daoName => $where) {
344 $dao = new $daoName();
346 $dao->whereAdd($where);
348 $short_name = substr($daoName, strrpos($daoName, '_') +
1);
349 $this->stats
['entities'][] = array(
350 'name' => $short_name,
351 'size' => $dao->count(),
357 * Fetch stats about enabled components/extensions
358 * Add info to the 'extensions' array
360 private function getExtensionStats() {
362 $config = CRM_Core_Config
::singleton();
363 foreach ($config->enableComponents
as $comp) {
364 $this->stats
['extensions'][] = array(
365 'name' => 'org.civicrm.component.' . strtolower($comp),
367 'version' => $this->stats
['version'],
370 // Contrib extensions
371 $mapper = CRM_Extension_System
::singleton()->getMapper();
372 $dao = new CRM_Core_DAO_Extension();
374 while ($dao->fetch()) {
375 $info = $mapper->keyToInfo($dao->full_name
);
376 $this->stats
['extensions'][] = array(
377 'name' => $dao->full_name
,
378 'enabled' => $dao->is_active
,
379 'version' => isset($info->version
) ?
$info->version
: NULL,
385 * Send the request to civicrm.org
386 * Store results in the cache file
388 private function pingBack() {
392 'header' => 'Content-type: application/x-www-form-urlencoded',
393 'content' => http_build_query($this->stats
),
396 $ctx = stream_context_create($params);
397 $rawJson = file_get_contents($this->pingbackUrl
, FALSE, $ctx);
398 $versionInfo = $rawJson ?
json_decode($rawJson, TRUE) : NULL;
399 // If we couldn't fetch or parse the data $versionInfo will be NULL
400 // Otherwise it will be an array and we'll cache it.
401 // Note the array may be empty e.g. in the case of a pre-alpha with no releases
402 $this->isInfoAvailable
= $versionInfo !== NULL;
403 if ($this->isInfoAvailable
) {
404 $this->writeCacheFile($rawJson);
405 $this->setVersionInfo($versionInfo);
412 private function readCacheFile() {
413 if (file_exists($this->cacheFile
)) {
414 $this->setVersionInfo(json_decode(file_get_contents($this->cacheFile
), TRUE));
421 * Save version info to file.
422 * @param string $contents
425 private function writeCacheFile($contents) {
426 if (file_put_contents($this->cacheFile
, $contents) === FALSE) {
427 throw new Exception('File not writable');
432 * Lookup version_check scheduled job
434 private function getJob() {
435 $jobs = civicrm_api3('Job', 'get', array(
437 'api_action' => "version_check",
438 'api_entity' => "job",
440 $this->cronJob
= CRM_Utils_Array
::value(0, $jobs['values'], array());