diff options
20 files changed, 444 insertions, 71 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..3d6914fe5c 100644 --- a/core/Plugin/Segment.php +++ b/core/Plugin/Segment.php @@ -51,6 +51,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 @@ -168,6 +169,27 @@ class Segment } /** + * 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; + } + + /** + * @return array + * @ignore + */ + public function getUnionOfSegments() + { + return $this->unionOfSegments; + } + + /** * @return string * @ignore */ @@ -196,6 +218,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 +272,10 @@ class Segment 'sqlSegment' => $this->sqlSegment, ); + if (!empty($this->unionOfSegments)) { + $segment['unionOfSegments'] = $this->unionOfSegments; + } + if (!empty($this->sqlFilter)) { $segment['sqlFilter'] = $this->sqlFilter; } 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..cd2e0038bd 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; @@ -149,11 +161,11 @@ class SegmentExpression $operand = $this->getSqlMatchFromDefinition($operandDefinition, $availableTables); - if ($operand[1] !== null) { + if ($operand[self::INDEX_OPERAND_OPERATOR] !== null) { $this->valuesBind = array_merge($this->valuesBind, $operand[1]); } - $operand = $operand[0]; + $operand = $operand[self::INDEX_OPERAND_NAME]; $sqlSubExpressions[] = array( self::INDEX_BOOL_OPERATOR => $operator, @@ -193,7 +205,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 @@ -241,6 +255,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)'; @@ -372,15 +394,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 +435,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 +446,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/plugins/API/API.php b/plugins/API/API.php index 4ea0cb4055..09e4719824 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -129,6 +129,14 @@ class API extends \Piwik\Plugin\API $segment->setPermission($isAuthenticatedWithViewAccess); } + if ($segment->getSqlSegment() && $segment->getUnionOfSegments()) { + throw new \Exception(sprintf('Union of segments and SQL segment is set for segment "%s", use only one of them', $segment->getName())); + } + + if ($segment->getUnionOfSegments() && in_array($segment->getSegment(), $segment->getUnionOfSegments(), true)) { + throw new \Exception(sprintf('The segment %s contains a union segment to itself', $segment->getName())); + } + $segments[] = $segment->toArray(); } } @@ -493,30 +501,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 +523,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 +583,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 +599,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/tests/PHPUnit/Integration/SegmentTest.php b/tests/PHPUnit/Integration/SegmentTest.php index 9179c6d61a..f88d1248f6 100644 --- a/tests/PHPUnit/Integration/SegmentTest.php +++ b/tests/PHPUnit/Integration/SegmentTest.php @@ -446,6 +446,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_apiGetReportMetadata__API.getSegmentsMetadata.xml b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml index a8376a8758..017837d9a8 100644 --- a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml +++ b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getSegmentsMetadata.xml @@ -416,6 +416,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\%%'))), ); } |