Skip to content

Commit

Permalink
refs #8076 #9224 adding new segment ActionUrl + new operators starts …
Browse files Browse the repository at this point in the history
…with and ends with
  • Loading branch information
tsteur committed Nov 17, 2015
1 parent e90d8e4 commit 601bf2e
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 10 deletions.
31 changes: 31 additions & 0 deletions core/Plugin/Segment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -167,6 +168,23 @@ public function setSqlSegment($sqlSegment)
$this->sqlSegment = $sqlSegment;
}

/**
* 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;
}

public function getUnionOfSegments()
{
return $this->unionOfSegments;
}

/**
* @return string
* @ignore
Expand Down Expand Up @@ -195,6 +213,15 @@ public function getType()
return $this->type;
}

/**
* @return string
* @ignore
*/
public function getName()
{
return $this->name;
}

/**
* Returns the name of this segment as it should appear in segment expressions.
*
Expand Down Expand Up @@ -241,6 +268,10 @@ public function toArray()
'sqlSegment' => $this->sqlSegment,
);

if (!empty($this->unionOfSegments)) {
$segment['unionOfSegments'] = $this->unionOfSegments;
}

if (!empty($this->sqlFilter)) {
$segment['sqlFilter'] = $this->sqlFilter;
}
Expand Down
41 changes: 40 additions & 1 deletion core/Segment.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,50 @@ protected function initializeSegment($string, $idSites)
// parse segments
$expressions = $segment->parseSubExpressions();

// segment metadata
if (empty($this->availableSegments)) {
$this->availableSegments = API::getInstance()->getSegmentsMetadata($this->idSites, $_hideImplementationData = false);
}

$expressionsWithUnions = array();
foreach ($expressions as $expression) {
$operand = $expression[SegmentExpression::INDEX_OPERAND];
$name = $operand[0];

foreach ($this->availableSegments as $availableSegment) {
if ($availableSegment['segment'] != $name) {
continue;
}

if (!empty($availableSegment['unionOfSegments'])) {
$count = 0;
foreach ($availableSegment['unionOfSegments'] as $replace) {
$count++;
$operator = SegmentExpression::BOOL_OPERATOR_OR;
if ($count === count($availableSegment['unionOfSegments'])) {
$operator = SegmentExpression::BOOL_OPERATOR_END;
}

$operand[0] = $replace;
$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
);
}
}
}

// convert segments name to sql segment
// check that user is allowed to view this segment
// and apply a filter to the value to match if necessary (to map DB fields format)
$cleanedExpressions = array();
foreach ($expressions as $expression) {
foreach ($expressionsWithUnions as $expression) {
$operand = $expression[SegmentExpression::INDEX_OPERAND];
$cleanedExpression = $this->getCleanedExpression($operand);
$expression[SegmentExpression::INDEX_OPERAND] = $cleanedExpression;
Expand Down
32 changes: 25 additions & 7 deletions core/Segment/SegmentExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -89,7 +95,9 @@ public function parseSubExpressions()
. 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) {
Expand Down Expand Up @@ -193,7 +201,9 @@ protected function getSqlMatchFromDefinition($def, &$availableTables)
// 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
Expand Down Expand Up @@ -241,6 +251,14 @@ protected function getSqlMatchFromDefinition($def, &$availableTables)
$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)';
Expand Down Expand Up @@ -372,15 +390,15 @@ protected function parseTree()
$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)) {
Expand Down Expand Up @@ -413,7 +431,7 @@ public function getSql()
$operator = $expression[self::INDEX_BOOL_OPERATOR];
$operand = $expression[self::INDEX_OPERAND];

if ($operator == 'OR'
if ($operator == self::BOOL_OPERATOR_OR
&& !$subExpression
) {
$sql .= ' (';
Expand All @@ -424,7 +442,7 @@ public function getSql()

$sql .= $operand;

if ($operator == 'AND'
if ($operator == self::BOOL_OPERATOR_AND
&& $subExpression
) {
$sql .= ')';
Expand Down
12 changes: 10 additions & 2 deletions core/Tracker/TableLogAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,21 @@ private static function getSelectQueryWhereNameContains($matchType, $actionType)
$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;
Expand Down
4 changes: 4 additions & 0 deletions plugins/API/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ public function getSegmentsMetadata($idSites = array(), $_hideImplementationData
$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()));
}

$segments[] = $segment->toArray();
}
}
Expand Down
32 changes: 32 additions & 0 deletions plugins/Actions/Columns/ActionUrl.php
Original file line number Diff line number Diff line change
@@ -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);
}

}
1 change: 1 addition & 0 deletions plugins/Actions/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 601bf2e

Please sign in to comment.