diff options
31 files changed, 545 insertions, 147 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d8edb1632..33d9a60b7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API' ### New APIs * Add your own SMS/Text provider by creating a new class in the `SMSProvider` directory of your plugin. The class has to extend `Piwik\Plugins\MobileMessaging\SMSProvider` and implement the required methods. - + * Segments can now be composed by a union of multiple segments. To do this set an array of segments that shall be used for that segment `$segment->setUnionOfSegments(array('outlinkUrl', 'downloadUrl'))` instead of defining a SQL column. + ## Piwik 2.15.0 ### New commands diff --git a/core/Plugin/Segment.php b/core/Plugin/Segment.php index 8a9688fb0b..2a20208ea2 100644 --- a/core/Plugin/Segment.php +++ b/core/Plugin/Segment.php @@ -7,6 +7,7 @@ * */ namespace Piwik\Plugin; +use Exception; /** * Creates a new segment that can be used for instance within the {@link \Piwik\Columns\Dimension::configureSegment()} @@ -51,6 +52,7 @@ class Segment private $acceptValues; private $permission; private $suggestedValuesCallback; + private $unionOfSegments; /** * If true, this segment will only be visible to the user if the user has view access @@ -122,6 +124,7 @@ class Segment public function setSegment($segment) { $this->segment = $segment; + $this->check(); } /** @@ -165,6 +168,29 @@ class Segment public function setSqlSegment($sqlSegment) { $this->sqlSegment = $sqlSegment; + $this->check(); + } + + /** + * Set a list of segments that should be used instead of fetching the values from a single column. + * All set segments will be applied via an OR operator. + * + * @param array $segments + * @api + */ + public function setUnionOfSegments($segments) + { + $this->unionOfSegments = $segments; + $this->check(); + } + + /** + * @return array + * @ignore + */ + public function getUnionOfSegments() + { + return $this->unionOfSegments; } /** @@ -196,6 +222,15 @@ class Segment } /** + * @return string + * @ignore + */ + public function getName() + { + return $this->name; + } + + /** * Returns the name of this segment as it should appear in segment expressions. * * @return string @@ -241,6 +276,10 @@ class Segment 'sqlSegment' => $this->sqlSegment, ); + if (!empty($this->unionOfSegments)) { + $segment['unionOfSegments'] = $this->unionOfSegments; + } + if (!empty($this->sqlFilter)) { $segment['sqlFilter'] = $this->sqlFilter; } @@ -288,4 +327,15 @@ class Segment { $this->requiresAtLeastViewAccess = $requiresAtLeastViewAccess; } + + private function check() + { + if ($this->sqlSegment && $this->unionOfSegments) { + throw new Exception(sprintf('Union of segments and SQL segment is set for segment "%s", use only one of them', $this->name)); + } + + if ($this->segment && $this->unionOfSegments && in_array($this->segment, $this->unionOfSegments, true)) { + throw new Exception(sprintf('The segment %s contains a union segment to itself', $this->name)); + } + } } diff --git a/core/Segment.php b/core/Segment.php index 451afdb393..d9f8d163c6 100644 --- a/core/Segment.php +++ b/core/Segment.php @@ -110,6 +110,35 @@ class Segment } } + private function getAvailableSegments() + { + // segment metadata + if (empty($this->availableSegments)) { + $this->availableSegments = API::getInstance()->getSegmentsMetadata($this->idSites, $_hideImplementationData = false); + } + + return $this->availableSegments; + } + + private function getSegmentByName($name) + { + $segments = $this->getAvailableSegments(); + + foreach ($segments as $segment) { + if ($segment['segment'] == $name && !empty($name)) { + + // check permission + if (isset($segment['permission']) && $segment['permission'] != 1) { + throw new NoAccessException("You do not have enough permission to access the segment " . $name); + } + + return $segment; + } + } + + throw new Exception("Segment '$name' is not a supported segment."); + } + /** * @param $string * @param $idSites @@ -127,6 +156,7 @@ class Segment // parse segments $expressions = $segment->parseSubExpressions(); + $expressions = $this->getExpressionsWithUnionsResolved($expressions); // convert segments name to sql segment // check that user is allowed to view this segment @@ -142,6 +172,41 @@ class Segment $segment->setSubExpressionsAfterCleanup($cleanedExpressions); } + private function getExpressionsWithUnionsResolved($expressions) + { + $expressionsWithUnions = array(); + foreach ($expressions as $expression) { + $operand = $expression[SegmentExpression::INDEX_OPERAND]; + $name = $operand[SegmentExpression::INDEX_OPERAND_NAME]; + + $availableSegment = $this->getSegmentByName($name); + + if (!empty($availableSegment['unionOfSegments'])) { + $count = 0; + foreach ($availableSegment['unionOfSegments'] as $segmentNameOfUnion) { + $count++; + $operator = SegmentExpression::BOOL_OPERATOR_OR; // we connect all segments within that union via OR + if ($count === count($availableSegment['unionOfSegments'])) { + $operator = $expression[SegmentExpression::INDEX_BOOL_OPERATOR]; + } + + $operand[SegmentExpression::INDEX_OPERAND_NAME] = $segmentNameOfUnion; + $expressionsWithUnions[] = array( + SegmentExpression::INDEX_BOOL_OPERATOR => $operator, + SegmentExpression::INDEX_OPERAND => $operand + ); + } + } else { + $expressionsWithUnions[] = array( + SegmentExpression::INDEX_BOOL_OPERATOR => $expression[SegmentExpression::INDEX_BOOL_OPERATOR], + SegmentExpression::INDEX_OPERAND => $operand + ); + } + } + + return $expressionsWithUnions; + } + /** * Returns `true` if the segment is empty, `false` if otherwise. */ @@ -154,33 +219,15 @@ class Segment protected function getCleanedExpression($expression) { - if (empty($this->availableSegments)) { - $this->availableSegments = API::getInstance()->getSegmentsMetadata($this->idSites, $_hideImplementationData = false); - } + $name = $expression[SegmentExpression::INDEX_OPERAND_NAME]; + $matchType = $expression[SegmentExpression::INDEX_OPERAND_OPERATOR]; + $value = $expression[SegmentExpression::INDEX_OPERAND_VALUE]; - $name = $expression[0]; - $matchType = $expression[1]; - $value = $expression[2]; - $sqlName = ''; + $segment = $this->getSegmentByName($name); + $sqlName = $segment['sqlSegment']; - foreach ($this->availableSegments as $segment) { - if ($segment['segment'] != $name) { - continue; - } - - $sqlName = $segment['sqlSegment']; - - // check permission - if (isset($segment['permission']) - && $segment['permission'] != 1 - ) { - throw new NoAccessException("You do not have enough permission to access the segment " . $name); - } - - if ($matchType == SegmentExpression::MATCH_IS_NOT_NULL_NOR_EMPTY - || $matchType == SegmentExpression::MATCH_IS_NULL_OR_EMPTY) { - break; - } + if ($matchType != SegmentExpression::MATCH_IS_NOT_NULL_NOR_EMPTY + && $matchType != SegmentExpression::MATCH_IS_NULL_OR_EMPTY) { if (isset($segment['sqlFilterValue'])) { $value = call_user_func($segment['sqlFilterValue'], $value); @@ -201,12 +248,6 @@ class Segment $matchType = SegmentExpression::MATCH_ACTIONS_CONTAINS; } } - - break; - } - - if (empty($sqlName)) { - throw new Exception("Segment '$name' is not a supported segment."); } return array($sqlName, $matchType, $value); diff --git a/core/Segment/SegmentExpression.php b/core/Segment/SegmentExpression.php index 7eac1bcdb0..5f1a7fea27 100644 --- a/core/Segment/SegmentExpression.php +++ b/core/Segment/SegmentExpression.php @@ -27,6 +27,12 @@ class SegmentExpression const MATCH_LESS = '<'; const MATCH_CONTAINS = '=@'; const MATCH_DOES_NOT_CONTAIN = '!@'; + const MATCH_STARTS_WITH = '=^'; + const MATCH_ENDS_WITH = '=$'; + + const BOOL_OPERATOR_OR = 'OR'; + const BOOL_OPERATOR_AND = 'AND'; + const BOOL_OPERATOR_END = ''; // Note: you can't write this in the API, but access this feature // via field!= <- IS NOT NULL @@ -40,6 +46,10 @@ class SegmentExpression const INDEX_BOOL_OPERATOR = 0; const INDEX_OPERAND = 1; + const INDEX_OPERAND_NAME = 0; + const INDEX_OPERAND_OPERATOR = 1; + const INDEX_OPERAND_VALUE = 2; + const SQL_WHERE_DO_NOT_MATCH_ANY_ROW = "(1 = 0)"; const SQL_WHERE_MATCHES_ALL_ROWS = "(1 = 1)"; @@ -89,7 +99,9 @@ class SegmentExpression . self::MATCH_LESS_OR_EQUAL . '|' . self::MATCH_LESS . '|' . self::MATCH_CONTAINS . '|' - . self::MATCH_DOES_NOT_CONTAIN + . self::MATCH_DOES_NOT_CONTAIN . '|' + . preg_quote(self::MATCH_STARTS_WITH) . '|' + . preg_quote(self::MATCH_ENDS_WITH) . '){1}(.*)/'; $match = preg_match($pattern, $operand, $matches); if ($match == 0) { @@ -115,9 +127,9 @@ class SegmentExpression $parsedSubExpressions[] = array( self::INDEX_BOOL_OPERATOR => $operator, self::INDEX_OPERAND => array( - $leftMember, - $operation, - $valueRightMember, + self::INDEX_OPERAND_NAME => $leftMember, + self::INDEX_OPERAND_OPERATOR => $operation, + self::INDEX_OPERAND_VALUE => $valueRightMember, )); } $this->parsedSubExpressions = $parsedSubExpressions; @@ -146,14 +158,17 @@ class SegmentExpression $operator = $leaf[self::INDEX_BOOL_OPERATOR]; $operandDefinition = $leaf[self::INDEX_OPERAND]; - $operand = $this->getSqlMatchFromDefinition($operandDefinition, $availableTables); - if ($operand[1] !== null) { - $this->valuesBind = array_merge($this->valuesBind, $operand[1]); + if ($operand[self::INDEX_OPERAND_OPERATOR] !== null) { + if (is_array($operand[self::INDEX_OPERAND_OPERATOR])) { + $this->valuesBind = array_merge($this->valuesBind, $operand[self::INDEX_OPERAND_OPERATOR]); + } else { + $this->valuesBind[] = $operand[self::INDEX_OPERAND_OPERATOR]; + } } - $operand = $operand[0]; + $operand = $operand[self::INDEX_OPERAND_NAME]; $sqlSubExpressions[] = array( self::INDEX_BOOL_OPERATOR => $operator, @@ -177,12 +192,12 @@ class SegmentExpression */ protected function getSqlMatchFromDefinition($def, &$availableTables) { - $fields = $def[0]; + $field = $def[0]; $matchType = $def[1]; $value = $def[2]; // Segment::getCleanedExpression() may return array(null, $matchType, null) - $operandWillNotMatchAnyRow = empty($fields) && is_null($value); + $operandWillNotMatchAnyRow = empty($field) && is_null($value); if($operandWillNotMatchAnyRow) { if($matchType == self::MATCH_EQUAL) { // eg. pageUrl==DoesNotExist @@ -193,7 +208,9 @@ class SegmentExpression // Not equal to NULL means it matches all rows $sqlExpression = self::SQL_WHERE_MATCHES_ALL_ROWS; } elseif($matchType == self::MATCH_CONTAINS - || $matchType == self::MATCH_DOES_NOT_CONTAIN) { + || $matchType == self::MATCH_DOES_NOT_CONTAIN + || $matchType == self::MATCH_STARTS_WITH + || $matchType == self::MATCH_ENDS_WITH) { // no action was found for CONTAINS / DOES NOT CONTAIN // eg. pageUrl=@DoesNotExist -> matches no row // eg. pageUrl!@DoesNotExist -> matches no rows @@ -207,10 +224,6 @@ class SegmentExpression return array($sqlExpression, $value = null); } - if (!is_array($fields)) { - $fields = array($fields); - } - $alsoMatchNULLValues = false; switch ($matchType) { case self::MATCH_EQUAL: @@ -241,6 +254,14 @@ class SegmentExpression $value = '%' . $this->escapeLikeString($value) . '%'; $alsoMatchNULLValues = true; break; + case self::MATCH_STARTS_WITH: + $sqlMatch = '%s LIKE'; + $value = $this->escapeLikeString($value) . '%'; + break; + case self::MATCH_ENDS_WITH: + $sqlMatch = '%s LIKE'; + $value = '%' . $this->escapeLikeString($value); + break; case self::MATCH_IS_NOT_NULL_NOR_EMPTY: $sqlMatch = '%s IS NOT NULL AND (%s <> \'\' OR %s = 0)'; @@ -267,44 +288,23 @@ class SegmentExpression // We match NULL values when rows are excluded only when we are not doing a $alsoMatchNULLValues = $alsoMatchNULLValues && !empty($value); + $sqlMatch = str_replace('%s', $field, $sqlMatch); - $sqlExpressions = array(); - $values = array(); - foreach ($fields as $field) { - $sqlMatchReplaced = str_replace('%s', $field, $sqlMatch); - - if ($matchType === self::MATCH_ACTIONS_CONTAINS - || is_null($value) - ) { - $sqlExpression = "( $sqlMatchReplaced )"; + if ($matchType === self::MATCH_ACTIONS_CONTAINS + || is_null($value) + ) { + $sqlExpression = "( $sqlMatch )"; + } else { + if ($alsoMatchNULLValues) { + $sqlExpression = "( $field IS NULL OR $sqlMatch ? )"; } else { - if ($alsoMatchNULLValues) { - $sqlExpression = "( $field IS NULL OR $sqlMatchReplaced ? )"; - } else { - $sqlExpression = "$sqlMatchReplaced ?"; - } - } - - $sqlExpressions[] = $sqlExpression; - - if ($value !== null) { - if(is_array($value)) { - $values = array_merge($values, $value); - } else { - $values[] = $value; - } + $sqlExpression = "$sqlMatch ?"; } - - $this->checkFieldIsAvailable($field, $availableTables); } - if (count($fields) == 1) { - $sqlExpression = reset($sqlExpressions); - } else { - $sqlExpression = '((' . implode(") OR (", $sqlExpressions) . '))'; - } + $this->checkFieldIsAvailable($field, $availableTables); - return array($sqlExpression, $values); + return array($sqlExpression, $value); } /** @@ -372,15 +372,15 @@ class SegmentExpression $operand = substr($operand, 0, -1); } $operand .= $char; - $tree[] = array(self::INDEX_BOOL_OPERATOR => '', self::INDEX_OPERAND => $operand); + $tree[] = array(self::INDEX_BOOL_OPERATOR => self::BOOL_OPERATOR_END, self::INDEX_OPERAND => $operand); break; } if ($isAND && !$isBackslash) { - $tree[] = array(self::INDEX_BOOL_OPERATOR => 'AND', self::INDEX_OPERAND => $operand); + $tree[] = array(self::INDEX_BOOL_OPERATOR => self::BOOL_OPERATOR_AND, self::INDEX_OPERAND => $operand); $operand = ''; } elseif ($isOR && !$isBackslash) { - $tree[] = array(self::INDEX_BOOL_OPERATOR => 'OR', self::INDEX_OPERAND => $operand); + $tree[] = array(self::INDEX_BOOL_OPERATOR => self::BOOL_OPERATOR_OR, self::INDEX_OPERAND => $operand); $operand = ''; } else { if ($isBackslash && ($isAND || $isOR)) { @@ -413,7 +413,7 @@ class SegmentExpression $operator = $expression[self::INDEX_BOOL_OPERATOR]; $operand = $expression[self::INDEX_OPERAND]; - if ($operator == 'OR' + if ($operator == self::BOOL_OPERATOR_OR && !$subExpression ) { $sql .= ' ('; @@ -424,7 +424,7 @@ class SegmentExpression $sql .= $operand; - if ($operator == 'AND' + if ($operator == self::BOOL_OPERATOR_AND && $subExpression ) { $sql .= ')'; diff --git a/core/Tracker/TableLogAction.php b/core/Tracker/TableLogAction.php index db9f7859fd..50696a70f3 100644 --- a/core/Tracker/TableLogAction.php +++ b/core/Tracker/TableLogAction.php @@ -65,13 +65,21 @@ class TableLogAction $sql = 'SELECT idaction FROM ' . Common::prefixTable('log_action') . ' WHERE %s AND type = ' . $actionType . ' )'; switch ($matchType) { - case '=@': + case SegmentExpression::MATCH_CONTAINS: // use concat to make sure, no %s occurs because some plugins use %s in their sql $where = '( name LIKE CONCAT(\'%\', ?, \'%\') '; break; - case '!@': + case SegmentExpression::MATCH_DOES_NOT_CONTAIN: $where = '( name NOT LIKE CONCAT(\'%\', ?, \'%\') '; break; + case SegmentExpression::MATCH_STARTS_WITH: + // use concat to make sure, no %s occurs because some plugins use %s in their sql + $where = '( name LIKE CONCAT(?, \'%\') '; + break; + case SegmentExpression::MATCH_ENDS_WITH: + // use concat to make sure, no %s occurs because some plugins use %s in their sql + $where = '( name LIKE CONCAT(\'%\', ?) '; + break; default: throw new \Exception("This match type $matchType is not available for action-segments."); break; diff --git a/lang/en.json b/lang/en.json index 615ad706c0..c5a32ed0d8 100644 --- a/lang/en.json +++ b/lang/en.json @@ -264,6 +264,8 @@ "OperationIsNot": "Is not", "OperationLessThan": "Less than", "OperationNotEquals": "Not Equals", + "OperationStartsWith": "Starts with", + "OperationEndsWith": "Ends with", "OptionalSmtpPort": "Optional. Defaults to 25 for unencrypted and TLS SMTP, and 465 for SSL SMTP.", "Options": "Options", "OrCancel": "or %s Cancel %s", diff --git a/plugins/API/API.php b/plugins/API/API.php index 4ea0cb4055..e5d9fbcd2a 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -493,30 +493,20 @@ class API extends \Piwik\Plugin\API if (empty(Config::getInstance()->General['enable_segment_suggested_values'])) { return array(); } + 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."); - } + $segment = $this->findSegment($segmentName, $idSite); // if segment has suggested values callback then return result from it instead $suggestedValuesCallbackRequiresTable = false; - if (isset($segmentFound['suggestedValuesCallback'])) { + if (isset($segment['suggestedValuesCallback'])) { $suggestedValuesCallbackRequiresTable = $this->doesSuggestedValuesCallbackNeedData( - $segmentFound['suggestedValuesCallback']); + $segment['suggestedValuesCallback']); if (!$suggestedValuesCallbackRequiresTable) { - return call_user_func($segmentFound['suggestedValuesCallback'], $idSite, $maxSuggestionsToReturn); + return call_user_func($segment['suggestedValuesCallback'], $idSite, $maxSuggestionsToReturn); } } @@ -525,6 +515,57 @@ class API extends \Piwik\Plugin\API return array(); } + if (!empty($segment['unionOfSegments'])) { + $values = array(); + foreach ($segment['unionOfSegments'] as $unionSegmentName) { + $unionSegment = $this->findSegment($unionSegmentName, $idSite); + + try { + $result = $this->getSuggestedValuesForSegmentName($idSite, $unionSegment, $maxSuggestionsToReturn); + if (!empty($result)) { + $values = array_merge($result, $values); + } + } catch (\Exception $e) { + // we ignore if there was no data found for $unionSegmentName + } + } + + if (empty($values)) { + throw new \Exception("There was no data to suggest for $segmentName"); + } + + } else { + $values = $this->getSuggestedValuesForSegmentName($idSite, $segment, $maxSuggestionsToReturn); + } + + $values = $this->getMostFrequentValues($values); + $values = array_slice($values, 0, $maxSuggestionsToReturn); + $values = array_map(array('Piwik\Common', 'unsanitizeInputValue'), $values); + + return $values; + } + + private function findSegment($segmentName, $idSite) + { + $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 $segmentName not found."); + } + + return $segmentFound; + } + + private function getSuggestedValuesForSegmentName($idSite, $segment, $maxSuggestionsToReturn) + { $startDate = Date::now()->subDay(60)->toString(); $requestLastVisits = "method=Live.getLastVisitsDetails &idSite=$idSite @@ -534,6 +575,8 @@ class API extends \Piwik\Plugin\API &serialize=0 &flat=1"; + $segmentName = $segment['segment']; + // Select non empty fields only // Note: this optimization has only a very minor impact $requestLastVisits .= "&segment=$segmentName" . urlencode('!='); @@ -548,22 +591,18 @@ class API extends \Piwik\Plugin\API $request = new Request($requestLastVisits); $table = $request->process(); + if (empty($table)) { throw new \Exception("There was no data to suggest for $segmentName"); } - if ($suggestedValuesCallbackRequiresTable) { - $values = call_user_func($segmentFound['suggestedValuesCallback'], $idSite, $maxSuggestionsToReturn, $table); + if (isset($segment['suggestedValuesCallback']) && + $this->doesSuggestedValuesCallbackNeedData($segment['suggestedValuesCallback'])) { + $values = call_user_func($segment['suggestedValuesCallback'], $idSite, $maxSuggestionsToReturn, $table); } else { $values = $this->getSegmentValuesFromVisitorLog($segmentName, $table); } - $values = $this->getMostFrequentValues($values); - - $values = array_slice($values, 0, $maxSuggestionsToReturn); - - $values = array_map(array('Piwik\Common', 'unsanitizeInputValue'), $values); - return $values; } diff --git a/plugins/Actions/Columns/ActionUrl.php b/plugins/Actions/Columns/ActionUrl.php new file mode 100644 index 0000000000..5fa80d1efa --- /dev/null +++ b/plugins/Actions/Columns/ActionUrl.php @@ -0,0 +1,32 @@ +<?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\Actions\Columns; + +use Piwik\Piwik; +use Piwik\Plugin\Dimension\ActionDimension; +use Piwik\Plugins\Actions\Segment; + +class ActionUrl extends ActionDimension +{ + public function getName() + { + return Piwik::translate('Actions_ColumnActionURL'); + } + + protected function configureSegments() + { + $segment = new Segment(); + $segment->setSegment('actionUrl'); + $segment->setName('Actions_ColumnActionURL'); + $segment->setUnionOfSegments(array('pageUrl', 'downloadUrl', 'outlinkUrl')); + + $this->addSegment($segment); + } + +} diff --git a/plugins/Actions/lang/en.json b/plugins/Actions/lang/en.json index 5a31efee34..b4853e18f5 100644 --- a/plugins/Actions/lang/en.json +++ b/plugins/Actions/lang/en.json @@ -2,6 +2,7 @@ "Actions": { "AvgGenerationTimeTooltip": "Average based on %s hit(s) %s between %s and %s", "ColumnClickedURL": "Clicked URL", + "ColumnActionURL": "Action URL", "ColumnClicks": "Clicks", "ColumnClicksDocumentation": "The number of times this link was clicked.", "ColumnDownloadURL": "Download URL", diff --git a/plugins/CustomVariables/Columns/Base.php b/plugins/CustomVariables/Columns/Base.php index 1818ca3145..4c6a4ea542 100644 --- a/plugins/CustomVariables/Columns/Base.php +++ b/plugins/CustomVariables/Columns/Base.php @@ -16,7 +16,7 @@ use Piwik\Plugins\CustomVariables\CustomVariables; class Base extends VisitDimension { - protected function configureSegmentsFor($fieldPrefix, $segmentNameSuffix) + protected function configureSegmentsFor($segmentNameSuffix) { $numCustomVariables = CustomVariables::getNumUsableCustomVariables(); @@ -25,10 +25,7 @@ class Base extends VisitDimension $segment->setSegment('customVariable' . $segmentNameSuffix); $segment->setName($this->getName() . ' (' . Piwik::translate('CustomVariables_ScopeVisit') . ')'); $segment->setCategory('CustomVariables_CustomVariables'); - $segment->setSqlSegment($this->getSegmentColumns('log_visit.' . $fieldPrefix, $numCustomVariables)); - $segment->setSuggestedValuesCallback(function ($idSite, $ignore, DataTable $table) use ($segmentNameSuffix) { - return $table->getColumnsStartingWith('customVariable' . $segmentNameSuffix); - }); + $segment->setUnionOfSegments($this->getSegmentColumns('customVariable' . $segmentNameSuffix, $numCustomVariables)); $this->addSegment($segment); $segment = new Segment(); @@ -36,10 +33,7 @@ class Base extends VisitDimension $segment->setSegment('customVariablePage' . $segmentNameSuffix); $segment->setName($this->getName() . ' (' . Piwik::translate('CustomVariables_ScopePage') . ')'); $segment->setCategory('CustomVariables_CustomVariables'); - $segment->setSqlSegment($this->getSegmentColumns('log_link_visit_action.' . $fieldPrefix, $numCustomVariables)); - $segment->setSuggestedValuesCallback(function ($idSite, $ignore, DataTable $table) use ($segmentNameSuffix) { - return $table->getColumnsStartingWith('customVariablePage' . $segmentNameSuffix); - }); + $segment->setUnionOfSegments($this->getSegmentColumns('customVariablePage' . $segmentNameSuffix, $numCustomVariables)); $this->addSegment($segment); } diff --git a/plugins/CustomVariables/Columns/CustomVariableName.php b/plugins/CustomVariables/Columns/CustomVariableName.php index 85aa46436f..b42f2e8ae7 100644 --- a/plugins/CustomVariables/Columns/CustomVariableName.php +++ b/plugins/CustomVariables/Columns/CustomVariableName.php @@ -14,7 +14,7 @@ class CustomVariableName extends Base { protected function configureSegments() { - $this->configureSegmentsFor('custom_var_k', 'Name'); + $this->configureSegmentsFor('Name'); } public function getName() diff --git a/plugins/CustomVariables/Columns/CustomVariableValue.php b/plugins/CustomVariables/Columns/CustomVariableValue.php index 03046758cf..a565a8136c 100644 --- a/plugins/CustomVariables/Columns/CustomVariableValue.php +++ b/plugins/CustomVariables/Columns/CustomVariableValue.php @@ -14,7 +14,7 @@ class CustomVariableValue extends Base { protected function configureSegments() { - $this->configureSegmentsFor('custom_var_v', 'Value'); + $this->configureSegmentsFor('Value'); } public function getName() diff --git a/plugins/SegmentEditor/SegmentSelectorControl.php b/plugins/SegmentEditor/SegmentSelectorControl.php index e37354e1cd..9dc2659660 100644 --- a/plugins/SegmentEditor/SegmentSelectorControl.php +++ b/plugins/SegmentEditor/SegmentSelectorControl.php @@ -115,6 +115,8 @@ class SegmentSelectorControl extends UIControl 'General_OperationGreaterThan', 'General_OperationContains', 'General_OperationDoesNotContain', + 'General_OperationStartsWith', + 'General_OperationEndsWith', 'General_OperationIs', 'General_OperationIsNot', 'General_OperationContains', diff --git a/plugins/SegmentEditor/javascripts/Segmentation.js b/plugins/SegmentEditor/javascripts/Segmentation.js index 0e47bd7d19..ec1e8f23b8 100644 --- a/plugins/SegmentEditor/javascripts/Segmentation.js +++ b/plugins/SegmentEditor/javascripts/Segmentation.js @@ -49,6 +49,8 @@ Segmentation = (function($) { self.availableMatches["dimension"]["!="] = self.translations['General_OperationIsNot']; self.availableMatches["dimension"]["=@"] = self.translations['General_OperationContains']; self.availableMatches["dimension"]["!@"] = self.translations['General_OperationDoesNotContain']; + self.availableMatches["dimension"]["=^"] = self.translations['General_OperationStartsWith']; + self.availableMatches["dimension"]["=$"] = self.translations['General_OperationEndsWith']; segmentation.prototype.setAvailableSegments = function (segments) { this.availableSegments = segments; @@ -264,7 +266,7 @@ Segmentation = (function($) { }; var findAndExplodeByMatch = function(metric){ - var matches = ["==" , "!=" , "<=", ">=", "=@" , "!@","<",">"]; + var matches = ["==" , "!=" , "<=", ">=", "=@" , "!@","<",">", "=^", "=$"]; var newMetric = {}; var minPos = metric.length; var match, index; diff --git a/plugins/SegmentEditor/lang/en.json b/plugins/SegmentEditor/lang/en.json index 46bdf5fef2..933ac73884 100644 --- a/plugins/SegmentEditor/lang/en.json +++ b/plugins/SegmentEditor/lang/en.json @@ -26,6 +26,7 @@ "YouMayChangeSetting": "Alternatively you may change the setting in the config file (%s), or edit this Segment and choose '%s'.", "YouMustBeLoggedInToCreateSegments": "You must be logged in to create and edit custom visitor segments.", "YouDontHaveAccessToCreateSegments": "You don't have the required access level to create and edit segments.", - "AddingSegmentForAllWebsitesDisabled": "Adding segments for all websites has been disabled." + "AddingSegmentForAllWebsitesDisabled": "Adding segments for all websites has been disabled.", + "SegmentXIsAUnionOf": "%s is a union of these segments:" } }
\ No newline at end of file diff --git a/plugins/SegmentEditor/templates/_segmentSelector.twig b/plugins/SegmentEditor/templates/_segmentSelector.twig index 7a8019d37e..f2d53b60c7 100644 --- a/plugins/SegmentEditor/templates/_segmentSelector.twig +++ b/plugins/SegmentEditor/templates/_segmentSelector.twig @@ -61,6 +61,8 @@ <option value=">">{{ 'General_OperationGreaterThan'|translate }}</option> <option value="=@">{{ 'General_OperationContains'|translate }}</option> <option value="!@">{{ 'General_OperationDoesNotContain'|translate }}</option> + <option value="=^">{{ 'General_OperationStartsWith'|translate }}</option> + <option value="=$">{{ 'General_OperationEndsWith'|translate }}</option> </select> </div> <div class="segment-input metricValueBlock"> @@ -99,7 +101,17 @@ <a class="metric_category" href="#">{{ category }}</a> <ul style="display:none;"> {% for segmentInCategory in segmentsInCategory %} - <li data-metric="{{ segmentInCategory.segment }}"><a class="ddmetric" href="#">{{ segmentInCategory.name }}</a></li> + {% set title = segmentInCategory.name %} + {% if segmentInCategory.unionOfSegments is defined and segmentInCategory.unionOfSegments %} + {% set title = 'SegmentEditor_SegmentXIsAUnionOf'|translate(title) %} + {% for unionSegment in segmentInCategory.unionOfSegments %} + {% set title = title ~ ' ' ~ unionSegment %} + {% if not loop.last %} + {% set title = title ~ ',' %} + {% endif %} + {% endfor %} + {% endif %} + <li data-metric="{{ segmentInCategory.segment }}" title="{{ title|e('html_attr') }}"><a class="ddmetric" href="#">{{ segmentInCategory.name }}</a></li> {% endfor %} </ul> </li> diff --git a/tests/PHPUnit/Integration/SegmentTest.php b/tests/PHPUnit/Integration/SegmentTest.php index 9179c6d61a..483f047433 100644 --- a/tests/PHPUnit/Integration/SegmentTest.php +++ b/tests/PHPUnit/Integration/SegmentTest.php @@ -94,8 +94,7 @@ class SegmentTest extends IntegrationTestCase // test multiple column segments array('customVariableName==abc;customVariableValue==def', array( - 'where' => ' ((log_visit.custom_var_k1 = ?) OR (log_visit.custom_var_k2 = ?) OR (log_visit.custom_var_k3 = ?) OR (log_visit.custom_var_k4 = ?) OR (log_visit.custom_var_k5 = ?))' - . ' AND ((log_visit.custom_var_v1 = ?) OR (log_visit.custom_var_v2 = ?) OR (log_visit.custom_var_v3 = ?) OR (log_visit.custom_var_v4 = ?) OR (log_visit.custom_var_v5 = ?)) ', + 'where' => ' (log_visit.custom_var_k1 = ? OR log_visit.custom_var_k2 = ? OR log_visit.custom_var_k3 = ? OR log_visit.custom_var_k4 = ? OR log_visit.custom_var_k5 = ?) AND (log_visit.custom_var_v1 = ? OR log_visit.custom_var_v2 = ? OR log_visit.custom_var_v3 = ? OR log_visit.custom_var_v4 = ? OR log_visit.custom_var_v5 = ? )', 'bind' => array( 'abc', 'abc', 'abc', 'abc', 'abc', 'def', 'def', 'def', 'def', 'def', @@ -446,6 +445,32 @@ class SegmentTest extends IntegrationTestCase $this->assertEquals($this->removeExtraWhiteSpaces($expected), $this->removeExtraWhiteSpaces($query)); } + public function test_getSelectQuery_whenUnionOfSegmentsAreUsed() + { + $select = 'log_visit.*'; + $from = 'log_visit'; + $where = false; + $bind = array(); + + $segment = 'actionUrl=@myTestUrl'; + $segment = new Segment($segment, $idSites = array()); + + $query = $segment->getSelectQuery($select, $from, $where, $bind); + + $expected = array( + "sql" => " SELECT log_inner.* FROM ( + SELECT log_visit.* FROM log_visit AS log_visit + LEFT JOIN log_link_visit_action AS log_link_visit_action + ON log_link_visit_action.idvisit = log_visit.idvisit + WHERE (( log_link_visit_action.idaction_url IN (SELECT idaction FROM log_action WHERE ( name LIKE CONCAT('%', ?, '%') AND type = 1 )) ) + OR ( log_link_visit_action.idaction_url IN (SELECT idaction FROM log_action WHERE ( name LIKE CONCAT('%', ?, '%') AND type = 3 )) ) + OR ( log_link_visit_action.idaction_url IN (SELECT idaction FROM log_action WHERE ( name LIKE CONCAT('%', ?, '%') AND type = 2 )) ) ) + GROUP BY log_visit.idvisit ORDER BY NULL ) AS log_inner", + "bind" => array('myTestUrl', 'myTestUrl', 'myTestUrl')); + + $this->assertEquals($this->removeExtraWhiteSpaces($expected), $this->removeExtraWhiteSpaces($query)); + } + public function test_getSelectQuery_whenJoinConversionOnAction_segmentUsesPageUrl() { $this->insertPageUrlAsAction('example.com/anypage'); diff --git a/tests/PHPUnit/System/AutoSuggestAPITest.php b/tests/PHPUnit/System/AutoSuggestAPITest.php index 2ec2900f5e..9ab0023c2f 100644 --- a/tests/PHPUnit/System/AutoSuggestAPITest.php +++ b/tests/PHPUnit/System/AutoSuggestAPITest.php @@ -17,7 +17,6 @@ use Piwik\Plugins\CustomVariables\Columns\CustomVariableValue; use Piwik\Plugins\CustomVariables\Model; use Piwik\Tests\Framework\TestCase\SystemTestCase; use Piwik\Tests\Fixtures\ManyVisitsWithGeoIP; -use Piwik\Tests\Framework\Fixture; use Piwik\Tracker\Cache; /** diff --git a/tests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentContainsTest.php b/tests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentContainsTest.php index 92a4f2dde2..bad8cc45b3 100755 --- a/tests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentContainsTest.php +++ b/tests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentContainsTest.php @@ -48,6 +48,13 @@ class TwoVisitsWithCustomVariablesSegmentContainsTest extends SystemTestCase array("pageTitle=@Profile pa", '_SegmentPageTitleContains', $api), array("pageUrl!@user/profile", '_SegmentPageUrlExcludes', $api), array("pageTitle!@Profile pa", '_SegmentPageTitleExcludes', $api), + // starts with + array('pageUrl=^example.org/home', '_SegmentPageUrlStartsWith', array('Actions.getPageUrls')), + array('pageTitle=^Profile pa', '_SegmentPageTitleStartsWith', array('Actions.getPageTitles')), + + // ends with + array('pageUrl=$er/profile', '_SegmentPageUrlEndsWith', array('Actions.getPageUrls')), + array('pageTitle=$page', '_SegmentPageTitleEndsWith', array('Actions.getPageTitles')), ); foreach ($segmentsToTest as $segment) { diff --git a/tests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentMatchNONETest.php b/tests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentMatchNONETest.php index 3667f7f65b..65ff528dc6 100755 --- a/tests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentMatchNONETest.php +++ b/tests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentMatchNONETest.php @@ -67,7 +67,7 @@ class TwoVisitsWithCustomVariablesSegmentMatchNONETest extends SystemTestCase $matchNone = $segment . '!=' . $value; // deviceType != campaign matches ALL visits, but we want to match None - if($segment == 'deviceType') { + if ($segment == 'deviceType') { $matchNone = $segment . '==car%20browser'; } $segmentExpression[] = $matchNone; diff --git a/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_actionUrl__API.getSuggestedValuesForSegment.xml b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_actionUrl__API.getSuggestedValuesForSegment.xml new file mode 100644 index 0000000000..2e03969e6d --- /dev/null +++ b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_actionUrl__API.getSuggestedValuesForSegment.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row>http://piwik.net/grue/lair</row> + <row>http://piwik.net/space/quest/iv</row> + <row>http://example-outlink.org/1.html</row> + <row>http://example.org/path/file1.zip</row> + <row>http://example.org/path/file0.zip</row> + <row>http://example-outlink.org/0.html</row> + <row>http://example.org/path/file2.zip</row> + <row>http://example.org/path/file3.zip</row> + <row>http://example-outlink.org/3.html</row> + <row>http://example-outlink.org/2.html</row> + <row>http://example.org/path/file4.zip</row> + <row>http://example.org/path/file5.zip</row> + <row>http://example.org/path/file8.zip</row> + <row>http://example-outlink.org/6.html</row> + <row>http://example-outlink.org/7.html</row> + <row>http://example-outlink.org/5.html</row> + <row>http://example-outlink.org/4.html</row> + <row>http://example.org/path/file7.zip</row> + <row>http://example-outlink.org/8.html</row> + <row>http://example.org/path/file6.zip</row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_actionUrl__VisitsSummary.get_range.xml b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_actionUrl__VisitsSummary.get_range.xml new file mode 100644 index 0000000000..7ace3fcbe7 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_actionUrl__VisitsSummary.get_range.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <nb_visits>18</nb_visits> + <nb_actions>18</nb_actions> + <nb_visits_converted>18</nb_visits_converted> + <bounce_count>18</bounce_count> + <sum_visit_length>0</sum_visit_length> + <max_actions>1</max_actions> + <bounce_rate>100%</bounce_rate> + <nb_actions_per_visit>1</nb_actions_per_visit> + <avg_time_on_site>0</avg_time_on_site> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableName__API.getSuggestedValuesForSegment.xml b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableName__API.getSuggestedValuesForSegment.xml index b3e5836d32..9c10ae8b37 100644 --- a/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableName__API.getSuggestedValuesForSegment.xml +++ b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableName__API.getSuggestedValuesForSegment.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" ?> <result> - <row>Cvar 5 name</row> <row>Cvar 1 name</row> + <row>Cvar 5 name</row> </result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariablePageValue__API.getSuggestedValuesForSegment.xml b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariablePageValue__API.getSuggestedValuesForSegment.xml index fb3a6b6413..0ab1e3a74c 100644 --- a/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariablePageValue__API.getSuggestedValuesForSegment.xml +++ b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariablePageValue__API.getSuggestedValuesForSegment.xml @@ -1,22 +1,22 @@ <?xml version="1.0" encoding="utf-8" ?> <result> <row>CAT</row> + <row>Cvar5 PAGE value is 0</row> <row>Cvar5 PAGE value is 1</row> <row>Cvar2 PAGE value is 1</row> <row>Cvar2 PAGE value is 0</row> - <row>Cvar5 PAGE value is 0</row> - <row>Cvar5 PAGE value is 3</row> <row>Cvar2 PAGE value is 3</row> - <row>Cvar2 PAGE value is 2</row> <row>Cvar5 PAGE value is 2</row> - <row>Cvar5 PAGE value is 4</row> + <row>Cvar5 PAGE value is 3</row> + <row>Cvar2 PAGE value is 2</row> + <row>Cvar2 PAGE value is 5</row> <row>Cvar2 PAGE value is 4</row> - <row>Cvar2 PAGE value is 7</row> - <row>Cvar5 PAGE value is 8</row> - <row>Cvar2 PAGE value is 8</row> - <row>Cvar5 PAGE value is 7</row> <row>Cvar2 PAGE value is 6</row> - <row>Cvar2 PAGE value is 5</row> - <row>Cvar5 PAGE value is 6</row> + <row>Cvar5 PAGE value is 8</row> <row>Cvar5 PAGE value is 5</row> + <row>Cvar5 PAGE value is 6</row> + <row>Cvar5 PAGE value is 4</row> + <row>Cvar5 PAGE value is 7</row> + <row>Cvar2 PAGE value is 8</row> + <row>Cvar2 PAGE value is 7</row> </result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableValue__API.getSuggestedValuesForSegment.xml b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableValue__API.getSuggestedValuesForSegment.xml index 662ed617b1..6be328a177 100644 --- a/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableValue__API.getSuggestedValuesForSegment.xml +++ b/tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableValue__API.getSuggestedValuesForSegment.xml @@ -1,21 +1,21 @@ <?xml version="1.0" encoding="utf-8" ?> <result> - <row>Cvar5 value is 1</row> - <row>Cvar1 value is 0</row> <row>Cvar1 value is 1</row> + <row>Cvar1 value is 0</row> + <row>Cvar5 value is 1</row> <row>Cvar5 value is 0</row> <row>Cvar1 value is 3</row> - <row>Cvar5 value is 3</row> - <row>Cvar5 value is 2</row> <row>Cvar1 value is 2</row> - <row>Cvar5 value is 4</row> + <row>Cvar5 value is 2</row> + <row>Cvar5 value is 3</row> <row>Cvar1 value is 4</row> <row>Cvar5 value is 7</row> - <row>Cvar1 value is 7</row> - <row>Cvar5 value is 8</row> - <row>Cvar1 value is 6</row> + <row>Cvar1 value is 5</row> <row>Cvar5 value is 6</row> + <row>Cvar1 value is 7</row> <row>Cvar5 value is 5</row> - <row>Cvar1 value is 5</row> + <row>Cvar5 value is 8</row> <row>Cvar1 value is 8</row> + <row>Cvar5 value is 4</row> + <row>Cvar1 value is 6</row> </result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml index a8376a8758..e2c3f30c00 100644 --- a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml +++ b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml @@ -274,6 +274,13 @@ <category>Custom Variables</category> <name>Custom Variable name (scope visit)</name> <segment>customVariableName</segment> + <unionOfSegments> + <row>customVariableName1</row> + <row>customVariableName2</row> + <row>customVariableName3</row> + <row>customVariableName4</row> + <row>customVariableName5</row> + </unionOfSegments> </row> <row> <type>dimension</type> @@ -310,6 +317,13 @@ <category>Custom Variables</category> <name>Custom Variable name (scope page)</name> <segment>customVariablePageName</segment> + <unionOfSegments> + <row>customVariablePageName1</row> + <row>customVariablePageName2</row> + <row>customVariablePageName3</row> + <row>customVariablePageName4</row> + <row>customVariablePageName5</row> + </unionOfSegments> </row> <row> <type>dimension</type> @@ -346,6 +360,13 @@ <category>Custom Variables</category> <name>Custom Variable value (scope page)</name> <segment>customVariablePageValue</segment> + <unionOfSegments> + <row>customVariablePageValue1</row> + <row>customVariablePageValue2</row> + <row>customVariablePageValue3</row> + <row>customVariablePageValue4</row> + <row>customVariablePageValue5</row> + </unionOfSegments> </row> <row> <type>dimension</type> @@ -382,6 +403,13 @@ <category>Custom Variables</category> <name>Custom Variable value (scope visit)</name> <segment>customVariableValue</segment> + <unionOfSegments> + <row>customVariableValue1</row> + <row>customVariableValue2</row> + <row>customVariableValue3</row> + <row>customVariableValue4</row> + <row>customVariableValue5</row> + </unionOfSegments> </row> <row> <type>dimension</type> @@ -416,6 +444,17 @@ <row> <type>dimension</type> <category>Actions</category> + <name>Action URL</name> + <segment>actionUrl</segment> + <unionOfSegments> + <row>pageUrl</row> + <row>downloadUrl</row> + <row>outlinkUrl</row> + </unionOfSegments> + </row> + <row> + <type>dimension</type> + <category>Actions</category> <name>Clicked URL</name> <segment>outlinkUrl</segment> </row> diff --git a/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageTitleEndsWith__Actions.getPageTitles_day.xml b/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageTitleEndsWith__Actions.getPageTitles_day.xml new file mode 100644 index 0000000000..38eb716dff --- /dev/null +++ b/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageTitleEndsWith__Actions.getPageTitles_day.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label> Homepage</label> + <nb_visits>2</nb_visits> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_hits>2</nb_hits> + <sum_time_spent>360</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>3</entry_nb_actions> + <entry_sum_visit_length>364</entry_sum_visit_length> + <entry_bounce_count>0</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>180</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>50%</exit_rate> + </row> + <row> + <label> Profile page</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>0%</exit_rate> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageTitleStartsWith__Actions.getPageTitles_day.xml b/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageTitleStartsWith__Actions.getPageTitles_day.xml new file mode 100644 index 0000000000..7cfe228274 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageTitleStartsWith__Actions.getPageTitles_day.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label> Profile page</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>0%</exit_rate> + </row> + <row> + <label> Profile page for user *_)%</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageUrlEndsWith__Actions.getPageUrls_day.xml b/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageUrlEndsWith__Actions.getPageUrls_day.xml new file mode 100644 index 0000000000..c64e18155a --- /dev/null +++ b/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageUrlEndsWith__Actions.getPageUrls_day.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>user</label> + <nb_visits>1</nb_visits> + <nb_hits>2</nb_hits> + <sum_time_spent>0</sum_time_spent> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>100%</exit_rate> + <subtable> + <row> + <label>/profile</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>2</nb_hits> + <sum_time_spent>0</sum_time_spent> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>100%</exit_rate> + <url>http://example.org/user/profile</url> + </row> + </subtable> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageUrlStartsWith__Actions.getPageUrls_day.xml b/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageUrlStartsWith__Actions.getPageUrls_day.xml new file mode 100644 index 0000000000..21450c0dd4 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageUrlStartsWith__Actions.getPageUrls_day.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>/homepage</label> + <nb_visits>2</nb_visits> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_hits>2</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>3</entry_nb_actions> + <entry_sum_visit_length>364</entry_sum_visit_length> + <entry_bounce_count>0</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>50%</exit_rate> + <url>http://example.org/homepage</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fhomepage</segment> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/Unit/Segment/SegmentExpressionTest.php b/tests/PHPUnit/Unit/Segment/SegmentExpressionTest.php index 6d9dcc67b1..71e64863c1 100644 --- a/tests/PHPUnit/Unit/Segment/SegmentExpressionTest.php +++ b/tests/PHPUnit/Unit/Segment/SegmentExpressionTest.php @@ -72,6 +72,7 @@ class SegmentExpressionTest extends \PHPUnit_Framework_TestCase array('A==B,C==D', array('where' => " (A = ? OR C = ? )", 'bind' => array('B', 'D'))), array('A!=B;C==D', array('where' => " ( A IS NULL OR A <> ? ) AND C = ? ", 'bind' => array('B', 'D'))), array('A!=B;C==D,E!=Hello World!=', array('where' => " ( A IS NULL OR A <> ? ) AND (C = ? OR ( E IS NULL OR E <> ? ) )", 'bind' => array('B', 'D', 'Hello World!='))), + array('A=@B;C=$D', array('where' => " A LIKE ? AND C LIKE ? ", 'bind' => array('%B%', '%D'))), array('A>B', array('where' => " A > ? ", 'bind' => array('B'))), array('A<B', array('where' => " A < ? ", 'bind' => array('B'))), @@ -83,6 +84,8 @@ class SegmentExpressionTest extends \PHPUnit_Framework_TestCase array('A=@B_', array('where' => " A LIKE ? ", 'bind' => array('%B\_%'))), array('A!@B%', array('where' => " ( A IS NULL OR A NOT LIKE ? ) ", 'bind' => array('%B\%%'))), + array('A=$B%', array('where' => " A LIKE ? ", 'bind' => array('%B\%'))), + array('A=^B%', array('where' => " A LIKE ? ", 'bind' => array('B\%%'))), ); } |