PageRenderTime 855ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/API/API.php

https://github.com/CodeYellowBV/piwik
PHP | 705 lines | 431 code | 67 blank | 207 comment | 43 complexity | d13821d4ba0a2695745d9b8a01663f00 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\Plugins\API;
  10. use Piwik\API\Proxy;
  11. use Piwik\API\Request;
  12. use Piwik\Config;
  13. use Piwik\DataTable;
  14. use Piwik\DataTable\Filter\ColumnDelete;
  15. use Piwik\DataTable\Row;
  16. use Piwik\Date;
  17. use Piwik\Menu\MenuTop;
  18. use Piwik\Metrics;
  19. use Piwik\Period;
  20. use Piwik\Period\Range;
  21. use Piwik\Piwik;
  22. use Piwik\Plugins\CoreAdminHome\CustomLogo;
  23. use Piwik\Tracker\GoalManager;
  24. use Piwik\Translate;
  25. use Piwik\Version;
  26. require_once PIWIK_INCLUDE_PATH . '/core/Config.php';
  27. /**
  28. * 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
  29. * human readable and more complete outputs than normal API methods.
  30. *
  31. * Some of the information that is returned by the Metadata API:
  32. * <ul>
  33. * <li>the dynamically generated list of all API methods via "getReportMetadata"</li>
  34. * <li>the list of metrics that will be returned by each method, along with their human readable name, via "getDefaultMetrics" and "getDefaultProcessedMetrics"</li>
  35. * <li>the list of segments metadata supported by all functions that have a 'segment' parameter</li>
  36. * <li>the (truly magic) method "getProcessedReport" will return a human readable version of any other report, and include the processed metrics such as
  37. * conversion rate, time on site, etc. which are not directly available in other methods.</li>
  38. * <li>the method "getSuggestedValuesForSegment" returns top suggested values for a particular segment. It uses the Live.getLastVisitsDetails API to fetch the most recently used values, and will return the most often used values first.</li>
  39. * </ul>
  40. * 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.
  41. * More information on the <a href='http://piwik.org/docs/analytics-api/metadata/' target='_blank'>Metadata API documentation page</a>
  42. *
  43. * @method static \Piwik\Plugins\API\API getInstance()
  44. */
  45. class API extends \Piwik\Plugin\API
  46. {
  47. /**
  48. * Get Piwik version
  49. * @return string
  50. */
  51. public function getPiwikVersion()
  52. {
  53. Piwik::checkUserHasSomeViewAccess();
  54. return Version::VERSION;
  55. }
  56. /**
  57. * Returns the section [APISettings] if defined in config.ini.php
  58. * @return array
  59. */
  60. public function getSettings()
  61. {
  62. return Config::getInstance()->APISettings;
  63. }
  64. /**
  65. * Default translations for many core metrics.
  66. * This is used for exports with translated labels. The exports contain columns that
  67. * are not visible in the UI and not present in the API meta data. These columns are
  68. * translated here.
  69. * @return array
  70. */
  71. static public function getDefaultMetricTranslations()
  72. {
  73. return Metrics::getDefaultMetricTranslations();
  74. }
  75. public function getSegmentsMetadata($idSites = array(), $_hideImplementationData = true)
  76. {
  77. $segments = array();
  78. /**
  79. * Triggered when gathering all available segment dimensions.
  80. *
  81. * This event can be used to make new segment dimensions available.
  82. *
  83. * **Example**
  84. *
  85. * public function getSegmentsMetadata(&$segments, $idSites)
  86. * {
  87. * $segments[] = array(
  88. * 'type' => 'dimension',
  89. * 'category' => Piwik::translate('General_Visit'),
  90. * 'name' => 'General_VisitorIP',
  91. * 'segment' => 'visitIp',
  92. * 'acceptedValues' => '13.54.122.1, etc.',
  93. * 'sqlSegment' => 'log_visit.location_ip',
  94. * 'sqlFilter' => array('Piwik\IP', 'P2N'),
  95. * 'permission' => $isAuthenticatedWithViewAccess,
  96. * );
  97. * }
  98. *
  99. * @param array &$dimensions The list of available segment dimensions. Append to this list to add
  100. * new segments. Each element in this list must contain the
  101. * following information:
  102. *
  103. * - **type**: Either `'metric'` or `'dimension'`. `'metric'` means
  104. * the value is a numeric and `'dimension'` means it is
  105. * a string. Also, `'metric'` values will be displayed
  106. * under **Visit (metrics)** in the Segment Editor.
  107. * - **category**: The segment category name. This can be an existing
  108. * segment category visible in the segment editor.
  109. * - **name**: The pretty name of the segment. Can be a translation token.
  110. * - **segment**: The segment name, eg, `'visitIp'` or `'searches'`.
  111. * - **acceptedValues**: A string describing one or two exacmple values, eg
  112. * `'13.54.122.1, etc.'`.
  113. * - **sqlSegment**: The table column this segment will segment by.
  114. * For example, `'log_visit.location_ip'` for the
  115. * **visitIp** segment.
  116. * - **sqlFilter**: A PHP callback to apply to segment values before
  117. * they are used in SQL.
  118. * - **permission**: True if the current user has view access to this
  119. * segment, false if otherwise.
  120. * @param array $idSites The list of site IDs we're getting the available segments
  121. * for. Some segments (such as Goal segments) depend on the
  122. * site.
  123. */
  124. Piwik::postEvent('API.getSegmentDimensionMetadata', array(&$segments, $idSites));
  125. $isAuthenticatedWithViewAccess = Piwik::isUserHasViewAccess($idSites) && !Piwik::isUserIsAnonymous();
  126. $segments[] = array(
  127. 'type' => 'dimension',
  128. 'category' => Piwik::translate('General_Visit'),
  129. 'name' => 'General_VisitorID',
  130. 'segment' => 'visitorId',
  131. 'acceptedValues' => '34c31e04394bdc63 - any 16 Hexadecimal chars ID, which can be fetched using the Tracking API function getVisitorId()',
  132. 'sqlSegment' => 'log_visit.idvisitor',
  133. 'sqlFilterValue' => array('Piwik\Common', 'convertVisitorIdToBin'),
  134. 'permission' => $isAuthenticatedWithViewAccess,
  135. );
  136. $segments[] = array(
  137. 'type' => 'dimension',
  138. 'category' => Piwik::translate('General_Visit'),
  139. 'name' => Piwik::translate('General_Visit') . " ID",
  140. 'segment' => 'visitId',
  141. 'acceptedValues' => 'Any integer. ',
  142. 'sqlSegment' => 'log_visit.idvisit',
  143. 'permission' => $isAuthenticatedWithViewAccess,
  144. );
  145. $segments[] = array(
  146. 'type' => 'metric',
  147. 'category' => Piwik::translate('General_Visit'),
  148. 'name' => 'General_VisitorIP',
  149. 'segment' => 'visitIp',
  150. 'acceptedValues' => '13.54.122.1. </code>Select IP ranges with notation: <code>visitIp>13.54.122.0;visitIp<13.54.122.255',
  151. 'sqlSegment' => 'log_visit.location_ip',
  152. 'sqlFilterValue' => array('Piwik\IP', 'P2N'),
  153. 'permission' => $isAuthenticatedWithViewAccess,
  154. );
  155. $segments[] = array(
  156. 'type' => 'metric',
  157. 'category' => Piwik::translate('General_Visit'),
  158. 'name' => 'General_NbActions',
  159. 'segment' => 'actions',
  160. 'sqlSegment' => 'log_visit.visit_total_actions',
  161. );
  162. $segments[] = array(
  163. 'type' => 'metric',
  164. 'category' => Piwik::translate('General_Visit'),
  165. 'name' => 'General_NbSearches',
  166. 'segment' => 'searches',
  167. 'sqlSegment' => 'log_visit.visit_total_searches',
  168. 'acceptedValues' => 'To select all visits who used internal Site Search, use: &segment=searches>0',
  169. );
  170. $segments[] = array(
  171. 'type' => 'metric',
  172. 'category' => Piwik::translate('General_Visit'),
  173. 'name' => 'General_ColumnVisitDuration',
  174. 'segment' => 'visitDuration',
  175. 'sqlSegment' => 'log_visit.visit_total_time',
  176. );
  177. $segments[] = array(
  178. 'type' => 'dimension',
  179. 'category' => Piwik::translate('General_Visit'),
  180. 'name' => Piwik::translate('General_VisitType'),
  181. 'segment' => 'visitorType',
  182. 'acceptedValues' => 'new, returning, returningCustomer' . ". " . Piwik::translate('General_VisitTypeExample', '"&segment=visitorType==returning,visitorType==returningCustomer"'),
  183. 'sqlSegment' => 'log_visit.visitor_returning',
  184. 'sqlFilterValue' => function ($type) {
  185. return $type == "new" ? 0 : ($type == "returning" ? 1 : 2);
  186. }
  187. );
  188. $segments[] = array(
  189. 'type' => 'metric',
  190. 'category' => Piwik::translate('General_Visit'),
  191. 'name' => 'General_DaysSinceLastVisit',
  192. 'segment' => 'daysSinceLastVisit',
  193. 'sqlSegment' => 'log_visit.visitor_days_since_last',
  194. );
  195. $segments[] = array(
  196. 'type' => 'metric',
  197. 'category' => Piwik::translate('General_Visit'),
  198. 'name' => 'General_DaysSinceFirstVisit',
  199. 'segment' => 'daysSinceFirstVisit',
  200. 'sqlSegment' => 'log_visit.visitor_days_since_first',
  201. );
  202. $segments[] = array(
  203. 'type' => 'metric',
  204. 'category' => Piwik::translate('General_Visit'),
  205. 'name' => 'General_NumberOfVisits',
  206. 'segment' => 'visitCount',
  207. 'sqlSegment' => 'log_visit.visitor_count_visits',
  208. );
  209. $segments[] = array(
  210. 'type' => 'dimension',
  211. 'category' => Piwik::translate('General_Visit'),
  212. 'name' => 'General_VisitConvertedGoal',
  213. 'segment' => 'visitConverted',
  214. 'acceptedValues' => '0, 1',
  215. 'sqlSegment' => 'log_visit.visit_goal_converted',
  216. );
  217. $segments[] = array(
  218. 'type' => 'dimension',
  219. 'category' => Piwik::translate('General_Visit'),
  220. 'name' => Piwik::translate('General_EcommerceVisitStatusDesc'),
  221. 'segment' => 'visitEcommerceStatus',
  222. 'acceptedValues' => implode(", ", self::$visitEcommerceStatus)
  223. . '. ' . Piwik::translate('General_EcommerceVisitStatusEg', '"&segment=visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart"'),
  224. 'sqlSegment' => 'log_visit.visit_goal_buyer',
  225. 'sqlFilterValue' => __NAMESPACE__ . '\API::getVisitEcommerceStatus',
  226. );
  227. $segments[] = array(
  228. 'type' => 'metric',
  229. 'category' => Piwik::translate('General_Visit'),
  230. 'name' => 'General_DaysSinceLastEcommerceOrder',
  231. 'segment' => 'daysSinceLastEcommerceOrder',
  232. 'sqlSegment' => 'log_visit.visitor_days_since_order',
  233. );
  234. foreach ($segments as &$segment) {
  235. $segment['name'] = Piwik::translate($segment['name']);
  236. $segment['category'] = Piwik::translate($segment['category']);
  237. if ($_hideImplementationData) {
  238. unset($segment['sqlFilter']);
  239. unset($segment['sqlFilterValue']);
  240. unset($segment['sqlSegment']);
  241. }
  242. }
  243. usort($segments, array($this, 'sortSegments'));
  244. return $segments;
  245. }
  246. static protected $visitEcommerceStatus = array(
  247. GoalManager::TYPE_BUYER_NONE => 'none',
  248. GoalManager::TYPE_BUYER_ORDERED => 'ordered',
  249. GoalManager::TYPE_BUYER_OPEN_CART => 'abandonedCart',
  250. GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART => 'orderedThenAbandonedCart',
  251. );
  252. /**
  253. * @ignore
  254. */
  255. static public function getVisitEcommerceStatusFromId($id)
  256. {
  257. if (!isset(self::$visitEcommerceStatus[$id])) {
  258. throw new \Exception("Unexpected ECommerce status value ");
  259. }
  260. return self::$visitEcommerceStatus[$id];
  261. }
  262. /**
  263. * @ignore
  264. */
  265. static public function getVisitEcommerceStatus($status)
  266. {
  267. $id = array_search($status, self::$visitEcommerceStatus);
  268. if ($id === false) {
  269. throw new \Exception("Invalid 'visitEcommerceStatus' segment value $status");
  270. }
  271. return $id;
  272. }
  273. private function sortSegments($row1, $row2)
  274. {
  275. $columns = array('type', 'category', 'name', 'segment');
  276. foreach ($columns as $column) {
  277. // Keep segments ordered alphabetically inside categories..
  278. $type = -1;
  279. if ($column == 'name') $type = 1;
  280. $compare = $type * strcmp($row1[$column], $row2[$column]);
  281. // hack so that custom variables "page" are grouped together in the doc
  282. if ($row1['category'] == Piwik::translate('CustomVariables_CustomVariables')
  283. && $row1['category'] == $row2['category']
  284. ) {
  285. $compare = strcmp($row1['segment'], $row2['segment']);
  286. return $compare;
  287. }
  288. if ($compare != 0) {
  289. return $compare;
  290. }
  291. }
  292. return $compare;
  293. }
  294. /**
  295. * Returns the url to application logo (~280x110px)
  296. *
  297. * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
  298. * @return string
  299. */
  300. public function getLogoUrl($pathOnly = false)
  301. {
  302. $logo = new CustomLogo();
  303. return $logo->getLogoUrl($pathOnly);
  304. }
  305. /**
  306. * Returns the url to header logo (~127x50px)
  307. *
  308. * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
  309. * @return string
  310. */
  311. public function getHeaderLogoUrl($pathOnly = false)
  312. {
  313. $logo = new CustomLogo();
  314. return $logo->getHeaderLogoUrl($pathOnly);
  315. }
  316. /**
  317. * Returns the URL to application SVG Logo
  318. *
  319. * @ignore
  320. * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL.
  321. * @return string
  322. */
  323. public function getSVGLogoUrl($pathOnly = false)
  324. {
  325. $logo = new CustomLogo();
  326. return $logo->getSVGLogoUrl($pathOnly);
  327. }
  328. /**
  329. * Returns whether there is an SVG Logo available.
  330. * @ignore
  331. * @return bool
  332. */
  333. public function hasSVGLogo()
  334. {
  335. $logo = new CustomLogo();
  336. return $logo->hasSVGLogo();
  337. }
  338. /**
  339. * Loads reports metadata, then return the requested one,
  340. * matching optional API parameters.
  341. */
  342. public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false,
  343. $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false)
  344. {
  345. Translate::reloadLanguage($language);
  346. $reporter = new ProcessedReport();
  347. $metadata = $reporter->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language, $period, $date, $hideMetricsDoc, $showSubtableReports);
  348. return $metadata;
  349. }
  350. /**
  351. * Triggers a hook to ask plugins for available Reports.
  352. * Returns metadata information about each report (category, name, dimension, metrics, etc.)
  353. *
  354. * @param string $idSites Comma separated list of website Ids
  355. * @param bool|string $period
  356. * @param bool|Date $date
  357. * @param bool $hideMetricsDoc
  358. * @param bool $showSubtableReports
  359. * @return array
  360. */
  361. public function getReportMetadata($idSites = '', $period = false, $date = false, $hideMetricsDoc = false,
  362. $showSubtableReports = false)
  363. {
  364. $reporter = new ProcessedReport();
  365. $metadata = $reporter->getReportMetadata($idSites, $period, $date, $hideMetricsDoc, $showSubtableReports);
  366. return $metadata;
  367. }
  368. public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false,
  369. $apiParameters = false, $idGoal = false, $language = false,
  370. $showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false)
  371. {
  372. $reporter = new ProcessedReport();
  373. $processed = $reporter->getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment,
  374. $apiParameters, $idGoal, $language, $showTimer, $hideMetricsDoc, $idSubtable, $showRawMetrics);
  375. return $processed;
  376. }
  377. /**
  378. * Get a combined report of the *.get API methods.
  379. */
  380. public function get($idSite, $period, $date, $segment = false, $columns = false)
  381. {
  382. $columns = Piwik::getArrayFromApiParameter($columns);
  383. // build columns map for faster checks later on
  384. $columnsMap = array();
  385. foreach ($columns as $column) {
  386. $columnsMap[$column] = true;
  387. }
  388. // find out which columns belong to which plugin
  389. $columnsByPlugin = array();
  390. $meta = \Piwik\Plugins\API\API::getInstance()->getReportMetadata($idSite, $period, $date);
  391. foreach ($meta as $reportMeta) {
  392. // scan all *.get reports
  393. if ($reportMeta['action'] == 'get'
  394. && !isset($reportMeta['parameters'])
  395. && $reportMeta['module'] != 'API'
  396. && !empty($reportMeta['metrics'])
  397. ) {
  398. $plugin = $reportMeta['module'];
  399. foreach ($reportMeta['metrics'] as $column => $columnTranslation) {
  400. // a metric from this report has been requested
  401. if (isset($columnsMap[$column])
  402. // or by default, return all metrics
  403. || empty($columnsMap)
  404. ) {
  405. $columnsByPlugin[$plugin][] = $column;
  406. }
  407. }
  408. }
  409. }
  410. krsort($columnsByPlugin);
  411. $mergedDataTable = false;
  412. $params = compact('idSite', 'period', 'date', 'segment', 'idGoal');
  413. foreach ($columnsByPlugin as $plugin => $columns) {
  414. // load the data
  415. $className = Request::getClassNameAPI($plugin);
  416. $params['columns'] = implode(',', $columns);
  417. $dataTable = Proxy::getInstance()->call($className, 'get', $params);
  418. // make sure the table has all columns
  419. $array = ($dataTable instanceof DataTable\Map ? $dataTable->getDataTables() : array($dataTable));
  420. foreach ($array as $table) {
  421. // we don't support idSites=all&date=DATE1,DATE2
  422. if ($table instanceof DataTable) {
  423. $firstRow = $table->getFirstRow();
  424. if (!$firstRow) {
  425. $firstRow = new Row;
  426. $table->addRow($firstRow);
  427. }
  428. foreach ($columns as $column) {
  429. if ($firstRow->getColumn($column) === false) {
  430. $firstRow->setColumn($column, 0);
  431. }
  432. }
  433. }
  434. }
  435. // merge reports
  436. if ($mergedDataTable === false) {
  437. $mergedDataTable = $dataTable;
  438. } else {
  439. $this->mergeDataTables($mergedDataTable, $dataTable);
  440. }
  441. }
  442. return $mergedDataTable;
  443. }
  444. /**
  445. * Merge the columns of two data tables.
  446. * Manipulates the first table.
  447. */
  448. private function mergeDataTables($table1, $table2)
  449. {
  450. // handle table arrays
  451. if ($table1 instanceof DataTable\Map && $table2 instanceof DataTable\Map) {
  452. $subTables2 = $table2->getDataTables();
  453. foreach ($table1->getDataTables() as $index => $subTable1) {
  454. $subTable2 = $subTables2[$index];
  455. $this->mergeDataTables($subTable1, $subTable2);
  456. }
  457. return;
  458. }
  459. $firstRow1 = $table1->getFirstRow();
  460. $firstRow2 = $table2->getFirstRow();
  461. if ($firstRow2 instanceof Row) {
  462. foreach ($firstRow2->getColumns() as $metric => $value) {
  463. $firstRow1->setColumn($metric, $value);
  464. }
  465. }
  466. }
  467. /**
  468. * Given an API report to query (eg. "Referrers.getKeywords", and a Label (eg. "free%20software"),
  469. * this function will query the API for the previous days/weeks/etc. and will return
  470. * a ready to use data structure containing the metrics for the requested Label, along with enriched information (min/max values, etc.)
  471. *
  472. * @param int $idSite
  473. * @param string $period
  474. * @param Date $date
  475. * @param string $apiModule
  476. * @param string $apiAction
  477. * @param bool|string $label
  478. * @param bool|string $segment
  479. * @param bool|string $column
  480. * @param bool|string $language
  481. * @param bool|int $idGoal
  482. * @param bool|string $legendAppendMetric
  483. * @param bool|string $labelUseAbsoluteUrl
  484. * @return array
  485. */
  486. public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true)
  487. {
  488. $rowEvolution = new RowEvolution();
  489. return $rowEvolution->getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label, $segment, $column,
  490. $language, $idGoal, $legendAppendMetric, $labelUseAbsoluteUrl);
  491. }
  492. public function getLastDate($date, $period)
  493. {
  494. $lastDate = Range::getLastDate($date, $period);
  495. return array_shift($lastDate);
  496. }
  497. /**
  498. * Performs multiple API requests at once and returns every result.
  499. *
  500. * @param array $urls The array of API requests.
  501. * @return array
  502. */
  503. public function getBulkRequest($urls)
  504. {
  505. if (empty($urls)) {
  506. return array();
  507. }
  508. $urls = array_map('urldecode', $urls);
  509. $urls = array_map(array('Piwik\Common', 'unsanitizeInputValue'), $urls);
  510. $result = array();
  511. foreach ($urls as $url) {
  512. $req = new Request($url . '&format=php&serialize=0');
  513. $result[] = $req->process();
  514. }
  515. return $result;
  516. }
  517. /**
  518. * Given a segment, will return a list of the most used values for this particular segment.
  519. * @param $segmentName
  520. * @param $idSite
  521. * @throws \Exception
  522. * @return array
  523. */
  524. public function getSuggestedValuesForSegment($segmentName, $idSite)
  525. {
  526. if(empty(Config::getInstance()->General['enable_segment_suggested_values'])) {
  527. return array();
  528. }
  529. Piwik::checkUserHasViewAccess($idSite);
  530. $maxSuggestionsToReturn = 30;
  531. $segmentsMetadata = $this->getSegmentsMetadata($idSite, $_hideImplementationData = false);
  532. $segmentFound = false;
  533. foreach ($segmentsMetadata as $segmentMetadata) {
  534. if ($segmentMetadata['segment'] == $segmentName) {
  535. $segmentFound = $segmentMetadata;
  536. break;
  537. }
  538. }
  539. if (empty($segmentFound)) {
  540. throw new \Exception("Requested segment not found.");
  541. }
  542. // if period=range is disabled, do not proceed
  543. if(!Period\Factory::isPeriodEnabledForAPI('range')) {
  544. return array();
  545. }
  546. $startDate = Date::now()->subDay(60)->toString();
  547. $requestLastVisits = "method=Live.getLastVisitsDetails
  548. &idSite=$idSite
  549. &period=range
  550. &date=$startDate,today
  551. &format=original
  552. &serialize=0
  553. &flat=1";
  554. // Select non empty fields only
  555. // Note: this optimization has only a very minor impact
  556. $requestLastVisits .= "&segment=$segmentName" . urlencode('!=');
  557. // By default Live fetches all actions for all visitors, but we'd rather do this only when required
  558. if ($this->doesSegmentNeedActionsData($segmentName)) {
  559. $requestLastVisits .= "&filter_limit=400";
  560. } else {
  561. $requestLastVisits .= "&doNotFetchActions=1";
  562. $requestLastVisits .= "&filter_limit=800";
  563. }
  564. $request = new Request($requestLastVisits);
  565. $table = $request->process();
  566. if (empty($table)) {
  567. throw new \Exception("There was no data to suggest for $segmentName");
  568. }
  569. // Cleanup data to return the top suggested (non empty) labels for this segment
  570. $values = $table->getColumn($segmentName);
  571. // Select also flattened keys (custom variables "page" scope, page URLs for one visit, page titles for one visit)
  572. $valuesBis = $table->getColumnsStartingWith($segmentName . ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP);
  573. $values = array_merge($values, $valuesBis);
  574. $values = $this->getMostFrequentValues($values);
  575. $values = array_slice($values, 0, $maxSuggestionsToReturn);
  576. $values = array_map(array('Piwik\Common', 'unsanitizeInputValue'), $values);
  577. return $values;
  578. }
  579. /**
  580. * @param $segmentName
  581. * @return bool
  582. */
  583. protected function doesSegmentNeedActionsData($segmentName)
  584. {
  585. // If you update this, also update flattenVisitorDetailsArray
  586. $segmentsNeedActionsInfo = array('visitConvertedGoalId',
  587. 'pageUrl', 'pageTitle', 'siteSearchKeyword',
  588. 'entryPageTitle', 'entryPageUrl', 'exitPageTitle', 'exitPageUrl');
  589. $isCustomVariablePage = stripos($segmentName, 'customVariablePage') !== false;
  590. $isEventSegment = stripos($segmentName, 'event') !== false;
  591. $doesSegmentNeedActionsInfo = in_array($segmentName, $segmentsNeedActionsInfo) || $isCustomVariablePage || $isEventSegment;
  592. return $doesSegmentNeedActionsInfo;
  593. }
  594. /**
  595. * @param $values
  596. * @param $value
  597. * @return array
  598. */
  599. private function getMostFrequentValues($values)
  600. {
  601. // remove false values (while keeping zeros)
  602. $values = array_filter($values, 'strlen');
  603. // array_count_values requires strings or integer, convert floats to string (mysqli)
  604. foreach ($values as &$value) {
  605. if (is_numeric($value)) {
  606. $value = (string)round($value, 3);
  607. }
  608. }
  609. // we have a list of all values. let's show the most frequently used first.
  610. $values = array_count_values($values);
  611. arsort($values);
  612. $values = array_keys($values);
  613. return $values;
  614. }
  615. }
  616. /**
  617. */
  618. class Plugin extends \Piwik\Plugin
  619. {
  620. public function __construct()
  621. {
  622. // this class is named 'Plugin', manually set the 'API' plugin
  623. parent::__construct($pluginName = 'API');
  624. }
  625. /**
  626. * @see Piwik\Plugin::getListHooksRegistered
  627. */
  628. public function getListHooksRegistered()
  629. {
  630. return array(
  631. 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles'
  632. );
  633. }
  634. public function getStylesheetFiles(&$stylesheets)
  635. {
  636. $stylesheets[] = "plugins/API/stylesheets/listAllAPI.less";
  637. }
  638. }