PageRenderTime 47ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/plugins/Live/API.php

https://github.com/quarkness/piwik
PHP | 511 lines | 370 code | 49 blank | 92 comment | 44 complexity | 4b9f12e636e82e357c04dc9c81abbb8e MD5 | raw file
  1. <?php
  2. /**
  3. * Piwik - Open source web analytics
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. * @version $Id$
  8. *
  9. * @category Piwik_Plugins
  10. * @package Piwik_Live
  11. */
  12. /**
  13. * @see plugins/Referers/functions.php
  14. */
  15. require_once PIWIK_INCLUDE_PATH . '/plugins/Live/Visitor.php';
  16. /**
  17. * The Live! API lets you access complete visit level information about your visitors. Combined with the power of <a href='http://piwik.org/docs/analytics-api/segmentation/' target='_blank'>Segmentation</a>,
  18. * you will be able to request visits filtered by any criteria.
  19. *
  20. * The method "getLastVisitsDetails" will return extensive data for each visit, which includes: server time, visitId, visitorId,
  21. * visitorType (new or returning), number of pages, list of all pages (and events, file downloaded and outlinks clicked),
  22. * custom variables names and values set to this visit, number of goal conversions (and list of all Goal conversions for this visit,
  23. * with time of conversion, revenue, URL, etc.), but also other attributes such as: days since last visit, days since first visit,
  24. * country, continent, visitor IP,
  25. * provider, referrer used (referrer name, keyword if it was a search engine, full URL), campaign name and keyword, operating system,
  26. * browser, type of screen, resolution, supported browser plugins (flash, java, silverlight, pdf, etc.), various dates & times format to make
  27. * it easier for API users... and more!
  28. *
  29. * With the parameter <a href='http://piwik.org/docs/analytics-api/segmentation/' target='_blank'>'&segment='</a> you can filter the
  30. * returned visits by any criteria (visitor IP, visitor ID, country, keyword used, time of day, etc.).
  31. *
  32. * The method "getCounters" is used to return a simple counter: visits, number of actions, number of converted visits, in the last N minutes.
  33. *
  34. * See also the documentation about <a href='http://piwik.org/docs/real-time/' target='_blank'>Real time widget and visitor level reports</a> in Piwik.
  35. * @package Piwik_Live
  36. */
  37. class Piwik_Live_API
  38. {
  39. static private $instance = null;
  40. /**
  41. * @return Piwik_Live_API
  42. */
  43. static public function getInstance()
  44. {
  45. if (self::$instance == null)
  46. {
  47. self::$instance = new self;
  48. }
  49. return self::$instance;
  50. }
  51. /**
  52. * This will return simple counters, for a given website ID, for visits over the last N minutes
  53. *
  54. * @param int Id Site
  55. * @param int Number of minutes to look back at
  56. *
  57. * @return array( visits => N, actions => M, visitsConverted => P )
  58. */
  59. public function getCounters($idSite, $lastMinutes, $segment = false)
  60. {
  61. Piwik::checkUserHasViewAccess($idSite);
  62. $lastMinutes = (int)$lastMinutes;
  63. $select = "count(*) as visits,
  64. SUM(log_visit.visit_total_actions) as actions,
  65. SUM(log_visit.visit_goal_converted) as visitsConverted";
  66. $from = "log_visit";
  67. $where = "log_visit.idsite = ?
  68. AND log_visit.visit_last_action_time >= ?";
  69. $bind = array(
  70. $idSite,
  71. Piwik_Date::factory(time() - $lastMinutes * 60)->toString('Y-m-d H:i:s')
  72. );
  73. $segment = new Piwik_Segment($segment, $idSite);
  74. $query = $segment->getSelectQuery($select, $from, $where, $bind);
  75. $data = Piwik_FetchAll($query['sql'], $query['bind']);
  76. // These could be unset for some reasons, ensure they are set to 0
  77. empty($data[0]['actions']) ? $data[0]['actions'] = 0 : '';
  78. empty($data[0]['visitsConverted']) ? $data[0]['visitsConverted'] = 0 : '';
  79. return $data;
  80. }
  81. /**
  82. * The same functionnality can be obtained using segment=visitorId==$visitorId with getLastVisitsDetails
  83. *
  84. * @deprecated
  85. * @ignore
  86. */
  87. public function getLastVisitsForVisitor( $visitorId, $idSite, $filter_limit = 10 )
  88. {
  89. Piwik::checkUserHasViewAccess($idSite);
  90. $visitorDetails = $this->loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $filter_limit, $maxIdVisit = false, $visitorId);
  91. $table = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite);
  92. return $table;
  93. }
  94. /**
  95. * Returns the last visits tracked in the specified website
  96. * You can define any number of filters: none, one, many or all parameters can be defined
  97. *
  98. * @param int Site ID
  99. * @param string (optional) Period to restrict to when looking at the logs
  100. * @param string (optional) Date to restrict to
  101. * @param int (optional) Number of visits rows to return
  102. * @param int (optional) Maximum idvisit to restrict the query to (useful when paginating)
  103. * @param int (optional) Minimum timestamp to restrict the query to (useful when paginating or refreshing visits)
  104. *
  105. * @return Piwik_DataTable
  106. */
  107. public function getLastVisitsDetails( $idSite, $period = false, $date = false, $segment = false, $filter_limit = false, $maxIdVisit = false, $minTimestamp = false )
  108. {
  109. if(empty($filter_limit))
  110. {
  111. $filter_limit = 10;
  112. }
  113. Piwik::checkUserHasViewAccess($idSite);
  114. $visitorDetails = $this->loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment, $filter_limit, $maxIdVisit, $visitorId = false, $minTimestamp);
  115. $dataTable = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite);
  116. return $dataTable;
  117. }
  118. /**
  119. * @deprecated
  120. */
  121. public function getLastVisits( $idSite, $filter_limit = 10, $minTimestamp = false )
  122. {
  123. return $this->getLastVisitsDetails($idSite, $period = false, $date = false, $filter_limit, $maxIdVisit = false, $minTimestamp );
  124. }
  125. /**
  126. * For an array of visits, query the list of pages for this visit
  127. * as well as make the data human readable
  128. */
  129. private function getCleanedVisitorsFromDetails($visitorDetails, $idSite)
  130. {
  131. $table = new Piwik_DataTable();
  132. $site = new Piwik_Site($idSite);
  133. $timezone = $site->getTimezone();
  134. $currencies = Piwik_SitesManager_API::getInstance()->getCurrencySymbols();
  135. foreach($visitorDetails as $visitorDetail)
  136. {
  137. $this->cleanVisitorDetails($visitorDetail, $idSite);
  138. $visitor = new Piwik_Live_Visitor($visitorDetail);
  139. $visitorDetailsArray = $visitor->getAllVisitorDetails();
  140. $visitorDetailsArray['siteCurrency'] = $site->getCurrency();
  141. $visitorDetailsArray['siteCurrencySymbol'] = @$currencies[$site->getCurrency()];
  142. $visitorDetailsArray['serverTimestamp'] = $visitorDetailsArray['lastActionTimestamp'];
  143. $dateTimeVisit = Piwik_Date::factory($visitorDetailsArray['lastActionTimestamp'], $timezone);
  144. $visitorDetailsArray['serverTimePretty'] = $dateTimeVisit->getLocalized('%time%');
  145. $visitorDetailsArray['serverDatePretty'] = $dateTimeVisit->getLocalized('%shortDay% %day% %shortMonth%');
  146. $dateTimeVisitFirstAction = Piwik_Date::factory($visitorDetailsArray['firstActionTimestamp'], $timezone);
  147. $visitorDetailsArray['serverDatePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized('%shortDay% %day% %shortMonth%');
  148. $visitorDetailsArray['serverTimePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized('%time%');
  149. $idvisit = $visitorDetailsArray['idVisit'];
  150. $sqlCustomVariables = '';
  151. for($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++)
  152. {
  153. $sqlCustomVariables .= ', custom_var_k' . $i . ', custom_var_v' . $i;
  154. }
  155. // The second join is a LEFT join to allow returning records that don't have a matching page title
  156. // eg. Downloads, Outlinks. For these, idaction_name is set to 0
  157. $sql = "
  158. SELECT
  159. log_action.type as type,
  160. log_action.name AS url,
  161. log_action_title.name AS pageTitle,
  162. log_action.idaction AS pageIdAction,
  163. log_link_visit_action.idlink_va AS pageId,
  164. log_link_visit_action.server_time as serverTimePretty
  165. $sqlCustomVariables
  166. FROM " .Piwik_Common::prefixTable('log_link_visit_action')." AS log_link_visit_action
  167. INNER JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action
  168. ON log_link_visit_action.idaction_url = log_action.idaction
  169. LEFT JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action_title
  170. ON log_link_visit_action.idaction_name = log_action_title.idaction
  171. WHERE log_link_visit_action.idvisit = ?
  172. ";
  173. $actionDetails = Piwik_FetchAll($sql, array($idvisit));
  174. foreach($actionDetails as &$actionDetail)
  175. {
  176. $customVariablesPage = array();
  177. for($i = 1; $i <= Piwik_Tracker::MAX_CUSTOM_VARIABLES; $i++)
  178. {
  179. if(!empty($actionDetail['custom_var_k'.$i])
  180. && !empty($actionDetail['custom_var_v'.$i]))
  181. {
  182. $customVariablesPage[$i] = array(
  183. 'customVariableName'.$i => $actionDetail['custom_var_k'.$i],
  184. 'customVariableValue'.$i => $actionDetail['custom_var_v'.$i],
  185. );
  186. }
  187. unset($actionDetail['custom_var_k'.$i]);
  188. unset($actionDetail['custom_var_v'.$i]);
  189. }
  190. if(!empty($customVariablesPage))
  191. {
  192. $actionDetail['customVariables'] = $customVariablesPage;
  193. }
  194. }
  195. // If the visitor converted a goal, we shall select all Goals
  196. $sql = "
  197. SELECT
  198. 'goal' as type,
  199. goal.name as goalName,
  200. goal.revenue as revenue,
  201. log_conversion.idlink_va as goalPageId,
  202. log_conversion.server_time as serverTimePretty,
  203. log_conversion.url as url
  204. FROM ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion
  205. LEFT JOIN ".Piwik_Common::prefixTable('goal')." AS goal
  206. ON (goal.idsite = log_conversion.idsite
  207. AND
  208. goal.idgoal = log_conversion.idgoal)
  209. AND goal.deleted = 0
  210. WHERE log_conversion.idvisit = ?
  211. AND log_conversion.idgoal > 0
  212. ";
  213. $goalDetails = Piwik_FetchAll($sql, array($idvisit));
  214. $sql = "SELECT
  215. case idgoal when ".Piwik_Tracker_GoalManager::IDGOAL_CART." then '".Piwik_Archive::LABEL_ECOMMERCE_CART."' else '".Piwik_Archive::LABEL_ECOMMERCE_ORDER."' end as type,
  216. idorder as orderId,
  217. ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue')." as revenue,
  218. ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_subtotal')." as revenueSubTotal,
  219. ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_tax')." as revenueTax,
  220. ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_shipping')." as revenueShipping,
  221. ".Piwik_ArchiveProcessing_Day::getSqlRevenue('revenue_discount')." as revenueDiscount,
  222. items as items,
  223. log_conversion.server_time as serverTimePretty
  224. FROM ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion
  225. WHERE idvisit = ?
  226. AND idgoal <= ".Piwik_Tracker_GoalManager::IDGOAL_ORDER;
  227. $ecommerceDetails = Piwik_FetchAll($sql, array($idvisit));
  228. foreach($ecommerceDetails as &$ecommerceDetail)
  229. {
  230. if($ecommerceDetail['type'] == Piwik_Archive::LABEL_ECOMMERCE_CART)
  231. {
  232. unset($ecommerceDetail['orderId']);
  233. unset($ecommerceDetail['revenueSubTotal']);
  234. unset($ecommerceDetail['revenueTax']);
  235. unset($ecommerceDetail['revenueShipping']);
  236. unset($ecommerceDetail['revenueDiscount']);
  237. }
  238. // 25.00 => 25
  239. foreach($ecommerceDetail as $column => $value)
  240. {
  241. if(strpos($column, 'revenue') !== false)
  242. {
  243. if($value == round($value))
  244. {
  245. $ecommerceDetail[$column] = round($value);
  246. }
  247. }
  248. }
  249. }
  250. $actions = array_merge($actionDetails, $goalDetails, $ecommerceDetails);
  251. usort($actions, array($this, 'sortByServerTime'));
  252. $visitorDetailsArray['actionDetails'] = $actions;
  253. // Convert datetimes to the site timezone
  254. foreach($visitorDetailsArray['actionDetails'] as &$details)
  255. {
  256. switch($details['type'])
  257. {
  258. case 'goal':
  259. $details['icon'] = 'themes/default/images/goal.png';
  260. break;
  261. case Piwik_Archive::LABEL_ECOMMERCE_ORDER:
  262. case Piwik_Archive::LABEL_ECOMMERCE_CART:
  263. $details['icon'] = 'themes/default/images/'.$details['type'].'.gif';
  264. break;
  265. case Piwik_Tracker_Action_Interface::TYPE_DOWNLOAD:
  266. $details['type'] = 'download';
  267. $details['icon'] = 'themes/default/images/download.png';
  268. break;
  269. case Piwik_Tracker_Action_Interface::TYPE_OUTLINK:
  270. $details['type'] = 'outlink';
  271. $details['icon'] = 'themes/default/images/link.gif';
  272. break;
  273. default:
  274. $details['type'] = 'action';
  275. $details['icon'] = null;
  276. break;
  277. }
  278. $dateTimeVisit = Piwik_Date::factory($details['serverTimePretty'], $timezone);
  279. $details['serverTimePretty'] = $dateTimeVisit->getLocalized('%shortDay% %day% %shortMonth% %time%');
  280. }
  281. $visitorDetailsArray['goalConversions'] = count($goalDetails);
  282. // Enrich ecommerce carts/orders with the list of products
  283. usort($ecommerceDetails, array($this, 'sortByServerTime'));
  284. foreach($ecommerceDetails as $key => &$ecommerceConversion)
  285. {
  286. $sql = "SELECT
  287. log_action_sku.name as itemSKU,
  288. log_action_name.name as itemName,
  289. log_action_category.name as itemCategory,
  290. ".Piwik_ArchiveProcessing_Day::getSqlRevenue('price')." as price,
  291. quantity as quantity
  292. FROM ".Piwik_Common::prefixTable('log_conversion_item')."
  293. INNER JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action_sku
  294. ON idaction_sku = log_action_sku.idaction
  295. LEFT JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action_name
  296. ON idaction_name = log_action_name.idaction
  297. LEFT JOIN " .Piwik_Common::prefixTable('log_action')." AS log_action_category
  298. ON idaction_category = log_action_category.idaction
  299. WHERE idvisit = ?
  300. AND idorder = ?
  301. AND deleted = 0
  302. ";
  303. $bind = array($idvisit, isset($ecommerceConversion['orderId']) ? $ecommerceConversion['orderId'] : Piwik_Tracker_GoalManager::ITEM_IDORDER_ABANDONED_CART);
  304. $itemsDetails = Piwik_FetchAll($sql, $bind);
  305. foreach($itemsDetails as &$detail)
  306. {
  307. if($detail['price'] == round($detail['price']))
  308. {
  309. $detail['price'] = round($detail['price']);
  310. }
  311. }
  312. $ecommerceConversion['itemDetails'] = $itemsDetails;
  313. }
  314. $table->addRowFromArray( array(Piwik_DataTable_Row::COLUMNS => $visitorDetailsArray));
  315. }
  316. return $table;
  317. }
  318. private function sortByServerTime($a, $b)
  319. {
  320. $ta = strtotime($a['serverTimePretty']);
  321. $tb = strtotime($b['serverTimePretty']);
  322. return $ta < $tb
  323. ? -1
  324. : ($ta == $tb
  325. ? 0
  326. : 1 );
  327. }
  328. private function loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $filter_limit = false, $maxIdVisit = false, $visitorId = false, $minTimestamp = false)
  329. {
  330. // var_dump($period); var_dump($date); var_dump($filter_limit); var_dump($maxIdVisit); var_dump($visitorId);
  331. //var_dump($minTimestamp);
  332. if(empty($filter_limit))
  333. {
  334. $filter_limit = 100;
  335. }
  336. $where = $whereBind = array();
  337. $where[] = "log_visit.idsite = ? ";
  338. $whereBind[] = $idSite;
  339. $orderBy = "idsite, visit_last_action_time DESC";
  340. $orderByParent = "sub.visit_last_action_time DESC";
  341. if(!empty($visitorId))
  342. {
  343. $where[] = "log_visit.idvisitor = ? ";
  344. $whereBind[] = @Piwik_Common::hex2bin($visitorId);
  345. }
  346. if(!empty($maxIdVisit))
  347. {
  348. $where[] = "log_visit.idvisit < ? ";
  349. $whereBind[] = $maxIdVisit;
  350. $orderBy = "idvisit DESC";
  351. $orderByParent = "sub.idvisit DESC";
  352. }
  353. if(!empty($minTimestamp))
  354. {
  355. $where[] = "log_visit.visit_last_action_time > ? ";
  356. $whereBind[] = date("Y-m-d H:i:s", $minTimestamp);
  357. }
  358. // If no other filter, only look at the last 24 hours of stats
  359. if(empty($visitorId)
  360. && empty($maxIdVisit)
  361. && empty($period)
  362. && empty($date))
  363. {
  364. $period = 'day';
  365. $date = 'yesterdaySameTime';
  366. }
  367. // SQL Filter with provided period
  368. if (!empty($period) && !empty($date))
  369. {
  370. $currentSite = new Piwik_Site($idSite);
  371. $currentTimezone = $currentSite->getTimezone();
  372. $dateString = $date;
  373. if($period == 'range')
  374. {
  375. $processedPeriod = new Piwik_Period_Range('range', $date);
  376. if($parsedDate = Piwik_Period_Range::parseDateRange($date))
  377. {
  378. $dateString = $parsedDate[2];
  379. }
  380. }
  381. else
  382. {
  383. $processedDate = Piwik_Date::factory($date);
  384. if($date == 'today'
  385. || $date == 'now'
  386. || $processedDate->toString() == Piwik_Date::factory('now', $currentTimezone)->toString())
  387. {
  388. $processedDate = $processedDate->subDay(1);
  389. }
  390. $processedPeriod = Piwik_Period::factory($period, $processedDate);
  391. }
  392. $dateStart = $processedPeriod->getDateStart()->setTimezone($currentTimezone);
  393. $where[] = "log_visit.visit_last_action_time >= ?";
  394. $whereBind[] = $dateStart->toString('Y-m-d H:i:s');
  395. if(!in_array($date, array('now', 'today', 'yesterdaySameTime'))
  396. && strpos($date, 'last') === false
  397. && strpos($date, 'previous') === false
  398. && Piwik_Date::factory($dateString)->toString('Y-m-d') != Piwik_Date::factory('now', $currentTimezone)->toString())
  399. {
  400. $dateEnd = $processedPeriod->getDateEnd()->setTimezone($currentTimezone);
  401. $where[] = " log_visit.visit_last_action_time <= ?";
  402. $dateEndString = $dateEnd->addDay(1)->toString('Y-m-d H:i:s');
  403. $whereBind[] = $dateEndString;
  404. }
  405. }
  406. if(count($where) > 0)
  407. {
  408. $where = join("
  409. AND ", $where);
  410. }
  411. else
  412. {
  413. $where = false;
  414. }
  415. $segment = new Piwik_Segment($segment, $idSite);
  416. // Subquery to use the indexes for ORDER BY
  417. $select = "log_visit.*";
  418. $from = "log_visit";
  419. $subQuery = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy);
  420. $sqlLimit = $filter_limit >= 1 ? " LIMIT ".(int)$filter_limit : "";
  421. // Group by idvisit so that a visitor converting 2 goals only appears once
  422. $sql = "
  423. SELECT sub.*
  424. FROM (
  425. ".$subQuery['sql']."
  426. $sqlLimit
  427. ) AS sub
  428. GROUP BY sub.idvisit
  429. ORDER BY $orderByParent
  430. ";
  431. try {
  432. $data = Piwik_FetchAll($sql, $subQuery['bind']);
  433. } catch(Exception $e) {
  434. echo $e->getMessage();exit;
  435. }
  436. //var_dump($whereBind); echo($sql);
  437. //var_dump($data);
  438. return $data;
  439. }
  440. /**
  441. * Removes fields that are not meant to be displayed (md5 config hash)
  442. * Or that the user should only access if he is super user or admin (cookie, IP)
  443. *
  444. * @return void
  445. */
  446. private function cleanVisitorDetails( &$visitorDetails, $idSite )
  447. {
  448. $toUnset = array('config_id');
  449. if(Piwik::isUserIsAnonymous())
  450. {
  451. $toUnset[] = 'idvisitor';
  452. $toUnset[] = 'location_ip';
  453. }
  454. foreach($toUnset as $keyName)
  455. {
  456. if(isset($visitorDetails[$keyName]))
  457. {
  458. unset($visitorDetails[$keyName]);
  459. }
  460. }
  461. }
  462. }