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