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