* SPDX-License-Identifier: AGPL-3.0-only
************************************/
require_once('data/CRMEntity.php');
require_once('data/Tracker.php');
require_once('modules/PickList/PickListUtils.php');
require_once('modules/Reports/ReportRun.php');
/* crmv@82770 - support for ChartJS */
/* crmv@83040 - support for drill-down */
// pChart classes
require_once('include/pChart/class/pData.class.php');
require_once('include/pChart/class/pDraw.class.php');
require_once('include/pChart/class/pImage.class.php');
require_once('include/pChart/class/pPie.class.php');
require_once('include/pChart/class/pSplit.class.php');
require_once('include/pChart/class/pScatter.class.php'); // crmv@53923
class Charts extends CRMEntity {
public $db, $log; // Used in class functions of CRMEntity
public $table_name = 'vte_charts';
public $table_index= 'chartid';
public $column_fields = Array();
/** Indicator if this is a custom module or standard module */
public $IsCustomModule = true;
/**
* Mandatory table for supporting custom fields.
*/
public $customFieldTable = Array('vte_chartscf', 'chartid');
/**
* Mandatory for Saving, Include tables related to this module.
*/
public $tab_name = Array('vte_crmentity', 'vte_charts', 'vte_chartscf', 'vte_chartscache');
/**
* Mandatory for Saving, Include tablename and tablekey columnname here.
*/
public $tab_name_index = Array(
'vte_crmentity' => 'crmid',
'vte_charts' => 'chartid',
'vte_chartscf' => 'chartid',
'vte_chartscache' => 'chartid');
/**
* Mandatory for Listing (Related listview)
*/
public $list_fields = Array (
/* Format: Field Label => Array(tablename, columnname) */
// tablename should not have prefix 'vte_'
'Chart Name'=> Array('charts', 'chartname'),
'Assigned To' => Array('crmentity','smownerid')
);
public $list_fields_name = Array(
/* Format: Field Label => fieldname */
'Chart Name'=> 'chartname',
'Assigned To' => 'assigned_user_id'
);
// Make the field link to detail view from list view (Fieldname)
public $list_link_field = 'chartname';
// For Popup listview and UI type support
public $search_fields = Array(
/* Format: Field Label => Array(tablename, columnname) */
// tablename should not have prefix 'vte_'
'Chart Name'=> Array('charts', 'chartname')
);
public $search_fields_name = Array(
/* Format: Field Label => fieldname */
'Chart Name'=> 'chartname'
);
// For Popup window record selection
public $popup_fields = Array('chartname');
// Placeholder for sort fields - All the fields will be initialized for Sorting through initSortFields
public $sortby_fields = Array();
// For Alphabetical search
public $def_basicsearch_col = 'chartname';
// Column value to use on detail view record text display
public $def_detailview_recname = 'chartname';
// Required Information for enabling Import feature
public $required_fields = Array('chartname'=>1);
public $default_order_by = 'chartname';
public $default_sort_order='ASC';
// Used when enabling/disabling the mandatory fields for the module.
// Refers to vte_field.fieldname values.
public $mandatory_fields = Array('createdtime', 'modifiedtime', 'chartname');
//crmv@10759
public $search_base_field = 'chartname';
//crmv@10759 e
public $chartLibrary = 'ChartJS'; // can be pChart or ChartJS. pChart is currently deprecated and will be removed in the future
public $cachedir = 'cache/charts/';
public $cachefile_date = null;
public $image_size_x = 512;
public $image_size_y = 384;
public $graph_size_ratio = 3.2; // pie radius (=imagesize/ratio)
public $thumbnail_size = 160;
public $limit_data = 50; // in caso non si usi il merge, limito i dati
public $filtered_data = false; // prende i dati dalle tabelle con i dati filtrati // crmv@31209
public $label_split = 12; // split labels longer than 20chars // crmv@30976
public $merge_threshold = 3; // percentuale al di sotto della quale accorpare gli spicchi piccoli
public $homestuffid = null;
public $cachefield = 'chart_filename';
public $cache_duration = 24; // crmv@134727 - cache duration, in hours (for new js charts)
public $default_options = array(
// general options
'Main' => array(
'TitleHeight' => 0, // altezza della barra del titolo // TODO: remove me
'Padding' => 0, //
'PaddingLeft' => 0, // This padding is added only to the left
'PaddingBottom' => 0, // This padding is added only at the bottom
'GraphShadow' => true,
'ShowLegend' => false,
'StretchPalette' => false, // stretch palette to fit the number of values
),
'Scale' => array(
'ScaleSpacing' => 1,
),
'Legend' => array(
'Mode' => LEGEND_VERTICAL,
'Style' => LEGEND_ROUND,
'R' => 0xE0, 'G' => 0xE0, 'B' => 0xE0, "Alpha" => 80,
'BorderR' => 0xD0, 'BorderG' => 0xD0, 'BorderB' => 0xD0,
'FontName' => 'include/pChart/fonts/MYRIADPRO-REGULAR.OTF',
'FontSize' => 10,
//'Family' => LEGEND_FAMILY_CIRCLE,
'Position' => TEXT_ALIGN_TOPLEFT, // not a Pchart option!!, valid values: same as drawText format, only starting with LEFT
),
// specific options for graph types
'BarVertical' => array(
'DisplayOrientation' => ORIENTATION_VERTICAL,
'DisplayValues' => TRUE,
'Rounded' => TRUE,
'Surrounding' => -20,
'DisplayPos' => LABEL_POS_OUTSIDE,
'RecordImageMap' => TRUE,
'CyclePalette' => TRUE,
//'Gradient' => TRUE, // only if rounded false
),
'BarHorizontal' => array(
'DisplayOrientation' => ORIENTATION_HORIZONTAL,
'DisplayValues' => TRUE,
'Rounded' => TRUE,
'Surrounding' => -20,
'RecordImageMap' => TRUE,
'DisplayPos' => LABEL_POS_OUTSIDE,
'CyclePalette' => TRUE,
),
'Line' => array(
'LineWeight' => 1, // not a pChart option // crmv@53923
'DisplayValues' => TRUE,
'RecordImageMap' => TRUE,
),
'Pie' => array(
'DrawLabels'=>TRUE,
'DrawLabelLine' => FALSE,
'DrawLabelArrow' => TRUE,
'LabelDistance' => 20,
'LabelArrowSize' => 6,
'SecondPass' => TRUE,
'WriteValues' => FALSE,
'ValueR' => 0x40, 'ValueG' => 0x40, 'ValueB' => 0x40,
'Border' => TRUE,
'BorderR' => 0xF0, 'BorderG' => 0xF0, 'BorderB' => 0xF0,
//'LabelStacked' => TRUE,
'LabelColor' => PIE_LABEL_COLOR_MANUAL,
'LabelR' => 0x70, 'LabelG' => 0x70, 'LabelB' => 0x70,
'RecordImageMap' => TRUE,
'CyclePalette' => TRUE, // if false, use random colors
),
// DEPRECATED
/*'Pie3D' => array(
'DrawLabels'=>TRUE,
'DrawLabelLine' => FALSE,
'DrawLabelArrow' => TRUE,
'LabelDistance' => 20,
'LabelArrowSize' => 6,
//'LabelStacked' => TRUE,
'LabelColor' => PIE_LABEL_COLOR_MANUAL,
'LabelR' => 0x40, 'LabelG' => 0x40, 'LabelB' => 0x40,
'SecondPass' => TRUE,
'WriteValues' => FALSE,
'ValueR' => 0x40, 'ValueG' => 0x40, 'ValueB' => 0x40,
//'ValuePadding' => 0,
'Border' => TRUE,
'BorderR' => 0xD0, 'BorderG' => 0xD0, 'BorderB' => 0xD0,
'RecordImageMap' => TRUE,
'CyclePalette' => TRUE,
),
*/
'Ring' => array(
'DrawLabels'=>TRUE,
'DrawLabelLine' => FALSE,
'DrawLabelArrow' => TRUE,
'LabelDistance' => 20,
'LabelArrowSize' => 6,
'LabelColor' => PIE_LABEL_COLOR_MANUAL,
'LabelR' => 0x70, 'LabelG' => 0x70, 'LabelB' => 0x70,
'WriteValues' => FALSE,
'ValueR' => 0x40, 'ValueG' => 0x40, 'ValueB' => 0x40,
'Border' => TRUE,
'BorderR' => 0xD0, 'BorderG' => 0xD0, 'BorderB' => 0xD0,
'RecordImageMap' => TRUE,
'CyclePalette' => TRUE,
),
// DEPRECATED
/*
'Ring3D' => array(
'DrawLabels'=>TRUE,
'DrawLabelLine' => FALSE,
'DrawLabelArrow' => TRUE,
'LabelDistance' => 20,
'LabelArrowSize' => 6,
'LabelColor' => PIE_LABEL_COLOR_MANUAL,
'LabelR' => 0x40, 'LabelG' => 0x40, 'LabelB' => 0x40,
'WriteValues' => FALSE,
'ValueR' => 0x40, 'ValueG' => 0x40, 'ValueB' => 0x40,
'DataGapAngle' => 0,
'DataGapRadius' => 0,
'RecordImageMap' => TRUE,
'CyclePalette' => TRUE,
),
*/
'Split' => array(
'TextPos' => TEXT_POS_RIGHT,
'TextPadding'=>10,
'Spacing'=>20,
'Surrounding'=>40,
'RecordImageMap' => TRUE,
'CyclePalette' => TRUE,
),
// crmv@53923
'Scatter' => array(
)
// crmv@53923e
);
public $default_options_js = array(
// general options
'Main' => array(
'chartjs' => array(
'animation' => true,
'tooltipFontSize' => 12,
'legend' => false,
//'tooltipTemplate' => "<%if (label){%><%=label%>: <%}%><%= value %> (<%= Math.round(circumference / 6.283 * 100) %>%)", // TODO: find out how to put the percentage
),
),
'BarVertical' => array(
'chartjs' => array(
'barStrokeWidth' => 1,
// legend is disabled for bars,since there is only one serie
//'legendTemplate' => "
-legend\"><% for (var i=0; i- \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
",
'tooltipTemplate' => "<%=label%>###<%=value%>", // this is handled by javascript
),
),
'BarHorizontal' => array(
'chartjs' => array(
'barStrokeWidth' => 1,
//'legendTemplate' => "-legend\"><% for (var i=0; i- \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
",
'tooltipTemplate' => "<%=label%>###<%=value%>", // this is handled by javascript
),
),
'Pie' => array(
'chartjs' => array(
'legendTemplate' => "-legend\"><% for (var i=0; i- \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
<%}%>
",
'tooltipTemplate' => "<%if (label){%><%=label%>: <%}%><%= formatUserNumber(value) %> (<%= Math.round(circumference / 6.283 * 100) %>%)",
),
),
'Ring' => array(
'chartjs' => array(
'legendTemplate' => "-legend\"><% for (var i=0; i- \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
<%}%>
",
'tooltipTemplate' => "<%if (label){%><%=label%>: <%}%><%= formatUserNumber(value) %> (<%= Math.round(circumference / 6.283 * 100) %>%)",
),
),
'Line' => array(
'chartjs' => array(
'tooltipTemplate' => "<%=label%>###<%=value%>", // this is handled by javascript
),
),
);
function __construct() {
global $log, $table_prefix; // crmv@97862
parent::__construct(); // crmv@37004
$this->column_fields = getColumnFields('Charts'); // crmv@97862
$this->db = PearDatabase::getInstance();
$this->log = $log;
$this->table_name = $table_prefix.'_charts';
$this->customFieldTable = Array($table_prefix.'_chartscf', 'chartid');
$this->tab_name = Array($table_prefix.'_crmentity', $table_prefix.'_charts', $table_prefix.'_chartscf', $table_prefix.'_chartscache');
$this->tab_name_index = Array(
$table_prefix.'_crmentity' => 'crmid',
$table_prefix.'_charts' => 'chartid',
$table_prefix.'_chartscf' => 'chartid',
$table_prefix.'_chartscache' => 'chartid',
);
}
function save_module($module) {
global $adb;
// invalidate filename
$this->invalidateCache();
}
// crmv@113417
function retrieve_entity_info($recordid, $module, $dieOnError=true, $onlyFields = array()) {
$ret = parent::retrieve_entity_info($recordid, $module, $dieOnError, $onlyFields);
// get cache file date
$fname = $this->column_fields[$this->cachefield];
$this->cachefile_date[$this->cachefield] = null;
if (!empty($fname) && is_readable($fname)) {
$this->cachefile_date[$this->cachefield] = filemtime($fname);
}
return $ret;
}
// crmv@113417e
/**
* Return query to use based on given modulename, fieldname
* Useful to handle specific case handling for Popup
*/
function getQueryByModuleField($module, $fieldname, $srcrecord) {
// $srcrecord could be empty
}
function getQueryExtraJoin() {
global $table_prefix;
return "LEFT JOIN {$table_prefix}_report ON {$table_prefix}_charts.reportid = {$table_prefix}_report.reportid";
}
function getQueryExtraWhere() {
global $table_prefix, $current_user;
$repquery = '';
if ($current_user->is_admin != 'on') {
$userGroups = new GetUserGroups();
$userGroups->getAllUserGroups($current_user->id);
$user_groups = $userGroups->user_groups;
$groupquery = '';
if (!empty($user_groups)) {
$groupquery = "OR (setype = 'groups' AND shareid in (".implode(',', $user_groups)."))";
}
// crmv@144121
$repquery = " AND (
({$table_prefix}_report.sharingtype = 'Public') OR
({$table_prefix}_report.owner = '{$current_user->id}') OR
({$table_prefix}_report.sharingtype = 'Shared' AND EXISTS (
SELECT reportid FROM {$table_prefix}_reportsharing WHERE reportid = {$table_prefix}_charts.reportid AND ((setype = 'users' AND shareid = '{$current_user->id}') $groupquery)
))
)";
// crmv@144121e
}
// crmv@30967
$fldid = intval($_REQUEST['folderid']);
if ($fldid > 0) $repquery .= " and {$this->table_name}.folderid = '$fldid'";
// crmv@30967e
return $repquery;
}
// set the field that holds the filename of the cached file
function setCacheField($fieldcache = 'chart_filename') {
if (!empty($fieldcache)) {
$this->cachefield = $fieldcache;
$fname = $this->column_fields[$this->cachefield];
if (!empty($fname) && is_readable($fname)) {
$this->cachefile_date[$this->cachefield] = filemtime($fname);
}
}
}
// genera il nome del file in cui scrivere il grafico
function generateFileName($format = 'png') {
if (!is_dir($this->cachedir)) {
@mkdir($this->cachedir, 0755, true);
}
if (!is_writable($this->cachedir)) {
//throw new Exception("Directory $basedir is not writable.");
return null;
}
$randval = uniqid().mt_rand(0, 1000);
return $this->cachedir."chart_{$randval}.$format";
}
function initGraphOptions(&$DataSet) {
if ($this->chartLibrary == 'pChart') {
return $this->initGraphOptionsPChart($DataSet);
} elseif ($this->chartLibrary == 'ChartJS') {
return $this->initGraphOptionsChartJS($DataSet);
}
}
function initGraphOptionsChartJS(&$rawdata) {
$type = $this->column_fields['chart_type'];
if (empty($type)) return null;
if ($this->column_fields['chart_legend'] == 1) {
if ($type == 'Pie' || $type == 'Ring') {
$this->default_options_js['Main']['chartjs']['legend'] = true;
}
}
if ($this->column_fields['chart_values'] != 'ChartValuesNone') {
if ($type == 'Pie' || $type == 'Ring') {
$this->default_options_js['Main']['chartjs']['drawLabels'] = true;
$this->default_options_js['Main']['chartjs']['labelType'] = $this->column_fields['chart_values'];
}
}
if ($this->column_fields['chart_labels'] == 1) {
if ($type == 'Pie' || $type == 'Ring') {
$this->default_options_js['Main']['chartjs']['drawLabels'] = true;
$this->default_options_js['Main']['chartjs']['labelType'] = 'ChartLabels';
}
}
}
// change default options according to the graph type
function initGraphOptionsPChart(&$DataSet) { // crmv@53923
$type = $this->column_fields['chart_type'];
if (empty($type)) return null;
$explode = $this->column_fields['chart_exploded'];
$dataMin = $DataSet->getMin('Serie1'); // crmv@41431
$dataMax = $DataSet->getMax('Serie1'); //crmv@73193
switch ($type) {
case 'BarVertical':
$this->default_options['Scale']['Pos'] = SCALE_POS_LEFTRIGHT;
//crmv@73193
//if ($dataMin >= 0) $this->default_options['Scale']['Mode'] = SCALE_MODE_START0; // crmv@41431
if ($dataMin >= 0){
$this->default_options['Scale']['Mode'] = SCALE_MODE_MANUAL;
$min = 0;
$max = round((floatval($dataMax)*110/100),0, PHP_ROUND_HALF_UP); //add 10% to max value to avoid cutting labels
$AxisBoundaries = array(0=>array("Min"=>$min,"Max"=>$max));
$this->default_options['Scale']['ManualScale']=$AxisBoundaries;
}
//crmv@73193e
$this->default_options[$type]['OverrideColors'] = $DataSet->getPalette();
// crmv@53923 - calculate the bottom padding
$values = $DataSet->getValues('Labels');
if (is_array($values)) {
$maxNewLine = 0;
foreach ($values as $v) {
$c = substr_count($v, "\n");
if ($c > $maxNewLine) $maxNewLine = $c;
}
$this->default_options['Main']["PaddingBottom"] = 18*($maxNewLine+1);
}
// crmv@53923e
break;
case 'BarHorizontal':
$this->default_options['Scale']['Pos'] = SCALE_POS_TOPBOTTOM;
if ($dataMin >= 0) $this->default_options['Scale']['Mode'] = SCALE_MODE_START0; // crmv@41431
$this->default_options[$type]['OverrideColors'] = $DataSet->getPalette();
$this->default_options['Main']["PaddingLeft"] = 80;
break;
case 'Line':
// crmv@53923
$dataMax = $DataSet->getMax('Serie1');
$dataMaxLen = strlen(strval(intval($dataMax)));
$this->default_options['Scale']["DrawSubTicks"] = TRUE;
$this->default_options['Main']["Padding"] = 20;
$this->default_options['Main']["PaddingLeft"] = 5*$dataMaxLen;
// crmv@53923e
break;
case 'Pie':
$this->default_options[$type]['Radius'] = min($this->image_size_x, $this->image_size_y)/$this->graph_size_ratio;
if ($explode) {
$this->default_options[$type]['DataGapAngle'] = 8;
$this->default_options[$type]['DataGapRadius'] = 10;
}
break;
// DEPRECATED
/*case 'Pie3D':
$this->default_options[$type]['Radius'] = min($this->image_size_x, $this->image_size_y)/$this->graph_size_ratio;
if ($explode) {
$this->default_options[$type]['DataGapAngle'] = 8;
$this->default_options[$type]['DataGapRadius'] = 10;
}
break;
*/
case 'Ring':
$this->default_options[$type]['InnerRadius'] = min($this->image_size_x, $this->image_size_y)/(2*$this->graph_size_ratio);
$this->default_options[$type]['OuterRadius'] = min($this->image_size_x, $this->image_size_y)/$this->graph_size_ratio;
if ($explode) {
$this->default_options[$type]['DataGapAngle'] = 8;
$this->default_options[$type]['DataGapRadius'] = 10;
}
break;
// DEPRECATED
/*case 'Ring3D':
$this->default_options[$type]['InnerRadius'] = min($this->image_size_x, $this->image_size_y)/(2*$this->graph_size_ratio);
$this->default_options[$type]['OuterRadius'] = min($this->image_size_x, $this->image_size_y)/$this->graph_size_ratio;
if ($explode) {
$this->default_options[$type]['DataGapAngle'] = 8;
$this->default_options[$type]['DataGapRadius'] = 10;
}
break;
*/
// DEPRECATED
/*case 'Split':
$this->default_options['Main']['GraphShadow']= false;
break;
*/
// crmv@53923 - EXPERIMENTAL
case 'Scatter':
$DataSet->setAxisXY(0,AXIS_X);
$DataSet->setAxisPosition(0,AXIS_POSITION_BOTTOM);
$DataSet->setSerieOnAxis("Serie1",1);
$DataSet->setAxisXY(1,AXIS_Y);
$DataSet->setAxisPosition(1,AXIS_POSITION_LEFT);
$DataSet->setScatterSerie('Labels', 'Serie1', 0);
$dataMax = $DataSet->getMax('Serie1');
$dataMaxLen = strlen(strval(intval($dataMax)));
$this->default_options['Main']["Padding"] = 20;
$this->default_options['Main']["PaddingLeft"] = 5*$dataMaxLen;
break;
// crmv@53923e
}
if ($this->column_fields['chart_legend'] == 1) {
$this->default_options['Main']['ShowLegend'] = true;
}
if ($this->column_fields['chart_labels'] == 0) {
$this->default_options[$type]['DrawLabels'] = FALSE;
}
switch ($this->column_fields['chart_values']) {
case 'ChartValuesRaw':
$this->default_options[$type]['WriteValues'] = PIE_VALUE_NATURAL;
$this->default_options[$type]['ValuePosition'] = PIE_VALUE_INSIDE;
break;
case 'ChartValuesPercent':
$this->default_options[$type]['WriteValues'] = PIE_VALUE_PERCENTAGE;
$this->default_options[$type]['ValuePosition'] = PIE_VALUE_INSIDE;
break;
case 'ChartValuesNone':
default:
$this->default_options[$type]['WriteValues'] = FALSE;
$this->default_options[$type]['ValuePosition'] = PIE_VALUE_INSIDE;
break;
}
}
protected function getValueColumn($level = 1) {
// crmv@30976
$formula = $this->column_fields['chart_formula'];
switch ($formula) {
default:
case 'COUNT':
$formulacol = "count_liv{$level}";
break;
case 'SUM':
$formulacol = "formula{$level}_sum";
break;
case 'AVG':
$formulacol = "formula{$level}_avg";
break;
case 'MIN':
$formulacol = "formula{$level}_min";
break;
case 'MAX':
$formulacol = "formula{$level}_max";
break;
}
// crmv@30976e
return $formulacol;
}
// recupera i dati per disegnare il grafico
function getChartData($level = 1, $levelIds = array()) {
global $adb, $table_prefix, $current_user; // crmv@97862
$level = intval($level) ?: 1;
$ret = array();
$reportid = $this->column_fields['reportid'];
if (empty($reportid)) return null;
$formulacol = $this->getValueColumn($level);
// crmv@31209 crmv@185894
$oReportRun = ReportRun::getInstance($reportid);
$oReportRun->enableCacheDb();
$datatable_master = $datatable = $oReportRun->getLivTable('levels');
if ($this->filtered_data) $datatable = $oReportRun->getLivTable('liv',$level);
// crmv@31209e crmv@185894e
// TODO: controlla se report ha i conteggi
// TODO: limite di dati presi
// TODO: check query for Oracle and MsSQL
// crmv@104070 crmv@186088
// basic check to see if there are rows
if (!$this->filtered_data) {
$res = $adb->limitPquerySlave('Reports',"select reportid FROM {$datatable_master} where reportid = ? and userid = ?", 0, 1, array($reportid, $current_user->id)); // crmv@185894
if ($res && $adb->num_rows($res) == 0) {
// no rows, maybe it has never run!
$this->reloadReport();
}
}
// crmv@104070e crmv@186088e
// crmv@134727
// info generali sul report
$res = $adb->pquerySlave('Reports', // crmv@185894
"SELECT r.reportname, rs.generatedtime
FROM {$table_prefix}_report r
LEFT JOIN {$table_prefix}_report_stats rs ON rs.reportid = r.reportid AND rs.userid = ?
WHERE r.reportid = ?",
array($current_user->id, $reportid)
);
if ($res) {
$ret['reportname'] = $adb->query_result_no_html($res, 0, 'reportname');
$this->chart_title = $ret['reportname'] . (empty($this->column_fields['chartname']) ? '' : (' - '.$this->column_fields['chartname']) );
$ret['generatedtime'] = $adb->query_result_no_html($res, 0, 'generatedtime');
}
// crmv@172355
// check if report has summary, but allow sdk reports
$oReportRun = $this->getReportRunObj();
if ($oReportRun instanceof ReportRun && !$oReportRun->hasSummary()) return 'NO_SUMMARY';
// crmv@172355e
// regenerate if too old
$cacheDate = time() - $this->cache_duration*3600;
if (empty($ret['generatedtime']) || strtotime($ret['generatedtime']) < $cacheDate) {
$this->reloadReport();
$ret['generatedtime'] = date('Y-m-d H:i:s');
}
// crmv@134727e
$params = array($reportid, $current_user->id); // crmv@97862
$subwhere = '';
// conditions for sub levels
if ($level > 1 && is_array($levelIds) && count($levelIds) > 0) {
$i = 0;
$levelIds = array_values($levelIds);
for ($slevel=1; $slevel<$level; ++$slevel) {
$levelid = $levelIds[$i++];
$subwhere .= " AND id_liv{$slevel} = ?";
$params[] = $levelid;
}
}
// ordino per conteggio
if ($this->column_fields['chart_order_data'] == 'OrderAsc') {
$orderby = "ORDER BY val ASC"; // crmv@165801
} elseif ($this->column_fields['chart_order_data'] == 'OrderDesc') {
$orderby = "ORDER BY val DESC"; // crmv@165801
} else {
$orderby = '';
}
$colors = $this->getReportColors(); // crmv@133997
$total = 0;
$total_rows = 0;
// limito i valori nel caso non abbia il merge attivo
// crmv@185894
if ($this->column_fields['chart_merge_small']) {
$res = $adb->pquerySlave('Reports',"SELECT MAX(value_liv{$level}) AS valname, MAX($formulacol) AS val, id_liv{$level} AS dataid FROM $datatable WHERE reportid = ? AND userid = ? $subwhere GROUP BY id_liv{$level} ".$orderby, $params); // crmv@30976 crmv@31209 crmv@97862 crmv@165801
if ($res) $total_rows = $adb->num_rows($res);
} else {
$rescount = $adb->pquerySlave('Reports',"SELECT COUNT(*) as rcount FROM (SELECT id_liv{$level} FROM {$datatable} WHERE reportid = ? AND userid = ? $subwhere GROUP BY id_liv{$level}) tt", $params); // crmv@97862 crmv@192261
if ($rescount) $total_rows = $adb->query_result_no_html($rescount, 0, 'rcount');
unset($rescount);
$res = $adb->limitpQuerySlave('Reports',"SELECT MAX(value_liv{$level}) AS valname, MAX($formulacol) AS val, id_liv{$level} AS dataid FROM $datatable WHERE reportid = ? AND userid = ? $subwhere GROUP BY id_liv{$level} ".$orderby, 0, $this->limit_data, $params); // crmv@30976 crmv@31209 crmv@97862 crmv@165801
}
// crmv@185894e
if ($res) {
// crmv@109353
while ($row = $adb->FetchByAssoc($res, -1, false)) {
$value = $row['val'];
$label = $row['valname'];
// check/convert date fields
if (trim($label) === '') {
$label = getTranslatedString('LBL_EMPTY_LABEL', 'Charts');
} elseif (preg_match('/^[12][0-9]{3}-[01][0-9]-[0123][0-9]$/', $label)) {
$label = getDisplayDate($label);
}
// crmv@30976
if ($this->label_split > 0) {
$label = wordwrap($label, $this->label_split);
}
// crmv@30976e
$ret['values'][] = $value;
$ret['labels'][] = decode_html($label); //crmv@123493
$ret['dataids'][] = $row['dataid'];
// crmv@133997
if ($level == 1 && !empty($colors)) {
$color = $colors[$row['valname']];
$ret['colors'][] = $color;
}
// crmv@133997e
$total += $value;
}
// crmv@109353e
}
// controllo se ho limitato i risultati
$ret['limited'] = ($total_rows != count($ret['values']));
$this->mergeSmallData($ret, $total);
if (is_array($ret['values'])) $ret['values'] = array_values($ret['values']);
if (is_array($ret['labels'])) $ret['labels'] = array_values($ret['labels']);
if (is_array($ret['dataids'])) $ret['dataids'] = array_values($ret['dataids']);
return $ret;
}
public function getLevelIdsValues($levelIds) { // crmv@99131
global $adb;
$values = array();
$level = count($levelIds)+1;
$reportid = $this->column_fields['reportid'];
// crmv@185894
$oReportRun = ReportRun::getInstance($reportid);
$oReportRun->enableCacheDb();
// crmv@185894e
$formulacol = $this->getValueColumn($level);
$params = array($reportid);
$subwhere = '';
if ($level > 1) {
$i = 0;
$levelIds = array_values($levelIds);
for ($slevel=1; $slevel<$level; ++$slevel) {
$levelid = $levelIds[$i++];
$subwhere .= " AND id_liv{$slevel} = ?";
$params[] = $levelid;
}
$res = $adb->limitpQuerySlave('Reports',"SELECT * FROM ".$oReportRun->getLivTable('levels')." WHERE reportid = ? $subwhere ", 0, 1, $params); // crmv@30976 crmv@31209 crmv@185894
if ($res) {
$row = $adb->FetchByAssoc($res, -1, false);
for ($slevel=1; $slevel<$level; ++$slevel) {
$levelid = $row['id_liv'.$slevel];
$values[$levelid] = array(
'dataid' => $levelid,
'label' => trim($row['value_liv'.$slevel]) ?: getTranslatedString('LBL_EMPTY_LABEL', 'Charts'),
'count' => $row['count_liv'.$slevel],
'value' => $row[$formulacol],
);
}
}
}
return $values;
}
public function getMaxLevels() { // crmv@99131
global $adb, $current_user;
$levels = 0;
$reportid = $this->column_fields['reportid'];
// crmv@185894
$oReportRun = ReportRun::getInstance($reportid);
$oReportRun->enableCacheDb();
$res = $adb->limitpQuerySlave('Reports',"SELECT * FROM ".$oReportRun->getLivTable('levels')." WHERE reportid = ? AND userid = ?", 0, 1, array($reportid, $current_user->id)); // crmv@143801
// crmv@185894e
if ($res) {
$row = $adb->FetchByAssoc($res, -1, false);
for ($i=1; $i<=7; ++$i) {
if (isset($row['id_liv'.$i]) && $row['id_liv'.$i] > 0) {
++$levels;
} else {
break;
}
}
}
return $levels;
}
// crmv@99131
public function getLevelTitles() { // crmv@99131
global $adb, $table_prefix;
$names = array();
$reportid = $this->column_fields['reportid'];
$reports = Reports::getInstance();
$fields = $reports->getColumns($reportid);
foreach ($fields as $field) {
if ($field['group'] == 1 && $field['fieldid'] > 0) {
$finfo = $reports->getFieldInfoById($field['fieldid']);
if ($finfo['label']) {
$names[] = $finfo['label'];
} else {
$names[] = getTranslatedString($finfo['fieldlabel'], $finfo['module']);
}
}
}
return $names;
}
// crmv@99131e
// unisce i dati troppo piccoli
protected function mergeSmallData(&$ret, $valuetotal) {
// unisco gli spicchi piccoli (sommo i valori che sono < del 3%)
if ($valuetotal > 0 && $this->column_fields['chart_merge_small']) {
$replace_pos = null;
$replace_val = 0;
$remove_data = array();
$i = 0;
foreach ($ret['values'] as $v) {
$vpercent = 100.0*$v/$valuetotal;
if ($vpercent < $this->merge_threshold) {
$replace_val += $v;
if (is_null($replace_pos)) {
$replace_pos = $i;
} else {
$remove_data[] = $i;
}
}
++$i;
}
if (!is_null($replace_pos) && count($remove_data) > 0) {
$remove_data = array_reverse($remove_data);
foreach ($remove_data as $rpos) {
unset($ret['values'][$rpos]);
unset($ret['labels'][$rpos]);
unset($ret['dataids'][$rpos]);
}
$ret['values'][$replace_pos] = $replace_val;
$ret['labels'][$replace_pos] = getTranslatedString('LBL_OTHERS_LABEL', 'Charts')." (".strval(count($remove_data)+1).")";
$ret['dataids'][$replace_pos] = -1;
}
}
}
// restituisce un set di dati per le anteprime
function getChartDemoData() {
$this->chart_title = 'test';
$ret = array();
$ret['values'] = array(5, 7,12, 1,17, 9,27,13,52, 48, 22);
$ret['labels'] = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
$ret['dataids'] = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
$total = array_sum($ret['values']);
$this->mergeSmallData($ret, $total);
if ($this->column_fields['chart_order_data'] == 'OrderAsc') {
$vlist = array_combine($ret['labels'], $ret['values']);
asort($vlist);
$ret['values'] = array_values($vlist);
$ret['labels'] = array_keys($vlist);
} elseif ($this->column_fields['chart_order_data'] == 'OrderDesc') {
$vlist = array_combine($ret['labels'], $ret['values']);
arsort($vlist);
$ret['values'] = array_values($vlist);
$ret['labels'] = array_keys($vlist);
} else {
// no sorting
}
$ret['values'] = array_values($ret['values']);
$ret['labels'] = array_values($ret['labels']);
$ret['dataids'] = array_values($ret['dataids']);
return $ret;
}
function getChartTitle() {
if (empty($this->chart_title)) {
$this->getChartData();
}
return $this->chart_title;
}
function generateChart($usedemodata = false, $level = 1, $levelIds = array()) {
if ($this->chartLibrary == 'pChart') {
return $this->generateChartPChart($usedemodata, $level, $levelIds);
} elseif ($this->chartLibrary == 'ChartJS') {
return $this->generateChartJS($usedemodata, $level, $levelIds);
}
}
function generateChartJS($usedemodata = false, $level = 1, $levelIds = array()) {
$type = $this->column_fields['chart_type'];
if (empty($type)) return null;
if ($usedemodata)
$rawdata = $this->getChartDemoData($level, $levelIds);
else
$rawdata = $this->getChartData($level, $levelIds);
if (empty($rawdata) || empty($rawdata['values'])) return null; // crmv@31209
if ($rawdata == 'NO_SUMMARY') return 'NO_SUMMARY'; // crmv@172355
// load palette
$palfile = $this->column_fields['chart_palette'];
if (empty($palfile) || !is_readable('modules/Charts/palettes/'.$palfile.'.color')) {
if (in_array($type, array('BarHorizontal', 'BarVertical', 'Line'))) {
$palfile = 'vtetheme';
} else {
$palfile = 'default';
}
}
$mainPalette = $this->parsePaletteFile("modules/Charts/palettes/$palfile.color");
$palette = $this->paletteRGB2Css($mainPalette);
$hpalette = $this->paletteRGB2Css($this->variatePalette($mainPalette, 'lighten', array('percentage'=>10)));
$spalette = $this->paletteRGB2Css($this->variatePalette($mainPalette, 'darken', array('percentage'=>10)));
$paletteCount = count($mainPalette);
// add some basic vars
$rawdata['type'] = $type;
$rawdata['canvas_width'] = $this->image_size_x;
$rawdata['canvas_height'] = $this->image_size_y;
$rawdata['level'] = $level;
$rawdata['levelids'] = $this->getLevelIdsValues($levelIds);
$rawdata['maxlevels'] = $this->getMaxLevels();
if ($rawdata['maxlevels'] > 1) {
$rawdata['leveltitles'] = $this->getLevelTitles();
}
// alter options and other variables
$this->initGraphOptions($rawdata);
// set chartjs options
$rawdata['options'] = $this->default_options_js['Main']['chartjs'] ?: array();
$rawdata['options'] = array_merge_recursive($rawdata['options'], $this->default_options_js[$type]['chartjs'] ?: array());
// reorder data
if (is_array($rawdata['values'])) {
$jsValues = array();
$count = count($rawdata['values']);
// crmv@133997
if ($rawdata['colors']) {
$colors = $rawdata['colors'];
$hcolors = $this->paletteRGB2Css($this->variatePalette($this->paletteCss2RGB($colors), 'lighten', array('percentage'=>10)));
$scolors = $this->paletteRGB2Css($this->variatePalette($this->paletteCss2RGB($colors), 'darken', array('percentage'=>10)));
}
// crmv@133997e
if ($type == 'Pie' || $type == 'Ring') {
foreach ($rawdata['values'] as $k=>$value) {
$jsValues[] = array(
'value' => $value,
'label' => $rawdata['labels'][$k],
'dataid' => $rawdata['dataids'][$k],
// crmv@133997
'color' => $colors[$k] ?: $palette[$k % $paletteCount],
'highlight' => $hcolors[$k] ?: $hpalette[$k % $paletteCount],
// crmv@133997e
);
}
} else {
$k = 0;
$jsValues['labels'] = $rawdata['labels'];
$jsValues['datasets'] = array();
$jsValues['datasets'][] = array(
'label' => 'Serie1',
'data' => $rawdata['values'],
'dataids' => $rawdata['dataids'],
// crmv@133997
'fillColor' => $colors ?: $palette[($type == 'Line' ? 1 : $k) % $paletteCount],
'strokeColor' => $scolors ?: $spalette[$k % $paletteCount],
'highlightFill' => $hcolors ?: $hpalette[$k % $paletteCount],
'highlightStroke' => $colors ?:$palette[$k % $paletteCount],
// crmv@133997e
);
}
unset($rawdata['labels']);
unset($rawdata['dataids']);
unset($rawdata['colors']); // crmv@133997
$rawdata['values'] = $jsValues;
} else {
unset($rawdata['labels']);
unset($rawdata['dataids']);
unset($rawdata['colors']); // crmv@133997
$rawdata['values'] = array();
}
return $rawdata;
}
// genera il grafico e restituisce il nome del file
function generateChartPChart($usedemodata = false, $level = 1, $levelIds = array()) {
global $adb, $table_prefix;
// reuse cached image
$fname = $this->column_fields[$this->cachefield];
if (!empty($fname) && is_readable($fname)) {
$this->map_file = preg_replace('/\.png/', '_map.map', $fname);
return $fname;
}
$fname = $this->generateFileName();
if (empty($fname)) return null;
$type = $this->column_fields['chart_type'];
if (empty($type)) return null;
if ($usedemodata)
$rawdata = $this->getChartDemoData($level, $levelIds);
else
$rawdata = $this->getChartData($level, $levelIds);
if (empty($rawdata) || empty($rawdata['values'])) return null; // crmv@31209
if ($rawdata == 'NO_SUMMARY') return 'NO_SUMMARY'; // crmv@172355
// dataset
$DataSet = new pData();
$DataSet->addPoints($rawdata['values'], 'Serie1');
$DataSet->addPoints($rawdata['labels'], "Labels");
if ($type == 'Line') {
$DataSet->setSerieWeight("Serie1", $this->default_options[$type]['LineWeight']);
}
$DataSet->setSerieDescription("Labels","Report");
$DataSet->setAbscissa("Labels");
// crmv@53923
if ($rawdata['timestamp']) {
$DataSet->setXAxisDisplay(AXIS_FORMAT_DATE);
}
// crmv@53923e
// load palette
$palfile = $this->column_fields['chart_palette'];
if (empty($palfile) || !is_readable('modules/Charts/palettes/'.$palfile.'.color')) {
$palfile = 'default';
}
$DataSet->loadPalette("modules/Charts/palettes/$palfile.color", TRUE);
$this->initGraphOptions($DataSet);
if ($this->default_options['Main']['StretchPalette'])
$DataSet->stretchPalette('Serie1');
$myPicture = new pImage($this->image_size_x, $this->image_size_y, $DataSet);
$myPicture->Antialias = TRUE;
// sfondino con righette
$imgPadding = $this->default_options['Main']['Padding'];
$imgPaddingLeft = $this->default_options['Main']['PaddingLeft']; // crmv@53923
$imgPaddingBottom = $this->default_options['Main']['PaddingBottom']; // crmv@53923
//$Settings = array("R"=>255, "G"=>255, "B"=>255, "Dash"=>1, "DashR"=>190, "DashG"=>200, "DashB"=>240);
//$myPicture->drawFilledRectangle($imgPadding,$imgPadding,$this->image_size_x-$imgPadding,$this->image_size_y-$imgPadding,$Settings);
// sfondo gradient
//$Settings = array("StartR"=>230, "StartG"=>240, "StartB"=>255, "EndR"=>200, "EndG"=>210, "EndB"=>240, 'Alpha'=>80);
//$myPicture->drawGradientArea($imgPadding,$imgPadding,$this->image_size_x-$imgPadding,$this->image_size_y-$imgPadding,DIRECTION_VERTICAL, $Settings);
// bordo
//$myPicture->drawRectangle(0,0,$this->image_size_x-1,$this->image_size_y-1,array("R"=>200,"G"=>200,"B"=>200));
// titolo
//$myPicture->setFontProperties(array("FontName"=>"include/pChart/fonts/VeraBd.ttf","FontSize"=>11));
//$myPicture->drawGradientArea(0,0,$this->image_size_x,$this->default_options['Main']['TitleHeight'],DIRECTION_VERTICAL,array("StartR"=>255,"StartG"=>255,"StartB"=>255,"EndR"=>200,"EndG"=>200,"EndB"=>200,"Alpha"=>100));
//$myPicture->drawText(10,$this->default_options['Main']['TitleHeight']/2 - 2,$rawdata['reportname'], array("R"=>0,"G"=>0,"B"=>0, 'Align'=>TEXT_ALIGN_MIDDLE_LEFT));
// image map
$mapname = preg_replace('/\.png/', '_map', $fname);
$this->map_file = $mapname.'.map';
$myPicture->initialiseImageMap($mapname, IMAGE_MAP_STORAGE_FILE, basename($mapname), $this->cachedir);
// ombra
if ($this->default_options['Main']['GraphShadow']) {
$myPicture->setShadow(TRUE,array("X"=>1,"Y"=>1,"R"=>150,"G"=>150,"B"=>150,"Alpha"=>50));
}
// grafico
$myPicture->setFontProperties(array("FontName"=>'include/pChart/fonts/MYRIADPRO-REGULAR.OTF',"FontSize"=>10,"R"=>100,"G"=>100,"B"=>100));
$myPicture->setGraphArea($imgPadding+$imgPaddingLeft,$imgPadding+$this->default_options['Main']['TitleHeight'], $this->image_size_x-$imgPadding-1, $this->image_size_y-$imgPadding-$imgPaddingBottom-1); // crmv@53923
// spostamento del centro del grafico
$shift_x = 0;
$shift_y = 0;
// calcolo Y della legenda
if ($this->default_options['Main']['ShowLegend']) {
switch ($this->default_options['Legend']['Position']) {
case TEXT_ALIGN_BOTTOMLEFT:
$legend_y = $this->image_size_y - $imgPadding - 20;
$shift_x = 0;
$shift_y = -20;
break;
case TEXT_ALIGN_TOPLEFT:
default:
$legend_y = 20+$this->default_options['Main']['TitleHeight'];
$shift_x = 40;
$shift_y = 0;
break;
}
}
switch ($type) {
case 'BarVertical':
$myPicture->drawScale($this->default_options['Scale']);
$myPicture->drawBarChart($this->default_options[$type]);
break;
case 'BarHorizontal':
$myPicture->drawScale($this->default_options['Scale']);
$myPicture->drawBarChart($this->default_options[$type]);
break;
case 'Line':
$myPicture->drawScale($this->default_options['Scale']);
$myPicture->drawLineChart($this->default_options[$type]);
break;
case 'Pie':
$PieChart = new pPie($myPicture,$DataSet);
$PieChart->draw2DPie($this->image_size_x/2+$shift_x, $this->image_size_y/2+$shift_y, $this->default_options[$type]);
if ($this->default_options['Main']['ShowLegend']) {
$myPicture->setShadow(FALSE);
$PieChart->drawPieLegend(15,$legend_y,$this->default_options['Legend']);
}
break;
case 'Pie3D':
$PieChart = new pPie($myPicture,$DataSet);
$PieChart->draw3DPie($this->image_size_x/2+$shift_x, $this->image_size_y/2+$shift_y, $this->default_options[$type]);
if ($this->default_options['Main']['ShowLegend']) {
$myPicture->setShadow(FALSE);
$PieChart->drawPieLegend(15,$legend_y,$this->default_options['Legend']);
}
break;
case 'Ring':
$PieChart = new pPie($myPicture,$DataSet);
$PieChart->draw2DRing($this->image_size_x/2+$shift_x, $this->image_size_y/2+$shift_y, $this->default_options[$type]);
if ($this->default_options['Main']['ShowLegend']) {
$myPicture->setShadow(FALSE);
$PieChart->drawPieLegend(15,$legend_y,$this->default_options['Legend']);
}
break;
case 'Ring3D':
$PieChart = new pPie($myPicture,$DataSet);
$PieChart->draw3DRing($this->image_size_x/2+$shift_x, $this->image_size_y/2+$shift_y, $this->default_options[$type]);
if ($this->default_options['Main']['ShowLegend']) {
$myPicture->setShadow(FALSE);
$PieChart->drawPieLegend(15,$legend_y,$this->default_options['Legend']);
}
break;
// crmv@53923
case 'Split':
$SplitChart = new pSplit($myPicture, $DataSet);
$SplitChart->drawSplitPath($myPicture,$DataSet,$this->default_options[$type]);
break;
case 'Scatter':
/* Create the Scatter chart object - EXPERIMENTAL */
$myPicture->setGraphArea($imgPadding+$imgPaddingLeft,$imgPadding+$this->default_options['Main']['TitleHeight'], $this->image_size_x-$imgPadding-1, $this->image_size_y-$imgPadding-1-20);
$scatterChart = new pScatter($myPicture,$DataSet);
$scatterChart->drawScatterScale();
$scatterChart->drawScatterLineChart(0,0,$this->default_options[$type]);
if ($this->default_options['Main']['ShowLegend']) {
$myPicture->setShadow(FALSE);
$scatterChart->drawScatterLegend(20,20,$this->default_options['Legend']);
}
break;
// crmv@53923e
}
if ($this->default_options['Main']['ShowLegend']) {
}
// aggiungo warning su dati limitati
if ($rawdata['limited']) {
switch ($type) {
case 'BarVertical':
$myPicture->drawText($this->image_size_x-10,10, getTranslatedString('LBL_PARTIAL_DATA', 'Charts'), array("R"=>200,"G"=>0,"B"=>0, 'Align'=>TEXT_ALIGN_TOPRIGHT));
break;
default:
$myPicture->drawText($this->image_size_x-10,$this->image_size_y-10, getTranslatedString('LBL_PARTIAL_DATA', 'Charts'), array("R"=>200,"G"=>0,"B"=>0, 'Align'=>TEXT_ALIGN_BOTTOMRIGHT));
break;
}
}
// render image
$myPicture->Render($fname);
$this->cachefile_date[$this->cachefield] = filemtime($fname);
// anteprima
//$this->createThumbnail($fname);
// save it into database
$chartid = intval($this->column_fields["record_id"]);
if ($chartid > 0) {
$adb->pquery("update {$table_prefix}_chartscache set {$this->cachefield} = ? where {$this->table_index} = ?", array($fname, $chartid));
}
return $fname;
}
function getMapData() {
$mapfile = $this->map_file;
if (empty($mapfile) || !is_readable($mapfile)) return array();
$ret = '';
$Handle = @fopen($mapfile, "r");
if ($Handle) {
while (($Buffer = fgets($Handle, 4096)) !== false) {
$ret .= $Buffer;
}
@fclose($Handle);
}
// now parse it
$retzones = array();
$zones = explode("\r\n", $ret);
foreach ($zones as $zdata) {
$zinfo = explode(IMAGE_MAP_DELIMITER, $zdata);
if (empty($zinfo[0]) || empty($zinfo[1])) continue;
$retzones[] = array(
'shape'=>$zinfo[0],
'coords'=>$zinfo[1],
'color'=>$zinfo[2],
'label'=>$zinfo[3],
'value'=>formatUserNumber(floatval($zinfo[4])), // crmv@92350
'percent'=>$zinfo[5],
);
}
return $retzones;
}
// crmv@172355
public function getReportRunObj() {
global $adb, $table_prefix;
$reportid = $this->column_fields['reportid'];
if ($reportid > 0) {
$folderid = getSingleFieldValue($table_prefix.'_report', 'folderid', 'reportid', $reportid);
$sdkrep = SDK::getReport($reportid, $folderid);
if (!is_null($sdkrep)) {
require_once($sdkrep['reportrun']);
$oReportRun = new $sdkrep['runclass']($reportid);
} else {
$oReportRun = ReportRun::getInstance($reportid);
}
}
return $oReportRun;
}
// ricalcola il report
// crmv@97862
function reloadReport() {
$oReportRun = $this->getReportRunObj();
if ($oReportRun) {
if ($oReportRun instanceof ReportRun && !$oReportRun->hasSummary()) return; // crmv@172355 - allow sdk reports
$oReportRun->setReportTab('COUNT');
$oReportRun->setOutputFormat('NULL');
$oReportRun->GenerateReport();
$this->invalidateCache();
}
}
// crmv@97862e crmv@172355e
// crmv@133997
/**
* Return the color for each cluster
*/
public function getReportColors() {
$colors = array();
$reportid = $this->column_fields['reportid'];
if ($reportid > 0) {
$REP = Reports::getInstance();
$clusters = $REP->getClusters($reportid);
if (is_array($clusters) && count($clusters) > 0) {
foreach ($clusters as $cluster) {
if ($cluster['name'] && $cluster['color']) {
$colors[$cluster['name']] = $cluster['color'];
}
}
if (count($colors) > 0) {
// add the empty color (grey)
$colors['-'] = '#a0a0a0';
}
}
}
return $colors;
}
// crmv@133997e
// invalida la cache immagine
// TODO: invalida tutte le cache
function invalidateCache() {
global $adb, $table_prefix;
$chartid = $this->column_fields["record_id"];
$adb->pquery("update {$table_prefix}_chartscache set {$this->cachefield} = NULL where {$this->table_index} = ?", array($chartid));
// delete files
// TODO: thumbnails
if (is_writable($this->column_fields[$this->cachefield])) {
$mapname = preg_replace('/\.png/', '_map.map', $this->column_fields[$this->cachefield]);
$thumbname = preg_replace('/\.png/', '_thumb.png', $this->column_fields[$this->cachefield]);
@unlink($this->column_fields[$this->cachefield]);
@unlink($mapname);
@unlink($thumbname);
$this->column_fields[$this->cachefield] = '';
unset($this->cachefile_date[$this->cachefield]);
$this->map_file = null;
}
}
// crea una anteprima del grafico
function createThumbnail($filename) {
if ($this->chartLibrary == 'ChartJS') {
// just set the sizes
$newy = intval(floatval($this->thumbnail_size) * $this->image_size_y / $this->image_size_x);
$this->image_size_x = $this->thumbnail_size;
$this->image_size_y = $newy;
return null;
}
if (function_exists('imagecopyresampled') && is_readable($filename)) {
$image_src = imagecreatefrompng($filename);
$newy = intval(floatval($this->thumbnail_size) * $this->image_size_y / $this->image_size_x);
$image_dest = imagecreatetruecolor($this->thumbnail_size, $newy);
imagecopyresampled($image_dest, $image_src, 0, 0, 0, 0, $this->thumbnail_size, $newy, $this->image_size_x, $this->image_size_y);
$outname = preg_replace('/\.png/', '_thumb.png', $filename);
imagepng($image_dest, $outname);
return $outname;
}
return null;
}
function renderChart($showdate = true, $showborder = true, $viewreportlink = false) { // crmv@128369
global $app_strings, $mod_strings, $theme, $current_language;
// map data is used only for php generated charts
if ($this->chartLibrary == 'pChart') {
$fname = $this->generateChart();
$mapdata = $this->getMapData();
$chartdata = null;
} elseif ($this->chartLibrary == 'ChartJS') {
$fname = null;
$mapdata = null;
$chartdata = $this->generateChart();
}
$smarty = new VteSmarty();
$smarty->assign('APP', $app_strings);
$smarty->assign('MOD', return_module_language($current_language,'Charts'));
$smarty->assign('THEME', $theme);
$smarty->assign('IMAGE_PATH', "themes/$theme/images/");
$smarty->assign('CHART_ID', $this->column_fields['record_id']);
$smarty->assign('REPORTID', $this->column_fields['reportid']); // crmv@128369
$smarty->assign('CHART_TITLE', $this->getChartTitle());
$smarty->assign('CHART_PATH', $fname);
// variables used for home block
$smarty->assign('HOME_STUFFID', $this->homestuffid);
$smarty->assign('HOME_STUFFSIZE', $this->homestuffsize);
$smarty->assign('CHART_SHOWREPORTLINK', $viewreportlink); // crmv@128369
$smarty->assign('CHART_SHOWBORDER', $showborder);
$smarty->assign('CHART_SHOWDATE', $showdate);
$smarty->assign('CHART_LIMIT_DATA_N', $this->limit_data); // crmv@191909
if ($showdate) {
// crmv@134727
if ($this->chartLibrary == 'pChart') {
$smarty->assign('CHART_LASTUPDATE', $this->cachefile_date[$this->cachefield]);
$extdate = date('Y-m-d H:i:s', $this->cachefile_date[$this->cachefield]);
} elseif ($this->chartLibrary == 'ChartJS' && $chartdata['generatedtime']) {
$smarty->assign('CHART_LASTUPDATE', strtotime($chartdata['generatedtime']));
$extdate = $chartdata['generatedtime'];
}
if ($extdate) {
$smarty->assign('CHART_LASTUPDATE_DISPLAY', getDisplayDate($extdate));
$smarty->assign('CHART_LASTUPDATE_RELATIVE', getFriendlyDate($extdate));
}
// crmv@134727e
}
$smarty->assign('CHART_MAP', $mapdata);
$smarty->assign('CHART_DATA', $chartdata);
if ($this->chartLibrary == 'pChart') {
$htmldata = $smarty->fetch('modules/Charts/RenderChart.tpl');
} elseif ($this->chartLibrary == 'ChartJS') {
$htmldata = $smarty->fetch('modules/Charts/RenderChartJS.tpl');
} else {
throw new Exception("Chart library '{$this->chartLibrary}' is not supported");
}
return $htmldata;
}
function renderHomeBlock() {
global $adb, $table_prefix, $current_user;
$size = $this->homestuffsize;
if (empty($size)) $size = 1;
// get home layout
$layout = 4;
$res = $adb->pquery("select layout from {$table_prefix}_home_layout where userid = ?", array($current_user->id));
if ($res && $adb->num_rows($res) > 0) {
$layout = intval($adb->query_result($res, 0, 'layout'));
if ($layout == 0) $layout = 4;
}
switch ($layout) {
case 2:
$single_size = 520;
break;
case 3:
$single_size = 360;
break;
case 4:
default:
$single_size = 260;
}
$this->setCacheField('chart_file_home');
$this->image_size_x = $single_size * $size;
$this->image_size_y = 260;
return $this->renderChart(true, false, true); // crmv@128369
}
// crmv83340
function renderModuleHomeBlock($layout = 4) {
global $adb, $table_prefix, $current_user;
$size = $this->homestuffsize;
if (empty($size)) $size = 1;
switch ($layout) {
case 2:
$single_size = 520;
break;
case 3:
$single_size = 360;
break;
case 4:
default:
$single_size = 260;
}
//$this->setCacheField('chart_file_home');
$this->image_size_x = $single_size * $size;
$this->image_size_y = 260;
return $this->renderChart(true, false, true); // crmv@128369
}
// crmv83340e
// restituisce un array di istanze per i grafici
function getChartsForReport($reportid, $usefilters = false) { // crmv@31209
global $adb, $table_prefix;
$ret = array();
$res = $adb->pquery("select {$this->table_index} as chartid from {$this->table_name} inner join {$table_prefix}_crmentity crment on crment.crmid = {$this->table_name}.{$this->table_index} where crment.deleted = 0 and reportid = ?", array($reportid));
if ($res) {
while ($row = $adb->fetchByAssoc($res, -1, false)) {
$chid = $row['chartid'];
$chclass = CRMEntity::getInstance('Charts');
$chclass->retrieve_entity_info($chid, 'Charts');
if ($usefilters) $chclass->filtered_data = true; // crmv@31209
$ret[] = $chclass;
}
}
return $ret;
}
// crmv@172355
/**
* Count how many charts exists for the specified report
*/
public function countChartsForReport($reportid) {
global $adb, $table_prefix;
$res = $adb->pquerySlave('Reports', // crmv@185894
"SELECT COUNT(*) AS cnt
FROM {$this->table_name} ch
INNER JOIN {$table_prefix}_crmentity c ON c.crmid = ch.{$this->table_index} AND c.deleted = 0
WHERE ch.reportid = ?", array($reportid)
);
if ($res && $adb->num_rows($res) > 0) {
return $adb->query_result_no_html($res, 0, 'cnt');
}
return 0;
}
// crmv@172355e
function getChartTypes() {
global $adb, $current_user, $table_prefix;
$chtypes = getAssignedPicklistValues('chart_type', $current_user->roleid, $adb, 'Charts');
return $chtypes;
}
function getQuickCreateDefault($module, $qcreate_array, $search_field, $search_text) {
$col_fields = parent::getQuickCreateDefault($module, $qcreate_array, $search_field, $search_text);
// add defaults
$col_fields['chart_labels'] = 1;
$col_fields['chart_merge_small'] = 1;
return $col_fields;
}
// crmv@30967
function getFolderContent($folderid) {
global $adb, $table_prefix, $current_user, $app_strings, $mod_strings;
$folderinfo = getEntityFolder($folderid);
$queryGenerator = QueryGenerator::getInstance('Charts', $current_user);
$queryGenerator->initForDefaultCustomView();
$queryGenerator->addField('chart_filename');
$list_query = $queryGenerator->getQuery();
// only in selected folder
$list_query .= " AND {$this->table_name}.folderid = '$folderid'";
// order by most recent first
$list_query .= " ORDER BY {$table_prefix}_crmentity.modifiedtime DESC";
$count = 0;
$res = $adb->query(replaceSelectQuery($list_query,'count(*) as cnt'));
if ($res) $count = $adb->query_result($res,0,'cnt');
$smarty = new VteSmarty();
$smarty->assign('FOLDERINFO', $folderinfo);
$smarty->assign('APP', $app_strings);
$smarty->assign('MOD', $mod_strings);
$smarty->assign('TOTALCOUNT', $count);
// retrieve the first documents as a preview
$html = '';
$res = $adb->limitQuery($list_query, 0, 3);
if ($res) {
$arr = array();
while ($row = $adb->fetchByAssoc($res)) {
$arr[] = $row;
}
$smarty->assign('FOLDERDATA', $arr);
}
$html = $smarty->fetch('modules/Charts/FolderTooltip.tpl');
return array('count'=>$count, 'html'=>$html);
}
// crmv@30967e
/**
* Invoked when special actions are performed on the module.
* @param String Module name
* @param String Event Type (module.postinstall, module.disabled, module.enabled, module.preuninstall)
*/
function vtlib_handler($modulename, $event_type) {
global $adb, $table_prefix;
if($event_type == 'module.postinstall') {
$clModule = Vtecrm_Module::getInstance($modulename);
$clModule->disableTools(Array('Import', 'Export', 'DuplicatesHandling'));
$adb->pquery('UPDATE '.$table_prefix.'_tab SET customized=0 WHERE name=?', array($modulename));
// blocco info chiuso di default
//$adb->pquery("update {$table_prefix}_blocks set display_status = 0 where blocklabel = ?", array('LBL_CHARTS_INFORMATION'));
$clModule->hide(array('hide_report'=>1)); // crmv@38798
$modInstance = CRMEntity::getInstance($modulename);
// hide it from many places
if (method_exists($modInstance, 'hide'))
$modInstance->hide(array('hide_module_manager'=>1,'hide_profile'=>1,'hide_report'=>1));
if(!Vtecrm_Utils::CheckTable($this->table_name)) {
Vtecrm_Utils::CreateTable(
$this->table_name,
"{$this->table_index} I(19) PRIMARY",
true);
}
if(!Vtecrm_Utils::CheckTable($this->customFieldTable[0])) {
Vtecrm_Utils::CreateTable(
$this->customFieldTable[0],
"{$this->customFieldTable[1]} I(19) PRIMARY",
true);
}
$ctable = $table_prefix.'_chartscache';
if(!Vtecrm_Utils::CheckTable($ctable)) {
Vtecrm_Utils::CreateTable(
$ctable,
"{$this->customFieldTable[1]} I(19) PRIMARY",
true);
}
// table for charts in homepage
$hometable = $table_prefix.'_homecharts';
if(!Vtecrm_Utils::CheckTable($hometable)) {
Vtecrm_Utils::CreateTable(
$hometable,
"stuffid I(19) PRIMARY,
chartid I(19)",
true);
}
if (class_exists('SDK')) {
SDK::file2DbLanguages($modulename);
SDK::setAdvancedPermissionFunction('Charts', 'chartsPermission', 'modules/Charts/SDK/advPermission.php');
// extra languages
SDK::setLanguageEntry('APP_STRINGS', 'it_it', 'SINGLE_Charts', 'Grafico');
SDK::setLanguageEntry('APP_STRINGS', 'en_us', 'SINGLE_Charts', 'Chart');
SDK::setLanguageEntry('Home', 'it_it', 'LBL_HOME_CHART_NAME', 'Nome Grafico');
SDK::setLanguageEntry('Home', 'en_us', 'LBL_HOME_CHART_NAME', 'Chart Name');
}
// add a default folder
addEntityFolder('Charts', 'Default', '', 1);
// create examples
include('modules/Charts/InstallExamples.php');
} else if($event_type == 'module.disabled') {
// TODO Handle actions when this module is disabled.
} else if($event_type == 'module.enabled') {
// TODO Handle actions when this module is enabled.
} else if($event_type == 'module.preuninstall') {
// TODO Handle actions when this module is about to be deleted.
} else if($event_type == 'module.preupdate') {
// TODO Handle actions before this module is updated.
} else if($event_type == 'module.postupdate') {
// reload translations from module files
// TODO: non sovrascrivere personalizzazioni
if (class_exists('SDK')) {
SDK::deleteLanguage($modulename);
SDK::file2DbLanguages($modulename);
SDK::setLanguageEntry('APP_STRINGS', 'it_it', 'SINGLE_Charts', 'Grafico');
SDK::setLanguageEntry('APP_STRINGS', 'en_us', 'SINGLE_Charts', 'Chart');
}
}
}
function trash($module, $id) {
global $adb, $table_prefix;
parent::trash($module, $id);
// delete charts from home page
$stuffids = array();
$res = $adb->pquery("select stuffid from {$table_prefix}_homecharts where chartid = ?", array($id));
if ($res) {
while ($row = $adb->fetchByAssoc($res, -1, false)) $stuffids[] = $row['stuffid'];
if (count($stuffids) > 0) {
$adb->pquery("delete from {$table_prefix}_homecharts where stuffid in (".generateQuestionMarks($stuffids).")", $stuffids);
$adb->pquery("delete from {$table_prefix}_homestuff where stuffid in (".generateQuestionMarks($stuffids).")", $stuffids);
}
}
}
/**
* Handle saving related module information.
* NOTE: This function has been added to CRMEntity (base class).
* You can override the behavior by re-defining it here.
*/
/*
function save_related_module($module, $crmid, $with_module, $with_crmid) {
parent::save_related_module($module, $crmid, $with_module, $with_crmid);
//...
}
*/
/**
* Handle deleting related module information.
* NOTE: This function has been added to CRMEntity (base class).
* You can override the behavior by re-defining it here.
*/
//function delete_related_module($module, $crmid, $with_module, $with_crmid) { }
/**
* Handle getting related list information.
* NOTE: This function has been added to CRMEntity (base class).
* You can override the behavior by re-defining it here.
*/
//function get_related_list($id, $cur_tab_id, $rel_tab_id, $actions=false) { }
/**
* Handle getting dependents list information.
* NOTE: This function has been added to CRMEntity (base class).
* You can override the behavior by re-defining it here.
*/
//function get_dependents_list($id, $cur_tab_id, $rel_tab_id, $actions=false) { }
// widget code
static function getWidget($name) {
if ($name == 'DetailViewBlockChartWidget' && isPermitted('Charts', 'DetailView') == 'yes') {
require_once dirname(__FILE__) . '/widgets/DetailViewBlockChart.php';
return (new Charts_DetailViewBlockChartWidget(null));
}
return false;
}
// output a rgb style palette
function parsePaletteFile($file) {
$rgbPalette = array();
$data = @file_get_contents($file);
if (empty($data)) return $rgbPalette;
$lines = array_filter(explode("\n", str_replace("\r", '', $data)));
foreach ($lines as $line) {
list($r, $g, $b, $a) = array_map('trim', explode(",", strtolower($line)));
$r = intval($r,0);
$g = intval($g,0);
$b = intval($b,0);
$a2 = intval($a);
if ($a === null || $a === '' || $a2 == 100) {
$rgbPalette[] = array($r, $g, $b);
} else {
$rgbPalette[] = array($r, $g, $b, $a2);
}
}
return $rgbPalette;
}
// convert a rgb palette into a css-style palette
function paletteRGB2Css($rgbPalette) {
$cpalette = array();
foreach ($rgbPalette as $c) {
if (!isset($c[3]) || $c[3] === '' || $c[3] == 100) {
$out = sprintf("#%02x%02x%02x", $c[0], $c[1], $c[2]);
} else {
$out = sprintf("rgba(%d,%d,%d,%d)", $c[0], $c[1], $c[2], $c[3]);
}
$cpalette[] = $out;
}
return $cpalette;
}
// crmv@133997
// convert a css palette into a rgb palette
function paletteCss2RGB($cssPalette) {
$cpalette = array();
foreach ($cssPalette as $c) {
if ($c[0] == '#') {
if (strlen($c) == 4) {
// short format
$out = array(hexdec($c[1]), hexdec($c[2]), hexdec($c[3]));
} else {
$out = array(hexdec(substr($c, 1, 2)), hexdec(substr($c, 3, 2)), hexdec(substr($c, 5, 2)));
}
} else {
// other formarts not supported
}
$cpalette[] = $out;
}
return $cpalette;
}
// crmv@133997e
function variatePalette($rgbPalette, $variationType, $variationParams = array()) {
$vpalette = array();
if ($variationType == 'lighten' || $variationType == 'darken') {
$perc = $variationParams['percentage'] ?: 10;
foreach ($rgbPalette as $color) {
$hsl = self::rgb2hsl($color[0], $color[1], $color[2]);
if ($variationType == 'lighten') {
$hsl[2] = max(0.0, min($hsl[2] * (1.0 + ($perc / 100)), 1.0));
} else {
$hsl[2] = max(0.0, min($hsl[2] * (1.0 - ($perc / 100)), 1.0));
}
$rgb = self::hsl2rgb($hsl[0], $hsl[1], $hsl[2]);
$vpalette[] = $rgb;
}
} else {
// other variations not supported
$vpalette = $rgbPalette;
}
return $vpalette;
}
// some helper functions to manage the palette
static function rgb2hsl($r, $g, $b) {
$var_R = ($r / 255);
$var_G = ($g / 255);
$var_B = ($b / 255);
$var_Min = min($var_R, $var_G, $var_B);
$var_Max = max($var_R, $var_G, $var_B);
$del_Max = $var_Max - $var_Min;
$v = $var_Max;
if ($del_Max == 0) {
$h = 0;
$s = 0;
} else {
$s = $del_Max / $var_Max;
$del_R = ( ( ( $var_Max - $var_R ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
$del_G = ( ( ( $var_Max - $var_G ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
$del_B = ( ( ( $var_Max - $var_B ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
if ($var_R == $var_Max) $h = $del_B - $del_G;
else if ($var_G == $var_Max) $h = ( 1 / 3 ) + $del_R - $del_B;
else if ($var_B == $var_Max) $h = ( 2 / 3 ) + $del_G - $del_R;
if ($h < 0) $h++;
if ($h > 1) $h--;
}
return array($h, $s, $v);
}
static function hsl2rgb($h, $s, $v) {
if($s == 0) {
$r = $g = $b = $v * 255;
} else {
$var_H = $h * 6;
$var_i = floor( $var_H );
$var_1 = $v * ( 1 - $s );
$var_2 = $v * ( 1 - $s * ( $var_H - $var_i ) );
$var_3 = $v * ( 1 - $s * (1 - ( $var_H - $var_i ) ) );
if ($var_i == 0) { $var_R = $v ; $var_G = $var_3 ; $var_B = $var_1 ; }
else if ($var_i == 1) { $var_R = $var_2 ; $var_G = $v ; $var_B = $var_1 ; }
else if ($var_i == 2) { $var_R = $var_1 ; $var_G = $v ; $var_B = $var_3 ; }
else if ($var_i == 3) { $var_R = $var_1 ; $var_G = $var_2 ; $var_B = $v ; }
else if ($var_i == 4) { $var_R = $var_3 ; $var_G = $var_1 ; $var_B = $v ; }
else { $var_R = $v ; $var_G = $var_1 ; $var_B = $var_2 ; }
$r = $var_R * 255;
$g = $var_G * 255;
$b = $var_B * 255;
}
return array($r, $g, $b);
}
}
?>