1<?php 2/** 3 * Matomo - free/libre analytics platform 4 * 5 * @link https://matomo.org 6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later 7 * 8 */ 9namespace Piwik\Plugin; 10use Exception; 11use Piwik\Development; 12 13/** 14 * Creates a new segment that can be used for instance within the {@link \Piwik\Columns\Dimension::configureSegment()} 15 * method. Make sure to set at least the following values: {@link setName()}, {@link setSegment()}, 16 * {@link setSqlSegment()}, {@link setType()} and {@link setCategory()}. If you are using a segment in the context of a 17 * dimension the type and the SQL segment is usually set for you automatically. 18 * 19 * Example: 20 * ``` 21 $segment = new \Piwik\Plugin\Segment(); 22 $segment->setType(\Piwik\Plugin\Segment::TYPE_DIMENSION); 23 $segment->setName('General_EntryKeyword'); 24 $segment->setCategory('General_Visit'); 25 $segment->setSegment('entryKeyword'); 26 $segment->setSqlSegment('log_visit.entry_keyword'); 27 $segment->setAcceptedValues('Any keywords people search for on your website such as "help" or "imprint"'); 28 ``` 29 * @api 30 * @since 2.5.0 31 */ 32class Segment 33{ 34 /** 35 * Segment type 'dimension'. Can be used along with {@link setType()}. 36 * @api 37 */ 38 const TYPE_DIMENSION = 'dimension'; 39 40 /** 41 * Segment type 'metric'. Can be used along with {@link setType()}. 42 * @api 43 */ 44 const TYPE_METRIC = 'metric'; 45 46 private $type; 47 private $category; 48 private $name; 49 private $segment; 50 private $sqlSegment; 51 private $sqlFilter; 52 private $sqlFilterValue; 53 private $acceptValues; 54 private $permission; 55 private $suggestedValuesCallback; 56 private $unionOfSegments; 57 private $isInternalSegment = false; 58 private $suggestedValuesApi = ''; 59 60 /** 61 * If true, this segment will only be visible to a registered user (see API.getSegmentsMetadata). 62 * 63 * @var bool 64 */ 65 private $requiresRegisteredUser = false; 66 67 /** 68 * @ignore 69 */ 70 final public function __construct() 71 { 72 $this->init(); 73 } 74 75 /** 76 * Here you can initialize this segment and set any default values. It is called directly after the object is 77 * created. 78 * @api 79 */ 80 protected function init() 81 { 82 } 83 84 /** 85 * Here you should explain which values are accepted/useful for your segment, for example: 86 * "1, 2, 3, etc." or "comcast.net, proxad.net, etc.". If the value needs any special encoding you should mention 87 * this as well. For example "Any URL including protocol. The URL must be URL encoded." 88 * 89 * @param string $acceptedValues 90 * @api 91 */ 92 public function setAcceptedValues($acceptedValues) 93 { 94 $this->acceptValues = $acceptedValues; 95 } 96 97 /** 98 * Set (overwrite) the category this segment belongs to. It should be a translation key such as 'General_Actions' 99 * or 'General_Visit'. 100 * @param string $category 101 * @api 102 */ 103 public function setCategory($category) 104 { 105 $this->category = $category; 106 } 107 108 /** 109 * Set (overwrite) the segment display name. This name will be visible in the API and the UI. It should be a 110 * translation key such as 'Actions_ColumnEntryPageTitle' or 'Resolution_ColumnResolution'. 111 * @param string $name 112 * @api 113 */ 114 public function setName($name) 115 { 116 $this->name = $name; 117 } 118 119 /** 120 * Set (overwrite) the name of the segment. The name should be lower case first and has to be unique. The segment 121 * name defined here needs to be set in the URL to actually apply this segment. Eg if the segment is 'searches' 122 * you need to set "&segment=searches>0" in the UI. 123 * @param string $segment 124 * @api 125 */ 126 public function setSegment($segment) 127 { 128 $this->segment = $segment; 129 $this->check(); 130 } 131 132 /** 133 * Sometimes you want users to set values that differ from the way they are actually stored. For instance if you 134 * want to allow to filter by any URL than you might have to resolve this URL to an action id. Or a country name 135 * maybe has to be mapped to a 2 letter country code. You can do this by specifying either a callable such as 136 * `array('Classname', 'methodName')` or by passing a closure. There will be four values passed to the given closure 137 * or callable: `string $valueToMatch`, `string $segment` (see {@link setSegment()}), `string $matchType` 138 * (eg SegmentExpression::MATCH_EQUAL or any other match constant of this class) and `$segmentName`. 139 * 140 * If the closure returns NULL, then Piwik assumes the segment sub-string will not match any visitor. 141 * 142 * @param string|\Closure $sqlFilter 143 * @api 144 */ 145 public function setSqlFilter($sqlFilter) 146 { 147 $this->sqlFilter = $sqlFilter; 148 } 149 150 /** 151 * Similar to {@link setSqlFilter()} you can map a given segment value to another value. For instance you could map 152 * "new" to 0, 'returning' to 1 and any other value to '2'. You can either define a callable or a closure. There 153 * will be only one value passed to the closure or callable which contains the value a user has set for this 154 * segment. This callback is called shortly before {@link setSqlFilter()}. 155 * @param string|array $sqlFilterValue 156 * @api 157 */ 158 public function setSqlFilterValue($sqlFilterValue) 159 { 160 $this->sqlFilterValue = $sqlFilterValue; 161 } 162 163 /** 164 * Defines to which column in the MySQL database the segment belongs: 'mytablename.mycolumnname'. Eg 165 * 'log_visit.idsite'. When a segment is applied the given or filtered value will be compared with this column. 166 * 167 * @param string $sqlSegment 168 * @api 169 */ 170 public function setSqlSegment($sqlSegment) 171 { 172 $this->sqlSegment = $sqlSegment; 173 $this->check(); 174 } 175 176 /** 177 * Set a list of segments that should be used instead of fetching the values from a single column. 178 * All set segments will be applied via an OR operator. 179 * 180 * @param array $segments 181 * @api 182 */ 183 public function setUnionOfSegments($segments) 184 { 185 $this->unionOfSegments = $segments; 186 $this->check(); 187 } 188 189 /** 190 * @return array 191 * @ignore 192 */ 193 public function getUnionOfSegments() 194 { 195 return $this->unionOfSegments; 196 } 197 198 /** 199 * @return string 200 * @ignore 201 */ 202 public function getSqlSegment() 203 { 204 return $this->sqlSegment; 205 } 206 207 /** 208 * @return string 209 * @ignore 210 */ 211 public function getSqlFilterValue() 212 { 213 return $this->sqlFilterValue; 214 } 215 216 /** 217 * @return string 218 * @ignore 219 */ 220 public function getAcceptValues() 221 { 222 return $this->acceptValues; 223 } 224 225 /** 226 * @return string 227 * @ignore 228 */ 229 public function getSqlFilter() 230 { 231 return $this->sqlFilter; 232 } 233 234 /** 235 * Set (overwrite) the type of this segment which is usually either a 'dimension' or a 'metric'. 236 * @param string $type See constansts TYPE_* 237 * @api 238 */ 239 public function setType($type) 240 { 241 $this->type = $type; 242 } 243 244 /** 245 * @return string 246 * @ignore 247 */ 248 public function getType() 249 { 250 return $this->type; 251 } 252 253 /** 254 * @return string 255 * @ignore 256 */ 257 public function getName() 258 { 259 return $this->name; 260 } 261 262 /** 263 * @return string 264 * @ignore 265 */ 266 public function getCategoryId() 267 { 268 return $this->category; 269 } 270 271 /** 272 * Returns the name of this segment as it should appear in segment expressions. 273 * 274 * @return string 275 */ 276 public function getSegment() 277 { 278 return $this->segment; 279 } 280 281 /** 282 * @return string 283 * @ignore 284 */ 285 public function getSuggestedValuesCallback() 286 { 287 return $this->suggestedValuesCallback; 288 } 289 290 /** 291 * Set callback which will be executed when user will call for suggested values for segment. 292 * 293 * @param callable $suggestedValuesCallback 294 */ 295 public function setSuggestedValuesCallback($suggestedValuesCallback) 296 { 297 $this->suggestedValuesCallback = $suggestedValuesCallback; 298 } 299 300 /** 301 * @return string 302 * @ignore 303 */ 304 public function getSuggestedValuesApi() 305 { 306 return $this->suggestedValuesApi; 307 } 308 309 /** 310 * Set callback which will be executed when user will call for suggested values for segment. 311 * 312 * @param string $suggestedValuesApi 313 */ 314 public function setSuggestedValuesApi($suggestedValuesApi) 315 { 316 if (!empty($suggestedValuesApi) && is_string($suggestedValuesApi)) { 317 if (Development::isEnabled() && strpos($suggestedValuesApi, '.get') === false) { 318 throw new Exception('Invalid suggested values API defined, expecting ".get" to be present.'); 319 } 320 } else { 321 $suggestedValuesApi = ''; 322 } 323 $this->suggestedValuesApi = $suggestedValuesApi; 324 } 325 326 /** 327 * You can restrict the access to this segment by passing a boolean `false`. For instance if you want to make 328 * a certain segment only available to users having super user access you could do the following: 329 * `$segment->setPermission(Piwik::hasUserSuperUserAccess());` 330 * @param bool $permission 331 * @api 332 */ 333 public function setPermission($permission) 334 { 335 $this->permission = $permission; 336 } 337 338 /** 339 * @return array 340 * @ignore 341 */ 342 public function toArray() 343 { 344 $segment = array( 345 'type' => $this->type, 346 'category' => $this->category, 347 'name' => $this->name, 348 'segment' => $this->segment, 349 'sqlSegment' => $this->sqlSegment, 350 ); 351 352 if (!empty($this->unionOfSegments)) { 353 $segment['unionOfSegments'] = $this->unionOfSegments; 354 } 355 356 if (!empty($this->sqlFilter)) { 357 $segment['sqlFilter'] = $this->sqlFilter; 358 } 359 360 if (!empty($this->sqlFilterValue)) { 361 $segment['sqlFilterValue'] = $this->sqlFilterValue; 362 } 363 364 if (!empty($this->acceptValues)) { 365 $segment['acceptedValues'] = $this->acceptValues; 366 } 367 368 if (isset($this->permission)) { 369 $segment['permission'] = $this->permission; 370 } 371 372 if (is_callable($this->suggestedValuesCallback)) { 373 $segment['suggestedValuesCallback'] = $this->suggestedValuesCallback; 374 } 375 376 if (is_string($this->suggestedValuesApi) && !empty($this->suggestedValuesApi)) { 377 $segment['suggestedValuesApi'] = $this->suggestedValuesApi; 378 } 379 380 return $segment; 381 } 382 383 /** 384 * Returns true if this segment should only be visible to registered users (see API.getSegmentsMetadata), 385 * false if it should always be visible to any user (even the anonymous user). 386 * 387 * @return boolean 388 * @ignore 389 */ 390 public function isRequiresRegisteredUser() 391 { 392 return $this->requiresRegisteredUser; 393 } 394 395 /** 396 * Sets whether the segment should only be visible to registered users. If set to false it will be even visible to 397 * the anonymous user 398 * 399 * @param boolean $requiresRegisteredUser 400 * @ignore 401 */ 402 public function setRequiresRegisteredUser($requiresRegisteredUser) 403 { 404 $this->requiresRegisteredUser = $requiresRegisteredUser; 405 } 406 407 /** 408 * Sets whether the segment is for internal use only and should not be visible in the UI or in API metadata output. 409 * These types of segments are, for example, used in unions for other segments, but have no value to users. 410 * 411 * @param bool $value 412 */ 413 public function setIsInternal($value) 414 { 415 $this->isInternalSegment = $value; 416 } 417 418 /** 419 * Gets whether the segment is for internal use only and should not be visible in the UI or in API metadata output. 420 * These types of segments are, for example, used in unions for other segments, but have no value to users. 421 * 422 * @return bool 423 */ 424 public function isInternal() 425 { 426 return $this->isInternalSegment; 427 } 428 429 private function check() 430 { 431 if ($this->sqlSegment && $this->unionOfSegments) { 432 throw new Exception(sprintf('Union of segments and SQL segment is set for segment "%s", use only one of them', $this->name)); 433 } 434 435 if ($this->segment && $this->unionOfSegments && in_array($this->segment, $this->unionOfSegments, true)) { 436 throw new Exception(sprintf('The segment %s contains a union segment to itself', $this->name)); 437 } 438 } 439} 440