Skip to content

Commit

Permalink
Merge pull request #9228 from piwik/9224
Browse files Browse the repository at this point in the history
Add new segment ActionUrl + new operators starts with and ends with
  • Loading branch information
tsteur committed Nov 22, 2015
2 parents 741397e + ada836d commit f9c8511
Show file tree
Hide file tree
Showing 31 changed files with 545 additions and 147 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions core/Plugin/Segment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -122,6 +124,7 @@ public function setName($name)
public function setSegment($segment)
{
$this->segment = $segment;
$this->check();
}

/**
Expand Down Expand Up @@ -165,6 +168,29 @@ public function setSqlFilterValue($sqlFilterValue)
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;
}

/**
Expand Down Expand Up @@ -195,6 +221,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 +276,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 Expand Up @@ -288,4 +327,15 @@ public function setRequiresAtLeastViewAccess($requiresAtLeastViewAccess)
{
$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));
}
}
}
103 changes: 72 additions & 31 deletions core/Segment.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,35 @@ public function __construct($segmentCondition, $idSites)
}
}

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
Expand All @@ -127,6 +156,7 @@ protected function initializeSegment($string, $idSites)

// parse segments
$expressions = $segment->parseSubExpressions();
$expressions = $this->getExpressionsWithUnionsResolved($expressions);

// convert segments name to sql segment
// check that user is allowed to view this segment
Expand All @@ -142,6 +172,41 @@ protected function initializeSegment($string, $idSites)
$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.
*/
Expand All @@ -154,33 +219,15 @@ public function isEmpty()

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);
Expand All @@ -201,12 +248,6 @@ protected function getCleanedExpression($expression)
$matchType = SegmentExpression::MATCH_ACTIONS_CONTAINS;
}
}

break;
}

if (empty($sqlName)) {
throw new Exception("Segment '$name' is not a supported segment.");
}

return array($sqlName, $matchType, $value);
Expand Down
Loading

0 comments on commit f9c8511

Please sign in to comment.