Issues (4069)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

include/SugarCharts/SugarChart.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2 1
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3
/*********************************************************************************
4
 * SugarCRM Community Edition is a customer relationship management program developed by
5
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6
7
 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
8
 * Copyright (C) 2011 - 2014 Salesagility Ltd.
9
 *
10
 * This program is free software; you can redistribute it and/or modify it under
11
 * the terms of the GNU Affero General Public License version 3 as published by the
12
 * Free Software Foundation with the addition of the following permission added
13
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
14
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
15
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License along with
23
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
24
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
 * 02110-1301 USA.
26
 *
27
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
28
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
29
 *
30
 * The interactive user interfaces in modified source and object code versions
31
 * of this program must display Appropriate Legal Notices, as required under
32
 * Section 5 of the GNU Affero General Public License version 3.
33
 *
34
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
35
 * these Appropriate Legal Notices must retain the display of the "Powered by
36
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
37
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
38
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
39
 ********************************************************************************/
40
41
42
/**
43
 * Generic chart
44
 * @api
45
 */
46
class SugarChart {
47
48
	private $db;
49
	protected $ss;
50
	var $forceHideDataGroupLink = false;
51
	var $data_set = array();
52
	var $display_data = array();
53
	var $chart_properties = array();
54
	var $chart_yAxis = array();
55
	var $group_by = array();
56
	var $super_set = array();
57
	var $colors_list = array();
58
	var $base_url = array();
59
	var $url_params = array();
60
	var $display_labels = array();
61
62
	var $currency_symbol;
63
	var $thousands_symbol;
64
	var $is_currency;
65
	var $supports_image_export = false;
66
	var $print_html_legend_pdf = false;
67
	var $image_export_type = "";
68
69 1
	public function __construct() {
70 1
		$this->db = DBManagerFactory::getInstance();
71 1
		$this->ss = new Sugar_Smarty();
72
73 1
		$this->chart_yAxis['yMin'] = 0;
74 1
		$this->chart_yAxis['yMax'] = 0;
75
76
77 1
		if ($GLOBALS['current_user']->getPreference('currency')){
78
79
            $currency = new Currency();
80
            $currency->retrieve($GLOBALS['current_user']->getPreference('currency'));
81
            $this->div = $currency->conversion_rate;
82
            $this->currency_symbol = $currency->symbol;
83
        }
84
        else{
85 1
	        $this->currency_symbol = $GLOBALS['sugar_config']['default_currency_symbol'];
86 1
			$this->div = 1;
87 1
			$this->is_currency = false;
88
        }
89 1
        $this->image_export_type = (extension_loaded('gd') && function_exists('gd_info')) ? "png" : "jpg";
90 1
	}
91
92
	function getData($query){
93
		$result = $this->db->query($query);
94
95
		$row = $this->db->fetchByAssoc($result);
96
97
		while ($row != null){
98
			$this->data_set[] = $row;
99
			$row = $this->db->fetchByAssoc($result);
100
		}
101
	}
102
103
	function constructBaseURL(){
104
		$numParams = 0;
105
		$url = 'index.php?';
106
107
		foreach ($this->base_url as $param => $value){
108
			if ($numParams == 0){
109
				$url .= $param . '=' . $value;
110
			}
111
			else{
112
				$url .= '&' .$param . '=' .$value;
113
			}
114
			$numParams++;
115
		}
116
117
		return $url;
118
	}
119
120
	function constructURL(){
121
		$url = $this->constructBaseURL();
122
		foreach ($this->url_params as $param => $value){
123
			if ($param == 'assigned_user_id') $param = 'assigned_user_id[]';
124
			if (is_array($value)){
125
				foreach($value as $multiple){
126
					$url .= '&' . $param . '=' . urlencode($multiple);
127
				}
128
			}
129
			else{
130
				$url .= '&' . $param . '=' . urlencode($value);
131
			}
132
		}
133
		return $url;
134
	}
135
136
	function setData($dataSet){
137
		$this->data_set = $dataSet;
138
	}
139
140
    function setProperties($title, $subtitle, $type, $legend='on', $labels='value', $print='on', $thousands = false)
141
    {
142
        $this->chart_properties['title'] = $title;
143
        $this->chart_properties['subtitle'] = $subtitle;
144
        $this->chart_properties['type'] = $type;
145
        $this->chart_properties['legend'] = $legend;
146
        $this->chart_properties['labels'] = $labels;
147
        $this->chart_properties['thousands'] = $thousands;
148
    }
149
150
	function setDisplayProperty($property, $value){
151
		$this->chart_properties[$property] = $value;
152
	}
153
154
	function setColors($colors = array()){
155
		$this->colors_list = $colors;
156
	}
157
158
    /**
159
     * returns the header for the constructed xml file for sugarcharts
160
	 *
161
     * @param 	nothing
162
     * @return	string $header XML header
163
     */
164
	function xmlHeader(){
165
		$header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
166
		$header .= "<sugarcharts version=\"1.0\">\n";
167
168
		return $header;
169
	}
170
171
	/**
172
     * returns the footer for the constructed xml file for sugarcharts
173
	 *
174
     * @param 	nothing
175
     * @return	string $footer XML footer
176
     */
177
	function xmlFooter(){
178
		$footer = "</sugarcharts>";
179
180
		return $footer;
181
	}
182
183
	/**
184
     * returns the properties tag for the constructed xml file for sugarcharts
185
	 *
186
     * @param 	nothing
187
     * @return	string $properties XML properties tag
188
     */
189
	function xmlProperties(){
190
		// open the properties tag
191
		$properties = $this->tab("<properties>",1);
192
193
		// grab the property and value from the chart_properties variable
194
		foreach ($this->chart_properties as $key => $value){
195
		    if(is_array($value)) continue;
196
			$properties .= $this->tab("<$key>$value</$key>",2);
197
		}
198
199
		if (!empty($this->colors_list)){
200
			// open the colors tag
201
			$properties .= $this->tab("<colors>",2);
202
			foreach ($this->colors_list as $color){
203
				$properties .= $this->tab("<color>$color</color>",3);
204
			}
205
206
			// close the colors tag
207
			$properties .= $this->tab("</colors>",2);
208
		}
209
210
		// close the properties tag
211
		$properties .= $this->tab("</properties>",1);
212
213
		return $properties;
214
	}
215
216
	/**
217
     * returns the y-axis values for the chart
218
	 *
219
     * @param 	nothing
220
     * @return	string $yAxis XML yAxis tag
221
     */
222
	function xmlYAxis(){
223
		$this->chart_yAxis['yStep'] = '100';
224
		$this->chart_yAxis['yLog'] = '1';
225
		$this->chart_yAxis['yMax'] = $this->is_currency ? $this->convertCurrency($this->chart_yAxis['yMax']) : $this->chart_yAxis['yMax'];
226
		$max = $this->chart_yAxis['yMax'];
227
		$exp = ($max == 0) ? 1 : floor(log10($max));
228
		$baseval = $max / pow(10, $exp);
229
230
		// steps will be 10^n, 2*10^n, 5*10^n (where n >= 0)
231
		if ($baseval > 0 && $baseval <= 1){
232
			$step = 2 * pow(10, $exp-1);
233
		}
234
		else if ($baseval > 1 && $baseval <= 3){
235
			$step = 5 * pow(10, $exp-1);
236
		}
237
		else if ($baseval > 3 && $baseval <= 6){
238
			$step = 10 * pow(10, $exp-1);
239
		}
240
		else if ($baseval > 6 && $baseval <= 10){
241
			$step = 20 * pow(10, $exp-1);
242
		}
243
244
		// edge cases for values less than 10
245
		if ($max == 0 || $step < 1){
246
			$step = 1;
247
		}
248
249
		$this->chart_yAxis['yStep'] = $step;
250
251
		// to compensate, the yMax should be at least one step above the max value
252
			$this->chart_yAxis['yMax'] += $this->chart_yAxis['yStep'];
253
254
		$yAxis = $this->tab("<yAxis>" ,1);
255
256
		foreach ($this->chart_yAxis as $key => $value){
257
			$yAxis .= $this->tabValue("{$key}",$value, 2);
258
		}
259
260
		$yAxis .= $this->tab("</yAxis>" ,1);
261
262
		return $yAxis;
263
	}
264
265
	/**
266
     * returns the total amount value for the group by field
267
	 *
268
     * @param 	group by field
269
     * @return	int $total total value
270
     */
271
	function calculateTotal($group_by){
272
		$total = 0;
273
274
		for($i =0; $i < count($this->data_set); $i++){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
275
			if ($this->data_set[$i][$this->group_by[0]] == $group_by){
276
				$total += $this->data_set[$i]['total'];
277
			}
278
		}
279
		return $total;
280
	}
281
282
	/**
283
     * returns text with tabs appended before it
284
	 *
285
     * @param 	string $str input string
286
	 *			int $depth number of times to tab
287
     * @return	string with tabs appended before it
288
     */
289
	function tab($str, $depth){
290
		return str_repeat("\t", $depth) . $str . "\n";
291
	}
292
	/**
293
     * returns text with tabs appended before it
294
	 *
295
     * @param 	string $str xml tag
296
     			int $tagFormat 2 = open and close tag, 1 = close, 0 = open
0 ignored issues
show
There is no parameter named $str. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
297
     			sting $value input string
298
	 *			int $depth number of times to tab
299
     * @return	string with tabs appended before it
300
     */
301
302
	function tabValue($tag,$value,$depth) {
303
304
			return $this->tab("<{$tag}>".htmlspecialchars($value,ENT_QUOTES)."</{$tag}>",$depth);
305
306
	}
307
	/**
308
     * returns xml data format
309
	 *
310
     * @param 	none
311
     * @return	string with xml data format
312
     */
313
	function processData(){
314
		$data = array();
315
316
		$group_by = $this->group_by[0];
317
		if (isset($this->group_by[1])){
318
			$drill_down = $this->group_by[1];
319
		}
320
321
		$prev_group_by = '';
322
323
		for($i =0; $i < count($this->data_set); $i++){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
324
			if ($this->data_set[$i][$group_by] != $prev_group_by){
325
				$prev_group_by = $this->data_set[$i][$group_by];
326
				$data[$this->data_set[$i][$group_by]] = array();
327
			}
328
329
            $data[$this->data_set[$i][$group_by]][] = $this->data_set[$i];
330
331
			// push new item onto legend items list
332
			if (isset($drill_down)){
333
				if (!in_array($this->data_set[$i][$drill_down], $this->super_set)){
334
					$this->super_set[] = $this->data_set[$i][$drill_down];
335
				}
336
			}
337
		}
338
339
		return $data;
340
	}
341
342
	function processDataGroup($tablevel, $title, $value, $label, $link){
343
		$link = $this->forceHideDataGroupLink ? '' : $link;
344
		$data = $this->tab('<group>',$tablevel);
345
		$data .= $this->tabValue('title',$title,$tablevel+1);
346
		$data .= $this->tabValue('value',$value,$tablevel+1);
347
		$data .= $this->tabValue('label',$label,$tablevel+1);
348
		$data .= $this->tab('<link>' . $link . '</link>',$tablevel+1);
349
		$data .= $this->tab('</group>',$tablevel);
350
		return $data;
351
	}
352
353
	function calculateGroupByTotal($dataset){
354
		$total = 0;
355
356
		foreach ($dataset as $key => $value){
357
			$total += $value;
358
		}
359
360
		return $total;
361
	}
362
363
	function calculateSingleBarMax($dataset){
364
		$max = 0;
365
		foreach ($dataset as $value){
366
			if ($value > $max){
367
				$max = $value;
368
			}
369
		}
370
371
		return $max;
372
	}
373
374
	/**
375
     * returns correct yAxis min/max
376
	 *
377
     * @param 	value to check
378
     * @return	yAxis min and max
379
     */
380
	function checkYAxis($value){
381
		if ($value < $this->chart_yAxis['yMin']){
382
			$this->chart_yAxis['yMin'] = $value;
383
		}
384
		else if ($value > $this->chart_yAxis['yMax']){
385
			$this->chart_yAxis['yMax'] = $value;
386
		}
387
	}
388
389
390
    /**
391
     * Convert the amount given to the User's currency.
392
     *
393
     * TODO make this use the Currency module to convert from dollars and make
394
     * it deprecated.
395
     *
396
     * @param float $to_convert
397
     *   The amount to be converted.
398
     *
399
     * @return float
400
     *   The amount converted in the User's current currency.
401
     *
402
     * @see Currency::convertFromDollar()
403
     * @see SugarChart::__construct()
404
     */
405
    function convertCurrency($to_convert)
406
    {
407
        global $locale;
408
409
        $decimals = $locale->getPrecision();
410
        $amount = round($to_convert * $this->div, $decimals);
411
412
        return $amount;
413
    }
414
415
	function formatNumber($number, $decimals= null, $decimal_point= null, $thousands_sep= null){
416
		global $locale;
417
		if(is_null($decimals)) {
418
			$decimals = $locale->getPrecision();
419
		}
420
		$seps = get_number_seperators();
421
		$thousands_sep = $seps[0];
422
		$decimal_point = $seps[1];
423
		return number_format($number, $decimals, $decimal_point, $thousands_sep);
424
	}
425
426
	function getTotal(){
427
		$new_data = $this->processData();
428
		$total = 0;
429
		foreach ($new_data as $groupByKey => $value){
430
			$total += $this->calculateTotal($groupByKey);
431
		}
432
433
		return $total;
434
	}
435
436
	function xmlDataForGroupByChart(){
437
		$data = '';
438
		$idcounter = 0;
439
		foreach ($this->data_set as $key => $value){
440
			$amount = $this->is_currency ? $this->convertCurrency($this->calculateGroupByTotal($value)) : $this->calculateGroupByTotal($value);
441
            $label = $this->is_currency ? ($this->currency_symbol . $this->formatNumber($amount)) : $amount;
442
443
			$data .= $this->tab('<group>',2);
444
			if (!empty($this->display_labels[$key]))
445
			{
446
				$data .= $this->tabValue('title', $this->display_labels[$key],3);
447
				$data .= $this->tabValue('id', ++$idcounter,3);
448
			}
449
			else
450
			{
451
				$data .= $this->tabValue('title', $key,3);
452
			}
453
			$data .= $this->tabValue('value',$amount,3);
454
			$data .= $this->tabValue('label',$label,3);
455
			$data .= $this->tab('<link></link>',3);
456
			$data .= $this->tab('<subgroups>',3);
457
458
			foreach ($value as $k => $v){
459
                $amount = $this->is_currency ? $this->convertCurrency($v) : $v;
460
                $label = $this->is_currency ? ($this->currency_symbol . $this->formatNumber($amount)) : $amount;
461
462
				$data .= $this->tab('<group>',4);
463
				$data .= $this->tabValue('title',$k,5);
464
				$data .= $this->tabValue('value',$amount,5);
465
				$data .= $this->tabValue('label',$label,5);
466
				$data .= $this->tab('<link></link>',5);
467
				$data .= $this->tab('</group>',4);
468
				$this->checkYAxis($v);
469
			}
470
			$data .= $this->tab('</subgroups>',3);
471
			$data .= $this->tab('</group>',2);
472
		}
473
474
		return $data;
475
	}
476
477
	function xmlDataForGaugeChart(){
478
		$data = '';
479
		$gaugePosition = $this->data_set[0]['num'];
480
		$this->chart_yAxis['yMax'] = $this->chart_properties['gaugeTarget'];
481
		$this->chart_yAxis['yStep'] = 1;
482
		$data .= $this->processDataGroup(2, 'GaugePosition', $gaugePosition, $gaugePosition, '');
483
		if (isset($this->chart_properties['gaugePhases']) && is_array($this->chart_properties['gaugePhases'])) {
484
			$data .= $this->processGauge($gaugePosition, $this->chart_properties['gaugeTarget'], $this->chart_properties['gaugePhases']);
0 ignored issues
show
The method processGauge() does not seem to exist on object<SugarChart>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
485
		} else {
486
			$data .= $this->processGauge($gaugePosition, $this->chart_properties['gaugeTarget']);
0 ignored issues
show
The method processGauge() does not seem to exist on object<SugarChart>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
487
		}
488
489
		return $data;
490
	}
491
492
	function xmlDataBarChart(){
493
		$data = '';
494
		$max = $this->calculateSingleBarMax($this->data_set);
495
		$this->checkYAxis($max);
496
497
		if (isset($this->group_by[0])){
498
			$group_by = $this->group_by[0];
499
			if (isset($this->group_by[1])){
500
				$drill_down = $this->group_by[1];
501
			}
502
		}
503
504
		foreach ($this->data_set as $key => $value){
505
			if ($this->is_currency){
506
				$value = $this->convertCurrency($value);
507
				$label = $this->currency_symbol;
508
				$label .= $this->formatNumber($value);
509
				$label .= $this->thousands_symbol;
510
			}
511
			else{
512
				$label = $value;
513
			}
514
515
			$data .= $this->tab('<group>', 2);
516
			$data .= $this->tabValue('title',$key, 3);
517
			$data .= $this->tabValue('value',$value, 3);
518
			$data .= $this->tabValue('label',$label, 3);
519
			if (isset($drill_down) && $drill_down){
520
				if ($this->group_by[0] == 'm') {
521
                    $additional_param = '&date_closed_advanced=' . urlencode($key);
522
				} else if ( $this->group_by[0] == 'sales_stage' ) {
523
                    $additional_param = '&sales_stage_advanced[]='.urlencode(array_search($key,$GLOBALS['app_list_strings']['sales_stage_dom']));
524
                } else{
525
					$additional_param = "&" . $this->group_by[0] . "=" . urlencode($key);
526
				}
527
				$url = $this->constructURL() . $additional_param;
528
529
				$data .= $this->tab('<link>' . $url . '</link>', 3);
530
			}
531
			$data .= $this->tab('<subgroups>', 3);
532
			$data .= $this->tab('</subgroups>', 3);
533
			$data .= $this->tab('</group>', 2);
534
		}
535
		return $data;
536
	}
537
538
	function xmlDataGenericChart(){
539
		$data = '';
540
		$group_by = $this->group_by[0];
541
		if (isset($this->group_by[1])){
542
			$drill_down = $this->group_by[1];
543
		}
544
		$new_data = $this->processData();
545
546
		foreach ($new_data as $groupByKey => $value){
547
			$total = $this->calculateTotal($groupByKey);
548
			$this->checkYAxis($total);
549
550
			if ($this->group_by[0] == 'm'){
551
				$additional_param = '&date_closed_advanced=' . urlencode($groupByKey);
552
			}
553
			else{
554
				$paramValue = (isset($value[0]['key']) && $value[0]['key'] != '') ? $value[0]['key'] : $groupByKey;
555
				$paramValue = (isset($value[0][$this->group_by[0]."_dom_option"]) && $value[0][$this->group_by[0]."_dom_option"] != '') ? $value[0][$this->group_by[0]."_dom_option"] : $paramValue;
556
				$additional_param = "&" . $this->group_by[0] . "=" . urlencode($paramValue);
557
			}
558
559
			$url = $this->constructURL() . $additional_param;
560
561
			$amount = $this->is_currency ? $this->convertCurrency($total) : $total;
562
			$label = $this->is_currency ? ($this->currency_symbol . $this->formatNumber($amount) . 'K') : $amount;
563
564
			$data .= $this->tab('<group>',2);
565
			$data .= $this->tabValue('title',$groupByKey,3);
566
			$data .= $this->tabValue('value',$amount,3);
567
			$data .= $this->tabValue('label',$label,3);
568
			$data .= $this->tab('<link>' . $url . '</link>',3);
569
570
			$data .= $this->tab('<subgroups>',3);
571
			$processed = array();
572
573
			if (isset($drill_down) && $drill_down != ''){
574
                /*
575
                * Bug 44696 - Ivan D.
576
                * We have to iterate users in order since they are in the super_set for every group.
577
                * This is required to display the correct links for each user in a drill down chart.
578
                */
579
                foreach ($this->super_set as $superSetKey => $superSetValue)
580
                {
581
                    $objectInSaleStage = false;
582
                    foreach ($value as $internalKey => $internalValue)
583
                    {
584
                        if ($internalValue[$drill_down] == $superSetValue)
585
                        {
586
                            $objectInSaleStage = $value[$internalKey];
587
                        }
588
                    }
589
590
                    if ($objectInSaleStage)
591
                    {
592
                        if (isset($objectInSaleStage[$group_by]) && $objectInSaleStage[$group_by] == $groupByKey)
593
                        {
594
                            if ($drill_down == 'user_name')
595
                            {
596
                                $drill_down_param = '&assigned_user_id[]=' . urlencode($objectInSaleStage['assigned_user_id']);
597
                            }
598
                            else if ($drill_down == 'm')
599
                            {
600
                                $drill_down_param = '&date_closed_advanced=' . urlencode($objectInSaleStage[$drill_down]);
601
                            }
602
                            else
603
                            {
604
                                $paramValue = (isset($objectInSaleStage[$drill_down . "_dom_option"]) && $objectInSaleStage[$drill_down . "_dom_option"] != '') ? $objectInSaleStage[$drill_down . "_dom_option"] : $objectInSaleStage[$drill_down];
605
                                $drill_down_param = '&' . $drill_down . '=' . urlencode($paramValue);
606
                            }
607
608
                            if ($this->is_currency)
609
                            {
610
                                $sub_amount = $this->formatNumber($this->convertCurrency($objectInSaleStage['total']));
611
                                $sub_amount_formatted = $this->currency_symbol . $sub_amount . 'K';
612
                                //bug: 38877 - do not format the amount for the value as it breaks the chart
613
                                $sub_amount = $this->convertCurrency($objectInSaleStage['total']);
614
                            }
615
                            else
616
                            {
617
                                $sub_amount = $objectInSaleStage['total'];
618
                                $sub_amount_formatted = $sub_amount;
619
                            }
620
621
                            $data .= $this->processDataGroup(4, $objectInSaleStage[$drill_down], $sub_amount, $sub_amount_formatted, $url . $drill_down_param);
622
                        }
623
                        else
624
                        {
625
                            $data .= $this->nullGroup($superSetValue, $url);
626
                        }
627
628
                    }
629
                    else
630
                    {
631
                        $data .= $this->nullGroup($superSetValue, $url);
632
                    }
633
                }
634
			}
635
636
			$data .= $this->tab('</subgroups>',3);
637
			$data .= $this->tab('</group>',2);
638
		}
639
		return $data;
640
	}
641
642
643
    /**
644
     * nullGroup
645
     * This function sets a null group by clause
646
     *
647
     * @param $sugarSetValue Mixed value
648
     * @param $url String value of URL for the link
649
     */
650
    private function nullGroup($superSetValue, $url) {
651
        return $this->processDataGroup(4, $superSetValue, 'NULL', '', $url);
652
    }
653
654
655
    /**
656
     * returns a name for the XML File
657
     *
658
     * @param string $file_id - unique id to make part of the file name
659
     */
660
    public static function getXMLFileName($file_id)
661
    {
662
        create_cache_directory("xml/".$GLOBALS['current_user']->getUserPrivGuid() . "_{$file_id}.xml");
663
664
        return sugar_cached("xml/"). $GLOBALS['current_user']->getUserPrivGuid() . "_" . $file_id . ".xml";
665
    }
666
667
    public function processXmlData(){
668
    	$data = '';
669
670
		if ($this->chart_properties['type'] == 'group by chart'){
671
			$data .= $this->xmlDataForGroupByChart();
672
		}
673
		else if ($this->chart_properties['type'] == 'bar chart' || $this->chart_properties['type'] == 'horizontal bar chart'){
674
			$data .= $this->xmlDataBarChart();
675
		}
676
		else{
677
			$data .= $this->xmlDataGenericChart();
678
		}
679
680
		return $data;
681
    }
682
683
	function xmlData(){
684
		$data = $this->tab('<data>',1);
685
		$data .= $this->processXmlData();
686
		$data .= $this->tab('</data>',1);
687
688
		return $data;
689
	}
690
691
	/**
692
     * function to generate XML and return it
693
	 *
694
     * @param 	none
695
     * @return	string $xmlContents with xml information
696
     */
697
	function generateXML($xmlDataName = false){
698
		$xmlContents = $this->xmlHeader();
699
		$xmlContents .= $this->xmlProperties();
700
		$xmlContents .= $this->xmlData();
701
		$xmlContents .= $this->xmlYAxis();
702
		$xmlContents .= $this->xmlFooter();
703
704
		return $xmlContents;
705
	}
706
707
	/**
708
     * function to save XML contents into a file
709
	 *
710
     * @param 	string $xmlFilename location of the xml file
711
	 *			string $xmlContents contents of the xml file
712
     * @return	string boolean denoting whether save has failed
713
     */
714
	function saveXMLFile($xmlFilename,$xmlContents) {
715
		global $app_strings;
716
		global $locale;
717
718
		$xmlContents = chr(255).chr(254).$GLOBALS['locale']->translateCharset($xmlContents, 'UTF-8', 'UTF-16LE');
719
720
        // Create dir if it doesn't exist
721
        $dir = dirname($xmlFilename);
722
        if (!sugar_is_dir($dir))
723
        {
724
            sugar_mkdir($dir, null, true);
725
        }
726
727
		// open file
728
		if (!$fh = sugar_fopen($xmlFilename, 'w')) {
729
			$GLOBALS['log']->debug("Cannot open file ($xmlFilename)");
730
			return;
731
		}
732
733
		// write the contents to the file
734
		if (fwrite($fh,$xmlContents) === FALSE) {
735
			$GLOBALS['log']->debug("Cannot write to file ($xmlFilename)");
736
			return false;
737
		}
738
739
		$GLOBALS['log']->debug("Success, wrote ($xmlContents) to file ($xmlFilename)");
740
741
		fclose($fh);
742
		return true;
743
	}
744
745
	/**
746
     * generates xml file for Flash charts to use for internationalized instances
747
	 *
748
     * @param 	string $xmlFile	location of the XML file to write to
749
     * @return	none
750
     */
751
	function generateChartStrings($xmlFile){
752
		global $current_language, $app_list_strings;
753
754
		$chartStringsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
755
		$chartStringsXML .= "<sugarlanguage version=\"1.0\">\n";
756
		$chartStringsXML .= $this->tab("<charts>",1);
757
758
		if (empty($app_list_strings)) {
759
		    //set module and application string arrays based upon selected language
760
			$app_list_strings = return_app_list_strings_language($current_language);
761
		}
762
763
		// retrieve the strings defined at include/language/en_us.lang.php
764
		foreach ($app_list_strings['chart_strings'] as $tag => $chart_string){
765
			$chartStringsXML .= $this->tab("<$tag>$chart_string</$tag>",2);
766
		}
767
768
		$chartStringsXML .= $this->tab("</charts>",1);
769
		$chartStringsXML .= "</sugarlanguage>\n";
770
771
		$this->saveXMLFile($xmlFile, $chartStringsXML);
772
	}
773
774
	/**
775
     * wrapper function to return the html code containing the chart in a div
776
	 *
777
     * @param 	string $name 	name of the div
778
	 *			string $xmlFile	location of the XML file
779
	 *			string $style	optional additional styles for the div
780
     * @return	string returns the html code through smarty
781
     */
782
	function display($name, $xmlFile, $width='320', $height='480', $resize=false){
783
784
785
		// generate strings for chart if it does not exist
786
		global $current_language, $theme, $sugar_config,$app_strings;
787
788
		$this->app_strings = $app_strings;
789
		$this->chartStringsXML = sugar_cached("xml/").'chart_strings.' . $current_language .'.lang.xml';
790
		if (!file_exists($this->chartStringsXML)){
791
			$this->generateChartStrings($this->chartStringsXML);
792
		}
793
794
		$templateFile = "";
795
		return $templateFile;
796
	}
797
798
799
	function getDashletScript($id,$xmlFile="") {
800
801
	$xmlFile = (!$xmlFile) ? $sugar_config['tmp_dir']. $current_user->id . '_' . $this->id . '.xml' : $xmlFile;
802
	$chartStringsXML = $GLOBALS['sugar_config']['tmp_dir'].'chart_strings.' . $current_language .'.lang.xml';
803
804
	$this->ss->assign('chartName', $id);
805
    $this->ss->assign('chartXMLFile', $xmlFile);
806
    $this->ss->assign('chartStyleCSS', SugarThemeRegistry::current()->getCSSURL('chart.css'));
807
    $this->ss->assign('chartColorsXML', SugarThemeRegistry::current()->getImageURL('sugarColors.xml'));
808
    $this->ss->assign('chartLangFile', $GLOBALS['sugar_config']['tmp_dir'].'chart_strings.' . $GLOBALS['current_language'] .'.lang.xml');
809
810
		$templateFile = "";
811
		return $templateFile;
812
	}
813
814
815
  /**
816
         This function is used for localize all the characters in the Chart. And it can also sort all the dom_values by the sequence defined in the dom, but this may produce a lot of extra empty data in the xml file, when the chart is sorted by two key cols.
817
         If the data quantity is large, it maybe a little slow.
818
    * @param         array $data_set           The data get from database
819
                           string $keycolname1      We will sort by this key first
820
                           bool $translate1            Whether to trabslate the first column
821
                           string $keycolname1      We will sort by this key secondly, and  it can be null, then it will only sort by the first column.
822
                           bool $translate1            Whether to trabslate the second column
823
                           bool $ifsort2                 Whether to sort by the second column or just translate the second column.
824
    * @return        The sorted and translated data.
825
   */
826
    function sortData($data_set, $keycolname1=null, $translate1=false, $keycolname2=null, $translate2=false, $ifsort2=false) {
827
        //You can set whether the columns need to be translated or sorted. It the column needn't to be translated, the sorting must be done in SQL, this function will not do the sorting.
828
        global $app_list_strings;
829
        $sortby1[] = array();
830
        foreach ($data_set as $row) {
831
            $sortby1[]  = $row[$keycolname1];
832
        }
833
        $sortby1 = array_unique($sortby1);
834
        //The data is from the database, the sorting should be done in the sql. So I will not do the sort here.
835
        if($translate1) {
836
            $temp_sortby1 = array();
837
            foreach(array_keys($app_list_strings[$keycolname1.'_dom']) as $sortby1_value) {
838
                if(in_array($sortby1_value, $sortby1)) {
839
                    $temp_sortby1[] = $sortby1_value;
840
                }
841
            }
842
            $sortby1 = $temp_sortby1;
843
        }
844
845
        //if(isset($sortby1[0]) && $sortby1[0]=='') unset($sortby1[0]);//the beginning of lead_source_dom is blank.
846
        if(isset($sortby1[0]) && $sortby1[0]==array()) unset($sortby1[0]);//the beginning of month after search is blank.
847
848
        if($ifsort2==false) $sortby2=array(0);
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
849
850
        if($keycolname2!=null) {
851
            $sortby2 = array();
852
            foreach ($data_set as $row) {
853
                $sortby2[]  = $row[$keycolname2];
854
            }
855
            //The data is from the database, the sorting should be done in the sql. So I will not do the sort here.
856
            $sortby2 = array_unique($sortby2);
857
            if($translate2) {
858
                $temp_sortby2 = array();
859
                foreach(array_keys($app_list_strings[$keycolname2.'_dom']) as $sortby2_value) {
860
                    if(in_array($sortby2_value, $sortby2)) {
861
                        $temp_sortby2[] = $sortby2_value;
862
                    }
863
                }
864
                $sortby2 = $temp_sortby2;
865
            }
866
        }
867
868
        $data=array();
869
870
        foreach($sortby1 as $sort1) {
871
            foreach($sortby2 as $sort2) {
872
                if($ifsort2) $a=0;
873
                foreach($data_set as $key => $value){
874
                    if($value[$keycolname1] == $sort1 && (!$ifsort2 || $value[$keycolname2]== $sort2)) {
875
                        if($translate1) {
876
                            $value[$keycolname1.'_dom_option'] = $value[$keycolname1];
877
                            $value[$keycolname1] = $app_list_strings[$keycolname1.'_dom'][$value[$keycolname1]];
878
                        }
879
                        if($translate2) {
880
                            $value[$keycolname2.'_dom_option'] = $value[$keycolname2];
881
                            $value[$keycolname2] = $app_list_strings[$keycolname2.'_dom'][$value[$keycolname2]];
882
                        }
883
                        array_push($data, $value);
884
                        unset($data_set[$key]);
885
                        $a=1;
886
                        }
887
                }
888
                if($ifsort2 && $a==0) {//Add 0 for sorting by the second column, if the first row doesn't have a certain col, it will fill the column with 0.
889
                    $val=array();
890
                    $val['total'] = 0;
891
                    $val['count'] = 0;
892
                    if($translate1) {
893
                        $val[$keycolname1] = $app_list_strings[$keycolname1.'_dom'][$sort1];
894
                        $val[$keycolname1.'_dom_option'] = $sort1;
895
                    }
896
                    else {
897
                        $val[$keycolname1] = $sort1;
898
                    }
899
                    if($translate2) {
900
                        $val[$keycolname2] = $app_list_strings[$keycolname2.'_dom'][$sort2];
901
                        $val[$keycolname2.'_dom_option'] = $sort2;
902
                    }
903
                    elseif($keycolname2!=null) {
904
                        $val[$keycolname2] = $sort2;
905
                    }
906
                    array_push($data, $val);
907
                }
908
            }
909
        }
910
        return $data;
911
    }
912
913
    function getChartResources() {
914
915
		$resources = "";
916
		return $resources;
917
	}
918
919
	function getMySugarChartResources() {
920
		$mySugarResources = "";
921
		return $mySugarResources;
922
	}
923
924
	/**
925
     * wrapper function to return chart array after any additional processing
926
	 *
927
     * @param 	array $chartsArray 	array of chart config items that need processing
928
     * @return	array $chartArray after it has been process
929
     */
930
	function chartArray($chartsArray) {
931
932
		return $chartsArray;
933
	}
934
935
} // end class def
936