PDF rausgenommen
This commit is contained in:
571
msd2/tracking/piwik/plugins/ImageGraph/API.php
Normal file
571
msd2/tracking/piwik/plugins/ImageGraph/API.php
Normal file
@ -0,0 +1,571 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\ImageGraph;
|
||||
|
||||
use Exception;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Archive\DataTableFactory;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Map;
|
||||
use Piwik\Filesystem;
|
||||
use Piwik\Period;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\SettingsServer;
|
||||
use Piwik\Translate;
|
||||
|
||||
/**
|
||||
* The ImageGraph.get API call lets you generate beautiful static PNG Graphs for any existing Matomo report.
|
||||
* Supported graph types are: line plot, 2D/3D pie chart and vertical bar chart.
|
||||
*
|
||||
* A few notes about some of the parameters available:<br/>
|
||||
* - $graphType defines the type of graph plotted, accepted values are: 'evolution', 'verticalBar', 'pie' and '3dPie'<br/>
|
||||
* - $colors accepts a comma delimited list of colors that will overwrite the default Matomo colors <br/>
|
||||
* - you can also customize the width, height, font size, metric being plotted (in case the data contains multiple columns/metrics).
|
||||
*
|
||||
* See also <a href='http://matomo.org/docs/analytics-api/metadata/#toc-static-image-graphs'>How to embed static Image Graphs?</a> for more information.
|
||||
*
|
||||
* @method static \Piwik\Plugins\ImageGraph\API getInstance()
|
||||
*/
|
||||
class API extends \Piwik\Plugin\API
|
||||
{
|
||||
const FILENAME_KEY = 'filename';
|
||||
const TRUNCATE_KEY = 'truncate';
|
||||
const WIDTH_KEY = 'width';
|
||||
const HEIGHT_KEY = 'height';
|
||||
const MAX_WIDTH = 2048;
|
||||
const MAX_HEIGHT = 2048;
|
||||
|
||||
private static $DEFAULT_PARAMETERS = array(
|
||||
StaticGraph::GRAPH_TYPE_BASIC_LINE => array(
|
||||
self::FILENAME_KEY => 'BasicLine',
|
||||
self::TRUNCATE_KEY => 6,
|
||||
self::WIDTH_KEY => 1044,
|
||||
self::HEIGHT_KEY => 290,
|
||||
),
|
||||
StaticGraph::GRAPH_TYPE_VERTICAL_BAR => array(
|
||||
self::FILENAME_KEY => 'BasicBar',
|
||||
self::TRUNCATE_KEY => 6,
|
||||
self::WIDTH_KEY => 1044,
|
||||
self::HEIGHT_KEY => 290,
|
||||
),
|
||||
StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR => array(
|
||||
self::FILENAME_KEY => 'HorizontalBar',
|
||||
self::TRUNCATE_KEY => null, // horizontal bar graphs are dynamically truncated
|
||||
self::WIDTH_KEY => 800,
|
||||
self::HEIGHT_KEY => 290,
|
||||
),
|
||||
StaticGraph::GRAPH_TYPE_3D_PIE => array(
|
||||
self::FILENAME_KEY => '3DPie',
|
||||
self::TRUNCATE_KEY => 5,
|
||||
self::WIDTH_KEY => 1044,
|
||||
self::HEIGHT_KEY => 290,
|
||||
),
|
||||
StaticGraph::GRAPH_TYPE_BASIC_PIE => array(
|
||||
self::FILENAME_KEY => 'BasicPie',
|
||||
self::TRUNCATE_KEY => 5,
|
||||
self::WIDTH_KEY => 1044,
|
||||
self::HEIGHT_KEY => 290,
|
||||
),
|
||||
);
|
||||
|
||||
private static $DEFAULT_GRAPH_TYPE_OVERRIDE = array(
|
||||
'Referrers_getReferrerType' => array(
|
||||
false // override if !$isMultiplePeriod
|
||||
=> StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR,
|
||||
),
|
||||
);
|
||||
|
||||
const GRAPH_OUTPUT_INLINE = 0;
|
||||
const GRAPH_OUTPUT_FILE = 1;
|
||||
const GRAPH_OUTPUT_PHP = 2;
|
||||
|
||||
const DEFAULT_ORDINATE_METRIC = 'nb_visits';
|
||||
const FONT_DIR = '/plugins/ImageGraph/fonts/';
|
||||
const DEFAULT_FONT = 'tahoma.ttf';
|
||||
const UNICODE_FONT = 'unifont.ttf';
|
||||
const DEFAULT_FONT_SIZE = 9;
|
||||
const DEFAULT_LEGEND_FONT_SIZE_OFFSET = 2;
|
||||
const DEFAULT_TEXT_COLOR = '222222';
|
||||
const DEFAULT_BACKGROUND_COLOR = 'FFFFFF';
|
||||
const DEFAULT_GRID_COLOR = 'CCCCCC';
|
||||
|
||||
// number of row evolutions to plot when no labels are specified, can be overridden using &filter_limit
|
||||
const DEFAULT_NB_ROW_EVOLUTIONS = 5;
|
||||
const MAX_NB_ROW_LABELS = 10;
|
||||
|
||||
public function get(
|
||||
$idSite,
|
||||
$period,
|
||||
$date,
|
||||
$apiModule,
|
||||
$apiAction,
|
||||
$graphType = false,
|
||||
$outputType = API::GRAPH_OUTPUT_INLINE,
|
||||
$columns = false,
|
||||
$labels = false,
|
||||
$showLegend = true,
|
||||
$width = false,
|
||||
$height = false,
|
||||
$fontSize = API::DEFAULT_FONT_SIZE,
|
||||
$legendFontSize = false,
|
||||
$aliasedGraph = true,
|
||||
$idGoal = false,
|
||||
$colors = false,
|
||||
$textColor = API::DEFAULT_TEXT_COLOR,
|
||||
$backgroundColor = API::DEFAULT_BACKGROUND_COLOR,
|
||||
$gridColor = API::DEFAULT_GRID_COLOR,
|
||||
$idSubtable = false,
|
||||
$legendAppendMetric = true,
|
||||
$segment = false,
|
||||
$idDimension = false
|
||||
)
|
||||
{
|
||||
$idSite = (int) $idSite;
|
||||
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
// Health check - should we also test for GD2 only?
|
||||
if (!SettingsServer::isGdExtensionEnabled()) {
|
||||
throw new Exception('Error: To create graphs in Matomo, please enable GD php extension (with Freetype support) in php.ini,
|
||||
and restart your web server.');
|
||||
}
|
||||
|
||||
$useUnicodeFont = array(
|
||||
'am', 'ar', 'el', 'fa', 'fi', 'he', 'ja', 'ka', 'ko', 'te', 'th', 'zh-cn', 'zh-tw',
|
||||
);
|
||||
$languageLoaded = Translate::getLanguageLoaded();
|
||||
$font = self::getFontPath(self::DEFAULT_FONT);
|
||||
if (in_array($languageLoaded, $useUnicodeFont)) {
|
||||
$unicodeFontPath = self::getFontPath(self::UNICODE_FONT);
|
||||
$font = file_exists($unicodeFontPath) ? $unicodeFontPath : $font;
|
||||
}
|
||||
|
||||
// save original GET to reset after processing. Important for API-in-API-call
|
||||
$savedGET = $_GET;
|
||||
|
||||
try {
|
||||
$apiParameters = array();
|
||||
if (!empty($idGoal)) {
|
||||
$apiParameters = array('idGoal' => $idGoal);
|
||||
}
|
||||
if (!empty($idDimension)) {
|
||||
$apiParameters = array('idDimension' => $idDimension);
|
||||
}
|
||||
// Fetch the metadata for given api-action
|
||||
$parameters = array(
|
||||
'idSite' => $idSite,
|
||||
'apiModule' => $apiModule,
|
||||
'apiAction' => $apiAction,
|
||||
'apiParameters' => $apiParameters,
|
||||
'language' => $languageLoaded,
|
||||
'period' => $period,
|
||||
'date' => $date,
|
||||
'hideMetricsDoc' => false,
|
||||
'showSubtableReports' => true
|
||||
);
|
||||
|
||||
$metadata = Request::processRequest('API.getMetadata', $parameters);
|
||||
if (!$metadata) {
|
||||
throw new Exception('Invalid API Module and/or API Action');
|
||||
}
|
||||
|
||||
$metadata = $metadata[0];
|
||||
$reportHasDimension = !empty($metadata['dimension']);
|
||||
$constantRowsCount = !empty($metadata['constantRowsCount']);
|
||||
|
||||
$isMultiplePeriod = Period::isMultiplePeriod($date, $period);
|
||||
if (!$reportHasDimension && !$isMultiplePeriod) {
|
||||
throw new Exception('The graph cannot be drawn for this combination of \'date\' and \'period\' parameters.');
|
||||
}
|
||||
|
||||
if (empty($legendFontSize)) {
|
||||
$legendFontSize = (int)$fontSize + self::DEFAULT_LEGEND_FONT_SIZE_OFFSET;
|
||||
}
|
||||
|
||||
if (empty($graphType)) {
|
||||
if ($isMultiplePeriod) {
|
||||
$graphType = StaticGraph::GRAPH_TYPE_BASIC_LINE;
|
||||
} else {
|
||||
if ($constantRowsCount) {
|
||||
$graphType = StaticGraph::GRAPH_TYPE_VERTICAL_BAR;
|
||||
} else {
|
||||
$graphType = StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR;
|
||||
}
|
||||
}
|
||||
|
||||
$reportUniqueId = $metadata['uniqueId'];
|
||||
if (isset(self::$DEFAULT_GRAPH_TYPE_OVERRIDE[$reportUniqueId][$isMultiplePeriod])) {
|
||||
$graphType = self::$DEFAULT_GRAPH_TYPE_OVERRIDE[$reportUniqueId][$isMultiplePeriod];
|
||||
}
|
||||
} else {
|
||||
$availableGraphTypes = StaticGraph::getAvailableStaticGraphTypes();
|
||||
if (!in_array($graphType, $availableGraphTypes)) {
|
||||
throw new Exception(
|
||||
Piwik::translate(
|
||||
'General_ExceptionInvalidStaticGraphType',
|
||||
array($graphType, implode(', ', $availableGraphTypes))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$width = (int)$width;
|
||||
$height = (int)$height;
|
||||
if (empty($width)) {
|
||||
$width = self::$DEFAULT_PARAMETERS[$graphType][self::WIDTH_KEY];
|
||||
}
|
||||
if (empty($height)) {
|
||||
$height = self::$DEFAULT_PARAMETERS[$graphType][self::HEIGHT_KEY];
|
||||
}
|
||||
|
||||
// Cap width and height to a safe amount
|
||||
$width = min($width, self::MAX_WIDTH);
|
||||
$height = min($height, self::MAX_HEIGHT);
|
||||
|
||||
$reportColumns = array_merge(
|
||||
!empty($metadata['metrics']) ? $metadata['metrics'] : array(),
|
||||
!empty($metadata['processedMetrics']) ? $metadata['processedMetrics'] : array(),
|
||||
!empty($metadata['metricsGoal']) ? $metadata['metricsGoal'] : array(),
|
||||
!empty($metadata['processedMetricsGoal']) ? $metadata['processedMetricsGoal'] : array()
|
||||
);
|
||||
|
||||
$ordinateColumns = array();
|
||||
if (empty($columns)) {
|
||||
$ordinateColumns[] =
|
||||
empty($reportColumns[self::DEFAULT_ORDINATE_METRIC]) ? key($metadata['metrics']) : self::DEFAULT_ORDINATE_METRIC;
|
||||
} else {
|
||||
$ordinateColumns = explode(',', $columns);
|
||||
foreach ($ordinateColumns as $column) {
|
||||
if (empty($reportColumns[$column])) {
|
||||
throw new Exception(
|
||||
Piwik::translate(
|
||||
'ImageGraph_ColumnOrdinateMissing',
|
||||
array($column, implode(',', array_keys($reportColumns)))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ordinateLabels = array();
|
||||
foreach ($ordinateColumns as $column) {
|
||||
$ordinateLabels[$column] = $reportColumns[$column];
|
||||
}
|
||||
|
||||
// sort and truncate filters
|
||||
$defaultFilterTruncate = self::$DEFAULT_PARAMETERS[$graphType][self::TRUNCATE_KEY];
|
||||
switch ($graphType) {
|
||||
case StaticGraph::GRAPH_TYPE_3D_PIE:
|
||||
case StaticGraph::GRAPH_TYPE_BASIC_PIE:
|
||||
|
||||
if (count($ordinateColumns) > 1) {
|
||||
// CpChart doesn't support multiple series on pie charts
|
||||
throw new Exception("Pie charts do not currently support multiple series");
|
||||
}
|
||||
|
||||
$_GET['filter_sort_column'] = reset($ordinateColumns);
|
||||
$this->setFilterTruncate($defaultFilterTruncate);
|
||||
break;
|
||||
|
||||
case StaticGraph::GRAPH_TYPE_VERTICAL_BAR:
|
||||
case StaticGraph::GRAPH_TYPE_BASIC_LINE:
|
||||
|
||||
if (!$isMultiplePeriod && !$constantRowsCount) {
|
||||
$this->setFilterTruncate($defaultFilterTruncate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$ordinateLogos = array();
|
||||
|
||||
// row evolutions
|
||||
if ($isMultiplePeriod && $reportHasDimension) {
|
||||
$plottedMetric = reset($ordinateColumns);
|
||||
|
||||
// when no labels are specified, getRowEvolution returns the top N=filter_limit row evolutions
|
||||
// rows are sorted using filter_sort_column (see DataTableGenericFilter for more info)
|
||||
if (!$labels) {
|
||||
$savedFilterSortColumnValue = Common::getRequestVar('filter_sort_column', '');
|
||||
$_GET['filter_sort_column'] = $plottedMetric;
|
||||
|
||||
$savedFilterLimitValue = Common::getRequestVar('filter_limit', -1, 'int');
|
||||
if ($savedFilterLimitValue == -1 || $savedFilterLimitValue > self::MAX_NB_ROW_LABELS) {
|
||||
$_GET['filter_limit'] = self::DEFAULT_NB_ROW_EVOLUTIONS;
|
||||
}
|
||||
}
|
||||
|
||||
$parameters = array(
|
||||
'idSite' => $idSite,
|
||||
'period' => $period,
|
||||
'date' => $date,
|
||||
'apiModule' => $apiModule,
|
||||
'apiAction' => $apiAction,
|
||||
'label' => $labels,
|
||||
'segment' => $segment,
|
||||
'column' => $plottedMetric,
|
||||
'language' => $languageLoaded,
|
||||
'idGoal' => $idGoal,
|
||||
'idDimension' => $idDimension,
|
||||
'legendAppendMetric' => $legendAppendMetric,
|
||||
'labelUseAbsoluteUrl' => false
|
||||
);
|
||||
$processedReport = Request::processRequest('API.getRowEvolution', $parameters);
|
||||
|
||||
//@review this test will need to be updated after evaluating the @review comment in API/API.php
|
||||
if (!$processedReport) {
|
||||
throw new Exception(Piwik::translate('General_NoDataForGraph'));
|
||||
}
|
||||
|
||||
// restoring generic filter parameters
|
||||
if (!$labels) {
|
||||
$_GET['filter_sort_column'] = $savedFilterSortColumnValue;
|
||||
if ($savedFilterLimitValue != -1) {
|
||||
$_GET['filter_limit'] = $savedFilterLimitValue;
|
||||
}
|
||||
}
|
||||
|
||||
// retrieve metric names & labels
|
||||
$metrics = $processedReport['metadata']['metrics'];
|
||||
$ordinateLabels = array();
|
||||
|
||||
// getRowEvolution returned more than one label
|
||||
if (!array_key_exists($plottedMetric, $metrics)) {
|
||||
$ordinateColumns = array();
|
||||
$i = 0;
|
||||
foreach ($metrics as $metric => $info) {
|
||||
$ordinateColumn = $plottedMetric . '_' . $i++;
|
||||
$ordinateColumns[] = $metric;
|
||||
$ordinateLabels[$ordinateColumn] = $info['name'];
|
||||
|
||||
if (isset($info['logo'])) {
|
||||
$ordinateLogo = $info['logo'];
|
||||
|
||||
// @review pChart does not support gifs in graph legends, would it be possible to convert all plugin pictures (cookie.gif, flash.gif, ..) to png files?
|
||||
if (!strstr($ordinateLogo, '.gif')) {
|
||||
$absoluteLogoPath = self::getAbsoluteLogoPath($ordinateLogo);
|
||||
if (file_exists($absoluteLogoPath)) {
|
||||
$ordinateLogos[$ordinateColumn] = $absoluteLogoPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$ordinateLabels[$plottedMetric] = $processedReport['label'] . ' (' . $metrics[$plottedMetric]['name'] . ')';
|
||||
}
|
||||
} else {
|
||||
$parameters = array(
|
||||
'idSite' => $idSite,
|
||||
'period' => $period,
|
||||
'date' => $date,
|
||||
'apiModule' => $apiModule,
|
||||
'apiAction' => $apiAction,
|
||||
'segment' => $segment,
|
||||
'apiParameters' => false,
|
||||
'idGoal' => $idGoal,
|
||||
'idDimension' => $idDimension,
|
||||
'language' => $languageLoaded,
|
||||
'showTimer' => true,
|
||||
'hideMetricsDoc' => false,
|
||||
'idSubtable' => $idSubtable,
|
||||
'showRawMetrics' => false
|
||||
);
|
||||
$processedReport = Request::processRequest('API.getProcessedReport', $parameters);
|
||||
}
|
||||
|
||||
// prepare abscissa and ordinate series
|
||||
$abscissaSeries = array();
|
||||
$abscissaLogos = array();
|
||||
$ordinateSeries = array();
|
||||
/** @var \Piwik\DataTable\Simple|\Piwik\DataTable\Map $reportData */
|
||||
$reportData = $processedReport['reportData'];
|
||||
$hasData = false;
|
||||
$hasNonZeroValue = false;
|
||||
|
||||
if (!$isMultiplePeriod || !($reportData instanceof Map)) {
|
||||
$reportMetadata = $processedReport['reportMetadata']->getRows();
|
||||
|
||||
$i = 0;
|
||||
// $reportData instanceof DataTable
|
||||
foreach ($reportData->getRows() as $row) // Row[]
|
||||
{
|
||||
// $row instanceof Row
|
||||
$rowData = $row->getColumns(); // Associative Array
|
||||
$abscissaSeries[] = Common::unsanitizeInputValue($rowData['label']);
|
||||
|
||||
foreach ($ordinateColumns as $column) {
|
||||
$parsedOrdinateValue = $this->parseOrdinateValue($rowData[$column]);
|
||||
$hasData = true;
|
||||
|
||||
if ($parsedOrdinateValue != 0) {
|
||||
$hasNonZeroValue = true;
|
||||
}
|
||||
$ordinateSeries[$column][] = $parsedOrdinateValue;
|
||||
}
|
||||
|
||||
if (isset($reportMetadata[$i])) {
|
||||
$rowMetadata = $reportMetadata[$i]->getColumns();
|
||||
if (isset($rowMetadata['logo'])) {
|
||||
$absoluteLogoPath = self::getAbsoluteLogoPath($rowMetadata['logo']);
|
||||
if (file_exists($absoluteLogoPath)) {
|
||||
$abscissaLogos[$i] = $absoluteLogoPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
} else // if the report has no dimension we have multiple reports each with only one row within the reportData
|
||||
{
|
||||
// $periodsData instanceof Simple[]
|
||||
$periodsData = array_values($reportData->getDataTables());
|
||||
$periodsCount = count($periodsData);
|
||||
|
||||
for ($i = 0; $i < $periodsCount; $i++) {
|
||||
// $periodsData[$i] instanceof Simple
|
||||
// $rows instanceof Row[]
|
||||
if (empty($periodsData[$i])) {
|
||||
continue;
|
||||
}
|
||||
$rows = $periodsData[$i]->getRows();
|
||||
|
||||
if (array_key_exists(0, $rows)) {
|
||||
$rowData = $rows[0]->getColumns(); // associative Array
|
||||
|
||||
foreach ($ordinateColumns as $column) {
|
||||
if(!isset($rowData[$column])) {
|
||||
continue;
|
||||
}
|
||||
$ordinateValue = $rowData[$column];
|
||||
$parsedOrdinateValue = $this->parseOrdinateValue($ordinateValue);
|
||||
|
||||
$hasData = true;
|
||||
|
||||
if (!empty($parsedOrdinateValue)) {
|
||||
$hasNonZeroValue = true;
|
||||
}
|
||||
|
||||
$ordinateSeries[$column][] = $parsedOrdinateValue;
|
||||
}
|
||||
} else {
|
||||
foreach ($ordinateColumns as $column) {
|
||||
$ordinateSeries[$column][] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$rowId = $periodsData[$i]->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getLocalizedShortString();
|
||||
$abscissaSeries[] = Common::unsanitizeInputValue($rowId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasData || !$hasNonZeroValue) {
|
||||
throw new Exception(Piwik::translate('General_NoDataForGraph'));
|
||||
}
|
||||
|
||||
//Setup the graph
|
||||
$graph = StaticGraph::factory($graphType);
|
||||
$graph->setWidth($width);
|
||||
$graph->setHeight($height);
|
||||
$graph->setFont($font);
|
||||
$graph->setFontSize($fontSize);
|
||||
$graph->setLegendFontSize($legendFontSize);
|
||||
$graph->setOrdinateLabels($ordinateLabels);
|
||||
$graph->setShowLegend($showLegend);
|
||||
$graph->setAliasedGraph($aliasedGraph);
|
||||
$graph->setAbscissaSeries($abscissaSeries);
|
||||
$graph->setAbscissaLogos($abscissaLogos);
|
||||
$graph->setOrdinateSeries($ordinateSeries);
|
||||
$graph->setOrdinateLogos($ordinateLogos);
|
||||
$graph->setColors(!empty($colors) ? explode(',', $colors) : array());
|
||||
$graph->setTextColor($textColor);
|
||||
$graph->setBackgroundColor($backgroundColor);
|
||||
$graph->setGridColor($gridColor);
|
||||
|
||||
// when requested period is day, x-axis unit is time and all date labels can not be displayed
|
||||
// within requested width, force labels to be skipped every 6 days to delimit weeks
|
||||
if ($period == 'day' && $isMultiplePeriod) {
|
||||
$graph->setForceSkippedLabels(6);
|
||||
}
|
||||
|
||||
// render graph
|
||||
$graph->renderGraph();
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$graph = new \Piwik\Plugins\ImageGraph\StaticGraph\Exception();
|
||||
$graph->setWidth($width);
|
||||
$graph->setHeight($height);
|
||||
$graph->setFont($font);
|
||||
$graph->setFontSize($fontSize);
|
||||
$graph->setBackgroundColor($backgroundColor);
|
||||
$graph->setTextColor($textColor);
|
||||
$graph->setException($e);
|
||||
$graph->renderGraph();
|
||||
}
|
||||
|
||||
// restoring get parameters
|
||||
$_GET = $savedGET;
|
||||
|
||||
switch ($outputType) {
|
||||
case self::GRAPH_OUTPUT_FILE:
|
||||
if ($idGoal != '') {
|
||||
$idGoal = '_' . $idGoal;
|
||||
}
|
||||
if ($idDimension != '') {
|
||||
$idDimension = '__' . $idDimension;
|
||||
}
|
||||
$fileName = self::$DEFAULT_PARAMETERS[$graphType][self::FILENAME_KEY] . '_' . $apiModule . '_' . $apiAction . $idGoal . $idDimension . ' ' . str_replace(',', '-', $date) . ' ' . $idSite . '.png';
|
||||
$fileName = str_replace(array(' ', '/'), '_', $fileName);
|
||||
|
||||
if (!Filesystem::isValidFilename($fileName)) {
|
||||
throw new Exception('Error: Image graph filename ' . $fileName . ' is not valid.');
|
||||
}
|
||||
|
||||
return $graph->sendToDisk($fileName);
|
||||
|
||||
case self::GRAPH_OUTPUT_PHP:
|
||||
return $graph->getRenderedImage();
|
||||
|
||||
case self::GRAPH_OUTPUT_INLINE:
|
||||
default:
|
||||
$graph->sendToBrowser();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
private function setFilterTruncate($default)
|
||||
{
|
||||
$_GET['filter_truncate'] = Common::getRequestVar('filter_truncate', $default, 'int');
|
||||
}
|
||||
|
||||
private static function parseOrdinateValue($ordinateValue)
|
||||
{
|
||||
$ordinateValue = @str_replace(',', '.', $ordinateValue);
|
||||
|
||||
// convert hh:mm:ss formatted time values to number of seconds
|
||||
if (preg_match('/([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})/', $ordinateValue, $matches)) {
|
||||
$hour = $matches[1];
|
||||
$min = $matches[2];
|
||||
$sec = $matches[3];
|
||||
|
||||
$ordinateValue = ($hour * 3600) + ($min * 60) + $sec;
|
||||
}
|
||||
|
||||
// OK, only numbers from here please (strip out currency sign)
|
||||
$ordinateValue = preg_replace('/[^0-9.]/', '', $ordinateValue);
|
||||
return $ordinateValue;
|
||||
}
|
||||
|
||||
private static function getFontPath($font)
|
||||
{
|
||||
return PIWIK_INCLUDE_PATH . self::FONT_DIR . $font;
|
||||
}
|
||||
|
||||
protected static function getAbsoluteLogoPath($relativeLogoPath)
|
||||
{
|
||||
return PIWIK_INCLUDE_PATH . '/' . $relativeLogoPath;
|
||||
}
|
||||
}
|
79
msd2/tracking/piwik/plugins/ImageGraph/Controller.php
Normal file
79
msd2/tracking/piwik/plugins/ImageGraph/Controller.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\ImageGraph;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\API\API as APIPlugins;
|
||||
use Piwik\SettingsPiwik;
|
||||
use Piwik\View;
|
||||
|
||||
class Controller extends \Piwik\Plugin\Controller
|
||||
{
|
||||
/**
|
||||
* @internal For Debugging only
|
||||
* Call metadata reports and draw the default graph for each report.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
Piwik::checkUserHasSomeAdminAccess();
|
||||
$idSite = $this->idSite ?: 1;
|
||||
$period = Common::getRequestVar('period', 'day', 'string');
|
||||
$date = Common::getRequestVar('date', 'today', 'string');
|
||||
$_GET['token_auth'] = Piwik::getCurrentUserTokenAuth();
|
||||
$reports = APIPlugins::getInstance()->getReportMetadata($idSite, $period, $date);
|
||||
$plot = array();
|
||||
foreach ($reports as $report) {
|
||||
if (!empty($report['imageGraphUrl'])) {
|
||||
$plot[] = array(
|
||||
// Title
|
||||
$report['category'] . ' › ' . $report['name'],
|
||||
//URL
|
||||
SettingsPiwik::getPiwikUrl() . $report['imageGraphUrl']
|
||||
);
|
||||
}
|
||||
}
|
||||
$view = new View('@ImageGraph/index');
|
||||
$view->titleAndUrls = $plot;
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
// Draw graphs for all sizes (DEBUG)
|
||||
public function testAllSizes()
|
||||
{
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
|
||||
$view = new View('@ImageGraph/testAllSizes');
|
||||
$this->setGeneralVariablesView($view);
|
||||
|
||||
$period = Common::getRequestVar('period', 'day', 'string');
|
||||
$date = Common::getRequestVar('date', 'today', 'string');
|
||||
|
||||
$_GET['token_auth'] = Piwik::getCurrentUserTokenAuth();
|
||||
$availableReports = APIPlugins::getInstance()->getReportMetadata($this->idSite, $period, $date);
|
||||
$view->availableReports = $availableReports;
|
||||
$view->graphTypes = array(
|
||||
'', // default graph type
|
||||
// 'evolution',
|
||||
// 'verticalBar',
|
||||
// 'horizontalBar',
|
||||
// 'pie',
|
||||
// '3dPie',
|
||||
);
|
||||
$view->graphSizes = array(
|
||||
array(null, null), // default graph size
|
||||
array(460, 150), // standard phone
|
||||
array(300, 150), // standard phone 2
|
||||
array(240, 150), // smallest mobile display
|
||||
array(800, 150), // landscape mode
|
||||
array(600, 300, $fontSize = 18, 300, 150), // iphone requires bigger font, then it will be scaled down by ios
|
||||
);
|
||||
return $view->render();
|
||||
}
|
||||
}
|
186
msd2/tracking/piwik/plugins/ImageGraph/ImageGraph.php
Normal file
186
msd2/tracking/piwik/plugins/ImageGraph/ImageGraph.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\ImageGraph;
|
||||
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\Period;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Scheduler\Scheduler;
|
||||
use Piwik\Site;
|
||||
use Piwik\Url;
|
||||
use Piwik\Period\Factory as PeriodFactory;
|
||||
|
||||
class ImageGraph extends \Piwik\Plugin
|
||||
{
|
||||
private static $CONSTANT_ROW_COUNT_REPORT_EXCEPTIONS = array(
|
||||
'Referrers_getReferrerType',
|
||||
);
|
||||
|
||||
// row evolution support not yet implemented for these APIs
|
||||
private static $REPORTS_DISABLED_EVOLUTION_GRAPH = array(
|
||||
'Referrers_getAll',
|
||||
);
|
||||
|
||||
/**
|
||||
* @see Piwik\Plugin::registerEvents
|
||||
*/
|
||||
public function registerEvents()
|
||||
{
|
||||
$hooks = array(
|
||||
'API.getReportMetadata.end' => array('function' => 'getReportMetadata',
|
||||
'after' => true),
|
||||
);
|
||||
return $hooks;
|
||||
}
|
||||
|
||||
// Number of periods to plot on an evolution graph
|
||||
const GRAPH_EVOLUTION_LAST_PERIODS = 30;
|
||||
|
||||
/**
|
||||
* @param array $reports
|
||||
* @param array $info
|
||||
* @return mixed
|
||||
*/
|
||||
public function getReportMetadata(&$reports, $info)
|
||||
{
|
||||
$idSite = $info['idSite'];
|
||||
|
||||
// If only one website is selected, we add the Graph URL
|
||||
if (empty($idSite)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// in case API.getReportMetadata was not called with date/period we use sane defaults
|
||||
if (empty($info['period'])) {
|
||||
$info['period'] = 'day';
|
||||
}
|
||||
if (empty($info['date'])) {
|
||||
$info['date'] = 'today';
|
||||
}
|
||||
|
||||
// need two sets of period & date, one for single period graphs, one for multiple periods graphs
|
||||
if (Period::isMultiplePeriod($info['date'], $info['period'])) {
|
||||
$periodForMultiplePeriodGraph = $info['period'];
|
||||
$dateForMultiplePeriodGraph = $info['date'];
|
||||
|
||||
$periodForSinglePeriodGraph = 'range';
|
||||
$dateForSinglePeriodGraph = $info['date'];
|
||||
} else {
|
||||
$periodForSinglePeriodGraph = $info['period'];
|
||||
$dateForSinglePeriodGraph = $info['date'];
|
||||
|
||||
$piwikSite = new Site($idSite);
|
||||
if ($periodForSinglePeriodGraph == 'range') {
|
||||
// for period=range, show the configured sub-periods
|
||||
$periodForMultiplePeriodGraph = Config::getInstance()->General['graphs_default_period_to_plot_when_period_range'];
|
||||
$dateForMultiplePeriodGraph = $dateForSinglePeriodGraph;
|
||||
} else if ($info['period'] == 'day' || !Config::getInstance()->General['graphs_show_evolution_within_selected_period']) {
|
||||
// for period=day, always show the last n days
|
||||
// if graphs_show_evolution_within_selected_period=false, show the last n periods
|
||||
$periodForMultiplePeriodGraph = $periodForSinglePeriodGraph;
|
||||
$dateForMultiplePeriodGraph = Range::getRelativeToEndDate(
|
||||
$periodForSinglePeriodGraph,
|
||||
'last' . self::getDefaultGraphEvolutionLastPeriods(),
|
||||
$dateForSinglePeriodGraph,
|
||||
$piwikSite
|
||||
);
|
||||
} else {
|
||||
// if graphs_show_evolution_within_selected_period=true, show the days within the period
|
||||
// (except if the period is day, see above)
|
||||
$periodForMultiplePeriodGraph = 'day';
|
||||
$period = PeriodFactory::build($info['period'], $info['date']);
|
||||
$start = $period->getDateStart()->toString();
|
||||
$end = $period->getDateEnd()->toString();
|
||||
$dateForMultiplePeriodGraph = $start . ',' . $end;
|
||||
}
|
||||
}
|
||||
|
||||
$token_auth = Common::getRequestVar('token_auth', false);
|
||||
|
||||
$segment = Request::getRawSegmentFromRequest();
|
||||
|
||||
/** @var Scheduler $scheduler */
|
||||
$scheduler = StaticContainer::getContainer()->get('Piwik\Scheduler\Scheduler');
|
||||
$isRunningTask = $scheduler->isRunningTask();
|
||||
|
||||
// add the idSubtable if it exists
|
||||
$idSubtable = Common::getRequestVar('idSubtable', false);
|
||||
|
||||
$urlPrefix = "index.php?";
|
||||
foreach ($reports as &$report) {
|
||||
$reportModule = $report['module'];
|
||||
$reportAction = $report['action'];
|
||||
$reportUniqueId = $reportModule . '_' . $reportAction;
|
||||
|
||||
$parameters = array();
|
||||
$parameters['module'] = 'API';
|
||||
$parameters['method'] = 'ImageGraph.get';
|
||||
$parameters['idSite'] = $idSite;
|
||||
$parameters['apiModule'] = $reportModule;
|
||||
$parameters['apiAction'] = $reportAction;
|
||||
if (!empty($token_auth)) {
|
||||
$parameters['token_auth'] = $token_auth;
|
||||
}
|
||||
|
||||
// Forward custom Report parameters to the graph URL
|
||||
if (!empty($report['parameters'])) {
|
||||
$parameters = array_merge($parameters, $report['parameters']);
|
||||
}
|
||||
if (empty($report['dimension'])) {
|
||||
$parameters['period'] = $periodForMultiplePeriodGraph;
|
||||
$parameters['date'] = $dateForMultiplePeriodGraph;
|
||||
} else {
|
||||
$parameters['period'] = $periodForSinglePeriodGraph;
|
||||
$parameters['date'] = $dateForSinglePeriodGraph;
|
||||
}
|
||||
|
||||
if ($idSubtable !== false) {
|
||||
$parameters['idSubtable'] = $idSubtable;
|
||||
}
|
||||
|
||||
if (!empty($_GET['_restrictSitesToLogin']) && $isRunningTask) {
|
||||
$parameters['_restrictSitesToLogin'] = $_GET['_restrictSitesToLogin'];
|
||||
}
|
||||
|
||||
if (!empty($segment)) {
|
||||
$parameters['segment'] = $segment;
|
||||
}
|
||||
|
||||
$report['imageGraphUrl'] = $urlPrefix . Url::getQueryStringFromParameters($parameters);
|
||||
|
||||
// thanks to API.getRowEvolution, reports with dimensions can now be plotted using an evolution graph
|
||||
// however, most reports with a fixed set of dimension values are excluded
|
||||
// this is done so Piwik Mobile and Scheduled Reports do not display them
|
||||
$reportWithDimensionsSupportsEvolution = empty($report['constantRowsCount']) || in_array($reportUniqueId, self::$CONSTANT_ROW_COUNT_REPORT_EXCEPTIONS);
|
||||
|
||||
$reportSupportsEvolution = !in_array($reportUniqueId, self::$REPORTS_DISABLED_EVOLUTION_GRAPH);
|
||||
|
||||
if ($reportSupportsEvolution
|
||||
&& $reportWithDimensionsSupportsEvolution
|
||||
) {
|
||||
$parameters['period'] = $periodForMultiplePeriodGraph;
|
||||
$parameters['date'] = $dateForMultiplePeriodGraph;
|
||||
$report['imageGraphEvolutionUrl'] = $urlPrefix . Url::getQueryStringFromParameters($parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getDefaultGraphEvolutionLastPeriods()
|
||||
{
|
||||
$lastPeriods = (int) Config::getInstance()->General['graphs_default_evolution_graph_last_days_amount'];
|
||||
if ($lastPeriods <= 0) {
|
||||
throw new \Exception("Invalid value '$lastPeriods' supplied for [General] graphs_default_evolution_graph_last_days_amount config.");
|
||||
}
|
||||
return $lastPeriods;
|
||||
}
|
||||
}
|
386
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph.php
Normal file
386
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph.php
Normal file
@ -0,0 +1,386 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\ImageGraph;
|
||||
|
||||
use CpChart\Chart\Data;
|
||||
use CpChart\Chart\Image;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\NumberFormatter;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\BaseFactory;
|
||||
|
||||
/**
|
||||
* The StaticGraph abstract class is used as a base class for different types of static graphs.
|
||||
*
|
||||
*/
|
||||
abstract class StaticGraph extends BaseFactory
|
||||
{
|
||||
const GRAPH_TYPE_BASIC_LINE = "evolution";
|
||||
const GRAPH_TYPE_VERTICAL_BAR = "verticalBar";
|
||||
const GRAPH_TYPE_HORIZONTAL_BAR = "horizontalBar";
|
||||
const GRAPH_TYPE_3D_PIE = "3dPie";
|
||||
const GRAPH_TYPE_BASIC_PIE = "pie";
|
||||
|
||||
private static $availableStaticGraphTypes = array(
|
||||
self::GRAPH_TYPE_BASIC_LINE => 'Evolution',
|
||||
self::GRAPH_TYPE_VERTICAL_BAR => 'VerticalBar',
|
||||
self::GRAPH_TYPE_HORIZONTAL_BAR => 'HorizontalBar',
|
||||
self::GRAPH_TYPE_BASIC_PIE => 'Pie',
|
||||
self::GRAPH_TYPE_3D_PIE => 'Pie3D',
|
||||
);
|
||||
|
||||
const ABSCISSA_SERIE_NAME = 'ABSCISSA';
|
||||
|
||||
private $aliasedGraph;
|
||||
|
||||
/**
|
||||
* @var Image
|
||||
*/
|
||||
protected $pImage;
|
||||
/**
|
||||
* @var Data
|
||||
*/
|
||||
protected $pData;
|
||||
protected $ordinateLabels;
|
||||
protected $showLegend;
|
||||
protected $abscissaSeries;
|
||||
protected $abscissaLogos;
|
||||
protected $ordinateSeries;
|
||||
protected $ordinateLogos;
|
||||
protected $colors;
|
||||
protected $font;
|
||||
protected $fontSize;
|
||||
protected $textColor = [];
|
||||
protected $backgroundColor = [];
|
||||
protected $gridColor = [];
|
||||
protected $legendFontSize;
|
||||
protected $width;
|
||||
protected $height;
|
||||
protected $forceSkippedLabels = false;
|
||||
|
||||
abstract protected function getDefaultColors();
|
||||
|
||||
abstract public function renderGraph();
|
||||
|
||||
protected static function getClassNameFromClassId($graphType)
|
||||
{
|
||||
$className = self::$availableStaticGraphTypes[$graphType];
|
||||
$className = __NAMESPACE__ . "\\StaticGraph\\" . $className;
|
||||
return $className;
|
||||
}
|
||||
|
||||
protected static function getInvalidClassIdExceptionMessage($graphType)
|
||||
{
|
||||
return Piwik::translate(
|
||||
'General_ExceptionInvalidStaticGraphType',
|
||||
array($graphType, implode(', ', self::getAvailableStaticGraphTypes()))
|
||||
);
|
||||
}
|
||||
|
||||
public static function getAvailableStaticGraphTypes()
|
||||
{
|
||||
return array_keys(self::$availableStaticGraphTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save rendering to disk
|
||||
*
|
||||
* @param string $filename without path
|
||||
* @return string path of file
|
||||
*/
|
||||
public function sendToDisk($filename)
|
||||
{
|
||||
$filePath = self::getOutputPath($filename);
|
||||
$this->pImage->render($filePath);
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource rendered static graph
|
||||
*/
|
||||
public function getRenderedImage()
|
||||
{
|
||||
return $this->pImage->Picture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output rendering to browser
|
||||
*/
|
||||
public function sendToBrowser()
|
||||
{
|
||||
$this->pImage->stroke();
|
||||
}
|
||||
|
||||
public function setWidth($width)
|
||||
{
|
||||
$this->width = $width;
|
||||
}
|
||||
|
||||
public function setHeight($height)
|
||||
{
|
||||
$this->height = $height;
|
||||
}
|
||||
|
||||
public function setFontSize($fontSize)
|
||||
{
|
||||
if (!is_numeric($fontSize)) {
|
||||
$fontSize = API::DEFAULT_FONT_SIZE;
|
||||
}
|
||||
$this->fontSize = $fontSize;
|
||||
}
|
||||
|
||||
public function setLegendFontSize($legendFontSize)
|
||||
{
|
||||
$this->legendFontSize = $legendFontSize;
|
||||
}
|
||||
|
||||
public function setFont($font)
|
||||
{
|
||||
$this->font = $font;
|
||||
}
|
||||
|
||||
public function setTextColor($textColor)
|
||||
{
|
||||
$this->textColor = self::hex2rgb($textColor);
|
||||
}
|
||||
|
||||
public function setBackgroundColor($backgroundColor)
|
||||
{
|
||||
$this->backgroundColor = self::hex2rgb($backgroundColor);
|
||||
}
|
||||
|
||||
public function setGridColor($gridColor)
|
||||
{
|
||||
$this->gridColor = self::hex2rgb($gridColor);
|
||||
}
|
||||
|
||||
public function setOrdinateSeries($ordinateSeries)
|
||||
{
|
||||
$this->ordinateSeries = $ordinateSeries;
|
||||
}
|
||||
|
||||
public function setOrdinateLogos($ordinateLogos)
|
||||
{
|
||||
$this->ordinateLogos = $ordinateLogos;
|
||||
}
|
||||
|
||||
public function setAbscissaLogos($abscissaLogos)
|
||||
{
|
||||
$this->abscissaLogos = $abscissaLogos;
|
||||
}
|
||||
|
||||
public function setAbscissaSeries($abscissaSeries)
|
||||
{
|
||||
$this->abscissaSeries = $abscissaSeries;
|
||||
}
|
||||
|
||||
public function setShowLegend($showLegend)
|
||||
{
|
||||
$this->showLegend = $showLegend;
|
||||
}
|
||||
|
||||
public function setForceSkippedLabels($forceSkippedLabels)
|
||||
{
|
||||
$this->forceSkippedLabels = $forceSkippedLabels;
|
||||
}
|
||||
|
||||
public function setOrdinateLabels($ordinateLabels)
|
||||
{
|
||||
$this->ordinateLabels = $ordinateLabels;
|
||||
}
|
||||
|
||||
public function setAliasedGraph($aliasedGraph)
|
||||
{
|
||||
$this->aliasedGraph = $aliasedGraph;
|
||||
}
|
||||
|
||||
public function setColors($colors)
|
||||
{
|
||||
$i = 0;
|
||||
foreach ($this->getDefaultColors() as $colorKey => $defaultColor) {
|
||||
if (isset($colors[$i]) && $this->hex2rgb($colors[$i])) {
|
||||
$hexColor = $colors[$i];
|
||||
} else {
|
||||
$hexColor = $defaultColor;
|
||||
}
|
||||
|
||||
$this->colors[$colorKey] = $this->hex2rgb($hexColor);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return $filename with temp directory and delete file
|
||||
*
|
||||
* @static
|
||||
* @param $filename
|
||||
* @return string path of file in temp directory
|
||||
*/
|
||||
protected static function getOutputPath($filename)
|
||||
{
|
||||
$outputFilename = StaticContainer::get('path.tmp') . '/assets/' . $filename;
|
||||
|
||||
@chmod($outputFilename, 0600);
|
||||
@unlink($outputFilename);
|
||||
return $outputFilename;
|
||||
}
|
||||
|
||||
protected function initpData()
|
||||
{
|
||||
$this->pData = new Data();
|
||||
|
||||
foreach ($this->ordinateSeries as $column => $data) {
|
||||
$this->pData->addPoints($data, $column);
|
||||
$this->pData->setSerieDescription($column, $this->ordinateLabels[$column]);
|
||||
|
||||
if (isset($this->ordinateLogos[$column])) {
|
||||
$ordinateLogo = $this->createResizedImageCopyIfNeeded($this->ordinateLogos[$column]);
|
||||
$this->pData->setSeriePicture($column, $ordinateLogo);
|
||||
}
|
||||
}
|
||||
|
||||
$this->pData->setAxisDisplay(0, AXIS_FORMAT_CUSTOM, '\\Piwik\\Plugins\\ImageGraph\\formatYAxis');
|
||||
|
||||
$this->pData->addPoints($this->abscissaSeries, self::ABSCISSA_SERIE_NAME);
|
||||
$this->pData->setAbscissa(self::ABSCISSA_SERIE_NAME);
|
||||
}
|
||||
|
||||
protected function createResizedImageCopyIfNeeded($image)
|
||||
{
|
||||
$size = getimagesize($image);
|
||||
|
||||
if ($size[0] <= 16 && $size[1] <= 16) {
|
||||
return $image; // use original image if size fits
|
||||
}
|
||||
|
||||
$ratio = $size[0] / $size[1];
|
||||
if ($ratio > 1) {
|
||||
$width = 16;
|
||||
$height = 16 / $ratio;
|
||||
} else {
|
||||
$width = 16 * $ratio;
|
||||
$height = 16;
|
||||
}
|
||||
|
||||
$newImage = self::getOutputPath(md5($image) . '.png');
|
||||
|
||||
$src = imagecreatefromstring(file_get_contents($image));
|
||||
$dst = imagecreatetruecolor($width, $height);
|
||||
imagesavealpha($dst, true);
|
||||
$color = imagecolorallocatealpha($dst, 0, 0, 0, 127);
|
||||
imagefill($dst, 0, 0, $color);
|
||||
imagecopyresampled($dst, $src, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
|
||||
imagedestroy($src);
|
||||
imagepng($dst, $newImage);
|
||||
imagedestroy($dst);
|
||||
|
||||
return $newImage;
|
||||
}
|
||||
|
||||
protected function initpImage()
|
||||
{
|
||||
$this->pImage = new Image($this->width, $this->height, $this->pData);
|
||||
$this->pImage->Antialias = $this->aliasedGraph;
|
||||
|
||||
$this->pImage->setFontProperties(
|
||||
array_merge(
|
||||
array(
|
||||
'FontName' => $this->font,
|
||||
'FontSize' => $this->fontSize,
|
||||
),
|
||||
$this->textColor
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function getTextWidthHeight($text, $fontSize = false)
|
||||
{
|
||||
if (!$fontSize) {
|
||||
$fontSize = $this->fontSize;
|
||||
}
|
||||
|
||||
if (!$this->pImage) {
|
||||
$this->initpImage();
|
||||
}
|
||||
|
||||
// could not find a way to get pixel perfect width & height info using imageftbbox
|
||||
$textInfo = $this->pImage->drawText(
|
||||
0, 0, $text,
|
||||
array(
|
||||
'Alpha' => 0,
|
||||
'FontSize' => $fontSize,
|
||||
'FontName' => $this->font
|
||||
)
|
||||
);
|
||||
|
||||
return array($textInfo[1]['X'] + 1, $textInfo[0]['Y'] - $textInfo[2]['Y']);
|
||||
}
|
||||
|
||||
protected function getMaximumTextWidthHeight($values)
|
||||
{
|
||||
if (array_values($values) === $values) {
|
||||
$values = array('' => $values);
|
||||
}
|
||||
|
||||
$maxWidth = 0;
|
||||
$maxHeight = 0;
|
||||
foreach ($values as $data) {
|
||||
foreach ($data as $value) {
|
||||
list($valueWidth, $valueHeight) = $this->getTextWidthHeight($value);
|
||||
|
||||
if ($valueWidth > $maxWidth) {
|
||||
$maxWidth = $valueWidth;
|
||||
}
|
||||
|
||||
if ($valueHeight > $maxHeight) {
|
||||
$maxHeight = $valueHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array($maxWidth, $maxHeight);
|
||||
}
|
||||
|
||||
protected function drawBackground()
|
||||
{
|
||||
$this->pImage->drawFilledRectangle(
|
||||
0,
|
||||
0,
|
||||
$this->width,
|
||||
$this->height,
|
||||
array_merge(array('Alpha' => 100), $this->backgroundColor)
|
||||
);
|
||||
}
|
||||
|
||||
private static function hex2rgb($hexColor)
|
||||
{
|
||||
if (preg_match('/([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/', $hexColor, $matches)) {
|
||||
return array(
|
||||
'R' => hexdec($matches[1]),
|
||||
'G' => hexdec($matches[2]),
|
||||
'B' => hexdec($matches[3])
|
||||
);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global format method
|
||||
*
|
||||
* required to format y axis values using CpChart internal format callbacks
|
||||
* @param $value
|
||||
* @return mixed
|
||||
*/
|
||||
function formatYAxis($value)
|
||||
{
|
||||
return NumberFormatter::getInstance()->format($value);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Evolution extends GridGraph
|
||||
{
|
||||
|
||||
public function renderGraph()
|
||||
{
|
||||
$this->initGridChart(
|
||||
$displayVerticalGridLines = true,
|
||||
$bulletType = LEGEND_FAMILY_LINE,
|
||||
$horizontalGraph = false,
|
||||
$showTicks = true,
|
||||
$verticalLegend = true
|
||||
);
|
||||
|
||||
$this->pImage->drawLineChart();
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
use CpChart\Chart\Data;
|
||||
use Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Exception extends StaticGraph
|
||||
{
|
||||
const MESSAGE_RIGHT_MARGIN = 5;
|
||||
|
||||
/**
|
||||
* @var \Exception
|
||||
*/
|
||||
private $exception;
|
||||
|
||||
public function setException(\Exception $exception)
|
||||
{
|
||||
$this->exception = $exception;
|
||||
}
|
||||
|
||||
protected function getDefaultColors()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function setWidth($width)
|
||||
{
|
||||
if (empty($width)) {
|
||||
$width = 450;
|
||||
}
|
||||
parent::setWidth($width);
|
||||
}
|
||||
|
||||
public function setHeight($height)
|
||||
{
|
||||
if (empty($height)) {
|
||||
$height = 300;
|
||||
}
|
||||
parent::setHeight($height);
|
||||
}
|
||||
|
||||
public function renderGraph()
|
||||
{
|
||||
$this->pData = new Data();
|
||||
|
||||
$message = $this->exception->getMessage();
|
||||
list($textWidth, $textHeight) = $this->getTextWidthHeight($message);
|
||||
|
||||
if ($this->width == null) {
|
||||
$this->width = $textWidth + self::MESSAGE_RIGHT_MARGIN;
|
||||
}
|
||||
|
||||
if ($this->height == null) {
|
||||
$this->height = $textHeight;
|
||||
}
|
||||
|
||||
$this->initpImage();
|
||||
|
||||
$this->drawBackground();
|
||||
|
||||
$this->pImage->drawText(
|
||||
0,
|
||||
$textHeight,
|
||||
$message,
|
||||
$this->textColor
|
||||
);
|
||||
}
|
||||
}
|
485
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph/GridGraph.php
Normal file
485
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph/GridGraph.php
Normal file
@ -0,0 +1,485 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
use Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
abstract class GridGraph extends StaticGraph
|
||||
{
|
||||
const GRAPHIC_COLOR_KEY = 'GRAPHIC_COLOR';
|
||||
|
||||
const TRUNCATION_TEXT = '...';
|
||||
|
||||
const DEFAULT_TICK_ALPHA = 20;
|
||||
const DEFAULT_SERIE_WEIGHT = 0.5;
|
||||
const LEFT_GRID_MARGIN = 4;
|
||||
const BOTTOM_GRID_MARGIN = 10;
|
||||
const TOP_GRID_MARGIN_HORIZONTAL_GRAPH = 1;
|
||||
const RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH = 4;
|
||||
const OUTER_TICK_WIDTH = 5;
|
||||
const INNER_TICK_WIDTH = 0;
|
||||
const LABEL_SPACE_VERTICAL_GRAPH = 7;
|
||||
|
||||
const HORIZONTAL_LEGEND_TOP_MARGIN = 5;
|
||||
const HORIZONTAL_LEGEND_LEFT_MARGIN = 10;
|
||||
const HORIZONTAL_LEGEND_BOTTOM_MARGIN = 10;
|
||||
const VERTICAL_LEGEND_TOP_MARGIN = 8;
|
||||
const VERTICAL_LEGEND_LEFT_MARGIN = 6;
|
||||
const VERTICAL_LEGEND_MAX_WIDTH_PCT = 0.70;
|
||||
const LEGEND_LINE_BULLET_WIDTH = 14;
|
||||
const LEGEND_BOX_BULLET_WIDTH = 5;
|
||||
const LEGEND_BULLET_RIGHT_PADDING = 5;
|
||||
const LEGEND_ITEM_HORIZONTAL_INTERSTICE = 6;
|
||||
const LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET = 4;
|
||||
const LEGEND_SHADOW_OPACITY = 25;
|
||||
const LEGEND_VERTICAL_SHADOW_PADDING = 3;
|
||||
const LEGEND_HORIZONTAL_SHADOW_PADDING = 2;
|
||||
const PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE = 5;
|
||||
|
||||
protected function getDefaultColors()
|
||||
{
|
||||
return array(
|
||||
self::GRAPHIC_COLOR_KEY . '1' => '5170AE',
|
||||
self::GRAPHIC_COLOR_KEY . '2' => 'F29007',
|
||||
self::GRAPHIC_COLOR_KEY . '3' => 'CC3399',
|
||||
self::GRAPHIC_COLOR_KEY . '4' => '9933CC',
|
||||
self::GRAPHIC_COLOR_KEY . '5' => '80A033',
|
||||
self::GRAPHIC_COLOR_KEY . '6' => '246AD2'
|
||||
);
|
||||
}
|
||||
|
||||
protected function initGridChart(
|
||||
$displayVerticalGridLines,
|
||||
$bulletType,
|
||||
$horizontalGraph,
|
||||
$showTicks,
|
||||
$verticalLegend
|
||||
)
|
||||
{
|
||||
$this->initpData();
|
||||
|
||||
$colorIndex = 1;
|
||||
foreach ($this->ordinateSeries as $column => $data) {
|
||||
$this->pData->setSerieWeight($column, self::DEFAULT_SERIE_WEIGHT);
|
||||
$graphicColor = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];
|
||||
$this->pData->setPalette($column, $graphicColor);
|
||||
}
|
||||
|
||||
$this->initpImage();
|
||||
|
||||
// graph area coordinates
|
||||
$topLeftXValue = $this->getGridLeftMargin($horizontalGraph, $withLabel = true);
|
||||
$topLeftYValue = $this->getGridTopMargin($horizontalGraph, $verticalLegend);
|
||||
$bottomRightXValue = $this->width - $this->getGridRightMargin($horizontalGraph);
|
||||
$bottomRightYValue = $this->getGraphBottom($horizontalGraph);
|
||||
|
||||
$this->drawBackground();
|
||||
|
||||
$this->pImage->setGraphArea(
|
||||
$topLeftXValue,
|
||||
$topLeftYValue,
|
||||
$bottomRightXValue,
|
||||
$bottomRightYValue
|
||||
);
|
||||
|
||||
// determine how many labels need to be skipped
|
||||
$skippedLabels = 0;
|
||||
if (!$horizontalGraph) {
|
||||
list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
|
||||
$graphWidth = $bottomRightXValue - $topLeftXValue;
|
||||
$maxNumOfLabels = floor($graphWidth / ($abscissaMaxWidth + self::LABEL_SPACE_VERTICAL_GRAPH));
|
||||
|
||||
$abscissaSeriesCount = count($this->abscissaSeries);
|
||||
if ($maxNumOfLabels < $abscissaSeriesCount) {
|
||||
for ($candidateSkippedLabels = 1; $candidateSkippedLabels < $abscissaSeriesCount; $candidateSkippedLabels++) {
|
||||
$numberOfSegments = $abscissaSeriesCount / ($candidateSkippedLabels + 1);
|
||||
$numberOfCompleteSegments = floor($numberOfSegments);
|
||||
|
||||
$numberOfLabels = $numberOfCompleteSegments;
|
||||
if ($numberOfSegments > $numberOfCompleteSegments) {
|
||||
$numberOfLabels++;
|
||||
}
|
||||
|
||||
if ($numberOfLabels <= $maxNumOfLabels) {
|
||||
$skippedLabels = $candidateSkippedLabels;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->forceSkippedLabels
|
||||
&& $skippedLabels
|
||||
&& $skippedLabels < $this->forceSkippedLabels
|
||||
&& $abscissaSeriesCount > $this->forceSkippedLabels + 1
|
||||
) {
|
||||
$skippedLabels = $this->forceSkippedLabels;
|
||||
}
|
||||
}
|
||||
|
||||
$ordinateAxisLength =
|
||||
$horizontalGraph ? $bottomRightXValue - $topLeftXValue : $this->getGraphHeight($horizontalGraph, $verticalLegend);
|
||||
|
||||
$maxOrdinateValue = 0;
|
||||
foreach ($this->ordinateSeries as $column => $data) {
|
||||
$currentMax = $this->pData->getMax($column);
|
||||
|
||||
if ($currentMax > $maxOrdinateValue) {
|
||||
$maxOrdinateValue = $currentMax;
|
||||
}
|
||||
}
|
||||
|
||||
// rounding top scale value to the next multiple of 10
|
||||
if ($maxOrdinateValue > 10) {
|
||||
$modTen = $maxOrdinateValue % 10;
|
||||
if ($modTen) $maxOrdinateValue += 10 - $modTen;
|
||||
}
|
||||
|
||||
$gridColor = $this->gridColor;
|
||||
$this->pImage->drawScale(
|
||||
array(
|
||||
'Mode' => SCALE_MODE_MANUAL,
|
||||
'GridTicks' => 0,
|
||||
'LabelSkip' => $skippedLabels,
|
||||
'DrawXLines' => $displayVerticalGridLines,
|
||||
'Factors' => array(ceil($maxOrdinateValue / 2)),
|
||||
'MinDivHeight' => $ordinateAxisLength / 2,
|
||||
'AxisAlpha' => 0,
|
||||
'SkippedAxisAlpha' => 0,
|
||||
'TickAlpha' => $showTicks ? self::DEFAULT_TICK_ALPHA : 0,
|
||||
'InnerTickWidth' => self::INNER_TICK_WIDTH,
|
||||
'OuterTickWidth' => self::OUTER_TICK_WIDTH,
|
||||
'GridR' => $gridColor['R'],
|
||||
'GridG' => $gridColor['G'],
|
||||
'GridB' => $gridColor['B'],
|
||||
'GridAlpha' => 100,
|
||||
'ManualScale' => array(
|
||||
0 => array(
|
||||
'Min' => 0,
|
||||
'Max' => $maxOrdinateValue
|
||||
)
|
||||
),
|
||||
'Pos' => $horizontalGraph ? SCALE_POS_TOPBOTTOM : SCALE_POS_LEFTRIGHT,
|
||||
)
|
||||
);
|
||||
|
||||
if ($this->showLegend) {
|
||||
switch ($bulletType) {
|
||||
case LEGEND_FAMILY_LINE:
|
||||
$bulletWidth = self::LEGEND_LINE_BULLET_WIDTH;
|
||||
|
||||
// measured using a picture editing software
|
||||
$iconOffsetAboveLabelSymmetryAxis = -2;
|
||||
break;
|
||||
|
||||
case LEGEND_FAMILY_BOX:
|
||||
$bulletWidth = self::LEGEND_BOX_BULLET_WIDTH;
|
||||
|
||||
// measured using a picture editing software
|
||||
$iconOffsetAboveLabelSymmetryAxis = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
// pChart requires two coordinates to draw the legend $legendTopLeftXValue & $legendTopLeftYValue
|
||||
// $legendTopLeftXValue = legend's left padding
|
||||
$legendTopLeftXValue = $topLeftXValue + ($verticalLegend ? self::VERTICAL_LEGEND_LEFT_MARGIN : self::HORIZONTAL_LEGEND_LEFT_MARGIN);
|
||||
|
||||
// $legendTopLeftYValue = y coordinate of the top edge of the legend's icons
|
||||
// Caution :
|
||||
// - pChart will silently add some value (see $paddingAddedByPChart) to $legendTopLeftYValue depending on multiple criteria
|
||||
// - pChart will not take into account the size of the text. Setting $legendTopLeftYValue = 0 will crop the legend's labels
|
||||
// The following section of code determines the value of $legendTopLeftYValue while taking into account the following paremeters :
|
||||
// - whether legend items have icons
|
||||
// - whether icons are bigger than the legend's labels
|
||||
// - how much colored shadow padding is required
|
||||
list($maxLogoWidth, $maxLogoHeight) = self::getMaxLogoSize(array_values($this->ordinateLogos));
|
||||
if ($maxLogoHeight >= $this->legendFontSize) {
|
||||
$heightOfTextAboveBulletTop = 0;
|
||||
$paddingCreatedByLogo = $maxLogoHeight - $this->legendFontSize;
|
||||
$effectiveShadowPadding = $paddingCreatedByLogo < self::LEGEND_VERTICAL_SHADOW_PADDING * 2 ? self::LEGEND_VERTICAL_SHADOW_PADDING - ($paddingCreatedByLogo / 2) : 0;
|
||||
} else {
|
||||
if ($maxLogoHeight) {
|
||||
// measured using a picture editing software
|
||||
$iconOffsetAboveLabelSymmetryAxis = 5;
|
||||
}
|
||||
$heightOfTextAboveBulletTop = $this->legendFontSize / 2 - $iconOffsetAboveLabelSymmetryAxis;
|
||||
$effectiveShadowPadding = self::LEGEND_VERTICAL_SHADOW_PADDING;
|
||||
}
|
||||
|
||||
$effectiveLegendItemVerticalInterstice = $this->legendFontSize + self::LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET;
|
||||
$effectiveLegendItemHorizontalInterstice = self::LEGEND_ITEM_HORIZONTAL_INTERSTICE + self::LEGEND_HORIZONTAL_SHADOW_PADDING;
|
||||
|
||||
$legendTopMargin = $verticalLegend ? self::VERTICAL_LEGEND_TOP_MARGIN : self::HORIZONTAL_LEGEND_TOP_MARGIN;
|
||||
$requiredPaddingAboveItemBullet = $legendTopMargin + $heightOfTextAboveBulletTop + $effectiveShadowPadding;
|
||||
|
||||
$paddingAddedByPChart = 0;
|
||||
if ($verticalLegend) {
|
||||
if ($maxLogoHeight) {
|
||||
// see line 1691 of pDraw.class.php
|
||||
if ($maxLogoHeight < $effectiveLegendItemVerticalInterstice) {
|
||||
$paddingAddedByPChart = ($effectiveLegendItemVerticalInterstice / 2) - ($maxLogoHeight / 2);
|
||||
}
|
||||
} else {
|
||||
// see line 1711 of pDraw.class.php ($Y+$IconAreaHeight/2)
|
||||
$paddingAddedByPChart = $effectiveLegendItemVerticalInterstice / 2;
|
||||
}
|
||||
}
|
||||
|
||||
$legendTopLeftYValue = $paddingAddedByPChart < $requiredPaddingAboveItemBullet ? $requiredPaddingAboveItemBullet - $paddingAddedByPChart : 0;
|
||||
|
||||
// add colored background to each legend item
|
||||
if (count($this->ordinateLabels) > 1) {
|
||||
$currentPosition = $verticalLegend ? $legendTopMargin : $legendTopLeftXValue;
|
||||
$colorIndex = 1;
|
||||
foreach ($this->ordinateLabels as $metricCode => &$label) {
|
||||
$color = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];
|
||||
|
||||
$paddedBulletWidth = $bulletWidth;
|
||||
if (isset($this->ordinateLogos[$metricCode])) {
|
||||
$paddedBulletWidth = $maxLogoWidth;
|
||||
}
|
||||
$paddedBulletWidth += self::LEGEND_BULLET_RIGHT_PADDING;
|
||||
|
||||
// truncate labels if required
|
||||
if ($verticalLegend) {
|
||||
$label = $this->truncateLabel($label, ($this->width * self::VERTICAL_LEGEND_MAX_WIDTH_PCT) - $legendTopLeftXValue - $paddedBulletWidth, $this->legendFontSize);
|
||||
$this->pData->setSerieDescription($metricCode, $label);
|
||||
}
|
||||
|
||||
$rectangleTopLeftXValue = ($verticalLegend ? $legendTopLeftXValue : $currentPosition) + $paddedBulletWidth - self::LEGEND_HORIZONTAL_SHADOW_PADDING;
|
||||
$rectangleTopLeftYValue = $verticalLegend ? $currentPosition : $legendTopMargin;
|
||||
|
||||
list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $this->legendFontSize);
|
||||
$legendItemWidth = $paddedBulletWidth + $labelWidth + $effectiveLegendItemHorizontalInterstice;
|
||||
$rectangleBottomRightXValue = $rectangleTopLeftXValue + $labelWidth + (self::LEGEND_HORIZONTAL_SHADOW_PADDING * 2);
|
||||
|
||||
$legendItemHeight = max($maxLogoHeight, $this->legendFontSize) + ($effectiveShadowPadding * 2);
|
||||
$rectangleBottomRightYValue = $rectangleTopLeftYValue + $legendItemHeight;
|
||||
|
||||
$this->pImage->drawFilledRectangle(
|
||||
$rectangleTopLeftXValue,
|
||||
$rectangleTopLeftYValue,
|
||||
$rectangleBottomRightXValue,
|
||||
$rectangleBottomRightYValue,
|
||||
array(
|
||||
'Alpha' => self::LEGEND_SHADOW_OPACITY,
|
||||
'R' => $color['R'],
|
||||
'G' => $color['G'],
|
||||
'B' => $color['B'],
|
||||
)
|
||||
);
|
||||
|
||||
if ($verticalLegend) {
|
||||
$currentPositionIncrement = max($maxLogoHeight, $effectiveLegendItemVerticalInterstice, $this->legendFontSize) + self::PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE;
|
||||
} else {
|
||||
$currentPositionIncrement = $legendItemWidth;
|
||||
}
|
||||
|
||||
$currentPosition += $currentPositionIncrement;
|
||||
}
|
||||
}
|
||||
|
||||
// draw legend
|
||||
$legendColor = $this->textColor;
|
||||
$this->pImage->drawLegend(
|
||||
$legendTopLeftXValue,
|
||||
$legendTopLeftYValue,
|
||||
array(
|
||||
'Style' => LEGEND_NOBORDER,
|
||||
'FontSize' => $this->legendFontSize,
|
||||
'BoxWidth' => $bulletWidth,
|
||||
'XSpacing' => $effectiveLegendItemHorizontalInterstice, // not effective when vertical
|
||||
'Mode' => $verticalLegend ? LEGEND_VERTICAL : LEGEND_HORIZONTAL,
|
||||
'BoxHeight' => $verticalLegend ? $effectiveLegendItemVerticalInterstice : null,
|
||||
'Family' => $bulletType,
|
||||
'FontR' => $legendColor['R'],
|
||||
'FontG' => $legendColor['G'],
|
||||
'FontB' => $legendColor['B'],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function getMaxLogoSize($logoPaths)
|
||||
{
|
||||
$maxLogoWidth = 0;
|
||||
$maxLogoHeight = 0;
|
||||
foreach ($logoPaths as $logoPath) {
|
||||
list($logoWidth, $logoHeight) = self::getLogoSize($logoPath);
|
||||
|
||||
if ($logoWidth > $maxLogoWidth) {
|
||||
$maxLogoWidth = $logoWidth;
|
||||
}
|
||||
if ($logoHeight > $maxLogoHeight) {
|
||||
$maxLogoHeight = $logoHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return array($maxLogoWidth, $maxLogoHeight);
|
||||
}
|
||||
|
||||
protected static function getLogoSize($logoPath)
|
||||
{
|
||||
$pathInfo = getimagesize($logoPath);
|
||||
return array($pathInfo[0], $pathInfo[1]);
|
||||
}
|
||||
|
||||
protected function getGridLeftMargin($horizontalGraph, $withLabel)
|
||||
{
|
||||
$gridLeftMargin = self::LEFT_GRID_MARGIN + self::OUTER_TICK_WIDTH;
|
||||
|
||||
if ($withLabel) {
|
||||
list($maxTextWidth, $maxTextHeight) = $this->getMaximumTextWidthHeight($horizontalGraph ? $this->abscissaSeries : $this->ordinateSeries);
|
||||
$gridLeftMargin += $maxTextWidth;
|
||||
}
|
||||
|
||||
return $gridLeftMargin;
|
||||
}
|
||||
|
||||
protected function getGridTopMargin($horizontalGraph, $verticalLegend)
|
||||
{
|
||||
list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
|
||||
|
||||
if ($horizontalGraph) {
|
||||
$topMargin = $ordinateMaxHeight + self::TOP_GRID_MARGIN_HORIZONTAL_GRAPH + self::OUTER_TICK_WIDTH;
|
||||
} else {
|
||||
$topMargin = $ordinateMaxHeight / 2;
|
||||
}
|
||||
|
||||
if ($this->showLegend && !$verticalLegend) {
|
||||
$topMargin += $this->getHorizontalLegendHeight();
|
||||
}
|
||||
|
||||
return $topMargin;
|
||||
}
|
||||
|
||||
private function getHorizontalLegendHeight()
|
||||
{
|
||||
list($maxMetricLegendWidth, $maxMetricLegendHeight) =
|
||||
$this->getMaximumTextWidthHeight(array_values($this->ordinateLabels), $this->legendFontSize);
|
||||
|
||||
return $maxMetricLegendHeight + self::HORIZONTAL_LEGEND_BOTTOM_MARGIN + self::HORIZONTAL_LEGEND_TOP_MARGIN;
|
||||
}
|
||||
|
||||
protected function getGraphHeight($horizontalGraph, $verticalLegend)
|
||||
{
|
||||
return $this->getGraphBottom($horizontalGraph) - $this->getGridTopMargin($horizontalGraph, $verticalLegend);
|
||||
}
|
||||
|
||||
private function getGridBottomMargin($horizontalGraph)
|
||||
{
|
||||
$gridBottomMargin = self::BOTTOM_GRID_MARGIN;
|
||||
if (!$horizontalGraph) {
|
||||
list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
|
||||
$gridBottomMargin += $abscissaMaxHeight;
|
||||
}
|
||||
return $gridBottomMargin;
|
||||
}
|
||||
|
||||
protected function getGridRightMargin($horizontalGraph)
|
||||
{
|
||||
if ($horizontalGraph) {
|
||||
// in horizontal graphs, metric values are displayed on the far right of the bar
|
||||
list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
|
||||
return self::RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH + $ordinateMaxWidth;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getGraphBottom($horizontalGraph)
|
||||
{
|
||||
return $this->height - $this->getGridBottomMargin($horizontalGraph);
|
||||
}
|
||||
|
||||
protected function truncateLabel($label, $labelWidthLimit, $fontSize = false)
|
||||
{
|
||||
list($truncationTextWidth, $truncationTextHeight) = $this->getTextWidthHeight(self::TRUNCATION_TEXT, $fontSize);
|
||||
list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $fontSize);
|
||||
|
||||
if ($labelWidth > $labelWidthLimit) {
|
||||
$averageCharWidth = $labelWidth / strlen($label);
|
||||
$charsToKeep = floor(($labelWidthLimit - $truncationTextWidth) / $averageCharWidth);
|
||||
$label = substr($label, 0, $charsToKeep) . self::TRUNCATION_TEXT;
|
||||
}
|
||||
return $label;
|
||||
}
|
||||
// display min & max values
|
||||
// can not currently be used because pChart's label design is not flexible enough
|
||||
// e.g: it is not possible to remove the box border & the square icon
|
||||
// it would require modifying pChart code base which we try to avoid
|
||||
// see https://github.com/piwik/piwik/issues/3396
|
||||
// protected function displayMinMaxValues()
|
||||
// {
|
||||
// if ($displayMinMax)
|
||||
// {
|
||||
// // when plotting multiple metrics, display min & max on both series
|
||||
// // to fix: in vertical bars, labels are hidden when multiple metrics are plotted, hence the restriction on count($this->ordinateSeries) == 1
|
||||
// if ($this->multipleMetrics && count($this->ordinateSeries) == 1)
|
||||
// {
|
||||
// $colorIndex = 1;
|
||||
// foreach($this->ordinateSeries as $column => $data)
|
||||
// {
|
||||
// $color = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];
|
||||
//
|
||||
// $this->pImage->writeLabel(
|
||||
// $column,
|
||||
// self::locateMinMaxValue($data),
|
||||
// $Format = array(
|
||||
// 'NoTitle' => true,
|
||||
// 'DrawPoint' => false,
|
||||
// 'DrawSerieColor' => true,
|
||||
// 'TitleMode' => LABEL_TITLE_NOBACKGROUND,
|
||||
// 'GradientStartR' => $color['R'],
|
||||
// 'GradientStartG' => $color['G'],
|
||||
// 'GradientStartB' => $color['B'],
|
||||
// 'GradientEndR' => 255,
|
||||
// 'GradientEndG' => 255,
|
||||
// 'GradientEndB' => 255,
|
||||
// 'BoxWidth' => 0,
|
||||
// 'VerticalMargin' => 9,
|
||||
// 'HorizontalMargin' => 7,
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // display only one min & max label
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// protected static function locateMinMaxValue($data)
|
||||
// {
|
||||
// $firstValue = $data[0];
|
||||
// $minValue = $firstValue;
|
||||
// $minValueIndex = 0;
|
||||
// $maxValue = $firstValue;
|
||||
// $maxValueIndex = 0;
|
||||
// foreach($data as $index => $value)
|
||||
// {
|
||||
// if ($value > $maxValue)
|
||||
// {
|
||||
// $maxValue = $value;
|
||||
// $maxValueIndex = $index;
|
||||
// }
|
||||
//
|
||||
// if ($value < $minValue)
|
||||
// {
|
||||
// $minValue = $value;
|
||||
// $minValueIndex = $index;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return array($minValueIndex, $maxValueIndex);
|
||||
// }
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
use Piwik\Piwik;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class HorizontalBar extends GridGraph
|
||||
{
|
||||
const INTERLEAVE = 0.30;
|
||||
const PADDING_CHARS = ' ';
|
||||
const LEGEND_SQUARE_WIDTH = 11;
|
||||
const MIN_SPACE_BETWEEN_HORIZONTAL_VALUES = 5;
|
||||
const LOGO_MIN_RIGHT_MARGIN = 3;
|
||||
|
||||
public function renderGraph()
|
||||
{
|
||||
$verticalLegend = false;
|
||||
|
||||
// create resized copies of logo to match maximum width / height
|
||||
foreach ($this->abscissaLogos as &$logoPath) {
|
||||
$logoPath = $this->createResizedImageCopyIfNeeded($logoPath);
|
||||
list($logoWidth, $logoHeight) = self::getLogoSize($logoPath);
|
||||
$logoPathToHeight[$logoPath] = $logoHeight;
|
||||
}
|
||||
|
||||
// determine the maximum logo width & height
|
||||
list($maxLogoWidth, $maxLogoHeight) = self::getMaxLogoSize($this->abscissaLogos);
|
||||
|
||||
// truncate report
|
||||
$graphHeight = $this->getGraphBottom($horizontalGraph = true) - $this->getGridTopMargin($horizontalGraph = true, $verticalLegend);
|
||||
|
||||
list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
|
||||
list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
|
||||
|
||||
$numberOfSeries = count($this->ordinateSeries);
|
||||
$ordinateMaxHeight = $ordinateMaxHeight * $numberOfSeries;
|
||||
|
||||
$textMaxHeight = $abscissaMaxHeight > $ordinateMaxHeight ? $abscissaMaxHeight : $ordinateMaxHeight;
|
||||
|
||||
$minLineWidth = ($textMaxHeight > $maxLogoHeight ? $textMaxHeight : $maxLogoHeight) + (self::MIN_SPACE_BETWEEN_HORIZONTAL_VALUES * $numberOfSeries);
|
||||
$maxNumOfValues = floor($graphHeight / $minLineWidth);
|
||||
$abscissaSeriesCount = count($this->abscissaSeries);
|
||||
|
||||
if ($maxNumOfValues < $abscissaSeriesCount - 1) {
|
||||
$sumOfOthers = array();
|
||||
$truncatedOrdinateSeries = array();
|
||||
$truncatedAbscissaLogos = array();
|
||||
$truncatedAbscissaSeries = array();
|
||||
foreach ($this->ordinateSeries as $column => $data) {
|
||||
$truncatedOrdinateSeries[$column] = array();
|
||||
$sumOfOthers[$column] = 0;
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
for (; $i < $maxNumOfValues; $i++) {
|
||||
foreach ($this->ordinateSeries as $column => $data) {
|
||||
$truncatedOrdinateSeries[$column][] = $data[$i];
|
||||
}
|
||||
|
||||
$truncatedAbscissaLogos[] = isset($this->abscissaLogos[$i]) ? $this->abscissaLogos[$i] : null;
|
||||
$truncatedAbscissaSeries[] = $this->abscissaSeries[$i];
|
||||
}
|
||||
|
||||
for (; $i < $abscissaSeriesCount; $i++) {
|
||||
foreach ($this->ordinateSeries as $column => $data) {
|
||||
$sumOfOthers[$column] += $data[$i];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->ordinateSeries as $column => $data) {
|
||||
$truncatedOrdinateSeries[$column][] = $sumOfOthers[$column];
|
||||
}
|
||||
|
||||
$truncatedAbscissaSeries[] = Piwik::translate('General_Others');
|
||||
$this->abscissaSeries = $truncatedAbscissaSeries;
|
||||
$this->ordinateSeries = $truncatedOrdinateSeries;
|
||||
$this->abscissaLogos = $truncatedAbscissaLogos;
|
||||
}
|
||||
|
||||
// blank characters are used to pad labels so the logo can be displayed
|
||||
$paddingText = '';
|
||||
$paddingWidth = 0;
|
||||
if ($maxLogoWidth > 0) {
|
||||
while ($paddingWidth < $maxLogoWidth + self::LOGO_MIN_RIGHT_MARGIN) {
|
||||
$paddingText .= self::PADDING_CHARS;
|
||||
list($paddingWidth, $paddingHeight) = $this->getTextWidthHeight($paddingText);
|
||||
}
|
||||
}
|
||||
|
||||
// determine the maximum label width according to the minimum comfortable graph size
|
||||
$gridRightMargin = $this->getGridRightMargin($horizontalGraph = true);
|
||||
$minGraphSize = ($this->width - $gridRightMargin) / 2;
|
||||
|
||||
$metricLegendWidth = 0;
|
||||
foreach ($this->ordinateLabels as $column => $label) {
|
||||
list($textWidth, $textHeight) = $this->getTextWidthHeight($label);
|
||||
$metricLegendWidth += $textWidth;
|
||||
}
|
||||
|
||||
$legendWidth = $metricLegendWidth + ((self::HORIZONTAL_LEGEND_LEFT_MARGIN + self::LEGEND_SQUARE_WIDTH) * $numberOfSeries);
|
||||
if ($this->showLegend) {
|
||||
if ($legendWidth > $minGraphSize) {
|
||||
$minGraphSize = $legendWidth;
|
||||
}
|
||||
}
|
||||
|
||||
$gridLeftMarginWithoutLabels = $this->getGridLeftMargin($horizontalGraph = true, $withLabel = false);
|
||||
$labelWidthLimit =
|
||||
$this->width
|
||||
- $gridLeftMarginWithoutLabels
|
||||
- $gridRightMargin
|
||||
- $paddingWidth
|
||||
- $minGraphSize;
|
||||
|
||||
// truncate labels if needed
|
||||
foreach ($this->abscissaSeries as &$label) {
|
||||
$label = $this->truncateLabel($label, $labelWidthLimit);
|
||||
}
|
||||
|
||||
$gridLeftMarginBeforePadding = $this->getGridLeftMargin($horizontalGraph = true, $withLabel = true);
|
||||
|
||||
// pad labels for logo space
|
||||
foreach ($this->abscissaSeries as &$label) {
|
||||
$label .= $paddingText;
|
||||
}
|
||||
|
||||
$this->initGridChart(
|
||||
$displayVerticalGridLines = false,
|
||||
$bulletType = LEGEND_FAMILY_BOX,
|
||||
$horizontalGraph = true,
|
||||
$showTicks = false,
|
||||
$verticalLegend
|
||||
);
|
||||
|
||||
$valueColor = $this->textColor;
|
||||
$this->pImage->drawBarChart(
|
||||
array(
|
||||
'DisplayValues' => true,
|
||||
'Interleave' => self::INTERLEAVE,
|
||||
'DisplayR' => $valueColor['R'],
|
||||
'DisplayG' => $valueColor['G'],
|
||||
'DisplayB' => $valueColor['B'],
|
||||
)
|
||||
);
|
||||
|
||||
// // display icons
|
||||
$graphData = $this->pData->getData();
|
||||
$numberOfRows = count($this->abscissaSeries);
|
||||
$logoInterleave = $this->getGraphHeight(true, $verticalLegend) / $numberOfRows;
|
||||
for ($i = 0; $i < $numberOfRows; $i++) {
|
||||
if (isset($this->abscissaLogos[$i])) {
|
||||
$currentLogoPath = $this->abscissaLogos[$i];
|
||||
|
||||
if (isset($logoPathToHeight[$currentLogoPath])) {
|
||||
$logoHeight = $logoPathToHeight[$currentLogoPath];
|
||||
|
||||
$pathInfo = pathinfo($currentLogoPath);
|
||||
$logoExtension = strtoupper($pathInfo['extension']);
|
||||
$drawingFunction = 'drawFrom' . $logoExtension;
|
||||
|
||||
$logoYPosition =
|
||||
($logoInterleave * $i)
|
||||
+ $this->getGridTopMargin(true, $verticalLegend)
|
||||
+ $graphData['Axis'][1]['Margin']
|
||||
- $logoHeight / 2
|
||||
+ 1;
|
||||
|
||||
if (method_exists($this->pImage, $drawingFunction)) {
|
||||
$this->pImage->$drawingFunction(
|
||||
$gridLeftMarginBeforePadding,
|
||||
$logoYPosition,
|
||||
$currentLogoPath
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph/Pie.php
Normal file
26
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph/Pie.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Pie extends PieGraph
|
||||
{
|
||||
public function renderGraph()
|
||||
{
|
||||
$this->initPieGraph(false);
|
||||
|
||||
$this->pieChart->draw2DPie(
|
||||
$this->xPosition,
|
||||
$this->yPosition,
|
||||
$this->pieConfig
|
||||
);
|
||||
}
|
||||
}
|
27
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph/Pie3D.php
Normal file
27
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph/Pie3D.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Pie3D extends PieGraph
|
||||
{
|
||||
public function renderGraph()
|
||||
{
|
||||
$this->initPieGraph(true);
|
||||
|
||||
$this->pieChart->draw3DPie(
|
||||
$this->xPosition,
|
||||
$this->yPosition,
|
||||
$this->pieConfig
|
||||
);
|
||||
}
|
||||
}
|
126
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph/PieGraph.php
Normal file
126
msd2/tracking/piwik/plugins/ImageGraph/StaticGraph/PieGraph.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
use CpChart\Chart\Pie;
|
||||
use Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
abstract class PieGraph extends StaticGraph
|
||||
{
|
||||
const RADIUS_MARGIN = 40;
|
||||
const PIE_RIGHT_MARGIN = 20;
|
||||
const SECTOR_GAP = 2.5;
|
||||
|
||||
const SLICE_COLOR_KEY = "SLICE_COLOR";
|
||||
|
||||
/**
|
||||
* @var Pie
|
||||
*/
|
||||
protected $pieChart;
|
||||
protected $xPosition;
|
||||
protected $yPosition;
|
||||
protected $pieConfig;
|
||||
|
||||
protected function getDefaultColors()
|
||||
{
|
||||
return array(
|
||||
self::SLICE_COLOR_KEY . '1' => '3C5A69',
|
||||
self::SLICE_COLOR_KEY . '2' => '679BB5',
|
||||
self::SLICE_COLOR_KEY . '3' => '695A3C',
|
||||
self::SLICE_COLOR_KEY . '4' => 'B58E67',
|
||||
self::SLICE_COLOR_KEY . '5' => '8AA68A',
|
||||
self::SLICE_COLOR_KEY . '6' => 'A4D2A6',
|
||||
);
|
||||
}
|
||||
|
||||
protected function initPieGraph($showLegend)
|
||||
{
|
||||
$this->truncateSmallValues();
|
||||
$this->initpData();
|
||||
$this->initpImage();
|
||||
|
||||
if ($this->height > $this->width) {
|
||||
$radius = ($this->width / 2) - self::RADIUS_MARGIN;
|
||||
} else {
|
||||
$radius = ($this->height / 2) - self::RADIUS_MARGIN;
|
||||
}
|
||||
|
||||
$this->pieChart = new Pie($this->pImage, $this->pData);
|
||||
|
||||
$numberOfSlices = count($this->abscissaSeries);
|
||||
$numberOfAvailableColors = count($this->colors);
|
||||
for ($i = 0; $i < $numberOfSlices; $i++) {
|
||||
$this->pieChart->setSliceColor($i, $this->colors[self::SLICE_COLOR_KEY . (($i % $numberOfAvailableColors) + 1)]);
|
||||
}
|
||||
|
||||
// max abscissa label width is used to set the pie right margin
|
||||
list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
|
||||
|
||||
$this->xPosition = $this->width - $radius - $abscissaMaxWidth - self::PIE_RIGHT_MARGIN;
|
||||
$this->yPosition = $this->height / 2;
|
||||
|
||||
if ($showLegend) {
|
||||
$this->pieChart->drawPieLegend(15, 40, array("Alpha" => 20));
|
||||
}
|
||||
|
||||
$this->pieConfig =
|
||||
array(
|
||||
'Radius' => $radius,
|
||||
'DrawLabels' => true,
|
||||
'DataGapAngle' => self::SECTOR_GAP,
|
||||
'DataGapRadius' => self::SECTOR_GAP,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* this method logic is close to Piwik's core filter_truncate.
|
||||
* it uses a threshold to determine if an abscissa value should be drawn on the PIE
|
||||
* discarded abscissa values are summed in the 'other' abscissa value
|
||||
*
|
||||
* if this process is not perform, CpChart will draw pie slices that are too small to see
|
||||
*/
|
||||
private function truncateSmallValues()
|
||||
{
|
||||
$metricColumns = array_keys($this->ordinateSeries);
|
||||
$metricColumn = $metricColumns[0];
|
||||
|
||||
$ordinateValuesSum = 0;
|
||||
foreach ($this->ordinateSeries[$metricColumn] as $ordinateValue) {
|
||||
$ordinateValuesSum += $ordinateValue;
|
||||
}
|
||||
|
||||
$truncatedOrdinateSeries[$metricColumn] = array();
|
||||
$truncatedAbscissaSeries = array();
|
||||
$smallValuesSum = 0;
|
||||
|
||||
$ordinateValuesCount = count($this->ordinateSeries[$metricColumn]);
|
||||
for ($i = 0; $i < $ordinateValuesCount - 1; $i++) {
|
||||
$ordinateValue = $this->ordinateSeries[$metricColumn][$i];
|
||||
if ($ordinateValue / $ordinateValuesSum > 0.01) {
|
||||
$truncatedOrdinateSeries[$metricColumn][] = $ordinateValue;
|
||||
$truncatedAbscissaSeries[] = $this->abscissaSeries[$i];
|
||||
} else {
|
||||
$smallValuesSum += $ordinateValue;
|
||||
}
|
||||
}
|
||||
|
||||
$smallValuesSum += $this->ordinateSeries[$metricColumn][$ordinateValuesCount - 1];
|
||||
if (($smallValuesSum / $ordinateValuesSum) > 0.01) {
|
||||
$truncatedOrdinateSeries[$metricColumn][] = $smallValuesSum;
|
||||
$truncatedAbscissaSeries[] = end($this->abscissaSeries);
|
||||
}
|
||||
|
||||
$this->ordinateSeries = $truncatedOrdinateSeries;
|
||||
$this->abscissaSeries = $truncatedAbscissaSeries;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\ImageGraph\StaticGraph;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class VerticalBar extends GridGraph
|
||||
{
|
||||
const INTERLEAVE = 0.10;
|
||||
|
||||
public function renderGraph()
|
||||
{
|
||||
$this->initGridChart(
|
||||
$displayVerticalGridLines = false,
|
||||
$bulletType = LEGEND_FAMILY_BOX,
|
||||
$horizontalGraph = false,
|
||||
$showTicks = true,
|
||||
$verticalLegend = false
|
||||
);
|
||||
|
||||
$this->pImage->drawBarChart(
|
||||
array(
|
||||
'Interleave' => self::INTERLEAVE,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
BIN
msd2/tracking/piwik/plugins/ImageGraph/fonts/tahoma.ttf
Normal file
BIN
msd2/tracking/piwik/plugins/ImageGraph/fonts/tahoma.ttf
Normal file
Binary file not shown.
6
msd2/tracking/piwik/plugins/ImageGraph/lang/ar.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/ar.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "لم نعثر على العمود '%1$s' في هذا التقرير. جرّب أياً من %2$s",
|
||||
"PluginDescription": "أنشئ رسوم PNG ثابتة جميلة لأي من تقارير البيانات لديك."
|
||||
}
|
||||
}
|
5
msd2/tracking/piwik/plugins/ImageGraph/lang/bg.json
Normal file
5
msd2/tracking/piwik/plugins/ImageGraph/lang/bg.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Колоната '%1$s' не е намерена в този доклад. Опитайте с %2$s"
|
||||
}
|
||||
}
|
5
msd2/tracking/piwik/plugins/ImageGraph/lang/ca.json
Normal file
5
msd2/tracking/piwik/plugins/ImageGraph/lang/ca.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "La columan '%1$s' no s'ha trobat en aquest informe. Proveu algun dels següents: %2$s"
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/cs.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/cs.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Sloupec %1$s nebyl v tomto hlášení nalezen. Zkuste některé z %2$s",
|
||||
"PluginDescription": "Generujte pěkné statické PNG grafy z jakéhokoliv hlášení."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/da.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/da.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Kolonnen '%1$s' blev ikke fundet i denne rapport. Prøv en af %2$s",
|
||||
"PluginDescription": "Generer flotte statiske PNG Graph billeder til alle datarapporter."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/de.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/de.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Die Spalte '%1$s' wurde in diesem Bericht nicht gefunden. Probieren Sie es mit einer hiervon: %2$s",
|
||||
"PluginDescription": "Generieren Sie schöne statische PNG Graph Bilder für Ihre Datenberichte."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/el.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/el.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Η στήλη '%1$s' δεν βρέθηκε στην αναφορά. Δοκιμάστε οποιαδήποτε από τις %2$s",
|
||||
"PluginDescription": "Δημιουργήστε όμορφες στατικές εικόνες γραφικών PNG για κάθε μία από τις αναφορές σας."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/en.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/en.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "The column '%1$s' was not found in this report. Try any of %2$s",
|
||||
"PluginDescription": "Generate beautiful static PNG Graph images for any of your data report."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/es-ar.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/es-ar.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "No se encontró la columna \"%1$s\" en este informe. Intentá cualquiera de %2$s.",
|
||||
"PluginDescription": "Generá bellas imágenes estáticas de gráficos PNG de cualquiera de tus informes de datos."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/es.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/es.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "La columna '%1$s' no se encontró en este informe. Inténtalo con alguno de %2$s",
|
||||
"PluginDescription": "Genera bellas imágenes estáticas de gráficos PNG para cualquiera de sus informes de datos."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/fa.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/fa.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "ستون '%1$s' در این گزارش پیدا نشد. تلاش کنید برای %2$s",
|
||||
"PluginDescription": "تصاویر نمودار PNG استاتیک زیبا برای هر کدام از گزارش دادههای خود ایجاد کنید."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/fi.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/fi.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Kolumnia '%1$s' ei löydetty tässä raportissa. Kokeile mitä tahansa %2$s",
|
||||
"PluginDescription": "Luo kauniita staattisia PNG-kuvaajia mistä tahansa raportista."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/fr.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/fr.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "La colonne '%1$s' n'a pas été trouvée dans ce rapport. Essayez parmi %2$s",
|
||||
"PluginDescription": "Générez de magnifiques images statiques PNG de graphiques de vos données de n'importe quelle donnée de votre rapport."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/hi.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/hi.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "स्तंभ '%1$s' इस रिपोर्ट में नहीं मिला था. किसी %2$sका प्रयास करें",
|
||||
"PluginDescription": "अपने डेटा रिपोर्ट से किसी के लिए सुंदर स्थिर PNG ग्राफ छवियों को उत्पन्न।"
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/id.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/id.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Kolom '%1$s' tidak ditemukan dalam laporan ini. Coba salah satu dari %2$s",
|
||||
"PluginDescription": "Buat gambar Grafik PNG statis cantik untuk semua dari laporan data Anda"
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/it.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/it.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "La colonna '%1$s' non è stato trovata in questo report. Prova una di queste %2$s",
|
||||
"PluginDescription": "Genera delle belle immagini statiche PNG di grafici per ognuno dei tuoi report dati."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/ja.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/ja.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "列 '%1$s' は、このレポートで見つかりませんでした。%2$s のいずれかをお試しください。",
|
||||
"PluginDescription": "データ報告のいずれかのための美しい静的なPNGグラフの画像を生成します。"
|
||||
}
|
||||
}
|
5
msd2/tracking/piwik/plugins/ImageGraph/lang/ko.json
Normal file
5
msd2/tracking/piwik/plugins/ImageGraph/lang/ko.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"PluginDescription": "Matomo 보고서의 아름다운 정적 PNG 그래프 이미지를 생성합니다."
|
||||
}
|
||||
}
|
5
msd2/tracking/piwik/plugins/ImageGraph/lang/lv.json
Normal file
5
msd2/tracking/piwik/plugins/ImageGraph/lang/lv.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"PluginDescription": "Ģenerējiet skaistus, statiskus PNG formāta grafikus jebkurai Matomo atskaitei."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/nb.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/nb.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Kolonnen «%1$s» ble ikke funnet i denne rapporten. Prøv %2$s",
|
||||
"PluginDescription": "Generer statiske PNG-grafebilder for alle dine datarapporter."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/nl.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/nl.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "De kolom '%1$s' is niet gevonden in dit rapport. Probeer één van %2$s",
|
||||
"PluginDescription": "Genereer mooie statische PNG Grafiek afbeeldingen voor elk van uw data rapporten."
|
||||
}
|
||||
}
|
5
msd2/tracking/piwik/plugins/ImageGraph/lang/nn.json
Normal file
5
msd2/tracking/piwik/plugins/ImageGraph/lang/nn.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"PluginDescription": "Opprett vakre statiske grafar som PNG-bilete for ein Matomo-rapport."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/pl.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/pl.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Kolumna '%1$s' nie została odnaleziona w tym raporcie. Spróbuj jednej z %2$s",
|
||||
"PluginDescription": "Generuj piękne statyczne Wykresy Obrazkowe w formacie PNG dla dowolnych danych raportu."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/pt-br.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/pt-br.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "A coluna '%1$s' não foi encontrada neste relatório. Tente qualquer %2$s",
|
||||
"PluginDescription": "Gera belas imagens estáticas PNG Grapf para os seus relatórios de dados."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/pt.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/pt.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "A coluna '%1$s' não foi encontrada neste relatório. Experimente qualquer uma de %2$s",
|
||||
"PluginDescription": "Gerar bonitas imagens PNG estáticas de gráficos para qualquer um dos seus relatórios de dados."
|
||||
}
|
||||
}
|
5
msd2/tracking/piwik/plugins/ImageGraph/lang/ro.json
Normal file
5
msd2/tracking/piwik/plugins/ImageGraph/lang/ro.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Coloana '%1$s' nu a fost gasita in acest raport. Incearca oricare dintre %2$s"
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/ru.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/ru.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Колонка ’%1$s’ не была найдена в этом отчете. Попробуйте что-нибудь из %2$s",
|
||||
"PluginDescription": "Генерируйте красивые статические изображения графиков PNG для любого вашего информационного отчета."
|
||||
}
|
||||
}
|
5
msd2/tracking/piwik/plugins/ImageGraph/lang/sl.json
Normal file
5
msd2/tracking/piwik/plugins/ImageGraph/lang/sl.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"PluginDescription": "Ustvarite prelepe, statične, PNG grafe za vsa Matomo poročila."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/sq.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/sq.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Në këtë raport s’u gjet shtylla '%1$s'. Provoni cilëndo nga %2$s",
|
||||
"PluginDescription": "Prodhoni figura të hijshme Grafikësh statikë PNG për çfarëdo të dhënash të raportit tuaj."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/sr.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/sr.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Kolona '%1$s' nije nađena u ovom izveštaju. Pokušajte bilo koju od %2$s",
|
||||
"PluginDescription": "Kreirajte predivne statične PNG grafikone za bilo koji od vaših izveštaja."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/sv.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/sv.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Kolumn '%1$s' var inte hittad i den här rapporten. Pröva någon av %2$s",
|
||||
"PluginDescription": "Generera fina statiska PNG-grafer för dina data rapporter."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/tr.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/tr.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "'%1$s' sütunu bu raporda bulunamadı Herhangi bir %2$s deneyin",
|
||||
"PluginDescription": "Veri raporlarınız için güzel PNG çizelgeleri oluşturur."
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/uk.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/uk.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Колонка ’%1$s’ була знайдена в цьому звіті. Спробуйте що-небудь з %2$s",
|
||||
"PluginDescription": "Генеруйте красиві статичні зображення графіків PNG для будь-якого вашого інформаційного звіту."
|
||||
}
|
||||
}
|
5
msd2/tracking/piwik/plugins/ImageGraph/lang/vi.json
Normal file
5
msd2/tracking/piwik/plugins/ImageGraph/lang/vi.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "Cột này '%1$s' không thấy trong báo cáo này. Thử bất kỳ của %2$s"
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/zh-cn.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/zh-cn.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "报表中没有 '%1$s' 栏,请试试 %2$s",
|
||||
"PluginDescription": "在你的数据报告中生成精美的静态PNG的图。"
|
||||
}
|
||||
}
|
6
msd2/tracking/piwik/plugins/ImageGraph/lang/zh-tw.json
Normal file
6
msd2/tracking/piwik/plugins/ImageGraph/lang/zh-tw.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"ImageGraph": {
|
||||
"ColumnOrdinateMissing": "在這份報表中找不到欄位「%1$s」。嘗試下列任一:%2$s",
|
||||
"PluginDescription": "從你任何資料報表產生優美的靜態 PNG 圖像檔案。"
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{% for plot in titleAndUrls %}
|
||||
<h2>{{ plot.0 }}</h2>
|
||||
<a href='{{ plot.1 }}'>
|
||||
<img border=0 src="{{ plot.1 }}">
|
||||
</a>
|
||||
{% endfor %}
|
@ -0,0 +1,55 @@
|
||||
{% extends 'dashboard.twig' %}
|
||||
|
||||
{% block topcontrols %}
|
||||
{% include '@CoreHome/_periodSelect.twig' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% set showSitesSelection=true %}
|
||||
|
||||
<div>
|
||||
<h2>{{ 'ImageGraph_ImageGraph'|translate }} ::: {{ siteName }}</h2>
|
||||
|
||||
<div class="entityContainer" style="width:100%;">
|
||||
<div class="entityAddContainer">
|
||||
<table class="dataTable entityTable">
|
||||
<thead>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="first">
|
||||
<th style="white-space:normal;">Category</th>
|
||||
<th style="white-space:normal;">Name</th>
|
||||
|
||||
{% for type in graphTypes %}
|
||||
<th style="white-space:normal;">{{ type }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for report in availableReports %}
|
||||
{% if report.imageGraphUrl is defined %}
|
||||
<tr>
|
||||
<td>{{ report.category }}</td>
|
||||
<td>{{ report.name }}</td>
|
||||
{% for type in graphTypes %}
|
||||
<td>
|
||||
<h2>Graph {{ type }} for all supported sizes</h2>
|
||||
{% for sizes in graphSizes %}
|
||||
<p>{{ sizes.0 }}
|
||||
x {{ sizes.1 }} {% if sizes.2 is defined %} (scaled down to {{ sizes.3 }} x {{ sizes.4 }}){% endif %}</p>
|
||||
<img src="{{ report.imageGraphUrl }}&graphType={{ type }}&width={{ sizes.0 }}&height={{ sizes.1 }}{% if sizes.2 is
|
||||
defined %}&fontSize={{ sizes.2 }}{% endif %}"
|
||||
{% if sizes.3 is defined %}width={{ sizes.3 }}{% endif %}
|
||||
{% if sizes.4 is defined %}height={{ sizes.4 }}{% endif %}
|
||||
/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a id="bottom"></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user