update Piwik to version 2.16 (fixes #91)
This commit is contained in:
parent
9abe039dc8
commit
47263617c5
87 changed files with 418860 additions and 226988 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -11,24 +11,24 @@ namespace Piwik\Plugins\Live;
|
|||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\Date;
|
||||
use Piwik\Db;
|
||||
use Piwik\MetricsFormatter;
|
||||
use Piwik\Metrics\Formatter;
|
||||
use Piwik\Period;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\Referrers\API as APIReferrers;
|
||||
use Piwik\Plugins\SitesManager\API as APISitesManager;
|
||||
use Piwik\Segment;
|
||||
use Piwik\Site;
|
||||
use Piwik\Tracker;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @see plugins/Live/Visitor.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/Live/Visitor.php';
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/functions.php';
|
||||
|
||||
/**
|
||||
* 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>,
|
||||
|
|
@ -43,19 +43,27 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/Live/Visitor.php';
|
|||
* browser, type of screen, resolution, supported browser plugins (flash, java, silverlight, pdf, etc.), various dates & times format to make
|
||||
* it easier for API users... and more!
|
||||
*
|
||||
* With the parameter <a href='http://piwik.org/docs/analytics-api/segmentation/' target='_blank'>'&segment='</a> you can filter the
|
||||
* With the parameter <a href='http://piwik.org/docs/analytics-api/segmentation/' rel='noreferrer' target='_blank'>'&segment='</a> you can filter the
|
||||
* returned visits by any criteria (visitor IP, visitor ID, country, keyword used, time of day, etc.).
|
||||
*
|
||||
* The method "getCounters" is used to return a simple counter: visits, number of actions, number of converted visits, in the last N minutes.
|
||||
*
|
||||
* 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.
|
||||
* See also the documentation about <a href='http://piwik.org/docs/real-time/' rel='noreferrer' target='_blank'>Real time widget and visitor level reports</a> in Piwik.
|
||||
* @method static \Piwik\Plugins\Live\API getInstance()
|
||||
*/
|
||||
class API extends \Piwik\Plugin\API
|
||||
{
|
||||
const VISITOR_PROFILE_MAX_VISITS_TO_AGGREGATE = 100;
|
||||
const VISITOR_PROFILE_MAX_VISITS_TO_SHOW = 10;
|
||||
const VISITOR_PROFILE_DATE_FORMAT = '%day% %shortMonth% %longYear%';
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return simple counters, for a given website ID, for visits over the last N minutes
|
||||
|
|
@ -63,43 +71,60 @@ class API extends \Piwik\Plugin\API
|
|||
* @param int $idSite Id Site
|
||||
* @param int $lastMinutes Number of minutes to look back at
|
||||
* @param bool|string $segment
|
||||
* @param array $showColumns The columns to show / not to request. Eg 'visits', 'actions', ...
|
||||
* @param array $hideColumns The columns to hide / not to request. Eg 'visits', 'actions', ...
|
||||
* @return array( visits => N, actions => M, visitsConverted => P )
|
||||
*/
|
||||
public function getCounters($idSite, $lastMinutes, $segment = false)
|
||||
public function getCounters($idSite, $lastMinutes, $segment = false, $showColumns = array(), $hideColumns = array())
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
$lastMinutes = (int)$lastMinutes;
|
||||
$model = new Model();
|
||||
|
||||
$select = "count(*) as visits,
|
||||
SUM(log_visit.visit_total_actions) as actions,
|
||||
SUM(log_visit.visit_goal_converted) as visitsConverted,
|
||||
COUNT(DISTINCT log_visit.idvisitor) as visitors";
|
||||
$counters = array();
|
||||
|
||||
$from = "log_visit";
|
||||
|
||||
list($whereIdSites, $idSites) = $this->getIdSitesWhereClause($idSite);
|
||||
|
||||
$where = $whereIdSites . "AND log_visit.visit_last_action_time >= ?";
|
||||
$bind = $idSites;
|
||||
$bind[] = Date::factory(time() - $lastMinutes * 60)->toString('Y-m-d H:i:s');
|
||||
|
||||
$segment = new Segment($segment, $idSite);
|
||||
$query = $segment->getSelectQuery($select, $from, $where, $bind);
|
||||
|
||||
$data = Db::fetchAll($query['sql'], $query['bind']);
|
||||
|
||||
// These could be unset for some reasons, ensure they are set to 0
|
||||
if (empty($data[0]['actions'])) {
|
||||
$data[0]['actions'] = 0;
|
||||
$hasVisits = true;
|
||||
if ($this->shouldColumnBePresentInResponse('visits', $showColumns, $hideColumns)) {
|
||||
$counters['visits'] = $model->getNumVisits($idSite, $lastMinutes, $segment);
|
||||
$hasVisits = !empty($counters['visits']);
|
||||
}
|
||||
if (empty($data[0]['visitsConverted'])) {
|
||||
$data[0]['visitsConverted'] = 0;
|
||||
|
||||
if ($this->shouldColumnBePresentInResponse('actions', $showColumns, $hideColumns)) {
|
||||
if ($hasVisits) {
|
||||
$counters['actions'] = $model->getNumActions($idSite, $lastMinutes, $segment);
|
||||
} else {
|
||||
$counters['actions'] = 0;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
|
||||
if ($this->shouldColumnBePresentInResponse('visitors', $showColumns, $hideColumns)) {
|
||||
if ($hasVisits) {
|
||||
$counters['visitors'] = $model->getNumVisitors($idSite, $lastMinutes, $segment);
|
||||
} else {
|
||||
$counters['visitors'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->shouldColumnBePresentInResponse('visitsConverted', $showColumns, $hideColumns)) {
|
||||
if ($hasVisits) {
|
||||
$counters['visitsConverted'] = $model->getNumVisitsConverted($idSite, $lastMinutes, $segment);
|
||||
} else {
|
||||
$counters['visitsConverted'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return array($counters);
|
||||
}
|
||||
|
||||
private function shouldColumnBePresentInResponse($column, $showColumns, $hideColumns)
|
||||
{
|
||||
$show = (empty($showColumns) || in_array($column, $showColumns));
|
||||
$hide = in_array($column, $hideColumns);
|
||||
|
||||
return $show && !$hide;
|
||||
}
|
||||
|
||||
/**
|
||||
* The same functionnality can be obtained using segment=visitorId==$visitorId with getLastVisitsDetails
|
||||
* The same functionality can be obtained using segment=visitorId==$visitorId with getLastVisitsDetails
|
||||
*
|
||||
* @deprecated
|
||||
* @ignore
|
||||
|
|
@ -114,9 +139,7 @@ class API extends \Piwik\Plugin\API
|
|||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
$countVisitorsToFetch = $filter_limit;
|
||||
|
||||
$table = $this->loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $countVisitorsToFetch, $visitorId);
|
||||
$table = $this->loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $offset = 0, $filter_limit, $minTimestamp = false, $filterSortOrder = false, $visitorId);
|
||||
$this->addFilterToCleanVisitors($table, $idSite, $flat);
|
||||
|
||||
return $table;
|
||||
|
|
@ -130,7 +153,7 @@ class API extends \Piwik\Plugin\API
|
|||
* @param bool|string $period Period to restrict to when looking at the logs
|
||||
* @param bool|string $date Date to restrict to
|
||||
* @param bool|int $segment (optional) Number of visits rows to return
|
||||
* @param bool|int $countVisitorsToFetch (optional) Only return the last X visits. By default the last GET['filter_offset']+GET['filter_limit'] are returned.
|
||||
* @param bool|int $countVisitorsToFetch DEPRECATED (optional) Only return the last X visits. Please use the API paramaeter 'filter_offset' and 'filter_limit' instead.
|
||||
* @param bool|int $minTimestamp (optional) Minimum timestamp to restrict the query to (useful when paginating or refreshing visits)
|
||||
* @param bool $flat
|
||||
* @param bool $doNotFetchActions
|
||||
|
|
@ -138,17 +161,34 @@ class API extends \Piwik\Plugin\API
|
|||
*/
|
||||
public function getLastVisitsDetails($idSite, $period = false, $date = false, $segment = false, $countVisitorsToFetch = false, $minTimestamp = false, $flat = false, $doNotFetchActions = false)
|
||||
{
|
||||
if (false === $countVisitorsToFetch) {
|
||||
$filter_limit = Common::getRequestVar('filter_limit', 10, 'int');
|
||||
$filter_offset = Common::getRequestVar('filter_offset', 0, 'int');
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
$countVisitorsToFetch = $filter_limit + $filter_offset;
|
||||
if ($countVisitorsToFetch !== false) {
|
||||
$filterLimit = (int) $countVisitorsToFetch;
|
||||
$filterOffset = 0;
|
||||
} else {
|
||||
$filterLimit = Common::getRequestVar('filter_limit', 10, 'int');
|
||||
$filterOffset = Common::getRequestVar('filter_offset', 0, 'int');
|
||||
}
|
||||
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
$dataTable = $this->loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment, $countVisitorsToFetch, $visitorId = false, $minTimestamp);
|
||||
$filterSortOrder = Common::getRequestVar('filter_sort_order', false, 'string');
|
||||
|
||||
$dataTable = $this->loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment, $filterOffset, $filterLimit, $minTimestamp, $filterSortOrder, $visitorId = false);
|
||||
$this->addFilterToCleanVisitors($dataTable, $idSite, $flat, $doNotFetchActions);
|
||||
|
||||
$filterSortColumn = Common::getRequestVar('filter_sort_column', false, 'string');
|
||||
|
||||
if ($filterSortColumn) {
|
||||
$this->logger->warning('Sorting the API method "Live.getLastVisitDetails" by column is currently not supported. To avoid this warning remove the URL parameter "filter_sort_column" from your API request.');
|
||||
}
|
||||
|
||||
// Usually one would Sort a DataTable and then apply a Limit. In this case we apply a Limit first in SQL
|
||||
// for fast offset usage see https://github.com/piwik/piwik/issues/7458. Sorting afterwards would lead to a
|
||||
// wrong sorting result as it would only sort the limited results. Therefore we do not support a Sort for this
|
||||
// API
|
||||
$dataTable->disableFilter('Sort');
|
||||
$dataTable->disableFilter('Limit'); // limit is already applied here
|
||||
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
|
|
@ -158,14 +198,19 @@ class API extends \Piwik\Plugin\API
|
|||
* @param int $idSite Site ID
|
||||
* @param bool|false|string $visitorId The ID of the visitor whose profile to retrieve.
|
||||
* @param bool|false|string $segment
|
||||
* @param bool $checkForLatLong If true, hasLatLong will appear in the output and be true if
|
||||
* one of the first 100 visits has a latitude/longitude.
|
||||
* @param bool|false|int $limitVisits
|
||||
* @return array
|
||||
*/
|
||||
public function getVisitorProfile($idSite, $visitorId = false, $segment = false, $checkForLatLong = false)
|
||||
public function getVisitorProfile($idSite, $visitorId = false, $segment = false, $limitVisits = false)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
if ($limitVisits === false) {
|
||||
$limitVisits = VisitorProfile::VISITOR_PROFILE_MAX_VISITS_TO_SHOW;
|
||||
} else {
|
||||
$limitVisits = (int) $limitVisits;
|
||||
}
|
||||
|
||||
if ($visitorId === false) {
|
||||
$visitorId = $this->getMostRecentVisitorId($idSite, $segment);
|
||||
}
|
||||
|
|
@ -173,196 +218,16 @@ class API extends \Piwik\Plugin\API
|
|||
$newSegment = ($segment === false ? '' : $segment . ';') . 'visitorId==' . $visitorId;
|
||||
|
||||
$visits = $this->loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $newSegment,
|
||||
$numVisitorsToFetch = self::VISITOR_PROFILE_MAX_VISITS_TO_AGGREGATE,
|
||||
$overrideVisitorId = false,
|
||||
$minTimestamp = false);
|
||||
$offset = 0,
|
||||
$limit = self::VISITOR_PROFILE_MAX_VISITS_TO_AGGREGATE);
|
||||
$this->addFilterToCleanVisitors($visits, $idSite, $flat = false, $doNotFetchActions = false, $filterNow = true);
|
||||
|
||||
if ($visits->getRowsCount() == 0) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$isEcommerceEnabled = Site::isEcommerceEnabledFor($idSite);
|
||||
|
||||
$result = array();
|
||||
$result['totalVisits'] = 0;
|
||||
$result['totalVisitDuration'] = 0;
|
||||
$result['totalActions'] = 0;
|
||||
$result['totalSearches'] = 0;
|
||||
$result['totalPageViews'] = 0;
|
||||
$result['totalGoalConversions'] = 0;
|
||||
$result['totalConversionsByGoal'] = array();
|
||||
|
||||
if ($isEcommerceEnabled) {
|
||||
$result['totalEcommerceConversions'] = 0;
|
||||
$result['totalEcommerceRevenue'] = 0;
|
||||
$result['totalEcommerceItems'] = 0;
|
||||
$result['totalAbandonedCarts'] = 0;
|
||||
$result['totalAbandonedCartsRevenue'] = 0;
|
||||
$result['totalAbandonedCartsItems'] = 0;
|
||||
}
|
||||
|
||||
$countries = array();
|
||||
$continents = array();
|
||||
$cities = array();
|
||||
$siteSearchKeywords = array();
|
||||
|
||||
$pageGenerationTimeTotal = 0;
|
||||
|
||||
// aggregate all requested visits info for total_* info
|
||||
foreach ($visits->getRows() as $visit) {
|
||||
++$result['totalVisits'];
|
||||
|
||||
$result['totalVisitDuration'] += $visit->getColumn('visitDuration');
|
||||
$result['totalActions'] += $visit->getColumn('actions');
|
||||
$result['totalGoalConversions'] += $visit->getColumn('goalConversions');
|
||||
|
||||
// individual goal conversions are stored in action details
|
||||
foreach ($visit->getColumn('actionDetails') as $action) {
|
||||
if ($action['type'] == 'goal') {
|
||||
// handle goal conversion
|
||||
$idGoal = $action['goalId'];
|
||||
$idGoalKey = 'idgoal=' . $idGoal;
|
||||
|
||||
if (!isset($result['totalConversionsByGoal'][$idGoalKey])) {
|
||||
$result['totalConversionsByGoal'][$idGoalKey] = 0;
|
||||
}
|
||||
++$result['totalConversionsByGoal'][$idGoalKey];
|
||||
|
||||
if (!empty($action['revenue'])) {
|
||||
if (!isset($result['totalRevenueByGoal'][$idGoalKey])) {
|
||||
$result['totalRevenueByGoal'][$idGoalKey] = 0;
|
||||
}
|
||||
$result['totalRevenueByGoal'][$idGoalKey] += $action['revenue'];
|
||||
}
|
||||
} else if ($action['type'] == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER // handle ecommerce order
|
||||
&& $isEcommerceEnabled
|
||||
) {
|
||||
++$result['totalEcommerceConversions'];
|
||||
$result['totalEcommerceRevenue'] += $action['revenue'];
|
||||
$result['totalEcommerceItems'] += $action['items'];
|
||||
} else if ($action['type'] == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART // handler abandoned cart
|
||||
&& $isEcommerceEnabled
|
||||
) {
|
||||
++$result['totalAbandonedCarts'];
|
||||
$result['totalAbandonedCartsRevenue'] += $action['revenue'];
|
||||
$result['totalAbandonedCartsItems'] += $action['items'];
|
||||
}
|
||||
|
||||
if (isset($action['siteSearchKeyword'])) {
|
||||
$keyword = $action['siteSearchKeyword'];
|
||||
|
||||
if (!isset($siteSearchKeywords[$keyword])) {
|
||||
$siteSearchKeywords[$keyword] = 0;
|
||||
++$result['totalSearches'];
|
||||
}
|
||||
++$siteSearchKeywords[$keyword];
|
||||
}
|
||||
|
||||
if (isset($action['generationTime'])) {
|
||||
$pageGenerationTimeTotal += $action['generationTime'];
|
||||
++$result['totalPageViews'];
|
||||
}
|
||||
}
|
||||
|
||||
$countryCode = $visit->getColumn('countryCode');
|
||||
if (!isset($countries[$countryCode])) {
|
||||
$countries[$countryCode] = 0;
|
||||
}
|
||||
++$countries[$countryCode];
|
||||
|
||||
$continentCode = $visit->getColumn('continentCode');
|
||||
if (!isset($continents[$continentCode])) {
|
||||
$continents[$continentCode] = 0;
|
||||
}
|
||||
++$continents[$continentCode];
|
||||
|
||||
if (!array_key_exists($countryCode, $cities)) {
|
||||
$cities[$countryCode] = array();
|
||||
}
|
||||
$city = $visit->getColumn('city');
|
||||
if(!empty($city)) {
|
||||
$cities[$countryCode][] = $city;
|
||||
}
|
||||
}
|
||||
|
||||
// sort countries/continents/search keywords by visit/action
|
||||
asort($countries);
|
||||
asort($continents);
|
||||
arsort($siteSearchKeywords);
|
||||
|
||||
// transform country/continents/search keywords into something that will look good in XML
|
||||
$result['countries'] = $result['continents'] = $result['searches'] = array();
|
||||
|
||||
foreach ($countries as $countryCode => $nbVisits) {
|
||||
|
||||
$countryInfo = array('country' => $countryCode,
|
||||
'nb_visits' => $nbVisits,
|
||||
'flag' => \Piwik\Plugins\UserCountry\getFlagFromCode($countryCode),
|
||||
'prettyName' => \Piwik\Plugins\UserCountry\countryTranslate($countryCode));
|
||||
if(!empty($cities[$countryCode])) {
|
||||
$countryInfo['cities'] = array_unique($cities[$countryCode]);
|
||||
}
|
||||
$result['countries'][] = $countryInfo;
|
||||
}
|
||||
foreach ($continents as $continentCode => $nbVisits) {
|
||||
$result['continents'][] = array('continent' => $continentCode,
|
||||
'nb_visits' => $nbVisits,
|
||||
'prettyName' => \Piwik\Plugins\UserCountry\continentTranslate($continentCode));
|
||||
}
|
||||
foreach ($siteSearchKeywords as $keyword => $searchCount) {
|
||||
$result['searches'][] = array('keyword' => $keyword,
|
||||
'searches' => $searchCount);
|
||||
}
|
||||
|
||||
if ($result['totalPageViews']) {
|
||||
$result['averagePageGenerationTime'] =
|
||||
round($pageGenerationTimeTotal / $result['totalPageViews'], $precision = 2);
|
||||
}
|
||||
|
||||
$result['totalVisitDurationPretty'] = MetricsFormatter::getPrettyTimeFromSeconds($result['totalVisitDuration']);
|
||||
|
||||
// use requested visits for first/last visit info
|
||||
$rows = $visits->getRows();
|
||||
$result['firstVisit'] = $this->getVisitorProfileVisitSummary(end($rows));
|
||||
$result['lastVisit'] = $this->getVisitorProfileVisitSummary(reset($rows));
|
||||
|
||||
// check if requested visits have lat/long
|
||||
if ($checkForLatLong) {
|
||||
$result['hasLatLong'] = false;
|
||||
foreach ($rows as $visit) {
|
||||
if ($visit->getColumn('latitude') !== false) { // realtime map only checks for latitude
|
||||
$result['hasLatLong'] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save count of visits we queries
|
||||
$result['visitsAggregated'] = count($rows);
|
||||
|
||||
// use N most recent visits for last_visits
|
||||
$visits->deleteRowsOffset(self::VISITOR_PROFILE_MAX_VISITS_TO_SHOW);
|
||||
$result['lastVisits'] = $visits;
|
||||
|
||||
// use the right date format for the pretty server date
|
||||
$timezone = Site::getTimezoneFor($idSite);
|
||||
foreach ($result['lastVisits']->getRows() as $visit) {
|
||||
$dateTimeVisitFirstAction = Date::factory($visit->getColumn('firstActionTimestamp'), $timezone);
|
||||
|
||||
$datePretty = $dateTimeVisitFirstAction->getLocalized(self::VISITOR_PROFILE_DATE_FORMAT);
|
||||
$visit->setColumn('serverDatePrettyFirstAction', $datePretty);
|
||||
|
||||
$dateTimePretty = $datePretty . ' ' . $visit->getColumn('serverTimePrettyFirstAction');
|
||||
$visit->setColumn('serverDateTimePrettyFirstAction', $dateTimePretty);
|
||||
}
|
||||
|
||||
// get visitor IDs that are adjacent to this one in log_visit
|
||||
// TODO: make sure order of visitor ids is not changed if a returning visitor visits while the user is
|
||||
// looking at the popup.
|
||||
$latestVisitTime = reset($rows)->getColumn('lastActionDateTime');
|
||||
$result['nextVisitorId'] = $this->getAdjacentVisitorId($idSite, $visitorId, $latestVisitTime, $segment, $getNext = true);
|
||||
$result['previousVisitorId'] = $this->getAdjacentVisitorId($idSite, $visitorId, $latestVisitTime, $segment, $getNext = false);
|
||||
$profile = new VisitorProfile($idSite);
|
||||
$result = $profile->makeVisitorProfile($visits, $visitorId, $segment, $limitVisits);
|
||||
|
||||
/**
|
||||
* Triggered in the Live.getVisitorProfile API method. Plugins can use this event
|
||||
|
|
@ -395,130 +260,45 @@ class API extends \Piwik\Plugin\API
|
|||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
|
||||
// for faster performance search for a visitor within the last 7 days first
|
||||
$minTimestamp = Date::now()->subDay(7)->getTimestamp();
|
||||
|
||||
$dataTable = $this->loadLastVisitorDetailsFromDatabase(
|
||||
$idSite, $period = false, $date = false, $segment, $numVisitorsToFetch = 1,
|
||||
$visitorId = false, $minTimestamp = false
|
||||
$idSite, $period = false, $date = false, $segment, $offset = 0, $limit = 1, $minTimestamp
|
||||
);
|
||||
|
||||
if (0 >= $dataTable->getRowsCount()) {
|
||||
$minTimestamp = Date::now()->subYear(1)->getTimestamp();
|
||||
// no visitor found in last 7 days, look further back for up to 1 year. This query will be slower
|
||||
$dataTable = $this->loadLastVisitorDetailsFromDatabase(
|
||||
$idSite, $period = false, $date = false, $segment, $offset = 0, $limit = 1, $minTimestamp
|
||||
);
|
||||
}
|
||||
|
||||
if (0 >= $dataTable->getRowsCount()) {
|
||||
// no visitor found in last year, look over all logs. This query might be quite slow
|
||||
$dataTable = $this->loadLastVisitorDetailsFromDatabase(
|
||||
$idSite, $period = false, $date = false, $segment, $offset = 0, $limit = 1
|
||||
);
|
||||
}
|
||||
|
||||
if (0 >= $dataTable->getRowsCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$visitDetails = $dataTable->getFirstRow()->getColumns();
|
||||
$visitor = new Visitor($visitDetails);
|
||||
$visitorFactory = new VisitorFactory();
|
||||
$visitDetails = $dataTable->getFirstRow()->getColumns();
|
||||
$visitor = $visitorFactory->create($visitDetails);
|
||||
|
||||
return $visitor->getVisitorId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of a visitor that is adjacent to another visitor (by time of last action)
|
||||
* in the log_visit table.
|
||||
*
|
||||
* @param int $idSite The ID of the site whose visits should be looked at.
|
||||
* @param string $visitorId The ID of the visitor to get an adjacent visitor for.
|
||||
* @param string $visitLastActionTime The last action time of the latest visit for $visitorId.
|
||||
* @param string $segment
|
||||
* @param bool $getNext Whether to retrieve the next visitor or the previous visitor. The next
|
||||
* visitor will be the visitor that appears chronologically later in the
|
||||
* log_visit table. The previous visitor will be the visitor that appears
|
||||
* earlier.
|
||||
* @return string The hex visitor ID.
|
||||
*/
|
||||
private function getAdjacentVisitorId($idSite, $visitorId, $visitLastActionTime, $segment, $getNext)
|
||||
{
|
||||
if ($getNext) {
|
||||
$visitLastActionTimeCondition = "sub.visit_last_action_time <= ?";
|
||||
$orderByDir = "DESC";
|
||||
} else {
|
||||
$visitLastActionTimeCondition = "sub.visit_last_action_time >= ?";
|
||||
$orderByDir = "ASC";
|
||||
}
|
||||
|
||||
$visitLastActionDate = Date::factory($visitLastActionTime);
|
||||
$dateOneDayAgo = $visitLastActionDate->subDay(1);
|
||||
$dateOneDayInFuture = $visitLastActionDate->addDay(1);
|
||||
|
||||
$select = "log_visit.idvisitor, MAX(log_visit.visit_last_action_time) as visit_last_action_time";
|
||||
$from = "log_visit";
|
||||
$where = "log_visit.idsite = ? AND log_visit.idvisitor <> ? AND visit_last_action_time >= ? and visit_last_action_time <= ?";
|
||||
$whereBind = array($idSite, @Common::hex2bin($visitorId), $dateOneDayAgo->toString('Y-m-d H:i:s'), $dateOneDayInFuture->toString('Y-m-d H:i:s'));
|
||||
$orderBy = "MAX(log_visit.visit_last_action_time) $orderByDir";
|
||||
$groupBy = "log_visit.idvisitor";
|
||||
|
||||
$segment = new Segment($segment, $idSite);
|
||||
$queryInfo = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy, $groupBy);
|
||||
|
||||
$sql = "SELECT sub.idvisitor, sub.visit_last_action_time
|
||||
FROM ({$queryInfo['sql']}) as sub
|
||||
WHERE $visitLastActionTimeCondition
|
||||
LIMIT 1";
|
||||
$bind = array_merge($queryInfo['bind'], array($visitLastActionTime));
|
||||
|
||||
$visitorId = Db::fetchOne($sql, $bind);
|
||||
if (!empty($visitorId)) {
|
||||
$visitorId = bin2hex($visitorId);
|
||||
}
|
||||
return $visitorId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary for an important visit. Used to describe the first & last visits of a visitor.
|
||||
*
|
||||
* @param Row $visit
|
||||
* @return array
|
||||
*/
|
||||
private function getVisitorProfileVisitSummary($visit)
|
||||
{
|
||||
$today = Date::today();
|
||||
|
||||
$serverDate = $visit->getColumn('serverDate');
|
||||
return array(
|
||||
'date' => $serverDate,
|
||||
'prettyDate' => Date::factory($serverDate)->getLocalized(self::VISITOR_PROFILE_DATE_FORMAT),
|
||||
'daysAgo' => (int)Date::secondsToDays($today->getTimestamp() - Date::factory($serverDate)->getTimestamp()),
|
||||
'referrerType' => $visit->getColumn('referrerType'),
|
||||
'referralSummary' => self::getReferrerSummaryForVisit($visit),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary for a visit's referral.
|
||||
*
|
||||
* @param Row $visit
|
||||
* @return bool|mixed|string
|
||||
* @ignore
|
||||
*/
|
||||
public static function getReferrerSummaryForVisit($visit)
|
||||
{
|
||||
$referrerType = $visit->getColumn('referrerType');
|
||||
if ($referrerType === false
|
||||
|| $referrerType == 'direct'
|
||||
) {
|
||||
$result = Piwik::translate('Referrers_DirectEntry');
|
||||
} else if ($referrerType == 'search') {
|
||||
$result = $visit->getColumn('referrerName');
|
||||
|
||||
$keyword = $visit->getColumn('referrerKeyword');
|
||||
if ($keyword !== false
|
||||
&& $keyword != APIReferrers::getKeywordNotDefinedString()
|
||||
) {
|
||||
$result .= ' (' . $keyword . ')';
|
||||
}
|
||||
} else if ($referrerType == 'campaign') {
|
||||
$result = Piwik::translate('Referrers_ColumnCampaign') . ' (' . $visit->getColumn('referrerName') . ')';
|
||||
} else {
|
||||
$result = $visit->getColumn('referrerName');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function getLastVisits($idSite, $filter_limit = 10, $minTimestamp = false)
|
||||
{
|
||||
return $this->getLastVisitsDetails($idSite, $period = false, $date = false, $segment = false, $countVisitorsToFetch = $filter_limit, $minTimestamp, $flat = false);
|
||||
return $this->getLastVisitsDetails($idSite, $period = false, $date = false, $segment = false, $filter_limit, $minTimestamp, $flat = false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -541,26 +321,34 @@ class API extends \Piwik\Plugin\API
|
|||
/** @var DataTable $table */
|
||||
$actionsLimit = (int)Config::getInstance()->General['visitor_log_maximum_actions_per_visit'];
|
||||
|
||||
$website = new Site($idSite);
|
||||
$timezone = $website->getTimezone();
|
||||
$currencies = APISitesManager::getInstance()->getCurrencySymbols();
|
||||
$visitorFactory = new VisitorFactory();
|
||||
$website = new Site($idSite);
|
||||
$timezone = $website->getTimezone();
|
||||
$currency = $website->getCurrency();
|
||||
$currencies = APISitesManager::getInstance()->getCurrencySymbols();
|
||||
|
||||
// live api is not summable, prevents errors like "Unexpected ECommerce status value"
|
||||
$table->deleteRow(DataTable::ID_SUMMARY_ROW);
|
||||
|
||||
foreach ($table->getRows() as $visitorDetailRow) {
|
||||
$visitorDetailsArray = Visitor::cleanVisitorDetails($visitorDetailRow->getColumns());
|
||||
|
||||
$visitor = new Visitor($visitorDetailsArray);
|
||||
$visitor = $visitorFactory->create($visitorDetailsArray);
|
||||
$visitorDetailsArray = $visitor->getAllVisitorDetails();
|
||||
|
||||
$visitorDetailsArray['siteCurrency'] = $website->getCurrency();
|
||||
$visitorDetailsArray['siteCurrency'] = $currency;
|
||||
$visitorDetailsArray['siteCurrencySymbol'] = @$currencies[$visitorDetailsArray['siteCurrency']];
|
||||
$visitorDetailsArray['serverTimestamp'] = $visitorDetailsArray['lastActionTimestamp'];
|
||||
|
||||
$dateTimeVisit = Date::factory($visitorDetailsArray['lastActionTimestamp'], $timezone);
|
||||
$visitorDetailsArray['serverTimePretty'] = $dateTimeVisit->getLocalized('%time%');
|
||||
$visitorDetailsArray['serverDatePretty'] = $dateTimeVisit->getLocalized(Piwik::translate('CoreHome_ShortDateFormat'));
|
||||
if ($dateTimeVisit) {
|
||||
$visitorDetailsArray['serverTimePretty'] = $dateTimeVisit->getLocalized(Date::TIME_FORMAT);
|
||||
$visitorDetailsArray['serverDatePretty'] = $dateTimeVisit->getLocalized(Date::DATE_FORMAT_LONG);
|
||||
}
|
||||
|
||||
$dateTimeVisitFirstAction = Date::factory($visitorDetailsArray['firstActionTimestamp'], $timezone);
|
||||
$visitorDetailsArray['serverDatePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized(Piwik::translate('CoreHome_ShortDateFormat'));
|
||||
$visitorDetailsArray['serverTimePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized('%time%');
|
||||
$visitorDetailsArray['serverDatePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized(Date::DATE_FORMAT_LONG);
|
||||
$visitorDetailsArray['serverTimePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized(Date::TIME_FORMAT);
|
||||
|
||||
$visitorDetailsArray['actionDetails'] = array();
|
||||
if (!$doNotFetchActions) {
|
||||
|
|
@ -576,124 +364,33 @@ class API extends \Piwik\Plugin\API
|
|||
});
|
||||
}
|
||||
|
||||
private function loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment = false, $countVisitorsToFetch = 100, $visitorId = false, $minTimestamp = false)
|
||||
private function loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment = false, $offset = 0, $limit = 100, $minTimestamp = false, $filterSortOrder = false, $visitorId = false)
|
||||
{
|
||||
$where = $whereBind = array();
|
||||
|
||||
list($whereClause, $idSites) = $this->getIdSitesWhereClause($idSite);
|
||||
|
||||
$where[] = $whereClause;
|
||||
$whereBind = $idSites;
|
||||
|
||||
$orderBy = "idsite, visit_last_action_time DESC";
|
||||
$orderByParent = "sub.visit_last_action_time DESC";
|
||||
if (!empty($visitorId)) {
|
||||
$where[] = "log_visit.idvisitor = ? ";
|
||||
$whereBind[] = @Common::hex2bin($visitorId);
|
||||
}
|
||||
|
||||
if (!empty($minTimestamp)) {
|
||||
$where[] = "log_visit.visit_last_action_time > ? ";
|
||||
$whereBind[] = date("Y-m-d H:i:s", $minTimestamp);
|
||||
}
|
||||
|
||||
// If no other filter, only look at the last 24 hours of stats
|
||||
if (empty($visitorId)
|
||||
&& empty($countVisitorsToFetch)
|
||||
&& empty($period)
|
||||
&& empty($date)
|
||||
) {
|
||||
$period = 'day';
|
||||
$date = 'yesterdaySameTime';
|
||||
}
|
||||
|
||||
// SQL Filter with provided period
|
||||
if (!empty($period) && !empty($date)) {
|
||||
$currentSite = new Site($idSite);
|
||||
$currentTimezone = $currentSite->getTimezone();
|
||||
|
||||
$dateString = $date;
|
||||
if ($period == 'range') {
|
||||
$processedPeriod = new Range('range', $date);
|
||||
if ($parsedDate = Range::parseDateRange($date)) {
|
||||
$dateString = $parsedDate[2];
|
||||
}
|
||||
} else {
|
||||
$processedDate = Date::factory($date);
|
||||
if ($date == 'today'
|
||||
|| $date == 'now'
|
||||
|| $processedDate->toString() == Date::factory('now', $currentTimezone)->toString()
|
||||
) {
|
||||
$processedDate = $processedDate->subDay(1);
|
||||
}
|
||||
$processedPeriod = Period::factory($period, $processedDate);
|
||||
}
|
||||
$dateStart = $processedPeriod->getDateStart()->setTimezone($currentTimezone);
|
||||
$where[] = "log_visit.visit_last_action_time >= ?";
|
||||
$whereBind[] = $dateStart->toString('Y-m-d H:i:s');
|
||||
|
||||
if (!in_array($date, array('now', 'today', 'yesterdaySameTime'))
|
||||
&& strpos($date, 'last') === false
|
||||
&& strpos($date, 'previous') === false
|
||||
&& Date::factory($dateString)->toString('Y-m-d') != Date::factory('now', $currentTimezone)->toString()
|
||||
) {
|
||||
$dateEnd = $processedPeriod->getDateEnd()->setTimezone($currentTimezone);
|
||||
$where[] = " log_visit.visit_last_action_time <= ?";
|
||||
$dateEndString = $dateEnd->addDay(1)->toString('Y-m-d H:i:s');
|
||||
$whereBind[] = $dateEndString;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($where) > 0) {
|
||||
$where = join("
|
||||
AND ", $where);
|
||||
} else {
|
||||
$where = false;
|
||||
}
|
||||
|
||||
$segment = new Segment($segment, $idSite);
|
||||
|
||||
// Subquery to use the indexes for ORDER BY
|
||||
$select = "log_visit.*";
|
||||
$from = "log_visit";
|
||||
$subQuery = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy);
|
||||
|
||||
$sqlLimit = $countVisitorsToFetch >= 1 ? " LIMIT 0, " . (int)$countVisitorsToFetch : "";
|
||||
|
||||
// Group by idvisit so that a visitor converting 2 goals only appears once
|
||||
$sql = "
|
||||
SELECT sub.*
|
||||
FROM (
|
||||
" . $subQuery['sql'] . "
|
||||
$sqlLimit
|
||||
) AS sub
|
||||
GROUP BY sub.idvisit
|
||||
ORDER BY $orderByParent
|
||||
";
|
||||
try {
|
||||
$data = Db::fetchAll($sql, $subQuery['bind']);
|
||||
} catch (Exception $e) {
|
||||
echo $e->getMessage();
|
||||
exit;
|
||||
}
|
||||
$model = new Model();
|
||||
$data = $model->queryLogVisits($idSite, $period, $date, $segment, $offset, $limit, $visitorId, $minTimestamp, $filterSortOrder);
|
||||
return $this->makeVisitorTableFromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @return DataTable
|
||||
* @throws Exception
|
||||
*/
|
||||
private function makeVisitorTableFromArray($data)
|
||||
{
|
||||
$dataTable = new DataTable();
|
||||
$dataTable->addRowsFromSimpleArray($data);
|
||||
|
||||
if (!empty($data[0])) {
|
||||
$columnsToNotAggregate = array_map(function () {
|
||||
return 'skip';
|
||||
}, $data[0]);
|
||||
|
||||
$dataTable->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, $columnsToNotAggregate);
|
||||
}
|
||||
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idSite
|
||||
* @return array
|
||||
*/
|
||||
private function getIdSitesWhereClause($idSite)
|
||||
{
|
||||
$idSites = array($idSite);
|
||||
Piwik::postEvent('Live.API.getIdSitesString', array(&$idSites));
|
||||
|
||||
$idSitesBind = Common::getSqlStringFieldsArray($idSites);
|
||||
$whereClause = "log_visit.idsite in ($idSitesBind) ";
|
||||
return array($whereClause, $idSites);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -11,12 +11,10 @@ namespace Piwik\Plugins\Live;
|
|||
use Piwik\API\Request;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\MetricsFormatter;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\Goals\API as APIGoals;
|
||||
use Piwik\Url;
|
||||
use Piwik\View;
|
||||
use Piwik\ViewDataTable\Factory;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
|
@ -40,31 +38,6 @@ class Controller extends \Piwik\Plugin\Controller
|
|||
return $this->render($view);
|
||||
}
|
||||
|
||||
public function getSimpleLastVisitCount()
|
||||
{
|
||||
$lastMinutes = Config::getInstance()->General[self::SIMPLE_VISIT_COUNT_WIDGET_LAST_MINUTES_CONFIG_KEY];
|
||||
|
||||
$lastNData = Request::processRequest('Live.getCounters', array('lastMinutes' => $lastMinutes));
|
||||
|
||||
$view = new View('@Live/getSimpleLastVisitCount');
|
||||
$view->lastMinutes = $lastMinutes;
|
||||
$view->visitors = MetricsFormatter::getPrettyNumber($lastNData[0]['visitors']);
|
||||
$view->visits = MetricsFormatter::getPrettyNumber($lastNData[0]['visits']);
|
||||
$view->actions = MetricsFormatter::getPrettyNumber($lastNData[0]['actions']);
|
||||
$view->refreshAfterXSecs = Config::getInstance()->General['live_widget_refresh_after_seconds'];
|
||||
$view->translations = array(
|
||||
'one_visitor' => Piwik::translate('Live_NbVisitor'),
|
||||
'visitors' => Piwik::translate('Live_NbVisitors'),
|
||||
'one_visit' => Piwik::translate('General_OneVisit'),
|
||||
'visits' => Piwik::translate('General_NVisits'),
|
||||
'one_action' => Piwik::translate('General_OneAction'),
|
||||
'actions' => Piwik::translate('VisitsSummary_NbActionsDescription'),
|
||||
'one_minute' => Piwik::translate('General_OneMinute'),
|
||||
'minutes' => Piwik::translate('General_NMinutes')
|
||||
);
|
||||
return $this->render($view);
|
||||
}
|
||||
|
||||
public function ajaxTotalVisitors()
|
||||
{
|
||||
$view = new View('@Live/ajaxTotalVisitors');
|
||||
|
|
@ -83,29 +56,25 @@ class Controller extends \Piwik\Plugin\Controller
|
|||
public function indexVisitorLog()
|
||||
{
|
||||
$view = new View('@Live/indexVisitorLog.twig');
|
||||
$view->filterEcommerce = Common::getRequestVar('filterEcommerce', 0, 'int');
|
||||
$view->visitorLog = $this->getLastVisitsDetails();
|
||||
$view->visitorLog = $this->renderReport('getLastVisitsDetails');
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
public function getLastVisitsDetails()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Widget
|
||||
*/
|
||||
public function getVisitorLog()
|
||||
{
|
||||
return $this->getLastVisitsDetails();
|
||||
return $this->renderReport('getLastVisitsDetails');
|
||||
}
|
||||
|
||||
public function getLastVisitsStart()
|
||||
{
|
||||
// hack, ensure we load today's visits by default
|
||||
$_GET['date'] = 'today';
|
||||
\Piwik\Period\Factory::checkPeriodIsEnabled('day');
|
||||
$_GET['period'] = 'day';
|
||||
|
||||
$view = new View('@Live/getLastVisitsStart');
|
||||
$view->idSite = $this->idSite;
|
||||
$api = new Request("method=Live.getLastVisitsDetails&idSite={$this->idSite}&filter_limit=10&format=php&serialize=0&disable_generic_filters=1");
|
||||
|
|
@ -118,9 +87,9 @@ class Controller extends \Piwik\Plugin\Controller
|
|||
private function setCounters($view)
|
||||
{
|
||||
$segment = Request::getRawSegmentFromRequest();
|
||||
$last30min = API::getInstance()->getCounters($this->idSite, $lastMinutes = 30, $segment);
|
||||
$last30min = API::getInstance()->getCounters($this->idSite, $lastMinutes = 30, $segment, array('visits', 'actions'));
|
||||
$last30min = $last30min[0];
|
||||
$today = API::getInstance()->getCounters($this->idSite, $lastMinutes = 24 * 60, $segment);
|
||||
$today = API::getInstance()->getCounters($this->idSite, $lastMinutes = 24 * 60, $segment, array('visits', 'actions'));
|
||||
$today = $today[0];
|
||||
$view->visitorsCountHalfHour = $last30min['visits'];
|
||||
$view->visitorsCountToday = $today['visits'];
|
||||
|
|
@ -139,7 +108,7 @@ class Controller extends \Piwik\Plugin\Controller
|
|||
$view = new View('@Live/getVisitorProfilePopup.twig');
|
||||
$view->idSite = $idSite;
|
||||
$view->goals = APIGoals::getInstance()->getGoals($idSite);
|
||||
$view->visitorData = Request::processRequest('Live.getVisitorProfile', array('checkForLatLong' => true));
|
||||
$view->visitorData = Request::processRequest('Live.getVisitorProfile');
|
||||
$view->exportLink = $this->getVisitorProfileExportLink();
|
||||
|
||||
if (Common::getRequestVar('showMap', 1) == 1
|
||||
|
|
@ -163,7 +132,7 @@ class Controller extends \Piwik\Plugin\Controller
|
|||
'date' => false
|
||||
));
|
||||
$view->visitData = $visits->getFirstRow()->getColumns();
|
||||
$view->visitReferralSummary = API::getReferrerSummaryForVisit($visits->getFirstRow());
|
||||
$view->visitReferralSummary = VisitorProfile::getReferrerSummaryForVisit($visits->getFirstRow());
|
||||
$view->showLocation = true;
|
||||
$this->setWidgetizedVisitorProfileUrl($view);
|
||||
$view->exportLink = $this->getVisitorProfileExportLink();
|
||||
|
|
@ -175,18 +144,20 @@ class Controller extends \Piwik\Plugin\Controller
|
|||
$startCounter = Common::getRequestVar('filter_offset', 0, 'int');
|
||||
$nextVisits = Request::processRequest('Live.getLastVisitsDetails', array(
|
||||
'segment' => self::getSegmentWithVisitorId(),
|
||||
'filter_limit' => API::VISITOR_PROFILE_MAX_VISITS_TO_SHOW,
|
||||
'filter_limit' => VisitorProfile::VISITOR_PROFILE_MAX_VISITS_TO_SHOW,
|
||||
'filter_offset' => $startCounter,
|
||||
'period' => false,
|
||||
'date' => false
|
||||
));
|
||||
|
||||
$idSite = Common::getRequestVar('idSite', null, 'int');
|
||||
|
||||
if (empty($nextVisits)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$view = new View('@Live/getVisitList.twig');
|
||||
$view->idSite = Common::getRequestVar('idSite', null, 'int');
|
||||
$view->idSite = $idSite;
|
||||
$view->startCounter = $startCounter + 1;
|
||||
$view->visits = $nextVisits;
|
||||
return $view->render();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,11 +8,7 @@
|
|||
*/
|
||||
namespace Piwik\Plugins\Live;
|
||||
|
||||
use Piwik\Menu\MenuMain;
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
|
||||
use Piwik\WidgetsList;
|
||||
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/Live/VisitorLog.php';
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -21,17 +17,14 @@ class Live extends \Piwik\Plugin
|
|||
{
|
||||
|
||||
/**
|
||||
* @see Piwik\Plugin::getListHooksRegistered
|
||||
* @see Piwik\Plugin::registerEvents
|
||||
*/
|
||||
public function getListHooksRegistered()
|
||||
public function registerEvents()
|
||||
{
|
||||
return array(
|
||||
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
|
||||
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
|
||||
'WidgetsList.addWidgets' => 'addWidget',
|
||||
'Menu.Reporting.addItems' => 'addMenu',
|
||||
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
|
||||
'ViewDataTable.getDefaultType' => 'getDefaultTypeViewDataTable'
|
||||
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -43,22 +36,12 @@ class Live extends \Piwik\Plugin
|
|||
|
||||
public function getJsFiles(&$jsFiles)
|
||||
{
|
||||
$jsFiles[] = "libs/bower_components/visibilityjs/lib/visibility.core.js";
|
||||
$jsFiles[] = "plugins/Live/javascripts/live.js";
|
||||
$jsFiles[] = "plugins/Live/javascripts/SegmentedVisitorLog.js";
|
||||
$jsFiles[] = "plugins/Live/javascripts/visitorProfile.js";
|
||||
$jsFiles[] = "plugins/Live/javascripts/visitorLog.js";
|
||||
}
|
||||
|
||||
public function addMenu()
|
||||
{
|
||||
MenuMain::getInstance()->add('General_Visitors', 'Live_VisitorLog', array('module' => 'Live', 'action' => 'indexVisitorLog'), true, $order = 5);
|
||||
}
|
||||
|
||||
public function addWidget()
|
||||
{
|
||||
WidgetsList::add('Live!', 'Live_VisitorsInRealTime', 'Live', 'widget');
|
||||
WidgetsList::add('Live!', 'Live_VisitorLog', 'Live', 'getVisitorLog', array('small' => 1));
|
||||
WidgetsList::add('Live!', 'Live_RealTimeVisitorCount', 'Live', 'getSimpleLastVisitCount');
|
||||
WidgetsList::add('Live!', 'Live_VisitorProfile', 'Live', 'getVisitorProfilePopup');
|
||||
$jsFiles[] = "plugins/Live/javascripts/rowaction.js";
|
||||
}
|
||||
|
||||
public function getClientSideTranslationKeys(&$translationKeys)
|
||||
|
|
@ -68,10 +51,11 @@ class Live extends \Piwik\Plugin
|
|||
$translationKeys[] = "Live_ShowMap";
|
||||
$translationKeys[] = "Live_HideMap";
|
||||
$translationKeys[] = "Live_PageRefreshed";
|
||||
}
|
||||
|
||||
public function getDefaultTypeViewDataTable(&$defaultViewTypes)
|
||||
{
|
||||
$defaultViewTypes['Live.getLastVisitsDetails'] = VisitorLog::ID;
|
||||
$translationKeys[] = "Live_RowActionTooltipTitle";
|
||||
$translationKeys[] = "Live_RowActionTooltipDefault";
|
||||
$translationKeys[] = "Live_RowActionTooltipWithDimension";
|
||||
$translationKeys[] = "Live_SegmentedVisitorLogTitle";
|
||||
$translationKeys[] = "General_Segment";
|
||||
$translationKeys[] = "General_And";
|
||||
}
|
||||
}
|
||||
506
www/analytics/plugins/Live/Model.php
Normal file
506
www/analytics/plugins/Live/Model.php
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\Live;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataAccess\LogAggregator;
|
||||
use Piwik\Date;
|
||||
use Piwik\Db;
|
||||
use Piwik\Period;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\CustomVariables\CustomVariables;
|
||||
use Piwik\Segment;
|
||||
use Piwik\Site;
|
||||
use Piwik\Tracker\GoalManager;
|
||||
|
||||
class Model
|
||||
{
|
||||
|
||||
/**
|
||||
* @param $idVisit
|
||||
* @param $actionsLimit
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function queryActionsForVisit($idVisit, $actionsLimit)
|
||||
{
|
||||
$maxCustomVariables = CustomVariables::getNumUsableCustomVariables();
|
||||
|
||||
$sqlCustomVariables = '';
|
||||
for ($i = 1; $i <= $maxCustomVariables; $i++) {
|
||||
$sqlCustomVariables .= ', custom_var_k' . $i . ', custom_var_v' . $i;
|
||||
}
|
||||
// The second join is a LEFT join to allow returning records that don't have a matching page title
|
||||
// eg. Downloads, Outlinks. For these, idaction_name is set to 0
|
||||
$sql = "
|
||||
SELECT
|
||||
COALESCE(log_action_event_category.type, log_action.type, log_action_title.type) AS type,
|
||||
log_action.name AS url,
|
||||
log_action.url_prefix,
|
||||
log_action_title.name AS pageTitle,
|
||||
log_action.idaction AS pageIdAction,
|
||||
log_link_visit_action.idlink_va,
|
||||
log_link_visit_action.server_time as serverTimePretty,
|
||||
log_link_visit_action.time_spent_ref_action as timeSpentRef,
|
||||
log_link_visit_action.idlink_va AS pageId,
|
||||
log_link_visit_action.custom_float
|
||||
" . $sqlCustomVariables . ",
|
||||
log_action_event_category.name AS eventCategory,
|
||||
log_action_event_action.name as eventAction
|
||||
FROM " . Common::prefixTable('log_link_visit_action') . " AS log_link_visit_action
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action
|
||||
ON log_link_visit_action.idaction_url = log_action.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_title
|
||||
ON log_link_visit_action.idaction_name = log_action_title.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_event_category
|
||||
ON log_link_visit_action.idaction_event_category = log_action_event_category.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_event_action
|
||||
ON log_link_visit_action.idaction_event_action = log_action_event_action.idaction
|
||||
WHERE log_link_visit_action.idvisit = ?
|
||||
ORDER BY server_time ASC
|
||||
LIMIT 0, $actionsLimit
|
||||
";
|
||||
$actionDetails = Db::fetchAll($sql, array($idVisit));
|
||||
return $actionDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idVisit
|
||||
* @param $limit
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function queryGoalConversionsForVisit($idVisit, $limit)
|
||||
{
|
||||
$sql = "
|
||||
SELECT
|
||||
'goal' as type,
|
||||
goal.name as goalName,
|
||||
goal.idgoal as goalId,
|
||||
log_conversion.revenue as revenue,
|
||||
log_conversion.idlink_va,
|
||||
log_conversion.idlink_va as goalPageId,
|
||||
log_conversion.server_time as serverTimePretty,
|
||||
log_conversion.url as url
|
||||
FROM " . Common::prefixTable('log_conversion') . " AS log_conversion
|
||||
LEFT JOIN " . Common::prefixTable('goal') . " AS goal
|
||||
ON (goal.idsite = log_conversion.idsite
|
||||
AND
|
||||
goal.idgoal = log_conversion.idgoal)
|
||||
AND goal.deleted = 0
|
||||
WHERE log_conversion.idvisit = ?
|
||||
AND log_conversion.idgoal > 0
|
||||
ORDER BY server_time ASC
|
||||
LIMIT 0, $limit
|
||||
";
|
||||
$goalDetails = Db::fetchAll($sql, array($idVisit));
|
||||
return $goalDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idVisit
|
||||
* @param $limit
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function queryEcommerceConversionsForVisit($idVisit, $limit)
|
||||
{
|
||||
$sql = "SELECT
|
||||
case idgoal when " . GoalManager::IDGOAL_CART
|
||||
. " then '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART
|
||||
. "' else '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER . "' end as type,
|
||||
idorder as orderId,
|
||||
" . LogAggregator::getSqlRevenue('revenue') . " as revenue,
|
||||
" . LogAggregator::getSqlRevenue('revenue_subtotal') . " as revenueSubTotal,
|
||||
" . LogAggregator::getSqlRevenue('revenue_tax') . " as revenueTax,
|
||||
" . LogAggregator::getSqlRevenue('revenue_shipping') . " as revenueShipping,
|
||||
" . LogAggregator::getSqlRevenue('revenue_discount') . " as revenueDiscount,
|
||||
items as items,
|
||||
log_conversion.server_time as serverTimePretty,
|
||||
log_conversion.idlink_va
|
||||
FROM " . Common::prefixTable('log_conversion') . " AS log_conversion
|
||||
WHERE idvisit = ?
|
||||
AND idgoal <= " . GoalManager::IDGOAL_ORDER . "
|
||||
ORDER BY server_time ASC
|
||||
LIMIT 0, $limit";
|
||||
$ecommerceDetails = Db::fetchAll($sql, array($idVisit));
|
||||
return $ecommerceDetails;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $idVisit
|
||||
* @param $idOrder
|
||||
* @param $actionsLimit
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function queryEcommerceItemsForOrder($idVisit, $idOrder, $actionsLimit)
|
||||
{
|
||||
$sql = "SELECT
|
||||
log_action_sku.name as itemSKU,
|
||||
log_action_name.name as itemName,
|
||||
log_action_category.name as itemCategory,
|
||||
" . LogAggregator::getSqlRevenue('price') . " as price,
|
||||
quantity as quantity
|
||||
FROM " . Common::prefixTable('log_conversion_item') . "
|
||||
INNER JOIN " . Common::prefixTable('log_action') . " AS log_action_sku
|
||||
ON idaction_sku = log_action_sku.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_name
|
||||
ON idaction_name = log_action_name.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_category
|
||||
ON idaction_category = log_action_category.idaction
|
||||
WHERE idvisit = ?
|
||||
AND idorder = ?
|
||||
AND deleted = 0
|
||||
LIMIT 0, $actionsLimit
|
||||
";
|
||||
|
||||
$bind = array($idVisit, $idOrder);
|
||||
|
||||
$itemsDetails = Db::fetchAll($sql, $bind);
|
||||
return $itemsDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idSite
|
||||
* @param $period
|
||||
* @param $date
|
||||
* @param $segment
|
||||
* @param $limit
|
||||
* @param $visitorId
|
||||
* @param $minTimestamp
|
||||
* @param $filterSortOrder
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function queryLogVisits($idSite, $period, $date, $segment, $offset, $limit, $visitorId, $minTimestamp, $filterSortOrder)
|
||||
{
|
||||
list($sql, $bind) = $this->makeLogVisitsQueryString($idSite, $period, $date, $segment, $offset, $limit, $visitorId, $minTimestamp, $filterSortOrder);
|
||||
|
||||
return Db::fetchAll($sql, $bind);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idSite
|
||||
* @param $lastMinutes
|
||||
* @param $segment
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getNumActions($idSite, $lastMinutes, $segment)
|
||||
{
|
||||
return $this->getLastMinutesCounterForQuery(
|
||||
$idSite,
|
||||
$lastMinutes,
|
||||
$segment,
|
||||
'COUNT(*)',
|
||||
'log_link_visit_action',
|
||||
'log_link_visit_action.server_time >= ?'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idSite
|
||||
* @param $lastMinutes
|
||||
* @param $segment
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getNumVisitsConverted($idSite, $lastMinutes, $segment)
|
||||
{
|
||||
return $this->getLastMinutesCounterForQuery(
|
||||
$idSite,
|
||||
$lastMinutes,
|
||||
$segment,
|
||||
'COUNT(*)',
|
||||
'log_conversion',
|
||||
'log_conversion.server_time >= ?'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idSite
|
||||
* @param $lastMinutes
|
||||
* @param $segment
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getNumVisits($idSite, $lastMinutes, $segment)
|
||||
{
|
||||
return $this->getLastMinutesCounterForQuery(
|
||||
$idSite,
|
||||
$lastMinutes,
|
||||
$segment,
|
||||
'COUNT(log_visit.visit_last_action_time)',
|
||||
'log_visit',
|
||||
'log_visit.visit_last_action_time >= ?'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idSite
|
||||
* @param $lastMinutes
|
||||
* @param $segment
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getNumVisitors($idSite, $lastMinutes, $segment)
|
||||
{
|
||||
return $this->getLastMinutesCounterForQuery(
|
||||
$idSite,
|
||||
$lastMinutes,
|
||||
$segment,
|
||||
'COUNT(DISTINCT log_visit.idvisitor)',
|
||||
'log_visit',
|
||||
'log_visit.visit_last_action_time >= ?'
|
||||
);
|
||||
}
|
||||
|
||||
private function getLastMinutesCounterForQuery($idSite, $lastMinutes, $segment, $select, $from, $where)
|
||||
{
|
||||
$lastMinutes = (int)$lastMinutes;
|
||||
|
||||
if (empty($lastMinutes)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
list($whereIdSites, $idSites) = $this->getIdSitesWhereClause($idSite, $from);
|
||||
|
||||
$bind = $idSites;
|
||||
$bind[] = Date::factory(time() - $lastMinutes * 60)->toString('Y-m-d H:i:s');
|
||||
|
||||
$where = $whereIdSites . "AND " . $where;
|
||||
|
||||
$segment = new Segment($segment, $idSite);
|
||||
$query = $segment->getSelectQuery($select, $from, $where, $bind);
|
||||
|
||||
$numVisitors = Db::fetchOne($query['sql'], $query['bind']);
|
||||
|
||||
return $numVisitors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idSite
|
||||
* @param string $table
|
||||
* @return array
|
||||
*/
|
||||
private function getIdSitesWhereClause($idSite, $table = 'log_visit')
|
||||
{
|
||||
$idSites = array($idSite);
|
||||
Piwik::postEvent('Live.API.getIdSitesString', array(&$idSites));
|
||||
|
||||
$idSitesBind = Common::getSqlStringFieldsArray($idSites);
|
||||
$whereClause = $table . ".idsite in ($idSitesBind) ";
|
||||
return array($whereClause, $idSites);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ID of a visitor that is adjacent to another visitor (by time of last action)
|
||||
* in the log_visit table.
|
||||
*
|
||||
* @param int $idSite The ID of the site whose visits should be looked at.
|
||||
* @param string $visitorId The ID of the visitor to get an adjacent visitor for.
|
||||
* @param string $visitLastActionTime The last action time of the latest visit for $visitorId.
|
||||
* @param string $segment
|
||||
* @param bool $getNext Whether to retrieve the next visitor or the previous visitor. The next
|
||||
* visitor will be the visitor that appears chronologically later in the
|
||||
* log_visit table. The previous visitor will be the visitor that appears
|
||||
* earlier.
|
||||
* @return string The hex visitor ID.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function queryAdjacentVisitorId($idSite, $visitorId, $visitLastActionTime, $segment, $getNext)
|
||||
{
|
||||
if ($getNext) {
|
||||
$visitLastActionTimeCondition = "sub.visit_last_action_time <= ?";
|
||||
$orderByDir = "DESC";
|
||||
} else {
|
||||
$visitLastActionTimeCondition = "sub.visit_last_action_time >= ?";
|
||||
$orderByDir = "ASC";
|
||||
}
|
||||
|
||||
$visitLastActionDate = Date::factory($visitLastActionTime);
|
||||
$dateOneDayAgo = $visitLastActionDate->subDay(1);
|
||||
$dateOneDayInFuture = $visitLastActionDate->addDay(1);
|
||||
|
||||
$select = "log_visit.idvisitor, MAX(log_visit.visit_last_action_time) as visit_last_action_time";
|
||||
$from = "log_visit";
|
||||
$where = "log_visit.idsite = ? AND log_visit.idvisitor <> ? AND visit_last_action_time >= ? and visit_last_action_time <= ?";
|
||||
$whereBind = array($idSite, @Common::hex2bin($visitorId), $dateOneDayAgo->toString('Y-m-d H:i:s'), $dateOneDayInFuture->toString('Y-m-d H:i:s'));
|
||||
$orderBy = "MAX(log_visit.visit_last_action_time) $orderByDir";
|
||||
$groupBy = "log_visit.idvisitor";
|
||||
|
||||
$segment = new Segment($segment, $idSite);
|
||||
$queryInfo = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy, $groupBy);
|
||||
|
||||
$sql = "SELECT sub.idvisitor, sub.visit_last_action_time FROM ({$queryInfo['sql']}) as sub
|
||||
WHERE $visitLastActionTimeCondition
|
||||
LIMIT 1";
|
||||
$bind = array_merge($queryInfo['bind'], array($visitLastActionTime));
|
||||
|
||||
$visitorId = Db::fetchOne($sql, $bind);
|
||||
if (!empty($visitorId)) {
|
||||
$visitorId = bin2hex($visitorId);
|
||||
}
|
||||
return $visitorId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idSite
|
||||
* @param $period
|
||||
* @param $date
|
||||
* @param $segment
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param $visitorId
|
||||
* @param $minTimestamp
|
||||
* @param $filterSortOrder
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function makeLogVisitsQueryString($idSite, $period, $date, $segment, $offset, $limit, $visitorId, $minTimestamp, $filterSortOrder)
|
||||
{
|
||||
// If no other filter, only look at the last 24 hours of stats
|
||||
if (empty($visitorId)
|
||||
&& empty($limit)
|
||||
&& empty($offset)
|
||||
&& empty($period)
|
||||
&& empty($date)
|
||||
) {
|
||||
$period = 'day';
|
||||
$date = 'yesterdaySameTime';
|
||||
}
|
||||
|
||||
|
||||
list($whereClause, $bindIdSites) = $this->getIdSitesWhereClause($idSite);
|
||||
|
||||
list($whereBind, $where) = $this->getWhereClauseAndBind($whereClause, $bindIdSites, $idSite, $period, $date, $visitorId, $minTimestamp);
|
||||
|
||||
if (strtolower($filterSortOrder) !== 'asc') {
|
||||
$filterSortOrder = 'DESC';
|
||||
}
|
||||
|
||||
$segment = new Segment($segment, $idSite);
|
||||
|
||||
// Subquery to use the indexes for ORDER BY
|
||||
$select = "log_visit.*";
|
||||
$from = "log_visit";
|
||||
$groupBy = false;
|
||||
$limit = $limit >= 1 ? (int)$limit : 0;
|
||||
$offset = $offset >= 1 ? (int)$offset : 0;
|
||||
|
||||
$orderBy = '';
|
||||
if (count($bindIdSites) <= 1) {
|
||||
$orderBy = 'idsite, ';
|
||||
}
|
||||
|
||||
$orderBy .= "visit_last_action_time " . $filterSortOrder;
|
||||
$orderByParent = "sub.visit_last_action_time " . $filterSortOrder;
|
||||
|
||||
$subQuery = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy, $groupBy, $limit, $offset);
|
||||
|
||||
$bind = $subQuery['bind'];
|
||||
// Group by idvisit so that a visitor converting 2 goals only appears once
|
||||
$sql = "
|
||||
SELECT sub.* FROM (
|
||||
" . $subQuery['sql'] . "
|
||||
) AS sub
|
||||
GROUP BY sub.idvisit
|
||||
ORDER BY $orderByParent
|
||||
";
|
||||
return array($sql, $bind);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idSite
|
||||
* @return Site
|
||||
*/
|
||||
protected function makeSite($idSite)
|
||||
{
|
||||
return new Site($idSite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $whereClause
|
||||
* @param array $bindIdSites
|
||||
* @param $idSite
|
||||
* @param $period
|
||||
* @param $date
|
||||
* @param $visitorId
|
||||
* @param $minTimestamp
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getWhereClauseAndBind($whereClause, $bindIdSites, $idSite, $period, $date, $visitorId, $minTimestamp)
|
||||
{
|
||||
$where = array();
|
||||
$where[] = $whereClause;
|
||||
$whereBind = $bindIdSites;
|
||||
|
||||
if (!empty($visitorId)) {
|
||||
$where[] = "log_visit.idvisitor = ? ";
|
||||
$whereBind[] = @Common::hex2bin($visitorId);
|
||||
}
|
||||
|
||||
if (!empty($minTimestamp)) {
|
||||
$where[] = "log_visit.visit_last_action_time > ? ";
|
||||
$whereBind[] = date("Y-m-d H:i:s", $minTimestamp);
|
||||
}
|
||||
|
||||
// SQL Filter with provided period
|
||||
if (!empty($period) && !empty($date)) {
|
||||
$currentSite = $this->makeSite($idSite);
|
||||
$currentTimezone = $currentSite->getTimezone();
|
||||
|
||||
$dateString = $date;
|
||||
if ($period == 'range') {
|
||||
$processedPeriod = new Range('range', $date);
|
||||
if ($parsedDate = Range::parseDateRange($date)) {
|
||||
$dateString = $parsedDate[2];
|
||||
}
|
||||
} else {
|
||||
$processedDate = Date::factory($date);
|
||||
if ($date == 'today'
|
||||
|| $date == 'now'
|
||||
|| $processedDate->toString() == Date::factory('now', $currentTimezone)->toString()
|
||||
) {
|
||||
$processedDate = $processedDate->subDay(1);
|
||||
}
|
||||
$processedPeriod = Period\Factory::build($period, $processedDate);
|
||||
}
|
||||
$dateStart = $processedPeriod->getDateStart()->setTimezone($currentTimezone);
|
||||
$where[] = "log_visit.visit_last_action_time >= ?";
|
||||
$whereBind[] = $dateStart->toString('Y-m-d H:i:s');
|
||||
|
||||
if (!in_array($date, array('now', 'today', 'yesterdaySameTime'))
|
||||
&& strpos($date, 'last') === false
|
||||
&& strpos($date, 'previous') === false
|
||||
&& Date::factory($dateString)->toString('Y-m-d') != Date::factory('now', $currentTimezone)->toString()
|
||||
) {
|
||||
$dateEnd = $processedPeriod->getDateEnd()->setTimezone($currentTimezone);
|
||||
$where[] = " log_visit.visit_last_action_time <= ?";
|
||||
$dateEndString = $dateEnd->addDay(1)->toString('Y-m-d H:i:s');
|
||||
$whereBind[] = $dateEndString;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($where) > 0) {
|
||||
$where = join("
|
||||
AND ", $where);
|
||||
} else {
|
||||
$where = false;
|
||||
}
|
||||
return array($whereBind, $where);
|
||||
}
|
||||
}
|
||||
21
www/analytics/plugins/Live/Reports/Base.php
Normal file
21
www/analytics/plugins/Live/Reports/Base.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Live\Reports;
|
||||
|
||||
abstract class Base extends \Piwik\Plugin\Report
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->category = 'Live!';
|
||||
}
|
||||
|
||||
public function configureReportMetadata(&$availableReports, $infos)
|
||||
{
|
||||
}
|
||||
}
|
||||
22
www/analytics/plugins/Live/Reports/GetLastVisits.php
Normal file
22
www/analytics/plugins/Live/Reports/GetLastVisits.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Live\Reports;
|
||||
|
||||
|
||||
class GetLastVisits extends Base
|
||||
{
|
||||
// this class only exists to disable the default sort column
|
||||
protected $defaultSortColumn = '';
|
||||
|
||||
public function buildReportMetadata()
|
||||
{
|
||||
// do not add this report as metadata
|
||||
}
|
||||
|
||||
}
|
||||
50
www/analytics/plugins/Live/Reports/GetLastVisitsDetails.php
Normal file
50
www/analytics/plugins/Live/Reports/GetLastVisitsDetails.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Live\Reports;
|
||||
|
||||
use Piwik\Menu\MenuReporting;
|
||||
use Piwik\Plugin\Report;
|
||||
use Piwik\Plugins\Live\Visualizations\VisitorLog;
|
||||
use Piwik\WidgetsList;
|
||||
|
||||
class GetLastVisitsDetails extends Base
|
||||
{
|
||||
protected $defaultSortColumn = '';
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->widgetTitle = 'Live_VisitorLog';
|
||||
$this->order = 2;
|
||||
}
|
||||
|
||||
public function getDefaultTypeViewDataTable()
|
||||
{
|
||||
return VisitorLog::ID;
|
||||
}
|
||||
|
||||
public function alwaysUseDefaultViewDataTable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function configureReportingMenu(MenuReporting $menu)
|
||||
{
|
||||
if ($this->isEnabled()) {
|
||||
$url = array('module' => $this->module, 'action' => 'indexVisitorLog');
|
||||
$menu->addVisitorsItem($this->widgetTitle, $url, $order = 5);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureWidget(WidgetsList $widget)
|
||||
{
|
||||
$widget->add($this->category, $this->widgetTitle, $this->module, 'getVisitorLog', array('small' => 1));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Live\Reports;
|
||||
|
||||
use Piwik\Config;
|
||||
use Piwik\Metrics\Formatter;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\Report;
|
||||
use Piwik\Plugins\Live\Controller;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\View;
|
||||
|
||||
class GetSimpleLastVisitCount extends Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->widgetTitle = 'Live_RealTimeVisitorCount';
|
||||
$this->order = 3;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$lastMinutes = Config::getInstance()->General[Controller::SIMPLE_VISIT_COUNT_WIDGET_LAST_MINUTES_CONFIG_KEY];
|
||||
|
||||
$params = array('lastMinutes' => $lastMinutes, 'showColumns' => array('visits', 'visitors', 'actions'));
|
||||
$lastNData = Request::processRequest('Live.getCounters', $params);
|
||||
|
||||
$formatter = new Formatter();
|
||||
|
||||
$view = new View('@Live/getSimpleLastVisitCount');
|
||||
$view->lastMinutes = $lastMinutes;
|
||||
$view->visitors = $formatter->getPrettyNumber($lastNData[0]['visitors']);
|
||||
$view->visits = $formatter->getPrettyNumber($lastNData[0]['visits']);
|
||||
$view->actions = $formatter->getPrettyNumber($lastNData[0]['actions']);
|
||||
$view->refreshAfterXSecs = Config::getInstance()->General['live_widget_refresh_after_seconds'];
|
||||
$view->translations = array(
|
||||
'one_visitor' => Piwik::translate('Live_NbVisitor'),
|
||||
'visitors' => Piwik::translate('Live_NbVisitors'),
|
||||
'one_visit' => Piwik::translate('General_OneVisit'),
|
||||
'visits' => Piwik::translate('General_NVisits'),
|
||||
'one_action' => Piwik::translate('General_OneAction'),
|
||||
'actions' => Piwik::translate('VisitsSummary_NbActionsDescription'),
|
||||
'one_minute' => Piwik::translate('Intl_OneMinute'),
|
||||
'minutes' => Piwik::translate('Intl_NMinutes')
|
||||
);
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -9,42 +9,25 @@
|
|||
namespace Piwik\Plugins\Live;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\DataAccess\LogAggregator;
|
||||
use Piwik\DataTable\Filter\ColumnDelete;
|
||||
use Piwik\Date;
|
||||
use Piwik\Db;
|
||||
use Piwik\IP;
|
||||
use Piwik\Metrics\Formatter;
|
||||
use Piwik\Network\IPUtils;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\API\API as APIMetadata;
|
||||
use Piwik\Plugins\CustomVariables\CustomVariables;
|
||||
use Piwik\Plugins\Referrers\API as APIReferrers;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
|
||||
use Piwik\Plugins\Actions\Actions\ActionSiteSearch;
|
||||
use Piwik\Tracker;
|
||||
use Piwik\Tracker\Action;
|
||||
use Piwik\Tracker\GoalManager;
|
||||
use Piwik\Tracker;
|
||||
use Piwik\Tracker\Visit;
|
||||
use Piwik\UrlHelper;
|
||||
|
||||
/**
|
||||
* @see plugins/Referrers/functions.php
|
||||
* @see plugins/UserCountry/functions.php
|
||||
* @see plugins/UserSettings/functions.php
|
||||
* @see plugins/Provider/functions.php
|
||||
*/
|
||||
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/Referrers/functions.php';
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/functions.php';
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserSettings/functions.php';
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/Provider/functions.php';
|
||||
|
||||
/**
|
||||
*/
|
||||
class Visitor
|
||||
class Visitor implements VisitorInterface
|
||||
{
|
||||
const DELIMITER_PLUGIN_NAME = ", ";
|
||||
|
||||
const EVENT_VALUE_PRECISION = 3;
|
||||
|
||||
private $details = array();
|
||||
|
||||
function __construct($visitorRawData)
|
||||
{
|
||||
$this->details = $visitorRawData;
|
||||
|
|
@ -52,84 +35,45 @@ class Visitor
|
|||
|
||||
function getAllVisitorDetails()
|
||||
{
|
||||
return array(
|
||||
$visitor = array(
|
||||
'idSite' => $this->getIdSite(),
|
||||
'idVisit' => $this->getIdVisit(),
|
||||
'visitIp' => $this->getIp(),
|
||||
'visitorId' => $this->getVisitorId(),
|
||||
'visitorType' => $this->getVisitorReturning(),
|
||||
'visitorTypeIcon' => $this->getVisitorReturningIcon(),
|
||||
'visitConverted' => $this->isVisitorGoalConverted(),
|
||||
'visitConvertedIcon' => $this->getVisitorGoalConvertedIcon(),
|
||||
'visitEcommerceStatus' => $this->getVisitEcommerceStatus(),
|
||||
'visitEcommerceStatusIcon' => $this->getVisitEcommerceStatusIcon(),
|
||||
|
||||
'searches' => $this->getNumberOfSearches(),
|
||||
'events' => $this->getNumberOfEvents(),
|
||||
'actions' => $this->getNumberOfActions(),
|
||||
// => false are placeholders to be filled in API later
|
||||
'actionDetails' => false,
|
||||
'customVariables' => $this->getCustomVariables(),
|
||||
'goalConversions' => false,
|
||||
'siteCurrency' => false,
|
||||
'siteCurrencySymbol' => false,
|
||||
|
||||
// all time entries
|
||||
'serverDate' => $this->getServerDate(),
|
||||
'visitLocalTime' => $this->getVisitLocalTime(),
|
||||
'visitLocalHour' => $this->getVisitLocalHour(),
|
||||
'visitServerHour' => $this->getVisitServerHour(),
|
||||
'firstActionTimestamp' => $this->getTimestampFirstAction(),
|
||||
'lastActionTimestamp' => $this->getTimestampLastAction(),
|
||||
'lastActionDateTime' => $this->getDateTimeLastAction(),
|
||||
|
||||
// standard attributes
|
||||
'visitDuration' => $this->getVisitLength(),
|
||||
'visitDurationPretty' => $this->getVisitLengthPretty(),
|
||||
'visitCount' => $this->getVisitCount(),
|
||||
'daysSinceLastVisit' => $this->getDaysSinceLastVisit(),
|
||||
'daysSinceFirstVisit' => $this->getDaysSinceFirstVisit(),
|
||||
'daysSinceLastEcommerceOrder' => $this->getDaysSinceLastEcommerceOrder(),
|
||||
'continent' => $this->getContinent(),
|
||||
'continentCode' => $this->getContinentCode(),
|
||||
'country' => $this->getCountryName(),
|
||||
'countryCode' => $this->getCountryCode(),
|
||||
'countryFlag' => $this->getCountryFlag(),
|
||||
'region' => $this->getRegionName(),
|
||||
'regionCode' => $this->getRegionCode(),
|
||||
'city' => $this->getCityName(),
|
||||
'location' => $this->getPrettyLocation(),
|
||||
'latitude' => $this->getLatitude(),
|
||||
'longitude' => $this->getLongitude(),
|
||||
'provider' => $this->getProvider(),
|
||||
'providerName' => $this->getProviderName(),
|
||||
'providerUrl' => $this->getProviderUrl(),
|
||||
|
||||
'referrerType' => $this->getReferrerType(),
|
||||
'referrerTypeName' => $this->getReferrerTypeName(),
|
||||
'referrerName' => $this->getReferrerName(),
|
||||
'referrerKeyword' => $this->getKeyword(),
|
||||
'referrerKeywordPosition' => $this->getKeywordPosition(),
|
||||
'referrerUrl' => $this->getReferrerUrl(),
|
||||
'referrerSearchEngineUrl' => $this->getSearchEngineUrl(),
|
||||
'referrerSearchEngineIcon' => $this->getSearchEngineIcon(),
|
||||
'operatingSystem' => $this->getOperatingSystem(),
|
||||
'operatingSystemCode' => $this->getOperatingSystemCode(),
|
||||
'operatingSystemShortName' => $this->getOperatingSystemShortName(),
|
||||
'operatingSystemIcon' => $this->getOperatingSystemIcon(),
|
||||
'browserFamily' => $this->getBrowserFamily(),
|
||||
'browserFamilyDescription' => $this->getBrowserFamilyDescription(),
|
||||
'browserName' => $this->getBrowser(),
|
||||
'browserIcon' => $this->getBrowserIcon(),
|
||||
'browserCode' => $this->getBrowserCode(),
|
||||
'browserVersion' => $this->getBrowserVersion(),
|
||||
'screenType' => $this->getScreenType(),
|
||||
'deviceType' => $this->getDeviceType(),
|
||||
'resolution' => $this->getResolution(),
|
||||
'screenTypeIcon' => $this->getScreenTypeIcon(),
|
||||
'plugins' => $this->getPlugins(),
|
||||
'pluginsIcons' => $this->getPluginIcons(),
|
||||
);
|
||||
|
||||
/**
|
||||
* This event can be used to add any details to a visitor. The visitor's details are for instance used in
|
||||
* API requests like 'Live.getVisitorProfile' and 'Live.getLastVisitDetails'. This can be useful for instance
|
||||
* in case your plugin defines any visit dimensions and you want to add the value of your dimension to a user.
|
||||
* It can be also useful if you want to enrich a visitor with custom fields based on other fields or if you
|
||||
* want to change or remove any fields from the user.
|
||||
*
|
||||
* **Example**
|
||||
*
|
||||
* Piwik::addAction('Live.getAllVisitorDetails', function (&visitor, $details) {
|
||||
* $visitor['userPoints'] = $details['actions'] + $details['events'] + $details['searches'];
|
||||
* unset($visitor['anyFieldYouWantToRemove']);
|
||||
* });
|
||||
*
|
||||
* @param array &visitor You can add or remove fields to the visitor array and it will reflected in the API output
|
||||
* @param array $details The details array contains all visit dimensions (columns of log_visit table)
|
||||
*/
|
||||
Piwik::postEvent('Live.getAllVisitorDetails', array(&$visitor, $this->details));
|
||||
|
||||
return $visitor;
|
||||
}
|
||||
|
||||
function getVisitorId()
|
||||
|
|
@ -140,41 +84,11 @@ class Visitor
|
|||
return false;
|
||||
}
|
||||
|
||||
function getVisitLocalTime()
|
||||
{
|
||||
return $this->details['visitor_localtime'];
|
||||
}
|
||||
|
||||
function getVisitServerHour()
|
||||
{
|
||||
return date('G', strtotime($this->details['visit_last_action_time']));
|
||||
}
|
||||
|
||||
function getVisitLocalHour()
|
||||
{
|
||||
return date('G', strtotime('2012-12-21 ' . $this->details['visitor_localtime']));
|
||||
}
|
||||
|
||||
function getVisitCount()
|
||||
{
|
||||
return $this->details['visitor_count_visits'];
|
||||
}
|
||||
|
||||
function getDaysSinceLastVisit()
|
||||
{
|
||||
return $this->details['visitor_days_since_last'];
|
||||
}
|
||||
|
||||
function getDaysSinceLastEcommerceOrder()
|
||||
{
|
||||
return $this->details['visitor_days_since_order'];
|
||||
}
|
||||
|
||||
function getDaysSinceFirstVisit()
|
||||
{
|
||||
return $this->details['visitor_days_since_first'];
|
||||
}
|
||||
|
||||
function getServerDate()
|
||||
{
|
||||
return date('Y-m-d', strtotime($this->details['visit_last_action_time']));
|
||||
|
|
@ -183,9 +97,9 @@ class Visitor
|
|||
function getIp()
|
||||
{
|
||||
if (isset($this->details['location_ip'])) {
|
||||
return IP::N2P($this->details['location_ip']);
|
||||
return IPUtils::binaryToStringIP($this->details['location_ip']);
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
function getIdVisit()
|
||||
|
|
@ -198,412 +112,16 @@ class Visitor
|
|||
return $this->details['idsite'];
|
||||
}
|
||||
|
||||
function getNumberOfActions()
|
||||
{
|
||||
return $this->details['visit_total_actions'];
|
||||
}
|
||||
|
||||
function getNumberOfEvents()
|
||||
{
|
||||
return $this->details['visit_total_events'];
|
||||
}
|
||||
|
||||
function getNumberOfSearches()
|
||||
{
|
||||
return $this->details['visit_total_searches'];
|
||||
}
|
||||
|
||||
function getVisitLength()
|
||||
{
|
||||
return $this->details['visit_total_time'];
|
||||
}
|
||||
|
||||
function getVisitLengthPretty()
|
||||
{
|
||||
return \Piwik\MetricsFormatter::getPrettyTimeFromSeconds($this->details['visit_total_time']);
|
||||
}
|
||||
|
||||
function getVisitorReturning()
|
||||
{
|
||||
$type = $this->details['visitor_returning'];
|
||||
return $type == 2
|
||||
? 'returningCustomer'
|
||||
: ($type == 1
|
||||
? 'returning'
|
||||
: 'new');
|
||||
}
|
||||
|
||||
function getVisitorReturningIcon()
|
||||
{
|
||||
$type = $this->getVisitorReturning();
|
||||
if ($type == 'returning'
|
||||
|| $type == 'returningCustomer'
|
||||
) {
|
||||
return "plugins/Live/images/returningVisitor.gif";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getTimestampFirstAction()
|
||||
{
|
||||
return strtotime($this->details['visit_first_action_time']);
|
||||
}
|
||||
|
||||
function getTimestampLastAction()
|
||||
{
|
||||
return strtotime($this->details['visit_last_action_time']);
|
||||
}
|
||||
|
||||
function getCountryCode()
|
||||
{
|
||||
return $this->details['location_country'];
|
||||
}
|
||||
|
||||
function getCountryName()
|
||||
{
|
||||
return \Piwik\Plugins\UserCountry\countryTranslate($this->getCountryCode());
|
||||
}
|
||||
|
||||
function getCountryFlag()
|
||||
{
|
||||
return \Piwik\Plugins\UserCountry\getFlagFromCode($this->getCountryCode());
|
||||
}
|
||||
|
||||
function getContinent()
|
||||
{
|
||||
return \Piwik\Plugins\UserCountry\continentTranslate($this->getContinentCode());
|
||||
}
|
||||
|
||||
function getContinentCode()
|
||||
{
|
||||
return Common::getContinent($this->details['location_country']);
|
||||
}
|
||||
|
||||
function getCityName()
|
||||
{
|
||||
if (!empty($this->details['location_city'])) {
|
||||
return $this->details['location_city'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRegionName()
|
||||
{
|
||||
$region = $this->getRegionCode();
|
||||
if ($region != '' && $region != Visit::UNKNOWN_CODE) {
|
||||
return GeoIp::getRegionNameFromCodes(
|
||||
$this->details['location_country'], $region);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRegionCode()
|
||||
{
|
||||
return $this->details['location_region'];
|
||||
}
|
||||
|
||||
function getPrettyLocation()
|
||||
{
|
||||
$parts = array();
|
||||
|
||||
$city = $this->getCityName();
|
||||
if (!empty($city)) {
|
||||
$parts[] = $city;
|
||||
}
|
||||
$region = $this->getRegionName();
|
||||
if (!empty($region)) {
|
||||
$parts[] = $region;
|
||||
}
|
||||
|
||||
// add country & return concatenated result
|
||||
$parts[] = $this->getCountryName();
|
||||
return implode(', ', $parts);
|
||||
}
|
||||
|
||||
function getLatitude()
|
||||
{
|
||||
if (!empty($this->details['location_latitude'])) {
|
||||
return $this->details['location_latitude'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getLongitude()
|
||||
{
|
||||
if (!empty($this->details['location_longitude'])) {
|
||||
return $this->details['location_longitude'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getCustomVariables()
|
||||
{
|
||||
$customVariables = array();
|
||||
|
||||
$maxCustomVariables = CustomVariables::getMaxCustomVariables();
|
||||
|
||||
for ($i = 1; $i <= $maxCustomVariables; $i++) {
|
||||
if (!empty($this->details['custom_var_k' . $i])) {
|
||||
$customVariables[$i] = array(
|
||||
'customVariableName' . $i => $this->details['custom_var_k' . $i],
|
||||
'customVariableValue' . $i => $this->details['custom_var_v' . $i],
|
||||
);
|
||||
}
|
||||
}
|
||||
return $customVariables;
|
||||
}
|
||||
|
||||
function getReferrerType()
|
||||
{
|
||||
return \Piwik\Plugins\Referrers\getReferrerTypeFromShortName($this->details['referer_type']);
|
||||
}
|
||||
|
||||
function getReferrerTypeName()
|
||||
{
|
||||
return \Piwik\Plugins\Referrers\getReferrerTypeLabel($this->details['referer_type']);
|
||||
}
|
||||
|
||||
function getKeyword()
|
||||
{
|
||||
$keyword = $this->details['referer_keyword'];
|
||||
if (\Piwik\Plugin\Manager::getInstance()->isPluginActivated('Referrers')
|
||||
&& $this->getReferrerType() == 'search'
|
||||
) {
|
||||
$keyword = \Piwik\Plugins\Referrers\API::getCleanKeyword($keyword);
|
||||
}
|
||||
return urldecode($keyword);
|
||||
}
|
||||
|
||||
function getReferrerUrl()
|
||||
{
|
||||
if ($this->getReferrerType() == 'search') {
|
||||
if (\Piwik\Plugin\Manager::getInstance()->isPluginActivated('Referrers')
|
||||
&& $this->details['referer_keyword'] == APIReferrers::LABEL_KEYWORD_NOT_DEFINED
|
||||
) {
|
||||
return 'http://piwik.org/faq/general/#faq_144';
|
||||
} // Case URL is google.XX/url.... then we rewrite to the search result page url
|
||||
elseif ($this->getReferrerName() == 'Google'
|
||||
&& strpos($this->details['referer_url'], '/url')
|
||||
) {
|
||||
$refUrl = @parse_url($this->details['referer_url']);
|
||||
if (isset($refUrl['host'])) {
|
||||
$url = \Piwik\Plugins\Referrers\getSearchEngineUrlFromUrlAndKeyword('http://google.com', $this->getKeyword());
|
||||
$url = str_replace('google.com', $refUrl['host'], $url);
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (\Piwik\UrlHelper::isLookLikeUrl($this->details['referer_url'])) {
|
||||
return $this->details['referer_url'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getKeywordPosition()
|
||||
{
|
||||
if ($this->getReferrerType() == 'search'
|
||||
&& strpos($this->getReferrerName(), 'Google') !== false
|
||||
) {
|
||||
$url = @parse_url($this->details['referer_url']);
|
||||
if (empty($url['query'])) {
|
||||
return null;
|
||||
}
|
||||
$position = UrlHelper::getParameterFromQueryString($url['query'], 'cd');
|
||||
if (!empty($position)) {
|
||||
return $position;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getReferrerName()
|
||||
{
|
||||
return urldecode($this->details['referer_name']);
|
||||
}
|
||||
|
||||
function getSearchEngineUrl()
|
||||
{
|
||||
if ($this->getReferrerType() == 'search'
|
||||
&& !empty($this->details['referer_name'])
|
||||
) {
|
||||
return \Piwik\Plugins\Referrers\getSearchEngineUrlFromName($this->details['referer_name']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getSearchEngineIcon()
|
||||
{
|
||||
$searchEngineUrl = $this->getSearchEngineUrl();
|
||||
if (!is_null($searchEngineUrl)) {
|
||||
return \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl($searchEngineUrl);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPlugins()
|
||||
{
|
||||
$plugins = array(
|
||||
'config_pdf',
|
||||
'config_flash',
|
||||
'config_java',
|
||||
'config_director',
|
||||
'config_quicktime',
|
||||
'config_realplayer',
|
||||
'config_windowsmedia',
|
||||
'config_gears',
|
||||
'config_silverlight',
|
||||
);
|
||||
$pluginShortNames = array();
|
||||
foreach ($plugins as $plugin) {
|
||||
if ($this->details[$plugin] == 1) {
|
||||
$pluginShortName = substr($plugin, 7);
|
||||
$pluginShortNames[] = $pluginShortName;
|
||||
}
|
||||
}
|
||||
return implode(self::DELIMITER_PLUGIN_NAME, $pluginShortNames);
|
||||
}
|
||||
|
||||
function getPluginIcons()
|
||||
{
|
||||
$pluginNames = $this->getPlugins();
|
||||
if (!empty($pluginNames)) {
|
||||
$pluginNames = explode(self::DELIMITER_PLUGIN_NAME, $pluginNames);
|
||||
$pluginIcons = array();
|
||||
|
||||
foreach ($pluginNames as $plugin) {
|
||||
$pluginIcons[] = array("pluginIcon" => \Piwik\Plugins\UserSettings\getPluginsLogo($plugin), "pluginName" => $plugin);
|
||||
}
|
||||
return $pluginIcons;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getOperatingSystemCode()
|
||||
{
|
||||
return $this->details['config_os'];
|
||||
}
|
||||
|
||||
function getOperatingSystem()
|
||||
{
|
||||
return \Piwik\Plugins\UserSettings\getOSLabel($this->details['config_os']);
|
||||
}
|
||||
|
||||
function getOperatingSystemShortName()
|
||||
{
|
||||
return \Piwik\Plugins\UserSettings\getOSShortLabel($this->details['config_os']);
|
||||
}
|
||||
|
||||
function getOperatingSystemIcon()
|
||||
{
|
||||
return \Piwik\Plugins\UserSettings\getOSLogo($this->details['config_os']);
|
||||
}
|
||||
|
||||
function getBrowserFamilyDescription()
|
||||
{
|
||||
return \Piwik\Plugins\UserSettings\getBrowserTypeLabel($this->getBrowserFamily());
|
||||
}
|
||||
|
||||
function getBrowserFamily()
|
||||
{
|
||||
return \Piwik\Plugins\UserSettings\getBrowserFamily($this->details['config_browser_name']);
|
||||
}
|
||||
|
||||
function getBrowserCode()
|
||||
{
|
||||
return $this->details['config_browser_name'];
|
||||
}
|
||||
|
||||
function getBrowserVersion()
|
||||
{
|
||||
return $this->details['config_browser_version'];
|
||||
}
|
||||
|
||||
function getBrowser()
|
||||
{
|
||||
return \Piwik\Plugins\UserSettings\getBrowserLabel($this->details['config_browser_name'] . ";" . $this->details['config_browser_version']);
|
||||
}
|
||||
|
||||
function getBrowserIcon()
|
||||
{
|
||||
return \Piwik\Plugins\UserSettings\getBrowsersLogo($this->details['config_browser_name'] . ";" . $this->details['config_browser_version']);
|
||||
}
|
||||
|
||||
function getScreenType()
|
||||
{
|
||||
return \Piwik\Plugins\UserSettings\getScreenTypeFromResolution($this->details['config_resolution']);
|
||||
}
|
||||
|
||||
function getDeviceType()
|
||||
{
|
||||
if (\Piwik\Plugin\Manager::getInstance()->isPluginActivated('DevicesDetection')) {
|
||||
return \Piwik\Plugins\DevicesDetection\getDeviceTypeLabel($this->details['config_device_type']);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getResolution()
|
||||
{
|
||||
return $this->details['config_resolution'];
|
||||
}
|
||||
|
||||
function getScreenTypeIcon()
|
||||
{
|
||||
return \Piwik\Plugins\UserSettings\getScreensLogo($this->getScreenType());
|
||||
}
|
||||
|
||||
function getProvider()
|
||||
{
|
||||
if (isset($this->details['location_provider'])) {
|
||||
return $this->details['location_provider'];
|
||||
} else {
|
||||
return Piwik::translate('General_Unknown');
|
||||
}
|
||||
}
|
||||
|
||||
function getProviderName()
|
||||
{
|
||||
return \Piwik\Plugins\Provider\getPrettyProviderName($this->getProvider());
|
||||
}
|
||||
|
||||
function getProviderUrl()
|
||||
{
|
||||
return \Piwik\Plugins\Provider\getHostnameUrl(@$this->details['location_provider']);
|
||||
}
|
||||
|
||||
function getDateTimeLastAction()
|
||||
{
|
||||
return date('Y-m-d H:i:s', strtotime($this->details['visit_last_action_time']));
|
||||
}
|
||||
|
||||
function getVisitEcommerceStatusIcon()
|
||||
{
|
||||
$status = $this->getVisitEcommerceStatus();
|
||||
|
||||
if (in_array($status, array('ordered', 'orderedThenAbandonedCart'))) {
|
||||
return "plugins/Zeitgeist/images/ecommerceOrder.gif";
|
||||
} elseif ($status == 'abandonedCart') {
|
||||
return "plugins/Zeitgeist/images/ecommerceAbandonedCart.gif";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getVisitEcommerceStatus()
|
||||
{
|
||||
return APIMetadata::getVisitEcommerceStatusFromId($this->details['visit_goal_buyer']);
|
||||
}
|
||||
|
||||
function getVisitorGoalConvertedIcon()
|
||||
{
|
||||
return $this->isVisitorGoalConverted()
|
||||
? "plugins/Zeitgeist/images/goal.png"
|
||||
: null;
|
||||
}
|
||||
|
||||
function isVisitorGoalConverted()
|
||||
{
|
||||
return $this->details['visit_goal_converted'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes fields that are not meant to be displayed (md5 config hash)
|
||||
* Or that the user should only access if he is Super User or admin (cookie, IP)
|
||||
|
|
@ -616,6 +134,7 @@ class Visitor
|
|||
$toUnset = array('config_id');
|
||||
if (Piwik::isUserIsAnonymous()) {
|
||||
$toUnset[] = 'idvisitor';
|
||||
$toUnset[] = 'user_id';
|
||||
$toUnset[] = 'location_ip';
|
||||
}
|
||||
foreach ($toUnset as $keyName) {
|
||||
|
|
@ -673,12 +192,21 @@ class Visitor
|
|||
// Flatten Page Titles/URLs
|
||||
$count = 1;
|
||||
foreach ($visitorDetailsArray['actionDetails'] as $action) {
|
||||
if (!empty($action['url'])) {
|
||||
$flattenedKeyName = 'pageUrl' . ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP . $count;
|
||||
$visitorDetailsArray[$flattenedKeyName] = $action['url'];
|
||||
}
|
||||
|
||||
// API.getSuggestedValuesForSegment
|
||||
$flattenForActionType = array(
|
||||
'outlink' => 'outlinkUrl',
|
||||
'download' => 'downloadUrl',
|
||||
'action' => 'pageUrl'
|
||||
);
|
||||
foreach($flattenForActionType as $actionType => $flattenedKeyPrefix) {
|
||||
if (!empty($action['url'])
|
||||
&& $action['type'] == $actionType) {
|
||||
$flattenedKeyName = $flattenedKeyPrefix . ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP . $count;
|
||||
$visitorDetailsArray[$flattenedKeyName] = $action['url'];
|
||||
}
|
||||
}
|
||||
|
||||
$flatten = array( 'pageTitle', 'siteSearchKeyword', 'eventCategory', 'eventAction', 'eventName', 'eventValue');
|
||||
foreach($flatten as $toFlatten) {
|
||||
if (!empty($action[$toFlatten])) {
|
||||
|
|
@ -713,7 +241,6 @@ class Visitor
|
|||
$visitorDetailsArray['exitPageUrl'] = $lastAction['url'];
|
||||
}
|
||||
|
||||
|
||||
return $visitorDetailsArray;
|
||||
}
|
||||
|
||||
|
|
@ -727,49 +254,16 @@ class Visitor
|
|||
{
|
||||
$idVisit = $visitorDetailsArray['idVisit'];
|
||||
|
||||
$maxCustomVariables = CustomVariables::getMaxCustomVariables();
|
||||
$model = new Model();
|
||||
$actionDetails = $model->queryActionsForVisit($idVisit, $actionsLimit);
|
||||
|
||||
$sqlCustomVariables = '';
|
||||
for ($i = 1; $i <= $maxCustomVariables; $i++) {
|
||||
$sqlCustomVariables .= ', custom_var_k' . $i . ', custom_var_v' . $i;
|
||||
}
|
||||
// The second join is a LEFT join to allow returning records that don't have a matching page title
|
||||
// eg. Downloads, Outlinks. For these, idaction_name is set to 0
|
||||
$sql = "
|
||||
SELECT
|
||||
COALESCE(log_action_event_category.type, log_action.type, log_action_title.type) AS type,
|
||||
log_action.name AS url,
|
||||
log_action.url_prefix,
|
||||
log_action_title.name AS pageTitle,
|
||||
log_action.idaction AS pageIdAction,
|
||||
log_link_visit_action.server_time as serverTimePretty,
|
||||
log_link_visit_action.time_spent_ref_action as timeSpentRef,
|
||||
log_link_visit_action.idlink_va AS pageId,
|
||||
log_link_visit_action.custom_float
|
||||
". $sqlCustomVariables . ",
|
||||
log_action_event_category.name AS eventCategory,
|
||||
log_action_event_action.name as eventAction
|
||||
FROM " . Common::prefixTable('log_link_visit_action') . " AS log_link_visit_action
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action
|
||||
ON log_link_visit_action.idaction_url = log_action.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_title
|
||||
ON log_link_visit_action.idaction_name = log_action_title.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_event_category
|
||||
ON log_link_visit_action.idaction_event_category = log_action_event_category.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_event_action
|
||||
ON log_link_visit_action.idaction_event_action = log_action_event_action.idaction
|
||||
WHERE log_link_visit_action.idvisit = ?
|
||||
ORDER BY server_time ASC
|
||||
LIMIT 0, $actionsLimit
|
||||
";
|
||||
$actionDetails = Db::fetchAll($sql, array($idVisit));
|
||||
$formatter = new Formatter();
|
||||
$maxCustomVariables = CustomVariables::getNumUsableCustomVariables();
|
||||
|
||||
foreach ($actionDetails as $actionIdx => &$actionDetail) {
|
||||
$actionDetail =& $actionDetails[$actionIdx];
|
||||
$customVariablesPage = array();
|
||||
|
||||
$maxCustomVariables = CustomVariables::getMaxCustomVariables();
|
||||
|
||||
for ($i = 1; $i <= $maxCustomVariables; $i++) {
|
||||
if (!empty($actionDetail['custom_var_k' . $i])) {
|
||||
$cvarKey = $actionDetail['custom_var_k' . $i];
|
||||
|
|
@ -786,10 +280,14 @@ class Visitor
|
|||
$actionDetail['customVariables'] = $customVariablesPage;
|
||||
}
|
||||
|
||||
if ($actionDetail['type'] == Action::TYPE_CONTENT) {
|
||||
|
||||
if($actionDetail['type'] == Action::TYPE_EVENT_CATEGORY) {
|
||||
unset($actionDetails[$actionIdx]);
|
||||
continue;
|
||||
|
||||
} elseif ($actionDetail['type'] == Action::TYPE_EVENT) {
|
||||
// Handle Event
|
||||
if(strlen($actionDetail['pageTitle']) > 0) {
|
||||
if (strlen($actionDetail['pageTitle']) > 0) {
|
||||
$actionDetail['eventName'] = $actionDetail['pageTitle'];
|
||||
}
|
||||
|
||||
|
|
@ -802,74 +300,32 @@ class Visitor
|
|||
}
|
||||
|
||||
// Event value / Generation time
|
||||
if($actionDetail['type'] == Action::TYPE_EVENT_CATEGORY) {
|
||||
if(strlen($actionDetail['custom_float']) > 0) {
|
||||
if ($actionDetail['type'] == Action::TYPE_EVENT) {
|
||||
if (strlen($actionDetail['custom_float']) > 0) {
|
||||
$actionDetail['eventValue'] = round($actionDetail['custom_float'], self::EVENT_VALUE_PRECISION);
|
||||
}
|
||||
} elseif ($actionDetail['custom_float'] > 0) {
|
||||
$actionDetail['generationTime'] = \Piwik\MetricsFormatter::getPrettyTimeFromSeconds($actionDetail['custom_float'] / 1000);
|
||||
$actionDetail['generationTime'] = $formatter->getPrettyTimeFromSeconds($actionDetail['custom_float'] / 1000, true);
|
||||
}
|
||||
unset($actionDetail['custom_float']);
|
||||
|
||||
if($actionDetail['type'] != Action::TYPE_EVENT_CATEGORY) {
|
||||
if ($actionDetail['type'] != Action::TYPE_EVENT) {
|
||||
unset($actionDetail['eventCategory']);
|
||||
unset($actionDetail['eventAction']);
|
||||
}
|
||||
|
||||
// Reconstruct url from prefix
|
||||
$actionDetail['url'] = Tracker\PageUrl::reconstructNormalizedUrl($actionDetail['url'], $actionDetail['url_prefix']);
|
||||
$url = Tracker\PageUrl::reconstructNormalizedUrl($actionDetail['url'], $actionDetail['url_prefix']);
|
||||
$url = Common::unsanitizeInputValue($url);
|
||||
|
||||
$actionDetail['url'] = $url;
|
||||
unset($actionDetail['url_prefix']);
|
||||
|
||||
// Set the time spent for this action (which is the timeSpentRef of the next action)
|
||||
if (isset($actionDetails[$actionIdx + 1])) {
|
||||
$actionDetail['timeSpent'] = $actionDetails[$actionIdx + 1]['timeSpentRef'];
|
||||
$actionDetail['timeSpentPretty'] = \Piwik\MetricsFormatter::getPrettyTimeFromSeconds($actionDetail['timeSpent']);
|
||||
}
|
||||
unset($actionDetails[$actionIdx]['timeSpentRef']); // not needed after timeSpent is added
|
||||
|
||||
}
|
||||
|
||||
// If the visitor converted a goal, we shall select all Goals
|
||||
$sql = "
|
||||
SELECT
|
||||
'goal' as type,
|
||||
goal.name as goalName,
|
||||
goal.idgoal as goalId,
|
||||
goal.revenue as revenue,
|
||||
log_conversion.idlink_va as goalPageId,
|
||||
log_conversion.server_time as serverTimePretty,
|
||||
log_conversion.url as url
|
||||
FROM " . Common::prefixTable('log_conversion') . " AS log_conversion
|
||||
LEFT JOIN " . Common::prefixTable('goal') . " AS goal
|
||||
ON (goal.idsite = log_conversion.idsite
|
||||
AND
|
||||
goal.idgoal = log_conversion.idgoal)
|
||||
AND goal.deleted = 0
|
||||
WHERE log_conversion.idvisit = ?
|
||||
AND log_conversion.idgoal > 0
|
||||
ORDER BY server_time ASC
|
||||
LIMIT 0, $actionsLimit
|
||||
";
|
||||
$goalDetails = Db::fetchAll($sql, array($idVisit));
|
||||
|
||||
$sql = "SELECT
|
||||
case idgoal when " . GoalManager::IDGOAL_CART . " then '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART . "' else '" . Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER . "' end as type,
|
||||
idorder as orderId,
|
||||
" . LogAggregator::getSqlRevenue('revenue') . " as revenue,
|
||||
" . LogAggregator::getSqlRevenue('revenue_subtotal') . " as revenueSubTotal,
|
||||
" . LogAggregator::getSqlRevenue('revenue_tax') . " as revenueTax,
|
||||
" . LogAggregator::getSqlRevenue('revenue_shipping') . " as revenueShipping,
|
||||
" . LogAggregator::getSqlRevenue('revenue_discount') . " as revenueDiscount,
|
||||
items as items,
|
||||
|
||||
log_conversion.server_time as serverTimePretty
|
||||
FROM " . Common::prefixTable('log_conversion') . " AS log_conversion
|
||||
WHERE idvisit = ?
|
||||
AND idgoal <= " . GoalManager::IDGOAL_ORDER . "
|
||||
ORDER BY server_time ASC
|
||||
LIMIT 0, $actionsLimit";
|
||||
$ecommerceDetails = Db::fetchAll($sql, array($idVisit));
|
||||
$goalDetails = $model->queryGoalConversionsForVisit($idVisit, $actionsLimit);
|
||||
|
||||
$ecommerceDetails = $model->queryEcommerceConversionsForVisit($idVisit, $actionsLimit);
|
||||
foreach ($ecommerceDetails as &$ecommerceDetail) {
|
||||
if ($ecommerceDetail['type'] == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
|
||||
unset($ecommerceDetail['orderId']);
|
||||
|
|
@ -891,31 +347,10 @@ class Visitor
|
|||
|
||||
// Enrich ecommerce carts/orders with the list of products
|
||||
usort($ecommerceDetails, array('static', 'sortByServerTime'));
|
||||
foreach ($ecommerceDetails as $key => &$ecommerceConversion) {
|
||||
$sql = "SELECT
|
||||
log_action_sku.name as itemSKU,
|
||||
log_action_name.name as itemName,
|
||||
log_action_category.name as itemCategory,
|
||||
" . LogAggregator::getSqlRevenue('price') . " as price,
|
||||
quantity as quantity
|
||||
FROM " . Common::prefixTable('log_conversion_item') . "
|
||||
INNER JOIN " . Common::prefixTable('log_action') . " AS log_action_sku
|
||||
ON idaction_sku = log_action_sku.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_name
|
||||
ON idaction_name = log_action_name.idaction
|
||||
LEFT JOIN " . Common::prefixTable('log_action') . " AS log_action_category
|
||||
ON idaction_category = log_action_category.idaction
|
||||
WHERE idvisit = ?
|
||||
AND idorder = ?
|
||||
AND deleted = 0
|
||||
LIMIT 0, $actionsLimit
|
||||
";
|
||||
$bind = array($idVisit, isset($ecommerceConversion['orderId'])
|
||||
? $ecommerceConversion['orderId']
|
||||
: GoalManager::ITEM_IDORDER_ABANDONED_CART
|
||||
);
|
||||
foreach ($ecommerceDetails as &$ecommerceConversion) {
|
||||
$idOrder = isset($ecommerceConversion['orderId']) ? $ecommerceConversion['orderId'] : GoalManager::ITEM_IDORDER_ABANDONED_CART;
|
||||
|
||||
$itemsDetails = Db::fetchAll($sql, $bind);
|
||||
$itemsDetails = $model->queryEcommerceItemsForOrder($idVisit, $idOrder, $actionsLimit);
|
||||
foreach ($itemsDetails as &$detail) {
|
||||
if ($detail['price'] == round($detail['price'])) {
|
||||
$detail['price'] = round($detail['price']);
|
||||
|
|
@ -924,54 +359,98 @@ class Visitor
|
|||
$ecommerceConversion['itemDetails'] = $itemsDetails;
|
||||
}
|
||||
|
||||
$actions = array_merge($actionDetails, $goalDetails, $ecommerceDetails);
|
||||
$actionDetails = array_values($actionDetails);
|
||||
|
||||
// Enrich with time spent per action
|
||||
foreach($actionDetails as $actionIdx => &$actionDetail) {
|
||||
|
||||
// Set the time spent for this action (which is the timeSpentRef of the next action)
|
||||
$nextActionFound = isset($actionDetails[$actionIdx + 1]);
|
||||
if ($nextActionFound) {
|
||||
$actionDetail['timeSpent'] = $actionDetails[$actionIdx + 1]['timeSpentRef'];
|
||||
} else {
|
||||
|
||||
// Last action of a visit.
|
||||
// By default, Piwik does not know how long the user stayed on the page
|
||||
// If enableHeartBeatTimer() is used in piwik.js then we can find the accurate time on page for the last pageview
|
||||
$visitTotalTime = $visitorDetailsArray['visitDuration'];
|
||||
$timeOfLastAction = Date::factory($actionDetail['serverTimePretty'])->getTimestamp();
|
||||
|
||||
$timeSpentOnAllActionsApartFromLastOne = ($timeOfLastAction - $visitorDetailsArray['firstActionTimestamp']);
|
||||
$timeSpentOnPage = $visitTotalTime - $timeSpentOnAllActionsApartFromLastOne;
|
||||
|
||||
// Safe net, we assume the time is correct when it's more than 10 seconds
|
||||
if ($timeSpentOnPage > 10) {
|
||||
$actionDetail['timeSpent'] = $timeSpentOnPage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (isset($actionDetail['timeSpent'])) {
|
||||
$actionDetail['timeSpentPretty'] = $formatter->getPrettyTimeFromSeconds($actionDetail['timeSpent'], true);
|
||||
}
|
||||
|
||||
unset($actionDetails[$actionIdx]['timeSpentRef']); // not needed after timeSpent is added
|
||||
|
||||
}
|
||||
|
||||
$actions = array_merge($actionDetails, $goalDetails, $ecommerceDetails);
|
||||
usort($actions, array('static', 'sortByServerTime'));
|
||||
|
||||
foreach ($actions as &$action) {
|
||||
unset($action['idlink_va']);
|
||||
}
|
||||
|
||||
$visitorDetailsArray['goalConversions'] = count($goalDetails);
|
||||
|
||||
$visitorDetailsArray['actionDetails'] = $actions;
|
||||
|
||||
foreach ($visitorDetailsArray['actionDetails'] as &$details) {
|
||||
switch ($details['type']) {
|
||||
case 'goal':
|
||||
$details['icon'] = 'plugins/Zeitgeist/images/goal.png';
|
||||
$details['icon'] = 'plugins/Morpheus/images/goal.png';
|
||||
break;
|
||||
case Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER:
|
||||
case Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART:
|
||||
$details['icon'] = 'plugins/Zeitgeist/images/' . $details['type'] . '.gif';
|
||||
$details['icon'] = 'plugins/Morpheus/images/' . $details['type'] . '.gif';
|
||||
break;
|
||||
case Action::TYPE_DOWNLOAD:
|
||||
$details['type'] = 'download';
|
||||
$details['icon'] = 'plugins/Zeitgeist/images/download.png';
|
||||
$details['icon'] = 'plugins/Morpheus/images/download.png';
|
||||
break;
|
||||
case Action::TYPE_OUTLINK:
|
||||
$details['type'] = 'outlink';
|
||||
$details['icon'] = 'plugins/Zeitgeist/images/link.gif';
|
||||
$details['icon'] = 'plugins/Morpheus/images/link.gif';
|
||||
break;
|
||||
case Action::TYPE_SITE_SEARCH:
|
||||
$details['type'] = 'search';
|
||||
$details['icon'] = 'plugins/Zeitgeist/images/search_ico.png';
|
||||
$details['icon'] = 'plugins/Morpheus/images/search_ico.png';
|
||||
break;
|
||||
case Action::TYPE_EVENT_CATEGORY:
|
||||
case Action::TYPE_EVENT:
|
||||
$details['type'] = 'event';
|
||||
$details['icon'] = 'plugins/Zeitgeist/images/event.png';
|
||||
$details['icon'] = 'plugins/Morpheus/images/event.png';
|
||||
break;
|
||||
default:
|
||||
$details['type'] = 'action';
|
||||
$details['icon'] = null;
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert datetimes to the site timezone
|
||||
$dateTimeVisit = Date::factory($details['serverTimePretty'], $timezone);
|
||||
$details['serverTimePretty'] = $dateTimeVisit->getLocalized(Piwik::translate('CoreHome_ShortDateFormat') . ' %time%');
|
||||
$details['serverTimePretty'] = $dateTimeVisit->getLocalized(Date::DATETIME_FORMAT_SHORT);
|
||||
$details['timestamp'] = $dateTimeVisit->getTimestamp();
|
||||
}
|
||||
$visitorDetailsArray['goalConversions'] = count($goalDetails);
|
||||
|
||||
|
||||
return $visitorDetailsArray;
|
||||
}
|
||||
|
||||
private static function getCustomVariablePrettyKey($key)
|
||||
{
|
||||
$rename = array(
|
||||
Tracker\ActionSiteSearch::CVAR_KEY_SEARCH_CATEGORY => Piwik::translate('Actions_ColumnSearchCategory'),
|
||||
Tracker\ActionSiteSearch::CVAR_KEY_SEARCH_COUNT => Piwik::translate('Actions_ColumnSearchResultsCount'),
|
||||
ActionSiteSearch::CVAR_KEY_SEARCH_CATEGORY => Piwik::translate('Actions_ColumnSearchCategory'),
|
||||
ActionSiteSearch::CVAR_KEY_SEARCH_COUNT => Piwik::translate('Actions_ColumnSearchResultsCount'),
|
||||
);
|
||||
if (isset($rename[$key])) {
|
||||
return $rename[$key];
|
||||
|
|
@ -983,10 +462,19 @@ class Visitor
|
|||
{
|
||||
$ta = strtotime($a['serverTimePretty']);
|
||||
$tb = strtotime($b['serverTimePretty']);
|
||||
return $ta < $tb
|
||||
? -1
|
||||
: ($ta == $tb
|
||||
? 0
|
||||
: 1);
|
||||
|
||||
if ($ta < $tb) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($ta == $tb) {
|
||||
if ($a['idlink_va'] > $b['idlink_va']) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
www/analytics/plugins/Live/VisitorFactory.php
Normal file
49
www/analytics/plugins/Live/VisitorFactory.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Live;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Piwik;
|
||||
|
||||
class VisitorFactory
|
||||
{
|
||||
/**
|
||||
* Returns Visitor object.
|
||||
* This method can be overwritten to use a different Visitor object
|
||||
*
|
||||
* @param array $visitorRawData
|
||||
* @throws \Exception
|
||||
* @return \Piwik\Plugins\Live\VisitorInterface
|
||||
* @ignore
|
||||
*/
|
||||
public function create(array $visitorRawData = array())
|
||||
{
|
||||
$visitor = null;
|
||||
|
||||
/**
|
||||
* Triggered while visit is filtering in live plugin. Subscribers to this
|
||||
* event can force the use of a custom visitor object that extends from
|
||||
* {@link Piwik\Plugins\Live\VisitorInterface}.
|
||||
*
|
||||
* @param \Piwik\Plugins\Live\VisitorInterface &$visitor Initialized to null, but can be set to
|
||||
* a new visitor object. If it isn't modified
|
||||
* Piwik uses the default class.
|
||||
* @param array $visitorRawData Raw data using in Visitor object constructor.
|
||||
*/
|
||||
Piwik::postEvent('Live.makeNewVisitorObject', array(&$visitor, $visitorRawData));
|
||||
|
||||
if (is_null($visitor)) {
|
||||
$visitor = new Visitor($visitorRawData);
|
||||
} elseif (!($visitor instanceof VisitorInterface)) {
|
||||
throw new Exception("The Visitor object set in the plugin must implement VisitorInterface");
|
||||
}
|
||||
|
||||
return $visitor;
|
||||
}
|
||||
}
|
||||
22
www/analytics/plugins/Live/VisitorInterface.php
Normal file
22
www/analytics/plugins/Live/VisitorInterface.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Live;
|
||||
|
||||
interface VisitorInterface
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAllVisitorDetails();
|
||||
|
||||
/**
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getVisitorId();
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Live;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\Visualization;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
* A special DataTable visualization for the Live.getLastVisitsDetails API method.
|
||||
*/
|
||||
class VisitorLog extends Visualization
|
||||
{
|
||||
const ID = 'Piwik\Plugins\Live\VisitorLog';
|
||||
const TEMPLATE_FILE = "@Live/_dataTableViz_visitorLog.twig";
|
||||
|
||||
public function beforeLoadDataTable()
|
||||
{
|
||||
$this->requestConfig->addPropertiesThatShouldBeAvailableClientSide(array(
|
||||
'filter_limit',
|
||||
'filter_offset',
|
||||
'filter_sort_column',
|
||||
'filter_sort_order',
|
||||
));
|
||||
|
||||
$this->requestConfig->filter_sort_column = 'idVisit';
|
||||
$this->requestConfig->filter_sort_order = 'asc';
|
||||
$this->requestConfig->filter_limit = 20;
|
||||
$this->requestConfig->disable_generic_filters = true;
|
||||
|
||||
$offset = Common::getRequestVar('filter_offset', 0);
|
||||
$limit = Common::getRequestVar('filter_limit', $this->requestConfig->filter_limit);
|
||||
|
||||
$this->config->filters[] = array('Limit', array($offset, $limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure visualization.
|
||||
*/
|
||||
public function beforeRender()
|
||||
{
|
||||
$this->config->datatable_js_type = 'VisitorLog';
|
||||
$this->config->enable_sort = false;
|
||||
$this->config->show_search = false;
|
||||
$this->config->show_exclude_low_population = false;
|
||||
$this->config->show_offset_information = false;
|
||||
$this->config->show_all_views_icons = false;
|
||||
$this->config->show_table_all_columns = false;
|
||||
$this->config->show_export_as_rss_feed = false;
|
||||
|
||||
$this->config->documentation = Piwik::translate('Live_VisitorLogDocumentation', array('<br />', '<br />'));
|
||||
|
||||
$filterEcommerce = Common::getRequestVar('filterEcommerce', 0, 'int');
|
||||
$this->config->custom_parameters = array(
|
||||
// set a very high row count so that the next link in the footer of the data table is always shown
|
||||
'totalRows' => 10000000,
|
||||
|
||||
'filterEcommerce' => $filterEcommerce,
|
||||
'pageUrlNotDefined' => Piwik::translate('General_NotDefined', Piwik::translate('Actions_ColumnPageURL')),
|
||||
|
||||
'smallWidth' => 1 == Common::getRequestVar('small', 0, 'int'),
|
||||
);
|
||||
|
||||
$this->config->footer_icons = array(
|
||||
array(
|
||||
'class' => 'tableAllColumnsSwitch',
|
||||
'buttons' => array(
|
||||
array(
|
||||
'id' => static::ID,
|
||||
'title' => Piwik::translate('Live_LinkVisitorLog'),
|
||||
'icon' => 'plugins/Zeitgeist/images/table.png'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// determine if each row has ecommerce activity or not
|
||||
if ($filterEcommerce) {
|
||||
$this->dataTable->filter(
|
||||
'ColumnCallbackAddMetadata',
|
||||
array(
|
||||
'actionDetails',
|
||||
'hasEcommerce',
|
||||
function ($actionDetails) use ($filterEcommerce) {
|
||||
foreach ($actionDetails as $action) {
|
||||
$isEcommerceOrder = $action['type'] == 'ecommerceOrder'
|
||||
&& $filterEcommerce == \Piwik\Plugins\Goals\Controller::ECOMMERCE_LOG_SHOW_ORDERS;
|
||||
$isAbandonedCart = $action['type'] == 'ecommerceAbandonedCart'
|
||||
&& $filterEcommerce == \Piwik\Plugins\Goals\Controller::ECOMMERCE_LOG_SHOW_ABANDONED_CARTS;
|
||||
if($isAbandonedCart || $isEcommerceOrder) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
398
www/analytics/plugins/Live/VisitorProfile.php
Normal file
398
www/analytics/plugins/Live/VisitorProfile.php
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\Live;
|
||||
|
||||
use Exception;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Date;
|
||||
use Piwik\Metrics\Formatter;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Site;
|
||||
use Piwik\Plugins\Referrers\API as APIReferrers;
|
||||
|
||||
class VisitorProfile
|
||||
{
|
||||
const VISITOR_PROFILE_MAX_VISITS_TO_SHOW = 10;
|
||||
|
||||
protected $profile = array();
|
||||
private $siteSearchKeywords = array();
|
||||
private $continents = array();
|
||||
private $countries = array();
|
||||
private $cities = array();
|
||||
private $pageGenerationTimeTotal = 0;
|
||||
|
||||
public function __construct($idSite)
|
||||
{
|
||||
$this->idSite = $idSite;
|
||||
$this->isEcommerceEnabled = Site::isEcommerceEnabledFor($this->idSite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $visits
|
||||
* @param $visitorId
|
||||
* @param $segment
|
||||
* @param $numLastVisits
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function makeVisitorProfile(DataTable $visits, $visitorId, $segment, $numLastVisits)
|
||||
{
|
||||
$this->initVisitorProfile();
|
||||
|
||||
/** @var DataTable\Row $visit */
|
||||
foreach ($visits->getRows() as $visit) {
|
||||
++$this->profile['totalVisits'];
|
||||
|
||||
$this->profile['totalVisitDuration'] += $visit->getColumn('visitDuration');
|
||||
$this->profile['totalActions'] += $visit->getColumn('actions');
|
||||
$this->profile['totalGoalConversions'] += $visit->getColumn('goalConversions');
|
||||
|
||||
// individual goal conversions are stored in action details
|
||||
foreach ($visit->getColumn('actionDetails') as $action) {
|
||||
$this->handleIfGoalAction($action);
|
||||
$this->handleIfEventAction($action);
|
||||
$this->handleIfDownloadAction($action);
|
||||
$this->handleIfOutlinkAction($action);
|
||||
$this->handleIfEcommerceAction($action);
|
||||
$this->handleIfSiteSearchAction($action);
|
||||
$this->handleIfPageViewAction($action);
|
||||
$this->handleIfPageGenerationTime($action);
|
||||
}
|
||||
$this->handleGeoLocation($visit);
|
||||
}
|
||||
|
||||
$this->handleGeoLocationCountries();
|
||||
$this->handleGeoLocationContinents();
|
||||
$this->handleSiteSearches();
|
||||
$this->handleAveragePageGenerationTime();
|
||||
|
||||
$formatter = new Formatter();
|
||||
$this->profile['totalVisitDurationPretty'] = $formatter->getPrettyTimeFromSeconds($this->profile['totalVisitDuration'], true);
|
||||
|
||||
$this->handleVisitsSummary($visits);
|
||||
$this->handleAdjacentVisitorIds($visits, $visitorId, $segment);
|
||||
|
||||
// use N most recent visits for last_visits
|
||||
$visits->deleteRowsOffset($numLastVisits);
|
||||
|
||||
$this->profile['lastVisits'] = $visits;
|
||||
|
||||
$this->profile['userId'] = $visit->getColumn('userId');
|
||||
|
||||
return $this->profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary for an important visit. Used to describe the first & last visits of a visitor.
|
||||
*
|
||||
* @param DataTable\Row $visit
|
||||
* @return array
|
||||
*/
|
||||
private function getVisitorProfileVisitSummary($visit)
|
||||
{
|
||||
$today = Date::today();
|
||||
|
||||
$serverDate = $visit->getColumn('firstActionTimestamp');
|
||||
return array(
|
||||
'date' => $serverDate,
|
||||
'prettyDate' => Date::factory($serverDate)->getLocalized(Date::DATE_FORMAT_LONG),
|
||||
'daysAgo' => (int)Date::secondsToDays($today->getTimestamp() - Date::factory($serverDate)->getTimestamp()),
|
||||
'referrerType' => $visit->getColumn('referrerType'),
|
||||
'referralSummary' => self::getReferrerSummaryForVisit($visit),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a summary for a visit's referral.
|
||||
*
|
||||
* @param DataTable\Row $visit
|
||||
* @return bool|mixed|string
|
||||
*/
|
||||
public static function getReferrerSummaryForVisit($visit)
|
||||
{
|
||||
$referrerType = $visit->getColumn('referrerType');
|
||||
if ($referrerType === false
|
||||
|| $referrerType == 'direct'
|
||||
) {
|
||||
return Piwik::translate('Referrers_DirectEntry');
|
||||
}
|
||||
|
||||
if ($referrerType == 'search') {
|
||||
$referrerName = $visit->getColumn('referrerName');
|
||||
|
||||
$keyword = $visit->getColumn('referrerKeyword');
|
||||
if ($keyword !== false
|
||||
&& $keyword != APIReferrers::getKeywordNotDefinedString()
|
||||
) {
|
||||
$referrerName .= ' (' . $keyword . ')';
|
||||
}
|
||||
return $referrerName;
|
||||
}
|
||||
|
||||
if ($referrerType == 'campaign') {
|
||||
|
||||
$summary = Piwik::translate('Referrers_ColumnCampaign') . ': ' . $visit->getColumn('referrerName');
|
||||
$keyword = $visit->getColumn('referrerKeyword');
|
||||
if (!empty($keyword)) {
|
||||
$summary .= ' - ' . $keyword;
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
return $visit->getColumn('referrerName');
|
||||
}
|
||||
|
||||
private function isEcommerceEnabled()
|
||||
{
|
||||
return $this->isEcommerceEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $action
|
||||
*/
|
||||
private function handleIfEventAction($action)
|
||||
{
|
||||
if ($action['type'] != 'event') {
|
||||
return;
|
||||
}
|
||||
$this->profile['totalEvents']++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $action
|
||||
*/
|
||||
private function handleIfDownloadAction($action)
|
||||
{
|
||||
if ($action['type'] != 'download') {
|
||||
return;
|
||||
}
|
||||
$this->profile['totalDownloads']++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $action
|
||||
*/
|
||||
private function handleIfOutlinkAction($action)
|
||||
{
|
||||
if ($action['type'] != 'outlink') {
|
||||
return;
|
||||
}
|
||||
$this->profile['totalOutlinks']++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $action
|
||||
*/
|
||||
private function handleIfPageViewAction($action)
|
||||
{
|
||||
if ($action['type'] != 'action') {
|
||||
return;
|
||||
}
|
||||
$this->profile['totalPageViews']++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $action
|
||||
*/
|
||||
private function handleIfEcommerceAction($action)
|
||||
{
|
||||
if (!$this->isEcommerceEnabled()) {
|
||||
return;
|
||||
}
|
||||
if ($action['type'] == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
|
||||
++$this->profile['totalEcommerceConversions'];
|
||||
$this->profile['totalEcommerceRevenue'] += $action['revenue'];
|
||||
$this->profile['totalEcommerceItems'] += $action['items'];
|
||||
} else if ($action['type'] == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
|
||||
++$this->profile['totalAbandonedCarts'];
|
||||
$this->profile['totalAbandonedCartsRevenue'] += $action['revenue'];
|
||||
$this->profile['totalAbandonedCartsItems'] += $action['items'];
|
||||
}
|
||||
}
|
||||
|
||||
private function handleIfGoalAction($action)
|
||||
{
|
||||
if ($action['type'] != 'goal') {
|
||||
return;
|
||||
}
|
||||
$idGoal = $action['goalId'];
|
||||
$idGoalKey = 'idgoal=' . $idGoal;
|
||||
|
||||
if (!isset($this->profile['totalConversionsByGoal'][$idGoalKey])) {
|
||||
$this->profile['totalConversionsByGoal'][$idGoalKey] = 0;
|
||||
}
|
||||
++$this->profile['totalConversionsByGoal'][$idGoalKey];
|
||||
|
||||
if (!empty($action['revenue'])) {
|
||||
if (!isset($this->profile['totalRevenueByGoal'][$idGoalKey])) {
|
||||
$this->profile['totalRevenueByGoal'][$idGoalKey] = 0;
|
||||
}
|
||||
$this->profile['totalRevenueByGoal'][$idGoalKey] += $action['revenue'];
|
||||
}
|
||||
}
|
||||
|
||||
private function handleIfSiteSearchAction($action)
|
||||
{
|
||||
if (!isset($action['siteSearchKeyword'])) {
|
||||
return;
|
||||
}
|
||||
$keyword = $action['siteSearchKeyword'];
|
||||
|
||||
if (!isset($this->siteSearchKeywords[$keyword])) {
|
||||
$this->siteSearchKeywords[$keyword] = 0;
|
||||
++$this->profile['totalSearches'];
|
||||
}
|
||||
++$this->siteSearchKeywords[$keyword];
|
||||
}
|
||||
|
||||
private function handleGeoLocation(DataTable\Row $visit)
|
||||
{
|
||||
// realtime map only checks for latitude
|
||||
$hasLatitude = $visit->getColumn('latitude') !== false;
|
||||
if ($hasLatitude) {
|
||||
$this->profile['hasLatLong'] = true;
|
||||
}
|
||||
|
||||
$countryCode = $visit->getColumn('countryCode');
|
||||
if (!isset($this->countries[$countryCode])) {
|
||||
$this->countries[$countryCode] = 0;
|
||||
}
|
||||
++$this->countries[$countryCode];
|
||||
|
||||
$continentCode = $visit->getColumn('continentCode');
|
||||
if (!isset($this->continents[$continentCode])) {
|
||||
$this->continents[$continentCode] = 0;
|
||||
}
|
||||
++$this->continents[$continentCode];
|
||||
|
||||
if ($countryCode && !array_key_exists($countryCode, $this->cities)) {
|
||||
$this->cities[$countryCode] = array();
|
||||
}
|
||||
$city = $visit->getColumn('city');
|
||||
if (!empty($city)) {
|
||||
$this->cities[$countryCode][] = $city;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleSiteSearches()
|
||||
{
|
||||
// sort by visit/action
|
||||
arsort($this->siteSearchKeywords);
|
||||
|
||||
foreach ($this->siteSearchKeywords as $keyword => $searchCount) {
|
||||
$this->profile['searches'][] = array('keyword' => $keyword,
|
||||
'searches' => $searchCount);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleGeoLocationContinents()
|
||||
{
|
||||
// sort by visit/action
|
||||
asort($this->continents);
|
||||
foreach ($this->continents as $continentCode => $nbVisits) {
|
||||
$this->profile['continents'][] = array('continent' => $continentCode,
|
||||
'nb_visits' => $nbVisits,
|
||||
'prettyName' => \Piwik\Plugins\UserCountry\continentTranslate($continentCode));
|
||||
}
|
||||
}
|
||||
|
||||
private function handleGeoLocationCountries()
|
||||
{
|
||||
// sort by visit/action
|
||||
asort($this->countries);
|
||||
|
||||
// transform country/continents/search keywords into something that will look good in XML
|
||||
$this->profile['countries'] = $this->profile['continents'] = $this->profile['searches'] = array();
|
||||
|
||||
foreach ($this->countries as $countryCode => $nbVisits) {
|
||||
|
||||
$countryInfo = array('country' => $countryCode,
|
||||
'nb_visits' => $nbVisits,
|
||||
'flag' => \Piwik\Plugins\UserCountry\getFlagFromCode($countryCode),
|
||||
'prettyName' => \Piwik\Plugins\UserCountry\countryTranslate($countryCode));
|
||||
if (!empty($this->cities[$countryCode])) {
|
||||
$countryInfo['cities'] = array_unique($this->cities[$countryCode]);
|
||||
}
|
||||
$this->profile['countries'][] = $countryInfo;
|
||||
}
|
||||
}
|
||||
|
||||
private function initVisitorProfile()
|
||||
{
|
||||
$this->profile['totalVisits'] = 0;
|
||||
$this->profile['totalVisitDuration'] = 0;
|
||||
$this->profile['totalActions'] = 0;
|
||||
$this->profile['totalEvents'] = 0;
|
||||
$this->profile['totalOutlinks'] = 0;
|
||||
$this->profile['totalDownloads'] = 0;
|
||||
$this->profile['totalSearches'] = 0;
|
||||
$this->profile['totalPageViews'] = 0;
|
||||
$this->profile['totalPageViewsWithTiming'] = 0;
|
||||
$this->profile['totalGoalConversions'] = 0;
|
||||
$this->profile['totalConversionsByGoal'] = array();
|
||||
$this->profile['hasLatLong'] = false;
|
||||
|
||||
if ($this->isEcommerceEnabled()) {
|
||||
$this->profile['totalEcommerceConversions'] = 0;
|
||||
$this->profile['totalEcommerceRevenue'] = 0;
|
||||
$this->profile['totalEcommerceItems'] = 0;
|
||||
$this->profile['totalAbandonedCarts'] = 0;
|
||||
$this->profile['totalAbandonedCartsRevenue'] = 0;
|
||||
$this->profile['totalAbandonedCartsItems'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleAveragePageGenerationTime()
|
||||
{
|
||||
if ($this->profile['totalPageViewsWithTiming']) {
|
||||
$this->profile['averagePageGenerationTime'] =
|
||||
round($this->pageGenerationTimeTotal / $this->profile['totalPageViewsWithTiming'], $precision = 2);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleIfPageGenerationTime($action)
|
||||
{
|
||||
if (isset($action['generationTime'])) {
|
||||
$this->pageGenerationTimeTotal += $action['generationTime'];
|
||||
++$this->profile['totalPageViewsWithTiming'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTable $visits
|
||||
* @param $visitorId
|
||||
* @param $segment
|
||||
*/
|
||||
private function handleAdjacentVisitorIds(DataTable $visits, $visitorId, $segment)
|
||||
{
|
||||
// get visitor IDs that are adjacent to this one in log_visit
|
||||
// TODO: make sure order of visitor ids is not changed if a returning visitor visits while the user is
|
||||
// looking at the popup.
|
||||
$rows = $visits->getRows();
|
||||
$latestVisitTime = reset($rows)->getColumn('lastActionDateTime');
|
||||
|
||||
$model = new Model();
|
||||
$this->profile['nextVisitorId'] = $model->queryAdjacentVisitorId($this->idSite, $visitorId, $latestVisitTime, $segment, $getNext = true);
|
||||
$this->profile['previousVisitorId'] = $model->queryAdjacentVisitorId($this->idSite, $visitorId, $latestVisitTime, $segment, $getNext = false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTable $visits
|
||||
*/
|
||||
private function handleVisitsSummary(DataTable $visits)
|
||||
{
|
||||
$rows = $visits->getRows();
|
||||
$this->profile['firstVisit'] = $this->getVisitorProfileVisitSummary(end($rows));
|
||||
$this->profile['lastVisit'] = $this->getVisitorProfileVisitSummary(reset($rows));
|
||||
$this->profile['visitsAggregated'] = count($rows);
|
||||
}
|
||||
}
|
||||
101
www/analytics/plugins/Live/Visualizations/VisitorLog.php
Normal file
101
www/analytics/plugins/Live/Visualizations/VisitorLog.php
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Live\Visualizations;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\ViewDataTable;
|
||||
use Piwik\Plugin\Visualization;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
* A special DataTable visualization for the Live.getLastVisitsDetails API method.
|
||||
*
|
||||
* @property VisitorLog\Config $config
|
||||
*/
|
||||
class VisitorLog extends Visualization
|
||||
{
|
||||
const ID = 'VisitorLog';
|
||||
const TEMPLATE_FILE = "@Live/_dataTableViz_visitorLog.twig";
|
||||
const FOOTER_ICON_TITLE = '';
|
||||
const FOOTER_ICON = '';
|
||||
|
||||
public static function getDefaultConfig()
|
||||
{
|
||||
return new VisitorLog\Config();
|
||||
}
|
||||
|
||||
public function beforeLoadDataTable()
|
||||
{
|
||||
$this->requestConfig->addPropertiesThatShouldBeAvailableClientSide(array(
|
||||
'filter_limit',
|
||||
'filter_offset',
|
||||
'filter_sort_column',
|
||||
'filter_sort_order',
|
||||
));
|
||||
|
||||
if (!is_numeric($this->requestConfig->filter_limit)) {
|
||||
$this->requestConfig->filter_limit = 20;
|
||||
}
|
||||
|
||||
$this->requestConfig->disable_generic_filters = true;
|
||||
$this->requestConfig->filter_sort_column = false;
|
||||
}
|
||||
|
||||
public function afterGenericFiltersAreAppliedToLoadedDataTable()
|
||||
{
|
||||
$this->requestConfig->filter_sort_column = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure visualization.
|
||||
*/
|
||||
public function beforeRender()
|
||||
{
|
||||
$this->config->disable_row_actions = true;
|
||||
$this->config->datatable_js_type = 'VisitorLog';
|
||||
$this->config->enable_sort = false;
|
||||
$this->config->show_search = false;
|
||||
$this->config->show_exclude_low_population = false;
|
||||
$this->config->show_offset_information = false;
|
||||
$this->config->show_all_views_icons = false;
|
||||
$this->config->show_table_all_columns = false;
|
||||
$this->config->show_export_as_rss_feed = false;
|
||||
|
||||
$this->config->documentation = Piwik::translate('Live_VisitorLogDocumentation', array('<br />', '<br />'));
|
||||
|
||||
if (!is_array($this->config->custom_parameters)) {
|
||||
$this->config->custom_parameters = array();
|
||||
}
|
||||
|
||||
// set a very high row count so that the next link in the footer of the data table is always shown
|
||||
$this->config->custom_parameters['totalRows'] = 10000000;
|
||||
$this->config->custom_parameters['smallWidth'] = (1 == Common::getRequestVar('small', 0, 'int'));
|
||||
$this->config->custom_parameters['hideProfileLink'] = (1 == Common::getRequestVar('hideProfileLink', 0, 'int'));
|
||||
$this->config->custom_parameters['pageUrlNotDefined'] = Piwik::translate('General_NotDefined', Piwik::translate('Actions_ColumnPageURL'));
|
||||
|
||||
$this->config->footer_icons = array(
|
||||
array(
|
||||
'class' => 'tableAllColumnsSwitch',
|
||||
'buttons' => array(
|
||||
array(
|
||||
'id' => static::ID,
|
||||
'title' => Piwik::translate('Live_LinkVisitorLog'),
|
||||
'icon' => 'plugins/Morpheus/images/table.png'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function canDisplayViewDataTable(ViewDataTable $view)
|
||||
{
|
||||
return ($view->requestConfig->getApiModuleToRequest() === 'Live');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\Live\Visualizations\VisitorLog;
|
||||
|
||||
use Piwik\ViewDataTable\Config as VisualizationConfig;
|
||||
|
||||
/**
|
||||
* DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true.
|
||||
*/
|
||||
class Config extends VisualizationConfig
|
||||
{
|
||||
/**
|
||||
* Controls whether any DataTable Row Action icons are shown. If true, no icons are shown.
|
||||
*
|
||||
* Default value: false
|
||||
*/
|
||||
public $disable_row_actions = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->addPropertiesThatShouldBeAvailableClientSide(array(
|
||||
'disable_row_actions',
|
||||
));
|
||||
|
||||
$this->addPropertiesThatCanBeOverwrittenByQueryParams(array(
|
||||
'disable_row_actions',
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
27
www/analytics/plugins/Live/Widgets.php
Normal file
27
www/analytics/plugins/Live/Widgets.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\Live;
|
||||
|
||||
use Piwik\Piwik;
|
||||
|
||||
class Widgets extends \Piwik\Plugin\Widgets
|
||||
{
|
||||
protected $category = 'Live!';
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->addWidget('Live_VisitorsInRealTime', 'widget');
|
||||
|
||||
// the visitor profile uses a segment that is not accessible to the anonymous user, so don't bother showing this widget
|
||||
if (!Piwik::isUserIsAnonymous()) {
|
||||
$this->addWidget('Live_VisitorProfile', 'getVisitorProfilePopup');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 669 B After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 619 B |
Binary file not shown.
|
Before Width: | Height: | Size: 666 B After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 407 B |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue