diff --git a/core/API/DocumentationGenerator.php b/core/API/DocumentationGenerator.php index 1c2d9a62a43..ff830133e77 100644 --- a/core/API/DocumentationGenerator.php +++ b/core/API/DocumentationGenerator.php @@ -132,6 +132,7 @@ public function getExampleUrl($class, $methodName, $parametersToSet = array()) 'lastMinutes' => '30', 'abandonedCarts' => '0', 'ip' => '194.57.91.215', +// 'segmentName' => 'browserCode', ); foreach ($parametersToSet as $name => $value) { @@ -231,4 +232,5 @@ public function getParametersString($class, $name) $sParameters = implode(", ", $asParameters); return "($sParameters)"; } + } diff --git a/core/API/Request.php b/core/API/Request.php index 1ce66abf379..b30fd663c16 100644 --- a/core/API/Request.php +++ b/core/API/Request.php @@ -111,7 +111,7 @@ private function sanitizeRequest() * It then calls the API Proxy which will call the requested method. * * @throws Piwik_FrontController_PluginDeactivatedException - * @return mixed The data resulting from the API call + * @return Piwik_DataTable|mixed The data resulting from the API call */ public function process() { diff --git a/core/DataTable.php b/core/DataTable.php index 1784802799a..b5e2064980e 100644 --- a/core/DataTable.php +++ b/core/DataTable.php @@ -675,6 +675,26 @@ public function getColumn($name) return $columnValues; } + /** + * Returns the array containing all rows values of all columns which name starts with $name + * + * @param $name + * @return array + */ + public function getColumnsStartingWith($name) + { + $columnValues = array(); + foreach ($this->getRows() as $row) { + $columns = $row->getColumns(); + foreach($columns as $column => $value) { + if(strpos($column, $name) === 0) { + $columnValues[] = $row->getColumn($column); + } + } + } + return $columnValues; + } + /** * Returns an array containing the rows Metadata values * diff --git a/core/DataTable/Filter/ColumnDelete.php b/core/DataTable/Filter/ColumnDelete.php index 3ac3f5b244a..709d56a02e1 100644 --- a/core/DataTable/Filter/ColumnDelete.php +++ b/core/DataTable/Filter/ColumnDelete.php @@ -33,6 +33,14 @@ class Piwik_DataTable_Filter_ColumnDelete extends Piwik_DataTable_Filter */ private $columnsToKeep; + /** + * Hack: when specifying "showColumns", sometimes we'd like to also keep columns that "look" like a given column, + * without manually specifying all these columns (which may not be possible if column names are generated dynamically) + * + * Column will be kept, if they match any name in the $columnsToKeep, or if they look like anyColumnToKeep__anythingHere + */ + const APPEND_TO_COLUMN_NAME_TO_KEEP = '__'; + /** * Delete the column, only if the value was zero * @@ -101,8 +109,18 @@ public function filter($table) if (!empty($this->columnsToKeep)) { foreach ($table->getRows() as $row) { foreach ($row->getColumns() as $name => $value) { - // label cannot be removed via whitelisting - if ($name != 'label' && !isset($this->columnsToKeep[$name])) { + + $keep = false; + // @see self::APPEND_TO_COLUMN_NAME_TO_KEEP + foreach($this->columnsToKeep as $nameKeep => $true) { + if(strpos($name, $nameKeep . self::APPEND_TO_COLUMN_NAME_TO_KEEP) === 0) { + $keep = true; + } + } + + if (!$keep + && $name != 'label' // label cannot be removed via whitelisting + && !isset($this->columnsToKeep[$name])) { $row->deleteColumn($name); } } diff --git a/core/Segment.php b/core/Segment.php index 7802ba440d3..6ba00c64781 100644 --- a/core/Segment.php +++ b/core/Segment.php @@ -58,11 +58,6 @@ public function __construct($string, $idSites) $segment->setSubExpressionsAfterCleanup($cleanedExpressions); } - public function getPrettyString() - { - //@TODO segment.getPrettyString - } - public function isEmpty() { return empty($this->string); @@ -71,16 +66,6 @@ public function isEmpty() protected $availableSegments = array(); protected $segmentsHumanReadable = ''; - private function getUniqueSqlFields() - { - $expressions = $this->segment->parsedSubExpressions; - $uniqueFields = array(); - foreach ($expressions as $expression) { - $uniqueFields[] = $expression[Piwik_SegmentExpression::INDEX_OPERAND][0]; - } - return $uniqueFields; - } - protected function getCleanedExpression($expression) { if (empty($this->availableSegments)) { @@ -114,7 +99,7 @@ protected function getCleanedExpression($expression) if (isset($segment['sqlFilter']) && !empty($segment['sqlFilter']) ) { - $value = call_user_func($segment['sqlFilter'], $value, $segment['sqlSegment'], $matchType); + $value = call_user_func($segment['sqlFilter'], $value, $segment['sqlSegment'], $matchType, $name); // sqlFilter-callbacks might return arrays for more complex cases // e.g. see Piwik_Actions::getIdActionFromSegment() diff --git a/lang/en.php b/lang/en.php index bdba2286c3e..4063febf0b5 100644 --- a/lang/en.php +++ b/lang/en.php @@ -413,6 +413,7 @@ 'Actions_ColumnSearchCategory' => 'Search Category', 'Actions_ColumnSearchResultsCount' => 'Search Results Count', 'Actions_ColumnSearchKeyword' => 'Keyword', + 'Actions_SiteSearchKeyword' => 'Keyword (Site Search)', 'Actions_ColumnClickedURL' => 'Clicked URL', 'Actions_ColumnDownloadURL' => 'Download URL', 'Actions_ColumnEntryPageURL' => 'Entry Page URL', diff --git a/libs/PiwikTracker/PiwikTracker.php b/libs/PiwikTracker/PiwikTracker.php index 97e5acbc82a..7f609317ca1 100644 --- a/libs/PiwikTracker/PiwikTracker.php +++ b/libs/PiwikTracker/PiwikTracker.php @@ -839,7 +839,7 @@ public function setBrowserHasCookies($bool) */ public function setDebugStringAppend($string) { - $this->DEBUG_APPEND_URL = $string; + $this->DEBUG_APPEND_URL = '&' . $string; } /** diff --git a/plugins/API/API.php b/plugins/API/API.php index 5ca4e2156c1..9c69be2d5c7 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -78,7 +78,8 @@ public function getCssFiles($notification) *
  • the list of metrics that will be returned by each method, along with their human readable name, via "getDefaultMetrics" and "getDefaultProcessedMetrics"
  • *
  • the list of segments metadata supported by all functions that have a 'segment' parameter
  • *
  • the (truly magic) method "getProcessedReport" will return a human readable version of any other report, and include the processed metrics such as - * conversion rate, time on site, etc. which are not directly available in other methods. + * conversion rate, time on site, etc. which are not directly available in other methods.
  • + *
  • the method "getSuggestedValuesForSegment" returns top suggested values for a particular segment. It uses the Live.getLastVisitsDetails API to fetch the most recently used values, and will return the most often used values first.
  • * * The Metadata API is for example used by the Piwik Mobile App to automatically display all Piwik reports, with translated report & columns names and nicely formatted values. * More information on the Metadata API documentation page @@ -912,7 +913,7 @@ private function hideShowMetrics($columns, $emptyColumns = array()) foreach ($columns as $name => $ignore) { // if the current column should not be kept, remove it $idx = array_search($name, $columnsToKeep); - if ($idx === FALSE) // if $name is not in $columnsToKeep + if ($idx === false) // if $name is not in $columnsToKeep { unset($columns[$name]); } @@ -1625,4 +1626,65 @@ public function getBulkRequest($urls) } return $result; } + + /** + * Given a segment, will return a list of the most used values for this particular segment. + * @param $segmentName + * @param $idSite + * @throws Exception + */ + public function getSuggestedValuesForSegment($segmentName, $idSite) + { + Piwik::checkUserHasViewAccess($idSite); + $maxSuggestionsToReturn = 30; + $segmentsMetadata = $this->getSegmentsMetadata($idSite, $_hideImplementationData = false); + + $segmentFound = false; + foreach($segmentsMetadata as $segmentMetadata) { + if($segmentMetadata['segment'] == $segmentName) { + $segmentFound = $segmentMetadata; + break; + } + } + if(empty($segmentFound)) { + throw new Exception("Requested segment not found."); + } + + $startDate = Piwik_Date::now()->subDay(60)->toString(); + + // we know which SQL field this segment matches to: call the LIVE api to get last 1000 visitors values + $request = new Piwik_API_Request("method=Live.getLastVisitsDetails + &idSite=$idSite + &period=range + &date=$startDate,today + &filter_limit=10000 + &format=original + &serialize=0 + &flat=1 + &segment="); + $table = $request->process(); + if(empty($table)) { + throw new Exception("There was no data to suggest for $segmentName"); + } + + // Cleanup data to return the top suggested (non empty) labels for this segment + $values = $table->getColumn($segmentName); + + // Select also flattened keys (custom variables "page" scope, page URLs for one visit, page titles for one visit) + $valuesBis = $table->getColumnsStartingWith($segmentName . Piwik_DataTable_Filter_ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP); + $values = array_merge($values, $valuesBis); + + // remove false values (while keeping zeros) + $values = array_filter( $values, 'strlen' ); + + // we have a list of all values. let's show the most frequently used first. + $values = array_count_values( $values ); + arsort($values); + $values = array_keys($values); + + $values = array_map( array('Piwik_Common', 'unsanitizeInputValue'), $values); + + $values = array_slice($values, 0, $maxSuggestionsToReturn); + return $values; + } } diff --git a/plugins/Actions/Actions.php b/plugins/Actions/Actions.php index 097d2e3dee0..8a9be015d73 100644 --- a/plugins/Actions/Actions.php +++ b/plugins/Actions/Actions.php @@ -103,7 +103,14 @@ public function getSegmentsMetadata($notification) 'sqlSegment' => 'log_link_visit_action.idaction_name', 'sqlFilter' => $sqlFilter, ); - // TODO here could add keyword segment and hack $sqlFilter to make it select the right idaction + $segments[] = array( + 'type' => 'dimension', + 'category' => 'Actions_Actions', + 'name' => 'Actions_SiteSearchKeyword', + 'segment' => 'siteSearchKeyword', + 'sqlSegment' => 'log_link_visit_action.idaction_name', + 'sqlFilter' => $sqlFilter, + ); } /** @@ -113,30 +120,29 @@ public function getSegmentsMetadata($notification) * Usually, these callbacks only return a value that should be compared to the * column in the database. In this case, that doesn't work since multiple IDs * can match an expression (e.g. "pageUrl=@foo"). - * @param string $string + * @param string $valueToMatch * @param string $sqlField * @param string $matchType * @throws Exception * @return array|int|string */ - public function getIdActionFromSegment($string, $sqlField, $matchType = '==') + public function getIdActionFromSegment($valueToMatch, $sqlField, $matchType, $segmentName) { - // Field is visit_*_idaction_url or visit_*_idaction_name - $actionType = strpos($sqlField, '_name') === false - ? Piwik_Tracker_Action::TYPE_ACTION_URL - : Piwik_Tracker_Action::TYPE_ACTION_NAME; + $actionType = $this->guessActionTypeFromSegment($segmentName); if ($actionType == Piwik_Tracker_Action::TYPE_ACTION_URL) { // for urls trim protocol and www because it is not recorded in the db - $string = preg_replace('@^http[s]?://(www\.)?@i', '', $string); + $valueToMatch = preg_replace('@^http[s]?://(www\.)?@i', '', $valueToMatch); } + $valueToMatch = Piwik_Common::sanitizeInputValue(Piwik_Common::unsanitizeInputValue($valueToMatch)); + // exact matches work by returning the id directly if ($matchType == Piwik_SegmentExpression::MATCH_EQUAL || $matchType == Piwik_SegmentExpression::MATCH_NOT_EQUAL ) { $sql = Piwik_Tracker_Action::getSqlSelectActionId(); - $bind = array($string, $string, $actionType); + $bind = array($valueToMatch, $valueToMatch, $actionType); $idAction = Piwik_FetchOne($sql, $bind); // if the action is not found, we hack -100 to ensure it tries to match against an integer // otherwise binding idaction_name to "false" returns some rows for some reasons (in case &segment=pageTitle==Větrnásssssss) @@ -150,23 +156,24 @@ public function getIdActionFromSegment($string, $sqlField, $matchType = '==') // build the expression based on the match type $sql = 'SELECT idaction FROM ' . Piwik_Common::prefixTable('log_action') . ' WHERE '; + $sqlMatchType = 'AND type = ' . $actionType; switch ($matchType) { case '=@': // use concat to make sure, no %s occurs because some plugins use %s in their sql - $sql .= '( name LIKE CONCAT(\'%\', ?, \'%\') AND type = ' . $actionType . ' )'; + $sql .= '( name LIKE CONCAT(\'%\', ?, \'%\') ' . $sqlMatchType . ' )'; break; case '!@': - $sql .= '( name NOT LIKE CONCAT(\'%\', ?, \'%\') AND type = ' . $actionType . ' )'; + $sql .= '( name NOT LIKE CONCAT(\'%\', ?, \'%\') ' . $sqlMatchType . ' )'; break; default: - throw new Exception("This match type is not available for action-segments."); + throw new Exception("This match type $matchType is not available for action-segments."); break; } return array( // mark that the returned value is an sql-expression instead of a literal value 'SQL' => $sql, - 'bind' => $string + 'bind' => $valueToMatch, ); } @@ -600,5 +607,26 @@ static protected function isCustomVariablesPluginsEnabled() { return Piwik_PluginsManager::getInstance()->isPluginActivated('CustomVariables'); } + + /** + * @param $segmentName + * @return int + * @throws Exception + */ + protected function guessActionTypeFromSegment($segmentName) + { + if (stripos($segmentName, 'pageurl') !== false) { + $actionType = Piwik_Tracker_Action::TYPE_ACTION_URL; + return $actionType; + } elseif (stripos($segmentName, 'pagetitle') !== false) { + $actionType = Piwik_Tracker_Action::TYPE_ACTION_NAME; + return $actionType; + } elseif (stripos($segmentName, 'sitesearch') !== false) { + $actionType = Piwik_Tracker_Action::TYPE_SITE_SEARCH; + return $actionType; + } else { + throw new Exception(" The segment $segmentName has an unexpected value."); + } + } } diff --git a/plugins/CoreUpdater/CoreUpdater.php b/plugins/CoreUpdater/CoreUpdater.php index 30a28f76a62..865c3da4adb 100644 --- a/plugins/CoreUpdater/CoreUpdater.php +++ b/plugins/CoreUpdater/CoreUpdater.php @@ -69,7 +69,8 @@ function dispatch() && $action == 'saveLanguage') ) { if (Piwik_FrontController::shouldRethrowException()) { - throw new Exception("Piwik and/or some plugins have been upgraded to a new version. Please run the update process first. See documentation: http://piwik.org/docs/update/"); + throw new Exception("Piwik and/or some plugins have been upgraded to a new version. \n". + "--> Please run the update process first. See documentation: http://piwik.org/docs/update/ \n"); } else { Piwik::redirectToModule('CoreUpdater'); } diff --git a/plugins/Live/API.php b/plugins/Live/API.php index 919afb0682a..5cb3043839f 100644 --- a/plugins/Live/API.php +++ b/plugins/Live/API.php @@ -97,13 +97,15 @@ public function getCounters($idSite, $lastMinutes, $segment = false) * @param int $visitorId * @param int $idSite * @param int $filter_limit + * @param bool $flat Whether to flatten the visitor details array + * * @return Piwik_DataTable */ - public function getLastVisitsForVisitor($visitorId, $idSite, $filter_limit = 10) + public function getLastVisitsForVisitor($visitorId, $idSite, $filter_limit = 10, $flat = false) { Piwik::checkUserHasViewAccess($idSite); $visitorDetails = $this->loadLastVisitorDetailsFromDatabase($idSite, $period = false, $date = false, $segment = false, $filter_limit, $filter_offset = false, $visitorId); - $table = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite); + $table = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite, $flat); return $table; } @@ -121,25 +123,23 @@ public function getLastVisitsForVisitor($visitorId, $idSite, $filter_limit = 10) * * @return Piwik_DataTable */ - public function getLastVisitsDetails($idSite, $period, $date, $segment = false, $filter_limit = false, $filter_offset = false, $minTimestamp = false) + public function getLastVisitsDetails($idSite, $period, $date, $segment = false, $filter_limit = false, $filter_offset = false, $minTimestamp = false, $flat = false) { if (empty($filter_limit)) { $filter_limit = 10; } Piwik::checkUserHasViewAccess($idSite); $visitorDetails = $this->loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment, $filter_limit, $filter_offset, $visitorId = false, $minTimestamp); - $dataTable = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite); - + $dataTable = $this->getCleanedVisitorsFromDetails($visitorDetails, $idSite, $flat); return $dataTable; } /** * @deprecated */ - public function getLastVisits($idSite, $filter_limit = 10, $minTimestamp = false) { - return $this->getLastVisitsDetails($idSite, $period = false, $date = false, $segment = false, $filter_limit, $filter_offset = false, $minTimestamp); + return $this->getLastVisitsDetails($idSite, $period = false, $date = false, $segment = false, $filter_limit, $filter_offset = false, $minTimestamp, $flat = false); } /** @@ -147,9 +147,10 @@ public function getLastVisits($idSite, $filter_limit = 10, $minTimestamp = false * as well as make the data human readable * @param array $visitorDetails * @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 * @return Piwik_DataTable */ - private function getCleanedVisitorsFromDetails($visitorDetails, $idSite) + private function getCleanedVisitorsFromDetails($visitorDetails, $idSite, $flat = false) { $actionsLimit = (int)Piwik_Config::getInstance()->General['visitor_log_maximum_actions_per_visit']; @@ -200,6 +201,7 @@ private function getCleanedVisitorsFromDetails($visitorDetails, $idSite) LEFT JOIN " . Piwik_Common::prefixTable('log_action') . " AS log_action_title ON log_link_visit_action.idaction_name = log_action_title.idaction WHERE log_link_visit_action.idvisit = ? + ORDER BY server_time ASC LIMIT 0, $actionsLimit "; $actionDetails = Piwik_FetchAll($sql, array($idvisit)); @@ -212,8 +214,8 @@ private function getCleanedVisitorsFromDetails($visitorDetails, $idSite) $cvarKey = $actionDetail['custom_var_k' . $i]; $cvarKey = $this->getCustomVariablePrettyKey($cvarKey); $customVariablesPage[$i] = array( - 'customVariableName' . $i => $cvarKey, - 'customVariableValue' . $i => $actionDetail['custom_var_v' . $i], + 'customVariablePageName' . $i => $cvarKey, + 'customVariablePageValue' . $i => $actionDetail['custom_var_v' . $i], ); } unset($actionDetail['custom_var_k' . $i]); @@ -222,21 +224,30 @@ private function getCleanedVisitorsFromDetails($visitorDetails, $idSite) if (!empty($customVariablesPage)) { $actionDetail['customVariables'] = $customVariablesPage; } - // reconstruct url from prefix + + // Reconstruct url from prefix $actionDetail['url'] = Piwik_Tracker_Action::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) + + // 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::getPrettyTimeFromSeconds($actionDetail['timeSpent']); } unset($actionDetails[$actionIdx]['timeSpentRef']); // not needed after timeSpent is added - // handle generation time + + // Handle generation time if ($actionDetail['custom_float'] > 0) { $actionDetail['generationTime'] = Piwik::getPrettyTimeFromSeconds($actionDetail['custom_float'] / 1000); } unset($actionDetail['custom_float']); + + // Handle Site Search + if($actionDetail['type'] == Piwik_Tracker_Action::TYPE_SITE_SEARCH) { + $actionDetail['siteSearchKeyword'] = $actionDetail['pageTitle']; + unset($actionDetail['pageTitle']); + } } // If the visitor converted a goal, we shall select all Goals @@ -244,6 +255,7 @@ private function getCleanedVisitorsFromDetails($visitorDetails, $idSite) 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, @@ -256,6 +268,7 @@ private function getCleanedVisitorsFromDetails($visitorDetails, $idSite) AND goal.deleted = 0 WHERE log_conversion.idvisit = ? AND log_conversion.idgoal > 0 + ORDER BY server_time ASC LIMIT 0, $actionsLimit "; $goalDetails = Piwik_FetchAll($sql, array($idvisit)); @@ -274,6 +287,7 @@ private function getCleanedVisitorsFromDetails($visitorDetails, $idSite) FROM " . Piwik_Common::prefixTable('log_conversion') . " AS log_conversion WHERE idvisit = ? AND idgoal <= " . Piwik_Tracker_GoalManager::IDGOAL_ORDER . " + ORDER BY server_time ASC LIMIT 0, $actionsLimit"; $ecommerceDetails = Piwik_FetchAll($sql, array($idvisit)); @@ -365,9 +379,13 @@ private function getCleanedVisitorsFromDetails($visitorDetails, $idSite) // Convert datetimes to the site timezone $dateTimeVisit = Piwik_Date::factory($details['serverTimePretty'], $timezone); $details['serverTimePretty'] = $dateTimeVisit->getLocalized(Piwik_Translate('CoreHome_ShortDateFormat') . ' %time%'); + } $visitorDetailsArray['goalConversions'] = count($goalDetails); + if($flat) { + $visitorDetailsArray = $this->flattenVisitorDetailsArray($visitorDetailsArray); + } $table->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => $visitorDetailsArray)); } return $table; @@ -385,6 +403,92 @@ private function getCustomVariablePrettyKey($key) return $key; } + /** + * The &flat=1 feature is used by API.getSuggestedValuesForSegment + * + * @param $visitorDetailsArray + * @return array + */ + private function flattenVisitorDetailsArray($visitorDetailsArray) + { + // 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 . Piwik_DataTable_Filter_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' . Piwik_DataTable_Filter_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' . Piwik_DataTable_Filter_ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP . $count; + $visitorDetailsArray[$flattenedKeyName] = $action['url']; + } + + if(!empty($action['pageTitle'])) { + $flattenedKeyName = 'pageTitle' . Piwik_DataTable_Filter_ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP . $count; + $visitorDetailsArray[$flattenedKeyName] = $action['pageTitle']; + } + + if(!empty($action['siteSearchKeyword'])) { + $flattenedKeyName = 'siteSearchKeyword' . Piwik_DataTable_Filter_ColumnDelete::APPEND_TO_COLUMN_NAME_TO_KEEP . $count; + $visitorDetailsArray[$flattenedKeyName] = $action['siteSearchKeyword']; + } + $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; + } + private function sortByServerTime($a, $b) { $ta = strtotime($a['serverTimePretty']); diff --git a/plugins/Live/Visitor.php b/plugins/Live/Visitor.php index aaa4e8a61da..45a3ec7f080 100644 --- a/plugins/Live/Visitor.php +++ b/plugins/Live/Visitor.php @@ -60,6 +60,8 @@ function getAllVisitorDetails() // all time entries 'serverDate' => $this->getServerDate(), 'visitLocalTime' => $this->getVisitLocalTime(), + 'visitLocalHour' => $this->getVisitLocalHour(), + 'visitServerHour' => $this->getVisitServerHour(), 'firstActionTimestamp' => $this->getTimestampFirstAction(), 'lastActionTimestamp' => $this->getTimestampLastAction(), 'lastActionDateTime' => $this->getDateTimeLastAction(), @@ -77,6 +79,7 @@ function getAllVisitorDetails() 'countryCode' => $this->getCountryCode(), 'countryFlag' => $this->getCountryFlag(), 'region' => $this->getRegionName(), + 'regionCode' => $this->getRegionCode(), 'city' => $this->getCityName(), 'location' => $this->getPrettyLocation(), 'latitude' => $this->getLatitude(), @@ -93,12 +96,15 @@ function getAllVisitorDetails() '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(), 'resolution' => $this->getResolution(), 'screenTypeIcon' => $this->getScreenTypeIcon(), @@ -120,6 +126,16 @@ 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']; @@ -249,7 +265,7 @@ function getCityName() public function getRegionName() { - $region = $this->details['location_region']; + $region = $this->getRegionCode(); if ($region != '' && $region != Piwik_Tracker_Visit::UNKNOWN_CODE) { return Piwik_UserCountry_LocationProvider_GeoIp::getRegionNameFromCodes( $this->details['location_country'], $region); @@ -257,6 +273,11 @@ public function getRegionName() return null; } + public function getRegionCode() + { + return $this->details['location_region']; + } + function getPrettyLocation() { $parts = array(); @@ -430,6 +451,11 @@ function getPluginIcons() return null; } + function getOperatingSystemCode() + { + return $this->details['config_os']; + } + function getOperatingSystem() { return Piwik_getOSLabel($this->details['config_os']); @@ -455,6 +481,16 @@ function getBrowserFamily() return Piwik_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_getBrowserLabel($this->details['config_browser_name'] . ";" . $this->details['config_browser_version']); diff --git a/plugins/Transitions/API.php b/plugins/Transitions/API.php index 80afa16d945..4457d71046e 100644 --- a/plugins/Transitions/API.php +++ b/plugins/Transitions/API.php @@ -134,25 +134,25 @@ private function deriveIdAction($actionName, $actionType) case 'url': $originalActionName = $actionName; $actionName = Piwik_Common::unsanitizeInputValue($actionName); - $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url'); + $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url', Piwik_SegmentExpression::MATCH_EQUAL, 'pageUrl'); if ($id < 0) { // an example where this is needed is urls containing < or > $actionName = $originalActionName; - $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url'); + $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_url', Piwik_SegmentExpression::MATCH_EQUALs, 'pageUrl'); } return $id; case 'title': - $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_name'); + $id = $actionsPlugin->getIdActionFromSegment($actionName, 'idaction_name', Piwik_SegmentExpression::MATCH_EQUAL, 'pageTitle'); if ($id < 0) { - $unkown = Piwik_Actions_ArchivingHelper::getUnknownActionName( + $unknown = Piwik_Actions_ArchivingHelper::getUnknownActionName( Piwik_Tracker_Action::TYPE_ACTION_NAME); - if (trim($actionName) == trim($unkown)) { - $id = $actionsPlugin->getIdActionFromSegment('', 'idaction_name'); + if (trim($actionName) == trim($unknown)) { + $id = $actionsPlugin->getIdActionFromSegment('', 'idaction_name', Piwik_SegmentExpression::MATCH_EQUAL, 'pageTitle'); } } diff --git a/plugins/UserCountry/UserCountry.php b/plugins/UserCountry/UserCountry.php index 20d2935248a..34003af178b 100644 --- a/plugins/UserCountry/UserCountry.php +++ b/plugins/UserCountry/UserCountry.php @@ -164,7 +164,7 @@ public function getSegmentsMetadata($notification) 'type' => 'dimension', 'category' => 'Visit Location', 'name' => Piwik_Translate('UserCountry_Country'), - 'segment' => 'country', + 'segment' => 'countryCode', 'sqlSegment' => 'log_visit.location_country', 'acceptedValues' => 'de, us, fr, in, es, etc.', ); @@ -172,7 +172,7 @@ public function getSegmentsMetadata($notification) 'type' => 'dimension', 'category' => 'Visit Location', 'name' => Piwik_Translate('UserCountry_Continent'), - 'segment' => 'continent', + 'segment' => 'continentCode', 'sqlSegment' => 'log_visit.location_country', 'acceptedValues' => 'eur, asi, amc, amn, ams, afr, ant, oce', 'sqlFilter' => array('Piwik_UserCountry', 'getCountriesForContinent'), @@ -181,7 +181,7 @@ public function getSegmentsMetadata($notification) 'type' => 'dimension', 'category' => 'Visit Location', 'name' => Piwik_Translate('UserCountry_Region'), - 'segment' => 'region', + 'segment' => 'regionCode', 'sqlSegment' => 'log_visit.location_region', 'acceptedValues' => '01 02, OR, P8, etc.
    eg. region=A1;country=fr', ); @@ -197,7 +197,7 @@ public function getSegmentsMetadata($notification) 'type' => 'dimension', 'category' => 'Visit Location', 'name' => Piwik_Translate('UserCountry_Latitude'), - 'segment' => 'lat', + 'segment' => 'latitude', 'sqlSegment' => 'log_visit.location_latitude', 'acceptedValues' => '-33.578, 40.830, etc.
    You can select visitors within a lat/long range using &segment=lat>X;lat<Y;long>M;long<N.', ); @@ -205,7 +205,7 @@ public function getSegmentsMetadata($notification) 'type' => 'dimension', 'category' => 'Visit Location', 'name' => Piwik_Translate('UserCountry_Longitude'), - 'segment' => 'long', + 'segment' => 'longitude', 'sqlSegment' => 'log_visit.location_longitude', 'acceptedValues' => '-70.664, 14.326, etc.', ); @@ -489,9 +489,8 @@ private function setLongCityRegionId(&$row) */ public static function getCountriesForContinent($continent) { - $continent = strtolower($continent); - $result = array(); + $continent = strtolower($continent); foreach (Piwik_Common::getCountriesList() as $countryCode => $continentCode) { if ($continent == $continentCode) { $result[] = $countryCode; diff --git a/plugins/UserSettings/UserSettings.php b/plugins/UserSettings/UserSettings.php index d46b952c567..90be882806a 100644 --- a/plugins/UserSettings/UserSettings.php +++ b/plugins/UserSettings/UserSettings.php @@ -59,7 +59,7 @@ public function getInformation() 'UserSettings', 'getBrowser', 'UserSettings_ColumnBrowser', - 'browserName', + 'browserCode', 'log_visit.config_browser_name', 'FF, IE, CH, SF, OP, etc.', null,), @@ -110,7 +110,7 @@ public function getInformation() 'UserSettings', 'getOS', 'UserSettings_ColumnOperatingSystem', - 'operatingSystem', + 'operatingSystemCode', 'log_visit.config_os', 'WXP, WI7, MAC, LIN, AND, IPD, etc.', null,), diff --git a/tests/PHPUnit/Core/PluginsFunctions/WidgetsListTest.php b/tests/PHPUnit/Core/PluginsFunctions/WidgetsListTest.php index bd7983cdf6e..7c2c0e598c9 100644 --- a/tests/PHPUnit/Core/PluginsFunctions/WidgetsListTest.php +++ b/tests/PHPUnit/Core/PluginsFunctions/WidgetsListTest.php @@ -24,9 +24,7 @@ public function testGet() $_GET['idSite'] = 1; - $pluginsManager = Piwik_PluginsManager::getInstance(); - $pluginsToLoad = Piwik_Config::getInstance()->Plugins['Plugins']; - $pluginsManager->loadPlugins($pluginsToLoad); + IntegrationTestCase::loadAllPlugins(); Piwik_WidgetsList::_reset(); $widgets = Piwik_GetWidgetsList(); @@ -52,6 +50,7 @@ public function testGet() foreach ($numberOfWidgets AS $category => $widgetCount) { $this->assertEquals($widgetCount, count($widgets[$category]), sprintf("Widget: %s", $category)); } + IntegrationTestCase::unloadAllPlugins(); } /** @@ -132,4 +131,5 @@ public function testGetWithGoalsAndEcommerce() } } + } diff --git a/tests/PHPUnit/Core/SegmentTest.php b/tests/PHPUnit/Core/SegmentTest.php index 2d021ea65cc..35da2a58a32 100644 --- a/tests/PHPUnit/Core/SegmentTest.php +++ b/tests/PHPUnit/Core/SegmentTest.php @@ -44,17 +44,17 @@ public function getCommonTestData() { return array( // Normal segment - array('country==France', array( + array('countryCode==France', array( 'where' => ' log_visit.location_country = ? ', 'bind' => array('France'))), // unescape the comma please - array('country==a\,==', array( + array('countryCode==a\,==', array( 'where' => ' log_visit.location_country = ? ', 'bind' => array('a,=='))), // AND, with 2 values rewrites - array('country==a;visitorType!=returning;visitorType==new', array( + array('countryCode==a;visitorType!=returning;visitorType==new', array( 'where' => ' log_visit.location_country = ? AND log_visit.visitor_returning <> ? AND log_visit.visitor_returning = ? ', 'bind' => array('a', '1', '0'))), diff --git a/tests/PHPUnit/DatabaseTestCase.php b/tests/PHPUnit/DatabaseTestCase.php index 2b0627f251b..9f91c7910c5 100644 --- a/tests/PHPUnit/DatabaseTestCase.php +++ b/tests/PHPUnit/DatabaseTestCase.php @@ -15,6 +15,7 @@ */ class DatabaseTestCase extends PHPUnit_Framework_TestCase { + /** * Setup the database and create the base tables for all tests */ @@ -58,14 +59,7 @@ public function setUp() public function tearDown() { parent::tearDown(); - try { - $plugins = Piwik_PluginsManager::getInstance()->getLoadedPlugins(); - foreach ($plugins AS $plugin) { - $plugin->uninstall(); - } - Piwik_PluginsManager::getInstance()->unloadPlugins(); - } catch (Exception $e) { - } + IntegrationTestCase::unloadAllPlugins(); Piwik::dropDatabase(); Piwik_DataTable_Manager::getInstance()->deleteAll(); Piwik_Option::getInstance()->clearCache(); @@ -76,4 +70,5 @@ public function tearDown() Piwik_TablePartitioning::$tablesAlreadyInstalled = null; Zend_Registry::_unsetInstance(); } + } diff --git a/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php b/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php index 33f2f7006cc..88cd783ae12 100644 --- a/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php +++ b/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php @@ -30,6 +30,9 @@ class Test_Piwik_Fixture_ManyVisitsWithGeoIP extends Test_Piwik_BaseFixture '103.29.196.229', // in Indonesia (Bali), (only Indonesia will show up) ); + protected $idGoal; + protected $idGoal2; + public function setUp() { $this->setUpWebsitesAndGoals(); @@ -58,7 +61,8 @@ public function tearDown() private function setUpWebsitesAndGoals() { self::createWebsite($this->dateTime, 0, "Site 1"); - Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'all', 'url', 'http', 'contains', false, 5); + $this->idGoal = Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'all', 'url', 'http', 'contains', false, 5); + $this->idGoal2 = Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'two', 'url', 'xxxxxxxxxxxxx', 'contains', false, 5); } private function trackVisits($visitorCount, $setIp = false, $useLocal = true, $doBulk = false) @@ -73,7 +77,7 @@ private function trackVisits($visitorCount, $setIp = false, $useLocal = true, $d $t->setTokenAuth(self::getTokenAuth()); } for ($i = 0; $i != $visitorCount; ++$i) { - $t->setNewVisitorId(); + $t->setVisitorId( substr(md5($i + 1000), 0, $t::LENGTH_VISITOR_ID)); if ($setIp) { $t->setIp(current($this->ips)); next($this->ips); @@ -85,7 +89,12 @@ private function trackVisits($visitorCount, $setIp = false, $useLocal = true, $d $date = Piwik_Date::factory($dateTime)->addDay($i); $t->setForceVisitDateTime($date->getDatetime()); $t->setUrl("http://piwik.net/grue/lair"); - $r = $t->doTrackPageView('It\'s pitch black...'); + $t->setCustomVariable(1, 'Cvar 1 name', 'Cvar1 value is ' .$i , 'visit'); + $t->setCustomVariable(5, 'Cvar 5 name', 'Cvar5 value is ' .$i , 'visit'); + $t->setCustomVariable(2, 'Cvar 2 PAGE name', 'Cvar2 PAGE value is ' .$i, 'page'); + $t->setCustomVariable(5, 'Cvar 5 PAGE name', 'Cvar5 PAGE value is ' .$i, 'page'); + + $r = $t->doTrackPageView('It\'s