dolibarr 22.0.5
customreports.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2020-2024 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
4 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
47'
48@phan-var-force ?int[] $toselect
49';
50
51// Initialise values
52$search_groupby = array();
53$tabfamily = null;
54$objecttype = null;
55
56if (!defined('USE_CUSTOM_REPORT_AS_INCLUDE')) {
57 require '../main.inc.php';
58
59 // Get parameters
60 $action = GETPOST('action', 'aZ09') ? GETPOST('action', 'aZ09') : 'view'; // The action 'add', 'create', 'edit', 'update', 'view', ...
61 $massaction = GETPOST('massaction', 'alpha'); // The bulk action (combo box choice into lists)
62
63 $mode = GETPOST('mode', 'alpha');
64 $objecttype = (string) GETPOST('objecttype', 'aZ09arobase');
65 $tabfamily = GETPOST('tabfamily', 'aZ09');
66
67 $search_measures = GETPOST('search_measures', 'array');
68
69 //$search_xaxis = GETPOST('search_xaxis', 'array');
70 if (GETPOST('search_xaxis', 'alpha') && GETPOST('search_xaxis', 'alpha') != '-1') {
71 $search_xaxis = array(GETPOST('search_xaxis', 'alpha'));
72 }
73 //$search_groupby = GETPOST('search_groupby', 'array');
74 if (GETPOST('search_groupby', 'alpha') && GETPOST('search_groupby', 'alpha') != '-1') {
75 $search_groupby = array(GETPOST('search_groupby', 'alpha'));
76 }
77
78 '@phan-var-force string[] $search_groupby';
79
80 $search_yaxis = GETPOST('search_yaxis', 'array');
81 $search_graph = (string) GETPOST('search_graph', 'restricthtml');
82
83 // Load variable for pagination
84 $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit;
85 $sortfield = GETPOST('sortfield', 'aZ09comma');
86 $sortorder = GETPOST('sortorder', 'aZ09comma');
87 $page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page");
88 if (empty($page) || $page == -1 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha') || (empty($toselect) && $massaction === '0')) {
89 $page = 0;
90 } // If $page is not defined, or '' or -1 or if we click on clear filters or if we select empty mass action
91 $offset = $limit * $page;
92 $pageprev = $page - 1;
93 $pagenext = $page + 1;
94
95 $object = null;
96} else {
97 // When included into a main page
98 '
99 @phan-var-force int<0,1> $SHOWLEGEND
100 @phan-var-force string customreportkey
101 ';
102
103 // $search_measures, $search_xaxis or $search_yaxis may have been defined by the parent.
104
105 if (empty($user) || empty($user->id)) {
106 print 'Page is called as an include but $user and its permission loaded with loadRights() are not defined. We stop here.';
107 exit(-1);
108 }
109 if (empty($object)) {
110 print 'Page is called as an include but $object is not defined. We stop here.';
111 exit(-1);
112 }
113}
114
115// In customreport context, we force the protection to avoid forging of criteria including bind SQL injection
116$conf->global->MAIN_DISALLOW_UNSECURED_SELECT_INTO_EXTRAFIELDS_FILTER = 1;
117
118if (empty($mode)) {
119 $mode = 'graph';
120}
121if (!isset($search_measures)) {
122 $search_measures = array(0 => 't.count');
123}
124if (!isset($search_xaxis)) {
125 // Ensure value is set and not null.
126 $search_xaxis = array();
127}
128if (!isset($search_graph)) {
129 // Ensure value is set and not null
130 $search_graph = '';
131}
132if (!empty($object)) {
133 $objecttype = $object->element.($object->module ? '@'.$object->module : '');
134}
135if ((!is_string($objecttype) || empty($objecttype)) && isModEnabled('societe')) {
136 $objecttype = 'thirdparty';
137}
138'@phan-var-force string $objecttype'; // Help phan that suggests $objecttype can be null
139
140require_once DOL_DOCUMENT_ROOT."/core/class/extrafields.class.php";
141require_once DOL_DOCUMENT_ROOT."/core/class/html.form.class.php";
142require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
143require_once DOL_DOCUMENT_ROOT."/core/lib/company.lib.php";
144require_once DOL_DOCUMENT_ROOT."/core/lib/date.lib.php";
145require_once DOL_DOCUMENT_ROOT."/core/lib/customreports.lib.php";
146require_once DOL_DOCUMENT_ROOT."/core/class/dolgraph.class.php";
147require_once DOL_DOCUMENT_ROOT."/core/class/doleditor.class.php";
148require_once DOL_DOCUMENT_ROOT."/core/class/html.formother.class.php";
149
150// Load traductions files requiredby by page
151$langs->loadLangs(array("companies", "other", "exports", "sendings"));
152
153$extrafields = new ExtraFields($db);
154
155$hookmanager->initHooks(array('customreport')); // Note that conf->hooks_modules contains array
156
157$title = '';
158$picto = '';
159$errormessage = null;
160$keyforlabeloffield = null;
161$head = array();
162$ObjectClassName = '';
163// Objects available by default
164$arrayoftype = array(
165 'thirdparty' => array('label' => 'ThirdParties', 'picto' => 'company', 'ObjectClassName' => 'Societe', 'enabled' => isModEnabled('societe'), 'ClassPath' => "/societe/class/societe.class.php", 'langs' => 'companies'),
166 'contact' => array('label' => 'Contacts', 'picto' => 'contact', 'ObjectClassName' => 'Contact', 'enabled' => isModEnabled('societe'), 'ClassPath' => "/contact/class/contact.class.php"),
167 'proposal' => array('label' => 'Proposals', 'picto' => 'proposal', 'ObjectClassName' => 'Propal', 'enabled' => isModEnabled('propal'), 'ClassPath' => "/comm/propal/class/propal.class.php", 'langs' => 'propal'),
168 'order' => array('label' => 'Orders', 'picto' => 'order', 'ObjectClassName' => 'Commande', 'enabled' => isModEnabled('order'), 'ClassPath' => "/commande/class/commande.class.php", 'langs' => 'orders'),
169 'orderdet' => array('label' => 'SaleOrderLines', 'picto' => 'order', 'ObjectClassName' => 'OrderLine', 'enabled' => isModEnabled('order'), 'ClassPath' => "/commande/class/orderline.class.php", 'langs' => 'orders'),
170 'invoice' => array('label' => 'Invoices', 'picto' => 'bill', 'ObjectClassName' => 'Facture', 'enabled' => isModEnabled('invoice'), 'ClassPath' => "/compta/facture/class/facture.class.php", 'langs' => 'bills'),
171 'invoice_template' => array('label' => 'PredefinedInvoices', 'picto' => 'bill', 'ObjectClassName' => 'FactureRec', 'enabled' => isModEnabled('invoice'), 'ClassPath' => "/compta/facture/class/facture-rec.class.php", 'langs' => 'bills'),
172 'contract' => array('label' => 'Contracts', 'picto' => 'contract', 'ObjectClassName' => 'Contrat', 'enabled' => isModEnabled('contract'), 'ClassPath' => "/contrat/class/contrat.class.php", 'langs' => 'contracts'),
173 'contractdet' => array('label' => 'ContractLines', 'picto' => 'contract', 'ObjectClassName' => 'ContratLigne', 'enabled' => isModEnabled('contract'), 'ClassPath' => "/contrat/class/contrat.class.php", 'langs' => 'contracts'),
174 'bom' => array('label' => 'BOM', 'picto' => 'bom', 'ObjectClassName' => 'Bom', 'enabled' => isModEnabled('bom')),
175 'mrp' => array('label' => 'MO', 'picto' => 'mrp', 'ObjectClassName' => 'Mo', 'enabled' => isModEnabled('mrp'), 'ClassPath' => "/mrp/class/mo.class.php"),
176 'ticket' => array('label' => 'Ticket', 'picto' => 'ticket', 'ObjectClassName' => 'Ticket', 'enabled' => isModEnabled('ticket')),
177 'member' => array('label' => 'Adherent', 'picto' => 'member', 'ObjectClassName' => 'Adherent', 'enabled' => isModEnabled('member'), 'ClassPath' => "/adherents/class/adherent.class.php", 'langs' => 'members'),
178 'cotisation' => array('label' => 'Subscriptions', 'picto' => 'member', 'ObjectClassName' => 'Subscription', 'enabled' => isModEnabled('member'), 'ClassPath' => "/adherents/class/subscription.class.php", 'langs' => 'members'),
179);
180
181
182// Complete $arrayoftype by external modules
183$parameters = array('objecttype' => $objecttype, 'tabfamily' => $tabfamily);
184// @phan-suppress-next-line PhanTypeMismatchArgumentNullable
185$reshook = $hookmanager->executeHooks('loadDataForCustomReports', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
186if ($reshook < 0) {
187 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
188} elseif (is_array($hookmanager->resArray)) {
189 if (!empty($hookmanager->resArray['title'])) { // Add entries for tabs
190 $title = $hookmanager->resArray['title'];
191 }
192 if (!empty($hookmanager->resArray['picto'])) { // Add entries for tabs
193 $picto = $hookmanager->resArray['picto'];
194 }
195 if (!empty($hookmanager->resArray['head'])) { // Add entries for tabs
196 $head = array_merge($head, $hookmanager->resArray['head']);
197 }
198 if (!empty($hookmanager->resArray['arrayoftype'])) { // Add entries from hook
199 foreach ($hookmanager->resArray['arrayoftype'] as $key => $val) {
200 $arrayoftype[$key] = $val;
201 }
202 }
203}
204
205// Load the main $object for statistics
206if ($objecttype) {
207 try {
208 if (!empty($arrayoftype[$objecttype]['ClassPath'])) {
209 $fileforclass = $arrayoftype[$objecttype]['ClassPath'];
210 } else {
211 $fileforclass = "/".$objecttype."/class/".$objecttype.".class.php";
212 }
213 $ObjectClassName = null;
214
215 if ($fileforclass !== null) {
216 dol_include_once($fileforclass);
217
218 $ObjectClassName = $arrayoftype[$objecttype]['ObjectClassName'];
219 }
220 if (!empty($ObjectClassName)) {
221 if (class_exists($ObjectClassName)) {
222 $object = new $ObjectClassName($db);
223 } else {
224 print 'Failed to load class for type '.$objecttype.'. Class file found but Class object named '.$ObjectClassName.' not found.';
225 }
226 } else {
227 print 'Failed to load class for type '.$objecttype.'. Class file name is unknown.';
228 }
229 } catch (Exception $e) {
230 print 'Failed to load class for type '.$objecttype.'. Class path not found.';
231 }
232}
233
234'@phan-var-force CommonObject $object';
235
236// Security check
237$socid = 0;
238if ($user->socid > 0) { // Protection if external user
239 //$socid = $user->socid;
240 accessforbidden('Access forbidden to external users');
241}
242
243// Fetch optionals attributes and labels
244$extrafields->fetch_name_optionals_label('all'); // We load all extrafields definitions for all objects
245//$extrafields->fetch_name_optionals_label($object->table_element_line);
246
247if (!empty($object->table_element)) {
248 $search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_');
249} else {
250 $search_array_options = array();
251}
252
253$search_component_params = array('');
254$search_component_params_hidden = trim(GETPOST('search_component_params_hidden', 'alphanohtml'));
255$search_component_params_input = trim(GETPOST('search_component_params_input', 'alphanohtml'));
256//var_dump($search_component_params_hidden);
257//var_dump($search_component_params_input);
258
259// If string is not an universal filter string, we try to convert it into universal filter syntax string
260$errorstr = '';
261forgeSQLFromUniversalSearchCriteria($search_component_params_input, $errorstr); // Try conversion UFS->SQL
262//var_dump($errorstr);
263if ($errorstr) {
264 $value = $search_component_params_input;
265
266 $value = preg_replace('/([a-z\.]+)\s*([!<>=]+|in|notin|like|notlike)\s*/', '\1:\2:', $value); // Clean string 'x < 10' into 'x:<:10' so we can then explode on space to get all AND tests to do
267 $value = preg_replace('/\s*\|\s*/', '|', $value);
268 //var_dump($value);
269
270 $crits = explode(' ', trim($value)); // the string after the name of the field. Explode on each AND
271 $res = '';
272
273 $i1 = 0; // count the nb of and criteria added (all fields / criteria)
274 foreach ($crits as $crit) { // Loop on each AND criteria
275 $crit = trim($crit);
276
277 $i2 = 0; // count the nb of valid criteria added for this first criteria
278 $newres = '';
279 $tmpcrits = explode('|', $crit);
280 $i3 = 0; // count the nb of valid criteria added for this current field
281 foreach ($tmpcrits as $tmpcrit) {
282 if ($tmpcrit !== '0' && empty($tmpcrit)) {
283 continue;
284 }
285 $tmpcrit = trim($tmpcrit);
286 //var_dump($tmpcrit);
287
288 $errorstr = '';
289 $parenthesislevel = 0;
290 $rescheckfilter = dolCheckFilters($tmpcrit, $errorstr, $parenthesislevel);
291 if ($rescheckfilter) {
292 while ($parenthesislevel > 0) {
293 $tmpcrit = preg_replace('/^\‍(/', '', preg_replace('/\‍)$/', '', $tmpcrit));
294 $parenthesislevel--;
295 }
296 }
297
298 $field = preg_replace('/(:[!<>=\s]+:|:in:|:notin:|:like:|:notlike:).*$/', '', $tmpcrit); // the name of the field
299 $tmpcrit = preg_replace('/^.*(:[!<>=\s]+:|:in:|:notin:|:like:|:notlike:)/', '\1', $tmpcrit); // the condition after the name of the field
300 //var_dump($field); var_dump($tmpcrit); var_dump($i3);
301
302 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
303
304 $operator = '=';
305 $newcrit = preg_replace('/(:[!<>=\s]+:|:in:|:notin:|:like:|:notlike:)/', '', $tmpcrit);
306 //var_dump($newcrit);
307
308 $reg = array();
309 preg_match('/:([!<>=\s]+|in|notin|like|notlike):/', $tmpcrit, $reg);
310 if (!empty($reg[1])) {
311 $operator = $reg[1];
312 }
313 if ($newcrit != '') {
314 if (!preg_match('/^\'[^\']*\'$/', $newcrit)) {
315 $numnewcrit = price2num($newcrit);
316 $newres .= '('.$field.':'.$operator.':'.((float) $numnewcrit).')';
317 } else {
318 $newres .= '('.$field.':'.$operator.":".((string) $newcrit).')';
319 }
320 $i3++; // a criteria was added to string
321 }
322 }
323 $i2++; // a criteria for 1 more field was added to string
324
325 if ($newres) {
326 $res = $res.($res ? ' AND ' : '').($i2 > 1 ? '(' : '').$newres.($i2 > 1 ? ')' : '');
327 }
328 $i1++;
329 }
330 $res = "(".$res.")";
331
332 //var_dump($res);exit;
333 $search_component_params_input = $res;
334}
335
336$arrayofandtagshidden = dolForgeExplodeAnd($search_component_params_hidden);
337$arrayofandtagsinput = dolForgeExplodeAnd($search_component_params_input);
338
339$search_component_params_hidden = implode(' AND ', array_merge($arrayofandtagshidden, $arrayofandtagsinput));
340//var_dump($search_component_params_hidden);
341
342$MAXUNIQUEVALFORGROUP = 20;
343$MAXMEASURESINBARGRAPH = 20;
344$SHOWLEGEND = (isset($SHOWLEGEND) ? $SHOWLEGEND : 1);
345
346$YYYY = substr($langs->trans("Year"), 0, 1).substr($langs->trans("Year"), 0, 1).substr($langs->trans("Year"), 0, 1).substr($langs->trans("Year"), 0, 1);
347$MM = substr($langs->trans("Month"), 0, 1).substr($langs->trans("Month"), 0, 1);
348$DD = substr($langs->trans("Day"), 0, 1).substr($langs->trans("Day"), 0, 1);
349$HH = substr($langs->trans("Hour"), 0, 1).substr($langs->trans("Hour"), 0, 1);
350$MI = substr($langs->trans("Minute"), 0, 1).substr($langs->trans("Minute"), 0, 1);
351$SS = substr($langs->trans("Second"), 0, 1).substr($langs->trans("Second"), 0, 1);
352
353$arrayoffilterfields = array();
354$arrayofmesures = array();
355$arrayofxaxis = array();
356$arrayofgroupby = array();
357$arrayofyaxis = array();
358$arrayofvaluesforgroupby = array();
359
360if (!empty($object->element)) {
361 $features = $object->element;
362} else {
363 $features = '';
364}
365if (!empty($object->element_for_permission)) {
366 $features = $object->element_for_permission;
367} else {
368 $features .= (empty($object->module) ? '' : '@'.$object->module);
369}
370
371// Security check
372restrictedArea($user, $features, 0, '');
373
374
375/*
376 * Actions
377 */
378
379// None
380
381
382
383/*
384 * View
385 */
386
387$form = new Form($db);
388$formother = new FormOther($db);
389
390if (!defined('USE_CUSTOM_REPORT_AS_INCLUDE')) {
391 llxHeader('', $langs->transnoentitiesnoconv('CustomReports'), '');
392
393 if (empty($head)) {
394 print dol_get_fiche_head($head, 'customreports', $title, -2, $picto);
395 } else {
396 print dol_get_fiche_head($head, 'customreports', $title, -1, $picto);
397 }
398}
399
400// Define $newarrayoftype that is array of object available for report
401$newarrayoftype = array();
402foreach ($arrayoftype as $key => $val) {
403 if (dol_eval((string) $val['enabled'], 1, 1, '1')) {
404 $newarrayoftype[$key] = $arrayoftype[$key];
405 }
406 if (!empty($val['langs'])) {
407 $langs->load($val['langs']);
408 }
409}
410
411$count = 0;
412$label = '';
413if (array_key_exists($objecttype, $newarrayoftype)) {
414 $label = $langs->trans($newarrayoftype[$objecttype]['label']);
415}
416$arrayoffilterfields = fillArrayOfFilterFields($object, 't', $label, $arrayoffilterfields, 0, $count);
417$arrayoffilterfields = dol_sort_array($arrayoffilterfields, 'position', 'asc', 0, 0, 1);
418
419$count = 0;
420$arrayofmesures = fillArrayOfMeasures($object, 't', $label, $arrayofmesures, 0, $count);
421$arrayofmesures = dol_sort_array($arrayofmesures, 'position', 'asc', 0, 0, 1);
422
423$count = 0;
424$arrayofxaxis = fillArrayOfXAxis($object, 't', $label, $arrayofxaxis, 0, $count);
425$arrayofxaxis = dol_sort_array($arrayofxaxis, 'position', 'asc', 0, 0, 1);
426
427$count = 0;
428$arrayofgroupby = fillArrayOfGroupBy($object, 't', $label, $arrayofgroupby, 0, $count);
429$arrayofgroupby = dol_sort_array($arrayofgroupby, 'position', 'asc', 0, 0, 1);
430
431
432// Check parameters
433if ($action == 'viewgraph') {
434 if (!count($search_measures)) {
435 setEventMessages($langs->trans("AtLeastOneMeasureIsRequired"), null, 'warnings');
436 } elseif ($mode == 'graph' && is_array($search_xaxis) && count($search_xaxis) > 1) {
437 setEventMessages($langs->trans("OnlyOneFieldForXAxisIsPossible"), null, 'warnings');
438 $search_xaxis = array(0 => $search_xaxis[0]);
439 }
440 if (count($search_groupby) >= 2) {
441 setEventMessages($langs->trans("ErrorOnlyOneFieldForGroupByIsPossible"), null, 'warnings');
442 $search_groupby = array(0 => $search_groupby[0]);
443 }
444 if (!count($search_xaxis)) {
445 setEventMessages($langs->trans("AtLeastOneXAxisIsRequired"), null, 'warnings');
446 } elseif ($mode == 'graph' && $search_graph == 'bars' && count($search_measures) > $MAXMEASURESINBARGRAPH) {
447 $langs->load("errors");
448 setEventMessages($langs->trans("GraphInBarsAreLimitedToNMeasures", $MAXMEASURESINBARGRAPH), null, 'warnings');
449 $search_graph = 'lines';
450 }
451}
452
453// Get all possible values of fields when a 'group by' is set, and save this into $arrayofvaluesforgroupby
454// $arrayofvaluesforgroupby will be used to forge lael of each grouped series
455if (count($search_groupby)) {
456 $fieldtocount = '';
457 foreach ($search_groupby as $gkey => $gval) {
458 $gvalwithoutprefix = preg_replace('/^[a-z]+\./i', '', $gval);
459 $gvalsanitized = preg_replace('/[^a-z0-9\._\-]+/i', '', $gval);
460
461 if (preg_match('/\-year$/', $gvalsanitized)) {
462 $tmpval = preg_replace('/\-year$/', '', $gvalsanitized);
463 $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y')";
464 } elseif (preg_match('/\-month$/', $gvalsanitized)) {
465 $tmpval = preg_replace('/\-month$/', '', $gvalsanitized);
466 $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m')";
467 } elseif (preg_match('/\-day$/', $gvalsanitized)) {
468 $tmpval = preg_replace('/\-day$/', '', $gvalsanitized);
469 $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m-%d')";
470 } else {
471 $fieldtocount = $gvalsanitized;
472 }
473
474 $sql = "SELECT DISTINCT ".$fieldtocount." as val";
475
476 if (strpos($fieldtocount, 'te') === 0) {
477 $tabletouse = $object->table_element;
478 $tablealiastouse = 'te';
479 if (!empty($arrayofgroupby[$gval])) {
480 $tmpval = explode('.', $gval);
481 $tabletouse = $arrayofgroupby[$gval]['table'];
482 $tablealiastouse = $tmpval[0];
483 }
484 //var_dump($tablealiastouse);exit;
485
486 //$sql .= " FROM ".MAIN_DB_PREFIX.$object->table_element."_extrafields as te";
487 $sql .= " FROM ".MAIN_DB_PREFIX.$tabletouse."_extrafields as ".$tablealiastouse;
488 } else {
489 $tabletouse = $object->table_element;
490 $tablealiastouse = 't';
491 if (!empty($arrayofgroupby[$gval])) {
492 $tmpval = explode('.', $gval);
493 $tabletouse = $arrayofgroupby[$gval]['table'];
494 $tablealiastouse = $tmpval[0];
495 }
496 $sql .= " FROM ".MAIN_DB_PREFIX.$tabletouse." as ".$tablealiastouse;
497 }
498
499 // Add a where here keeping only the criteria on $tabletouse
500 /* TODO
501 if ($search_component_params_hidden) {
502 $errormessage = '';
503 $sql .= forgeSQLFromUniversalSearchCriteria($search_component_params_hidden, $errormessage);
504 }
505 */
506
507 $sql .= " LIMIT ".((int) ($MAXUNIQUEVALFORGROUP + 1));
508
509 //print $sql;
510 $resql = $db->query($sql);
511 if (!$resql) {
512 dol_print_error($db);
513 }
514
515 while ($obj = $db->fetch_object($resql)) {
516 if (is_null($obj->val)) {
517 $keytouse = '__NULL__';
518 $valuetranslated = $langs->transnoentitiesnoconv("NotDefined");
519 } elseif ($obj->val === '') {
520 $keytouse = '';
521 $valuetranslated = $langs->transnoentitiesnoconv("Empty");
522 } else {
523 $keytouse = (string) $obj->val;
524 $valuetranslated = $obj->val;
525 }
526
527 $regs = array();
528 if (isset($object->fields[$gvalwithoutprefix])) {
529 if (!empty($object->fields[$gvalwithoutprefix]['arrayofkeyval'])) {
530 $valuetranslated = $object->fields[$gvalwithoutprefix]['arrayofkeyval'][$obj->val];
531 if (is_null($valuetranslated)) {
532 $valuetranslated = $langs->transnoentitiesnoconv("UndefinedKey");
533 }
534 $valuetranslated = $langs->trans($valuetranslated);
535 } elseif (preg_match('/integer:([^:]+):([^:]+)$/', $object->fields[$gvalwithoutprefix]['type'], $regs)) {
536 $classname = $regs[1];
537 $classpath = $regs[2];
538 dol_include_once($classpath);
539 if (class_exists($classname)) {
540 $tmpobject = new $classname($db);
541 '@phan-var-force CommonObject $tmpobject';
542 $tmpobject->fetch($obj->val);
543 foreach ($tmpobject->fields as $fieldkey => $field) {
544 if ($field['showoncombobox']) {
545 $valuetranslated = $tmpobject->$fieldkey;
546 //if ($valuetranslated == '-') $valuetranslated = $langs->transnoentitiesnoconv("Unknown")
547 break;
548 }
549 }
550 //$valuetranslated = $tmpobject->ref.'eee';
551 }
552 }
553 }
554
555 $arrayofvaluesforgroupby['g_'.$gkey][$keytouse] = $valuetranslated;
556 }
557 // Add also the possible NULL value if field is a parent field that is not a strict join
558 $tmpfield = explode('.', $gval);
559 if ($tmpfield[0] != 't' || (isset($object->fields[$tmpfield[1]]) && is_array($object->fields[$tmpfield[1]]) && empty($object->fields[$tmpfield[1]]['notnull']))) {
560 dol_syslog("The group by field ".$gval." may be null (because field is null or it is a left join), so we add __NULL__ entry in list of possible values");
561 //var_dump($gval); var_dump($object->fields);
562 $arrayofvaluesforgroupby['g_'.$gkey]['__NULL__'] = $langs->transnoentitiesnoconv("NotDefined");
563 }
564
565 if (is_array($arrayofvaluesforgroupby['g_'.$gkey])) {
566 asort($arrayofvaluesforgroupby['g_'.$gkey]);
567 }
568
569 // Add a protection/error to refuse the request if number of differentr values for the group by is higher than $MAXUNIQUEVALFORGROUP
570 if (is_array($arrayofvaluesforgroupby['g_'.$gkey]) && count($arrayofvaluesforgroupby['g_'.$gkey]) > $MAXUNIQUEVALFORGROUP) {
571 $langs->load("errors");
572
573 if (strpos($fieldtocount, 'te') === 0) { // This is a field of an extrafield
574 //if (!empty($extrafields->attributes[$object->table_element]['langfile'][$gvalwithoutprefix])) {
575 // $langs->load($extrafields->attributes[$object->table_element]['langfile'][$gvalwithoutprefix]);
576 //}
577 $keyforlabeloffield = $extrafields->attributes[$object->table_element]['label'][$gvalwithoutprefix];
578 $labeloffield = $langs->transnoentitiesnoconv($keyforlabeloffield);
579 } elseif (strpos($fieldtocount, 't__') === 0) { // This is a field of a foreign key
580 $reg = array();
581 if (preg_match('/^(.*)\.(.*)/', $gvalwithoutprefix, $reg)) {
582 /*
583 $gvalwithoutprefix = preg_replace('/\..*$/', '', $gvalwithoutprefix);
584 $gvalwithoutprefix = preg_replace('/^t__/', '', $gvalwithoutprefix);
585 $keyforlabeloffield = $object->fields[$gvalwithoutprefix]['label'];
586 $labeloffield = $langs->transnoentitiesnoconv($keyforlabeloffield).'-'.$reg[2];
587 */
588 $labeloffield = $arrayofgroupby[$fieldtocount]['labelnohtml'];
589 } else {
590 $labeloffield = 'FK_ISSUE'; // $langs->transnoentitiesnoconv($keyforlabeloffield);
591 }
592 } else { // This is a common field
593 $reg = array();
594 if (preg_match('/^(.*)\-(year|month|day)/', $gvalwithoutprefix, $reg)) {
595 $gvalwithoutprefix = preg_replace('/\-(year|month|day)/', '', $gvalwithoutprefix);
596 $keyforlabeloffield = $object->fields[$gvalwithoutprefix]['label'];
597 $labeloffield = $langs->transnoentitiesnoconv($keyforlabeloffield).'-'.$reg[2];
598 } else {
599 $keyforlabeloffield = $object->fields[$gvalwithoutprefix]['label'];
600 $labeloffield = $langs->transnoentitiesnoconv($keyforlabeloffield);
601 }
602 }
603 //var_dump($labeloffield);
604 setEventMessages($langs->transnoentitiesnoconv("ErrorTooManyDifferentValueForSelectedGroupBy", (string) $MAXUNIQUEVALFORGROUP, (string) $labeloffield), null, 'warnings');
605 $search_groupby = array();
606 }
607
608 $db->free($resql);
609 }
610}
611//var_dump($arrayofvaluesforgroupby);exit;
612
613
614$tmparray = dol_getdate(dol_now());
615$endyear = $tmparray['year'];
616$endmonth = $tmparray['mon'];
617$datelastday = dol_get_last_day($endyear, $endmonth, 1);
618$startyear = $endyear - 2;
619
620$param = '';
621
622
623if (!defined('MAIN_CUSTOM_REPORT_KEEP_GRAPH_ONLY')) {
624 print '<form method="post" action="'.$_SERVER['PHP_SELF'].'" autocomplete="off">';
625 print '<input type="hidden" name="token" value="'.newToken().'">';
626 print '<input type="hidden" name="action" value="viewgraph">';
627 print '<input type="hidden" name="tabfamily" value="'.(string) $tabfamily.'">';
628
629 $viewmode = '';
630
631 $viewmode .= '<div class="divadvancedsearchfield">';
632 $arrayofgraphs = array('bars' => 'Bars', 'lines' => 'Lines'); // also 'pies'
633 $viewmode .= '<div class="inline-block opacitymedium"><span class="fas fa-chart-area paddingright" title="'.$langs->trans("Graph").'"></span>'.$langs->trans("Graph").'</div> ';
634 $viewmode .= $form->selectarray('search_graph', $arrayofgraphs, $search_graph, 0, 0, 0, '', 1, 0, 0, '', 'graphtype width100');
635 $viewmode .= '</div>';
636
637 $num = 0;
638 $massactionbutton = '';
639 $nav = '';
640 $newcardbutton = '';
641 $limit = 0;
642
643 print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, -1, 'object_action', 0, $nav.'<span class="marginleftonly"></span>'.$newcardbutton, '', $limit, 1, 0, 1, $viewmode);
644
645
646 foreach ($newarrayoftype as $tmpkey => $tmpval) {
647 $newarrayoftype[$tmpkey]['label'] = img_picto('', $tmpval['picto'], 'class="pictofixedwidth"').$langs->trans($tmpval['label']);
648 }
649
650 print '<div class="liste_titre liste_titre_bydiv liste_titre_bydiv_inlineblock liste_titre_bydiv_nothingafter centpercent">';
651
652 // Select object
653 print '<div class="divadvancedsearchfield center floatnone">';
654 print '<div class="inline-block"><span class="opacitymedium">'.$langs->trans("StatisticsOn").'</span></div> ';
655
656 print $form->selectarray('objecttype', $newarrayoftype, $objecttype, 0, 0, 0, '', 1, 0, 0, '', 'minwidth250', 1, '', 0, 1);
657 if (empty($conf->use_javascript_ajax)) {
658 print '<input type="submit" class="button buttongen button-save nomargintop" name="changeobjecttype" value="'.$langs->trans("Refresh").'">';
659 } else {
660 print '<!-- js code to reload page with good object type -->
661 <script nonce="'.getNonce().'" type="text/javascript">
662 jQuery(document).ready(function() {
663 jQuery("#objecttype").change(function() {
664 console.log("Reload for "+jQuery("#objecttype").val());
665 location.href = "'.$_SERVER["PHP_SELF"].'?objecttype="+jQuery("#objecttype").val()+"'.($tabfamily ? '&tabfamily='.urlencode($tabfamily) : '').(GETPOSTINT('show_search_component_params_hidden') ? '&show_search_component_params_hidden='.((int) GETPOSTINT('show_search_component_params_hidden')) : '').'";
666 });
667 });
668 </script>';
669 }
670 print '</div><div class="clearboth"></div>';
671
672 if (!empty($newarrayoftype)) {
673 // Filter (you can use param &show_search_component_params_hidden=1 for debug)
674 if (!empty($object)) {
675 print '<div class="divadvancedsearchfield">';
676 print $form->searchComponent(array($object->element => $object->fields), $search_component_params, array(), $search_component_params_hidden, $arrayoffilterfields);
677 print '</div>';
678 }
679
680 // YAxis (add measures into array)
681 $count = 0;
682 //var_dump($arrayofmesures);
683 print '<div class="divadvancedsearchfield clearboth">';
684 print '<div class="inline-block"><span class="fas fa-ruler-combined paddingright pictofixedwidth" title="'.dol_escape_htmltag($langs->trans("Measures")).'"></span><span class="fas fa-caret-left caretleftaxis" title="'.dol_escape_htmltag($langs->trans("Measures")).'"></span></div>';
685 $simplearrayofmesures = array();
686 foreach ($arrayofmesures as $key => $val) {
687 $simplearrayofmesures[$key] = $arrayofmesures[$key]['label'];
688 }
689 print $form->multiselectarray('search_measures', $simplearrayofmesures, $search_measures, 0, 0, 'minwidth300 widthcentpercentminusx', 1, 0, '', '', $langs->transnoentitiesnoconv("Measures")); // Fill the array $arrayofmeasures with possible fields
690 print '</div>';
691
692 // XAxis
693 $count = 0;
694 print '<div class="divadvancedsearchfield">';
695 print '<div class="inline-block"><span class="fas fa-ruler-combined paddingright pictofixedwidth" title="'.dol_escape_htmltag($langs->trans("XAxis")).'"></span><span class="fas fa-caret-down caretdownaxis" title="'.dol_escape_htmltag($langs->trans("XAxis")).'"></span></div>';
696 //var_dump($arrayofxaxis);
697 print $formother->selectXAxisField($object, $search_xaxis, $arrayofxaxis, $langs->trans("XAxis"), 'minwidth300 maxwidth400 widthcentpercentminusx'); // Fill the array $arrayofxaxis with possible fields
698 print '</div>';
699
700 // Group by
701 $count = 0;
702 print '<div class="divadvancedsearchfield">';
703 print '<div class="inline-block opacitymedium"><span class="fas fa-ruler-horizontal paddingright pictofixedwidth" title="'.dol_escape_htmltag($langs->trans("GroupBy")).'"></span></div>';
704 print $formother->selectGroupByField($object, $search_groupby, $arrayofgroupby, 'minwidth250 maxwidth300 widthcentpercentminusx', $langs->trans("GroupBy")); // Fill the array $arrayofgroupby with possible fields
705 print '</div>';
706 }
707
708 if ($mode == 'grid') {
709 // YAxis
710 print '<div class="divadvancedsearchfield">';
711 foreach ($object->fields as $key => $val) {
712 if (empty($val['measure']) && (!isset($val['enabled']) || dol_eval((string) $val['enabled'], 1, 1, '1'))) {
713 if (in_array($key, array('id', 'rowid', 'entity', 'last_main_doc', 'extraparams'))) {
714 continue;
715 }
716 if (preg_match('/^fk_/', $key)) {
717 continue;
718 }
719 if (in_array($val['type'], array('html', 'text'))) {
720 continue;
721 }
722 if (in_array($val['type'], array('timestamp', 'date', 'datetime'))) {
723 $arrayofyaxis['t.'.$key.'-year'] = array(
724 'label' => $langs->trans($val['label']).' ('.$YYYY.')',
725 'position' => $val['position'],
726 'table' => $object->table_element
727 );
728 $arrayofyaxis['t.'.$key.'-month'] = array(
729 'label' => $langs->trans($val['label']).' ('.$YYYY.'-'.$MM.')',
730 'position' => $val['position'],
731 'table' => $object->table_element
732 );
733 $arrayofyaxis['t.'.$key.'-day'] = array(
734 'label' => $langs->trans($val['label']).' ('.$YYYY.'-'.$MM.'-'.$DD.')',
735 'position' => $val['position'],
736 'table' => $object->table_element
737 );
738 } else {
739 $arrayofyaxis['t.'.$key] = array(
740 'label' => $val['label'],
741 'position' => (int) $val['position'],
742 'table' => $object->table_element
743 );
744 }
745 }
746 }
747 // Add measure from extrafields
748 if ($object->isextrafieldmanaged) {
749 foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) {
750 if (!empty($extrafields->attributes[$object->table_element]['totalizable'][$key]) && (!isset($extrafields->attributes[$object->table_element]['enabled'][$key]) || dol_eval((string) $extrafields->attributes[$object->table_element]['enabled'][$key], 1, 1, '1'))) {
751 $arrayofyaxis['te.'.$key] = array(
752 'label' => $extrafields->attributes[$object->table_element]['label'][$key],
753 'position' => (int) $extrafields->attributes[$object->table_element]['pos'][$key],
754 'table' => $object->table_element
755 );
756 }
757 }
758 }
759 $arrayofyaxis = dol_sort_array($arrayofyaxis, 'position');
760 $arrayofyaxislabel = array();
761 foreach ($arrayofyaxis as $key => $val) {
762 $arrayofyaxislabel[$key] = $val['label'];
763 }
764 print '<div class="inline-block opacitymedium"><span class="fas fa-ruler-vertical paddingright" title="'.$langs->trans("YAxis").'"></span>'.$langs->trans("YAxis").'</div> ';
765 print $form->multiselectarray('search_yaxis', $arrayofyaxislabel, $search_yaxis != null ? $search_yaxis : array(), 0, 0, 'minwidth100', 1);
766 print '</div>';
767 }
768
769 if (!empty($newarrayoftype)) {
770 print '<div class="divadvancedsearchfield">';
771 print '<input type="submit" class="button buttongen button-save nomargintop" value="'.$langs->trans("Refresh").'">';
772 print '</div>';
773 }
774
775 print '</div>';
776 print '</form>';
777}
778
779// Generate the SQL request
780$sql = '';
781if (!empty($search_measures) && !empty($search_xaxis)) {
782 $errormessage = '';
783
784 $fieldid = 'rowid';
785
786 $sql = "SELECT ";
787 foreach ($search_xaxis as $key => $val) {
788 if (preg_match('/\-year$/', $val)) {
789 $tmpval = preg_replace('/\-year$/', '', $val);
790 $sql .= "DATE_FORMAT(".$tmpval.", '%Y') as x_".$key.', ';
791 } elseif (preg_match('/\-month$/', $val)) {
792 $tmpval = preg_replace('/\-month$/', '', $val);
793 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m') as x_".$key.', ';
794 } elseif (preg_match('/\-day$/', $val)) {
795 $tmpval = preg_replace('/\-day$/', '', $val);
796 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d') as x_".$key.', ';
797 } else {
798 $sql .= $val." as x_".$key.", ";
799 }
800 }
801 if (!empty($search_groupby)) {
802 foreach ($search_groupby as $key => $val) {
803 if (preg_match('/\-year$/', $val)) {
804 $tmpval = preg_replace('/\-year$/', '', $val);
805 $sql .= "DATE_FORMAT(".$tmpval.", '%Y') as g_".$key.', ';
806 } elseif (preg_match('/\-month$/', $val)) {
807 $tmpval = preg_replace('/\-month$/', '', $val);
808 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m') as g_".$key.', ';
809 } elseif (preg_match('/\-day$/', $val)) {
810 $tmpval = preg_replace('/\-day$/', '', $val);
811 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d') as g_".$key.', ';
812 } else {
813 $sql .= $val." as g_".$key.", ";
814 }
815 }
816 }
817 foreach ($search_measures as $key => $val) {
818 if ($val == 't.count') {
819 $sql .= "COUNT(t.".$fieldid.") as y_".$key.', ';
820 } elseif (preg_match('/\-sum$/', $val)) {
821 $tmpval = preg_replace('/\-sum$/', '', $val);
822 $sql .= "SUM(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", ";
823 } elseif (preg_match('/\-average$/', $val)) {
824 $tmpval = preg_replace('/\-average$/', '', $val);
825 $sql .= "AVG(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", ";
826 } elseif (preg_match('/\-min$/', $val)) {
827 $tmpval = preg_replace('/\-min$/', '', $val);
828 $sql .= "MIN(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", ";
829 } elseif (preg_match('/\-max$/', $val)) {
830 $tmpval = preg_replace('/\-max$/', '', $val);
831 $sql .= "MAX(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", ";
832 } elseif (preg_match('/\-stddevpop$/', $val)) {
833 $tmpval = preg_replace('/\-stddevpop$/', '', $val);
834 $sql .= "STDDEV_POP(".$db->ifsql($tmpval.' IS NULL', '0', $tmpval).") as y_".$key.", ";
835 }
836 }
837 $sql = preg_replace('/,\s*$/', '', $sql);
838 $sql .= " FROM ".MAIN_DB_PREFIX.$object->table_element." as t";
839 // Add measure from extrafields
840 if ($object->isextrafieldmanaged) {
841 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$object->table_element."_extrafields as te ON te.fk_object = t.".$fieldid;
842 }
843 // Add table for link on multientity
844 if ($object->ismultientitymanaged) { // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
845 if ($object->ismultientitymanaged == 1) {
846 // No table to add here
847 } else {
848 $tmparray = explode('@', $object->ismultientitymanaged);
849 $sql .= " INNER JOIN ".MAIN_DB_PREFIX.$tmparray[1]." as parenttableforentity ON t.".$tmparray[0]." = parenttableforentity.rowid";
850 $sql .= " AND parenttableforentity.entity IN (".getEntity($tmparray[1]).")";
851 }
852 }
853
854 // Init the list of tables added. We include by default always the main table.
855 $listoftablesalreadyadded = array($object->table_element => $object->table_element);
856
857 // Add LEFT JOIN for all parent tables mentioned into the Xaxis
858 //var_dump($arrayofxaxis); var_dump($search_xaxis);
859 foreach ($search_xaxis as $key => $val) {
860 if (!empty($arrayofxaxis[$val])) {
861 $tmpval = explode('.', $val);
862 //var_dump($arrayofgroupby);
863 $tmpforloop = dolExplodeIntoArray($arrayofxaxis[$val]['tablefromt'], ',');
864 foreach ($tmpforloop as $tmptable => $tmptablealias) {
865 if (! in_array($tmptable, $listoftablesalreadyadded)) { // We do not add join for main table and tables already added
866 $tmpforexplode = explode('__', $tmptablealias);
867 $endpart = end($tmpforexplode);
868 $parenttableandfield = preg_replace('/__'.$endpart.'$/', '', $tmptablealias).'.'.$endpart;
869
870 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmptable." as ".$db->sanitize($tmptablealias)." ON ".$db->sanitize($parenttableandfield)." = ".$db->sanitize($tmptablealias).".rowid";
871 $listoftablesalreadyadded[$tmptable] = $tmptable;
872
873 if (preg_match('/^te/', $tmpval[0]) && preg_replace('/^t_/', 'te_', $tmptablealias) == $tmpval[0]) {
874 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmptable."_extrafields as ".$db->sanitize($tmpval[0])." ON ".$db->sanitize($tmpval[0]).".fk_object = ".$db->sanitize($tmptablealias).".rowid";
875 $listoftablesalreadyadded[$tmptable] = $tmptable;
876 }
877 }
878 }
879 } else {
880 $errormessage = 'Found a key into search_xaxis not found into arrayofxaxis';
881 }
882 }
883
884 // Add LEFT JOIN for all parent tables mentioned into the Group by
885 //var_dump($arrayofgroupby); var_dump($search_groupby);
886 foreach ($search_groupby as $key => $val) {
887 if (!empty($arrayofgroupby[$val])) {
888 $tmpval = explode('.', $val);
889 //var_dump($arrayofgroupby[$val]); var_dump($tmpval);
890 $tmpforloop = dolExplodeIntoArray($arrayofgroupby[$val]['tablefromt'], ',');
891 foreach ($tmpforloop as $tmptable => $tmptablealias) {
892 if (! in_array($tmptable, $listoftablesalreadyadded)) { // We do not add join for main table and tables already added
893 $tmpforexplode = explode('__', $tmptablealias);
894 $endpart = end($tmpforexplode);
895 $parenttableandfield = preg_replace('/__'.$endpart.'$/', '', $tmptablealias).'.'.$endpart;
896
897 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmptable." as ".$db->sanitize($tmptablealias)." ON ".$db->sanitize($parenttableandfield)." = ".$db->sanitize($tmptablealias).".rowid";
898 $listoftablesalreadyadded[$tmptable] = $tmptable;
899
900 if (preg_match('/^te/', $tmpval[0]) && preg_replace('/^t_/', 'te_', $tmptablealias) == $tmpval[0]) {
901 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmptable."_extrafields as ".$db->sanitize($tmpval[0])." ON ".$db->sanitize($tmpval[0]).".fk_object = ".$db->sanitize($tmptablealias).".rowid";
902 $listoftablesalreadyadded[$tmptable] = $tmptable;
903 }
904 }
905 }
906 } else {
907 $errormessage = 'Found a key into search_groupby not found into arrayofgroupby';
908 }
909 }
910
911 // Add LEFT JOIN for all parent tables mentioned into the Yaxis
912 //var_dump($arrayofgroupby); var_dump($search_groupby);
913 foreach ($search_measures as $key => $val) {
914 if (!empty($arrayofmesures[$val])) {
915 $tmpval = explode('.', $val);
916 //var_dump($arrayofgroupby);
917 $tmpforloop = dolExplodeIntoArray($arrayofmesures[$val]['tablefromt'], ',');
918 foreach ($tmpforloop as $tmptable => $tmptablealias) {
919 if (! in_array($tmptable, $listoftablesalreadyadded)) { // We do not add join for main table and tables already added
920 $tmpforexplode = explode('__', $tmptablealias);
921 $endpart = end($tmpforexplode);
922 $parenttableandfield = preg_replace('/__'.$endpart.'$/', '', $tmptablealias).'.'.$endpart;
923
924 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmptable." as ".$db->sanitize($tmptablealias)." ON ".$db->sanitize($parenttableandfield)." = ".$db->sanitize($tmptablealias).".rowid";
925 $listoftablesalreadyadded[$tmptable] = $tmptable;
926
927 if (preg_match('/^te/', $tmpval[0]) && preg_replace('/^t_/', 'te_', $tmptablealias) == $tmpval[0]) {
928 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmptable."_extrafields as ".$db->sanitize($tmpval[0])." ON ".$db->sanitize($tmpval[0]).".fk_object = ".$db->sanitize($tmptablealias).".rowid";
929 $listoftablesalreadyadded[$tmptable] = $tmptable;
930 }
931 }
932 }
933 } else {
934 $errormessage = 'Found a key into search_measures not found into arrayofmesures';
935 }
936 }
937
938 // Add LEFT JOIN for all tables mentioned into filter
939 if (!empty($search_component_params_hidden)) {
940 // Get all fields used into the filter
941 preg_match_all('/\b(t[\w]*_[\w]*)\.(\w+(-\w+)?)/', $search_component_params_hidden, $matches);
942 $fieldsUsedInFilter = array_unique($matches[0]);
943
944 // Remove fields used before to avoid double join
945 $fieldsToRemove = array_merge($search_measures, $search_groupby, $search_xaxis);
946 $fieldsUsedInFilter = array_diff($fieldsUsedInFilter, $fieldsToRemove);
947
948 foreach ($fieldsUsedInFilter as $key => $val) {
949 if (!empty($arrayoffilterfields[$val])) {
950 $tmpval = explode('.', $val);
951 $tmpforloop = dolExplodeIntoArray($arrayoffilterfields[$val]['tablefromt'], ',');
952 foreach ($tmpforloop as $tmptable => $tmptablealias) {
953 if (! in_array($tmptable, $listoftablesalreadyadded)) { // We do not add join for main table and tables already added
954 $tmpforexplode = explode('__', $tmptablealias);
955 $endpart = end($tmpforexplode);
956 $parenttableandfield = preg_replace('/__'.$endpart.'$/', '', $tmptablealias).'.'.$endpart;
957
958 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmptable." as ".$db->sanitize($tmptablealias)." ON ".$db->sanitize($parenttableandfield)." = ".$db->sanitize($tmptablealias).".rowid";
959 $listoftablesalreadyadded[$tmptable] = $tmptable;
960
961 if (preg_match('/^te/', $tmpval[0]) && preg_replace('/^t_/', 'te_', $tmptablealias) == $tmpval[0]) {
962 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmptable."_extrafields as ".$db->sanitize($tmpval[0])." ON ".$db->sanitize($tmpval[0]).".fk_object = ".$db->sanitize($tmptablealias).".rowid";
963 $listoftablesalreadyadded[$tmptable] = $tmptable;
964 }
965 }
966 }
967 } else {
968 $errormessage = 'Found a key into search_filterfields not found into arrayoffilterfields';
969 }
970 }
971 }
972
973 $sql .= " WHERE 1 = 1";
974 if ($object->ismultientitymanaged == 1) { // 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
975 $sql .= " AND t.entity IN (".getEntity($object->element).")";
976 }
977 // Add the where here
978 $sqlfilters = $search_component_params_hidden;
979 if ($sqlfilters) {
980 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage, 0, 0, 1);
981
982 // Replace date values by $db->idate(dol_mktime(...))
983 $sql = preg_replace_callback(
984 "/(\w+)\.(\w+)\s*(=|!=|<>|<|>|<=|>=)\s*'(\d{4})-(\d{2})-(\d{2})'/",
989 function (array $matches): string {
990 global $db;
991 $column = $matches[1] . '.' . $matches[2];
992 $operator = $matches[3];
993 $year = (int) $matches[4];
994 $month = (int) $matches[5];
995 $day = (int) $matches[6];
996
997 $startOfDay = $db->idate(dol_mktime(0, 0, 0, $month, $day, $year));
998 $endOfDay = $db->idate(dol_mktime(23, 59, 59, $month, $day, $year));
999
1000 switch ($operator) {
1001 case "=":
1002 return "($column >= '$startOfDay' AND $column <= '$endOfDay')";
1003 case "!=":
1004 case "<>":
1005 return "NOT ($column >= '$startOfDay' AND $column <= '$endOfDay')";
1006 case "<":
1007 return "$column < '$startOfDay'";
1008 case ">":
1009 return "$column > '$endOfDay'";
1010 case "<=":
1011 return "$column <= '$endOfDay'";
1012 case ">=":
1013 return "$column >= '$startOfDay'";
1014 default:
1015 return "";
1016 }
1017 },
1018 $sql
1019 );
1020 }
1021 $sql .= " GROUP BY ";
1022 foreach ($search_xaxis as $key => $val) {
1023 if (preg_match('/\-year$/', $val)) {
1024 $tmpval = preg_replace('/\-year$/', '', $val);
1025 $sql .= "DATE_FORMAT(".$tmpval.", '%Y'), ";
1026 } elseif (preg_match('/\-month$/', $val)) {
1027 $tmpval = preg_replace('/\-month$/', '', $val);
1028 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m'), ";
1029 } elseif (preg_match('/\-day$/', $val)) {
1030 $tmpval = preg_replace('/\-day$/', '', $val);
1031 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d'), ";
1032 } else {
1033 $sql .= $val.", ";
1034 }
1035 }
1036 if (!empty($search_groupby)) {
1037 foreach ($search_groupby as $key => $val) {
1038 if (preg_match('/\-year$/', $val)) {
1039 $tmpval = preg_replace('/\-year$/', '', $val);
1040 $sql .= "DATE_FORMAT(".$tmpval.", '%Y'), ";
1041 } elseif (preg_match('/\-month$/', $val)) {
1042 $tmpval = preg_replace('/\-month$/', '', $val);
1043 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m'), ";
1044 } elseif (preg_match('/\-day$/', $val)) {
1045 $tmpval = preg_replace('/\-day$/', '', $val);
1046 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d'), ";
1047 } else {
1048 $sql .= $val.', ';
1049 }
1050 }
1051 }
1052 $sql = preg_replace('/,\s*$/', '', $sql);
1053 $sql .= ' ORDER BY ';
1054 foreach ($search_xaxis as $key => $val) {
1055 if (preg_match('/\-year$/', $val)) {
1056 $tmpval = preg_replace('/\-year$/', '', $val);
1057 $sql .= "DATE_FORMAT(".$tmpval.", '%Y'), ";
1058 } elseif (preg_match('/\-month$/', $val)) {
1059 $tmpval = preg_replace('/\-month$/', '', $val);
1060 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m'), ";
1061 } elseif (preg_match('/\-day$/', $val)) {
1062 $tmpval = preg_replace('/\-day$/', '', $val);
1063 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d'), ";
1064 } else {
1065 $sql .= $val.', ';
1066 }
1067 }
1068 if (!empty($search_groupby)) {
1069 foreach ($search_groupby as $key => $val) {
1070 if (preg_match('/\-year$/', $val)) {
1071 $tmpval = preg_replace('/\-year$/', '', $val);
1072 $sql .= "DATE_FORMAT(".$tmpval.", '%Y'), ";
1073 } elseif (preg_match('/\-month$/', $val)) {
1074 $tmpval = preg_replace('/\-month$/', '', $val);
1075 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m'), ";
1076 } elseif (preg_match('/\-day$/', $val)) {
1077 $tmpval = preg_replace('/\-day$/', '', $val);
1078 $sql .= "DATE_FORMAT(".$tmpval.", '%Y-%m-%d'), ";
1079 } else {
1080 $sql .= $val.', ';
1081 }
1082 }
1083 }
1084 $sql = preg_replace('/,\s*$/', '', $sql);
1085
1086 // Can overwrite the SQL with a custom SQL string (when used as an include)
1087 if (!empty($customsql)) {
1088 $sql = $customsql;
1089 }
1090}
1091//print $sql;
1092
1093if ($errormessage) {
1094 print '<div class="warning">';
1095 print dol_escape_htmltag($errormessage);
1096 //print '<br>'.dol_escape_htmltag('SQL is '.$sql);
1097 print '</div>';
1098 $sql = '';
1099}
1100
1101$legend = array();
1102foreach ($search_measures as $key => $val) {
1103 $legend[] = $langs->trans($arrayofmesures[$val]['label']);
1104}
1105
1106$useagroupby = count($search_groupby);
1107//var_dump($useagroupby);
1108//var_dump($arrayofvaluesforgroupby);
1109
1110// Execute the SQL request
1111$totalnbofrecord = 0;
1112$data = array();
1113if ($sql) {
1114 $resql = $db->query($sql);
1115 if (!$resql) {
1116 print '<div class="warning">';
1117 print dol_escape_htmltag($db->lasterror());
1118 //print '<br>'.dol_escape_htmltag('SQL is '.$sql);
1119 print '</div>';
1120 } else {
1121 $ifetch = 0;
1122 $xi = 0;
1123 $oldlabeltouse = '';
1124 while ($obj = $db->fetch_object($resql)) {
1125 $ifetch++;
1126 if ($useagroupby) {
1127 $xval = $search_xaxis[0];
1128 $fieldforxkey = 'x_0';
1129 $xlabel = $obj->$fieldforxkey;
1130 $xvalwithoutprefix = preg_replace('/^[a-z]+\./', '', $xval);
1131
1132 // Define $xlabel
1133 if (!empty($object->fields[$xvalwithoutprefix]['arrayofkeyval'])) {
1134 $xlabel = $object->fields[$xvalwithoutprefix]['arrayofkeyval'][$obj->$fieldforxkey];
1135 }
1136 $labeltouse = (($xlabel || $xlabel == '0') ? dol_trunc($xlabel, 20, 'middle') : ($xlabel === '' ? $langs->transnoentitiesnoconv("Empty") : $langs->transnoentitiesnoconv("NotDefined")));
1137
1138 if ($oldlabeltouse !== '' && ($labeltouse != $oldlabeltouse)) {
1139 $xi++; // Increase $xi
1140 }
1141 //var_dump($labeltouse.' '.$oldlabeltouse.' '.$xi);
1142 $oldlabeltouse = $labeltouse;
1143
1144 /* Example of value for $arrayofvaluesforgroupby
1145 * array (size=1)
1146 * 'g_0' =>
1147 * array (size=6)
1148 * 0 => string '0' (length=1)
1149 * '' => string 'Empty' (length=5)
1150 * '__NULL__' => string 'Not defined' (length=11)
1151 * 'done' => string 'done' (length=4)
1152 * 'processing' => string 'processing' (length=10)
1153 * 'undeployed' => string 'undeployed' (length=10)
1154 */
1155 foreach ($search_measures as $key => $val) {
1156 $gi = 0;
1157 foreach ($search_groupby as $gkey => $gval) {
1158 //var_dump('*** Fetch #'.$ifetch.' for labeltouse='.$labeltouse.' measure number '.$key.' and group g_'.$gi);
1159 //var_dump($arrayofvaluesforgroupby);
1160 foreach ($arrayofvaluesforgroupby['g_'.$gi] as $gvaluepossiblekey => $gvaluepossiblelabel) {
1161 $ykeysuffix = $gvaluepossiblelabel;
1162 $gvalwithoutprefix = preg_replace('/^[a-z]+\./', '', $gval);
1163
1164 $fieldfory = 'y_'.$key;
1165 $fieldforg = 'g_'.$gi;
1166 $fieldforybis = 'y_'.$key.'_'.$ykeysuffix;
1167 //var_dump('gvaluepossiblekey='.$gvaluepossiblekey.' gvaluepossiblelabel='.$gvaluepossiblelabel.' ykeysuffix='.$ykeysuffix.' gval='.$gval.' gvalwithoutsuffix='.$gvalwithoutprefix);
1168 //var_dump('fieldforg='.$fieldforg.' obj->$fieldforg='.$obj->$fieldforg.' fieldfory='.$fieldfory.' obj->$fieldfory='.$obj->$fieldfory.' fieldforybis='.$fieldforybis);
1169
1170 if (!array_key_exists($xi, $data)) {
1171 $data[$xi] = array();
1172 }
1173
1174 if (!array_key_exists('label', $data[$xi])) {
1175 $data[$xi] = array();
1176 $data[$xi]['label'] = $labeltouse;
1177 }
1178
1179 $objfieldforg = $obj->$fieldforg;
1180 if (is_null($objfieldforg)) {
1181 $objfieldforg = '__NULL__';
1182 }
1183
1184 if ($gvaluepossiblekey == '0') { // $gvaluepossiblekey can have type int or string. So we create a special if, used when value is '0'
1185 //var_dump($objfieldforg.' == \'0\' -> '.($objfieldforg == '0'));
1186 if ($objfieldforg == '0') {
1187 // The record we fetch is for this group
1188 $data[$xi][$fieldforybis] = $obj->$fieldfory;
1189 } elseif (!isset($data[$xi][$fieldforybis])) {
1190 // The record we fetch is not for this group
1191 $data[$xi][$fieldforybis] = '0';
1192 }
1193 } else {
1194 //var_dump((string) $objfieldforg.' === '.(string) $gvaluepossiblekey.' -> '.((string) $objfieldforg === (string) $gvaluepossiblekey));
1195 if ((string) $objfieldforg === (string) $gvaluepossiblekey) {
1196 // The record we fetch is for this group
1197 $data[$xi][$fieldforybis] = $obj->$fieldfory;
1198 } elseif (!isset($data[$xi][$fieldforybis])) {
1199 // The record we fetch is not for this group
1200 $data[$xi][$fieldforybis] = '0';
1201 }
1202 }
1203 }
1204 //var_dump($data[$xi]);
1205 $gi++;
1206 }
1207 }
1208 } else { // No group by
1209 $xval = $search_xaxis[0];
1210 $fieldforxkey = 'x_0';
1211 $xlabel = $obj->$fieldforxkey;
1212 $xvalwithoutprefix = preg_replace('/^[a-z]+\./', '', $xval);
1213
1214 // Define $xlabel
1215 if (!empty($object->fields[$xvalwithoutprefix]['arrayofkeyval'])) {
1216 $xlabel = $object->fields[$xvalwithoutprefix]['arrayofkeyval'][$obj->$fieldforxkey];
1217 }
1218
1219 $labeltouse = (($xlabel || $xlabel == '0') ? dol_trunc($xlabel, 20, 'middle') : ($xlabel === '' ? $langs->transnoentitiesnoconv("Empty") : $langs->transnoentitiesnoconv("NotDefined")));
1220 $xarrayforallseries = array('label' => $labeltouse);
1221 foreach ($search_measures as $key => $val) {
1222 $fieldfory = 'y_'.$key;
1223 $xarrayforallseries[$fieldfory] = $obj->$fieldfory;
1224 }
1225 $data[$xi] = $xarrayforallseries;
1226 $xi++;
1227 }
1228 }
1229
1230 $totalnbofrecord = count($data);
1231 }
1232}
1233//var_dump($data);
1234
1235print '<!-- Section to show the result -->'."\n";
1236print '<div class="customreportsoutput'.($totalnbofrecord ? '' : ' customreportsoutputnotdata').'">';
1237
1238if (empty($newarrayoftype)) {
1239 $langs->load("admin");
1240 print info_admin($langs->trans("NoSupportedModulesHaveBeenActivated").' '.$langs->trans("YouCanEnableModulesFrom"), 0, 0, 'info');
1241}
1242
1243if ($mode == 'grid') {
1244 // TODO
1245}
1246
1247if ($mode == 'graph') {
1248 $WIDTH = '80%';
1249 $HEIGHT = (empty($_SESSION['dol_screenheight']) ? 400 : $_SESSION['dol_screenheight'] - 500);
1250
1251 // Show graph
1252 $px1 = new DolGraph();
1253 $mesg = $px1->isGraphKo();
1254 if (!$mesg) {
1255 //var_dump($legend);
1256 //var_dump($data);
1257 $px1->SetData($data);
1258 unset($data);
1259
1260 $arrayoftypes = array();
1261 foreach ($search_measures as $key => $val) {
1262 $arrayoftypes[] = $search_graph;
1263 }
1264
1265 $px1->SetLegend($legend);
1266 $px1->setShowLegend($SHOWLEGEND);
1267 $px1->SetMinValue((int) $px1->GetFloorMinValue());
1268 $px1->SetMaxValue($px1->GetCeilMaxValue());
1269 $px1->SetWidth($WIDTH);
1270 $px1->SetHeight($HEIGHT);
1271 $px1->SetYLabel($langs->trans("Y"));
1272 $px1->SetShading(3);
1273 $px1->SetHorizTickIncrement(1);
1274 $px1->SetCssPrefix("cssboxes");
1275 $px1->SetType($arrayoftypes);
1276 $px1->mode = 'depth';
1277 $px1->SetTitle('');
1278
1279 $dir = $conf->user->dir_temp;
1280 dol_mkdir($dir);
1281 // $customreportkey may be defined when using customreports.php as an include
1282 if (!empty($object->element)) {
1283 $filenamekey = $dir.'/customreport_'.$object->element.(empty($customreportkey) ? '' : $customreportkey).'.png';
1284 $fileurlkey = DOL_URL_ROOT.'/viewimage.php?modulepart=user&file=customreport_'.$object->element.(empty($customreportkey) ? '' : $customreportkey).'.png';
1285 }
1286
1287 if (isset($filenamekey) && isset($fileurlkey)) {
1288 $px1->draw($filenamekey, $fileurlkey);
1289 }
1290
1291 $texttoshow = $langs->trans("NoRecordFound");
1292 if (!GETPOSTISSET('search_measures') || !GETPOSTISSET('search_xaxis')) {
1293 $texttoshow = $langs->trans("SelectYourGraphOptionsFirst");
1294 }
1295
1296 print $px1->show($totalnbofrecord ? 0 : $texttoshow);
1297 }
1298}
1299
1300print '</div>';
1301
1302if ($sql && !defined('MAIN_CUSTOM_REPORT_KEEP_GRAPH_ONLY')) {
1303 // Show admin info
1304 print '<br>'.info_admin($langs->trans("SQLUsedForExport").':<br> '.$sql, 0, 0, '1', '', 'TechnicalInformation');
1305}
1306
1307
1308if (!defined('USE_CUSTOM_REPORT_AS_INCLUDE')) {
1309 print dol_get_fiche_end();
1310
1311 llxFooter();
1312 // End of page
1313
1314 $db->close();
1315}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class to build graphs.
Class to manage standard extra fields.
Class to manage generation of HTML components Only common components must be here.
Class permettant la generation de composants html autre Only common components are here.
fillArrayOfGroupBy($object, $tablealias, $labelofobject, &$arrayofgroupby, $level=0, &$count=0, &$tablepath='')
Fill arrayofgroupby for an object.
fillArrayOfFilterFields($object, $tablealias, $labelofobject, &$arrayoffields, $level=0, &$count=0, &$tablepath='')
Fill array of possible filter fields for an object.
fillArrayOfMeasures($object, $tablealias, $labelofobject, &$arrayofmesures, $level=0, &$count=0, &$tablepath='')
Fill arrayofmesures for an object.
fillArrayOfXAxis($object, $tablealias, $labelofobject, &$arrayofxaxis, $level=0, &$count=0, &$tablepath='')
Fill arrayofmesures for an object.
dol_get_last_day($year, $month=12, $gm=false)
Return GMT time for last day of a month or year.
Definition date.lib.php:619
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dolCheckFilters($sqlfilters, &$error='', &$parenthesislevel=0)
Return if a $sqlfilters parameter has a valid balance of parenthesis.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
dolForgeExplodeAnd($sqlfilters)
Explode an universal search string with AND parts.
dolExplodeIntoArray($string, $delimiter=';', $kv='=')
Split a string with 2 keys into key array.
print_barre_liste($title, $page, $file, $options='', $sortfield='', $sortorder='', $morehtmlcenter='', $num=-1, $totalnboflines='', $picto='generic', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limit=-1, $selectlimitsuffix=0, $hidenavigation=0, $pagenavastextinput=0, $morehtmlrightbeforearrow='')
Print a title with navigation controls for pagination.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
dol_now($mode='auto')
Return date for now.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
info_admin($text, $infoonimgalt=0, $nodiv=0, $admin='1', $morecss='hideonsmartphone', $textfordropdown='', $picto='')
Show information in HTML for admin users or standard users.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
restrictedArea(User $user, $features, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $isdraft=0, $mode=0)
Check permissions of a user to show a page and an object.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.