Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--core/Plugin/Segment.php50
-rw-r--r--core/Segment.php103
-rw-r--r--core/Segment/SegmentExpression.php104
-rw-r--r--core/Tracker/TableLogAction.php12
-rw-r--r--lang/en.json2
-rw-r--r--plugins/API/API.php85
-rw-r--r--plugins/Actions/Columns/ActionUrl.php32
-rw-r--r--plugins/Actions/lang/en.json1
-rw-r--r--plugins/CustomVariables/Columns/Base.php12
-rw-r--r--plugins/CustomVariables/Columns/CustomVariableName.php2
-rw-r--r--plugins/CustomVariables/Columns/CustomVariableValue.php2
-rw-r--r--plugins/SegmentEditor/SegmentSelectorControl.php2
-rw-r--r--plugins/SegmentEditor/javascripts/Segmentation.js4
-rw-r--r--plugins/SegmentEditor/lang/en.json3
-rw-r--r--plugins/SegmentEditor/templates/_segmentSelector.twig14
-rw-r--r--tests/PHPUnit/Integration/SegmentTest.php29
-rw-r--r--tests/PHPUnit/System/AutoSuggestAPITest.php1
-rwxr-xr-xtests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentContainsTest.php7
-rwxr-xr-xtests/PHPUnit/System/TwoVisitsWithCustomVariablesSegmentMatchNONETest.php2
-rw-r--r--tests/PHPUnit/System/expected/test_AutoSuggestAPITest_actionUrl__API.getSuggestedValuesForSegment.xml23
-rw-r--r--tests/PHPUnit/System/expected/test_AutoSuggestAPITest_actionUrl__VisitsSummary.get_range.xml12
-rw-r--r--tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableName__API.getSuggestedValuesForSegment.xml2
-rw-r--r--tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariablePageValue__API.getSuggestedValuesForSegment.xml20
-rw-r--r--tests/PHPUnit/System/expected/test_AutoSuggestAPITest_customVariableValue__API.getSuggestedValuesForSegment.xml18
-rw-r--r--tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml39
-rw-r--r--tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageTitleEndsWith__Actions.getPageTitles_day.xml30
-rw-r--r--tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageTitleStartsWith__Actions.getPageTitles_day.xml25
-rw-r--r--tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageUrlEndsWith__Actions.getPageUrls_day.xml28
-rw-r--r--tests/PHPUnit/System/expected/test_twoVisitsWithCustomVariables_SegmentPageUrlStartsWith__Actions.getPageUrls_day.xml22
-rw-r--r--tests/PHPUnit/Unit/Segment/SegmentExpressionTest.php3
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\%%'))),
);
}