PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/core/API/Request.php

https://github.com/CodeYellowBV/piwik
PHP | 398 lines | 165 code | 34 blank | 199 comment | 20 complexity | 7a70b4ca27cb9b204b5186c71b8a4b43 MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\API;
  10. use Exception;
  11. use Piwik\Access;
  12. use Piwik\Common;
  13. use Piwik\DataTable;
  14. use Piwik\Piwik;
  15. use Piwik\PluginDeactivatedException;
  16. use Piwik\SettingsServer;
  17. use Piwik\Url;
  18. use Piwik\UrlHelper;
  19. /**
  20. * Dispatches API requests to the appropriate API method.
  21. *
  22. * The Request class is used throughout Piwik to call API methods. The difference
  23. * between using Request and calling API methods directly is that Request
  24. * will do more after calling the API including: applying generic filters, applying queued filters,
  25. * and handling the **flat** and **label** query parameters.
  26. *
  27. * Additionally, the Request class will **forward current query parameters** to the request
  28. * which is more convenient than calling {@link Piwik\Common::getRequestVar()} many times over.
  29. *
  30. * In most cases, using a Request object to query the API is the correct approach.
  31. *
  32. * ### Post-processing
  33. *
  34. * The return value of API methods undergo some extra processing before being returned by Request.
  35. * To learn more about what happens to API results, read [this](/guides/piwiks-web-api#extra-report-processing).
  36. *
  37. * ### Output Formats
  38. *
  39. * The value returned by Request will be serialized to a certain format before being returned.
  40. * To see the list of supported output formats, read [this](/guides/piwiks-web-api#output-formats).
  41. *
  42. * ### Examples
  43. *
  44. * **Basic Usage**
  45. *
  46. * $request = new Request('method=UserSettings.getWideScreen&idSite=1&date=yesterday&period=week'
  47. * . '&format=xml&filter_limit=5&filter_offset=0')
  48. * $result = $request->process();
  49. * echo $result;
  50. *
  51. * **Getting a unrendered DataTable**
  52. *
  53. * // use the convenience method 'processRequest'
  54. * $dataTable = Request::processRequest('UserSettings.getWideScreen', array(
  55. * 'idSite' => 1,
  56. * 'date' => 'yesterday',
  57. * 'period' => 'week',
  58. * 'filter_limit' => 5,
  59. * 'filter_offset' => 0
  60. *
  61. * 'format' => 'original', // this is the important bit
  62. * ));
  63. * echo "This DataTable has " . $dataTable->getRowsCount() . " rows.";
  64. *
  65. * @see http://piwik.org/docs/analytics-api
  66. * @api
  67. */
  68. class Request
  69. {
  70. protected $request = null;
  71. /**
  72. * Converts the supplied request string into an array of query paramater name/value
  73. * mappings. The current query parameters (everything in `$_GET` and `$_POST`) are
  74. * forwarded to request array before it is returned.
  75. *
  76. * @param string|array $request The base request string or array, eg,
  77. * `'module=UserSettings&action=getWidescreen'`.
  78. * @return array
  79. */
  80. static public function getRequestArrayFromString($request)
  81. {
  82. $defaultRequest = $_GET + $_POST;
  83. $requestRaw = self::getRequestParametersGET();
  84. if (!empty($requestRaw['segment'])) {
  85. $defaultRequest['segment'] = $requestRaw['segment'];
  86. }
  87. $requestArray = $defaultRequest;
  88. if (!is_null($request)) {
  89. if (is_array($request)) {
  90. $url = array();
  91. foreach ($request as $key => $value) {
  92. $url[] = $key . "=" . $value;
  93. }
  94. $request = implode("&", $url);
  95. }
  96. $request = trim($request);
  97. $request = str_replace(array("\n", "\t"), '', $request);
  98. $requestParsed = UrlHelper::getArrayFromQueryString($request);
  99. $requestArray = $requestParsed + $defaultRequest;
  100. }
  101. foreach ($requestArray as &$element) {
  102. if (!is_array($element)) {
  103. $element = trim($element);
  104. }
  105. }
  106. return $requestArray;
  107. }
  108. /**
  109. * Constructor.
  110. *
  111. * @param string|array $request Query string that defines the API call (must at least contain a **method** parameter),
  112. * eg, `'method=UserSettings.getWideScreen&idSite=1&date=yesterday&period=week&format=xml'`
  113. * If a request is not provided, then we use the values in the `$_GET` and `$_POST`
  114. * superglobals.
  115. */
  116. public function __construct($request = null)
  117. {
  118. $this->request = self::getRequestArrayFromString($request);
  119. $this->sanitizeRequest();
  120. }
  121. /**
  122. * For backward compatibility: Piwik API still works if module=Referers,
  123. * we rewrite to correct renamed plugin: Referrers
  124. *
  125. * @param $module
  126. * @return string
  127. * @ignore
  128. */
  129. public static function renameModule($module)
  130. {
  131. $moduleToRedirect = array(
  132. 'Referers' => 'Referrers',
  133. 'PDFReports' => 'ScheduledReports',
  134. );
  135. if (isset($moduleToRedirect[$module])) {
  136. return $moduleToRedirect[$module];
  137. }
  138. return $module;
  139. }
  140. /**
  141. * Make sure that the request contains no logical errors
  142. */
  143. private function sanitizeRequest()
  144. {
  145. // The label filter does not work with expanded=1 because the data table IDs have a different meaning
  146. // depending on whether the table has been loaded yet. expanded=1 causes all tables to be loaded, which
  147. // is why the label filter can't descend when a recursive label has been requested.
  148. // To fix this, we remove the expanded parameter if a label parameter is set.
  149. if (isset($this->request['label']) && !empty($this->request['label'])
  150. && isset($this->request['expanded']) && $this->request['expanded']
  151. ) {
  152. unset($this->request['expanded']);
  153. }
  154. }
  155. /**
  156. * Dispatches the API request to the appropriate API method and returns the result
  157. * after post-processing.
  158. *
  159. * Post-processing includes:
  160. *
  161. * - flattening if **flat** is 0
  162. * - running generic filters unless **disable_generic_filters** is set to 1
  163. * - URL decoding label column values
  164. * - running queued filters unless **disable_queued_filters** is set to 1
  165. * - removing columns based on the values of the **hideColumns** and **showColumns** query parameters
  166. * - filtering rows if the **label** query parameter is set
  167. * - converting the result to the appropriate format (ie, XML, JSON, etc.)
  168. *
  169. * If `'original'` is supplied for the output format, the result is returned as a PHP
  170. * object.
  171. *
  172. * @throws PluginDeactivatedException if the module plugin is not activated.
  173. * @throws Exception if the requested API method cannot be called, if required parameters for the
  174. * API method are missing or if the API method throws an exception and the **format**
  175. * query parameter is **original**.
  176. * @return DataTable|Map|string The data resulting from the API call.
  177. */
  178. public function process()
  179. {
  180. // read the format requested for the output data
  181. $outputFormat = strtolower(Common::getRequestVar('format', 'xml', 'string', $this->request));
  182. // create the response
  183. $response = new ResponseBuilder($outputFormat, $this->request);
  184. try {
  185. // read parameters
  186. $moduleMethod = Common::getRequestVar('method', null, 'string', $this->request);
  187. list($module, $method) = $this->extractModuleAndMethod($moduleMethod);
  188. $module = $this->renameModule($module);
  189. if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated($module)) {
  190. throw new PluginDeactivatedException($module);
  191. }
  192. $apiClassName = $this->getClassNameAPI($module);
  193. self::reloadAuthUsingTokenAuth($this->request);
  194. // call the method
  195. $returnedValue = Proxy::getInstance()->call($apiClassName, $method, $this->request);
  196. $toReturn = $response->getResponse($returnedValue, $module, $method);
  197. } catch (Exception $e) {
  198. $toReturn = $response->getResponseException($e);
  199. }
  200. return $toReturn;
  201. }
  202. /**
  203. * Returns the name of a plugin's API class by plugin name.
  204. *
  205. * @param string $plugin The plugin name, eg, `'Referrers'`.
  206. * @return string The fully qualified API class name, eg, `'\Piwik\Plugins\Referrers\API'`.
  207. */
  208. static public function getClassNameAPI($plugin)
  209. {
  210. return sprintf('\Piwik\Plugins\%s\API', $plugin);
  211. }
  212. /**
  213. * If the token_auth is found in the $request parameter,
  214. * the current session will be authenticated using this token_auth.
  215. * It will overwrite the previous Auth object.
  216. *
  217. * @param array $request If null, uses the default request ($_GET)
  218. * @return void
  219. * @ignore
  220. */
  221. static public function reloadAuthUsingTokenAuth($request = null)
  222. {
  223. // if a token_auth is specified in the API request, we load the right permissions
  224. $token_auth = Common::getRequestVar('token_auth', '', 'string', $request);
  225. if ($token_auth) {
  226. /**
  227. * Triggered when authenticating an API request, but only if the **token_auth**
  228. * query parameter is found in the request.
  229. *
  230. * Plugins that provide authentication capabilities should subscribe to this event
  231. * and make sure the global authentication object (the object returned by `Registry::get('auth')`)
  232. * is setup to use `$token_auth` when its `authenticate()` method is executed.
  233. *
  234. * @param string $token_auth The value of the **token_auth** query parameter.
  235. */
  236. Piwik::postEvent('API.Request.authenticate', array($token_auth));
  237. Access::getInstance()->reloadAccess();
  238. SettingsServer::raiseMemoryLimitIfNecessary();
  239. }
  240. }
  241. /**
  242. * Returns array($class, $method) from the given string $class.$method
  243. *
  244. * @param string $parameter
  245. * @throws Exception
  246. * @return array
  247. */
  248. private function extractModuleAndMethod($parameter)
  249. {
  250. $a = explode('.', $parameter);
  251. if (count($a) != 2) {
  252. throw new Exception("The method name is invalid. Expected 'module.methodName'");
  253. }
  254. return $a;
  255. }
  256. /**
  257. * Helper method that processes an API request in one line using the variables in `$_GET`
  258. * and `$_POST`.
  259. *
  260. * @param string $method The API method to call, ie, `'Actions.getPageTitles'`.
  261. * @param array $paramOverride The parameter name-value pairs to use instead of what's
  262. * in `$_GET` & `$_POST`.
  263. * @return mixed The result of the API request. See {@link process()}.
  264. */
  265. public static function processRequest($method, $paramOverride = array())
  266. {
  267. $params = array();
  268. $params['format'] = 'original';
  269. $params['module'] = 'API';
  270. $params['method'] = $method;
  271. $params = $paramOverride + $params;
  272. // process request
  273. $request = new Request($params);
  274. return $request->process();
  275. }
  276. /**
  277. * Returns the original request parameters in the current query string as an array mapping
  278. * query parameter names with values. The result of this function will not be affected
  279. * by any modifications to `$_GET` and will not include parameters in `$_POST`.
  280. *
  281. * @return array
  282. */
  283. public static function getRequestParametersGET()
  284. {
  285. if (empty($_SERVER['QUERY_STRING'])) {
  286. return array();
  287. }
  288. $GET = UrlHelper::getArrayFromQueryString($_SERVER['QUERY_STRING']);
  289. return $GET;
  290. }
  291. /**
  292. * Returns the URL for the current requested report w/o any filter parameters.
  293. *
  294. * @param string $module The API module.
  295. * @param string $action The API action.
  296. * @param array $queryParams Query parameter overrides.
  297. * @return string
  298. */
  299. public static function getBaseReportUrl($module, $action, $queryParams = array())
  300. {
  301. $params = array_merge($queryParams, array('module' => $module, 'action' => $action));
  302. return Request::getCurrentUrlWithoutGenericFilters($params);
  303. }
  304. /**
  305. * Returns the current URL without generic filter query parameters.
  306. *
  307. * @param array $params Query parameter values to override in the new URL.
  308. * @return string
  309. */
  310. public static function getCurrentUrlWithoutGenericFilters($params)
  311. {
  312. // unset all filter query params so the related report will show up in its default state,
  313. // unless the filter param was in $queryParams
  314. $genericFiltersInfo = DataTableGenericFilter::getGenericFiltersInformation();
  315. foreach ($genericFiltersInfo as $filter) {
  316. foreach ($filter[1] as $queryParamName => $queryParamInfo) {
  317. if (!isset($params[$queryParamName])) {
  318. $params[$queryParamName] = null;
  319. }
  320. }
  321. }
  322. return Url::getCurrentQueryStringWithParametersModified($params);
  323. }
  324. /**
  325. * Returns whether the DataTable result will have to be expanded for the
  326. * current request before rendering.
  327. *
  328. * @return bool
  329. * @ignore
  330. */
  331. public static function shouldLoadExpanded()
  332. {
  333. // if filter_column_recursive & filter_pattern_recursive are supplied, and flat isn't supplied
  334. // we have to load all the child subtables.
  335. return Common::getRequestVar('filter_column_recursive', false) !== false
  336. && Common::getRequestVar('filter_pattern_recursive', false) !== false
  337. && !self::shouldLoadFlatten();
  338. }
  339. /**
  340. * @return bool
  341. */
  342. public static function shouldLoadFlatten()
  343. {
  344. return Common::getRequestVar('flat', false) == 1;
  345. }
  346. /**
  347. * Returns the segment query parameter from the original request, without modifications.
  348. *
  349. * @return array|bool
  350. */
  351. static public function getRawSegmentFromRequest()
  352. {
  353. // we need the URL encoded segment parameter, we fetch it from _SERVER['QUERY_STRING'] instead of default URL decoded _GET
  354. $segmentRaw = false;
  355. $segment = Common::getRequestVar('segment', '', 'string');
  356. if (!empty($segment)) {
  357. $request = Request::getRequestParametersGET();
  358. if (!empty($request['segment'])) {
  359. $segmentRaw = $request['segment'];
  360. }
  361. }
  362. return $segmentRaw;
  363. }
  364. }