Skip to content
Extraits de code Groupes Projets
API.php 12,8 ko
Newer Older
  • Learn to ignore specific revisions
  • mattpiwik's avatar
    mattpiwik a validé
    <?php
    /**
     * Piwik - Open source web analytics
     * 
     * @link http://piwik.org
     * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
    
    mattpiwik's avatar
    mattpiwik a validé
     * @version $Id$
    
    mattpiwik's avatar
    mattpiwik a validé
     * 
     * @category Piwik_Plugins
     * @package Piwik_ImageGraph
     */
    
    /**
    
     * The ImageGraph.get API call lets you generate beautiful static PNG Graphs for any existing Piwik report.
     * Supported graph types are: line plot, 2D/3D pie chart and vertical bar chart.
    
    mattpiwik's avatar
    mattpiwik a validé
     * 
    
    mattpiwik's avatar
    mattpiwik a validé
     * 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/>
    
    mattpiwik's avatar
    mattpiwik a validé
     * - $colors accepts a comma delimited list of colors that will overwrite the default Piwik colors <br/>
    
     * - you can also customize the width, height, font size, metric being plotted (in case the data contains multiple columns/metrics).
    
    mattpiwik's avatar
    mattpiwik a validé
     * 
    
     * See also <a href='http://piwik.org/docs/analytics-api/metadata/#toc-static-image-graphs'>How to embed static Image Graphs?</a> for more information.
     * 
    
    mattpiwik's avatar
    mattpiwik a validé
     * @package Piwik_ImageGraph
     */ 
    class Piwik_ImageGraph_API
    {
    
    	const FILENAME_KEY = 'filename';
    	const TRUNCATE_KEY = 'truncate';
    	const WIDTH_KEY = 'width';
    	const HEIGHT_KEY = 'height';
    
    	const MAX_WIDTH = 1024;
    	const MAX_HEIGHT = 1024; 
    
    
    	static private $DEFAULT_PARAMETERS = array(
    		Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_LINE => array(
    			self::FILENAME_KEY => 'BasicLine',
    			self::TRUNCATE_KEY => 6,
    			self::WIDTH_KEY => 1044,
    			self::HEIGHT_KEY => 290,
    		),
    		Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_VERTICAL_BAR => array(
    			self::FILENAME_KEY => 'BasicBar',
    			self::TRUNCATE_KEY => 6,
    			self::WIDTH_KEY => 1044,
    			self::HEIGHT_KEY => 290,
    		),
    		Piwik_ImageGraph_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,
    		),
    		Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_3D_PIE => array(
    			self::FILENAME_KEY => '3DPie',
    			self::TRUNCATE_KEY => 5,
    			self::WIDTH_KEY => 1044,
    			self::HEIGHT_KEY => 290,
    		),
    		Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_PIE => array(
    			self::FILENAME_KEY => 'BasicPie',
    			self::TRUNCATE_KEY => 5,
    			self::WIDTH_KEY => 1044,
    			self::HEIGHT_KEY => 290,
    		),
    	);
    
    	static private $DEFAULT_GRAPH_TYPE_OVERRIDE = array(
    		'UserSettings_getPlugin' => Piwik_ImageGraph_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;
    
    
    mattpiwik's avatar
    mattpiwik a validé
    	static private $instance = null;
    
    	/**
    	 * @return Piwik_ImageGraph_API
    	 */
    	static public function getInstance()
    	{
    		if (self::$instance == null)
    
    mattpiwik's avatar
    mattpiwik a validé
    			$c = __CLASS__;
    			self::$instance = new $c();
    		}
    		return self::$instance;
    	}
    	
    
    	public function get($idSite, $period, $date, $apiModule, $apiAction, $graphType = false,
    
    mattpiwik's avatar
    mattpiwik a validé
    						$outputType = Piwik_ImageGraph_API::GRAPH_OUTPUT_INLINE, $column = false, $showMetricTitle = true,
    						$width = false, $height = false, $fontSize = Piwik_ImageGraph_API::DEFAULT_FONT_SIZE, $aliasedGraph = true,
    
    						$idGoal = false, $colors = false)
    
    mattpiwik's avatar
    mattpiwik a validé
    	{
    
    JulienMoumne's avatar
    JulienMoumne a validé
    		Piwik::checkUserHasViewAccess($idSite);
    
    
    mattpiwik's avatar
    mattpiwik a validé
    		// Health check - should we also test for GD2 only?
    
    mattpiwik's avatar
    mattpiwik a validé
    		if(!Piwik::isGdExtensionEnabled())
    
    mattpiwik's avatar
    mattpiwik a validé
    		{
    
    			throw new Exception('Error: To create graphs in Piwik, please enable GD php extension (with Freetype support) in php.ini, and restart your web server.');
    
    mattpiwik's avatar
    mattpiwik a validé
    		}
    
    mattpiwik's avatar
    mattpiwik a validé
    		$useUnicodeFont = array(
    			'am', 'ar', 'el', 'fa' , 'fi', 'he', 'ja', 'ka', 'ko', 'te', 'th', 'zh-cn', 'zh-tw', 
    		);
    
    mattpiwik's avatar
    mattpiwik a validé
    		$languageLoaded = Piwik_Translate::getInstance()->getLanguageLoaded();
    
    mattpiwik's avatar
    mattpiwik a validé
    		$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;
    
    
    mattpiwik's avatar
    mattpiwik a validé
    		try
    		{
    
    			//Fetch the metadata for given api-action
    
    mattpiwik's avatar
    mattpiwik a validé
    			$metadata = Piwik_API_API::getInstance()->getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $languageLoaded, $period, $date);
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    				throw new Exception('Invalid API Module and/or API Action');
    
    mattpiwik's avatar
    mattpiwik a validé
    			}
    
    
    			$metadata = $metadata[0];
    			$reportHasDimension = !empty($metadata['dimension']);
    			$constantRowsCount = !empty($metadata['constantRowsCount']);
    
    
    JulienMoumne's avatar
    JulienMoumne a validé
    			$isMultiplePeriod = Piwik_Archive::isMultiplePeriod($date, $period);
    			if(($reportHasDimension && $isMultiplePeriod) || (!$reportHasDimension && !$isMultiplePeriod))
    			{
    				throw new Exception('The graph cannot be drawn for this combination of \'date\' and \'period\' parameters.');
    			}
    
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    mattpiwik's avatar
    mattpiwik a validé
    				{
    
    mattpiwik's avatar
    mattpiwik a validé
    					{
    
    						$graphType = Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_VERTICAL_BAR;
    
    mattpiwik's avatar
    mattpiwik a validé
    					}
    					else
    					{
    
    						$graphType = Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_HORIZONTAL_BAR;
    
    mattpiwik's avatar
    mattpiwik a validé
    					}
    				}
    
    				else
    				{
    					$graphType = Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_LINE;
    				}
    
    				$reportUniqueId = $metadata['uniqueId'];
    				if(isset(self::$DEFAULT_GRAPH_TYPE_OVERRIDE[$reportUniqueId]))
    				{
    					$graphType = self::$DEFAULT_GRAPH_TYPE_OVERRIDE[$reportUniqueId];
    				}
    
    mattpiwik's avatar
    mattpiwik a validé
    			}
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    				$availableGraphTypes = Piwik_ImageGraph_StaticGraph::getAvailableStaticGraphTypes();
    				if (!in_array($graphType, $availableGraphTypes))
    				{
    					throw new Exception(
    						Piwik_TranslateException(
    							'General_ExceptionInvalidStaticGraphType',
    							array($graphType, implode(', ', $availableGraphTypes))
    						)
    					);
    				}
    
    mattpiwik's avatar
    mattpiwik a validé
    			}
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    				$width = self::$DEFAULT_PARAMETERS[$graphType][self::WIDTH_KEY];
    
    mattpiwik's avatar
    mattpiwik a validé
    			}
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    				$height = self::$DEFAULT_PARAMETERS[$graphType][self::HEIGHT_KEY];
    
    mattpiwik's avatar
    mattpiwik a validé
    			}
    
    			// Cap width and height to a safe amount
    			$width = min($width, self::MAX_WIDTH);
    			$height = min($height, self::MAX_HEIGHT);
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    mattpiwik's avatar
    mattpiwik a validé
    			}
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    				// if it's a dimension-less report, the abscissa column can only be the date-index
    				$abscissaColumn = 'date';
    
    mattpiwik's avatar
    mattpiwik a validé
    			}
    
    			
    			$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()
    			);
    
    			$ordinateColumn = $column;
    			if(empty($ordinateColumn))
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    				$ordinateColumn = self::DEFAULT_ORDINATE_METRIC;
    
    				// if default ordinate metric not available for this report
    				if(empty($reportColumns[$ordinateColumn]))
    
    mattpiwik's avatar
    mattpiwik a validé
    				{
    
    					// take the first metric returned in the metadata
    					$ordinateColumn = key($metadata['metrics']);
    
    mattpiwik's avatar
    mattpiwik a validé
    				}
    			}
    			
    
    			// if we still don't have an ordinate column or the one provided by the API caller is invalid
    			if(empty($ordinateColumn) || empty($reportColumns[$ordinateColumn]))
    			{
    				throw new Exception(Piwik_Translate('ImageGraph_ColumnOrdinateMissing', $ordinateColumn));
    
    
    			$ordinateLabel = $reportColumns[$ordinateColumn];
    
    			// sort and truncate filters
    			$defaultFilterTruncate = self::$DEFAULT_PARAMETERS[$graphType][self::TRUNCATE_KEY];
    			switch($graphType)
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    				case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_3D_PIE:
    				case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_PIE:
    
    					$_GET['filter_sort_column'] = $ordinateColumn;
    					$this->setFilterTruncate($defaultFilterTruncate);
    					break;
    
    				case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_VERTICAL_BAR:
    				case Piwik_ImageGraph_StaticGraph::GRAPH_TYPE_BASIC_LINE:
    
    					if($reportHasDimension && !$constantRowsCount)
    					{
    						$this->setFilterTruncate($defaultFilterTruncate);
    					}
    					break;
    			}
    
    			$processedReport = Piwik_API_API::getInstance()->getProcessedReport(
    				$idSite,
    				$period,
    				$date,
    				$apiModule,
    
    mattpiwik's avatar
    mattpiwik a validé
    				$apiAction,
    				$segment = false,
    				$apiParameters = false, 
    
    mattpiwik's avatar
    mattpiwik a validé
    				$languageLoaded
    
    			);
    			// prepare abscissa and ordinate series
    			$abscissaSerie = array();
    			$ordinateSerie = array();
    			$ordinateLogos = array();
    			$reportData = $processedReport['reportData'];
    
    JulienMoumne's avatar
    JulienMoumne a validé
    			$hasData = false;
    
    JulienMoumne's avatar
    JulienMoumne a validé
    			$hasNonZeroValue = false;
    
    
    			if($reportHasDimension)
    			{
    				$reportMetadata = $processedReport['reportMetadata']->getRows();
    
    				$i = 0;
    
    JulienMoumne's avatar
    JulienMoumne a validé
    				// $reportData instanceof Piwik_DataTable
    
    				foreach($reportData->getRows() as $row) // Piwik_DataTable_Row[]
    
    mattpiwik's avatar
    mattpiwik a validé
    				{
    
    					// $row instanceof Piwik_DataTable_Row
    					$rowData = $row->getColumns(); // Associative Array
    					$abscissaSerie[] = Piwik_Common::unsanitizeInputValue($rowData[$abscissaColumn]);
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					$parsedOrdinateValue = $this->parseOrdinateValue($rowData[$ordinateColumn]);
    
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					$hasData = true;
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					if($parsedOrdinateValue != 0)
    					{
    						$hasNonZeroValue = true;
    					}
    
    					$ordinateSerie[] = $parsedOrdinateValue;
    
    
    mattpiwik's avatar
    mattpiwik a validé
    					{
    
    						$rowMetadata = $reportMetadata[$i]->getColumns();
    						if(isset($rowMetadata['logo']))
    
    mattpiwik's avatar
    mattpiwik a validé
    						{
    
    							$ordinateLogos[$i] = $rowMetadata['logo'];
    
    mattpiwik's avatar
    mattpiwik a validé
    						}
    					}
    
    mattpiwik's avatar
    mattpiwik a validé
    				}
    			}
    
    JulienMoumne's avatar
    JulienMoumne a validé
    			else // if the report has no dimension we have multiple reports each with only one row within the reportData
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    JulienMoumne's avatar
    JulienMoumne a validé
    				// $reportData instanceof Piwik_DataTable_Array
    				$periodsMetadata = array_values($reportData->metadata);
    
    				// $periodsData instanceof Piwik_DataTable_Simple[]
    				$periodsData = array_values($reportData->getArray());
    				$periodsCount = count($periodsMetadata);
    
    				for ($i = 0 ; $i < $periodsCount ; $i++)
    
    mattpiwik's avatar
    mattpiwik a validé
    				{
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					// $periodsData[$i] instanceof Piwik_DataTable_Simple
    					// $rows instanceof Piwik_DataTable_Row[]
    					$rows = $periodsData[$i]->getRows();
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					if(array_key_exists(0, $rows))
    					{
    						$rowData = $rows[0]->getColumns(); // associative Array
    						$ordinateValue = $rowData[$ordinateColumn];
    
    JulienMoumne's avatar
    JulienMoumne a validé
    						$parsedOrdinateValue = $this->parseOrdinateValue($ordinateValue);
    
    
    JulienMoumne's avatar
    JulienMoumne a validé
    						$hasData = true;
    
    JulienMoumne's avatar
    JulienMoumne a validé
    
    						if($parsedOrdinateValue != 0)
    						{
    							$hasNonZeroValue = true;
    						}
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					}
    					else
    					{
    
    JulienMoumne's avatar
    JulienMoumne a validé
    						$parsedOrdinateValue = 0;
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					}
    
    					$rowId = $periodsMetadata[$i]['period']->getLocalizedShortString();
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					$abscissaSerie[] = Piwik_Common::unsanitizeInputValue($rowId);
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					$ordinateSerie[] = $parsedOrdinateValue;
    
    mattpiwik's avatar
    mattpiwik a validé
    				}
    			}
    
    JulienMoumne's avatar
    JulienMoumne a validé
    			if(!$hasData || !$hasNonZeroValue)
    
    mattpiwik's avatar
    mattpiwik a validé
    			{
    
    				throw new Exception(Piwik_Translate('General_NoDataForGraph'));
    
    mattpiwik's avatar
    mattpiwik a validé
    			}
    			
    			//Setup the graph
    
    			$graph = Piwik_ImageGraph_StaticGraph::factory($graphType);
    			$graph->setWidth($width);
    			$graph->setHeight($height);
    			$graph->setFont($font);
    			$graph->setFontSize($fontSize);
    			$graph->setMetricTitle($ordinateLabel);
    			$graph->setShowMetricTitle($showMetricTitle);
    			$graph->setAliasedGraph($aliasedGraph);
    			$graph->setAbscissaSerie($abscissaSerie);
    			$graph->setOrdinateSerie($ordinateSerie);
    			$graph->setOrdinateLogos($ordinateLogos);
    			$graph->setColors(!empty($colors) ? explode(',', $colors) : array());
    
    			// render graph
    			$graph->renderGraph();
    
    mattpiwik's avatar
    mattpiwik a validé
    			
    		} catch (Exception $e) {
    
    
    			$graph = new Piwik_ImageGraph_StaticGraph_Exception();
    
    JulienMoumne's avatar
    JulienMoumne a validé
    			$graph->setWidth($width);
    			$graph->setHeight($height);
    
    			$graph->setFont($font);
    			$graph->setFontSize($fontSize);
    			$graph->setException($e);
    			$graph->renderGraph();
    
    mattpiwik's avatar
    mattpiwik a validé
    		}
    
    mattpiwik's avatar
    mattpiwik a validé
    		switch($outputType)
    		{
    			case self::GRAPH_OUTPUT_FILE:
    
    JulienMoumne's avatar
    JulienMoumne a validé
    				if($idGoal != '')
    				{
    					$idGoal = '_' . $idGoal;
    				}
    
    JulienMoumne's avatar
    JulienMoumne a validé
    				$fileName = self::$DEFAULT_PARAMETERS[$graphType][self::FILENAME_KEY] . '_' . $apiModule . '_' . $apiAction . $idGoal . ' ' . str_replace(',', '-', $date) . ' ' . $idSite . '.png';
    
    				$fileName = str_replace(array(' ','/'), '_', $fileName);
    
    
    mattpiwik's avatar
    mattpiwik a validé
    				if(!Piwik_Common::isValidFilename($fileName))
    				{
    
    					throw new Exception('Error: Image graph filename ' . $fileName . ' is not valid.');
    
    mattpiwik's avatar
    mattpiwik a validé
    				}
    
    JulienMoumne's avatar
    JulienMoumne a validé
    
    			case self::GRAPH_OUTPUT_PHP:
    
    				return $graph->getRenderedImage();
    
    JulienMoumne's avatar
    JulienMoumne a validé
    
    
    mattpiwik's avatar
    mattpiwik a validé
    			case self::GRAPH_OUTPUT_INLINE:
    			default:
    
    mattpiwik's avatar
    mattpiwik a validé
    				exit;
    
    mattpiwik's avatar
    mattpiwik a validé
    		}
    	}
    
    
    	private function setFilterTruncate($default)
    
    		$_GET['filter_truncate'] = Piwik_Common::getRequestVar('filter_truncate', $default, 'int');
    
    	private static function parseOrdinateValue($ordinateValue)
    
    mattpiwik's avatar
    mattpiwik a validé
    	{
    
    		$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))
    
    mattpiwik's avatar
    mattpiwik a validé
    		{
    
    			$hour = $matches[1];
    			$min = $matches[2];
    			$sec = $matches[3];
    
    			$ordinateValue = ($hour * 3600) + ($min * 60) + $sec;
    
    mattpiwik's avatar
    mattpiwik a validé
    		}
    
    mattpiwik's avatar
    mattpiwik a validé
    	}
    
    
    	private static function getFontPath($font)
    	{
    		return PIWIK_INCLUDE_PATH . self::FONT_DIR . $font;
    	}
    }