diff --git a/oss/piwik/trunk/core/API/DataTableGenericFilter.php b/oss/piwik/trunk/core/API/DataTableGenericFilter.php
new file mode 100644
index 00000000..ed44078e
--- /dev/null
+++ b/oss/piwik/trunk/core/API/DataTableGenericFilter.php
@@ -0,0 +1,145 @@
+table = $datatable;
+ $this->request = $request;
+ }
+
+ public function filter()
+ {
+ $this->applyGenericFilters($this->table);
+ }
+
+ /**
+ * Returns an array containing the information of the generic Piwik_DataTable_Filter
+ * to be applied automatically to the data resulting from the API calls.
+ *
+ * Order to apply the filters:
+ * 1 - Filter that remove filtered rows
+ * 2 - Filter that sort the remaining rows
+ * 3 - Filter that keep only a subset of the results
+ * 4 - Presentation filters
+ *
+ * @return array See the code for spec
+ */
+ public static function getGenericFiltersInformation()
+ {
+ $genericFilters = array(
+ 'Pattern' => array(
+ 'filter_column' => array('string'),
+ 'filter_pattern' => array('string'),
+ ),
+ 'PatternRecursive' => array(
+ 'filter_column_recursive' => array('string'),
+ 'filter_pattern_recursive' => array('string'),
+ ),
+ 'ExcludeLowPopulation' => array(
+ 'filter_excludelowpop' => array('string'),
+ 'filter_excludelowpop_value'=> array('float', '0'),
+ ),
+ 'AddColumnsWhenShowAllColumns' => array(
+ 'filter_add_columns_when_show_all_columns' => array('integer')
+ ),
+ 'UpdateColumnsWhenShowAllGoals' => array(
+ 'filter_update_columns_when_show_all_goals' => array('integer'),
+ 'filter_only_display_idgoal' => array('integer', Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals::GOALS_OVERVIEW),
+ ),
+ 'Sort' => array(
+ 'filter_sort_column' => array('string'),
+ 'filter_sort_order' => array('string'),
+ ),
+ 'Limit' => array(
+ 'filter_offset' => array('integer', '0'),
+ 'filter_limit' => array('integer'),
+ ),
+ );
+
+ return $genericFilters;
+ }
+
+ /**
+ * Apply generic filters to the DataTable object resulting from the API Call.
+ * Disable this feature by setting the parameter disable_generic_filters to 1 in the API call request.
+ *
+ * @param Piwik_DataTable
+ */
+ protected function applyGenericFilters($datatable)
+ {
+ if($datatable instanceof Piwik_DataTable_Array )
+ {
+ $tables = $datatable->getArray();
+ $filterWasApplied = false;
+ foreach($tables as $table)
+ {
+ $filterWasApplied = $this->applyGenericFilters($table);
+ // if no generic filter was applied to the first table, we can return
+ // as no filter would be applied to any other dataTable
+ if(!$filterWasApplied)
+ {
+ return;
+ }
+ }
+ return;
+ }
+
+ $genericFilters = self::getGenericFiltersInformation();
+
+ $filterApplied = false;
+ foreach($genericFilters as $filterName => $parameters)
+ {
+ $filterParameters = array();
+ $exceptionRaised = false;
+ foreach($parameters as $name => $info)
+ {
+ // parameter type to cast to
+ $type = $info[0];
+
+ // default value if specified, when the parameter doesn't have a value
+ $defaultValue = null;
+ if(isset($info[1]))
+ {
+ $defaultValue = $info[1];
+ }
+
+ try {
+ $value = Piwik_Common::getRequestVar($name, $defaultValue, $type, $this->request);
+ settype($value, $type);
+ $filterParameters[] = $value;
+ }
+ catch(Exception $e)
+ {
+ $exceptionRaised = true;
+ break;
+ }
+ }
+
+ if(!$exceptionRaised)
+ {
+ if($filterName == 'Limit')
+ {
+ $datatable->setRowsCountBeforeLimitFilter();
+ }
+ $datatable->filter($filterName, $filterParameters);
+ $filterApplied = true;
+ }
+ }
+ return $filterApplied;
+ }
+}
diff --git a/oss/piwik/trunk/core/API/DocumentationGenerator.php b/oss/piwik/trunk/core/API/DocumentationGenerator.php
new file mode 100644
index 00000000..25d5f383
--- /dev/null
+++ b/oss/piwik/trunk/core/API/DocumentationGenerator.php
@@ -0,0 +1,202 @@
+getLoadedPluginsName();
+ foreach( $plugins as $plugin )
+ {
+ $plugin = Piwik::unprefixClass($plugin);
+ try {
+ Piwik_API_Proxy::getInstance()->registerClass('Piwik_'.$plugin.'_API');
+ }
+ catch(Exception $e){
+ }
+ }
+ }
+
+ /**
+ * Returns a HTML page containing help for all the successfully loaded APIs.
+ * For each module it will return a mini help with the method names, parameters to give,
+ * links to get the result in Xml/Csv/etc
+ *
+ * @return string
+ */
+ public function getAllInterfaceString( $outputExampleUrls = true, $prefixUrls = '' )
+ {
+ $str = '';
+ $token_auth = "&token_auth=" . Piwik::getCurrentUserTokenAuth();
+ $parametersToSet = array(
+ 'idSite' => Piwik_Common::getRequestVar('idSite', 1, 'int'),
+ 'period' => Piwik_Common::getRequestVar('period', 'day', 'string'),
+ 'date' => Piwik_Common::getRequestVar('date', 'today', 'string')
+ );
+
+ foreach(Piwik_API_Proxy::getInstance()->getMetadata() as $class => $info)
+ {
+ $moduleName = Piwik_API_Proxy::getInstance()->getModuleNameFromClassName($class);
+ $str .= "\n
Module ".$moduleName."
";
+
+ foreach($info as $methodName => $infoMethod)
+ {
+ $params = $this->getStrListParameters($class, $methodName);
+ $str .= "\n" . "- $moduleName.$methodName " . $params . "";
+ $str .= '';
+
+ if($outputExampleUrls)
+ {
+ // we prefix all URLs with $prefixUrls
+ // used when we include this output in the Piwik official documentation for example
+ $str .= "";
+ $exampleUrl = $this->getExampleUrl($class, $methodName, $parametersToSet);
+ if($exampleUrl !== false)
+ {
+ $lastNUrls = '';
+ if( preg_match('/(&period)|(&date)/',$exampleUrl))
+ {
+ $exampleUrlRss1 = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last10') + $parametersToSet) ;
+ $exampleUrlRss2 = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last5','period' => 'week',) + $parametersToSet );
+ $lastNUrls = ", RSS of the last 10 days, 5 weeks,
+ XML of the last 10 days";
+ }
+ $exampleUrl = $prefixUrls . $exampleUrl ;
+ $str .= " [ Example in
+ XML,
+ PHP,
+ Json,
+ Csv,
+ Basic html
+ $lastNUrls
+ ]";
+ }
+ else
+ {
+ $str .= " [ No example available ]";
+ }
+ $str .= "";
+ }
+ $str .= '';
+ $str .= "\n
";
+ }
+ }
+ return $str;
+ }
+
+ /**
+ * Returns a string containing links to examples on how to call a given method on a given API
+ * It will export links to XML, CSV, HTML, JSON, PHP, etc.
+ * It will not export links for methods such as deleteSite or deleteUser
+ *
+ * @param string the class
+ * @param methodName the method
+ * @return string|false when not possible
+ */
+ protected function getExampleUrl($class, $methodName, $parametersToSet = array())
+ {
+ $knowExampleDefaultParametersValues = array(
+ 'access' => 'view',
+ 'userLogin' => 'test',
+ 'passwordMd5ied' => 'passwordExample',
+ 'email' => 'test@example.org',
+
+ 'languageCode' => 'fr',
+ );
+
+ foreach($parametersToSet as $name => $value)
+ {
+ $knowExampleDefaultParametersValues[$name] = $value;
+ }
+
+ // no links for these method names
+ $doNotPrintExampleForTheseMethods = array(
+ //Sites
+ 'deleteSite',
+ 'addSite',
+ 'updateSite',
+ 'addSiteAliasUrls',
+ //Users
+ 'deleteUser',
+ 'addUser',
+ 'updateUser',
+ 'setUserAccess',
+ //Goals
+ 'addGoal',
+ 'updateGoal',
+ 'deleteGoal',
+ );
+
+ if(in_array($methodName,$doNotPrintExampleForTheseMethods))
+ {
+ return false;
+ }
+
+ // we try to give an URL example to call the API
+ $aParameters = Piwik_API_Proxy::getInstance()->getParametersList($class, $methodName);
+ $moduleName = Piwik_API_Proxy::getInstance()->getModuleNameFromClassName($class);
+ $urlExample = '?module=API&method='.$moduleName.'.'.$methodName.'&';
+ foreach($aParameters as $nameVariable=> $defaultValue)
+ {
+ // if there isn't a default value for a given parameter,
+ // we need a 'know default value' or we can't generate the link
+ if($defaultValue instanceof Piwik_API_Proxy_NoDefaultValue)
+ {
+ if(isset($knowExampleDefaultParametersValues[$nameVariable]))
+ {
+ $exampleValue = $knowExampleDefaultParametersValues[$nameVariable];
+ $urlExample .= $nameVariable . '=' . $exampleValue . '&';
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ return substr($urlExample,0,-1);
+ }
+
+
+ /**
+ * Returns the methods $class.$name parameters (and default value if provided) as a string.
+ *
+ * @param string The class name
+ * @param string The method name
+ * @return string For example "(idSite, period, date = 'today')"
+ */
+ protected function getStrListParameters($class, $name)
+ {
+ $aParameters = Piwik_API_Proxy::getInstance()->getParametersList($class, $name);
+ $asParameters = array();
+ foreach($aParameters as $nameVariable=> $defaultValue)
+ {
+ $str = $nameVariable;
+ if(!($defaultValue instanceof Piwik_API_Proxy_NoDefaultValue))
+ {
+ $str .= " = '$defaultValue'";
+ }
+ $asParameters[] = $str;
+ }
+ $sParameters = implode(", ", $asParameters);
+ return "($sParameters)";
+ }
+}
diff --git a/oss/piwik/trunk/core/API/Proxy.php b/oss/piwik/trunk/core/API/Proxy.php
new file mode 100644
index 00000000..6e216eb5
--- /dev/null
+++ b/oss/piwik/trunk/core/API/Proxy.php
@@ -0,0 +1,329 @@
+noDefaultValue = new Piwik_API_Proxy_NoDefaultValue();
+ }
+
+ /**
+ * Singleton, returns instance
+ *
+ * @return Piwik_API_Proxy
+ */
+ static public function getInstance()
+ {
+ if (self::$instance == null)
+ {
+ $c = __CLASS__;
+ self::$instance = new $c();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Returns array containing reflection meta data for all the loaded classes
+ * eg. number of parameters, method names, etc.
+ *
+ * @return array
+ */
+ public function getMetadata()
+ {
+ return $this->metadataArray;
+ }
+
+ /**
+ * Registers the API information of a given module.
+ *
+ * The module to be registered must be
+ * - a singleton (providing a getInstance() method)
+ * - the API file must be located in plugins/ModuleName/API.php
+ * for example plugins/Referers/API.php
+ *
+ * The method will introspect the methods, their parameters, etc.
+ *
+ * @param string ModuleName eg. "Piwik_UserSettings_API"
+ */
+ public function registerClass( $className )
+ {
+ if(isset($this->alreadyRegistered[$className]))
+ {
+ return;
+ }
+ $this->includeApiFile( $className );
+ $this->checkClassIsSingleton($className);
+
+ $rClass = new ReflectionClass($className);
+ foreach($rClass->getMethods() as $method)
+ {
+ $this->loadMethodMetadata($className, $method);
+ }
+
+ $this->alreadyRegistered[$className] = true;
+ }
+
+ /**
+ * Returns number of classes already loaded
+ * @return int
+ */
+ public function getCountRegisteredClasses()
+ {
+ return count($this->alreadyRegistered);
+ }
+
+ /**
+ * Will execute $className->$methodName($parametersValues)
+ * If any error is detected (wrong number of parameters, method not found, class not found, etc.)
+ * it will throw an exception
+ *
+ * It also logs the API calls, with the parameters values, the returned value, the performance, etc.
+ * You can enable logging in config/global.ini.php (log_api_call)
+ *
+ * @param string The class name (eg. Piwik_Referers_API)
+ * @param string The method name
+ * @param array The parameters pairs (name=>value)
+ *
+ * @throws Piwik_Access_NoAccessException
+ */
+ public function call($className, $methodName, $parametersRequest )
+ {
+ $returnedValue = null;
+
+ try {
+ $this->registerClass($className);
+
+ // instanciate the object
+ $object = call_user_func(array($className, "getInstance"));
+
+ // check method exists
+ $this->checkMethodExists($className, $methodName);
+
+ // get the list of parameters required by the method
+ $parameterNamesDefaultValues = $this->getParametersList($className, $methodName);
+
+ // load parameters in the right order, etc.
+ $finalParameters = $this->getRequestParametersArray( $parameterNamesDefaultValues, $parametersRequest );
+
+ // start the timer
+ $timer = new Piwik_Timer();
+
+ // call the method
+ $returnedValue = call_user_func_array(array($object, $methodName), $finalParameters);
+
+ // log the API Call
+ Zend_Registry::get('logger_api_call')->logEvent(
+ $className,
+ $methodName,
+ $parameterNamesDefaultValues,
+ $finalParameters,
+ $timer->getTimeMs(),
+ $returnedValue
+ );
+ } catch(Piwik_Access_NoAccessException $e) {
+ throw $e;
+ }
+
+ return $returnedValue;
+ }
+
+ /**
+ * Returns the parameters names and default values for the method $name
+ * of the class $class
+ *
+ * @param string The class name
+ * @param string The method name
+ * @return array Format array(
+ * 'testParameter' => null, // no default value
+ * 'life' => 42, // default value = 42
+ * 'date' => 'yesterday',
+ * );
+ */
+ public function getParametersList($class, $name)
+ {
+ return $this->metadataArray[$class][$name]['parameters'];
+ }
+
+ /**
+ * Returns the 'moduleName' part of 'Piwik_moduleName_API' classname
+ * @param string "Piwik_Referers_API"
+ * @return string "Referers"
+ */
+ public function getModuleNameFromClassName( $className )
+ {
+ return str_replace(array('Piwik_', '_API'), '', $className);
+ }
+
+ /**
+ * Returns an array containing the values of the parameters to pass to the method to call
+ *
+ * @param array array of (parameter name, default value)
+ * @return array values to pass to the function call
+ * @throws exception If there is a parameter missing from the required function parameters
+ */
+ private function getRequestParametersArray( $requiredParameters, $parametersRequest )
+ {
+ $finalParameters = array();
+ foreach($requiredParameters as $name => $defaultValue)
+ {
+ try{
+ if($defaultValue instanceof Piwik_API_Proxy_NoDefaultValue)
+ {
+ $requestValue = Piwik_Common::getRequestVar($name, null, null, $parametersRequest);
+ }
+ else
+ {
+ try{
+ $requestValue = Piwik_Common::getRequestVar($name, $defaultValue, null, $parametersRequest);
+ } catch(Exception $e) {
+ $requestValue = $defaultValue;
+ }
+ }
+ } catch(Exception $e) {
+ throw new Exception(Piwik_TranslateException('General_ExceptionVariableNotFound', array($name)));
+ }
+ $finalParameters[] = $requestValue;
+ }
+ return $finalParameters;
+ }
+
+ /**
+ * Includes the class Piwik_UserSettings_API by looking up plugins/UserSettings/API.php
+ *
+ * @param string api class name eg. "Piwik_UserSettings_API"
+ */
+ private function includeApiFile($fileName)
+ {
+ $module = self::getModuleNameFromClassName($fileName);
+ $path = PIWIK_INCLUDE_PATH . '/plugins/' . $module . '/API.php';
+
+ if(is_readable($path))
+ {
+ require_once $path; // prefixed by PIWIK_INCLUDE_PATH
+ }
+ else
+ {
+ throw new Exception("API module $module not found.");
+ }
+ }
+
+ private function loadMethodMetadata($class, $method)
+ {
+ if($method->isPublic()
+ && !$method->isConstructor()
+ && $method->getName() != 'getInstance'
+ && false === strstr($method->getDocComment(), '@deprecated')
+ )
+ {
+ $name = $method->getName();
+ $parameters = $method->getParameters();
+
+ $aParameters = array();
+ foreach($parameters as $parameter)
+ {
+ $nameVariable = $parameter->getName();
+
+ $defaultValue = $this->noDefaultValue;
+ if($parameter->isDefaultValueAvailable())
+ {
+ $defaultValue = $parameter->getDefaultValue();
+ }
+
+ $aParameters[$nameVariable] = $defaultValue;
+ }
+ $this->metadataArray[$class][$name]['parameters'] = $aParameters;
+ $this->metadataArray[$class][$name]['numberOfRequiredParameters'] = $method->getNumberOfRequiredParameters();
+ }
+ }
+
+ /**
+ * Checks that the method exists in the class
+ *
+ * @param string The class name
+ * @param string The method name
+ * @throws exception If the method is not found
+ */
+ private function checkMethodExists($className, $methodName)
+ {
+ if(!$this->isMethodAvailable($className, $methodName))
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionMethodNotFound', array($methodName,$className)));
+ }
+ }
+
+ /**
+ * Returns the number of required parameters (parameters without default values).
+ *
+ * @param string The class name
+ * @param string The method name
+ * @return int The number of required parameters
+ */
+ private function getNumberOfRequiredParameters($class, $name)
+ {
+ return $this->metadataArray[$class][$name]['numberOfRequiredParameters'];
+ }
+
+ /**
+ * Returns true if the method is found in the API of the given class name.
+ *
+ * @param string The class name
+ * @param string The method name
+ * @return bool
+ */
+ private function isMethodAvailable( $className, $methodName)
+ {
+ return isset($this->metadataArray[$className][$methodName]);
+ }
+
+ /**
+ * Checks that the class is a Singleton (presence of the getInstance() method)
+ *
+ * @param string The class name
+ * @throws exception If the class is not a Singleton
+ */
+ private function checkClassIsSingleton($className)
+ {
+ if(!method_exists($className, "getInstance"))
+ {
+ throw new Exception("Objects that provide an API must be Singleton and have a 'static public function getInstance()' method.");
+ }
+ }
+}
diff --git a/oss/piwik/trunk/core/API/Request.php b/oss/piwik/trunk/core/API/Request.php
new file mode 100644
index 00000000..dafa9604
--- /dev/null
+++ b/oss/piwik/trunk/core/API/Request.php
@@ -0,0 +1,150 @@
+process();
+ * echo $result;
+ *
+ * @see http://dev.piwik.org/trac/wiki/API
+ * @package Piwik
+ * @subpackage Piwik_API
+ */
+class Piwik_API_Request
+{
+ protected $request = null;
+
+ /**
+ * Constructs the request to the API, given the request url
+ *
+ * @param string GET request that defines the API call (must at least contain a "method" parameter)
+ * Example: method=UserSettings.getWideScreen&idSite=1&date=yesterday&period=week&format=xml
+ * If a request is not provided, then we use the $_GET and $_POST superglobal and fetch
+ * the values directly from the HTTP GET query.
+ */
+ function __construct($request = null)
+ {
+ $defaultRequest = $_GET + $_POST;
+ $requestArray = $defaultRequest;
+
+ if(!is_null($request))
+ {
+ $request = trim($request);
+ $request = str_replace(array("\n","\t"),'', $request);
+ parse_str($request, $requestArray);
+
+ $requestArray = $requestArray + $defaultRequest;
+ }
+
+ foreach($requestArray as &$element)
+ {
+ if(!is_array($element))
+ {
+ $element = trim($element);
+ }
+ }
+
+ $this->request = $requestArray;
+ }
+
+ /**
+ * Handles the request to the API.
+ * It first checks that the method called (parameter 'method') is available in the module (it means that the method exists and is public)
+ * It then reads the parameters from the request string and throws an exception if there are missing parameters.
+ * It then calls the API Proxy which will call the requested method.
+ *
+ * @return mixed The data resulting from the API call
+ */
+ public function process()
+ {
+ // read the format requested for the output data
+ $outputFormat = strtolower(Piwik_Common::getRequestVar('format', 'xml', 'string', $this->request));
+
+ // create the response
+ $response = new Piwik_API_ResponseBuilder($outputFormat, $this->request);
+
+ try {
+ // read parameters
+ $moduleMethod = Piwik_Common::getRequestVar('method', null, null, $this->request);
+
+ list($module, $method) = $this->extractModuleAndMethod($moduleMethod);
+
+ if(!Piwik_PluginsManager::getInstance()->isPluginActivated($module))
+ {
+ throw new Piwik_FrontController_PluginDeactivatedException($module);
+ }
+ $module = "Piwik_" . $module . "_API";
+
+ self::reloadAuthUsingTokenAuth($this->request);
+
+ // call the method
+ $returnedValue = Piwik_API_Proxy::getInstance()->call($module, $method, $this->request);
+
+ $toReturn = $response->getResponse($returnedValue);
+ } catch(Exception $e ) {
+ $toReturn = $response->getResponseException( $e );
+ }
+ return $toReturn;
+ }
+
+ /**
+ * If the token_auth is found in the $request parameter,
+ * the current session will be authenticated using this token_auth.
+ * It will overwrite the previous Auth object.
+ *
+ * @param $request If null, uses the default request ($_GET)
+ * @return void
+ */
+ static public function reloadAuthUsingTokenAuth($request = null)
+ {
+ // if a token_auth is specified in the API request, we load the right permissions
+ $token_auth = Piwik_Common::getRequestVar('token_auth', '', 'string', $request);
+ if($token_auth)
+ {
+ Piwik_PostEvent('API.Request.authenticate', $token_auth);
+ Zend_Registry::get('access')->reloadAccess();
+ }
+ }
+
+ /**
+ * Returns array( $class, $method) from the given string $class.$method
+ *
+ * @return array
+ * @throws exception if the name is invalid
+ */
+ private function extractModuleAndMethod($parameter)
+ {
+ $a = explode('.',$parameter);
+ if(count($a) != 2)
+ {
+ throw new Exception("The method name is invalid. Expected 'module.methodName'");
+ }
+ return $a;
+ }
+}
diff --git a/oss/piwik/trunk/core/API/ResponseBuilder.php b/oss/piwik/trunk/core/API/ResponseBuilder.php
new file mode 100644
index 00000000..533a0913
--- /dev/null
+++ b/oss/piwik/trunk/core/API/ResponseBuilder.php
@@ -0,0 +1,293 @@
+request = $request;
+ $this->outputFormat = $outputFormat;
+ }
+
+ /**
+ * This method processes the data resulting from the API call.
+ *
+ * - If the data resulted from the API call is a Piwik_DataTable then
+ * - we apply the standard filters if the parameters have been found
+ * in the URL. For example to offset,limit the Table you can add the following parameters to any API
+ * call that returns a DataTable: filter_limit=10&filter_offset=20
+ * - we apply the filters that have been previously queued on the DataTable
+ * @see Piwik_DataTable::queueFilter()
+ * - we apply the renderer that generate the DataTable in a given format (XML, PHP, HTML, JSON, etc.)
+ * the format can be changed using the 'format' parameter in the request.
+ * Example: format=xml
+ *
+ * - If there is nothing returned (void) we display a standard success message
+ *
+ * - If there is a PHP array returned, we try to convert it to a dataTable
+ * It is then possible to convert this datatable to any requested format (xml/etc)
+ *
+ * - If a bool is returned we convert to a string (true is displayed as 'true' false as 'false')
+ *
+ * - If an integer / float is returned, we simply return it
+ *
+ * @throws Exception If an object/resource is returned, if any of conversion fails, etc.
+ *
+ * @param mixed The initial returned value, before post process. If set to null, success response is returned.
+ * @return mixed Usually a string, but can still be a PHP data structure if the format requested is 'original'
+ */
+ public function getResponse($value = null)
+ {
+ // when null or void is returned from the api call, we handle it as a successful operation
+ if(!isset($value))
+ {
+ return $this->handleSuccess();
+ }
+
+ // If the returned value is an object DataTable we
+ // apply the set of generic filters if asked in the URL
+ // and we render the DataTable according to the format specified in the URL
+ if($value instanceof Piwik_DataTable
+ || $value instanceof Piwik_DataTable_Array)
+ {
+ return $this->handleDataTable($value);
+ }
+
+ // Case an array is returned from the API call, we convert it to the requested format
+ // - if calling from inside the application (format = original)
+ // => the data stays unchanged (ie. a standard php array or whatever data structure)
+ // - if any other format is requested, we have to convert this data structure (which we assume
+ // to be an array) to a DataTable in order to apply the requested DataTable_Renderer (for example XML)
+ if(is_array($value))
+ {
+ return $this->handleArray($value);
+ }
+
+ // original data structure requested, we return without process
+ if( $this->outputFormat == 'original' )
+ {
+ return $value;
+ }
+
+ if( is_object($value)
+ || is_resource($value))
+ {
+ return $this->getResponseException(new Exception('The API cannot handle this data structure.'));
+ }
+
+ // bool // integer // float // serialized object
+ return $this->handleScalar($value);
+ }
+
+ /**
+ * Returns an error $message in the requested $format
+ *
+ * @param string $message
+ * @param string $format xml/json/php/csv
+ * @return string
+ */
+ public function getResponseException(Exception $e)
+ {
+ $format = strtolower($this->outputFormat);
+
+ if( $format == 'original' )
+ {
+ throw $e;
+ }
+
+ try
+ {
+ $renderer = Piwik_DataTable_Renderer::factory($format);
+
+ } catch (Exception $e) {
+
+ return "Error: " . $e->getMessage();
+ }
+
+ $renderer->setException($e);
+
+ if($format == 'php')
+ {
+ $renderer->setSerialize($this->caseRendererPHPSerialize());
+ }
+
+ return $renderer->renderException();
+ }
+
+ /**
+ * Returns true if the user requested to serialize the output data (&serialize=1 in the request)
+ *
+ * @param $defaultSerializeValue Default value in case the user hasn't specified a value
+ * @return bool
+ */
+ protected function caseRendererPHPSerialize($defaultSerializeValue = 1)
+ {
+ $serialize = Piwik_Common::getRequestVar('serialize', $defaultSerializeValue, 'int', $this->request);
+ if($serialize)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Apply the specified renderer to the DataTable
+ *
+ * @param Piwik_DataTable
+ * @return string
+ */
+ protected function getRenderedDataTable($dataTable)
+ {
+ $format = strtolower($this->outputFormat);
+
+ // if asked for original dataStructure
+ if($format == 'original')
+ {
+ // if the original dataStructure is a simpleDataTable
+ // and has only one column, we return the value
+ if($dataTable instanceof Piwik_DataTable_Simple)
+ {
+ $columns = $dataTable->getFirstRow()->getColumns();
+ if(count($columns) == 1)
+ {
+ $values = array_values($columns);
+ return $values[0];
+ }
+ }
+
+ // by default "original" data is not serialized
+ if($this->caseRendererPHPSerialize( $defaultSerialize = 0))
+ {
+ $dataTable = serialize($dataTable);
+ }
+ return $dataTable;
+ }
+
+ $renderer = Piwik_DataTable_Renderer::factory($format);
+ $renderer->setTable($dataTable);
+ $renderer->setRenderSubTables(Piwik_Common::getRequestVar('expanded', false, 'int', $this->request));
+ if($format == 'php')
+ {
+ $renderer->setSerialize( $this->caseRendererPHPSerialize());
+ $renderer->setPrettyDisplay(Piwik_Common::getRequestVar('prettyDisplay', false, 'int', $this->request));
+ }
+ else if($format == 'html')
+ {
+ $renderer->setTableId($this->request['method']);
+ }
+ else if($format == 'csv')
+ {
+ $renderer->setConvertToUnicode( Piwik_Common::getRequestVar('convertToUnicode', true, 'int') );
+ }
+
+ return $renderer->render();
+ }
+
+ /**
+ * Returns a success $message in the requested $format
+ *
+ * @param string $format xml/json/php/csv
+ * @param string $message
+ * @return string
+ */
+ protected function handleSuccess( $message = 'ok' )
+ {
+ switch($this->outputFormat)
+ {
+ case 'xml':
+ @header("Content-Type: text/xml;charset=utf-8");
+ $return =
+ "\n" .
+ "\n".
+ "\t\n".
+ "";
+ break;
+ case 'json':
+ @header( "Content-Type: application/json" );
+ $return = '{"result":"success", "message":"'.$message.'"}';
+ break;
+ case 'php':
+ $return = array('result' => 'success', 'message' => $message);
+ if($this->caseRendererPHPSerialize())
+ {
+ $return = serialize($return);
+ }
+ break;
+
+ case 'csv':
+ header("Content-Type: application/vnd.ms-excel");
+ header("Content-Disposition: attachment; filename=piwik-report-export.csv");
+ $return = "message\n".$message;
+ break;
+
+ default:
+ $return = 'Success:'.$message;
+ break;
+ }
+ return $return;
+ }
+
+ protected function handleScalar($scalar)
+ {
+ $dataTable = new Piwik_DataTable_Simple();
+ $dataTable->addRowsFromArray( array($scalar) );
+ return $this->getRenderedDataTable($dataTable);
+ }
+
+ protected function handleDataTable($datatable)
+ {
+ // if the flag disable_generic_filters is defined we skip the generic filters
+ if('false' == Piwik_Common::getRequestVar('disable_generic_filters', 'false', 'string', $this->request))
+ {
+ $genericFilter = new Piwik_API_DataTableGenericFilter($datatable, $this->request);
+ $genericFilter->filter();
+ }
+
+ // we automatically safe decode all datatable labels (against xss)
+ $datatable->queueFilter('SafeDecodeLabel');
+
+ // if the flag disable_queued_filters is defined we skip the filters that were queued
+ if(Piwik_Common::getRequestVar('disable_queued_filters', 'false', 'string', $this->request) == 'false')
+ {
+ $datatable->applyQueuedFilters();
+ }
+ return $this->getRenderedDataTable($datatable);
+ }
+
+ protected function handleArray($array)
+ {
+ if($this->outputFormat == 'original')
+ {
+ // we handle the serialization. Because some php array have a very special structure that
+ // couldn't be converted with the automatic DataTable->addRowsFromSimpleArray
+ // the user may want to request the original PHP data structure serialized by the API
+ // in case he has to setup serialize=1 in the URL
+ if($this->caseRendererPHPSerialize( $defaultSerialize = 0))
+ {
+ return serialize($array);
+ }
+ }
+ else
+ {
+ $dataTable = new Piwik_DataTable();
+ $dataTable->addRowsFromSimpleArray($array);
+ return $this->getRenderedDataTable($dataTable);
+ }
+ }
+}