Download plugins/Actions/VisitorDetails.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 20.5 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/plugins/Actions/VisitorDetails.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/plugins/Actions/VisitorDetails.php
-
curl -L -o VisitorDetails.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/plugins/Actions/VisitorDetails.php
20.5 kB
| /** | |
| * Matomo - free/libre analytics platform | |
| * | |
| * @link https://matomo.org | |
| * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later | |
| */ | |
| namespace Piwik\Plugins\Actions; | |
| use Piwik\Common; | |
| use Piwik\Date; | |
| use Piwik\Metrics\Formatter; | |
| use Piwik\Piwik; | |
| use Piwik\Plugin; | |
| use Piwik\Plugins\Live\VisitorDetailsAbstract; | |
| use Piwik\Site; | |
| use Piwik\Tracker\Action; | |
| use Piwik\Tracker\PageUrl; | |
| class VisitorDetails extends VisitorDetailsAbstract | |
| { | |
| public function extendVisitorDetails(&$visitor) | |
| { | |
| $visitor['searches'] = $this->details['visit_total_searches']; | |
| $visitor['actions'] = $this->details['visit_total_actions']; | |
| $visitor['interactions'] = $this->details['visit_total_interactions']; | |
| } | |
| public function provideActionsForVisitIds(&$actions, $visitIds) | |
| { | |
| $actionDetails = $this->queryActionsForVisits($visitIds); | |
| // use while / array_shift combination instead of foreach to save memory | |
| while (is_array($actionDetails) && count($actionDetails)) { | |
| $action = array_shift($actionDetails); | |
| $idVisit = $action['idvisit']; | |
| unset($action['idvisit']); | |
| $actions[$idVisit][] = $action; | |
| } | |
| } | |
| public function provideActionsForVisit(&$actions, $visitorDetails) | |
| { | |
| $actionDetails = $actions; | |
| $formatter = new Formatter(); | |
| // Enrich with time spent per action | |
| $nextActionId = 0; | |
| foreach ($actionDetails as $idx => &$action) { | |
| if ($idx < $nextActionId || !$this->isPageView($action)) { | |
| unset($action['timeSpentRef']); | |
| continue; // skip to next page view | |
| } | |
| $action['timeSpent'] = 0; | |
| // search for next action with timeSpentRef | |
| $nextActionId = $idx; | |
| $nextAction = null; | |
| do { | |
| $nextActionId++; | |
| $nextAction = isset($actionDetails[$nextActionId]) ? $actionDetails[$nextActionId] : null; | |
| if (is_null($nextAction)) { | |
| // Last action of a visit. | |
| // By default, Matomo 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 = $visitorDetails['visitDuration']; | |
| $timeOfLastAction = Date::factory($action['serverTimePretty'])->getTimestamp(); | |
| $timeSpentOnAllActionsApartFromLastOne = ($timeOfLastAction - $visitorDetails['firstActionTimestamp']); | |
| $timeSpentOnPage = $visitTotalTime - $timeSpentOnAllActionsApartFromLastOne; | |
| // Safe net, we assume the time is correct when it's more than 10 seconds | |
| if ($timeSpentOnPage > 10) { | |
| $action['timeSpent'] = $timeSpentOnPage; | |
| } | |
| break; | |
| } | |
| if (!array_key_exists('timeSpentRef', $nextAction)) { | |
| continue; | |
| } | |
| // Set the time spent for this action (which is the timeSpentRef of the next action) | |
| if ($nextAction) { | |
| $action['timeSpent'] += $nextAction['timeSpentRef'] ?? 0; | |
| } | |
| // sum spent time until next page view | |
| if ($this->isPageView($nextAction)) { | |
| break; | |
| } | |
| } while (isset($actionDetails[$nextActionId])); | |
| if (isset($action['timeSpent'])) { | |
| $action['timeSpentPretty'] = $formatter->getPrettyTimeFromSeconds($action['timeSpent'], true); | |
| } | |
| unset($action['timeSpentRef']); | |
| } | |
| $actions = $actionDetails; | |
| } | |
| private function shouldHandleAction($action) | |
| { | |
| $actionTypesToHandle = array( | |
| Action::TYPE_PAGE_URL, | |
| Action::TYPE_PAGE_TITLE, | |
| Action::TYPE_SITE_SEARCH, | |
| Action::TYPE_EVENT, | |
| Action::TYPE_OUTLINK, | |
| Action::TYPE_DOWNLOAD, | |
| ); | |
| if (empty($action['type'])) { | |
| return !empty($action['eventType']); | |
| } | |
| return in_array($action['type'], $actionTypesToHandle) || !empty($action['eventType']); | |
| } | |
| private function isPageView($action) | |
| { | |
| if (empty($action['type'])) { | |
| return false; | |
| } | |
| $pageViewTypes = array( | |
| Action::TYPE_PAGE_URL, | |
| Action::TYPE_PAGE_TITLE, | |
| ); | |
| return in_array($action['type'], $pageViewTypes); | |
| } | |
| public function extendActionDetails(&$action, $nextAction, $visitorDetails) | |
| { | |
| $actionType = ''; | |
| if (!empty($action['type'])) { | |
| $actionType = $action['type']; | |
| } | |
| $formatter = new Formatter(); | |
| if ($actionType == Action::TYPE_SITE_SEARCH) { | |
| // Handle Site Search | |
| $action['siteSearchKeyword'] = $action['pageTitle']; | |
| $action['siteSearchCategory'] = $action['search_cat']; | |
| $action['siteSearchCount'] = $action['search_count']; | |
| unset($action['pageTitle']); | |
| } | |
| unset($action['search_cat']); | |
| unset($action['search_count']); | |
| // Generation time | |
| if ($this->shouldHandleAction($action) && empty($action['eventType']) && isset($action['custom_float']) && $action['custom_float'] > 0) { | |
| $action['generationTimeMilliseconds'] = $action['custom_float']; | |
| $action['generationTime'] = $formatter->getPrettyTimeFromSeconds($action['custom_float'] / 1000, true); | |
| unset($action['custom_float']); | |
| } | |
| if (array_key_exists('custom_float', $action) && is_null($action['custom_float'])) { | |
| unset($action['custom_float']); | |
| } | |
| if (!array_key_exists('pageLoadTime', $action) || $action['pageLoadTime'] <= 0) { | |
| unset($action['pageLoadTime']); | |
| } else { | |
| $action['pageLoadTimeMilliseconds'] = $action['pageLoadTime']; | |
| $action['pageLoadTime'] = $formatter->getPrettyTimeFromSeconds($action['pageLoadTime'] / 1000, true); | |
| } | |
| if (array_key_exists('pageview_position', $action)) { | |
| $action['pageviewPosition'] = $action['pageview_position']; | |
| unset($action['pageview_position']); | |
| } | |
| // Reconstruct url from prefix | |
| if (array_key_exists('url', $action) && array_key_exists('url_prefix', $action)) { | |
| if ($action['url'] && stripos($action['url'], 'http://') !== 0 && stripos($action['url'], 'https://') !== 0) { | |
| $url = PageUrl::reconstructNormalizedUrl($action['url'], $action['url_prefix']); | |
| $url = Common::unsanitizeInputValue($url); | |
| $action['url'] = $url; | |
| } | |
| unset($action['url_prefix']); | |
| } | |
| if (!empty($action['url']) && strpos($action['url'], 'http://') === 0) { | |
| $host = parse_url($action['url'], PHP_URL_HOST); | |
| if ($host && PageUrl::shouldUseHttpsHost($visitorDetails['idSite'], $host)) { | |
| $action['url'] = 'https://' . mb_substr($action['url'], 7 /* = strlen('http://') */); | |
| } | |
| } | |
| switch ($actionType) { | |
| case 'goal': | |
| $action['icon'] = 'plugins/Morpheus/images/goal.png'; | |
| $action['iconSVG'] = 'plugins/Morpheus/images/goal.svg'; | |
| $action['title'] = Piwik::translate('Goals_GoalConversion'); | |
| $action['subtitle'] = $action['goalName']; | |
| if (!empty($action['revenue'])) { | |
| $action['subtitle'] .= ' (' . Piwik::translate('Goals_NRevenue', $formatter->getPrettyMoney($action['revenue'], $visitorDetails['idSite'])) . ')'; | |
| } | |
| break; | |
| case Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER: | |
| case Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART: | |
| $action['icon'] = 'plugins/Morpheus/images/' . $actionType . '.png'; | |
| $action['iconSVG'] = 'plugins/Morpheus/images/' . $actionType . '.svg'; | |
| if ($actionType == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { | |
| $action['title'] = Piwik::translate('CoreHome_VisitStatusOrdered') . ' (' . $action['orderId'] . ')'; | |
| } else { | |
| $action['title'] = Piwik::translate('Goals_AbandonedCart'); | |
| } | |
| $itemNames = implode(', ', array_column($action['itemDetails'], 'itemName')); | |
| $action['subtitle'] = Piwik::translate('Goals_NRevenue', $formatter->getPrettyMoney($action['revenue'], $visitorDetails['idSite'])); | |
| $action['subtitle'] .= ' - ' . Piwik::translate('Goals_NItems', $action['items']) . ': ' . $itemNames . ')'; | |
| break; | |
| case Action::TYPE_CONTENT: | |
| if (!empty($action['contentInteraction'])) { | |
| $action['icon'] = 'plugins/Morpheus/images/contentinteraction.png'; | |
| $action['iconSVG'] = 'plugins/Morpheus/images/contentinteraction.svg'; | |
| $action['title'] = Piwik::translate('Contents_ContentInteraction') . ' (' . $action['contentInteraction'] . ')'; | |
| } else { | |
| $action['icon'] = 'plugins/Morpheus/images/contentimpression.png'; | |
| $action['iconSVG'] = 'plugins/Morpheus/images/contentimpression.svg'; | |
| $action['title'] = Piwik::translate('Contents_ContentImpression'); | |
| } | |
| $action['subtitle'] = $action['contentName']; | |
| if (!empty($action['contentPiece'])) { | |
| $action['subtitle'] .= ' - ' . $action['contentPiece']; | |
| } | |
| break; | |
| case Action::TYPE_DOWNLOAD: | |
| $action['type'] = 'download'; | |
| $action['icon'] = 'plugins/Morpheus/images/download.png'; | |
| $action['iconSVG'] = 'plugins/Morpheus/images/download.svg'; | |
| $action['title'] = Piwik::translate('General_Download'); | |
| $action['subtitle'] = $action['url']; | |
| break; | |
| case Action::TYPE_OUTLINK: | |
| $action['type'] = 'outlink'; | |
| $action['icon'] = 'plugins/Morpheus/images/link.png'; | |
| $action['iconSVG'] = 'plugins/Morpheus/images/link.svg'; | |
| $action['title'] = Piwik::translate('General_Outlink'); | |
| $action['subtitle'] = $action['url']; | |
| break; | |
| case Action::TYPE_SITE_SEARCH: | |
| $action['type'] = 'search'; | |
| $action['icon'] = 'plugins/Morpheus/images/search.png'; | |
| $action['iconSVG'] = 'plugins/Morpheus/images/search.svg'; | |
| $action['title'] = Piwik::translate('Actions_SubmenuSitesearch'); | |
| $action['subtitle'] = $action['siteSearchKeyword']; | |
| if (!empty($action['siteSearchCategory'])) { | |
| $action['subtitle'] .= ' - ' . Piwik::translate('Actions_ColumnSearchCategory') . ': ' . $action['siteSearchCategory']; | |
| } | |
| if (!empty($action['siteSearchCount'])) { | |
| $action['subtitle'] .= ' - ' . Piwik::translate('Actions_ColumnSearchResultsCount') . ': ' . $action['siteSearchCount']; | |
| } | |
| break; | |
| case Action::TYPE_PAGE_URL: | |
| case Action::TYPE_PAGE_TITLE: | |
| case '': | |
| if (!isset($action['title'])) { | |
| $action['title'] = $action['pageTitle']; | |
| $action['subtitle'] = $action['url']; | |
| } | |
| $action['type'] = 'action'; | |
| $action['icon'] = ''; | |
| $action['iconSVG'] = 'plugins/Morpheus/images/action.svg'; | |
| break; | |
| } | |
| // Convert datetimes to the site timezone | |
| $dateTimeVisit = Date::factory( | |
| $action['serverTimePretty'], | |
| Site::getTimezoneFor($visitorDetails['idSite']) | |
| ); | |
| $action['serverTimePretty'] = $dateTimeVisit->getLocalized(Date::DATETIME_FORMAT_SHORT); | |
| $action['timestamp'] = $dateTimeVisit->getTimestamp(); | |
| unset($action['idlink_va']); | |
| } | |
| /** | |
| * @param array $idVisits | |
| * @return array | |
| */ | |
| protected function queryActionsForVisits($idVisits) | |
| { | |
| $customFields = array(); | |
| $customJoins = array(); | |
| Piwik::postEvent('Actions.getCustomActionDimensionFieldsAndJoins', array(&$customFields, &$customJoins)); | |
| $customFields = array_filter($customFields); | |
| array_unshift($customFields, ''); // add empty element at first | |
| $customActionDimensionFields = implode(', ', $customFields); | |
| // 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 | |
| $pagePerformanceSelect = ''; | |
| if (Plugin\Manager::getInstance()->isPluginActivated('PagePerformance')) { | |
| $pagePerformanceSelect = '( log_link_visit_action.time_network + | |
| log_link_visit_action.time_server + | |
| log_link_visit_action.time_transfer + | |
| log_link_visit_action.time_dom_completion + | |
| log_link_visit_action.time_dom_processing + | |
| log_link_visit_action.time_on_load ) AS pageLoadTime,'; | |
| } | |
| $sql = " | |
| SELECT | |
| log_link_visit_action.idvisit, | |
| COALESCE(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.idpageview, | |
| 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, | |
| $pagePerformanceSelect | |
| log_link_visit_action.pageview_position, | |
| log_link_visit_action.search_cat, | |
| log_link_visit_action.search_count | |
| " . $customActionDimensionFields . " | |
| 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 | |
| " . implode(" ", $customJoins) . " | |
| WHERE log_link_visit_action.idvisit IN ('" . implode("','", $idVisits) . "') | |
| ORDER BY log_link_visit_action.idvisit, log_link_visit_action.server_time, log_link_visit_action.idlink_va | |
| "; | |
| $actionDetails = $this->getDb()->fetchAll($sql); | |
| return $actionDetails; | |
| } | |
| private $visitedPageUrls = array(); | |
| private $siteSearchKeywords = array(); | |
| private $pageGenerationTimeTotal = 0; | |
| private $pageLoadTimeTotal = 0; | |
| public function initProfile($visits, &$profile) | |
| { | |
| $this->visitedPageUrls = array(); | |
| $this->siteSearchKeywords = array(); | |
| $this->pageGenerationTimeTotal = 0; | |
| $this->pageLoadTimeTotal = 0; | |
| $profile['totalActions'] = 0; | |
| $profile['totalOutlinks'] = 0; | |
| $profile['totalDownloads'] = 0; | |
| $profile['totalSearches'] = 0; | |
| $profile['totalPageViews'] = 0; | |
| $profile['totalUniquePageViews'] = 0; | |
| $profile['totalRevisitedPages'] = 0; | |
| $profile['totalPageViewsWithTiming'] = 0; | |
| $profile['totalPageViewsWithLoadTime'] = 0; | |
| $profile['searches'] = array(); | |
| } | |
| public function handleProfileVisit($visit, &$profile) | |
| { | |
| $profile['totalActions'] += $visit->getColumn('actions'); | |
| } | |
| public function handleProfileAction($action, &$profile) | |
| { | |
| $this->handleIfDownloadAction($action, $profile); | |
| $this->handleIfOutlinkAction($action, $profile); | |
| $this->handleIfSiteSearchAction($action, $profile); | |
| $this->handleIfPageViewAction($action, $profile); | |
| $this->handleIfPageGenerationTime($action, $profile); | |
| $this->handleIfPageLoadTime($action, $profile); | |
| } | |
| public function finalizeProfile($visits, &$profile) | |
| { | |
| $profile['visitedPages'] = []; | |
| foreach ($this->visitedPageUrls as $visitedPageUrl => $count) { | |
| $profile['visitedPages'][] = [ | |
| 'url' => $visitedPageUrl, | |
| 'count' => $count, | |
| ]; | |
| } | |
| usort($profile['visitedPages'], function ($a, $b) { | |
| if ($a['count'] == $b['count']) { | |
| return strcmp($a['url'], $b['url']); | |
| } | |
| return $a['count'] > $b['count'] ? -1 : 1; | |
| }); | |
| $this->handleSiteSearches($profile); | |
| $this->handleAveragePageGenerationTime($profile); | |
| $this->handleAveragePageLoadTime($profile); | |
| } | |
| /** | |
| * @param $action | |
| */ | |
| private function handleIfDownloadAction($action, &$profile) | |
| { | |
| if (empty($action['type']) || $action['type'] != 'download') { | |
| return; | |
| } | |
| $profile['totalDownloads']++; | |
| } | |
| /** | |
| * @param $action | |
| */ | |
| private function handleIfOutlinkAction($action, &$profile) | |
| { | |
| if (empty($action['type']) || $action['type'] != 'outlink') { | |
| return; | |
| } | |
| $profile['totalOutlinks']++; | |
| } | |
| /** | |
| * @param $action | |
| */ | |
| private function handleIfPageViewAction($action, &$profile) | |
| { | |
| if (empty($action['type']) || $action['type'] != 'action') { | |
| return; | |
| } | |
| $profile['totalPageViews']++; | |
| $pageUrl = $action['url']; | |
| if (!empty($pageUrl)) { | |
| if (!array_key_exists($pageUrl, $this->visitedPageUrls)) { | |
| $this->visitedPageUrls[$pageUrl] = 0; | |
| $profile['totalUniquePageViews']++; | |
| } | |
| $this->visitedPageUrls[$pageUrl]++; | |
| if ($this->visitedPageUrls[$pageUrl] == 2) { | |
| $profile['totalRevisitedPages']++; | |
| } | |
| } | |
| } | |
| private function handleIfSiteSearchAction($action, &$profile) | |
| { | |
| if (!isset($action['siteSearchKeyword'])) { | |
| return; | |
| } | |
| $keyword = $action['siteSearchKeyword']; | |
| if (!isset($this->siteSearchKeywords[$keyword])) { | |
| $this->siteSearchKeywords[$keyword] = 0; | |
| ++$profile['totalSearches']; | |
| } | |
| ++$this->siteSearchKeywords[$keyword]; | |
| } | |
| private function handleSiteSearches(&$profile) | |
| { | |
| // sort by visit/action | |
| arsort($this->siteSearchKeywords); | |
| foreach ($this->siteSearchKeywords as $keyword => $searchCount) { | |
| $profile['searches'][] = array( | |
| 'keyword' => $keyword, | |
| 'searches' => $searchCount, | |
| ); | |
| } | |
| } | |
| private function handleIfPageGenerationTime($action, &$profile) | |
| { | |
| if (isset($action['generationTimeMilliseconds'])) { | |
| $this->pageGenerationTimeTotal += $action['generationTimeMilliseconds']; | |
| ++$profile['totalPageViewsWithTiming']; | |
| } | |
| } | |
| private function handleAveragePageGenerationTime(&$profile) | |
| { | |
| if ($profile['totalPageViewsWithTiming']) { | |
| $profile['averagePageGenerationTime'] = | |
| round($this->pageGenerationTimeTotal / (1000 * $profile['totalPageViewsWithTiming']), $precision = 3); | |
| } | |
| } | |
| private function handleIfPageLoadTime($action, &$profile) | |
| { | |
| if (isset($action['pageLoadTimeMilliseconds'])) { | |
| $this->pageLoadTimeTotal += $action['pageLoadTimeMilliseconds']; | |
| ++$profile['totalPageViewsWithLoadTime']; | |
| } | |
| } | |
| private function handleAveragePageLoadTime(&$profile) | |
| { | |
| if ($profile['totalPageViewsWithLoadTime']) { | |
| $profile['averagePageLoadTime'] = | |
| round($this->pageLoadTimeTotal / (1000 * $profile['totalPageViewsWithLoadTime']), $precision = 3); | |
| } | |
| } | |
| } | |