]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarCharts/SugarChart.php
Release 6.5.9
[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                     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                 // open file
717                 if (!$fh = sugar_fopen($xmlFilename, 'w')) {
718                         $GLOBALS['log']->debug("Cannot open file ($xmlFilename)");
719                         return;
720                 }
721
722                 // write the contents to the file
723                 if (fwrite($fh,$xmlContents) === FALSE) {
724                         $GLOBALS['log']->debug("Cannot write to file ($xmlFilename)");
725                         return false;
726                 }
727
728                 $GLOBALS['log']->debug("Success, wrote ($xmlContents) to file ($xmlFilename)");
729
730                 fclose($fh);
731                 return true;
732         }
733
734         /**
735      * generates xml file for Flash charts to use for internationalized instances
736          *
737      * @param   string $xmlFile location of the XML file to write to
738      * @return  none
739      */
740         function generateChartStrings($xmlFile){
741                 global $current_language, $app_list_strings;
742
743                 $chartStringsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
744                 $chartStringsXML .= "<sugarlanguage version=\"1.0\">\n";
745                 $chartStringsXML .= $this->tab("<charts>",1);
746
747                 if (empty($app_list_strings)) {
748                     //set module and application string arrays based upon selected language
749                         $app_list_strings = return_app_list_strings_language($current_language);
750                 }
751
752                 // retrieve the strings defined at include/language/en_us.lang.php
753                 foreach ($app_list_strings['chart_strings'] as $tag => $chart_string){
754                         $chartStringsXML .= $this->tab("<$tag>$chart_string</$tag>",2);
755                 }
756
757                 $chartStringsXML .= $this->tab("</charts>",1);
758                 $chartStringsXML .= "</sugarlanguage>\n";
759
760                 $this->saveXMLFile($xmlFile, $chartStringsXML);
761         }
762
763         /**
764      * wrapper function to return the html code containing the chart in a div
765          *
766      * @param   string $name    name of the div
767          *                      string $xmlFile location of the XML file
768          *                      string $style   optional additional styles for the div
769      * @return  string returns the html code through smarty
770      */
771         function display($name, $xmlFile, $width='320', $height='480', $resize=false){
772
773
774                 // generate strings for chart if it does not exist
775                 global $current_language, $theme, $sugar_config,$app_strings;
776
777                 $this->app_strings = $app_strings;
778                 $this->chartStringsXML = sugar_cached("xml/").'chart_strings.' . $current_language .'.lang.xml';
779                 if (!file_exists($this->chartStringsXML)){
780                         $this->generateChartStrings($this->chartStringsXML);
781                 }
782
783                 $templateFile = "";
784                 return $templateFile;
785         }
786
787
788         function getDashletScript($id,$xmlFile="") {
789
790         $xmlFile = (!$xmlFile) ? $sugar_config['tmp_dir']. $current_user->id . '_' . $this->id . '.xml' : $xmlFile;
791         $chartStringsXML = $GLOBALS['sugar_config']['tmp_dir'].'chart_strings.' . $current_language .'.lang.xml';
792
793         $this->ss->assign('chartName', $id);
794     $this->ss->assign('chartXMLFile', $xmlFile);
795     $this->ss->assign('chartStyleCSS', SugarThemeRegistry::current()->getCSSURL('chart.css'));
796     $this->ss->assign('chartColorsXML', SugarThemeRegistry::current()->getImageURL('sugarColors.xml'));
797     $this->ss->assign('chartLangFile', $GLOBALS['sugar_config']['tmp_dir'].'chart_strings.' . $GLOBALS['current_language'] .'.lang.xml');
798
799                 $templateFile = "";
800                 return $templateFile;
801         }
802
803
804   /**
805          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.
806          If the data quantity is large, it maybe a little slow.
807     * @param         array $data_set           The data get from database
808                            string $keycolname1      We will sort by this key first
809                            bool $translate1            Whether to trabslate the first column
810                            string $keycolname1      We will sort by this key secondly, and  it can be null, then it will only sort by the first column.
811                            bool $translate1            Whether to trabslate the second column
812                            bool $ifsort2                 Whether to sort by the second column or just translate the second column.
813     * @return        The sorted and translated data.
814    */
815     function sortData($data_set, $keycolname1=null, $translate1=false, $keycolname2=null, $translate2=false, $ifsort2=false) {
816         //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.
817         global $app_list_strings;
818         $sortby1[] = array();
819         foreach ($data_set as $row) {
820             $sortby1[]  = $row[$keycolname1];
821         }
822         $sortby1 = array_unique($sortby1);
823         //The data is from the database, the sorting should be done in the sql. So I will not do the sort here.
824         if($translate1) {
825             $temp_sortby1 = array();
826             foreach(array_keys($app_list_strings[$keycolname1.'_dom']) as $sortby1_value) {
827                 if(in_array($sortby1_value, $sortby1)) {
828                     $temp_sortby1[] = $sortby1_value;
829                 }
830             }
831             $sortby1 = $temp_sortby1;
832         }
833
834         //if(isset($sortby1[0]) && $sortby1[0]=='') unset($sortby1[0]);//the beginning of lead_source_dom is blank.
835         if(isset($sortby1[0]) && $sortby1[0]==array()) unset($sortby1[0]);//the beginning of month after search is blank.
836
837         if($ifsort2==false) $sortby2=array(0);
838
839         if($keycolname2!=null) {
840             $sortby2 = array();
841             foreach ($data_set as $row) {
842                 $sortby2[]  = $row[$keycolname2];
843             }
844             //The data is from the database, the sorting should be done in the sql. So I will not do the sort here.
845             $sortby2 = array_unique($sortby2);
846             if($translate2) {
847                 $temp_sortby2 = array();
848                 foreach(array_keys($app_list_strings[$keycolname2.'_dom']) as $sortby2_value) {
849                     if(in_array($sortby2_value, $sortby2)) {
850                         $temp_sortby2[] = $sortby2_value;
851                     }
852                 }
853                 $sortby2 = $temp_sortby2;
854             }
855         }
856
857         $data=array();
858
859         foreach($sortby1 as $sort1) {
860             foreach($sortby2 as $sort2) {
861                 if($ifsort2) $a=0;
862                 foreach($data_set as $key => $value){
863                     if($value[$keycolname1] == $sort1 && (!$ifsort2 || $value[$keycolname2]== $sort2)) {
864                         if($translate1) {
865                             $value[$keycolname1.'_dom_option'] = $value[$keycolname1];
866                             $value[$keycolname1] = $app_list_strings[$keycolname1.'_dom'][$value[$keycolname1]];
867                         }
868                         if($translate2) {
869                             $value[$keycolname2.'_dom_option'] = $value[$keycolname2];
870                             $value[$keycolname2] = $app_list_strings[$keycolname2.'_dom'][$value[$keycolname2]];
871                         }
872                         array_push($data, $value);
873                         unset($data_set[$key]);
874                         $a=1;
875                         }
876                 }
877                 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.
878                     $val=array();
879                     $val['total'] = 0;
880                     $val['count'] = 0;
881                     if($translate1) {
882                         $val[$keycolname1] = $app_list_strings[$keycolname1.'_dom'][$sort1];
883                         $val[$keycolname1.'_dom_option'] = $sort1;
884                     }
885                     else {
886                         $val[$keycolname1] = $sort1;
887                     }
888                     if($translate2) {
889                         $val[$keycolname2] = $app_list_strings[$keycolname2.'_dom'][$sort2];
890                         $val[$keycolname2.'_dom_option'] = $sort2;
891                     }
892                     elseif($keycolname2!=null) {
893                         $val[$keycolname2] = $sort2;
894                     }
895                     array_push($data, $val);
896                 }
897             }
898         }
899         return $data;
900     }
901
902     function getChartResources() {
903
904                 $resources = "";
905                 return $resources;
906         }
907
908         function getMySugarChartResources() {
909                 $mySugarResources = "";
910                 return $mySugarResources;
911         }
912
913         /**
914      * wrapper function to return chart array after any additional processing
915          *
916      * @param   array $chartsArray      array of chart config items that need processing
917      * @return  array $chartArray after it has been process
918      */
919         function chartArray($chartsArray) {
920
921                 return $chartsArray;
922         }
923
924 } // end class def