339 lines
12 KiB
PHP
339 lines
12 KiB
PHP
<?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\Cache;
|
|
use Piwik\CacheId;
|
|
use Piwik\Config;
|
|
use Piwik\DataTable\Filter\ColumnDelete;
|
|
use Piwik\Date;
|
|
use Piwik\Metrics\Formatter;
|
|
use Piwik\Plugin;
|
|
use Piwik\Piwik;
|
|
use Piwik\Tracker\GoalManager;
|
|
|
|
class Visitor implements VisitorInterface
|
|
{
|
|
private $details = array();
|
|
|
|
function __construct($visitorRawData)
|
|
{
|
|
$this->details = $visitorRawData;
|
|
}
|
|
|
|
function getAllVisitorDetails()
|
|
{
|
|
$visitor = array();
|
|
|
|
$instances = self::getAllVisitorDetailsInstances();
|
|
|
|
foreach ($instances as $instance) {
|
|
$instance->setDetails($this->details);
|
|
$instance->extendVisitorDetails($visitor);
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
*
|
|
* @deprecated will be removed in Piwik 4
|
|
*/
|
|
Piwik::postEvent('Live.getAllVisitorDetails', array(&$visitor, $this->details));
|
|
|
|
return $visitor;
|
|
}
|
|
|
|
/**
|
|
* Returns all available visitor details instances
|
|
*
|
|
* @return VisitorDetailsAbstract[]
|
|
* @throws \Exception
|
|
*/
|
|
public static function getAllVisitorDetailsInstances()
|
|
{
|
|
$cacheId = CacheId::pluginAware('VisitorDetails');
|
|
$cache = Cache::getTransientCache();
|
|
|
|
if (!$cache->contains($cacheId)) {
|
|
$instances = [
|
|
new VisitorDetails() // needs to be first
|
|
];
|
|
|
|
/**
|
|
* Triggered to add new visitor details that cannot be picked up by the platform automatically.
|
|
*
|
|
* **Example**
|
|
*
|
|
* public function addVisitorDetails(&$visitorDetails)
|
|
* {
|
|
* $visitorDetails[] = new CustomVisitorDetails();
|
|
* }
|
|
*
|
|
* @param VisitorDetailsAbstract[] $visitorDetails An array of visitorDetails
|
|
*/
|
|
Piwik::postEvent('Live.addVisitorDetails', array(&$instances));
|
|
|
|
foreach (self::getAllVisitorDetailsClasses() as $className) {
|
|
$instance = new $className();
|
|
|
|
if ($instance instanceof VisitorDetails) {
|
|
continue;
|
|
}
|
|
|
|
$instances[] = $instance;
|
|
}
|
|
|
|
/**
|
|
* Triggered to filter / restrict vistor details.
|
|
*
|
|
* **Example**
|
|
*
|
|
* public function filterVisitorDetails(&$visitorDetails)
|
|
* {
|
|
* foreach ($visitorDetails as $index => $visitorDetail) {
|
|
* if (strpos(get_class($visitorDetail), 'MyPluginName') !== false) {}
|
|
* unset($visitorDetails[$index]); // remove all visitor details for a specific plugin
|
|
* }
|
|
* }
|
|
* }
|
|
*
|
|
* @param VisitorDetailsAbstract[] $visitorDetails An array of visitorDetails
|
|
*/
|
|
Piwik::postEvent('Live.filterVisitorDetails', array(&$instances));
|
|
|
|
$cache->save($cacheId, $instances);
|
|
}
|
|
|
|
return $cache->fetch($cacheId);
|
|
}
|
|
|
|
/**
|
|
* Returns class names of all VisitorDetails classes.
|
|
*
|
|
* @return string[]
|
|
* @api
|
|
*/
|
|
protected static function getAllVisitorDetailsClasses()
|
|
{
|
|
return Plugin\Manager::getInstance()->findComponents('VisitorDetails', 'Piwik\Plugins\Live\VisitorDetailsAbstract');
|
|
}
|
|
|
|
function getVisitorId()
|
|
{
|
|
if (isset($this->details['idvisitor'])) {
|
|
return bin2hex($this->details['idvisitor']);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Removes fields that are not meant to be displayed (md5 config hash)
|
|
* Or that the user should only access if they are 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[] = 'user_id';
|
|
$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 (!empty($visitorDetailsArray['customVariables'])
|
|
&& 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) {
|
|
|
|
// API.getSuggestedValuesForSegment
|
|
$flattenForActionType = array(
|
|
'outlink' => 'outlinkUrl',
|
|
'download' => 'downloadUrl',
|
|
'event' => 'eventUrl',
|
|
'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])) {
|
|
$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 array $visitorDetailsArray
|
|
* @param array $actionDetails preset action details
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function enrichVisitorArrayWithActions($visitorDetailsArray, $actionDetails = array())
|
|
{
|
|
$actionsLimit = (int)Config::getInstance()->General['visitor_log_maximum_actions_per_visit'];
|
|
$visitorDetailsManipulators = self::getAllVisitorDetailsInstances();
|
|
|
|
foreach ($visitorDetailsManipulators as $instance) {
|
|
$instance->provideActionsForVisit($actionDetails, $visitorDetailsArray);
|
|
}
|
|
|
|
foreach ($visitorDetailsManipulators as $instance) {
|
|
$instance->filterActions($actionDetails, $visitorDetailsArray);
|
|
}
|
|
|
|
usort($actionDetails, array('static', 'sortByServerTime'));
|
|
|
|
$actionDetails = array_values($actionDetails);
|
|
|
|
// limit actions
|
|
if ($actionsLimit < count($actionDetails)) {
|
|
$visitorDetailsArray['truncatedActionsCount'] = count($actionDetails) - $actionsLimit;
|
|
$actionDetails = array_slice($actionDetails, 0, $actionsLimit);
|
|
}
|
|
|
|
foreach ($actionDetails as $actionIdx => &$actionDetail) {
|
|
$actionDetail =& $actionDetails[$actionIdx];
|
|
$nextAction = isset($actionDetails[$actionIdx+1]) ? $actionDetails[$actionIdx+1] : null;
|
|
|
|
foreach ($visitorDetailsManipulators as $instance) {
|
|
$instance->extendActionDetails($actionDetail, $nextAction, $visitorDetailsArray);
|
|
}
|
|
}
|
|
|
|
$visitorDetailsArray['actionDetails'] = $actionDetails;
|
|
|
|
return $visitorDetailsArray;
|
|
}
|
|
|
|
private static function sortByServerTime($a, $b)
|
|
{
|
|
$ta = strtotime($a['serverTimePretty']);
|
|
$tb = strtotime($b['serverTimePretty']);
|
|
|
|
if ($ta < $tb) {
|
|
return -1;
|
|
}
|
|
|
|
if ($ta == $tb) {
|
|
if ($a['idlink_va'] == $b['idlink_va']) {
|
|
return strcmp($a['type'], $b['type']);
|
|
}
|
|
|
|
if ($a['idlink_va'] > $b['idlink_va']) {
|
|
return 1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|