]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Util/Telemetry.php
Scrutinizer Auto-Fixes
[galette.git] / galette / lib / Galette / Util / Telemetry.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Handle Telemetry data
7 *
8 * PHP version 5
9 *
10 * Copyright © 2017 GLPI and Contributors
11 * Copyright © 2017-2018 The Galette Team
12 *
13 * This file is part of Galette (http://galette.tuxfamily.org).
14 *
15 * Galette is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation, either version 3 of the License, or
18 * (at your option) any later version.
19 *
20 * Galette is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
27 *
28 * @category Util
29 * @package Galette
30 *
31 * @author Johan Cwiklinski <johan@x-tnd.be>
32 * @copyright 2017 GLPI and Contributors
33 * @copyright 2017-2018 The Galette Team
34 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
35 * @link http://galette.tuxfamily.org
36 * @since Available since 0.9
37 */
38
39 namespace Galette\Util;
40
41 use Analog\Analog;
42 use Galette\Core\Db;
43 use Galette\Core\Preferences;
44 use Galette\Core\Plugins;
45
46 /**
47 * Handle Telemetry data
48 *
49 * @category Util
50 * @name Telemetry
51 * @package Galette
52 * @author Johan Cwiklinski <johan@x-tnd.be>
53 * @copyright 2017 GLPI and Contributors
54 * @copyright 2017-2018 The Galette Team
55 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
56 * @link http://galette.tuxfamily.org
57 * @since Available since 0.9
58 */
59 class Telemetry
60 {
61 private $zdb;
62 private $prefs;
63 private $plugins;
64 private $quick = false;
65
66 /**
67 * Constructor
68 *
69 * @param Db $zdb Database instance
70 * @param Preferences $prefs Preferences instance
71 * @param Plugins $plugins Plugins instance
72 */
73 public function __construct(Db $zdb, Preferences $prefs, Plugins $plugins)
74 {
75 $this->zdb = $zdb;
76 $this->prefs = $prefs;
77 $this->plugins = $plugins;
78 }
79
80 /**
81 * Grab telemetry information
82 *
83 * @return array
84 */
85 public function getTelemetryInfos()
86 {
87 $data = [
88 'galette' => $this->grabGaletteInfos(),
89 'system' => [
90 'db' => $this->grabDbInfos(),
91 'web_server' => $this->grabWebserverInfos(),
92 'php' => $this->grabPhpInfos(),
93 'os' => $this->grabOsInfos()
94 ]
95 ];
96 return $data;
97 }
98
99 /**
100 * Grab Galette part information
101 *
102 * @return array
103 */
104 public function grabGaletteInfos()
105 {
106 $galette = [
107 'uuid' => $this->getInstanceUuid(),
108 'version' => GALETTE_VERSION,
109 'plugins' => [],
110 'default_language' => $this->prefs->pref_lang,
111 'usage' => [
112 'avg_members' => $this->getAverage(\Galette\Entity\Adherent::TABLE),
113 'avg_contributions' => $this->getAverage(\Galette\Entity\Contribution::TABLE),
114 'avg_transactions' => $this->getAverage(\Galette\Entity\Transaction::TABLE)
115 ]
116 ];
117
118 $plugins = $this->plugins->getModules();
119 foreach ($plugins as $plugin) {
120 $galette['plugins'][] = [
121 'key' => $plugin['name'],
122 'version' => $plugin['version']
123 ];
124 }
125
126 return $galette;
127 }
128
129 /**
130 * Grab DB part information
131 *
132 * @return array
133 */
134 public function grabDbInfos()
135 {
136 $dbinfos = $this->zdb->getInfos();
137 return $dbinfos;
138 }
139
140 /**
141 * Grab web server part information
142 *
143 * @return array
144 */
145 public function grabWebserverInfos()
146 {
147 $headers = false;
148 $engine = '';
149 $version = '';
150
151 // check if host is present (do no throw php warning in contrary of get_headers)
152 if (PHP_SAPI !== 'cli') {
153 $headers = get_headers($this->prefs->getURL());
154 }
155
156 if (is_array($headers)) {
157 //BEGIN EXTRACTING SERVER DETAILS
158 $pattern = '#^Server:*#i';
159 $matches = preg_grep($pattern, $headers);
160
161 if (count($matches)) {
162 $infos = current($matches);
163 $pattern = '#Server: ([^ ]+)/([^ ]+)#i';
164 preg_match($pattern, $infos, $srv_infos);
165 if (count($srv_infos) == 3) {
166 $engine = $srv_infos[1];
167 $version = $srv_infos[2];
168 }
169 }
170 }
171
172 $server = [
173 'engine' => $engine,
174 'version' => $version
175 ];
176
177 return $server;
178 }
179
180 /**
181 * Grab PHP part information
182 *
183 * @return array
184 */
185 public function grabPhpInfos()
186 {
187 $php = [
188 'version' => str_replace(PHP_EXTRA_VERSION, '', PHP_VERSION),
189 'modules' => get_loaded_extensions(),
190 'setup' => [
191 'max_execution_time' => ini_get('max_execution_time'),
192 'memory_limit' => ini_get('memory_limit'),
193 'post_max_size' => ini_get('post_max_size'),
194 'safe_mode' => ini_get('safe_mode'),
195 'session' => ini_get('session.save_handler'),
196 'upload_max_filesize' => ini_get('upload_max_filesize')
197 ]
198 ];
199
200 return $php;
201 }
202
203 /**
204 * Grab OS part information
205 *
206 * @return array
207 */
208 public function grabOsInfos()
209 {
210 $os = [
211 'family' => php_uname('s'),
212 'distribution' => '',
213 'version' => php_uname('r')
214 ];
215
216 return $os;
217 }
218
219 /**
220 * Count
221 *
222 * @param string $table Table to query
223 * @param array $where Where clause, if any
224 *
225 * @return integer
226 */
227 public function getCount($table, $where = [])
228 {
229 $select = $this->zdb->select($table);
230 $select->columns([
231 'cnt' => new \Laminas\Db\Sql\Expression(
232 'COUNT(1)'
233 )
234 ]);
235 $results = $this->zdb->execute($select);
236 $result = $results->current();
237 return (int)$result->cnt;
238 }
239
240 /**
241 * Calculate average parts
242 *
243 * @param string $table Table to query
244 * @param array $where Where clause, if any
245 *
246 * @return string
247 */
248 private function getAverage($table, $where = [])
249 {
250 $count = $this->getCount($table, $where);
251
252 if ($count <= 50) {
253 return '0-50';
254 } elseif ($count <= 250) {
255 return '50-250';
256 } elseif ($count <= 500) {
257 return '250-500';
258 } elseif ($count <= 1000) {
259 return '500-1000';
260 } elseif ($count <= 5000) {
261 return '1000-5000';
262 }
263 return '5000+';
264 }
265
266 /**
267 * Send telemetry information
268 *
269 * @return boolean
270 */
271 public function send()
272 {
273 $data = $this->getTelemetryInfos();
274 $infos = json_encode(['data' => $data]);
275
276 $uri = GALETTE_TELEMETRY_URI . 'telemetry';
277 $ch = curl_init($uri);
278 $opts = [
279 CURLOPT_URL => $uri,
280 CURLOPT_USERAGENT => 'Galette/' . GALETTE_VERSION,
281 CURLOPT_RETURNTRANSFER => 1,
282 CURLOPT_POSTFIELDS => $infos,
283 CURLOPT_HTTPHEADER => ['Content-Type:application/json']
284 ];
285 if ($this->quick === true) {
286 //set entire curl call timeout
287 $opts[CURLOPT_TIMEOUT] = 3;
288 //set curl connection timeout
289 $opts[CURLOPT_CONNECTTIMEOUT] = 2;
290 }
291
292 curl_setopt_array($ch, $opts);
293 $content = json_decode(curl_exec($ch));
294 $errstr = curl_error($ch);
295 curl_close($ch);
296
297 if ($content && property_exists($content, 'message')) {
298 if (property_exists($content, 'errors')) {
299 $errors = '';
300 foreach ($content->errors as $error) {
301 $errors .= "\n" . $error->property . ': ' . $error->message;
302 }
303 throw new \RuntimeException($errors);
304 }
305
306 $this->prefs->pref_telemetry_date = date('Y-m-d H:i:s');
307 $this->prefs->store();
308
309 //all is OK!
310 return true;
311 } else {
312 $message = 'Something went wrong sending telemetry information';
313 if ($errstr != '') {
314 $message .= ": $errstr";
315 }
316 Analog::log(
317 $message,
318 Analog::ERROR
319 );
320 throw new \RuntimeException($message);
321 }
322 }
323
324 /**
325 * Get UUID
326 *
327 * @param string $type UUID type (either instance or registration)
328 *
329 * @return string
330 */
331 private function getUuid($type)
332 {
333 $param = 'pref_' . $type . '_uuid';
334 $uuid = $this->prefs->$param;
335 if (empty($uuid)) {
336 $uuid = self::generateUuid($type);
337 }
338 return $uuid;
339 }
340
341 /**
342 * Get instance UUID
343 *
344 * @return string
345 */
346 private function getInstanceUuid()
347 {
348 return $this->getUuid('instance');
349 }
350
351 /**
352 * Get registration UUID
353 *
354 * @return string
355 */
356 final public function getRegistrationUuid()
357 {
358 return $this->getUuid('registration');
359 }
360
361
362 /**
363 * Generates an unique identifier and store it
364 *
365 * @param string $type UUID type (either instance or registration)
366 *
367 * @return string
368 */
369 final public function generateUuid($type)
370 {
371 $uuid = $this->getRandomString(40);
372 $param = 'pref_' . $type . '_uuid';
373 $this->prefs->$param = $uuid;
374 $this->prefs->store();
375 return $uuid;
376 }
377
378 /**
379 * Generates an unique identifier for current instance and store it
380 *
381 * @return string
382 */
383 final public function generateInstanceUuid()
384 {
385 return self::generateUuid('instance');
386 }
387
388 /**
389 * Generates an unique identifier for current instance and store it
390 *
391 * @return string
392 */
393 final public function generateRegistrationUuid()
394 {
395 return self::generateUuid('registration');
396 }
397
398 /**
399 * Get date telemetry has been sent
400 *
401 * @return string
402 */
403 public function getSentDate()
404 {
405 return $this->prefs->pref_telemetry_date;
406 }
407
408 /**
409 * Get date of registration
410 *
411 * @return string
412 */
413 public function getRegistrationDate()
414 {
415 return $this->prefs->pref_registration_date;
416 }
417
418 /**
419 * Does telemetry infos has been sent already?
420 *
421 * @return boolean
422 */
423 public function isSent()
424 {
425 return $this->getSentDate() != false;
426 }
427
428 /**
429 * Is instance registered?
430 *
431 * @return boolean
432 */
433 public function isRegistered()
434 {
435 return $this->getRegistrationDate() != false;
436 }
437
438 /**
439 * Get a random string
440 *
441 * @param integer $length of the random string
442 *
443 * @return string
444 *
445 * @see https://stackoverflow.com/questions/4356289/php-random-string-generator/31107425#31107425
446 */
447 private function getRandomString($length)
448 {
449 $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
450 $str = '';
451 $max = mb_strlen($keyspace, '8bit') - 1;
452 for ($i = 0; $i < $length; ++$i) {
453 $str .= $keyspace[random_int(0, $max)];
454 }
455 return $str;
456 }
457
458 /**
459 * Set quick mode
460 * Will set a short timeout on curl calls
461 *
462 * @return Telemetry
463 */
464 public function setQuick()
465 {
466 $this->quick = true;
467 return $this;
468 }
469 }