stats.class.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. <?php
  2. /* Copyright (C) 2003 Rodolphe Quiedeville <rodolphe@quiedeville.org>
  3. * Copyright (c) 2008-2013 Laurent Destailleur <eldy@users.sourceforge.net>
  4. * Copyright (C) 2012 Regis Houssin <regis.houssin@inodbox.com>
  5. * Copyright (C) 2012 Marcos García <marcosgdf@gmail.com>
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. /**
  21. * \file htdocs/core/class/stats.class.php
  22. * \ingroup core
  23. * \brief Common class to manage statistics reports
  24. */
  25. /**
  26. * Parent class of statistics class
  27. */
  28. abstract class Stats
  29. {
  30. protected $db;
  31. var $_lastfetchdate=array(); // Dates of cache file read by methods
  32. var $cachefilesuffix=''; // Suffix to add to name of cache file (to avoid file name conflicts)
  33. /**
  34. * Return nb of elements by month for several years
  35. *
  36. * @param int $endyear Start year
  37. * @param int $startyear End year
  38. * @param int $cachedelay Delay we accept for cache file (0=No read, no save of cache, -1=No read but save)
  39. * @param int $format 0=Label of absiss is a translated text, 1=Label of absiss is month number, 2=Label of absiss is first letter of month
  40. * @return array Array of values
  41. */
  42. function getNbByMonthWithPrevYear($endyear, $startyear, $cachedelay=0, $format=0)
  43. {
  44. global $conf,$user,$langs;
  45. if ($startyear > $endyear) return -1;
  46. $datay=array();
  47. // Search into cache
  48. if (! empty($cachedelay))
  49. {
  50. include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  51. include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
  52. }
  53. $newpathofdestfile=$conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix)?'':$this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache';
  54. $newmask='0644';
  55. $nowgmt = dol_now();
  56. $foundintocache=0;
  57. if ($cachedelay > 0)
  58. {
  59. $filedate=dol_filemtime($newpathofdestfile);
  60. if ($filedate >= ($nowgmt - $cachedelay))
  61. {
  62. $foundintocache=1;
  63. $this->_lastfetchdate[get_class($this).'_'.__FUNCTION__]=$filedate;
  64. }
  65. else
  66. {
  67. dol_syslog(get_class($this).'::'.__FUNCTION__." cache file ".$newpathofdestfile." is not found or older than now - cachedelay (".$nowgmt." - ".$cachedelay.") so we can't use it.");
  68. }
  69. }
  70. // Load file into $data
  71. if ($foundintocache) // Cache file found and is not too old
  72. {
  73. dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
  74. $data = json_decode(file_get_contents($newpathofdestfile), true);
  75. }
  76. else
  77. {
  78. $year=$startyear;
  79. while ($year <= $endyear)
  80. {
  81. $datay[$year] = $this->getNbByMonth($year, $format);
  82. $year++;
  83. }
  84. $data = array();
  85. for ($i = 0 ; $i < 12 ; $i++)
  86. {
  87. $data[$i][]=$datay[$endyear][$i][0];
  88. $year=$startyear;
  89. while($year <= $endyear)
  90. {
  91. $data[$i][]=$datay[$year][$i][1];
  92. $year++;
  93. }
  94. }
  95. }
  96. // Save cache file
  97. if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1))
  98. {
  99. dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
  100. if (! dol_is_dir($conf->user->dir_temp)) dol_mkdir($conf->user->dir_temp);
  101. $fp = fopen($newpathofdestfile, 'w');
  102. fwrite($fp, json_encode($data));
  103. fclose($fp);
  104. if (! empty($conf->global->MAIN_UMASK)) $newmask=$conf->global->MAIN_UMASK;
  105. @chmod($newpathofdestfile, octdec($newmask));
  106. $this->_lastfetchdate[get_class($this).'_'.__FUNCTION__]=$nowgmt;
  107. }
  108. // return array(array('Month',val1,val2,val3),...)
  109. return $data;
  110. }
  111. /**
  112. * Return amount of elements by month for several years.
  113. * Criterias used to build request are defined into the constructor of parent class into xxx/class/xxxstats.class.php
  114. * The caller of class can add more filters into sql request by adding criteris into the $stats->where property just after
  115. * calling constructor.
  116. *
  117. * @param int $endyear Start year
  118. * @param int $startyear End year
  119. * @param int $cachedelay Delay we accept for cache file (0=No read, no save of cache, -1=No read but save)
  120. * @param int $format 0=Label of absiss is a translated text, 1=Label of absiss is month number, 2=Label of absiss is first letter of month
  121. * @return array Array of values
  122. */
  123. function getAmountByMonthWithPrevYear($endyear, $startyear, $cachedelay=0, $format=0)
  124. {
  125. global $conf,$user,$langs;
  126. if ($startyear > $endyear) return -1;
  127. $datay=array();
  128. // Search into cache
  129. if (! empty($cachedelay))
  130. {
  131. include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  132. include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
  133. }
  134. $newpathofdestfile=$conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix)?'':$this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache';
  135. $newmask='0644';
  136. $nowgmt = dol_now();
  137. $foundintocache=0;
  138. if ($cachedelay > 0)
  139. {
  140. $filedate=dol_filemtime($newpathofdestfile);
  141. if ($filedate >= ($nowgmt - $cachedelay))
  142. {
  143. $foundintocache=1;
  144. $this->_lastfetchdate[get_class($this).'_'.__FUNCTION__]=$filedate;
  145. }
  146. else
  147. {
  148. dol_syslog(get_class($this).'::'.__FUNCTION__." cache file ".$newpathofdestfile." is not found or older than now - cachedelay (".$nowgmt." - ".$cachedelay.") so we can't use it.");
  149. }
  150. }
  151. // Load file into $data
  152. if ($foundintocache) // Cache file found and is not too old
  153. {
  154. dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
  155. $data = json_decode(file_get_contents($newpathofdestfile), true);
  156. }
  157. else
  158. {
  159. $year=$startyear;
  160. while($year <= $endyear)
  161. {
  162. $datay[$year] = $this->getAmountByMonth($year, $format);
  163. $year++;
  164. }
  165. $data = array();
  166. // $data = array('xval'=>array(0=>xlabel,1=>yval1,2=>yval2...),...)
  167. for ($i = 0 ; $i < 12 ; $i++)
  168. {
  169. $data[$i][]=$datay[$endyear][$i][0]; // set label
  170. $year=$startyear;
  171. while($year <= $endyear)
  172. {
  173. $data[$i][]=$datay[$year][$i][1]; // set yval for x=i
  174. $year++;
  175. }
  176. }
  177. }
  178. // Save cache file
  179. if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1))
  180. {
  181. dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
  182. if (! dol_is_dir($conf->user->dir_temp)) dol_mkdir($conf->user->dir_temp);
  183. $fp = fopen($newpathofdestfile, 'w');
  184. if ($fp)
  185. {
  186. fwrite($fp, json_encode($data));
  187. fclose($fp);
  188. if (! empty($conf->global->MAIN_UMASK)) $newmask=$conf->global->MAIN_UMASK;
  189. @chmod($newpathofdestfile, octdec($newmask));
  190. }
  191. else dol_syslog("Failed to write cache file", LOG_ERR);
  192. $this->_lastfetchdate[get_class($this).'_'.__FUNCTION__]=$nowgmt;
  193. }
  194. return $data;
  195. }
  196. /**
  197. * Return average of entity by month for several years
  198. *
  199. * @param int $endyear Start year
  200. * @param int $startyear End year
  201. * @return array Array of values
  202. */
  203. function getAverageByMonthWithPrevYear($endyear,$startyear)
  204. {
  205. if ($startyear > $endyear) return -1;
  206. $datay=array();
  207. $year=$startyear;
  208. while($year <= $endyear)
  209. {
  210. $datay[$year] = $this->getAverageByMonth($year);
  211. $year++;
  212. }
  213. $data = array();
  214. for ($i = 0 ; $i < 12 ; $i++)
  215. {
  216. $data[$i][]=$datay[$endyear][$i][0];
  217. $year=$startyear;
  218. while($year <= $endyear)
  219. {
  220. $data[$i][]=$datay[$year][$i][1];
  221. $year++;
  222. }
  223. }
  224. return $data;
  225. }
  226. /**
  227. * Return count, and sum of products
  228. *
  229. * @param int $year Year
  230. * @param int $cachedelay Delay we accept for cache file (0=No read, no save of cache, -1=No read but save)
  231. * @return array Array of values
  232. */
  233. function getAllByProductEntry($year,$cachedelay=0)
  234. {
  235. global $conf,$user,$langs;
  236. $datay=array();
  237. // Search into cache
  238. if (! empty($cachedelay))
  239. {
  240. include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  241. include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
  242. }
  243. $newpathofdestfile=$conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix)?'':$this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache';
  244. $newmask='0644';
  245. $nowgmt = dol_now();
  246. $foundintocache=0;
  247. if ($cachedelay > 0)
  248. {
  249. $filedate=dol_filemtime($newpathofdestfile);
  250. if ($filedate >= ($nowgmt - $cachedelay))
  251. {
  252. $foundintocache=1;
  253. $this->_lastfetchdate[get_class($this).'_'.__FUNCTION__]=$filedate;
  254. }
  255. else
  256. {
  257. dol_syslog(get_class($this).'::'.__FUNCTION__." cache file ".$newpathofdestfile." is not found or older than now - cachedelay (".$nowgmt." - ".$cachedelay.") so we can't use it.");
  258. }
  259. }
  260. // Load file into $data
  261. if ($foundintocache) // Cache file found and is not too old
  262. {
  263. dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
  264. $data = json_decode(file_get_contents($newpathofdestfile), true);
  265. }
  266. else
  267. {
  268. $data=$this->getAllByProduct($year);
  269. // $data[$i][]=$datay[$year][$i][1]; // set yval for x=i
  270. }
  271. // Save cache file
  272. if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1))
  273. {
  274. dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
  275. if (! dol_is_dir($conf->user->dir_temp)) dol_mkdir($conf->user->dir_temp);
  276. $fp = fopen($newpathofdestfile, 'w');
  277. if ($fp)
  278. {
  279. fwrite($fp, json_encode($data));
  280. fclose($fp);
  281. if (! empty($conf->global->MAIN_UMASK)) $newmask=$conf->global->MAIN_UMASK;
  282. @chmod($newpathofdestfile, octdec($newmask));
  283. }
  284. $this->_lastfetchdate[get_class($this).'_'.__FUNCTION__]=$nowgmt;
  285. }
  286. return $data;
  287. }
  288. // Here we have low level of shared code called by XxxStats.class.php
  289. /**
  290. * Return nb of elements by year
  291. *
  292. * @param string $sql SQL request
  293. * @return array
  294. */
  295. function _getNbByYear($sql)
  296. {
  297. $result = array();
  298. dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
  299. $resql=$this->db->query($sql);
  300. if ($resql)
  301. {
  302. $num = $this->db->num_rows($resql);
  303. $i = 0;
  304. while ($i < $num)
  305. {
  306. $row = $this->db->fetch_row($resql);
  307. $result[$i] = $row;
  308. $i++;
  309. }
  310. $this->db->free($resql);
  311. }
  312. else {
  313. dol_print_error($this->db);
  314. }
  315. return $result;
  316. }
  317. /**
  318. * Return nb of elements, total amount and avg amount each year
  319. *
  320. * @param string $sql SQL request
  321. * @return array Array with nb, total amount, average for each year
  322. */
  323. function _getAllByYear($sql)
  324. {
  325. $result = array();
  326. dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
  327. $resql=$this->db->query($sql);
  328. if ($resql)
  329. {
  330. $num = $this->db->num_rows($resql);
  331. $i = 0;
  332. while ($i < $num)
  333. {
  334. $row = $this->db->fetch_object($resql);
  335. $result[$i]['year'] = $row->year;
  336. $result[$i]['nb'] = $row->nb;
  337. if($i>0 && $row->nb>0) $result[$i-1]['nb_diff'] = ($result[$i-1]['nb'] - $row->nb) / $row->nb * 100;
  338. $result[$i]['total'] = $row->total;
  339. if($i>0 && $row->total>0) $result[$i-1]['total_diff'] = ($result[$i-1]['total'] - $row->total) / $row->total * 100;
  340. $result[$i]['avg'] = $row->avg;
  341. if($i>0 && $row->avg>0) $result[$i-1]['avg_diff'] = ($result[$i-1]['avg'] - $row->avg) / $row->avg * 100;
  342. // For some $sql only
  343. if (isset($row->weighted))
  344. {
  345. $result[$i]['weighted'] = $row->weighted;
  346. if($i>0 && $row->weighted>0) $result[$i-1]['avg_weighted'] = ($result[$i-1]['weighted'] - $row->weighted) / $row->weighted * 100;
  347. }
  348. $i++;
  349. }
  350. $this->db->free($resql);
  351. }
  352. else {
  353. dol_print_error($this->db);
  354. }
  355. return $result;
  356. }
  357. /**
  358. * Renvoie le nombre de proposition par mois pour une annee donnee
  359. *
  360. * @param int $year Year
  361. * @param string $sql SQL
  362. * @param int $format 0=Label of absiss is a translated text, 1=Label of absiss is month number, 2=Label of absiss is first letter of month
  363. * @return array Array of nb each month
  364. */
  365. function _getNbByMonth($year, $sql, $format=0)
  366. {
  367. global $langs;
  368. $result=array();
  369. $res=array();
  370. dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
  371. $resql=$this->db->query($sql);
  372. if ($resql)
  373. {
  374. $num = $this->db->num_rows($resql);
  375. $i = 0; $j = 0;
  376. while ($i < $num)
  377. {
  378. $row = $this->db->fetch_row($resql);
  379. $j = $row[0] * 1;
  380. $result[$j] = $row[1];
  381. $i++;
  382. }
  383. $this->db->free($resql);
  384. }
  385. else
  386. {
  387. dol_print_error($this->db);
  388. }
  389. for ($i = 1 ; $i < 13 ; $i++)
  390. {
  391. $res[$i] = (isset($result[$i])?$result[$i]:0);
  392. }
  393. $data = array();
  394. for ($i = 1 ; $i < 13 ; $i++)
  395. {
  396. $month='unknown';
  397. if ($format == 0) $month=$langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i));
  398. elseif ($format == 1) $month=$i;
  399. elseif ($format == 2) $month=$langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i));
  400. //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b"));
  401. //$month=dol_substr($month,0,3);
  402. $data[$i-1] = array($month, $res[$i]);
  403. }
  404. return $data;
  405. }
  406. /**
  407. * Renvoie le nombre d'element par mois pour une annee donnee
  408. *
  409. * @param int $year Year
  410. * @param string $sql SQL
  411. * @param int $format 0=Label of absiss is a translated text, 1=Label of absiss is month number, 2=Label of absiss is first letter of month
  412. * @return array
  413. */
  414. function _getAmountByMonth($year, $sql, $format=0)
  415. {
  416. global $langs;
  417. $result=array();
  418. $res=array();
  419. dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
  420. $resql=$this->db->query($sql);
  421. if ($resql)
  422. {
  423. $num = $this->db->num_rows($resql);
  424. $i = 0;
  425. while ($i < $num)
  426. {
  427. $row = $this->db->fetch_row($resql);
  428. $j = $row[0] * 1;
  429. $result[$j] = $row[1];
  430. $i++;
  431. }
  432. $this->db->free($resql);
  433. }
  434. else dol_print_error($this->db);
  435. for ($i = 1 ; $i < 13 ; $i++)
  436. {
  437. $res[$i] = (int) round((isset($result[$i])?$result[$i]:0));
  438. }
  439. $data = array();
  440. for ($i = 1 ; $i < 13 ; $i++)
  441. {
  442. $month='unknown';
  443. if ($format == 0) $month=$langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i));
  444. elseif ($format == 1) $month=$i;
  445. elseif ($format == 2) $month=$langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i));
  446. //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b"));
  447. //$month=dol_substr($month,0,3);
  448. $data[$i-1] = array($month, $res[$i]);
  449. }
  450. return $data;
  451. }
  452. /**
  453. * Renvoie le montant moyen par mois pour une annee donnee
  454. *
  455. * @param int $year Year
  456. * @param string $sql SQL
  457. * @param int $format 0=Label of absiss is a translated text, 1=Label of absiss is month number, 2=Label of absiss is first letter of month
  458. * @return array
  459. */
  460. function _getAverageByMonth($year, $sql, $format=0)
  461. {
  462. global $langs;
  463. $result=array();
  464. $res=array();
  465. dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
  466. $resql=$this->db->query($sql);
  467. if ($resql)
  468. {
  469. $num = $this->db->num_rows($resql);
  470. $i = 0; $j = 0;
  471. while ($i < $num)
  472. {
  473. $row = $this->db->fetch_row($resql);
  474. $j = $row[0] * 1;
  475. $result[$j] = $row[1];
  476. $i++;
  477. }
  478. $this->db->free($resql);
  479. }
  480. else dol_print_error($this->db);
  481. for ($i = 1 ; $i < 13 ; $i++)
  482. {
  483. $res[$i] = (isset($result[$i])?$result[$i]:0);
  484. }
  485. $data = array();
  486. for ($i = 1 ; $i < 13 ; $i++)
  487. {
  488. $month='unknown';
  489. if ($format == 0) $month=$langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i));
  490. elseif ($format == 1) $month=$i;
  491. elseif ($format == 2) $month=$langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i));
  492. //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b"));
  493. //$month=dol_substr($month,0,3);
  494. $data[$i-1] = array($month, $res[$i]);
  495. }
  496. return $data;
  497. }
  498. /**
  499. * Return number or total of product refs
  500. *
  501. * @param string $sql SQL
  502. * @param int $limit Limit
  503. * @return array
  504. */
  505. function _getAllByProduct($sql, $limit=10)
  506. {
  507. global $langs;
  508. $result=array();
  509. $res=array();
  510. dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
  511. $resql=$this->db->query($sql);
  512. if ($resql)
  513. {
  514. $num = $this->db->num_rows($resql);
  515. $i = 0; $other=0;
  516. while ($i < $num)
  517. {
  518. $row = $this->db->fetch_row($resql);
  519. if ($i < $limit || $num == $limit) $result[$i] = array($row[0],$row[1]); // Ref of product, nb
  520. else $other += $row[1];
  521. $i++;
  522. }
  523. if ($num > $limit) $result[$i] = array($langs->transnoentitiesnoconv("Other"),$other);
  524. $this->db->free($resql);
  525. }
  526. else dol_print_error($this->db);
  527. return $result;
  528. }
  529. }