Skip to content

Commit 26c5601

Browse files
authored
Merge pull request #367 from CleverTap/SDK-4010-onUserLogin-for-iOS-appends-user-string-prefix-to-both-Email-and-Name-system-user-properties
SDK-4010 - onUserLogin for iOS appends user string prefix to both Email and Name system user properties
2 parents 5cd3c15 + 17e226e commit 26c5601

13 files changed

+92
-84
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Change Log
22
All notable changes to this project will be documented in this file.
33

4+
### [Version 7.0.1](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.1) (August 22, 2024)
5+
6+
#### Fixed
7+
- Fixes a bug where some user properties were being sent with an incorrect prefix
8+
49
### [Version 7.0.0](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.0) (August 07, 2024)
510

611
#### Added

CleverTapSDK/CTConstants.h

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -226,40 +226,21 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY;
226226
#define CLTAP_INAPP_HTML_SPLIT @"\"##Vars##\""
227227
#define CLTAP_INAPP_IMAGE_INTERSTITIAL_HTML_NAME @"image_interstitial"
228228

229-
#pragma mark Constants for persisting Facebook data
230-
#define CLTAP_FB_NAME @"fbName"
231-
#define CLTAP_FB_ID @"fbId"
232-
#define CLTAP_FB_EMAIL @"fbEmail"
233-
#define CLTAP_FB_GENDER @"fbGender"
234-
#define CLTAP_FB_EDUCATION @"fbEducation"
235-
#define CLTAP_FB_EMPLOYED @"fbEmployed"
236-
#define CLTAP_FB_DOB @"fbDOB"
237-
#define CLTAP_FB_MARRIED @"fbMarried"
238-
239-
#pragma mark Constants for persisting G+ data
240-
#define CLTAP_GP_NAME @"gpName"
241-
#define CLTAP_GP_ID @"gpId"
242-
#define CLTAP_GP_EMAIL @"gpEmail"
243-
#define CLTAP_GP_GENDER @"gpGender"
244-
#define CLTAP_GP_EMPLOYED @"gpEmployed"
245-
#define CLTAP_GP_DOB @"gpDOB"
246-
#define CLTAP_GP_MARRIED @"gpMarried"
247-
248229
#pragma mark Constants for persisting system data
249230
#define CLTAP_SYS_CARRIER @"sysCarrier"
250231
#define CLTAP_SYS_CC @"sysCountryCode"
251232
#define CLTAP_SYS_TZ @"sysTZ"
252233

253-
#define CLTAP_USER_NAME @"userName"
254-
#define CLTAP_USER_EMAIL @"userEmail"
255-
#define CLTAP_USER_EDUCATION @"userEducation"
256-
#define CLTAP_USER_MARRIED @"userMarried"
257-
#define CLTAP_USER_DOB @"userDOB"
258-
#define CLTAP_USER_BIRTHDAY @"userBirthday"
259-
#define CLTAP_USER_EMPLOYED @"userEmployed"
260-
#define CLTAP_USER_GENDER @"userGender"
261-
#define CLTAP_USER_PHONE @"userPhone"
262-
#define CLTAP_USER_AGE @"userAge"
234+
#define CLTAP_USER_NAME @"Name"
235+
#define CLTAP_USER_EMAIL @"Email"
236+
#define CLTAP_USER_EDUCATION @"Education"
237+
#define CLTAP_USER_MARRIED @"Married"
238+
#define CLTAP_USER_DOB @"DOB"
239+
#define CLTAP_USER_BIRTHDAY @"Birthday"
240+
#define CLTAP_USER_EMPLOYED @"Employed"
241+
#define CLTAP_USER_GENDER @"Gender"
242+
#define CLTAP_USER_PHONE @"Phone"
243+
#define CLTAP_USER_AGE @"Age"
263244

264245
#define CLTAP_OPTOUT @"ct_optout"
265246

@@ -283,4 +264,4 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY;
283264
#pragma mark Constants for Encryption
284265
#define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel"
285266
#define CLTAP_ENCRYPTION_IV @"__CL3>3Rt#P__1V_"
286-
#define CLTAP_ENCRYPTION_PII_DATA (@[@"Identity", @"userEmail", @"userPhone", @"userName"]);
267+
#define CLTAP_ENCRYPTION_PII_DATA (@[@"Identity", @"Email", @"Phone", @"Name"]);

