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