diff --git a/class/digiriskstats.php b/class/digiriskstats.php
new file mode 100644
index 000000000..3a7e8b855
--- /dev/null
+++ b/class/digiriskstats.php
@@ -0,0 +1,593 @@
+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * \file class/digiriskstats.class.php
+ * \ingroup digiriskdolibarr
+ * \brief Common class to manage statistics reports
+ */
+
+/**
+ * Parent class of statistics class
+ */
+abstract class DigiriskStats
+{
+ protected $db;
+ protected $lastfetchdate = array(); // Dates of cache file read by methods
+ public $cachefilesuffix = ''; // Suffix to add to name of cache file (to avoid file name conflicts)
+
+ /**
+ * Return nb of elements by month for several years
+ *
+ * @param int $endyear Start year
+ * @param int $startyear End year
+ * @param int $cachedelay Delay we accept for cache file (0=No read, no save of cache, -1=No read but save)
+ * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month
+ * @param int $startmonth month of the fiscal year start min 1 max 12 ; if 1 = january
+ * @return array Array of values
+ */
+ public function getNbByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0, $format = 0, $startmonth = 1)
+ {
+ global $conf, $user, $langs;
+
+ if ($startyear > $endyear) {
+ return -1;
+ }
+
+ $datay = array();
+
+ // Search into cache
+ if (!empty($cachedelay)) {
+ include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+ include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
+ }
+
+ $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache';
+ $newmask = '0644';
+
+ $nowgmt = dol_now();
+
+ $foundintocache = 0;
+ if ($cachedelay > 0) {
+ $filedate = dol_filemtime($newpathofdestfile);
+ if ($filedate >= ($nowgmt - $cachedelay)) {
+ $foundintocache = 1;
+
+ $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate;
+ } else {
+ 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.");
+ }
+ }
+ // Load file into $data
+ if ($foundintocache) { // Cache file found and is not too old
+ dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
+ $data = json_decode(file_get_contents($newpathofdestfile), true);
+ } else {
+ $year = $startyear;
+ $sm = $startmonth - 1;
+ if ($sm != 0) {
+ $year = $year - 1;
+ }
+ while ($year <= $endyear) {
+ $datay[$year] = $this->getNbByMonth($year, $format);
+ $year++;
+ }
+
+ $data = array();
+
+ for ($i = 0; $i < 12; $i++) {
+ $data[$i][] = $datay[$endyear][($i + $sm) % 12][0];
+ $year = $startyear;
+ while ($year <= $endyear) {
+ $data[$i][] = $datay[$year][($i + $sm) % 12][1];
+ $year++;
+ }
+ }
+
+ }
+
+ // Save cache file
+ if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1)) {
+ dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
+ if (!dol_is_dir($conf->user->dir_temp)) {
+ dol_mkdir($conf->user->dir_temp);
+ }
+ $fp = fopen($newpathofdestfile, 'w');
+ fwrite($fp, json_encode($data));
+ fclose($fp);
+ if (!empty($conf->global->MAIN_UMASK)) {
+ $newmask = $conf->global->MAIN_UMASK;
+ }
+ @chmod($newpathofdestfile, octdec($newmask));
+
+ $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt;
+ }
+
+ // return array(array('Month',val1,val2,val3),...)
+ return $data;
+ }
+
+ /**
+ * Return amount of elements by month for several years.
+ * Criterias used to build request are defined into the constructor of parent class into xxx/class/xxxstats.class.php
+ * The caller of class can add more filters into sql request by adding criteris into the $stats->where property just after
+ * calling constructor.
+ *
+ * @param int $endyear Start year
+ * @param int $startyear End year
+ * @param int $cachedelay Delay we accept for cache file (0=No read, no save of cache, -1=No read but save)
+ * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month
+ * @param int $startmonth month of the fiscal year start min 1 max 12 ; if 1 = january
+ * @return array Array of values
+ */
+ public function getAmountByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0, $format = 0, $startmonth = 1)
+ {
+ global $conf, $user, $langs;
+
+ if ($startyear > $endyear) {
+ return -1;
+ }
+
+ $datay = array();
+
+ // Search into cache
+ if (!empty($cachedelay)) {
+ include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+ include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
+ }
+
+ $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache';
+ $newmask = '0644';
+
+ $nowgmt = dol_now();
+
+ $foundintocache = 0;
+ if ($cachedelay > 0) {
+ $filedate = dol_filemtime($newpathofdestfile);
+ if ($filedate >= ($nowgmt - $cachedelay)) {
+ $foundintocache = 1;
+
+ $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate;
+ } else {
+ 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.");
+ }
+ }
+
+ // Load file into $data
+ if ($foundintocache) { // Cache file found and is not too old
+ dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
+ $data = json_decode(file_get_contents($newpathofdestfile), true);
+ } else {
+ $year = $startyear;
+ $sm = $startmonth - 1;
+ if ($sm != 0) {
+ $year = $year - 1;
+ }
+ while ($year <= $endyear) {
+ $datay[$year] = $this->getAmountByMonth($year, $format);
+ $year++;
+ }
+
+ $data = array();
+ // $data = array('xval'=>array(0=>xlabel,1=>yval1,2=>yval2...),...)
+ for ($i = 0; $i < 12; $i++) {
+ $data[$i][] = isset($datay[$endyear][($i + $sm) % 12]['label']) ? $datay[$endyear][($i + $sm) % 12]['label'] : $datay[$endyear][($i + $sm) % 12][0]; // set label
+ $year = $startyear;
+ while ($year <= $endyear) {
+ $data[$i][] = $datay[$year][($i + $sm) % 12][1]; // set yval for x=i
+ $year++;
+ }
+ }
+ }
+
+ // Save cache file
+ if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1)) {
+ dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
+ if (!dol_is_dir($conf->user->dir_temp)) {
+ dol_mkdir($conf->user->dir_temp);
+ }
+ $fp = fopen($newpathofdestfile, 'w');
+ if ($fp) {
+ fwrite($fp, json_encode($data));
+ fclose($fp);
+ if (!empty($conf->global->MAIN_UMASK)) {
+ $newmask = $conf->global->MAIN_UMASK;
+ }
+ @chmod($newpathofdestfile, octdec($newmask));
+ } else {
+ dol_syslog("Failed to write cache file", LOG_ERR);
+ }
+ $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Return average of entity by month for several years
+ *
+ * @param int $endyear Start year
+ * @param int $startyear End year
+ * @param int $cachedelay Delay we accept for cache file (0=No read, no save of cache, -1=No read but save)
+ * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month
+ * @param int $startmonth month of the fiscal year start min 1 max 12 ; if 1 = january
+ * @return array Array of values
+ */
+ public function getAverageByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0, $format = 0, $startmonth = 1)
+ {
+ global $conf, $user, $langs;
+
+ if ($startyear > $endyear) {
+ return -1;
+ }
+
+ $datay = array();
+
+ // Search into cache
+ if (!empty($cachedelay)) {
+ include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+ include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
+ }
+
+ $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache';
+ $newmask = '0644';
+
+ $nowgmt = dol_now();
+
+ $foundintocache = 0;
+ if ($cachedelay > 0) {
+ $filedate = dol_filemtime($newpathofdestfile);
+ if ($filedate >= ($nowgmt - $cachedelay)) {
+ $foundintocache = 1;
+
+ $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate;
+ } else {
+ 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.");
+ }
+ }
+
+ // Load file into $data
+ if ($foundintocache) { // Cache file found and is not too old
+ dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
+ $data = json_decode(file_get_contents($newpathofdestfile), true);
+ } else {
+ $year = $startyear;
+ $sm = $startmonth - 1;
+ if ($sm != 0) {
+ $year = $year - 1;
+ }
+ while ($year <= $endyear) {
+ $datay[$year] = $this->getAverageByMonth($year);
+ $year++;
+ }
+
+ $data = array();
+
+ for ($i = 0; $i < 12; $i++) {
+ $data[$i][] = $datay[$endyear][($i + $sm) % 12][0];
+ $year = $startyear;
+ while ($year <= $endyear) {
+ $data[$i][] = $datay[$year][($i + $sm) % 12][1];
+ $year++;
+ }
+ }
+ }
+
+ // Save cache file
+ if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1)) {
+ dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
+ if (!dol_is_dir($conf->user->dir_temp)) {
+ dol_mkdir($conf->user->dir_temp);
+ }
+ $fp = fopen($newpathofdestfile, 'w');
+ if ($fp) {
+ fwrite($fp, json_encode($data));
+ fclose($fp);
+ if (!empty($conf->global->MAIN_UMASK)) {
+ $newmask = $conf->global->MAIN_UMASK;
+ }
+ @chmod($newpathofdestfile, octdec($newmask));
+ } else {
+ dol_syslog("Failed to write cache file", LOG_ERR);
+ }
+ $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt;
+ }
+
+ return $data;
+ }
+
+ // Here we have low level of shared code called by XxxStats.class.php
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ /**
+ * Return nb of elements by year
+ *
+ * @param string $sql SQL request
+ * @return array
+ */
+ protected function _getNbByYear($sql)
+ {
+ // phpcs:enable
+ $result = array();
+
+ dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ $num = $this->db->num_rows($resql);
+ $i = 0;
+ while ($i < $num) {
+ $row = $this->db->fetch_row($resql);
+ $result[$i] = $row;
+ $i++;
+ }
+ $this->db->free($resql);
+ } else {
+ dol_print_error($this->db);
+ }
+ return $result;
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ /**
+ * Return nb of elements, total amount and avg amount each year
+ *
+ * @param string $sql SQL request
+ * @return array Array with nb, total amount, average for each year
+ */
+ protected function _getAllByYear($sql)
+ {
+ // phpcs:enable
+ $result = array();
+
+ dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ $num = $this->db->num_rows($resql);
+ $i = 0;
+ while ($i < $num) {
+ $row = $this->db->fetch_object($resql);
+ $result[$i]['year'] = $row->year;
+ $result[$i]['nb'] = $row->nb;
+ if ($i > 0 && $row->nb > 0) {
+ $result[$i - 1]['nb_diff'] = ($result[$i - 1]['nb'] - $row->nb) / $row->nb * 100;
+ }
+ $result[$i]['total'] = $row->total;
+ if ($i > 0 && $row->total > 0) {
+ $result[$i - 1]['total_diff'] = ($result[$i - 1]['total'] - $row->total) / $row->total * 100;
+ }
+ $result[$i]['avg'] = $row->avg;
+ if ($i > 0 && $row->avg > 0) {
+ $result[$i - 1]['avg_diff'] = ($result[$i - 1]['avg'] - $row->avg) / $row->avg * 100;
+ }
+ // For some $sql only
+ if (isset($row->weighted)) {
+ $result[$i]['weighted'] = $row->weighted;
+ if ($i > 0 && $row->weighted > 0) {
+ $result[$i - 1]['avg_weighted'] = ($result[$i - 1]['weighted'] - $row->weighted) / $row->weighted * 100;
+ }
+ }
+ $i++;
+ }
+ $this->db->free($resql);
+ } else {
+ dol_print_error($this->db);
+ }
+ return $result;
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ /**
+ * Renvoie le nombre de documents par mois pour une annee donnee
+ * Return number of documents per month for a given year
+ *
+ * @param int $year Year
+ * @param string $sql SQL
+ * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month
+ * @return array Array of nb each month
+ */
+ protected function _getNbByMonth($year, $sql, $format = 0)
+ {
+ // phpcs:enable
+ global $langs;
+
+ $result = array();
+ $res = array();
+
+ dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ $num = $this->db->num_rows($resql);
+ $i = 0;
+ $j = 0;
+ while ($i < $num) {
+ $row = $this->db->fetch_row($resql);
+ $j = $row[0] * 1;
+ $result[$j] = $row[1];
+ $i++;
+ }
+ $this->db->free($resql);
+ } else {
+ dol_print_error($this->db);
+ }
+
+ for ($i = 1; $i < 13; $i++) {
+ $res[$i] = (isset($result[$i]) ? $result[$i] : 0);
+ }
+
+ $data = array();
+
+ for ($i = 1; $i < 13; $i++) {
+ $month = 'unknown';
+ if ($format == 0) {
+ $month = $langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i));
+ } elseif ($format == 1) {
+ $month = $i;
+ } elseif ($format == 2) {
+ $month = $langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i));
+ }
+ //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b"));
+ //$month=dol_substr($month,0,3);
+ $data[$i - 1] = array($month, $res[$i]);
+ }
+
+ return $data;
+ }
+
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ /**
+ * Return the amount per month for a given year
+ *
+ * @param int $year Year
+ * @param string $sql SQL
+ * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month
+ * @return array
+ */
+ protected function _getAmountByMonth($year, $sql, $format = 0)
+ {
+ // phpcs:enable
+ global $langs;
+
+ $result = array();
+ $res = array();
+
+ dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
+
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ $num = $this->db->num_rows($resql);
+ $i = 0;
+ while ($i < $num) {
+ $row = $this->db->fetch_row($resql);
+ $j = $row[0] * 1;
+ $result[$j] = $row[1];
+ $i++;
+ }
+ $this->db->free($resql);
+ } else {
+ dol_print_error($this->db);
+ }
+
+ for ($i = 1; $i < 13; $i++) {
+ $res[$i] = (int) round((isset($result[$i]) ? $result[$i] : 0));
+ }
+
+ $data = array();
+
+ for ($i = 1; $i < 13; $i++) {
+ $month = 'unknown';
+ if ($format == 0) {
+ $month = $langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i));
+ } elseif ($format == 1) {
+ $month = $i;
+ } elseif ($format == 2) {
+ $month = $langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i));
+ }
+ //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b"));
+ //$month=dol_substr($month,0,3);
+ $data[$i - 1] = array($month, $res[$i]);
+ }
+
+ return $data;
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ /**
+ * Renvoie le montant moyen par mois pour une annee donnee
+ * Return the amount average par month for a given year
+ *
+ * @param int $year Year
+ * @param string $sql SQL
+ * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month
+ * @return array
+ */
+ protected function _getAverageByMonth($year, $sql, $format = 0)
+ {
+ // phpcs:enable
+ global $langs;
+
+ $result = array();
+ $res = array();
+
+ dol_syslog(get_class($this).'::'.__FUNCTION__."", LOG_DEBUG);
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ $num = $this->db->num_rows($resql);
+ $i = 0;
+ $j = 0;
+ while ($i < $num) {
+ $row = $this->db->fetch_row($resql);
+ $j = $row[0] * 1;
+ $result[$j] = $row[1];
+ $i++;
+ }
+ $this->db->free($resql);
+ } else {
+ dol_print_error($this->db);
+ }
+
+ for ($i = 1; $i < 13; $i++) {
+ $res[$i] = (isset($result[$i]) ? $result[$i] : 0);
+ }
+
+ $data = array();
+
+ for ($i = 1; $i < 13; $i++) {
+ $month = 'unknown';
+ if ($format == 0) {
+ $month = $langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i));
+ } elseif ($format == 1) {
+ $month = $i;
+ } elseif ($format == 2) {
+ $month = $langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i));
+ }
+ //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b"));
+ //$month=dol_substr($month,0,3);
+ $data[$i - 1] = array($month, $res[$i]);
+ }
+
+ return $data;
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ /**
+ * Returns the summed amounts per year for a given number of past years ending now
+ * @param string $sql SQL
+ * @return array
+ */
+ protected function _getAmountByYear($sql)
+ {
+ $result = array();
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ $num = $this->db->num_rows($resql);
+ $i = 0;
+ while ($i < $num) {
+ $row = $this->db->fetch_row($resql);
+ $j = (int) $row[0];
+ $result[] = [
+ 0 => (int) $row[0],
+ 1 => (int) $row[1],
+ ];
+ $i++;
+ }
+ $this->db->free($resql);
+ }
+ return $result;
+ }
+}
+
diff --git a/class/ticketdigiriskstats.class.php b/class/ticketdigiriskstats.class.php
new file mode 100644
index 000000000..8aea386b1
--- /dev/null
+++ b/class/ticketdigiriskstats.class.php
@@ -0,0 +1,146 @@
+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * \file class/ticketdigiriskstats.class.php
+ * \ingroup digiriskdolibarr
+ * \brief Fichier de la classe de gestion des stats des tickets
+ */
+
+include_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php';
+include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
+
+require_once __DIR__ . '/digiriskstats.php';
+
+/**
+ * Class to manage stats for tickets
+ */
+class TicketDigiriskStats extends DigiriskStats
+{
+ /**
+ * @var string Name of table without prefix where object is stored
+ */
+ public $table_element;
+
+ public $socid;
+ public $userid;
+
+ public $from;
+ public $field;
+ public $where;
+ public $join;
+
+ /**
+ * Constructor
+ *
+ * @param DoliDB $db Database handler
+ * @param int $socid Id third party for filter. This value must be forced during the new to external user company if user is an external user.
+ * @param int $userid Id user for filter (creation user)
+ * @param int $digiriskelementid Id digiriskelement for filter
+ * @param int $categticketid Id category of ticket for filter
+ */
+ public function __construct($db, $socid, $userid = 0, $digiriskelementid = 0, $categticketid = 0)
+ {
+ $this->db = $db;
+ $this->socid = ($socid > 0 ? $socid : 0);
+ $this->userid = $userid;
+ $this->join = '';
+
+ $object = new Ticket($this->db);
+ $this->from = MAIN_DB_PREFIX.$object->table_element." as tk";
+ $this->where = "tk.fk_statut >= 0";
+ $this->where .= " AND tk.entity IN (".getEntity('ticket').")";
+ if ($this->socid) {
+ $this->where .= " AND tk.fk_soc = ".((int) $this->socid);
+ }
+ if (is_array($this->userid) && count($this->userid) > 0) {
+ $this->where .= ' AND fk_user_create IN ('.$this->db->sanitize(join(',', $this->userid)).')';
+ } elseif ($this->userid > 0) {
+ $this->where .= " AND fk_user_create = ".((int) $this->userid);
+ }
+
+ if ($digiriskelementid) {
+ $this->join .= ' LEFT JOIN '.MAIN_DB_PREFIX.'ticket_extrafields as tkextra ON tk.rowid = tkextra.fk_object';
+ $this->join .= ' LEFT JOIN '.MAIN_DB_PREFIX.'digiriskdolibarr_digiriskelement as e ON tkextra.digiriskdolibarr_ticket_service = e.rowid';
+ $this->where .= ' AND e.rowid = '.((int) $digiriskelementid);
+ }
+
+ if ($categticketid) {
+ $this->join .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_ticket as ctk ON ctk.fk_ticket = tk.rowid';
+ $this->join .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie as c ON c.rowid = ctk.fk_categorie';
+ $this->where .= ' AND c.rowid = '.((int) $categticketid);
+ }
+ }
+
+ /**
+ * Return ticket number by month for a year
+ *
+ * @param int $year Year to scan
+ * @param int $format 0=Label of abscissa is a translated text, 1=Label of abscissa is month number, 2=Label of abscissa is first letter of month
+ * @return array Array of values
+ */
+ public function getNbByMonth($year, $format = 0)
+ {
+ $sql = "SELECT date_format(tk.datec,'%m') as dc, COUNT(*) as nb";
+ $sql .= " FROM ".$this->from;
+ $sql .= $this->join;
+ $sql .= " WHERE tk.datec BETWEEN '".$this->db->idate(dol_get_first_day($year))."' AND '".$this->db->idate(dol_get_last_day($year))."'";
+ $sql .= " AND ".$this->where;
+ $sql .= " GROUP BY dc";
+ $sql .= $this->db->order('dc', 'DESC');
+
+ $res = $this->_getNbByMonth($year, $sql, $format);
+
+ return $res;
+ }
+
+
+ /**
+ * Return ticket number per year
+ *
+ * @return array Array with number by year
+ */
+ public function getNbByYear()
+ {
+ $sql = "SELECT date_format(tk.datec,'%Y') as dc, COUNT(*), SUM(c.".$this->field.")";
+ $sql .= " FROM ".$this->from;
+ $sql .= $this->join;
+ $sql .= " WHERE ".$this->where;
+ $sql .= " GROUP BY dc";
+ $sql .= $this->db->order('dc', 'DESC');
+
+ return $this->_getNbByYear($sql);
+ }
+
+ /**
+ * Return nb, total and average
+ *
+ * @return array Array of values
+ */
+ public function getAllByYear()
+ {
+ $sql = "SELECT date_format(tk.datec,'%Y') as year, COUNT(*) as nb";
+ $sql .= " FROM ".$this->from;
+ $sql .= $this->join;
+ $sql .= " WHERE ".$this->where;
+ $sql .= " GROUP BY year";
+ $sql .= $this->db->order('year', 'DESC');
+
+ return $this->_getAllByYear($sql);
+ }
+}
+
diff --git a/core/modules/modDigiriskDolibarr.class.php b/core/modules/modDigiriskDolibarr.class.php
index 135a95332..4863568cf 100644
--- a/core/modules/modDigiriskDolibarr.class.php
+++ b/core/modules/modDigiriskDolibarr.class.php
@@ -1275,8 +1275,8 @@ public function __construct($db)
'titre' => $langs->transnoentities('DashBoard'),
'prefix' => $pictoDigirisk,
'mainmenu' => 'ticket',
- 'leftmenu' => 'dashboardticket',
- 'url' => '/digiriskdolibarr/view/dashboard_ticket.php',
+ 'leftmenu' => 'ticketstats',
+ 'url' => '/digiriskdolibarr/view/ticketstats.php',
'langs' => 'digiriskdolibarr@digiriskdolibarr', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
'position' => 48520 + $r,
'enabled' => '$conf->digiriskdolibarr->enabled && $conf->ticket->enabled', // Define condition to show or hide menu entry. Use '$conf->digiriskdolibarr->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
@@ -1285,6 +1285,22 @@ public function __construct($db)
'user' => 0, // 0=Menu for internal users, 1=external users, 2=both
);
+// $this->menu[$r++] = array(
+// 'fk_menu' => 'fk_mainmenu=ticket', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
+// 'type' => 'left', // This is a Left menu entry
+// 'titre' => $langs->transnoentities('DashBoard'),
+// 'prefix' => $pictoDigirisk,
+// 'mainmenu' => 'ticket',
+// 'leftmenu' => 'dashboardticket',
+// 'url' => '/digiriskdolibarr/view/dashboard_ticket.php',
+// 'langs' => 'digiriskdolibarr@digiriskdolibarr', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
+// 'position' => 48520 + $r,
+// 'enabled' => '$conf->digiriskdolibarr->enabled && $conf->ticket->enabled', // Define condition to show or hide menu entry. Use '$conf->digiriskdolibarr->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
+// 'perms' => '$user->rights->ticket->read && $user->rights->digiriskdolibarr->lire', // Use 'perms'=>'$user->rights->digiriskdolibarr->level1->level2' if you want your menu with a permission rules
+// 'target' => '',
+// 'user' => 0, // 0=Menu for internal users, 1=external users, 2=both
+// );
+
// Exports profiles provided by this module
$r = 1;
diff --git a/langs/fr_FR/digiriskdolibarr.lang b/langs/fr_FR/digiriskdolibarr.lang
index 780e6f290..c1553c9ce 100644
--- a/langs/fr_FR/digiriskdolibarr.lang
+++ b/langs/fr_FR/digiriskdolibarr.lang
@@ -1428,6 +1428,7 @@ TicketCreationSubject = Création de ticket via l'interface publique de Digirisk
TicketDocumentCustomDigiriskTemplate = ODT custom des fiches de tickets
HowToSetupDigiriskElement = Pour configurer les éléments de Digirisk (groupements/unités de travail), veuillez cliquer sur ce lien :
ConfigDigiriskElement = Configuration des éléments de Digirisk
+TicketStatistics = Statistiques des tickets
diff --git a/view/ticketstats.php b/view/ticketstats.php
new file mode 100644
index 000000000..f306d6250
--- /dev/null
+++ b/view/ticketstats.php
@@ -0,0 +1,268 @@
+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * \file view/ticketstats.php
+ * \ingroup digiriskdolibarr
+ * \brief Page with tickets statistics
+ */
+
+// Load Dolibarr environment
+$res = 0;
+// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
+if ( ! $res && ! empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"] . "/main.inc.php";
+// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
+$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1;
+while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; }
+if ( ! $res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1)) . "/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1)) . "/main.inc.php";
+if ( ! $res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1))) . "/main.inc.php";
+// Try main.inc.php using relative path
+if ( ! $res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
+if ( ! $res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
+if ( ! $res) die("Include of main fails");
+
+// Global variables definitions
+global $conf, $db, $langs, $user;
+
+// Libraries
+require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php';
+if (!empty($conf->category->enabled)) {
+ require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
+}
+
+require_once __DIR__ . '/../class/ticketdigiriskstats.class.php';
+require_once __DIR__ . '/../class/digiriskelement.class.php';
+
+$WIDTH = DolGraph::getDefaultGraphSizeForStats('width');
+$HEIGHT = DolGraph::getDefaultGraphSizeForStats('height');
+
+// Load translation files required by the page
+$langs->loadLangs(array('orders', 'companies', 'other', 'tickets', 'categories'));
+
+if (!$user->rights->ticket->read) {
+ accessforbidden();
+}
+
+$object_status = GETPOST('object_status', 'intcomma');
+$userid = GETPOST('userid', 'int');
+$socid = GETPOST('socid', 'int');
+$digiriskelementid = GETPOST('digiriskelementid', 'int');
+$categticketid = GETPOST('categticketid', 'int');
+$ticketcats = GETPOST('ticketcats', 'array');
+
+// Initialize technical objects
+$object = new Ticket($db);
+$digiriskelement = new DigiriskElement($db);
+
+// Security check
+if ($user->socid > 0) {
+ $action = '';
+ $socid = $user->socid;
+}
+
+$nowyear = strftime("%Y", dol_now());
+$year = GETPOST('year') > 0 ? GETPOST('year', 'int') : $nowyear;
+$startyear = $year - (empty($conf->global->MAIN_STATS_GRAPHS_SHOW_N_YEARS) ? 2 : max(1, min(10, $conf->global->MAIN_STATS_GRAPHS_SHOW_N_YEARS)));
+$endyear = $year;
+
+/*
+ * View
+ */
+
+$form = new Form($db);
+
+$title = $langs->trans("TicketStatistics");
+$dir = $conf->ticket->dir_temp;
+
+llxHeader('', $title);
+
+print load_fiche_titre($title, '', 'ticket');
+
+dol_mkdir($dir);
+
+$stats = new TicketDigiriskStats($db, $socid, ($userid > 0 ? $userid: 0), ($digiriskelementid > 0 ? $digiriskelementid : 0), ($categticketid > 0 ? $categticketid: 0));
+if ($object_status != '' && $object_status >= 0) {
+ $stats->where .= ' AND tk.fk_statut IN ('.$db->sanitize($db->escape($object_status)).')';
+}
+if (is_array($ticketcats) && !empty($ticketcats)) {
+ $stats->from .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_ticket as cattk ON (tk.rowid = cattk.fk_ticket)';
+ $stats->where .= ' AND cattk.fk_categorie IN ('.$db->sanitize(implode(',', $ticketcats)).')';
+}
+
+// Build graphic number of object
+$data = $stats->getNbByMonthWithPrevYear($endyear, $startyear, 0, 0, $conf->global->SOCIETE_FISCAL_MONTH_START);
+
+if (empty($user->rights->societe->client->voir) || $user->socid) {
+ $filenamenb = $dir.'/ticketdigiriskstatsnbinyear-'.$user->id.'-'.$year.'.png';
+ $fileurlnb = DOL_URL_ROOT.'/viewimage.php?modulepart=ticketdigiriskstats&file=ticketdigiriskstatsnbinyear-'.$user->id.'-'.$year.'.png';
+} else {
+ $filenamenb = $dir.'/ticketdigiriskstatsnbinyear-'.$year.'.png';
+ $fileurlnb = DOL_URL_ROOT.'/viewimage.php?modulepart=ticketdigiriskstats&file=ticketdigiriskstatsnbinyear-'.$year.'.png';
+}
+
+$px1 = new DolGraph();
+$mesg = $px1->isGraphKo();
+if (!$mesg) {
+ $px1->SetData($data);
+ $i = $startyear; $legend = array();
+ while ($i <= $endyear) {
+ $legend[] = $i;
+ $i++;
+ }
+ $px1->SetLegend($legend);
+ $px1->SetMaxValue($px1->GetCeilMaxValue());
+ $px1->SetMinValue(min(0, $px1->GetFloorMinValue()));
+ $px1->SetWidth($WIDTH);
+ $px1->SetHeight($HEIGHT);
+ $px1->SetYLabel($langs->trans("NbOfTicket"));
+ $px1->SetShading(3);
+ $px1->SetHorizTickIncrement(1);
+ $px1->mode = 'depth';
+ $px1->SetTitle($langs->trans("NumberOfTicketsByMonth"));
+
+ $px1->draw($filenamenb, $fileurlnb);
+}
+
+// Show array
+$data = $stats->getAllByYear();
+$arrayyears = array();
+foreach ($data as $val) {
+ if (!empty($val['year'])) {
+ $arrayyears[$val['year']] = $val['year'];
+ }
+}
+if (!count($arrayyears)) {
+ $arrayyears[$nowyear] = $nowyear;
+}
+
+$h = 0;
+$head = array();
+$head[$h][0] = DOL_URL_ROOT.'/custom/digiriskdolibarr/view/ticketstats.php';
+$head[$h][1] = $langs->trans("ByMonthYear");
+$head[$h][2] = 'byyear';
+$h++;
+
+complete_head_from_modules($conf, $langs, null, $head, $h, 'ticket');
+
+print dol_get_fiche_head($head, 'byyear', $langs->trans("TicketStatistics"), -1);
+
+print '
';
+
+// Show filter box
+print '
';
+
+print '
';
+
+print '
';
+
+print '
';
+
+// Show graphs
+print '
';
+if ($mesg) {
+ print $mesg;
+} else {
+ print $px1->show();
+}
+print ' |
';
+
+print '
';
+print '';
+
+print dol_get_fiche_end();
+
+// End of page
+llxFooter();
+$db->close();