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