From ba55ebb573e56e5e738fe27df0ec96994fc7df3b Mon Sep 17 00:00:00 2001 From: YuCheng Hu Date: Sat, 10 Jul 2010 01:21:06 +0000 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Piwik=20=E5=88=B0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=BA=93=E4=B8=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +YUCHENG HU+ git-svn-id: https://svn.code.sf.net/p/hawebs/svn@428 a2543c7e-f6e9-4f8a-8bff-1ffc34733512 --- .../trunk/core/API/DataTableGenericFilter.php | 145 ++++++++ .../trunk/core/API/DocumentationGenerator.php | 202 +++++++++++ oss/piwik/trunk/core/API/Proxy.php | 329 ++++++++++++++++++ oss/piwik/trunk/core/API/Request.php | 150 ++++++++ oss/piwik/trunk/core/API/ResponseBuilder.php | 293 ++++++++++++++++ 5 files changed, 1119 insertions(+) create mode 100644 oss/piwik/trunk/core/API/DataTableGenericFilter.php create mode 100644 oss/piwik/trunk/core/API/DocumentationGenerator.php create mode 100644 oss/piwik/trunk/core/API/Proxy.php create mode 100644 oss/piwik/trunk/core/API/Request.php create mode 100644 oss/piwik/trunk/core/API/ResponseBuilder.php 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); + } + } +}