Skip to content
Extraits de code Groupes Projets
API.php 18 ko
Newer Older
  • Learn to ignore specific revisions
  • <?php
    
    /**
     * Piwik - Open source web analytics
     * 
     * @link http://piwik.org
    
    robocoder's avatar
    robocoder a validé
     * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
    
    robocoder's avatar
    robocoder a validé
     * @category Piwik_Plugins
     * @package Piwik_API
    
    robocoder's avatar
    robocoder a validé
    /**
     * @package Piwik_API
     */
    
    class Piwik_API extends Piwik_Plugin {
    
    	public function getInformation() {
    
    		return array(
    
    			'description' => Piwik_Translate('API_PluginDescription'),
    
    			'author' => 'Piwik',
    
    			'author_homepage' => 'http://piwik.org/',
    
    			'version' => Piwik_Version::VERSION,
    
    mattpiwik's avatar
    mattpiwik a validé
    	public function getListHooksRegistered() {
    
    			'AssetManager.getCssFiles' => 'getCssFiles',
    			'TopMenu.add' => 'addTopMenu',
    
    	public function addTopMenu() {
    		Piwik_AddTopMenu('General_API', array('module' => 'API', 'action' => 'listAllAPI'), true, 7);
    	}
    
    
    mattpiwik's avatar
    mattpiwik a validé
    	public function getCssFiles($notification) {
    
    		$cssFiles = &$notification->getNotificationObject();
    		
    
    robocoder's avatar
    robocoder a validé
    		$cssFiles[] = "plugins/API/css/styles.css";
    
    mattpiwik's avatar
    mattpiwik a validé
    
    
    
    robocoder's avatar
    robocoder a validé
    /**
    
    mattpiwik's avatar
    mattpiwik a validé
     * This API is the <a href='http://piwik.org/docs/analytics-api/metadata/' target='_blank'>Metadata API</a>: it gives information about all other available APIs methods, as well as providing
     * human readable and more complete outputs than normal API methods. 
     * 
     * Some of the information that is returned by the Metadata API: 
     * <ul>
     * <li>the dynamically generated list of all API methods via "getReportMetadata"</li> 
     * <li>the list of metrics that will be returned by each method, along with their human readable name, via "getDefaultMetrics" and "getDefaultProcessedMetrics"</li>
     * <li>the list of segments metadata supported by all functions that have a 'segment' parameter</li>
     * <li>the (truly magic) method "getProcessedReport" will return a human readable version of any other report, and include the processed metrics such as 
     * conversion rate, time on site, etc. which are not directly available in other methods.  
     * </ul>
     * The Metadata API is for example used by the Piwik Mobile App to automatically display all Piwik reports, with translated report & columns names and nicely formatted values.
     * More information on the <a href='http://piwik.org/docs/analytics-api/metadata/' target='_blank'>Metadata API documentation page</a>
    
    robocoder's avatar
    robocoder a validé
     * 
     * @package Piwik_API
     */
    
    mattpiwik's avatar
    mattpiwik a validé
    class Piwik_API_API 
    {
    
    mattpiwik's avatar
    mattpiwik a validé
    	static private $instance = null;
    
    	/**
    	 * @return Piwik_API_API
    	 */
    	static public function getInstance()
    	{
    		if (self::$instance == null)
    		{
    
    mattpiwik's avatar
    mattpiwik a validé
    		}
    		return self::$instance;
    	}
    
    	public function getDefaultMetrics() 
    	{
    		$translations = array(
    			// Standard metrics
        		'nb_visits' => 'General_ColumnNbVisits',
    
        		'nb_uniq_visitors' => 'General_ColumnNbUniqVisitors',
    
    mattpiwik's avatar
    mattpiwik a validé
        		'nb_actions' => 'General_ColumnNbActions',
    // Do not display these in reports, as they are not so relevant
    
    mattpiwik's avatar
    mattpiwik a validé
    // They are used to process metrics below
    //			'nb_visits_converted' => 'General_ColumnVisitsWithConversions',
    
    mattpiwik's avatar
    mattpiwik a validé
    //    		'max_actions' => 'General_ColumnMaxActions',
    //    		'sum_visit_length' => 'General_ColumnSumVisitLength',
    //			'bounce_count'
    		);
    		$translations = array_map('Piwik_Translate', $translations);
    		return $translations;
    	}
    
    	public function getDefaultProcessedMetrics()
    	{
    		$translations = array(
    
    mattpiwik's avatar
    mattpiwik a validé
    			// Processed in AddColumnsProcessedMetrics
    
    mattpiwik's avatar
    mattpiwik a validé
    			'nb_actions_per_visit' => 'General_ColumnActionsPerVisit',
        		'avg_time_on_site' => 'General_ColumnAvgTimeOnSite',
        		'bounce_rate' => 'General_ColumnBounceRate',
    
    mattpiwik's avatar
    mattpiwik a validé
        		'conversion_rate' => 'General_ColumnConversionRate',
    
    mattpiwik's avatar
    mattpiwik a validé
    		);
    		return array_map('Piwik_Translate', $translations);
    	}
    	
    
    mattpiwik's avatar
    mattpiwik a validé
    	public function getSegmentsMetadata($idSites = array(), $_hideImplementationData = true)
    	{
    		$segments = array();
    		Piwik_PostEvent('API.getSegmentsMetadata', $segments, $idSites);
    		
    		$segments[] = array(
    		        'type' => 'dimension',
    		        'category' => 'Visit',
    		        'name' => 'General_VisitorIP',
    		        'segment' => 'visitIp',
    
    mattpiwik's avatar
    mattpiwik a validé
    		        'sqlSegment' => 'location_ip',
    
    mattpiwik's avatar
    mattpiwik a validé
    		        'permission' => Piwik::isUserHasAdminAccess($idSites),
    
    	    );
    		$segments[] = array(
    		        'type' => 'dimension',
    		        'category' => 'Visit',
    		        'name' => 'General_VisitorID',
    		        'segment' => 'visitorId',
    				'acceptedValues' => '34c31e04394bdc63 - any 16 chars ID requested via the Tracking API function getVisitorId()',
    		        'sqlSegment' => 'idvisitor',
    		        'sqlFilter' => array('Piwik_Common', 'convertVisitorIdToBin'),
    
    mattpiwik's avatar
    mattpiwik a validé
    	    );
    		$segments[] = array(
    		        'type' => 'metric',
    		        'category' => 'Visit',
    
    		        'name' => 'General_NbActions',
    
    mattpiwik's avatar
    mattpiwik a validé
    		        'segment' => 'actions',
    		        'sqlSegment' => 'visit_total_actions',
    	    );
    		$segments[] = array(
    		        'type' => 'metric',
    		        'category' => 'Visit',
    		        'name' => 'General_ColumnVisitDuration',
    		        'segment' => 'visitDuration',
    		        'sqlSegment' => 'visit_total_time',
    	    );
    		$segments[] = array(
    		        'type' => 'dimension',
    		        'category' => 'Visit',
    		        'name' => 'General_VisitType',
    		        'segment' => 'visitorType',
    		        'acceptedValues' => 'new, returning',
    		        'sqlSegment' => 'visitor_returning',
    		        'sqlFilter' => create_function('$type', 'return $type == "new" ? 0 : 1;'),
    	    );
    
    mattpiwik's avatar
    mattpiwik a validé
    		$segments[] = array(
    		        'type' => 'metric',
    		        'category' => 'Visit',
    		        'name' => 'General_DaysSinceLastVisit',
    		        'segment' => 'daysSinceLastVisit',
    		        'sqlSegment' => 'visitor_days_since_last',
    	    );
    		$segments[] = array(
    		        'type' => 'metric',
    		        'category' => 'Visit',
    		        'name' => 'General_DaysSinceFirstVisit',
    		        'segment' => 'daysSinceFirstVisit',
    		        'sqlSegment' => 'visitor_days_since_first',
    	    );
    		$segments[] = array(
    		        'type' => 'metric',
    		        'category' => 'Visit',
    		        'name' => 'General_NumberOfVisits',
    		        'segment' => 'visitCount',
    		        'sqlSegment' => 'visitor_count_visits',
    	    );
    	    
    		$segments[] = array(
    		        'type' => 'metric',
    		        'category' => 'Visit',
    		        'name' => 'General_VisitConvertedGoal',
    		        'segment' => 'visitConverted',
    				'acceptedValues' => '0, 1',
    		        'sqlSegment' => 'visit_goal_converted',
    	    );
    
    mattpiwik's avatar
    mattpiwik a validé
    		foreach ($segments as &$segment) 
    		{
    		    $segment['name'] = Piwik_Translate($segment['name']);
    		    $segment['category'] = Piwik_Translate($segment['category']);
    		    
    		    if($_hideImplementationData)
    		    {
    		    	unset($segment['sqlFilter']);
    		    	unset($segment['sqlSegment']);
    		    }
    		}
    
    		
    		usort($segments, array($this, 'sortSegments'));
    
    mattpiwik's avatar
    mattpiwik a validé
    		return $segments;
    	}
    	
    
    	private function sortSegments($row1, $row2)
    	{
    		$columns = array('type', 'category', 'name', 'segment');
    		foreach($columns as $column)
    		{
    
    			$compare = -1 * strcmp($row1[$column], $row2[$column]);
    
    			if($compare != 0){
    				return $compare;
    			}
    		}
    		return $compare;
    	}
    
    mattpiwik's avatar
    mattpiwik a validé
        /**
    
    mattpiwik's avatar
    mattpiwik a validé
         * Loads reports metadata, then return the requested one, 
         * matching optional API parameters.
    
    mattpiwik's avatar
    mattpiwik a validé
    	public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false, $period = false)
    
    mattpiwik's avatar
    mattpiwik a validé
        	Piwik_Translate::getInstance()->reloadLanguage($language);
    
        	static $reportsMetadata = array();
    
    mattpiwik's avatar
    mattpiwik a validé
        	$cacheKey = $idSite.$language;
        	if(!isset($reportsMetadata[$cacheKey]))
    
    mattpiwik's avatar
    mattpiwik a validé
        		$reportsMetadata[$cacheKey] = $this->getReportMetadata($idSite);
    
    mattpiwik's avatar
    mattpiwik a validé
        	foreach($reportsMetadata[$cacheKey] as $report)
    
    mattpiwik's avatar
    mattpiwik a validé
        		// See ArchiveProcessing/Period.php - unique visitors are not processed for period != day
    	    	if($period != 'day'
    	    		&& !($apiModule == 'VisitsSummary' 
    	    			&& $apiAction == 'get'))
    	    	{
    	    		unset($report['metrics']['nb_uniq_visitors']);
    	    	}
    
        		if($report['module'] == $apiModule
        			&& $report['action'] == $apiAction)
    			{
    				if(empty($apiParameters))
    				{
            			return array($report);
    				}
    				if(empty($report['parameters']))
    				{
    					continue;
    				}
    				$diff = array_diff($report['parameters'], $apiParameters);
    				if(empty($diff))
    				{
    					return array($report);
    				}
    			}
        	}
        	return false;
        }
        
    
    mattpiwik's avatar
    mattpiwik a validé
    	/**
    	 * Triggers a hook to ask plugins for available Reports.
    
    	 * Returns metadata information about each report (category, name, dimension, metrics, etc.) 
    
    mattpiwik's avatar
    mattpiwik a validé
    	 *
    
    mattpiwik's avatar
    mattpiwik a validé
    	 * @param string $idSites Comma separated list of website Ids
    
    mattpiwik's avatar
    mattpiwik a validé
    	 * @return array
    	 */
    
    mattpiwik's avatar
    mattpiwik a validé
    	public function getReportMetadata($idSites = '') 
    
    mattpiwik's avatar
    mattpiwik a validé
    	{
    		$idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites);
    
    mattpiwik's avatar
    mattpiwik a validé
    		
    
    mattpiwik's avatar
    mattpiwik a validé
    		$availableReports = array();
    		Piwik_PostEvent('API.getReportMetadata', $availableReports, $idSites);
    		foreach ($availableReports as &$availableReport) {
    			if (!isset($availableReport['metrics'])) {
    				$availableReport['metrics'] = $this->getDefaultMetrics();
    			}
    			if (!isset($availableReport['processedMetrics'])) {
    				$availableReport['processedMetrics'] = $this->getDefaultProcessedMetrics();
    			}
    		}
    		
    		// Some plugins need to add custom metrics after all plugins hooked in
    		Piwik_PostEvent('API.getReportMetadata.end', $availableReports, $idSites);
    		
    
    mattpiwik's avatar
    mattpiwik a validé
    		// Sort results to ensure consistent order
    		usort($availableReports, array($this, 'sort'));
    
    
    mattpiwik's avatar
    mattpiwik a validé
    		$knownMetrics = array_merge( $this->getDefaultMetrics(), $this->getDefaultProcessedMetrics() );
    		foreach($availableReports as &$availableReport)
    		{
    
    mattpiwik's avatar
    mattpiwik a validé
    			// Ensure all metrics have a translation
    
    mattpiwik's avatar
    mattpiwik a validé
    			$metrics = $availableReport['metrics'];
    			$cleanedMetrics = array();
    			foreach($metrics as $metricId => $metricTranslation)
    			{
    
    mattpiwik's avatar
    mattpiwik a validé
    				// When simply the column name was given, ie 'metric' => array( 'nb_visits' )
    				// $metricTranslation is in this case nb_visits. We look for a known translation.
    
    mattpiwik's avatar
    mattpiwik a validé
    				if(is_numeric($metricId)
    					&& isset($knownMetrics[$metricTranslation]))
    				{
    					$metricId = $metricTranslation;
    					$metricTranslation = $knownMetrics[$metricTranslation];
    				}
    				$cleanedMetrics[$metricId] = $metricTranslation;
    			}
    			$availableReport['metrics'] = $cleanedMetrics;
    
    mattpiwik's avatar
    mattpiwik a validé
    			
    			// Remove array elements that are false (to clean up API output)
    			foreach($availableReport as $attributeName => $attributeValue)
    			{
    				if(empty($attributeValue))
    				{
    					unset($availableReport[$attributeName]);
    				}
    			}
    
    mattpiwik's avatar
    mattpiwik a validé
            	// when there are per goal metrics, don't display conversion_rate since it can differ from per goal sum
    	        if(isset($availableReport['metricsGoal']))
    	        {
    	        	unset($availableReport['processedMetrics']['conversion_rate']);
    	        	unset($availableReport['metricsGoal']['conversion_rate']);
    	        }
    
    mattpiwik's avatar
    mattpiwik a validé
    			
    			// Processing a uniqueId for each report, 
    			// can be used by UIs as a key to match a given report
    			$uniqueId = $availableReport['module'] . '_' . $availableReport['action'];
    			if(!empty($availableReport['parameters']))
    			{
    				foreach($availableReport['parameters'] as $key => $value)
    				{
    					$uniqueId .= '_' . $key . '--' . $value;
    				}
    			}
    			$availableReport['uniqueId'] = $uniqueId;
    
    mattpiwik's avatar
    mattpiwik a validé
    			
    			// Order is used to order reports internally, but not meant to be used outside
    			unset($availableReport['order']);
    
    mattpiwik's avatar
    mattpiwik a validé
    		}
    
    mattpiwik's avatar
    mattpiwik a validé
    		
    
    mattpiwik's avatar
    mattpiwik a validé
    		return $availableReports;
    	}
    
    mattpiwik's avatar
    mattpiwik a validé
    
    
    mattpiwik's avatar
    mattpiwik a validé
    	public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false, $apiParameters = false, $language = false, $showTimer = true)
    
    mattpiwik's avatar
    mattpiwik a validé
        {
    
    mattpiwik's avatar
    mattpiwik a validé
        	$timer = new Piwik_Timer();
    
    mattpiwik's avatar
    mattpiwik a validé
        	if($apiParameters === false)
        	{
        		$apiParameters = array();
        	}
            // Is this report found in the Metadata available reports?
    
    mattpiwik's avatar
    mattpiwik a validé
            $reportMetadata = $this->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language, $period);
    
            if(empty($reportMetadata))
    
    mattpiwik's avatar
    mattpiwik a validé
            {
    
    mattpiwik's avatar
    mattpiwik a validé
            	throw new Exception("Requested report $apiModule.$apiAction for Website id=$idSite not found in the list of available reports. \n");
    
    mattpiwik's avatar
    mattpiwik a validé
            }
    
            $reportMetadata = reset($reportMetadata);
    
    mattpiwik's avatar
    mattpiwik a validé
            
    		// Generate Api call URL passing custom parameters
    		$parameters = array_merge( $apiParameters, array(
    			'method' => $apiModule.'.'.$apiAction,
    			'idSite' => $idSite,
    			'period' => $period,
    			'date' => $date,
    			'format' => 'original',
    
    mattpiwik's avatar
    mattpiwik a validé
    			'serialize' => '0',
    			'language' => $language,
    
    mattpiwik's avatar
    mattpiwik a validé
    		));
    
    mattpiwik's avatar
    mattpiwik a validé
    		if(!empty($segment)) $parameters['segment'] = $segment;
    		
    
    mattpiwik's avatar
    mattpiwik a validé
    		$url = Piwik_Url::getQueryStringFromParameters($parameters);
            $request = new Piwik_API_Request($url);
            try {
            	/** @var Piwik_DataTable */
            	$dataTable = $request->process();
            } catch(Exception $e) {
            	throw new Exception("API returned an error: ".$e->getMessage()."\n");
            }
            // Table with a Dimension (Keywords, Pages, Browsers, etc.)
            if(isset($reportMetadata['dimension']))
            {
            	$callback = 'handleTableReport';
            }
            // Table without a dimension, simple list of general metrics (eg. VisitsSummary.get)
            else
            {
            	$callback = 'handleTableSimple';
            }
        	list($newReport, $columns, $rowsMetadata) = $this->$callback($idSite, $period, $dataTable, $reportMetadata);
        	foreach($columns as $columnId => &$name)
        	{
        		$name = ucfirst($name);
        	}
        	$website = new Piwik_Site($idSite);
    
    mattpiwik's avatar
    mattpiwik a validé
    //    	$segment = new Piwik_Segment($segment, $idSite);
    
    mattpiwik's avatar
    mattpiwik a validé
        	if($period == 'range')
        	{
    	    	$period = new Piwik_Period_Range($period, $date);
        	}
        	else
        	{
    	    	$period = Piwik_Period::factory($period, Piwik_Date::factory($date));
        	}
        	
    
    mattpiwik's avatar
    mattpiwik a validé
        	$return = array(
    
    mattpiwik's avatar
    mattpiwik a validé
    				'website' => $website->getName(),
    
    mattpiwik's avatar
    mattpiwik a validé
    				'prettyDate' => $period->getLocalizedLongString(),
    
    mattpiwik's avatar
    mattpiwik a validé
    //    			'prettySegment' => $segment->getPrettyString(),
    
    mattpiwik's avatar
    mattpiwik a validé
    				'metadata' => $reportMetadata, 
    				'columns' => $columns, 
    				'reportData' =>	$newReport, 
    				'reportMetadata' => $rowsMetadata,
    		);
    
    mattpiwik's avatar
    mattpiwik a validé
    		if($showTimer)
    		{
    			$return['timerMillis'] = $timer->getTimeMs(0);
    		}
    		return $return;
    
    mattpiwik's avatar
    mattpiwik a validé
        }
        
        private function handleTableSimple($idSite, $period, $dataTable, $reportMetadata)
        {
            $renderer = new Piwik_DataTable_Renderer_Php();
            $renderer->setTable($dataTable);
            $renderer->setSerialize(false);
            $reportTable = $renderer->render();
    
            $newReport = array();
            foreach($reportTable as $metric => $value)
            {
            	// Use translated metric from metadata
            	// If translation not found, do not display the returned data
            	if(isset($reportMetadata['metrics'][$metric]))
            	{
            		$value = Piwik::getPrettyValue($idSite, $metric, $value, $htmlAllowed = false, $timeAsSentence = false);
        		
            		$metric = $reportMetadata['metrics'][$metric];
                	$newReport[] = array(
                		'label' => $metric,
                		'value' => $value
                	);
            	}
            }
            
            $columns = array(
            	'label' => Piwik_Translate('General_Name'),
            	'value' => Piwik_Translate('General_Value'),
            );
        	return array(
        		$newReport, 
        		$columns,
        		$rowsMetadata = array()
        	);
        }
        
    
        private function handleTableReport($idSite, $period, $dataTable, &$reportMetadata)
    
    mattpiwik's avatar
    mattpiwik a validé
        {
        	// displayed columns
        	$columns = array_merge(
        		array('label' => $reportMetadata['dimension'] ),
        		$reportMetadata['metrics']
        	);
        	
            if(isset($reportMetadata['processedMetrics']))
            {
    
    mattpiwik's avatar
    mattpiwik a validé
            	$processedMetricsAdded = $this->getDefaultProcessedMetrics();
    
    mattpiwik's avatar
    mattpiwik a validé
            	foreach($processedMetricsAdded as $processedMetricId => $processedMetricTranslation)
            	{
            		// this processed metric can be displayed for this report
            		if(isset($reportMetadata['processedMetrics'][$processedMetricId]))
            		{
            			$columns[$processedMetricId] = $processedMetricTranslation;
            		}
            	}
            }
        	// Display the global Goal metrics 
            if(isset($reportMetadata['metricsGoal']))
            {
    
    mattpiwik's avatar
    mattpiwik a validé
            	$metricsGoalDisplay = array('revenue');
    
    mattpiwik's avatar
    mattpiwik a validé
        		// Add processed metrics to be displayed for this report
            	foreach($metricsGoalDisplay as $goalMetricId)
            	{
            		if(isset($reportMetadata['metricsGoal'][$goalMetricId]))
            		{
            			$columns[$goalMetricId] = $reportMetadata['metricsGoal'][$goalMetricId];
            		}
            	}
            }
    
            if(isset($reportMetadata['processedMetrics']))
    
            {
            	// Add processed metrics
            	$dataTable->filter('AddColumnsProcessedMetrics');
            }
    
    mattpiwik's avatar
    mattpiwik a validé
            $renderer = new Piwik_DataTable_Renderer_Php();
            $renderer->setTable($dataTable);
            $renderer->setSerialize(false);
            $reportTable = $renderer->render();
        	$rowsMetadata = array();
        	
        	$newReport = array();
        	foreach($reportTable as $rowId => $row)
        	{
    
    mattpiwik's avatar
    mattpiwik a validé
        		// ensure all displayed columns have 0 values
        		foreach($columns as $id => $name)
        		{
        			if(!isset($row[$id]))
        			{
        				$row[$id] = 0;
        			}
        		}
    
    mattpiwik's avatar
    mattpiwik a validé
        		$newRow = array();
        		foreach($row as $columnId => $value)
        		{
        			// Keep displayed columns
        			if(isset($columns[$columnId]))
        			{
            			$newRow[$columnId] = Piwik::getPrettyValue($idSite, $columnId, $value, $htmlAllowed = false, $timeAsSentence = false);
        			}
            		// We try and only keep metadata 
            		// - if the column value is not an array (eg. metrics per goal)
            		// - if the column name doesn't contain _ (which is by standard, a metric column)
        			elseif(!is_array($value)
        				&& strpos($columnId, '_') === false
        				)
        			{
        				$rowsMetadata[$rowId][$columnId] = $value;
        			}
        		}
        		$newReport[] = $newRow;
        	}
        	return array(
        		$newReport, 
        		$columns, 
        		$rowsMetadata
        	);
        }
    
    
    mattpiwik's avatar
    mattpiwik a validé
    	/**
    
    mattpiwik's avatar
    mattpiwik a validé
    	 * API metadata are sorted by category/name, 
    	 * with a little tweak to replicate the standard Piwik category ordering
    	 * 
    
    	 * @param string $a
    	 * @param string $b
    
    mattpiwik's avatar
    mattpiwik a validé
    	 * @return int
    	 */
    
    	private function sort($a, $b)
    	{
    
    mattpiwik's avatar
    mattpiwik a validé
    		static $order = null;
    		if(is_null($order))
    		{
    			$order = array(
    				Piwik_Translate('VisitsSummary_VisitsSummary'),
    				Piwik_Translate('Actions_Actions'),
    				Piwik_Translate('Referers_Referers'),
    				Piwik_Translate('Goals_Goals'),
    				Piwik_Translate('General_Visitors'),
    				Piwik_Translate('UserSettings_VisitorSettings'),
    			);
    		}
    
    mattpiwik's avatar
    mattpiwik a validé
    		return ($category = strcmp(array_search($a['category'], $order), array_search($b['category'], $order))) == 0 	
    
    mattpiwik's avatar
    mattpiwik a validé
    				?  (@$a['order'] < @$b['order'] ? -1 : 1)
    
    mattpiwik's avatar
    mattpiwik a validé
    				: $category;