PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/sites/all/modules/contrib/civicrm/CRM/Utils/VersionCheck.php

https://gitlab.com/virtualrealms/d7civicrm
PHP | 359 lines | 195 code | 29 blank | 135 comment | 17 complexity | d9637ac2c96bc53cde8b5a0725f63056 MD5 | raw file
  1. <?php
  2. /*
  3. +--------------------------------------------------------------------+
  4. | CiviCRM version 5 |
  5. +--------------------------------------------------------------------+
  6. | Copyright CiviCRM LLC (c) 2004-2019 |
  7. +--------------------------------------------------------------------+
  8. | This file is a part of CiviCRM. |
  9. | |
  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. |
  13. | |
  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. |
  18. | |
  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. +--------------------------------------------------------------------+
  26. */
  27. /**
  28. *
  29. * @package CRM
  30. * @copyright CiviCRM LLC (c) 2004-2019
  31. */
  32. class CRM_Utils_VersionCheck {
  33. const
  34. CACHEFILE_NAME = 'version-msgs-cache.json',
  35. // After which length of time we expire the cached version info (3 days).
  36. CACHEFILE_EXPIRE = 259200;
  37. /**
  38. * The version of the current (local) installation
  39. *
  40. * @var string
  41. */
  42. public $localVersion = NULL;
  43. /**
  44. * Info about available versions
  45. *
  46. * @var array
  47. */
  48. public $versionInfo = [];
  49. /**
  50. * @var bool
  51. */
  52. public $isInfoAvailable;
  53. /**
  54. * @var array
  55. */
  56. public $cronJob = [];
  57. /**
  58. * @var string
  59. */
  60. public $pingbackUrl = 'https://latest.civicrm.org/stable.php?format=summary';
  61. /**
  62. * Pingback params
  63. *
  64. * @var array
  65. */
  66. protected $stats = [];
  67. /**
  68. * Path to cache file
  69. *
  70. * @var string
  71. */
  72. public $cacheFile;
  73. /**
  74. * Class constructor.
  75. */
  76. public function __construct() {
  77. $this->localVersion = CRM_Utils_System::version();
  78. $this->cacheFile = CRM_Core_Config::singleton()->uploadDir . self::CACHEFILE_NAME;
  79. }
  80. /**
  81. * Self-populates version info
  82. *
  83. * @throws \Exception
  84. */
  85. public function initialize() {
  86. $this->getJob();
  87. // Populate remote $versionInfo from cache file
  88. $this->isInfoAvailable = $this->readCacheFile();
  89. // Fallback if scheduled job is enabled but has failed to run.
  90. $expiryTime = time() - self::CACHEFILE_EXPIRE;
  91. if (!empty($this->cronJob['is_active']) &&
  92. (!$this->isInfoAvailable || filemtime($this->cacheFile) < $expiryTime)
  93. ) {
  94. // First try updating the files modification time, for 2 reasons:
  95. // - if the file is not writeable, this saves the trouble of pinging back
  96. // - if the remote server is down, this will prevent an immediate retry
  97. if (touch($this->cacheFile) === FALSE) {
  98. throw new Exception('File not writable');
  99. }
  100. $this->fetch();
  101. }
  102. }
  103. /**
  104. * Sets $versionInfo
  105. *
  106. * @param $info
  107. */
  108. protected function setVersionInfo($info) {
  109. $this->versionInfo = $info;
  110. }
  111. /**
  112. * @return array|NULL
  113. * message: string
  114. * title: string
  115. * severity: string
  116. * Ex: 'info', 'notice', 'warning', 'critical'.
  117. */
  118. public function getVersionMessages() {
  119. return $this->isInfoAvailable ? $this->versionInfo : NULL;
  120. }
  121. /**
  122. * Called by version_check cron job
  123. */
  124. public function fetch() {
  125. $this->getSiteStats();
  126. $this->pingBack();
  127. }
  128. /**
  129. * Collect info about the site to be sent as pingback data.
  130. */
  131. private function getSiteStats() {
  132. $config = CRM_Core_Config::singleton();
  133. $siteKey = md5(defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY : '');
  134. // Calorie-free pingback for alphas
  135. $this->stats = ['version' => $this->localVersion];
  136. // Non-alpha versions get the full treatment
  137. if ($this->localVersion && !strpos($this->localVersion, 'alpha')) {
  138. $this->stats += [
  139. 'hash' => md5($siteKey . $config->userFrameworkBaseURL),
  140. 'uf' => $config->userFramework,
  141. 'lang' => $config->lcMessages,
  142. 'co' => $config->defaultContactCountry,
  143. 'ufv' => $config->userSystem->getVersion(),
  144. 'PHP' => phpversion(),
  145. 'MySQL' => CRM_Core_DAO::singleValueQuery('SELECT VERSION()'),
  146. 'communityMessagesUrl' => Civi::settings()->get('communityMessagesUrl'),
  147. ];
  148. $this->getDomainStats();
  149. $this->getPayProcStats();
  150. $this->getEntityStats();
  151. $this->getExtensionStats();
  152. }
  153. }
  154. /**
  155. * Get active payment processor types.
  156. */
  157. private function getPayProcStats() {
  158. $dao = new CRM_Financial_DAO_PaymentProcessor();
  159. $dao->is_active = 1;
  160. $dao->find();
  161. $ppTypes = [];
  162. // Get title for all processor types
  163. // FIXME: This should probably be getName, but it has always returned translated label so we stick with that for now as it would affect stats
  164. while ($dao->fetch()) {
  165. $ppTypes[] = CRM_Core_PseudoConstant::getLabel('CRM_Financial_BAO_PaymentProcessor', 'payment_processor_type_id', $dao->payment_processor_type_id);
  166. }
  167. // add the .-separated list of the processor types
  168. $this->stats['PPTypes'] = implode(',', array_unique($ppTypes));
  169. }
  170. /**
  171. * Fetch counts from entity tables.
  172. * Add info to the 'entities' array
  173. */
  174. private function getEntityStats() {
  175. $tables = [
  176. 'CRM_Activity_DAO_Activity' => 'is_test = 0',
  177. 'CRM_Case_DAO_Case' => 'is_deleted = 0',
  178. 'CRM_Contact_DAO_Contact' => 'is_deleted = 0',
  179. 'CRM_Contact_DAO_Relationship' => NULL,
  180. 'CRM_Campaign_DAO_Campaign' => NULL,
  181. 'CRM_Contribute_DAO_Contribution' => 'is_test = 0',
  182. 'CRM_Contribute_DAO_ContributionPage' => 'is_active = 1',
  183. 'CRM_Contribute_DAO_ContributionProduct' => NULL,
  184. 'CRM_Contribute_DAO_Widget' => 'is_active = 1',
  185. 'CRM_Core_DAO_Discount' => NULL,
  186. 'CRM_Price_DAO_PriceSetEntity' => NULL,
  187. 'CRM_Core_DAO_UFGroup' => 'is_active = 1',
  188. 'CRM_Event_DAO_Event' => 'is_active = 1',
  189. 'CRM_Event_DAO_Participant' => 'is_test = 0',
  190. 'CRM_Friend_DAO_Friend' => 'is_active = 1',
  191. 'CRM_Grant_DAO_Grant' => NULL,
  192. 'CRM_Mailing_DAO_Mailing' => 'is_completed = 1',
  193. 'CRM_Member_DAO_Membership' => 'is_test = 0',
  194. 'CRM_Member_DAO_MembershipBlock' => 'is_active = 1',
  195. 'CRM_Pledge_DAO_Pledge' => 'is_test = 0',
  196. 'CRM_Pledge_DAO_PledgeBlock' => NULL,
  197. 'CRM_Mailing_Event_DAO_Delivered' => NULL,
  198. ];
  199. foreach ($tables as $daoName => $where) {
  200. $dao = new $daoName();
  201. if ($where) {
  202. $dao->whereAdd($where);
  203. }
  204. $short_name = substr($daoName, strrpos($daoName, '_') + 1);
  205. $this->stats['entities'][] = [
  206. 'name' => $short_name,
  207. 'size' => $dao->count(),
  208. ];
  209. }
  210. }
  211. /**
  212. * Fetch stats about enabled components/extensions
  213. * Add info to the 'extensions' array
  214. */
  215. private function getExtensionStats() {
  216. // Core components
  217. $config = CRM_Core_Config::singleton();
  218. foreach ($config->enableComponents as $comp) {
  219. $this->stats['extensions'][] = [
  220. 'name' => 'org.civicrm.component.' . strtolower($comp),
  221. 'enabled' => 1,
  222. 'version' => $this->stats['version'],
  223. ];
  224. }
  225. // Contrib extensions
  226. $mapper = CRM_Extension_System::singleton()->getMapper();
  227. $dao = new CRM_Core_DAO_Extension();
  228. $dao->find();
  229. while ($dao->fetch()) {
  230. $info = $mapper->keyToInfo($dao->full_name);
  231. $this->stats['extensions'][] = [
  232. 'name' => $dao->full_name,
  233. 'enabled' => $dao->is_active,
  234. 'version' => isset($info->version) ? $info->version : NULL,
  235. ];
  236. }
  237. }
  238. /**
  239. * Fetch stats about domain and add to 'stats' array.
  240. */
  241. private function getDomainStats() {
  242. // Start with default value NULL, then check to see if there's a better
  243. // value to be had.
  244. $this->stats['domain_isoCode'] = NULL;
  245. $params = [
  246. 'id' => CRM_Core_Config::domainID(),
  247. ];
  248. $domain_result = civicrm_api3('domain', 'getsingle', $params);
  249. if (!empty($domain_result['contact_id'])) {
  250. $address_params = [
  251. 'contact_id' => $domain_result['contact_id'],
  252. 'is_primary' => 1,
  253. 'sequential' => 1,
  254. ];
  255. $address_result = civicrm_api3('address', 'get', $address_params);
  256. if ($address_result['count'] == 1 && !empty($address_result['values'][0]['country_id'])) {
  257. $country_params = [
  258. 'id' => $address_result['values'][0]['country_id'],
  259. ];
  260. $country_result = civicrm_api3('country', 'getsingle', $country_params);
  261. if (!empty($country_result['iso_code'])) {
  262. $this->stats['domain_isoCode'] = $country_result['iso_code'];
  263. }
  264. }
  265. }
  266. }
  267. /**
  268. * Send the request to civicrm.org
  269. * Store results in the cache file
  270. */
  271. private function pingBack() {
  272. $params = [
  273. 'http' => [
  274. 'method' => 'POST',
  275. 'header' => 'Content-type: application/x-www-form-urlencoded',
  276. 'content' => http_build_query($this->stats),
  277. ],
  278. ];
  279. $ctx = stream_context_create($params);
  280. $rawJson = file_get_contents($this->pingbackUrl, FALSE, $ctx);
  281. $versionInfo = $rawJson ? json_decode($rawJson, TRUE) : NULL;
  282. // If we couldn't fetch or parse the data $versionInfo will be NULL
  283. // Otherwise it will be an array and we'll cache it.
  284. // Note the array may be empty e.g. in the case of a pre-alpha with no releases
  285. $this->isInfoAvailable = $versionInfo !== NULL;
  286. if ($this->isInfoAvailable) {
  287. $this->writeCacheFile($rawJson);
  288. $this->setVersionInfo($versionInfo);
  289. }
  290. }
  291. /**
  292. * @return bool
  293. */
  294. private function readCacheFile() {
  295. if (file_exists($this->cacheFile)) {
  296. $this->setVersionInfo(json_decode(file_get_contents($this->cacheFile), TRUE));
  297. return TRUE;
  298. }
  299. return FALSE;
  300. }
  301. /**
  302. * Save version info to file.
  303. * @param string $contents
  304. * @throws \Exception
  305. */
  306. private function writeCacheFile($contents) {
  307. if (file_put_contents($this->cacheFile, $contents) === FALSE) {
  308. throw new Exception('File not writable');
  309. }
  310. }
  311. /**
  312. * Removes cached version info.
  313. */
  314. public function flushCache() {
  315. if (file_exists($this->cacheFile)) {
  316. unlink($this->cacheFile);
  317. }
  318. }
  319. /**
  320. * Lookup version_check scheduled job
  321. */
  322. private function getJob() {
  323. $jobs = civicrm_api3('Job', 'get', [
  324. 'sequential' => 1,
  325. 'api_action' => "version_check",
  326. 'api_entity' => "job",
  327. ]);
  328. $this->cronJob = CRM_Utils_Array::value(0, $jobs['values'], []);
  329. }
  330. }