FBAEMKit/FBAEMKit/FBAEMAdvertiserSingleEntryRule.m (221 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
#if !TARGET_OS_TV
#import "FBAEMAdvertiserSingleEntryRule.h"
#import <FBSDKCoreKit_Basics/FBSDKCoreKit_Basics.h>
#import <FBAEMKit/FBAEMKit-Swift.h>
static NSString *const OPERATOR_KEY = @"operator";
static NSString *const PARAMKEY_KEY = @"param_key";
static NSString *const STRING_VALUE_KEY = @"string_value";
static NSString *const NUMBER_VALUE_KEY = @"number_value";
static NSString *const ARRAY_VALUE_KEY = @"array_value";
static NSString *const PARAM_DELIMETER = @".";
static NSString *const ASTERISK_DELIMETER = @"[*]";
@implementation FBAEMAdvertiserSingleEntryRule
- (instancetype)initWithOperator:(FBAEMAdvertiserRuleOperator)op
paramKey:(NSString *)paramKey
linguisticCondition:(nullable NSString *)linguisticCondition
numericalCondition:(nullable NSNumber *)numericalCondition
arrayCondition:(nullable NSArray<NSString *> *)arrayCondition
{
if ((self = [super init])) {
_operator = op;
_paramKey = paramKey;
_linguisticCondition = linguisticCondition;
_numericalCondition = numericalCondition;
_arrayCondition = arrayCondition;
}
return self;
}
#pragma mark - FBAEMAdvertiserRuleMatching
- (BOOL)isMatchedEventParameters:(nullable NSDictionary<NSString *, id> *)eventParams
{
@try {
NSArray<NSString *> *paramPath = [_paramKey componentsSeparatedByString:PARAM_DELIMETER];
return [self isMatchedEventParameters:eventParams paramPath:paramPath];
} @catch (NSException *exception) {
#if DEBUG
#if FBTEST
@throw exception;
#endif
#endif
return NO;
}
}
- (BOOL)isMatchedEventParameters:(nullable NSDictionary<NSString *, id> *)eventParams
paramPath:(NSArray<NSString *> *)paramPath
{
eventParams = [FBSDKTypeUtility dictionaryValue:eventParams];
if (!eventParams || !paramPath.count) {
return NO;
}
NSString *param = [FBSDKTypeUtility stringValueOrNil:paramPath.firstObject];
if ([param hasSuffix:ASTERISK_DELIMETER]) {
return [self isMatchedWithAsteriskParam:param eventParameters:eventParams paramPath:paramPath];
}
// if data does not contain the key, we should return false directly.
if (!param || ![eventParams.allKeys containsObject:param]) {
return NO;
}
// Apply operator rule if the last param is reached
if (paramPath.count == 1) {
NSString *stringValue = nil;
NSNumber *numericalValue = nil;
switch (_operator) {
case FBAEMAdvertiserRuleOperatorContains:
case FBAEMAdvertiserRuleOperatorNotContains:
case FBAEMAdvertiserRuleOperatorStartsWith:
case FBAEMAdvertiserRuleOperatorCaseInsensitiveContains:
case FBAEMAdvertiserRuleOperatorCaseInsensitiveNotContains:
case FBAEMAdvertiserRuleOperatorCaseInsensitiveStartsWith:
case FBAEMAdvertiserRuleOperatorRegexMatch:
case FBAEMAdvertiserRuleOperatorEqual:
case FBAEMAdvertiserRuleOperatorNotEqual:
case FBAEMAdvertiserRuleOperatorCaseInsensitiveIsAny:
case FBAEMAdvertiserRuleOperatorCaseInsensitiveIsNotAny:
case FBAEMAdvertiserRuleOperatorIsAny:
case FBAEMAdvertiserRuleOperatorIsNotAny:
stringValue = [FBSDKTypeUtility dictionary:eventParams objectForKey:param ofType:NSString.class]; break;
case FBAEMAdvertiserRuleOperatorLessThan:
case FBAEMAdvertiserRuleOperatorLessThanOrEqual:
case FBAEMAdvertiserRuleOperatorGreaterThan:
case FBAEMAdvertiserRuleOperatorGreaterThanOrEqual:
numericalValue = [FBSDKTypeUtility dictionary:eventParams objectForKey:param ofType:NSNumber.class]; break;
default: break;
}
return [self isMatchedWithStringValue:stringValue numericalValue:numericalValue];
}
NSDictionary<NSString *, id> *subParams = [FBSDKTypeUtility dictionary:eventParams objectForKey:param ofType:NSDictionary.class];
NSRange range = NSMakeRange(1, paramPath.count - 1);
NSArray<NSString *> *subParamPath = [paramPath subarrayWithRange:range];
return [self isMatchedEventParameters:subParams paramPath:subParamPath];
}
- (BOOL)isMatchedWithAsteriskParam:(NSString *)param
eventParameters:(NSDictionary<NSString *, id> *)eventParams
paramPath:(NSArray<NSString *> *)paramPath
{
param = [param substringToIndex:param.length - ASTERISK_DELIMETER.length];
NSArray<NSDictionary<NSString *, id> *> *items = [FBSDKTypeUtility dictionary:eventParams objectForKey:param ofType:NSArray.class];
if (!items.count || paramPath.count < 2) {
return NO;
}
BOOL isMatched = NO;
NSRange range = NSMakeRange(1, paramPath.count - 1);
NSArray<NSString *> *subParamPath = [paramPath subarrayWithRange:range];
for (NSDictionary<NSString *, id> *item in items) {
isMatched |= [self isMatchedEventParameters:item paramPath:subParamPath];
if (isMatched) {
break;
}
}
return isMatched;
}
- (BOOL)isMatchedWithStringValue:(nullable NSString *)stringValue
numericalValue:(nullable NSNumber *)numericalValue
{
BOOL isMatched = NO;
switch (_operator) {
case FBAEMAdvertiserRuleOperatorContains:
isMatched = stringValue && [stringValue.lowercaseString containsString:_linguisticCondition.lowercaseString]; break;
case FBAEMAdvertiserRuleOperatorNotContains:
isMatched = !(stringValue && [stringValue.lowercaseString containsString:_linguisticCondition.lowercaseString]); break;
case FBAEMAdvertiserRuleOperatorStartsWith:
isMatched = stringValue && [stringValue.lowercaseString hasPrefix:_linguisticCondition.lowercaseString]; break;
case FBAEMAdvertiserRuleOperatorCaseInsensitiveContains:
isMatched = stringValue && [stringValue.lowercaseString containsString:_linguisticCondition.lowercaseString]; break;
case FBAEMAdvertiserRuleOperatorCaseInsensitiveNotContains:
isMatched = !(stringValue && [stringValue.lowercaseString containsString:_linguisticCondition.lowercaseString]); break;
case FBAEMAdvertiserRuleOperatorCaseInsensitiveStartsWith:
isMatched = stringValue && [stringValue.lowercaseString hasPrefix:_linguisticCondition.lowercaseString]; break;
case FBAEMAdvertiserRuleOperatorRegexMatch:
isMatched = stringValue && [self isRegexMatch:stringValue]; break;
case FBAEMAdvertiserRuleOperatorEqual:
isMatched = stringValue && [stringValue.lowercaseString isEqualToString:_linguisticCondition.lowercaseString]; break;
case FBAEMAdvertiserRuleOperatorNotEqual:
isMatched = !(stringValue && [stringValue.lowercaseString isEqualToString:_linguisticCondition.lowercaseString]); break;
case FBAEMAdvertiserRuleOperatorCaseInsensitiveIsAny:
isMatched = stringValue && [self isAnyOf:_arrayCondition stringValue:stringValue ignoreCase:YES]; break;
case FBAEMAdvertiserRuleOperatorCaseInsensitiveIsNotAny:
isMatched = !(stringValue && [self isAnyOf:_arrayCondition stringValue:stringValue ignoreCase:YES]); break;
case FBAEMAdvertiserRuleOperatorIsAny:
isMatched = stringValue && [self isAnyOf:_arrayCondition stringValue:stringValue ignoreCase:NO]; break;
case FBAEMAdvertiserRuleOperatorIsNotAny:
isMatched = !(stringValue && [self isAnyOf:_arrayCondition stringValue:stringValue ignoreCase:NO]); break;
case FBAEMAdvertiserRuleOperatorLessThan:
isMatched = (numericalValue != nil) && ([numericalValue compare:_numericalCondition] == NSOrderedAscending); break;
case FBAEMAdvertiserRuleOperatorLessThanOrEqual:
isMatched = (numericalValue != nil) && ([numericalValue compare:_numericalCondition] != NSOrderedDescending); break;
case FBAEMAdvertiserRuleOperatorGreaterThan:
isMatched = (numericalValue != nil) && ([numericalValue compare:_numericalCondition] == NSOrderedDescending); break;
case FBAEMAdvertiserRuleOperatorGreaterThanOrEqual:
isMatched = (numericalValue != nil) && ([numericalValue compare:_numericalCondition] != NSOrderedAscending); break;
default: break;
}
return isMatched;
}
- (BOOL)isRegexMatch:(NSString *)stringValue
{
if (!_linguisticCondition.length) {
return NO;
}
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:_linguisticCondition options:0 error:&error];
if (!regex || error) {
return NO;
}
NSRange searchedRange = NSMakeRange(0, stringValue.length);
NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:stringValue options:0 range:searchedRange];
return matches.count > 0;
}
- (BOOL)isAnyOf:(NSArray<NSString *> *)arrayCondition
stringValue:(NSString *)stringValue
ignoreCase:(BOOL)ignoreCase
{
NSMutableSet<NSString *> *set = [NSMutableSet new];
for (NSString *item in arrayCondition) {
if (ignoreCase) {
[set addObject:item.lowercaseString];
} else {
[set addObject:item];
}
}
if (ignoreCase) {
stringValue = stringValue.lowercaseString;
}
return [set containsObject:stringValue];
}
#pragma mark - NSCoding
+ (BOOL)supportsSecureCoding
{
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
FBAEMAdvertiserRuleOperator op = [decoder decodeIntegerForKey:OPERATOR_KEY];
NSString *paramKey = [decoder decodeObjectOfClass:NSString.class forKey:PARAMKEY_KEY];
NSString *linguisticCondition = [decoder decodeObjectOfClass:NSString.class forKey:STRING_VALUE_KEY];
NSNumber *numericalCondition = [decoder decodeObjectOfClass:NSNumber.class forKey:NUMBER_VALUE_KEY];
NSArray<NSString *> *arrayCondition = [decoder decodeObjectOfClass:NSArray.class forKey:ARRAY_VALUE_KEY];
return [self initWithOperator:op
paramKey:paramKey
linguisticCondition:linguisticCondition
numericalCondition:numericalCondition
arrayCondition:arrayCondition];
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeInteger:_operator forKey:OPERATOR_KEY];
[encoder encodeObject:_paramKey forKey:PARAMKEY_KEY];
[encoder encodeObject:_linguisticCondition forKey:STRING_VALUE_KEY];
[encoder encodeObject:_numericalCondition forKey:NUMBER_VALUE_KEY];
[encoder encodeObject:_arrayCondition forKey:ARRAY_VALUE_KEY];
}
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone
{
return self;
}
#if DEBUG && FBTEST
- (void)setOperator:(FBAEMAdvertiserRuleOperator)operator
{
_operator = operator;
}
#endif
@end
#endif