1919
2020use Monolog \Logger ;
2121use Optimizely \Enums \CommonAudienceEvaluationLogs as logs ;
22+ use Optimizely \Utils \SemVersionConditionEvaluator ;
2223use Optimizely \Utils \Validator ;
2324
2425class CustomAttributeConditionEvaluator
@@ -27,13 +28,20 @@ class CustomAttributeConditionEvaluator
2728
2829 const EXACT_MATCH_TYPE = 'exact ' ;
2930 const EXISTS_MATCH_TYPE = 'exists ' ;
31+ const GREATER_THAN_EQUAL_TO_MATCH_TYPE = 'ge ' ;
3032 const GREATER_THAN_MATCH_TYPE = 'gt ' ;
33+ const LESS_THAN_EQUAL_TO_MATCH_TYPE = 'le ' ;
3134 const LESS_THAN_MATCH_TYPE = 'lt ' ;
35+ const SEMVER_EQ = 'semver_eq ' ;
36+ const SEMVER_GE = 'semver_ge ' ;
37+ const SEMVER_GT = 'semver_gt ' ;
38+ const SEMVER_LE = 'semver_le ' ;
39+ const SEMVER_LT = 'semver_lt ' ;
3240 const SUBSTRING_MATCH_TYPE = 'substring ' ;
3341
3442 /**
3543 * @var UserAttributes
36- */
44+ */
3745 protected $ userAttributes ;
3846
3947 /**
@@ -57,7 +65,7 @@ protected function setNullForMissingKeys(array $leafCondition)
5765 {
5866 $ keys = ['type ' , 'match ' , 'value ' ];
5967 foreach ($ keys as $ key ) {
60- $ leafCondition [$ key ] = isset ($ leafCondition [$ key ]) ? $ leafCondition [$ key ]: null ;
68+ $ leafCondition [$ key ] = isset ($ leafCondition [$ key ]) ? $ leafCondition [$ key ] : null ;
6169 }
6270
6371 return $ leafCondition ;
@@ -70,8 +78,20 @@ protected function setNullForMissingKeys(array $leafCondition)
7078 */
7179 protected function getMatchTypes ()
7280 {
73- return array (self ::EXACT_MATCH_TYPE , self ::EXISTS_MATCH_TYPE , self ::GREATER_THAN_MATCH_TYPE ,
74- self ::LESS_THAN_MATCH_TYPE , self ::SUBSTRING_MATCH_TYPE );
81+ return array (
82+ self ::EXACT_MATCH_TYPE ,
83+ self ::EXISTS_MATCH_TYPE ,
84+ self ::GREATER_THAN_EQUAL_TO_MATCH_TYPE ,
85+ self ::GREATER_THAN_MATCH_TYPE ,
86+ self ::LESS_THAN_EQUAL_TO_MATCH_TYPE ,
87+ self ::LESS_THAN_MATCH_TYPE ,
88+ self ::SEMVER_EQ ,
89+ self ::SEMVER_GE ,
90+ self ::SEMVER_GT ,
91+ self ::SEMVER_LE ,
92+ self ::SEMVER_LT ,
93+ self ::SUBSTRING_MATCH_TYPE ,
94+ );
7595 }
7696
7797 /**
@@ -86,8 +106,15 @@ protected function getEvaluatorByMatchType($matchType)
86106 $ evaluatorsByMatchType = array ();
87107 $ evaluatorsByMatchType [self ::EXACT_MATCH_TYPE ] = 'exactEvaluator ' ;
88108 $ evaluatorsByMatchType [self ::EXISTS_MATCH_TYPE ] = 'existsEvaluator ' ;
109+ $ evaluatorsByMatchType [self ::GREATER_THAN_EQUAL_TO_MATCH_TYPE ] = 'greaterThanEqualToEvaluator ' ;
89110 $ evaluatorsByMatchType [self ::GREATER_THAN_MATCH_TYPE ] = 'greaterThanEvaluator ' ;
111+ $ evaluatorsByMatchType [self ::LESS_THAN_EQUAL_TO_MATCH_TYPE ] = 'lessThanEqualToEvaluator ' ;
90112 $ evaluatorsByMatchType [self ::LESS_THAN_MATCH_TYPE ] = 'lessThanEvaluator ' ;
113+ $ evaluatorsByMatchType [self ::SEMVER_EQ ] = 'semverEqualEvaluator ' ;
114+ $ evaluatorsByMatchType [self ::SEMVER_GE ] = 'semverGreaterThanEqualToEvaluator ' ;
115+ $ evaluatorsByMatchType [self ::SEMVER_GT ] = 'semverGreaterThanEvaluator ' ;
116+ $ evaluatorsByMatchType [self ::SEMVER_LE ] = 'semverLessThanEqualToEvaluator ' ;
117+ $ evaluatorsByMatchType [self ::SEMVER_LT ] = 'semverLessThanEvaluator ' ;
91118 $ evaluatorsByMatchType [self ::SUBSTRING_MATCH_TYPE ] = 'substringEvaluator ' ;
92119
93120 return $ evaluatorsByMatchType [$ matchType ];
@@ -109,6 +136,33 @@ protected function isValueTypeValidForExactConditions($value)
109136 return false ;
110137 }
111138
139+ /**
140+ * Returns result of SemVersionConditionEvaluator::compareVersion for given target and user versions.
141+ *
142+ * @param object $condition
143+ *
144+ * @return null|int 0 if user's version attribute is equal to the semver condition value,
145+ * 1 if user's version attribute is greater than the semver condition value,
146+ * -1 if user's version attribute is less than the semver condition value,
147+ * null if the condition value or user attribute value has an invalid type, or
148+ * if there is a mismatch between the user attribute type and the condition
149+ * value type.
150+ */
151+ protected function semverEvaluator ($ condition )
152+ {
153+ $ conditionName = $ condition ['name ' ];
154+ $ conditionValue = $ condition ['value ' ];
155+ $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ] : null ;
156+
157+ if (!Validator::validateNonEmptyString ($ conditionValue ) || !Validator::validateNonEmptyString ($ userValue )) {
158+ $ this ->logger ->log (Logger::WARNING , sprintf (
159+ logs::ATTRIBUTE_FORMAT_INVALID
160+ ));
161+ return null ;
162+ }
163+ return SemVersionConditionEvaluator::compareVersion ($ conditionValue , $ userValue , $ this ->logger );
164+ }
165+
112166 /**
113167 * Evaluate the given exact match condition for the given user attributes.
114168 *
@@ -124,7 +178,7 @@ protected function exactEvaluator($condition)
124178 {
125179 $ conditionName = $ condition ['name ' ];
126180 $ conditionValue = $ condition ['value ' ];
127- $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ]: null ;
181+ $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ] : null ;
128182
129183 if (!$ this ->isValueTypeValidForExactConditions ($ conditionValue ) ||
130184 ((is_int ($ conditionValue ) || is_float ($ conditionValue )) && !Validator::isFiniteNumber ($ conditionValue ))) {
@@ -189,7 +243,7 @@ protected function greaterThanEvaluator($condition)
189243 {
190244 $ conditionName = $ condition ['name ' ];
191245 $ conditionValue = $ condition ['value ' ];
192- $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ]: null ;
246+ $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ] : null ;
193247
194248 if (!Validator::isFiniteNumber ($ conditionValue )) {
195249 $ this ->logger ->log (Logger::WARNING , sprintf (
@@ -221,6 +275,52 @@ protected function greaterThanEvaluator($condition)
221275 return $ userValue > $ conditionValue ;
222276 }
223277
278+ /**
279+ * Evaluate the given greater than equal to match condition for the given user attributes.
280+ *
281+ * @param object $condition
282+ *
283+ * @return boolean true if the user attribute value is greater than or equal to the condition value,
284+ * false if the user attribute value is less than the condition value,
285+ * null if the condition value isn't a number or the user attribute value
286+ * isn't a number.
287+ */
288+ protected function greaterThanEqualToEvaluator ($ condition )
289+ {
290+ $ conditionName = $ condition ['name ' ];
291+ $ conditionValue = $ condition ['value ' ];
292+ $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ] : null ;
293+
294+ if (!Validator::isFiniteNumber ($ conditionValue )) {
295+ $ this ->logger ->log (Logger::WARNING , sprintf (
296+ logs::UNKNOWN_CONDITION_VALUE ,
297+ json_encode ($ condition )
298+ ));
299+ return null ;
300+ }
301+
302+ if (!(is_int ($ userValue ) || is_float ($ userValue ))) {
303+ $ this ->logger ->log (Logger::WARNING , sprintf (
304+ logs::UNEXPECTED_TYPE ,
305+ json_encode ($ condition ),
306+ gettype ($ userValue ),
307+ $ conditionName
308+ ));
309+ return null ;
310+ }
311+
312+ if (!Validator::isFiniteNumber ($ userValue )) {
313+ $ this ->logger ->log (Logger::WARNING , sprintf (
314+ logs::INFINITE_ATTRIBUTE_VALUE ,
315+ json_encode ($ condition ),
316+ $ conditionName
317+ ));
318+ return null ;
319+ }
320+
321+ return $ userValue >= $ conditionValue ;
322+ }
323+
224324 /**
225325 * Evaluate the given less than match condition for the given user attributes.
226326 *
@@ -235,7 +335,7 @@ protected function lessThanEvaluator($condition)
235335 {
236336 $ conditionName = $ condition ['name ' ];
237337 $ conditionValue = $ condition ['value ' ];
238- $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ]: null ;
338+ $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ] : null ;
239339
240340 if (!Validator::isFiniteNumber ($ conditionValue )) {
241341 $ this ->logger ->log (Logger::WARNING , sprintf (
@@ -267,7 +367,53 @@ protected function lessThanEvaluator($condition)
267367 return $ userValue < $ conditionValue ;
268368 }
269369
270- /**
370+ /**
371+ * Evaluate the given less than equal to match condition for the given user attributes.
372+ *
373+ * @param object $condition
374+ *
375+ * @return boolean true if the user attribute value is less than or equal to the condition value,
376+ * false if the user attribute value is greater than the condition value,
377+ * null if the condition value isn't a number or the user attribute value
378+ * isn't a number.
379+ */
380+ protected function lessThanEqualToEvaluator ($ condition )
381+ {
382+ $ conditionName = $ condition ['name ' ];
383+ $ conditionValue = $ condition ['value ' ];
384+ $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ] : null ;
385+
386+ if (!Validator::isFiniteNumber ($ conditionValue )) {
387+ $ this ->logger ->log (Logger::WARNING , sprintf (
388+ logs::UNKNOWN_CONDITION_VALUE ,
389+ json_encode ($ condition )
390+ ));
391+ return null ;
392+ }
393+
394+ if (!(is_int ($ userValue ) || is_float ($ userValue ))) {
395+ $ this ->logger ->log (Logger::WARNING , sprintf (
396+ logs::UNEXPECTED_TYPE ,
397+ json_encode ($ condition ),
398+ gettype ($ userValue ),
399+ $ conditionName
400+ ));
401+ return null ;
402+ }
403+
404+ if (!Validator::isFiniteNumber ($ userValue )) {
405+ $ this ->logger ->log (Logger::WARNING , sprintf (
406+ logs::INFINITE_ATTRIBUTE_VALUE ,
407+ json_encode ($ condition ),
408+ $ conditionName
409+ ));
410+ return null ;
411+ }
412+
413+ return $ userValue <= $ conditionValue ;
414+ }
415+
416+ /**
271417 * Evaluate the given substring than match condition for the given user attributes.
272418 *
273419 * @param object $condition
@@ -281,7 +427,7 @@ protected function substringEvaluator($condition)
281427 {
282428 $ conditionName = $ condition ['name ' ];
283429 $ conditionValue = $ condition ['value ' ];
284- $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ]: null ;
430+ $ userValue = isset ($ this ->userAttributes [$ conditionName ]) ? $ this ->userAttributes [$ conditionName ] : null ;
285431
286432 if (!is_string ($ conditionValue )) {
287433 $ this ->logger ->log (Logger::WARNING , sprintf (
@@ -304,6 +450,96 @@ protected function substringEvaluator($condition)
304450 return strpos ($ userValue , $ conditionValue ) !== false ;
305451 }
306452
453+ /**
454+ * Evaluate the given semantic version equal match condition for the given user attributes.
455+ *
456+ * @param object $condition
457+ *
458+ * @return boolean true if user's version attribute is equal to the semver condition value,
459+ * false if the user's version attribute is greater or less than the semver condition value,
460+ * null if the semver condition value or user's version attribute is invalid.
461+ */
462+ protected function semverEqualEvaluator ($ condition )
463+ {
464+ $ comparison = $ this ->semverEvaluator ($ condition );
465+ if ($ comparison === null ) {
466+ return null ;
467+ }
468+ return $ comparison === 0 ;
469+ }
470+
471+ /**
472+ * Evaluate the given semantic version greater than match condition for the given user attributes.
473+ *
474+ * @param object $condition
475+ *
476+ * @return boolean true if user's version attribute is greater than the semver condition value,
477+ * false if the user's version attribute is less than or equal to the semver condition value,
478+ * null if the semver condition value or user's version attribute is invalid.
479+ */
480+ protected function semverGreaterThanEvaluator ($ condition )
481+ {
482+ $ comparison = $ this ->semverEvaluator ($ condition );
483+ if ($ comparison === null ) {
484+ return null ;
485+ }
486+ return $ comparison > 0 ;
487+ }
488+
489+ /**
490+ * Evaluate the given semantic version greater than equal to match condition for the given user attributes.
491+ *
492+ * @param object $condition
493+ *
494+ * @return boolean true if user's version attribute is greater than or equal to the semver condition value,
495+ * false if the user's version attribute is less than the semver condition value,
496+ * null if the semver condition value or user's version attribute is invalid.
497+ */
498+ protected function semverGreaterThanEqualToEvaluator ($ condition )
499+ {
500+ $ comparison = $ this ->semverEvaluator ($ condition );
501+ if ($ comparison === null ) {
502+ return null ;
503+ }
504+ return $ comparison >= 0 ;
505+ }
506+
507+ /**
508+ * Evaluate the given semantic version less than match condition for the given user attributes.
509+ *
510+ * @param object $condition
511+ *
512+ * @return boolean true if user's version attribute is less than the semver condition value,
513+ * false if the user's version attribute is greater than or equal to the semver condition value,
514+ * null if the semver condition value or user's version attribute is invalid.
515+ */
516+ protected function semverLessThanEvaluator ($ condition )
517+ {
518+ $ comparison = $ this ->semverEvaluator ($ condition );
519+ if ($ comparison === null ) {
520+ return null ;
521+ }
522+ return $ comparison < 0 ;
523+ }
524+
525+ /**
526+ * Evaluate the given semantic version less than equal to match condition for the given user attributes.
527+ *
528+ * @param object $condition
529+ *
530+ * @return boolean true if user's version attribute is less than or equal to the semver condition value,
531+ * false if the user's version attribute is greater than the semver condition value,
532+ * null if the semver condition value or user's version attribute is invalid.
533+ */
534+ protected function semverLessThanEqualToEvaluator ($ condition )
535+ {
536+ $ comparison = $ this ->semverEvaluator ($ condition );
537+ if ($ comparison === null ) {
538+ return null ;
539+ }
540+ return $ comparison <= 0 ;
541+ }
542+
307543 /**
308544 * Function to evaluate audience conditions against user's attributes.
309545 *
0 commit comments