add piwik installation

This commit is contained in:
coderkun 2014-04-25 03:56:02 +02:00
commit 8c5d4f0c31
43 changed files with 563902 additions and 0 deletions

View file

@ -0,0 +1,699 @@
<?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 Exception;
use Piwik\Common;
use Piwik\Config;
use Piwik\DataTable\Row;
use Piwik\DataTable;
use Piwik\Date;
use Piwik\Db;
use Piwik\MetricsFormatter;
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;
/**
* @see plugins/Live/Visitor.php
*/
require_once PIWIK_INCLUDE_PATH . '/plugins/Live/Visitor.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>,
* you will be able to request visits filtered by any criteria.
*
* The method "getLastVisitsDetails" will return extensive data for each visit, which includes: server time, visitId, visitorId,
* visitorType (new or returning), number of pages, list of all pages (and events, file downloaded and outlinks clicked),
* custom variables names and values set to this visit, number of goal conversions (and list of all Goal conversions for this visit,
* with time of conversion, revenue, URL, etc.), but also other attributes such as: days since last visit, days since first visit,
* country, continent, visitor IP,
* provider, referrer used (referrer name, keyword if it was a search engine, full URL), campaign name and keyword, operating system,
* 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
* 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.
* @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%';
/**
* This will return simple counters, for a given website ID, for visits over the last N minutes
*
* @param int $idSite Id Site
* @param int $lastMinutes Number of minutes to look back at
* @param bool|string $segment
* @return array( visits => N, actions => M, visitsConverted => P )
*/
public function getCounters($idSite, $lastMinutes, $segment = false)
{
Piwik::checkUserHasViewAccess($idSite);
$lastMinutes = (int)$lastMinutes;
$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";
$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;
}
if (empty($data[0]['visitsConverted'])) {
$data[0]['visitsConverted'] = 0;
}
return $data;
}
/**
* The same functionnality can be obtained using segment=visitorId==$visitorId with getLastVisitsDetails
*
* @deprecated
* @ignore
* @param int $visitorId
* @param int $idSite
* @param int $filter_limit
* @param bool $flat Whether to flatten the visitor details array
*
* @return DataTable
*/
public function getLastVisitsForVisitor($visitorId, $idSite, $filter_limit = 10, $flat = false)
{
Piwik::checkUserHasViewAccess($idSite);
$countVisitorsToFetch = $filter_limit;
$table = $this->loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $countVisitorsToFetch, $visitorId);
$this->addFilterToCleanVisitors($table, $idSite, $flat);
return $table;
}
/**
* Returns the last visits tracked in the specified website
* You can define any number of filters: none, one, many or all parameters can be defined
*
* @param int $idSite Site ID
* @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 $minTimestamp (optional) Minimum timestamp to restrict the query to (useful when paginating or refreshing visits)
* @param bool $flat
* @param bool $doNotFetchActions
* @return DataTable
*/
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');
$countVisitorsToFetch = $filter_limit + $filter_offset;
}
Piwik::checkUserHasViewAccess($idSite);
$dataTable = $this->loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment, $countVisitorsToFetch, $visitorId = false, $minTimestamp);
$this->addFilterToCleanVisitors($dataTable, $idSite, $flat, $doNotFetchActions);
return $dataTable;
}
/**
* Returns an array describing a visitor using her last visits (uses a maximum of 100).
*
* @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.
* @return array
*/
public function getVisitorProfile($idSite, $visitorId = false, $segment = false, $checkForLatLong = false)
{
Piwik::checkUserHasViewAccess($idSite);
if ($visitorId === false) {
$visitorId = $this->getMostRecentVisitorId($idSite, $segment);
}
$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);
$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);
/**
* Triggered in the Live.getVisitorProfile API method. Plugins can use this event
* to discover and add extra data to visitor profiles.
*
* For example, if an email address is found in a custom variable, a plugin could load the
* gravatar for the email and add it to the visitor profile, causing it to display in the
* visitor profile popup.
*
* The following visitor profile elements can be set to augment the visitor profile popup:
*
* - **visitorAvatar**: A URL to an image to display in the top left corner of the popup.
* - **visitorDescription**: Text to be used as the tooltip of the avatar image.
*
* @param array &$visitorProfile The unaugmented visitor profile info.
*/
Piwik::postEvent('Live.getExtraVisitorDetails', array(&$result));
return $result;
}
/**
* Returns the visitor ID of the most recent visit.
*
* @param int $idSite
* @param bool|string $segment
* @return string
*/
public function getMostRecentVisitorId($idSite, $segment = false)
{
Piwik::checkUserHasViewAccess($idSite);
$dataTable = $this->loadLastVisitorDetailsFromDatabase(
$idSite, $period = false, $date = false, $segment, $numVisitorsToFetch = 1,
$visitorId = false, $minTimestamp = false
);
if (0 >= $dataTable->getRowsCount()) {
return false;
}
$visitDetails = $dataTable->getFirstRow()->getColumns();
$visitor = new Visitor($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);
}
/**
* For an array of visits, query the list of pages for this visit
* as well as make the data human readable
* @param DataTable $dataTable
* @param int $idSite
* @param bool $flat whether to flatten the array (eg. 'customVariables' names/values will appear in the root array rather than in 'customVariables' key
* @param bool $doNotFetchActions If set to true, we only fetch visit info and not actions (much faster)
* @param bool $filterNow If true, the visitors will be cleaned immediately
*/
private function addFilterToCleanVisitors(DataTable $dataTable, $idSite, $flat = false, $doNotFetchActions = false, $filterNow = false)
{
$filter = 'queueFilter';
if ($filterNow) {
$filter = 'filter';
}
$dataTable->$filter(function ($table) use ($idSite, $flat, $doNotFetchActions) {
/** @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();
foreach ($table->getRows() as $visitorDetailRow) {
$visitorDetailsArray = Visitor::cleanVisitorDetails($visitorDetailRow->getColumns());
$visitor = new Visitor($visitorDetailsArray);
$visitorDetailsArray = $visitor->getAllVisitorDetails();
$visitorDetailsArray['siteCurrency'] = $website->getCurrency();
$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'));
$dateTimeVisitFirstAction = Date::factory($visitorDetailsArray['firstActionTimestamp'], $timezone);
$visitorDetailsArray['serverDatePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized(Piwik::translate('CoreHome_ShortDateFormat'));
$visitorDetailsArray['serverTimePrettyFirstAction'] = $dateTimeVisitFirstAction->getLocalized('%time%');
$visitorDetailsArray['actionDetails'] = array();
if (!$doNotFetchActions) {
$visitorDetailsArray = Visitor::enrichVisitorArrayWithActions($visitorDetailsArray, $actionsLimit, $timezone);
}
if ($flat) {
$visitorDetailsArray = Visitor::flattenVisitorDetailsArray($visitorDetailsArray);
}
$visitorDetailRow->setColumns($visitorDetailsArray);
}
});
}
private function loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment = false, $countVisitorsToFetch = 100, $visitorId = false, $minTimestamp = 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;
}
$dataTable = new DataTable();
$dataTable->addRowsFromSimpleArray($data);
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);
}
}

View file

@ -0,0 +1,253 @@
<?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\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;
/**
*/
class Controller extends \Piwik\Plugin\Controller
{
const SIMPLE_VISIT_COUNT_WIDGET_LAST_MINUTES_CONFIG_KEY = 'live_widget_visitor_count_last_minutes';
function index()
{
return $this->widget();
}
public function widget()
{
$view = new View('@Live/index');
$view->idSite = $this->idSite;
$view = $this->setCounters($view);
$view->liveRefreshAfterMs = (int)Config::getInstance()->General['live_widget_refresh_after_seconds'] * 1000;
$view->visitors = $this->getLastVisitsStart($fetchPlease = true);
$view->liveTokenAuth = Piwik::getCurrentUserTokenAuth();
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');
$view = $this->setCounters($view);
$view->idSite = $this->idSite;
return $this->render($view);
}
private function render(View $view)
{
$rendered = $view->render();
return $rendered;
}
public function indexVisitorLog()
{
$view = new View('@Live/indexVisitorLog.twig');
$view->filterEcommerce = Common::getRequestVar('filterEcommerce', 0, 'int');
$view->visitorLog = $this->getLastVisitsDetails();
return $view->render();
}
public function getLastVisitsDetails()
{
return $this->renderReport(__FUNCTION__);
}
/**
* Widget
*/
public function getVisitorLog()
{
return $this->getLastVisitsDetails();
}
public function getLastVisitsStart()
{
// hack, ensure we load today's visits by default
$_GET['date'] = 'today';
$_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");
$visitors = $api->process();
$view->visitors = $visitors;
return $this->render($view);
}
private function setCounters($view)
{
$segment = Request::getRawSegmentFromRequest();
$last30min = API::getInstance()->getCounters($this->idSite, $lastMinutes = 30, $segment);
$last30min = $last30min[0];
$today = API::getInstance()->getCounters($this->idSite, $lastMinutes = 24 * 60, $segment);
$today = $today[0];
$view->visitorsCountHalfHour = $last30min['visits'];
$view->visitorsCountToday = $today['visits'];
$view->pisHalfhour = $last30min['actions'];
$view->pisToday = $today['actions'];
return $view;
}
/**
* Echo's HTML for visitor profile popup.
*/
public function getVisitorProfilePopup()
{
$idSite = Common::getRequestVar('idSite', null, 'int');
$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->exportLink = $this->getVisitorProfileExportLink();
if (Common::getRequestVar('showMap', 1) == 1
&& !empty($view->visitorData['hasLatLong'])
&& \Piwik\Plugin\Manager::getInstance()->isPluginLoaded('UserCountryMap')
) {
$view->userCountryMapUrl = $this->getUserCountryMapUrlForVisitorProfile();
}
$this->setWidgetizedVisitorProfileUrl($view);
return $view->render();
}
public function getSingleVisitSummary()
{
$view = new View('@Live/getSingleVisitSummary.twig');
$visits = Request::processRequest('Live.getLastVisitsDetails', array(
'segment' => 'visitId==' . Common::getRequestVar('visitId'),
'period' => false,
'date' => false
));
$view->visitData = $visits->getFirstRow()->getColumns();
$view->visitReferralSummary = API::getReferrerSummaryForVisit($visits->getFirstRow());
$view->showLocation = true;
$this->setWidgetizedVisitorProfileUrl($view);
$view->exportLink = $this->getVisitorProfileExportLink();
return $view->render();
}
public function getVisitList()
{
$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_offset' => $startCounter,
'period' => false,
'date' => false
));
if (empty($nextVisits)) {
return;
}
$view = new View('@Live/getVisitList.twig');
$view->idSite = Common::getRequestVar('idSite', null, 'int');
$view->startCounter = $startCounter + 1;
$view->visits = $nextVisits;
return $view->render();
}
private function getVisitorProfileExportLink()
{
return Url::getCurrentQueryStringWithParametersModified(array(
'module' => 'API',
'action' => 'index',
'method' => 'Live.getVisitorProfile',
'format' => 'XML',
'expanded' => 1
));
}
private function setWidgetizedVisitorProfileUrl($view)
{
if (\Piwik\Plugin\Manager::getInstance()->isPluginLoaded('Widgetize')) {
$view->widgetizedLink = Url::getCurrentQueryStringWithParametersModified(array(
'module' => 'Widgetize',
'action' => 'iframe',
'moduleToWidgetize' => 'Live',
'actionToWidgetize' => 'getVisitorProfilePopup'
));
}
}
private function getUserCountryMapUrlForVisitorProfile()
{
$params = array(
'module' => 'UserCountryMap',
'action' => 'realtimeMap',
'segment' => self::getSegmentWithVisitorId(),
'visitorId' => false,
'changeVisitAlpha' => 0,
'removeOldVisits' => 0,
'realtimeWindow' => 'false',
'showFooterMessage' => 0,
'showDateTime' => 0,
'doNotRefreshVisits' => 1
);
return Url::getCurrentQueryStringWithParametersModified($params);
}
private static function getSegmentWithVisitorId()
{
static $cached = null;
if ($cached === null) {
$segment = Request::getRawSegmentFromRequest();
if (!empty($segment)) {
$segment = urldecode($segment) . ';';
}
$idVisitor = Common::getRequestVar('visitorId', false);
if ($idVisitor === false) {
$idVisitor = Request::processRequest('Live.getMostRecentVisitorId');
}
$cached = urlencode($segment . 'visitorId==' . $idVisitor);
}
return $cached;
}
}

View file

@ -0,0 +1,77 @@
<?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\Menu\MenuMain;
use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
use Piwik\WidgetsList;
require_once PIWIK_INCLUDE_PATH . '/plugins/Live/VisitorLog.php';
/**
*
*/
class Live extends \Piwik\Plugin
{
/**
* @see Piwik\Plugin::getListHooksRegistered
*/
public function getListHooksRegistered()
{
return array(
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
'WidgetsList.addWidgets' => 'addWidget',
'Menu.Reporting.addItems' => 'addMenu',
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
'ViewDataTable.getDefaultType' => 'getDefaultTypeViewDataTable'
);
}
public function getStylesheetFiles(&$stylesheets)
{
$stylesheets[] = "plugins/Live/stylesheets/live.less";
$stylesheets[] = "plugins/Live/stylesheets/visitor_profile.less";
}
public function getJsFiles(&$jsFiles)
{
$jsFiles[] = "plugins/Live/javascripts/live.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');
}
public function getClientSideTranslationKeys(&$translationKeys)
{
$translationKeys[] = "Live_VisitorProfile";
$translationKeys[] = "Live_NoMoreVisits";
$translationKeys[] = "Live_ShowMap";
$translationKeys[] = "Live_HideMap";
$translationKeys[] = "Live_PageRefreshed";
}
public function getDefaultTypeViewDataTable(&$defaultViewTypes)
{
$defaultViewTypes['Live.getLastVisitsDetails'] = VisitorLog::ID;
}
}

View file

@ -0,0 +1,992 @@
<?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\DataAccess\LogAggregator;
use Piwik\DataTable\Filter\ColumnDelete;
use Piwik\Date;
use Piwik\Db;
use Piwik\IP;
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\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
{
const DELIMITER_PLUGIN_NAME = ", ";
const EVENT_VALUE_PRECISION = 3;
function __construct($visitorRawData)
{
$this->details = $visitorRawData;
}
function getAllVisitorDetails()
{
return 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(),
);
}
function getVisitorId()
{
if (isset($this->details['idvisitor'])) {
return bin2hex($this->details['idvisitor']);
}
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']));
}
function getIp()
{
if (isset($this->details['location_ip'])) {
return IP::N2P($this->details['location_ip']);
}
return false;
}
function getIdVisit()
{
return $this->details['idvisit'];
}
function getIdSite()
{
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)
*
* @param array $visitorDetails
* @return array
*/
public static function cleanVisitorDetails($visitorDetails)
{
$toUnset = array('config_id');
if (Piwik::isUserIsAnonymous()) {
$toUnset[] = 'idvisitor';
$toUnset[] = 'location_ip';
}
foreach ($toUnset as $keyName) {
if (isset($visitorDetails[$keyName])) {
unset($visitorDetails[$keyName]);
}
}
return $visitorDetails;
}
/**
* The &flat=1 feature is used by API.getSuggestedValuesForSegment
*
* @param $visitorDetailsArray
* @return array
*/
public static function flattenVisitorDetailsArray($visitorDetailsArray)
{
// NOTE: if you flatten more fields from the "actionDetails" array
// ==> also update API/API.php getSuggestedValuesForSegment(), the $segmentsNeedActionsInfo array
// flatten visit custom variables
if (is_array($visitorDetailsArray['customVariables'])) {
foreach ($visitorDetailsArray['customVariables'] as $thisCustomVar) {
$visitorDetailsArray = array_merge($visitorDetailsArray, $thisCustomVar);
}
unset($visitorDetailsArray['customVariables']);
}
// flatten page views custom variables
$count = 1;
foreach ($visitorDetailsArray['actionDetails'] as $action) {
if (!empty($action['customVariables'])) {
foreach ($action['customVariables'] as $thisCustomVar) {
foreach ($thisCustomVar as $cvKey => $cvValue) {
$flattenedKeyName = $cvKey . ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP . $count;
$visitorDetailsArray[$flattenedKeyName] = $cvValue;
$count++;
}
}
}
}
// Flatten Goals
$count = 1;
foreach ($visitorDetailsArray['actionDetails'] as $action) {
if (!empty($action['goalId'])) {
$flattenedKeyName = 'visitConvertedGoalId' . ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP . $count;
$visitorDetailsArray[$flattenedKeyName] = $action['goalId'];
$count++;
}
}
// 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
$flatten = array( 'pageTitle', 'siteSearchKeyword', 'eventCategory', 'eventAction', 'eventName', 'eventValue');
foreach($flatten as $toFlatten) {
if (!empty($action[$toFlatten])) {
$flattenedKeyName = $toFlatten . ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP . $count;
$visitorDetailsArray[$flattenedKeyName] = $action[$toFlatten];
}
}
$count++;
}
// Entry/exit pages
$firstAction = $lastAction = false;
foreach ($visitorDetailsArray['actionDetails'] as $action) {
if ($action['type'] == 'action') {
if (empty($firstAction)) {
$firstAction = $action;
}
$lastAction = $action;
}
}
if (!empty($firstAction['pageTitle'])) {
$visitorDetailsArray['entryPageTitle'] = $firstAction['pageTitle'];
}
if (!empty($firstAction['url'])) {
$visitorDetailsArray['entryPageUrl'] = $firstAction['url'];
}
if (!empty($lastAction['pageTitle'])) {
$visitorDetailsArray['exitPageTitle'] = $lastAction['pageTitle'];
}
if (!empty($lastAction['url'])) {
$visitorDetailsArray['exitPageUrl'] = $lastAction['url'];
}
return $visitorDetailsArray;
}
/**
* @param $visitorDetailsArray
* @param $actionsLimit
* @param $timezone
* @return array
*/
public static function enrichVisitorArrayWithActions($visitorDetailsArray, $actionsLimit, $timezone)
{
$idVisit = $visitorDetailsArray['idVisit'];
$maxCustomVariables = CustomVariables::getMaxCustomVariables();
$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));
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];
$cvarKey = static::getCustomVariablePrettyKey($cvarKey);
$customVariablesPage[$i] = array(
'customVariablePageName' . $i => $cvarKey,
'customVariablePageValue' . $i => $actionDetail['custom_var_v' . $i],
);
}
unset($actionDetail['custom_var_k' . $i]);
unset($actionDetail['custom_var_v' . $i]);
}
if (!empty($customVariablesPage)) {
$actionDetail['customVariables'] = $customVariablesPage;
}
if($actionDetail['type'] == Action::TYPE_EVENT_CATEGORY) {
// Handle Event
if(strlen($actionDetail['pageTitle']) > 0) {
$actionDetail['eventName'] = $actionDetail['pageTitle'];
}
unset($actionDetail['pageTitle']);
} else if ($actionDetail['type'] == Action::TYPE_SITE_SEARCH) {
// Handle Site Search
$actionDetail['siteSearchKeyword'] = $actionDetail['pageTitle'];
unset($actionDetail['pageTitle']);
}
// Event value / Generation time
if($actionDetail['type'] == Action::TYPE_EVENT_CATEGORY) {
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);
}
unset($actionDetail['custom_float']);
if($actionDetail['type'] != Action::TYPE_EVENT_CATEGORY) {
unset($actionDetail['eventCategory']);
unset($actionDetail['eventAction']);
}
// Reconstruct url from prefix
$actionDetail['url'] = Tracker\PageUrl::reconstructNormalizedUrl($actionDetail['url'], $actionDetail['url_prefix']);
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));
foreach ($ecommerceDetails as &$ecommerceDetail) {
if ($ecommerceDetail['type'] == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
unset($ecommerceDetail['orderId']);
unset($ecommerceDetail['revenueSubTotal']);
unset($ecommerceDetail['revenueTax']);
unset($ecommerceDetail['revenueShipping']);
unset($ecommerceDetail['revenueDiscount']);
}
// 25.00 => 25
foreach ($ecommerceDetail as $column => $value) {
if (strpos($column, 'revenue') !== false) {
if ($value == round($value)) {
$ecommerceDetail[$column] = round($value);
}
}
}
}
// 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
);
$itemsDetails = Db::fetchAll($sql, $bind);
foreach ($itemsDetails as &$detail) {
if ($detail['price'] == round($detail['price'])) {
$detail['price'] = round($detail['price']);
}
}
$ecommerceConversion['itemDetails'] = $itemsDetails;
}
$actions = array_merge($actionDetails, $goalDetails, $ecommerceDetails);
usort($actions, array('static', 'sortByServerTime'));
$visitorDetailsArray['actionDetails'] = $actions;
foreach ($visitorDetailsArray['actionDetails'] as &$details) {
switch ($details['type']) {
case 'goal':
$details['icon'] = 'plugins/Zeitgeist/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';
break;
case Action::TYPE_DOWNLOAD:
$details['type'] = 'download';
$details['icon'] = 'plugins/Zeitgeist/images/download.png';
break;
case Action::TYPE_OUTLINK:
$details['type'] = 'outlink';
$details['icon'] = 'plugins/Zeitgeist/images/link.gif';
break;
case Action::TYPE_SITE_SEARCH:
$details['type'] = 'search';
$details['icon'] = 'plugins/Zeitgeist/images/search_ico.png';
break;
case Action::TYPE_EVENT_CATEGORY:
$details['type'] = 'event';
$details['icon'] = 'plugins/Zeitgeist/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%');
}
$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'),
);
if (isset($rename[$key])) {
return $rename[$key];
}
return $key;
}
private static function sortByServerTime($a, $b)
{
$ta = strtotime($a['serverTimePretty']);
$tb = strtotime($b['serverTimePretty']);
return $ta < $tb
? -1
: ($ta == $tb
? 0
: 1);
}
}

View file

@ -0,0 +1,107 @@
<?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;
}
)
);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Some files were not shown because too many files have changed in this diff Show more