| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| 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); |
| |
| 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(); |
|
|
| |
| $nextActionId = 0; |
| foreach ($actionDetails as $idx => &$action) { |
| if ($idx < $nextActionId || !$this->isPageView($action)) { |
| unset($action['timeSpentRef']); |
| continue; |
| } |
|
|
| $action['timeSpent'] = 0; |
|
|
| |
| $nextActionId = $idx; |
| $nextAction = null; |
|
|
| do { |
| $nextActionId++; |
|
|
| $nextAction = isset($actionDetails[$nextActionId]) ? $actionDetails[$nextActionId] : null; |
|
|
| if (is_null($nextAction)) { |
| |
| |
| |
| $visitTotalTime = $visitorDetails['visitDuration']; |
| $timeOfLastAction = Date::factory($action['serverTimePretty'])->getTimestamp(); |
|
|
| $timeSpentOnAllActionsApartFromLastOne = ($timeOfLastAction - $visitorDetails['firstActionTimestamp']); |
| $timeSpentOnPage = $visitTotalTime - $timeSpentOnAllActionsApartFromLastOne; |
|
|
| |
| if ($timeSpentOnPage > 10) { |
| $action['timeSpent'] = $timeSpentOnPage; |
| } |
| break; |
| } |
|
|
| if (!array_key_exists('timeSpentRef', $nextAction)) { |
| continue; |
| } |
|
|
| |
| if ($nextAction) { |
| $action['timeSpent'] += $nextAction['timeSpentRef'] ?? 0; |
| } |
|
|
| |
| 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) { |
| |
| $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']); |
|
|
| |
| 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']); |
| } |
|
|
| |
| 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 ); |
| } |
| } |
|
|
| 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; |
| } |
|
|
| |
| $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']); |
| } |
|
|
| |
| |
| |
| |
| protected function queryActionsForVisits($idVisits) |
| { |
| $customFields = array(); |
| $customJoins = array(); |
|
|
| Piwik::postEvent('Actions.getCustomActionDimensionFieldsAndJoins', array(&$customFields, &$customJoins)); |
|
|
| $customFields = array_filter($customFields); |
| array_unshift($customFields, ''); |
| $customActionDimensionFields = implode(', ', $customFields); |
|
|
| |
| |
| $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); |
| } |
|
|
| |
| |
| |
| private function handleIfDownloadAction($action, &$profile) |
| { |
| if (empty($action['type']) || $action['type'] != 'download') { |
| return; |
| } |
| $profile['totalDownloads']++; |
| } |
|
|
| |
| |
| |
| private function handleIfOutlinkAction($action, &$profile) |
| { |
| if (empty($action['type']) || $action['type'] != 'outlink') { |
| return; |
| } |
| $profile['totalOutlinks']++; |
| } |
|
|
| |
| |
| |
| 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) |
| { |
| |
| 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); |
| } |
| } |
| } |
|
|