CleverTapSDK/CTKnownProfileFields.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ typedef enum {
77

88
@interface CTKnownProfileFields : NSObject
99

10-
+ (NSString *)getStorageValueForField:(KnownField)field;
11-
1210
+ (KnownField)getKnownFieldIfPossibleForKey:(NSString *)key;
1311

1412
@end

CleverTapSDK/CTKnownProfileFields.m

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,6 @@
44
@implementation CTKnownProfileFields {
55

66
}
7-
+ (NSString *)getStorageValueForField:(KnownField)field {
8-
switch (field) {
9-
case Name:
10-
return CLTAP_USER_NAME;
11-
case Email:
12-
return CLTAP_USER_EMAIL;
13-
case Education:
14-
return CLTAP_USER_EDUCATION;
15-
case Married:
16-
return CLTAP_USER_MARRIED;
17-
case DOB:
18-
return CLTAP_USER_DOB;
19-
case Birthday:
20-
return CLTAP_USER_BIRTHDAY;
21-
case Employed:
22-
return CLTAP_USER_EMPLOYED;
23-
case Gender:
24-
return CLTAP_USER_GENDER;
25-
case Phone:
26-
return CLTAP_USER_PHONE;
27-
case Age:
28-
return CLTAP_USER_AGE;
29-
default:
30-
return nil;
31-
}
32-
}
337

348
+ (KnownField)getKnownFieldIfPossibleForKey:(NSString *)key {
359
if ([key isEqualToString:@"Name"])

CleverTapSDK/CTLocalDataStore.m

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:(
5252
[self runOnBackgroundQueue:^{
5353
@synchronized (self->localProfileForSession) {
5454
// migrate to new persisted ct-accid-guid-userprofile
55-
[CTUserInfoMigrator migrateUserInfoFileForAccountID:self->_config.accountId deviceID:self->_deviceInfo.deviceId];
56-
55+
[CTUserInfoMigrator migrateUserInfoFileForDeviceID:self->_deviceInfo.deviceId config:self->_config];
5756
self->localProfileForSession = [self _inflateLocalProfile];
5857
for (NSString* key in [profileValues allKeys]) {
5958
[self setProfileFieldWithKey:key andValue:profileValues[key]];
@@ -548,6 +547,11 @@ - (id)getProfileFieldForKey:(NSString *)key {
548547
newValue = nil;
549548
[self removeProfileFieldForKey:key];
550549
}
550+
else if ([commandIdentifier isEqualToString:kCLTAP_COMMAND_SET] ||
551+
[commandIdentifier isEqualToString:kCLTAP_COMMAND_ADD] ||
552+
[commandIdentifier isEqualToString:kCLTAP_COMMAND_REMOVE]) {
553+
newValue = [CTProfileBuilder _constructLocalMultiValueWithOriginalValues:value forKey:key usingCommand:commandIdentifier localDataStore:self];
554+
}
551555
} else if ([newValue isKindOfClass:[NSString class]]) {
552556
// Remove the date prefix before evaluation and persisting
553557
NSString *newValueStr = (NSString *)newValue;
@@ -707,7 +711,7 @@ - (NSString *)profileFileName {
707711
}
708712

709713
- (NSMutableDictionary *)_inflateLocalProfile {
710-
NSSet *allowedClasses = [NSSet setWithObjects:[NSString class], [NSMutableDictionary class], nil];
714+
NSSet *allowedClasses = [NSSet setWithObjects:[NSArray class], [NSString class], [NSDictionary class], [NSNumber class], nil];
711715
NSMutableDictionary *_profile = (NSMutableDictionary *)[CTPreferences unarchiveFromFile:[self profileFileName] ofTypes:allowedClasses removeFile:NO];
712716
if (!_profile) {
713717
_profile = [NSMutableDictionary dictionary];
@@ -904,18 +908,16 @@ - (void)addPropertyFromStoreIfExists:(NSString *)profileKey
904908

905909
- (NSDictionary*)generateBaseProfile {
906910
NSMutableDictionary *profile = [NSMutableDictionary new];
907-
[self addPropertyFromStoreIfExists:@"Name" profile:profile storageKeys:@[CLTAP_USER_NAME, CLTAP_FB_NAME, CLTAP_GP_NAME]];
908-
[self addPropertyFromStoreIfExists:@"Gender" profile:profile storageKeys:@[CLTAP_USER_GENDER, CLTAP_FB_GENDER, CLTAP_GP_GENDER]];
909-
[self addPropertyFromStoreIfExists:@"Education" profile:profile storageKeys:@[CLTAP_USER_EDUCATION, CLTAP_FB_EDUCATION]];
910-
[self addPropertyFromStoreIfExists:@"Employed" profile:profile storageKeys:@[CLTAP_USER_EMPLOYED, CLTAP_FB_EMPLOYED, CLTAP_GP_EMPLOYED]];
911-
[self addPropertyFromStoreIfExists:@"Married" profile:profile storageKeys:@[CLTAP_USER_MARRIED, CLTAP_FB_MARRIED, CLTAP_GP_MARRIED]];
912-
[self addPropertyFromStoreIfExists:@"DOB" profile:profile storageKeys:@[CLTAP_USER_DOB, CLTAP_FB_DOB, CLTAP_GP_DOB]];
911+
[self addPropertyFromStoreIfExists:@"Name" profile:profile storageKeys:@[CLTAP_USER_NAME]];
912+
[self addPropertyFromStoreIfExists:@"Gender" profile:profile storageKeys:@[CLTAP_USER_GENDER]];
913+
[self addPropertyFromStoreIfExists:@"Education" profile:profile storageKeys:@[CLTAP_USER_EDUCATION]];
914+
[self addPropertyFromStoreIfExists:@"Employed" profile:profile storageKeys:@[CLTAP_USER_EMPLOYED]];
915+
[self addPropertyFromStoreIfExists:@"Married" profile:profile storageKeys:@[CLTAP_USER_MARRIED]];
916+
[self addPropertyFromStoreIfExists:@"DOB" profile:profile storageKeys:@[CLTAP_USER_DOB]];
913917
[self addPropertyFromStoreIfExists:@"Birthday" profile:profile storageKeys:@[CLTAP_USER_BIRTHDAY]];
914-
[self addPropertyFromStoreIfExists:@"FBID" profile:profile storageKeys:@[CLTAP_FB_ID]];
915-
[self addPropertyFromStoreIfExists:@"GPID" profile:profile storageKeys:@[CLTAP_GP_ID]];
916918
[self addPropertyFromStoreIfExists:@"Phone" profile:profile storageKeys:@[CLTAP_USER_PHONE]];
917919
[self addPropertyFromStoreIfExists:@"Age" profile:profile storageKeys:@[CLTAP_USER_AGE]];
918-
[self addPropertyFromStoreIfExists:@"Email" profile:profile storageKeys:@[CLTAP_USER_EMAIL, CLTAP_FB_EMAIL]];
920+
[self addPropertyFromStoreIfExists:@"Email" profile:profile storageKeys:@[CLTAP_USER_EMAIL]];
919921
[self addPropertyFromStoreIfExists:@"tz" profile:profile storageKeys:@[CLTAP_SYS_TZ]];
920922
[self addPropertyFromStoreIfExists:@"Carrier" profile:profile storageKeys:@[CLTAP_SYS_CARRIER]];
921923
[self addPropertyFromStoreIfExists:@"cc" profile:profile storageKeys:@[CLTAP_SYS_CC]];

CleverTapSDK/CTProfileBuilder.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@
2525

2626
+ (NSNumber *_Nullable)_getUpdatedValue:(NSNumber *_Nonnull)value forKey:(NSString *_Nonnull)key withCommand:(NSString *_Nonnull)command cachedValue:(id _Nullable)cachedValue;
2727

28+
+ (NSArray<NSString *> *_Nullable) _constructLocalMultiValueWithOriginalValues:(NSArray * _Nonnull)values forKey:(NSString * _Nonnull)key usingCommand:(NSString * _Nonnull)command localDataStore:(CTLocalDataStore* _Nonnull)dataStore;
29+
2830
@end

CleverTapSDK/CTProfileBuilder.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ + (void)build:(NSDictionary *)profile completionHandler:(void(^ _Nonnull )(NSDic
7070
// if a reserved key add to systemFields else add to customFields
7171
KnownField kf = [CTKnownProfileFields getKnownFieldIfPossibleForKey:key];
7272
if (kf != UNKNOWN) {
73-
systemFields[[CTKnownProfileFields getStorageValueForField:kf]] = value;
73+
systemFields[key] = value;
7474
} else {
7575
customFields[key] = value;
7676
}

CleverTapSDK/CTUserInfoMigrator.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
//
77

88
#import <Foundation/Foundation.h>
9+
#import "CleverTapInstanceConfig.h"
910

1011
@interface CTUserInfoMigrator : NSObject
1112

12-
+ (void)migrateUserInfoFileForAccountID:(NSString *)acc_id deviceID:(NSString *)device_id;
13+
+ (void)migrateUserInfoFileForDeviceID:(NSString *)device_id config:(CleverTapInstanceConfig *) config;
1314

1415
@end

CleverTapSDK/CTUserInfoMigrator.m

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,68 @@
11
#import "CTUserInfoMigrator.h"
22
#import "CTConstants.h"
3+
#import "CTPreferences.h"
34

45
@implementation CTUserInfoMigrator
56

6-
+ (void)migrateUserInfoFileForAccountID:(NSString *)acc_id deviceID:(NSString *)device_id {
7+
+ (void)migrateUserInfoFileForDeviceID:(NSString *)device_id config:(CleverTapInstanceConfig *) config {
8+
NSString *acc_id = config.accountId;
79
NSFileManager *fileManager = [NSFileManager defaultManager];
810
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
911
NSString *libraryPath = [paths objectAtIndex:0];
1012
NSString *userProfileWithAccountID = [NSString stringWithFormat:@"clevertap-%@-userprofile.plist", acc_id];
1113
NSString *userProfileWithAccountIDPath = [libraryPath stringByAppendingPathComponent:userProfileWithAccountID];
1214
NSString *userProfileWithAccountIDAndDeviceID = [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", acc_id, device_id];
1315
NSString *userProfileWithAccountIDAndDeviceIDPath = [libraryPath stringByAppendingPathComponent:userProfileWithAccountIDAndDeviceID];
14-
1516
NSError *error = nil;
17+
18+
// migration from 5.x and 6.x
1619
if ([fileManager fileExistsAtPath:userProfileWithAccountIDPath]) {
20+
// unarchive, remove user prefix, copy to new deviceid path
21+
NSSet *allowedClasses = [NSSet setWithObjects:[NSArray class], [NSString class], [NSDictionary class], [NSNumber class], nil];
22+
NSMutableDictionary *userProfile = [CTPreferences unarchiveFromFile:userProfileWithAccountID ofTypes:allowedClasses removeFile:NO];
23+
// remove prefixes from only the system fields
24+
[self removeUserPrefixInDictionary:userProfile];
25+
// store this new clean file at userProfileWithAccountIDAndDeviceIDPath
26+
[CTPreferences archiveObject:userProfile forFileName:userProfileWithAccountID config:config];
1727
[fileManager copyItemAtPath:userProfileWithAccountIDPath toPath:userProfileWithAccountIDAndDeviceIDPath error:&error];
1828
CleverTapLogStaticInternal(@"[CTUserInfo]: Local file copied successfully to %@", userProfileWithAccountIDAndDeviceIDPath);
1929
[fileManager removeItemAtPath:userProfileWithAccountIDPath error:&error];
2030
return;
31+
}
32+
33+
// migration from 7.0.0
34+
else if ([fileManager fileExistsAtPath:userProfileWithAccountIDAndDeviceIDPath]) {
35+
// unarchive the file at this path
36+
NSSet *allowedClasses = [NSSet setWithObjects:[NSArray class], [NSString class], [NSDictionary class], [NSNumber class], nil];
37+
NSMutableDictionary *userProfile = [CTPreferences unarchiveFromFile:userProfileWithAccountIDAndDeviceID ofTypes:allowedClasses removeFile:NO];
38+
// remove prefixes from only the system fields
39+
[self removeUserPrefixInDictionary:userProfile];
40+
// update the new file at userProfileWithAccountIDAndDeviceIDPath
41+
[CTPreferences archiveObject:userProfile forFileName:userProfileWithAccountIDAndDeviceID config:config];
42+
return;
2143
} else {
2244
CleverTapLogStaticInternal(@"[CTUserInfo]: Failed to copy local file: %@", error.localizedDescription);
2345
}
2446
}
2547

48+
+ (void)removeUserPrefixInDictionary:(NSMutableDictionary *)dictionary {
49+
// List of known profile fields
50+
NSArray *knownProfileFields = @[
51+
@"userName", @"userEmail", @"userEducation", @"userMarried",
52+
@"userDOB", @"userBirthday", @"userEmployed", @"userGender",
53+
@"userPhone", @"userAge"
54+
];
55+
56+
// Iterate through the original dictionary's keys
57+
for (NSString *key in [dictionary allKeys]) {
58+
if ([knownProfileFields containsObject:key]) {
59+
NSString *newKey = [key substringFromIndex:[@"user" length]];
60+
61+
// Update the dictionary with the new key
62+
id value = dictionary[key];
63+
[dictionary removeObjectForKey:key];
64+
[dictionary setObject:value forKey:newKey];
65+
}
66+
}
67+
}
2668
@end

CleverTapSDK/CleverTap.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,16 +373,16 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification;
373373
@method
374374
375375
@abstract
376-
Creates a separate and distinct user profile identified by one or more of Identity, Email, FBID or GPID values,
376+
Creates a separate and distinct user profile identified by one or more of Identity or Email values,
377377
and populated with the key-values included in the properties dictionary.
378378
379379
@discussion
380380
If your app is used by multiple users, you can use this method to assign them each a unique profile to track them separately.
381381
382-
If instead you wish to assign multiple Identity, Email, FBID and/or GPID values to the same user profile,
382+
If instead you wish to assign multiple Identity and/or Email values to the same user profile,
383383
use profilePush rather than this method.
384384
385-
If none of Identity, Email, FBID or GPID is included in the properties dictionary,
385+
If none of Identity or Email is included in the properties dictionary,
386386
all properties values will be associated with the current user profile.
387387
388388
When initially installed on this device, your app is assigned an "anonymous" profile.
@@ -412,10 +412,10 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification;
412412
@discussion
413413
If your app is used by multiple users, you can use this method to assign them each a unique profile to track them separately.
414414
415-
If instead you wish to assign multiple Identity, Email, FBID and/or GPID values to the same user profile,
415+
If instead you wish to assign multiple Identity and/or Email values to the same user profile,
416416
use profilePush rather than this method.
417417
418-
If none of Identity, Email, FBID or GPID is included in the properties dictionary,
418+
If none of Identity or Email is included in the properties dictionary,
419419
all properties values will be associated with the current user profile.
420420
421421
When initially installed on this device, your app is assigned an "anonymous" profile.

CleverTapSDK/CleverTapBuildInfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
#define WR_SDK_REVISION @"70000"
1+
#define WR_SDK_REVISION @"70001"

CleverTapSDKTests/CTUserInfoMigratorTest.m

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
#import <XCTest/XCTest.h>
1010
#import "CTUserInfoMigrator.h"
1111
#import "XCTestCase+XCTestCase_Tests.h"
12+
#import "CleverTapInstanceConfig.h"
1213

1314
@interface CTUserInfoMigratorTest : XCTestCase
1415

1516
@property (nonatomic, strong) NSFileManager *fileManager;
1617
@property (nonatomic, strong) NSString *libraryPath;
18+
@property (nonatomic, strong) CleverTapInstanceConfig *config;
1719

1820
@end
1921

@@ -27,6 +29,7 @@ - (void)setUp {
2729
// Get the path to the Library directory
2830
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
2931
self.libraryPath = [paths objectAtIndex:0];
32+
self.config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccID" accountToken:@"testToken" accountRegion:@"testRegion"];
3033
}
3134

3235
- (void)tearDown {
@@ -52,7 +55,7 @@ - (void)testMigrateUserInfoFileForAccountID_WhenOldFileExists_ShouldCopyToNewLoc
5255
[self.fileManager createFileAtPath:oldFilePath contents:[@"old content" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil];
5356

5457
// Call the method to migrate the user info file
55-
[CTUserInfoMigrator migrateUserInfoFileForAccountID:acc_id deviceID:device_id];
58+
[CTUserInfoMigrator migrateUserInfoFileForDeviceID:device_id config:self.config];
5659

5760
// Check that the old file has been copied to the new location
5861
NSString *newFileName = [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", acc_id, device_id];
@@ -77,7 +80,7 @@ - (void)testMigrateUserInfoFileForAccountID_WhenNewFileExists_ShouldNotCopyAndDe
7780
[self.fileManager createFileAtPath:newFilePath contents:[@"new content" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil];
7881

7982
// Call the method to migrate the user info file
80-
[CTUserInfoMigrator migrateUserInfoFileForAccountID:acc_id deviceID:device_id];
83+
[CTUserInfoMigrator migrateUserInfoFileForDeviceID:device_id config:self.config];
8184

8285
// Check that the new file still exists
8386
XCTAssertTrue([self.fileManager fileExistsAtPath:newFilePath], @"New plist file should exist");
@@ -96,7 +99,7 @@ - (void)testMigrateUserInfoFileForAccountID_WhenOldFileDoesNotExist_ShouldNotCre
9699
[self.fileManager removeItemAtPath:oldFilePath error:nil];
97100

98101
// Call the method to migrate the user info file
99-
[CTUserInfoMigrator migrateUserInfoFileForAccountID:acc_id deviceID:device_id];
102+
[CTUserInfoMigrator migrateUserInfoFileForDeviceID:device_id config:self.config];
100103

101104
// Check that the new file does not exist
102105
NSString *newFileName = [NSString stringWithFormat:@"clevertap-%@-%@-userprofile.plist", acc_id, device_id];

sdk-version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7.0.0
1+
7.0.1

0 commit comments

Comments
 (0)