]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarCharts/SugarChart.php
Release 6.5.16
[Github/sugarcrm.git] / include / SugarCharts / SugarChart.php
1 <?php
2 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  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38
39 /**
40  * Generic chart
41  * @api
42  */
43 class SugarChart {
44
45         private $db;
46         protected $ss;
47         var $forceHideDataGroupLink = false;
48         var $data_set = array();
49         var $display_data = array();
50         var $chart_properties = array();
51         var $chart_yAxis = array();
52         var $group_by = array();
53         var $super_set = array();
54         var $colors_list = array();
55         var $base_url = array();
56         var $url_params = array();
57         var $display_labels = array();
58
59         var $currency_symbol;
60         var $thousands_symbol;
61         var $is_currency;
62         var $supports_image_export = false;
63         var $print_html_legend_pdf = false;
64         var $image_export_type = "";
65
66         public function __construct() {
67                 $this->db = &DBManagerFactory::getInstance();
68                 $this->ss = new Sugar_Smarty();
69
70                 $this->chart_yAxis['yMin'] = 0;
71                 $this->chart_yAxis['yMax'] = 0;
72
73
74                 if ($GLOBALS['current_user']->getPreference('currency')){
75
76             $currency = new Currency();
77             $currency->retrieve($GLOBALS['current_user']->getPreference('currency'));
78             $this->div = $currency->conversion_rate;
79             $this->currency_symbol = $currency->symbol;
80         }
81         else{
82                 $this->currency_symbol = $GLOBALS['sugar_config']['default_currency_symbol'];
83                         $this->div = 1;
84                         $this->is_currency = false;
85         }
86         $this->image_export_type = (extension_loaded('gd') && function_exists('gd_info')) ? "png" : "jpg";
87         }
88
89         function getData($query){
90                 $result = $this->db->query($query);
91
92                 $row = $this->db->fetchByAssoc($result);
93
94                 while ($row != null){
95                         $this->data_set[] = $row;
96                         $row = $this->db->fetchByAssoc($result);
97                 }
98         }
99
100         function constructBaseURL(){
101                 $numParams = 0;
102                 $url = 'index.php?';
103
104                 foreach ($this->base_url as $param => $value){
105                         if ($numParams == 0){
106                                 $url .= $param . '=' . $value;
107                         }
108                         else{
109                                 $url .= '&' .$param . '=' .$value;
110                         }
111                         $numParams++;
112                 }
113
114                 return $url;
115         }
116
117         function constructURL(){
118                 $url = $this->constructBaseURL();
119                 foreach ($this->url_params as $param => $value){
120                         if ($param == 'assigned_user_id') $param = 'assigned_user_id[]';
121                         if (is_array($value)){
122                                 foreach($value as $multiple){
123                                         $url .= '&' . $param . '=' . urlencode($multiple);
124                                 }
125                         }
126                         else{
127                                 $url .= '&' . $param . '=' . urlencode($value);
128                         }
129                 }
130                 return $url;
131         }
132
133         function setData($dataSet){
134                 $this->data_set = $dataSet;
135         }
136
137     function setProperties($title, $subtitle, $type, $legend='on', $labels='value', $print='on', $thousands = false)
138     {
139         $this->chart_properties['title'] = $title;
140         $this->chart_properties['subtitle'] = $subtitle;
141         $this->chart_properties['type'] = $type;
142         $this->chart_properties['legend'] = $legend;
143         $this->chart_properties['labels'] = $labels;
144         $this->chart_properties['thousands'] = $thousands;
145     }
146
147         function setDisplayProperty($property, $value){
148                 $this->chart_properties[$property] = $value;
149         }
150
151         function setColors($colors = array()){
152                 $this->colors_list = $colors;
153         }
154
155     /**
156      * returns the header for the constructed xml file for sugarcharts
157          *
158      * @param   nothing
159      * @return  string $header XML header
160      */
161         function xmlHeader(){
162                 $header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
163                 $header .= "<sugarcharts version=\"1.0\">\n";
164
165                 return $header;
166         }
167
168         /**
169      * returns the footer for the constructed xml file for sugarcharts
170          *
171      * @param   nothing
172      * @return  string $footer XML footer
173      */
174         function xmlFooter(){
175                 $footer = "</sugarcharts>";
176
177                 return $footer;
178         }
179
180         /**
181      * returns the properties tag for the constructed xml file for sugarcharts
182          *
183      * @param   nothing
184      * @return  string $properties XML properties tag
185      */
186         function xmlProperties(){
187                 // open the properties tag
188                 $properties = $this->tab("<properties>",1);
189
190                 // grab the property and value from the chart_properties variable
191                 foreach ($this->chart_properties as $key => $value){
192                     if(is_array($value)) continue;
193                         $properties .= $this->tab("<$key>$value</$key>",2);
194                 }
195
196                 if (!empty($this->colors_list)){
197                         // open the colors tag
198                         $properties .= $this->tab("<colors>",2);
199                         foreach ($this->colors_list as $color){
200                                 $properties .= $this->tab("<color>$color</color>",3);
201                         }
202
203                         // close the colors tag
204                         $properties .= $this->tab("</colors>",2);
205                 }
206
207                 // close the properties tag
208                 $properties .= $this->tab("</properties>",1);
209
210                 return $properties;
211         }
212
213         /**
214      * returns the y-axis values for the chart
215          *
216      * @param   nothing
217      * @return  string $yAxis XML yAxis tag
218      */
219         function xmlYAxis(){
220                 $this->chart_yAxis['yStep'] = '100';
221                 $this->chart_yAxis['yLog'] = '1';
222                 $this->chart_yAxis['yMax'] = $this->is_currency ? $this->convertCurrency($this->chart_yAxis['yMax']) : $this->chart_yAxis['yMax'];
223                 $max = $this->chart_yAxis['yMax'];
224                 $exp = ($max == 0) ? 1 : floor(log10($max));
225                 $baseval = $max / pow(10, $exp);
226
227                 // steps will be 10^n, 2*10^n, 5*10^n (where n >= 0)
228                 if ($baseval > 0 && $baseval <= 1){
229                         $step = 2 * pow(10, $exp-1);
230                 }
231                 else if ($baseval > 1 && $baseval <= 3){
232                         $step = 5 * pow(10, $exp-1);
233                 }
234                 else if ($baseval > 3 && $baseval <= 6){
235                         $step = 10 * pow(10, $exp-1);
236                 }
237                 else if ($baseval > 6 && $baseval <= 10){
238                         $step = 20 * pow(10, $exp-1);
239                 }
240
241                 // edge cases for values less than 10
242                 if ($max == 0 || $step < 1){
243                         $step = 1;
244                 }
245
246                 $this->chart_yAxis['yStep'] = $step;
247
248                 // to compensate, the yMax should be at least one step above the max value
249                         $this->chart_yAxis['yMax'] += $this->chart_yAxis['yStep'];
250
251                 $yAxis = $this->tab("<yAxis>" ,1);
252
253                 foreach ($this->chart_yAxis as $key => $value){
254                         $yAxis .= $this->tabValue("{$key}",$value, 2);
255                 }
256
257                 $yAxis .= $this->tab("</yAxis>" ,1);
258
259                 return $yAxis;
260         }
261
262         /**
263      * returns the total amount value for the group by field
264          *
265      * @param   group by field
266      * @return  int $total total value
267      */
268         function calculateTotal($group_by){
269                 $total = 0;
270
271                 for($i =0; $i < count($this->data_set); $i++){
272                         if ($this->data_set[$i][$this->group_by[0]] == $group_by){
273                                 $total += $this->data_set[$i]['total'];
274                         }
275                 }
276                 return $total;
277         }
278
279         /**
280      * returns text with tabs appended before it
281          *
282      * @param   string $str input string
283          *                      int $depth number of times to tab
284      * @return  string with tabs appended before it
285      */
286         function tab($str, $depth){
287                 return str_repeat("\t", $depth) . $str . "\n";
288         }
289         /**
290      * returns text with tabs appended before it
291          *
292      * @param   string $str xml tag
293                         int $tagFormat 2 = open and close tag, 1 = close, 0 = open
294                         sting $value input string
295          *                      int $depth number of times to tab
296      * @return  string with tabs appended before it
297      */
298
299         function tabValue($tag,$value,$depth) {
300
301                         return $this->tab("<{$tag}>".htmlspecialchars($value,ENT_QUOTES)."</{$tag}>",$depth);
302
303         }
304         /**
305      * returns xml data format
306          *
307      * @param   none
308      * @return  string with xml data format
309      */
310         function processData(){
311                 $data = array();
312
313                 $group_by = $this->group_by[0];
314                 if (isset($this->group_by[1])){
315                         $drill_down = $this->group_by[1];
316                 }
317
318                 $prev_group_by = '';
319
320                 for($i =0; $i < count($this->data_set); $i++){
321                         if ($this->data_set[$i][$group_by] != $prev_group_by){
322                                 $prev_group_by = $this->data_set[$i][$group_by];
323                                 $data[$this->data_set[$i][$group_by]] = array();
324                         }
325
326             $data[$this->data_set[$i][$group_by]][] = $this->data_set[$i];
327
328                         // push new item onto legend items list
329                         if (isset($drill_down)){
330                                 if (!in_array($this->data_set[$i][$drill_down], $this->super_set)){
331                                         $this->super_set[] = $this->data_set[$i][$drill_down];
332                                 }
333                         }
334                 }
335
336                 return $data;
337         }
338
339         function processDataGroup($tablevel, $title, $value, $label, $link){
340                 $link = $this->forceHideDataGroupLink ? '' : $link;
341                 $data = $this->tab('<group>',$tablevel);
342                 $data .= $this->tabValue('title',$title,$tablevel+1);
343                 $data .= $this->tabValue('value',$value,$tablevel+1);
344                 $data .= $this->tabValue('label',$label,$tablevel+1);
345                 $data .= $this->tab('<link>' . $link . '</link>',$tablevel+1);
346                 $data .= $this->tab('</group>',$tablevel);
347                 return $data;
348         }
349
350         function calculateGroupByTotal($dataset){
351                 $total = 0;
352
353                 foreach ($dataset as $key => $value){
354                         $total += $value;
355                 }
356
357                 return $total;
358         }
359
360         function calculateSingleBarMax($dataset){
361                 $max = 0;
362                 foreach ($dataset as $value){
363                         if ($value > $max){
364                                 $max = $value;
365                         }
366                 }
367
368                 return $max;
369         }
370
371         /**
372      * returns correct yAxis min/max
373          *
374      * @param   value to check
375      * @return  yAxis min and max
376      */
377         function checkYAxis($value){
378                 if ($value < $this->chart_yAxis['yMin']){
379                         $this->chart_yAxis['yMin'] = $value;
380                 }
381                 else if ($value > $this->chart_yAxis['yMax']){
382                         $this->chart_yAxis['yMax'] = $value;
383                 }
384         }
385
386
387     /**
388      * Convert the amount given to the User's currency.
389      *
390      * TODO make this use the Currency module to convert from dollars and make
391      * it deprecated.
392      *
393      * @param float $to_convert
394      *   The amount to be converted.
395      *
396      * @return float
397      *   The amount converted in the User's current currency.
398      *
399      * @see Currency::convertFromDollar()
400      * @see SugarChart::__construct()
401      */
402     function convertCurrency($to_convert)
403     {
404         global $locale;
405
406         $decimals = $locale->getPrecision();
407         $amount = round($to_convert * $this->div, $decimals);
408
409         return $amount;
410     }
411
412         function formatNumber($number, $decimals= null, $decimal_point= null, $thousands_sep= null){
413                 global $locale;
414                 if(is_null($decimals)) {
415                         $decimals = $locale->getPrecision();
416                 }
417                 $seps = get_number_seperators();
418                 $thousands_sep = $seps[0];
419                 $decimal_point = $seps[1];
420                 return number_format($number, $decimals, $decimal_point, $thousands_sep);
421         }
422
423         function getTotal(){
424                 $new_data = $this->processData();
425                 $total = 0;
426                 foreach ($new_data as $groupByKey => $value){
427                         $total += $this->calculateTotal($groupByKey);
428                 }
429
430                 return $total;
431         }
432
433         function xmlDataForGroupByChart(){
434                 $data = '';
435                 $idcounter = 0;
436                 foreach ($this->data_set as $key => $value){
437                         $amount = $this->is_currency ? $this->convertCurrency($this->calculateGroupByTotal($value)) : $this->calculateGroupByTotal($value);
438             $label = $this->is_currency ? ($this->currency_symbol . $this->formatNumber($amount)) : $amount;
439
440                         $data .= $this->tab('<group>',2);
441                         if (!empty($this->display_labels[$key]))
442                         {
443                                 $data .= $this->tabValue('title', $this->display_labels[$key],3);
444                                 $data .= $this->tabValue('id', ++$idcounter,3);
445                         }
446                         else
447                         {
448                                 $data .= $this->tabValue('title', $key,3);
449                         }
450                         $data .= $this->tabValue('value',$amount,3);
451                         $data .= $this->tabValue('label',$label,3);
452                         $data .= $this->tab('<link></link>',3);
453                         $data .= $this->tab('<subgroups>',3);
454
455                         foreach ($value as $k => $v){
456                 $amount = $this->is_currency ? $this->convertCurrency($v) : $v;
457                 $label = $this->is_currency ? ($this->currency_symbol . $this->formatNumber($amount)) : $amount;
458
459                                 $data .= $this->tab('<group>',4);
460                                 $data .= $this->tabValue('title',$k,5);
461                                 $data .= $this->tabValue('value',$amount,5);
462                                 $data .= $this->tabValue('label',$label,5);
463                                 $data .= $this->tab('<link></link>',5);
464                                 $data .= $this->tab('</group>',4);
465                                 $this->checkYAxis($v);
466                         }
467                         $data .= $this->tab('</subgroups>',3);
468                         $data .= $this->tab('</group>',2);
469                 }
470
471                 return $data;
472         }
473
474         function xmlDataForGaugeChart(){
475                 $data = '';
476                 $gaugePosition = $this->data_set[0]['num'];
477                 $this->chart_yAxis['yMax'] = $this->chart_properties['gaugeTarget'];
478                 $this->chart_yAxis['yStep'] = 1;
479                 $data .= $this->processDataGroup(2, 'GaugePosition', $gaugePosition, $gaugePosition, '');
480                 if (isset($this->chart_properties['gaugePhases']) && is_array($this->chart_properties['gaugePhases'])) {
481                         $data .= $this->processGauge($gaugePosition, $this->chart_properties['gaugeTarget'], $this->chart_properties['gaugePhases']);
482                 } else {
483                         $data .= $this->processGauge($gaugePosition, $this->chart_properties['gaugeTarget']);
484                 }
485
486                 return $data;
487         }
488
489         function xmlDataBarChart(){
490                 $data = '';
491                 $max = $this->calculateSingleBarMax($this->data_set);
492                 $this->checkYAxis($max);
493
494                 if (isset($this->group_by[0])){
495                         $group_by = $this->group_by[0];
496                         if (isset($this->group_by[1])){
497                                 $drill_down = $this->group_by[1];
498                         }
499                 }
500
501                 foreach ($this->data_set as $key => $value){
502                         if ($this->is_currency){
503                                 $value = $this->convertCurrency($value);
504                                 $label = $this->currency_symbol;
505                                 $label .= $this->formatNumber($value);
506                                 $label .= $this->thousands_symbol;
507                         }
508                         else{
509                                 $label = $value;
510                         }
511
512                         $data .= $this->tab('<group>', 2);
513                         $data .= $this->tabValue('title',$key, 3);
514                         $data .= $this->tabValue('value',$value, 3);
515                         $data .= $this->tabValue('label',$label, 3);
516                         if (isset($drill_down) && $drill_down){
517                                 if ($this->group_by[0] == 'm') {
518                     $additional_param = '&date_closed_advanced=' . urlencode($key);
519                                 } else if ( $this->group_by[0] == 'sales_stage' ) {
520                     $additional_param = '&sales_stage_advanced[]='.urlencode(array_search($key,$GLOBALS['app_list_strings']['sales_stage_dom']));
521                 } else{
522                                         $additional_param = "&" . $this->group_by[0] . "=" . urlencode($key);
523                                 }
524                                 $url = $this->constructURL() . $additional_param;
525
526                                 $data .= $this->tab('<link>' . $url . '</link>', 3);
527                         }
528                         $data .= $this->tab('<subgroups>', 3);
529                         $data .= $this->tab('</subgroups>', 3);
530                         $data .= $this->tab('</group>', 2);
531                 }
532                 return $data;
533         }
534
535         function xmlDataGenericChart(){
536                 $data = '';
537                 $group_by = $this->group_by[0];
538                 if (isset($this->group_by[1])){
539                         $drill_down = $this->group_by[1];
540                 }
541                 $new_data = $this->processData();
542
543                 foreach ($new_data as $groupByKey => $value){
544                         $total = $this->calculateTotal($groupByKey);
545                         $this->checkYAxis($total);
546
547                         if ($this->group_by[0] == 'm'){
548                                 $additional_param = '&date_closed_advanced=' . urlencode($groupByKey);
549                         }
550                         else{
551                                 $paramValue = (isset($value[0]['key']) && $value[0]['key'] != '') ? $value[0]['key'] : $groupByKey;
552                                 $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;
553                                 $additional_param = "&" . $this->group_by[0] . "=" . urlencode($paramValue);
554                         }
555
556                         $url = $this->constructURL() . $additional_param;
557
558                         $amount = $this->is_currency ? $this->convertCurrency($total) : $total;
559                         $label = $this->is_currency ? ($this->currency_symbol . $this->formatNumber($amount) . 'K') : $amount;
560
561                         $data .= $this->tab('<group>',2);
562                         $data .= $this->tabValue('title',$groupByKey,3);
563                         $data .= $this->tabValue('value',$amount,3);
564                         $data .= $this->tabValue('label',$label,3);
565                         $data .= $this->tab('<link>' . $url . '</link>',3);
566
567                         $data .= $this->tab('<subgroups>',3);
568                         $processed = array();
569
570                         if (isset($drill_down) && $drill_down != ''){
571                 /*
572                 * Bug 44696 - Ivan D.
573                 * We have to iterate users in order since they are in the super_set for every group.
574                 * This is required to display the correct links for each user in a drill down chart.
575                 */
576                 foreach ($this->super_set as $superSetKey => $superSetValue)
577                 {
578                     $objectInSaleStage = false;
579                     foreach ($value as $internalKey => $internalValue)
580                     {
581                         if ($internalValue[$drill_down] == $superSetValue)
582                         {
583                             $objectInSaleStage = $value[$internalKey];
584                         }
585                     }
586
587                     if ($objectInSaleStage)
588                     {
589                         if (isset($objectInSaleStage[$group_by]) && $objectInSaleStage[$group_by] == $groupByKey)
590                         {
591                             if ($drill_down == 'user_name')
592                             {
593                                 $drill_down_param = '&assigned_user_id[]=' . urlencode($objectInSaleStage['assigned_user_id']);
594                             }
595                             else if ($drill_down == 'm')
596                             {
597                                 $drill_down_param = '&date_closed_advanced=' . urlencode($objectInSaleStage[$drill_down]);
598                             }
599                             else
600                             {
601                                 $paramValue = (isset($objectInSaleStage[$drill_down . "_dom_option"]) && $objectInSaleStage[$drill_down . "_dom_option"] != '') ? $objectInSaleStage[$drill_down . "_dom_option"] : $objectInSaleStage[$drill_down];
602                                 $drill_down_param = '&' . $drill_down . '=' . urlencode($paramValue);
603                             }
604
605                             if ($this->is_currency)
606                             {
607                                 $sub_amount = $this->formatNumber($this->convertCurrency($objectInSaleStage['total']));
608                                 $sub_amount_formatted = $this->currency_symbol . $sub_amount . 'K';
609                                 //bug: 38877 - do not format the amount for the value as it breaks the chart
610                                 $sub_amount = $this->convertCurrency($objectInSaleStage['total']);
611                             }
612                             else
613                             {
614                                 $sub_amount = $objectInSaleStage['total'];
615                                 $sub_amount_formatted = $sub_amount;
616                             }
617
618                             $data .= $this->processDataGroup(4, $objectInSaleStage[$drill_down], $sub_amount, $sub_amount_formatted, $url . $drill_down_param);
619                         }
620                         else
621                         {
622                             $data .= $this->nullGroup($superSetValue, $url);
623                         }
624
625                     }
626                     else
627                     {
628                         $data .= $this->nullGroup($superSetValue, $url);
629                     }
630                 }
631                         }
632
633                         $data .= $this->tab('</subgroups>',3);
634                         $data .= $this->tab('</group>',2);
635                 }
636                 return $data;
637         }
638
639
640     /**
641      * nullGroup
642      * This function sets a null group by clause
643      *
644      * @param $sugarSetValue Mixed value
645      * @param $url String value of URL for the link
646      */
647     private function nullGroup($superSetValue, $url) {
648         return $this->processDataGroup(4, $superSetValue, 'NULL', '', $url);
649     }
650
651
652     /**
653      * returns a name for the XML File
654      *
655      * @param string $file_id - unique id to make part of the file name
656      */
657     public static function getXMLFileName($file_id)
658     {
659         create_cache_directory("xml/".$GLOBALS['current_user']->getUserPrivGuid() . "_{$file_id}.xml");
660
661         return sugar_cached("xml/"). $GLOBALS['current_user']->getUserPrivGuid() . "_" . $file_id . ".xml";
662     }
663
664     public function processXmlData(){
665         $data = '';
666
667                 if ($this->chart_properties['type'] == 'group by chart'){
668                         $data .= $this->xmlDataForGroupByChart();
669                 }
670                 else if ($this->chart_properties['type'] == 'bar chart' || $this->chart_properties['type'] == 'horizontal bar chart'){
671                         $data .= $this->xmlDataBarChart();
672                 }
673                 else{
674                         $data .= $this->xmlDataGenericChart();
675                 }
676
677                 return $data;
678     }
679
680         function xmlData(){
681                 $data = $this->tab('<data>',1);
682                 $data .= $this->processXmlData();
683                 $data .= $this->tab('</data>',1);
684
685                 return $data;
686         }
687
688         /**
689      * function to generate XML and return it
690          *
691      * @param   none
692      * @return  string $xmlContents with xml information
693      */
694         function generateXML($xmlDataName = false){
695                 $xmlContents = $this->xmlHeader();
696                 $xmlContents .= $this->xmlProperties();
697                 $xmlContents .= $this->xmlData();
698                 $xmlContents .= $this->xmlYAxis();
699                 $xmlContents .= $this->xmlFooter();
700
701                 return $xmlContents;
702         }
703
704         /**
705      * function to save XML contents into a file
706          *
707      * @param   string $xmlFilename location of the xml file
708          *                      string $xmlContents contents of the xml file
709      * @return  string boolean denoting whether save has failed
710      */
711         function saveXMLFile($xmlFilename,$xmlContents) {
712                 global $app_strings;
713                 global $locale;
714
715                 $xmlContents = chr(255).chr(254).$GLOBALS['locale']->translateCharset($xmlContents, 'UTF-8', 'UTF-16LE');
716
717         // Create dir if it doesn't exist
718         $dir = dirname($xmlFilename);
719         if (!sugar_is_dir($dir))
720         {
721             sugar_mkdir($dir, null, true);
722         }
723
724                 // open file
725                 if (!$fh = sugar_fopen($xmlFilename, 'w')) {
726                         $GLOBALS['log']->debug("Cannot open file ($xmlFilename)");
727                         return;
728                 }
729
730                 // write the contents to the file
731                 if (fwrite($fh,$xmlContents) === FALSE) {
732                         $GLOBALS['log']->debug("Cannot write to file ($xmlFilename)");
733                         return false;
734                 }
735
736                 $GLOBALS['log']->debug("Success, wrote ($xmlContents) to file ($xmlFilename)");
737
738                 fclose($fh);
739                 return true;
740         }
741
742         /**
743      * generates xml file for Flash charts to use for internationalized instances
744          *
745      * @param   string $xmlFile location of the XML file to write to
746      * @return  none
747      */
748         function generateChartStrings($xmlFile){
749                 global $current_language, $app_list_strings;
750
751                 $chartStringsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
752                 $chartStringsXML .= "<sugarlanguage version=\"1.0\">\n";
753                 $chartStringsXML .= $this->tab("<charts>",1);
754
755                 if (empty($app_list_strings)) {
756                     //set module and application string arrays based upon selected language
757                         $app_list_strings = return_app_list_strings_language($current_language);
758                 }
759
760                 // retrieve the strings defined at include/language/en_us.lang.php
761                 foreach ($app_list_strings['chart_strings'] as $tag => $chart_string){
762                         $chartStringsXML .= $this->tab("<$tag>$chart_string</$tag>",2);
763                 }
764
765                 $chartStringsXML .= $this->tab("</charts>",1);
766                 $chartStringsXML .= "</sugarlanguage>\n";
767
768                 $this->saveXMLFile($xmlFile, $chartStringsXML);
769         }
770
771         /**
772      * wrapper function to return the html code containing the chart in a div
773          *
774      * @param   string $name    name of the div
775          *                      string $xmlFile location of the XML file
776          *                      string $style   optional additional styles for the div
777      * @return  string returns the html code through smarty
778      */
779         function display($name, $xmlFile, $width='320', $height='480', $resize=false){
780
781
782                 // generate strings for chart if it does not exist
783                 global $current_language, $theme, $sugar_config,$app_strings;
784
785                 $this->app_strings = $app_strings;
786                 $this->chartStringsXML = sugar_cached("xml/").'chart_strings.' . $current_language .'.lang.xml';
787                 if (!file_exists($this->chartStringsXML)){
788                         $this->generateChartStrings($this->chartStringsXML);
789                 }
790
791                 $templateFile = "";
792                 return $templateFile;
793         }
794
795
796         function getDashletScript($id,$xmlFile="") {
797
798         $xmlFile = (!$xmlFile) ? $sugar_config['tmp_dir']. $current_user->id . '_' . $this->id . '.xml' : $xmlFile;
799         $chartStringsXML = $GLOBALS['sugar_config']['tmp_dir'].'chart_strings.' . $current_language .'.lang.xml';
800
801         $this->ss->assign('chartName', $id);
802     $this->ss->assign('chartXMLFile', $xmlFile);
803     $this->ss->assign('chartStyleCSS', SugarThemeRegistry::current()->getCSSURL('chart.css'));
804     $this->ss->assign('chartColorsXML', SugarThemeRegistry::current()->getImageURL('sugarColors.xml'));
805     $this->ss->assign('chartLangFile', $GLOBALS['sugar_config']['tmp_dir'].'chart_strings.' . $GLOBALS['current_language'] .'.lang.xml');
806
807                 $templateFile = "";
808                 return $templateFile;
809         }
810
811
812   /**
813          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.
814          If the data quantity is large, it maybe a little slow.
815     * @param         array $data_set           The data get from database
816                            string $keycolname1      We will sort by this key first
817                            bool $translate1            Whether to trabslate the first column
818                            string $keycolname1      We will sort by this key secondly, and  it can be null, then it will only sort by the first column.
819                            bool $translate1            Whether to trabslate the second column
820                            bool $ifsort2                 Whether to sort by the second column or just translate the second column.
821     * @return        The sorted and translated data.
822    */
823     function sortData($data_set, $keycolname1=null, $translate1=false, $keycolname2=null, $translate2=false, $ifsort2=false) {
824         //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.
825         global $app_list_strings;
826         $sortby1[] = array();
827         foreach ($data_set as $row) {
828             $sortby1[]  = $row[$keycolname1];
829         }
830         $sortby1 = array_unique($sortby1);
831         //The data is from the database, the sorting should be done in the sql. So I will not do the sort here.
832         if($translate1) {
833             $temp_sortby1 = array();
834             foreach(array_keys($app_list_strings[$keycolname1.'_dom']) as $sortby1_value) {
835                 if(in_array($sortby1_value, $sortby1)) {
836                     $temp_sortby1[] = $sortby1_value;
837                 }
838             }
839             $sortby1 = $temp_sortby1;
840         }
841
842         //if(isset($sortby1[0]) && $sortby1[0]=='') unset($sortby1[0]);//the beginning of lead_source_dom is blank.
843         if(isset($sortby1[0]) && $sortby1[0]==array()) unset($sortby1[0]);//the beginning of month after search is blank.
844
845         if($ifsort2==false) $sortby2=array(0);
846
847         if($keycolname2!=null) {
848             $sortby2 = array();
849             foreach ($data_set as $row) {
850                 $sortby2[]  = $row[$keycolname2];
851             }
852             //The data is from the database, the sorting should be done in the sql. So I will not do the sort here.
853             $sortby2 = array_unique($sortby2);
854             if($translate2) {
855                 $temp_sortby2 = array();
856                 foreach(array_keys($app_list_strings[$keycolname2.'_dom']) as $sortby2_value) {
857                     if(in_array($sortby2_value, $sortby2)) {
858                         $temp_sortby2[] = $sortby2_value;
859                     }
860                 }
861                 $sortby2 = $temp_sortby2;
862             }
863         }
864
865         $data=array();
866
867         foreach($sortby1 as $sort1) {
868             foreach($sortby2 as $sort2) {
869                 if($ifsort2) $a=0;
870                 foreach($data_set as $key => $value){
871                     if($value[$keycolname1] == $sort1 && (!$ifsort2 || $value[$keycolname2]== $sort2)) {
872                         if($translate1) {
873                             $value[$keycolname1.'_dom_option'] = $value[$keycolname1];
874                             $value[$keycolname1] = $app_list_strings[$keycolname1.'_dom'][$value[$keycolname1]];
875                         }
876                         if($translate2) {
877                             $value[$keycolname2.'_dom_option'] = $value[$keycolname2];
878                             $value[$keycolname2] = $app_list_strings[$keycolname2.'_dom'][$value[$keycolname2]];
879                         }
880                         array_push($data, $value);
881                         unset($data_set[$key]);
882                         $a=1;
883                         }
884                 }
885                 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.
886                     $val=array();
887                     $val['total'] = 0;
888                     $val['count'] = 0;
889                     if($translate1) {
890                         $val[$keycolname1] = $app_list_strings[$keycolname1.'_dom'][$sort1];
891                         $val[$keycolname1.'_dom_option'] = $sort1;
892                     }
893                     else {
894                         $val[$keycolname1] = $sort1;
895                     }
896                     if($translate2) {
897                         $val[$keycolname2] = $app_list_strings[$keycolname2.'_dom'][$sort2];
898                         $val[$keycolname2.'_dom_option'] = $sort2;
899                     }
900                     elseif($keycolname2!=null) {
901                         $val[$keycolname2] = $sort2;
902                     }
903                     array_push($data, $val);
904                 }
905             }
906         }
907         return $data;
908     }
909
910     function getChartResources() {
911
912                 $resources = "";
913                 return $resources;
914         }
915
916         function getMySugarChartResources() {
917                 $mySugarResources = "";
918                 return $mySugarResources;
919         }
920
921         /**
922      * wrapper function to return chart array after any additional processing
923          *
924      * @param   array $chartsArray      array of chart config items that need processing
925      * @return  array $chartArray after it has been process
926      */
927         function chartArray($chartsArray) {
928
929                 return $chartsArray;
930         }
931
932 } // end class def