diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml deleted file mode 100644 index cb54aad..0000000 --- a/.github/workflows/sca-scan.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Source Composition Analysis Scan -on: - pull_request: - types: [opened, synchronize, reopened] -jobs: - security-sca: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: snyk/actions/setup@master - - name: Run Snyk to check for vulnerabilities - run: snyk test --all-projects --fail-on=all - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ee327b6..be8d1ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +### Version: 3.15.0 +#### Date: Jun-16-2025 + +##### Enhancement: + - Added support for Global Fields + +### Version: 3.14.0 +#### Date: Oct-04-2024 + +##### Enhancement: + - Early access header support + - Fetch asset by query + +### Version: 3.13.0 +#### Date: Aug-23-2024 + +##### Enhancement: + - Added support for Taxonomy + ### Version: 3.12.3 #### Date: May-16-2023 diff --git a/Contentstack.podspec b/Contentstack.podspec index 033315c..a382b2e 100644 --- a/Contentstack.podspec +++ b/Contentstack.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Contentstack' -s.version = '3.14.0' +s.version = '3.15.0' s.summary = 'Contentstack is a headless CMS with an API-first approach that puts content at the centre.' s.description = <<-DESC diff --git a/Contentstack.xcodeproj/project.pbxproj b/Contentstack.xcodeproj/project.pbxproj index 757dc08..babb0d5 100644 --- a/Contentstack.xcodeproj/project.pbxproj +++ b/Contentstack.xcodeproj/project.pbxproj @@ -107,6 +107,9 @@ 64B3EA282BF7C4AF009E0F38 /* libThirdPartyExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 238E841B1B4FE29A00BFDB32 /* libThirdPartyExtension.a */; }; 64F5220E2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 64F5220D2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy */; }; 64F5220F2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 64F5220D2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy */; }; + 678803D22DED82A100E4AA75 /* GlobalField.h in Headers */ = {isa = PBXBuildFile; fileRef = 678803D12DED829800E4AA75 /* GlobalField.h */; }; + 678803D42DED82AF00E4AA75 /* GlobalField.m in Sources */ = {isa = PBXBuildFile; fileRef = 678803D32DED82AC00E4AA75 /* GlobalField.m */; }; + 678803D62DEDB24800E4AA75 /* GlobalFieldTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 678803D52DEDB23800E4AA75 /* GlobalFieldTest.m */; }; E653FF942F28495541E9B22B /* libPods-Contentstack.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4514C6AB47DF26BA926C681A /* libPods-Contentstack.a */; }; /* End PBXBuildFile section */ @@ -244,6 +247,9 @@ 606DDA20A6F0593F40494FED /* Pods-ContentstackTest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContentstackTest.release.xcconfig"; path = "Target Support Files/Pods-ContentstackTest/Pods-ContentstackTest.release.xcconfig"; sourceTree = ""; }; 609D1D72B25D2FBE4E26FA70 /* Pods-ContentstackTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContentstackTest.debug.xcconfig"; path = "Target Support Files/Pods-ContentstackTest/Pods-ContentstackTest.debug.xcconfig"; sourceTree = ""; }; 64F5220D2BF5C76E00AE6E0F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 678803D12DED829800E4AA75 /* GlobalField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GlobalField.h; sourceTree = ""; }; + 678803D32DED82AC00E4AA75 /* GlobalField.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GlobalField.m; sourceTree = ""; }; + 678803D52DEDB23800E4AA75 /* GlobalFieldTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GlobalFieldTest.m; sourceTree = ""; }; 7EB1C6B5FF6A451CEB50B3A4 /* libPods-ContentstackTest.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ContentstackTest.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 8B7BE798B2EEFA3CC2763E3F /* Pods-Contentstack.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Contentstack.release.xcconfig"; path = "Target Support Files/Pods-Contentstack/Pods-Contentstack.release.xcconfig"; sourceTree = ""; }; 9980728A1E1BDC5000524FD3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -306,6 +312,8 @@ 230B38C11C16E98B00444A14 /* Contentstack */ = { isa = PBXGroup; children = ( + 678803D12DED829800E4AA75 /* GlobalField.h */, + 678803D32DED82AC00E4AA75 /* GlobalField.m */, 0F9C0FA4221ADAC80091205A /* Asset.h */, 0F9C0FA3221ADAC80091205A /* Asset.m */, 0F9C0F98221ADAC70091205A /* AssetLibrary.h */, @@ -432,6 +440,7 @@ children = ( 0F41A91025C7CC9C007EF2DA /* ContentstackTest.m */, 0F41A91125C7CC9C007EF2DA /* SyncTest.m */, + 678803D52DEDB23800E4AA75 /* GlobalFieldTest.m */, 23A53F591E277CD3001DBE35 /* Info.plist */, 473AFDAF2CA22233002D331D /* config.json */, ); @@ -534,6 +543,7 @@ 230B39011C16EB8D00444A14 /* ISO8601DateFormatter.h in Headers */, 23A53F401E276C83001DBE35 /* CSIOInternalHeaders.h in Headers */, 0F9C0FB1221ADAC90091205A /* AssetLibrary.h in Headers */, + 678803D22DED82A100E4AA75 /* GlobalField.h in Headers */, 0FD6BAEF29CD6E73001A0930 /* CSURLSessionDelegate.h in Headers */, 0F9C0FB6221ADAC90091205A /* Config.h in Headers */, 0F9C0FC2221ADAC90091205A /* Query.h in Headers */, @@ -777,6 +787,7 @@ 230B38FA1C16EB8000444A14 /* MMGenerator.h in Sources */, 0FEAEF2B2361A18600985FF9 /* CSURLSessionManager.m in Sources */, 230B38FB1C16EB8000444A14 /* MMHTMLParser.h in Sources */, + 678803D42DED82AF00E4AA75 /* GlobalField.m in Sources */, 230B38FC1C16EB8000444A14 /* MMMarkdown-Prefix.pch in Sources */, 0F9C0FC4221ADAC90091205A /* Config.m in Sources */, 230B38FD1C16EB8000444A14 /* MMMarkdown.h in Sources */, @@ -825,6 +836,7 @@ files = ( 0F41A91425C7CC9C007EF2DA /* ContentstackTest.m in Sources */, 0F41A91525C7CC9C007EF2DA /* SyncTest.m in Sources */, + 678803D62DEDB24800E4AA75 /* GlobalFieldTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Contentstack/GlobalField.h b/Contentstack/GlobalField.h new file mode 100644 index 0000000..cc2e156 --- /dev/null +++ b/Contentstack/GlobalField.h @@ -0,0 +1,210 @@ +// +// GlobalField.h +// Contentstack +// +// Created by Reeshika Hosmani on 02/06/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import + + +BUILT_ASSUME_NONNULL_BEGIN + +@interface GlobalField : NSObject + +- (instancetype)init UNAVAILABLE_ATTRIBUTE; +//- (instancetype)initWithStack:(Stack*)stack; +//- (instancetype)initWithStack:(Stack*)stack withName:(NSString*)globalFieldName; + +/**---------------------------------------------------------------------------------------- + * @name Properties + *----------------------------------------------------------------------------------------- + */ + +/** + * Readonly property to check title of GlobalField + */ +@property (nonatomic, copy, readonly) NSString *title; + +/** + * Readonly property to check version + */ +@property (nonatomic, assign, readonly) unsigned int version; + +/** + * Readonly property to check description + */ +@property (nonatomic, copy, readonly) NSString *Description; + +/** + * Readonly property to check value of global field's uid + */ +@property (nonatomic, copy, readonly) NSString *uid; + +/** + * Readonly property to check createdAt of global field + */ +@property (nonatomic, copy, readonly) NSDate *createdAt; + +/** + * Readonly property to check updatedAt of global field + */ +@property (nonatomic, copy, readonly) NSDate *updatedAt; + +/** + * Readonly property to check the branch + */ +@property (nonatomic, copy, readonly) NSString *branch; + +/** + * Readonly property to get schema of global field + */ +@property (nonatomic, copy, readonly) NSArray *schema; + +/** + * Readonly property to check if global field is inbuilt class + */ +@property (nonatomic, assign, readonly) BOOL inbuiltClass; + +/** + * Readonly property to check if global field maintains revisions + */ +@property (nonatomic, assign, readonly) BOOL maintainRevisions; + +/** + * Readonly property to get last activity of global field + */ +@property (nonatomic, copy, readonly) NSDictionary *lastActivity; + +/** + * Readonly property to get all properties of global field + */ +@property (nonatomic, copy, readonly) NSDictionary *properties; + +/** + * property to assign cache policy like CACHE_THEN_NETWORK, NETWORK_ELSE_CACHE, NETWORK_ONLY, etc. + */ +@property (nonatomic, assign) CachePolicy cachePolicy; +//MARK: - Manually set headers +/**--------------------------------------------------------------------------------------- + * @name Manually set headers + * --------------------------------------------------------------------------------------- + */ + +/** +Set a header for GlobalField. + + //Obj-C + [contentTypeObj setHeader:@"MyValue" forKey:@"My-Custom-Header"]; + //Swift + contentTypeObj.setHeader("MyValue", forKey: "My-Custom-Header") +@param headerValue The header key +@param headerKey The header value +*/ +- (void)setHeader:(NSString *)headerValue forKey:(NSString *)headerKey; +/** +Set a header for GlobalField. + + //Obj-C + [contentTypeObj addHeadersWithDictionary:@{@"My-Custom-Header": @"MyValue"}]; + + //Swift + contentTypeObj.addHeadersWithDictionary(["My-Custom-Header":"MyValue"]) + + + @param headers The headers as dictionary which needs to be added to the application + */ +- (void)addHeadersWithDictionary:(NSDictionary *)headers; +/** +Removes a header from this GlobalField. + + //Obj-C + [contentTypeObj removeHeaderForKey:@"My-Custom-Header"]; + + //Swift + contentTypeObj.removeHeaderForKey("My-Custom-Header") + + @param headerKey The header key that needs to be removed + */ +- (void)removeHeaderForKey:(NSString *)headerKey; + +/** + Retrieve the branch of the globalField. + + //Obj-C + Stack *stack = [Contentstack stackWithAPIKey:@"API_KEY" accessToken:@"DELIVERY_TOKEN" environmentName:@"ENVIRONMENT"]; + GlobalField *globalField = [stack GlobalField]; + [globalField includeBranch]; + + //Swift + var stack:Stack = Contentstack.stackWithAPIKey("API_KEY", accessToken:"DELIVERY_TOKEN", environmentName:@"ENVIRONMENT") + var globalField:Asset = stack.GlobalField() + globalField.includeBranch() + + */ +-(void)includeBranch; + +/** + Retrieve the schema of globalField. + + //Obj-C + Stack *stack = [Contentstack stackWithAPIKey:@"API_KEY" accessToken:@"DELIVERY_TOKEN" environmentName:@"ENVIRONMENT"]; + GlobalField *globalField = [stack GlobalField]; + [globalField includeGlobalFieldSchema]; + + //Swift + var stack:Stack = Contentstack.stackWithAPIKey("API_KEY", accessToken:"DELIVERY_TOKEN", environmentName:@"ENVIRONMENT") + var globalField:Asset = stack.GlobalField() + globalField.includeGlobalFieldSchema() + + */ +-(void)includeGlobalFieldSchema; + + +/** + Fetches an asset asynchronously provided asset UID + + //Obj-C + Stack *stack = [Contentstack stackWithAPIKey:@"API_KEY" accessToken:@"DELIVERY_TOKEN" environmentName:@"ENVIRONMENT"]; + //'ASSET_UID' is uid of an asset + Asset* assetObj = [stack assetWithUID:@"ASSET_UID"]; + [assetObj fetch:^(ResponseType type, NSError *error) { + //error if exists then use 'error' object for details + }]; + + //Swift + var stack:Stack = Contentstack.stackWithAPIKey("API_KEY", accessToken:"DELIVERY_TOKEN", environmentName:@"ENVIRONMENT") + //'ASSET_UID' is uid of an asset + var assetObj:Asset = stack.assetWithUID("ASSET_UID") + assetObj.fetch { (responseType, error!) -> Void in + //error if exists then use 'error' object for details + } + + @param callback Completion block with params NSError + */ + +- (void)fetch:(void(^)(ResponseType type, NSError * BUILT_NULLABLE_P error))callback; + +/** +This method provides all the GlobalFields present in the stack. + + //Obj-C + [globalField fetchAll:^(ResponseType type,NSArray * BUILT_NULLABLE_P result,NSError * BUILT_NULLABLE_P error { + //error for any error description + //result for reponse data + }]; + + //Swift + globalField.fetchAll { (type, result, error) -> Void in + //error for any error description + //result for reponse data + } + +@param completionBlock block to be called once operation is done. The result data contains all the globalFields. + */ +- (void)fetchAll:(void (^) (ResponseType type,NSArray * BUILT_NULLABLE_P result,NSError * BUILT_NULLABLE_P error))completionBlock; +@end + +BUILT_ASSUME_NONNULL_END diff --git a/Contentstack/GlobalField.m b/Contentstack/GlobalField.m new file mode 100644 index 0000000..1a512da --- /dev/null +++ b/Contentstack/GlobalField.m @@ -0,0 +1,223 @@ +// +// GlobalField.m +// Contentstack +// +// Created by Reeshika Hosmani on 02/06/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import "GlobalField.h" +#import "CSIOInternalHeaders.h" +#import "CSIOConstants.h" +#import "CSIOCoreHTTPNetworking.h" +#import "CSIOAPIURLs.h" +#import "NSObject+Extensions.h" +#import "Stack.h" + +@interface GlobalField () +@property (nonatomic, strong, getter=stack) Stack *csStack; +@property (nonatomic, strong) NSMutableDictionary *postParamDictionary; +@property (nonatomic, strong) NSMutableDictionary *headers; +@property (nonatomic, strong) NSURLSessionDataTask *requestOperation; +@property (nonatomic, strong) NSMutableDictionary *objectProperties; + + +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSString *uid; +@property (nonatomic, copy) NSString *Description; +@property (nonatomic, copy) NSDate *createdAt; +@property (nonatomic, copy) NSDate *updatedAt; +@property (nonatomic, copy) id responseJSON; +@property (nonatomic, assign) unsigned int version; +@property (nonatomic, copy) NSString *branch; +@property (nonatomic, copy) NSArray *schema; +@property (nonatomic, assign) BOOL inbuiltClass; +@property (nonatomic, assign) BOOL maintainRevisions; +@property (nonatomic, copy) NSDictionary *lastActivity; + +@end + +@implementation GlobalField + +-(instancetype)initWithStack:(Stack*)stack { + if (self = [super init]) { + _csStack = stack; + _postParamDictionary = [NSMutableDictionary dictionary]; + _headers = [NSMutableDictionary dictionary]; + _objectProperties = [NSMutableDictionary dictionary]; + } + return self; +} + +-(instancetype)initWithStack:(Stack*)stack withName:(NSString*)globalFieldName { + if (self = [self initWithStack:stack]) { + if(globalFieldName && globalFieldName.length) { + _uid = [globalFieldName copy]; + } + } + return self; +} + +//MARK: - Headers +- (void)setHeader:(NSString *)headerValue forKey:(NSString *)headerKey { + [self.headers setObject:headerValue forKey:headerKey]; +} + +- (void)addHeadersWithDictionary:(NSDictionary *)headers { + [headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [self.headers setObject:obj forKey:key]; + }]; +} + +- (void)removeHeaderForKey:(NSString *)headerKey { + if (self.headers[headerKey]) { + [self.headers removeObjectForKey:headerKey]; + } +} + +//MARK: - Configure +-(void)configureWithDictionary:(NSDictionary *)dictionary { + if (!dictionary) return; + + [[dictionary allKeys] enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL * _Nonnull stop) { + [self.objectProperties setObject:dictionary[key] forKey:key]; + }]; + + [[self.objectProperties allKeys] enumerateObjectsUsingBlock:^(NSString *objKey, NSUInteger idx, BOOL * _Nonnull stop) { + if (![[dictionary allKeys] containsObject:objKey]) { + [self.objectProperties removeObjectForKey:objKey]; + } + }]; + + if (self.objectProperties[kCSIO_UID]) { + self.uid = self.objectProperties[kCSIO_UID]; + } + + if (self.objectProperties[kCSIO_Title]) { + self.title = self.objectProperties[kCSIO_Title]; + } + + if (self.objectProperties[kCSIO_description]) { + self.Description = self.objectProperties[kCSIO_description]; + } + + if (self.objectProperties[kCSIO_version]) { + self.version = [self.objectProperties[kCSIO_version] intValue]; + } + + if (self.objectProperties[kCSIO_branch]) { + self.branch = self.objectProperties[kCSIO_branch]; + } + + if (self.objectProperties[kCSIO_CreatedAt]) { + self.createdAt = [_csStack.commonDateFormatter dateFromString:self.objectProperties[kCSIO_CreatedAt]]; + } + + if (self.objectProperties[kCSIO_UpdatedAt]) { + self.updatedAt = [_csStack.commonDateFormatter dateFromString:self.objectProperties[kCSIO_UpdatedAt]]; + } + + if (self.objectProperties[kCSIO_schema]) { + self.schema = self.objectProperties[kCSIO_schema]; + } + + if (self.objectProperties[kCSIO_inbuilt_class]) { + self.inbuiltClass = [self.objectProperties[kCSIO_inbuilt_class] boolValue]; + } + + if (self.objectProperties[kCSIO_maintain_revisions]) { + self.maintainRevisions = [self.objectProperties[kCSIO_maintain_revisions] boolValue]; + } + + if (self.objectProperties[kCSIO_last_activity]) { + self.lastActivity = self.objectProperties[kCSIO_last_activity]; + } +} + +//MARK: - Include Methods +-(void)includeBranch { + [self.postParamDictionary setObject:@"true" forKey:@"include_branch"]; +} + +-(void)includeGlobalFieldSchema { + [self.postParamDictionary setObject:@"true" forKey:@"include_global_field_schema"]; +} + +//MARK: getResult - +- (NSArray *)getResult{ + if ([self.objectProperties objectForKey:kCSIO_globalfields] && [[self.objectProperties objectForKey:kCSIO_globalfields] isKindOfClass:[NSArray class]]) { + NSArray *objectsArray = (NSArray*)[self.objectProperties objectForKey:kCSIO_globalfields]; + NSMutableArray *gfObjects = [NSMutableArray array]; + // if condition is fix for value of "entries" key ie.array inside array in response JSON + if (objectsArray.firstObject && [objectsArray.firstObject isKindOfClass:[NSArray class]]) { + [objectsArray enumerateObjectsUsingBlock:^(NSArray *obj, NSUInteger idx, BOOL * _Nonnull stop) { + [obj enumerateObjectsUsingBlock:^(NSDictionary *objDict, NSUInteger idx, BOOL * _Nonnull stop) { + GlobalField *globalField = [_csStack globalField]; + [globalField configureWithDictionary:objDict]; + [gfObjects addObject:globalField]; + }]; + }]; + } else { + [objectsArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSDictionary *objDict = (NSDictionary *)obj; + GlobalField *globalField = [_csStack globalField]; + [globalField configureWithDictionary:objDict]; + [gfObjects addObject:globalField]; + }]; + } + return gfObjects; + } else { + return nil; + } +} + +//MARK: - Fetch Methods +- (void)fetch:(void(^)(ResponseType type, NSError *BUILT_NULLABLE_P error))callback { + + [self.postParamDictionary setObject:_csStack.environment forKey:kCSIO_Environment]; + + NSMutableDictionary *paramDictionary = [NSMutableDictionary dictionaryWithDictionary:self.postParamDictionary]; + + NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithDictionary:_csStack.stackHeaders]; + [headers addEntriesFromDictionary:self.headers]; + + NSString *path = [CSIOAPIURLs fetchGlobalFieldWithVersion:self.uid withVersion:_csStack.version]; + + self.requestOperation = [_csStack.network requestForStack:_csStack withURLPath:path requestType:CSIOCoreNetworkingRequestTypeGET params:paramDictionary additionalHeaders:headers cachePolicy:self.cachePolicy completion:^(ResponseType responseType, id responseJSON, NSError *error) { + if (error) { + callback(responseType, error); + } else { + self.responseJSON = responseJSON; + [self configureWithDictionary:[responseJSON objectForKey:kCSIO_globalfield]]; + callback(responseType, nil); + } + }]; +} + + +//MARK: Fetch +- (void)fetchAll:(void (^) (ResponseType type,NSArray * BUILT_NULLABLE_P result,NSError * BUILT_NULLABLE_P error))completionBlock { + + + [self.postParamDictionary setObject:_csStack.environment forKey:kCSIO_Environment]; + + NSMutableDictionary *paramDictionary = [NSMutableDictionary dictionaryWithDictionary:self.postParamDictionary]; + + NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithDictionary:_csStack.stackHeaders]; + [headers addEntriesFromDictionary:self.headers]; + + NSString *path = [CSIOAPIURLs findGlobalFieldsWithVersion:_csStack.version]; + + self.requestOperation = [_csStack.network requestForStack:_csStack withURLPath:path requestType:CSIOCoreNetworkingRequestTypeGET params:paramDictionary additionalHeaders:headers cachePolicy:_cachePolicy completion:^(ResponseType responseType, id responseJSON, NSError *error) { + if (error) { + completionBlock(responseType, nil, error); + }else { + self.objectProperties = responseJSON; + NSArray *allGlobalFields = [self getResult]; + completionBlock(responseType, allGlobalFields, nil); + } + }]; +} + +@end + diff --git a/Contentstack/Stack.h b/Contentstack/Stack.h index 08fdad6..dbf3b23 100644 --- a/Contentstack/Stack.h +++ b/Contentstack/Stack.h @@ -15,6 +15,7 @@ @class AssetLibrary; @class Asset; @class SyncStack; +@class GlobalField; BUILT_ASSUME_NONNULL_BEGIN @@ -68,6 +69,10 @@ BUILT_ASSUME_NONNULL_BEGIN - (Taxonomy *)taxonomy; +- (GlobalField *)globalField; + +- (GlobalField *)globalFieldWithName:(NSString *)globalFieldName; + - (NSDictionary *)getHeaders; //MARK: - Manually set headers diff --git a/Contentstack/Stack.m b/Contentstack/Stack.m index edb1163..42faff2 100644 --- a/Contentstack/Stack.m +++ b/Contentstack/Stack.m @@ -14,6 +14,7 @@ #import "Taxonomy.h" #import "CSIOAPIURLs.h" #import "NSObject+Extensions.h" +#import "GlobalField.h" @interface Stack () @property (nonatomic, copy) NSString *apiKey; @@ -98,6 +99,16 @@ -(Taxonomy*)taxonomy { return taxonomy; } +-(GlobalField*)globalField { + GlobalField *globalField = [[GlobalField alloc] initWithStack:self]; + return globalField; +} + +-(GlobalField*)globalFieldWithName:(NSString*)globalFieldName { + GlobalField *globalField = [[GlobalField alloc] initWithStack:self withName:globalFieldName]; + return globalField; +} + //MARK: - Asset -(Asset *)asset { diff --git a/ContentstackInternal/CSIOAPIURLs.h b/ContentstackInternal/CSIOAPIURLs.h index 227c0e7..7bef71d 100644 --- a/ContentstackInternal/CSIOAPIURLs.h +++ b/ContentstackInternal/CSIOAPIURLs.h @@ -34,6 +34,11 @@ //AssetLibrary + (NSString *)fetchAssetLibraryWithVersion:(NSString*)version; +// GlobalField ++(NSString*)fetchGlobalFieldWithVersion:(NSString*) globalFieldUID withVersion:(NSString*)version; + ++(NSString*)findGlobalFieldsWithVersion:(NSString*)version; + //sync +(NSString *)syncWithVersion:(NSString*)version; @end diff --git a/ContentstackInternal/CSIOAPIURLs.m b/ContentstackInternal/CSIOAPIURLs.m index 987fba5..441f8a5 100644 --- a/ContentstackInternal/CSIOAPIURLs.m +++ b/ContentstackInternal/CSIOAPIURLs.m @@ -31,6 +31,11 @@ @implementation CSIOAPIURLs static NSString *syncData = @"/%@/stacks/sync"; // Taxonomy static NSString *fetchTaxonomyWithVersion = @"/%@/taxonomies/entries"; +//GlobalField +static NSString *fetchGlobalFieldWithVersion = @"/%@/global_fields/%@"; +//GlobalFields +static NSString *findGlobalFieldsWithVersion = @"/%@/global_fields"; + //MARK: Methods - @@ -77,4 +82,12 @@ + (NSString *)fetchAssetLibraryWithVersion:(NSString*)version{ +(NSString *)syncWithVersion:(NSString*)version { return [NSString stringWithFormat:syncData,version]; } + ++(NSString*)fetchGlobalFieldWithVersion:(NSString*)globalFieldUID withVersion:(NSString*)version { + return [NSString stringWithFormat:fetchGlobalFieldWithVersion, version, globalFieldUID]; +} + ++(NSString*)findGlobalFieldsWithVersion:(NSString*)version { + return [NSString stringWithFormat:findGlobalFieldsWithVersion, version]; +} @end diff --git a/ContentstackInternal/CSIOConstants.h b/ContentstackInternal/CSIOConstants.h index ac2ca73..69acd46 100644 --- a/ContentstackInternal/CSIOConstants.h +++ b/ContentstackInternal/CSIOConstants.h @@ -129,4 +129,14 @@ FOUNDATION_EXPORT NSString *const kCSIO_Content_Type; FOUNDATION_EXPORT NSString *const kCSIO_Type; FOUNDATION_EXPORT NSString *const kCSIO_Items; +//GlobalFields +FOUNDATION_EXPORT NSString *const kCSIO_version; +FOUNDATION_EXPORT NSString *const kCSIO_branch ; +FOUNDATION_EXPORT NSString *const kCSIO_schema; +FOUNDATION_EXPORT NSString *const kCSIO_maintain_revisions; +FOUNDATION_EXPORT NSString *const kCSIO_inbuilt_class ; +FOUNDATION_EXPORT NSString *const kCSIO_last_activity; +FOUNDATION_EXPORT NSString *const kCSIO_description; +FOUNDATION_EXPORT NSString *const kCSIO_globalfields; +FOUNDATION_EXPORT NSString *const kCSIO_globalfield; @end diff --git a/ContentstackInternal/CSIOConstants.m b/ContentstackInternal/CSIOConstants.m index 8023f17..8f37ac8 100644 --- a/ContentstackInternal/CSIOConstants.m +++ b/ContentstackInternal/CSIOConstants.m @@ -137,4 +137,14 @@ @implementation CSIOConstants NSString *const kCSIO_Items = @"items"; NSString *const kCSIO_Type = @"type"; +//GlobalFields +NSString *const kCSIO_globalfield = @"global_field"; +NSString *const kCSIO_globalfields= @"global_fields"; +NSString *const kCSIO_version = @"_version"; +NSString *const kCSIO_branch = @"_branch"; +NSString *const kCSIO_schema = @"schema"; +NSString *const kCSIO_maintain_revisions = @"maintain_revisions"; +NSString *const kCSIO_inbuilt_class = @"inbuilt_class"; +NSString *const kCSIO_last_activity = @"last_activity"; +NSString *const kCSIO_description = @"description"; @end diff --git a/ContentstackInternal/CSIOInternalHeaders.h b/ContentstackInternal/CSIOInternalHeaders.h index 458a057..6df6fca 100644 --- a/ContentstackInternal/CSIOInternalHeaders.h +++ b/ContentstackInternal/CSIOInternalHeaders.h @@ -23,6 +23,7 @@ #import "AssetLibrary.h" #import "Group.h" #import "CSError.h" +#import "GlobalField.h" //MARK: Extensions - @@ -129,3 +130,9 @@ +(instancetype)error:(NSDictionary*) errorDictionary statusCode:(NSInteger) statusCode; @end + +@interface GlobalField() +- (instancetype)initWithStack:(Stack*)stack; +- (instancetype)initWithStack:(Stack*)stack withName:(NSString*)globalFieldName; + +@end diff --git a/ContentstackTest/ContentstackTest.m b/ContentstackTest/ContentstackTest.m index ae88d95..7214b2d 100644 --- a/ContentstackTest/ContentstackTest.m +++ b/ContentstackTest/ContentstackTest.m @@ -403,7 +403,7 @@ -(void)testKVOEntryProperties { if (error) { XCTFail(@"~ ERR: %@, Message = %@", error.userInfo, error.description); }else { - NSLog(@"entry : %@", _kvoEntry); +// NSLog(@"entry : %@", _kvoEntry); } [expectation fulfill]; }]; @@ -572,7 +572,7 @@ - (void)testFetchMarkDownString { } } - NSLog(@"MarkDown Values = %@", markdownString); +// NSLog(@"MarkDown Values = %@", markdownString); } [expectation fulfill]; }]; @@ -593,7 +593,7 @@ - (void)testFetchMarkDownArray { }else { [self checkLanguageStatus:entry]; - NSLog(@"result %@", entry.locale); +// NSLog(@"result %@", entry.locale); NSArray *markdownArray = [entry HTMLArrayForMarkdownKey:@"markdown_multiple"]; [markdownArray enumerateObjectsUsingBlock:^(NSString *markdownString, NSUInteger idx, BOOL * _Nonnull stop) { @@ -610,7 +610,7 @@ - (void)testFetchMarkDownArray { } }]; - NSLog(@"MarkDown Array = %@, Total Objects = %lu",markdownArray, (unsigned long)[markdownArray count]); +// NSLog(@"MarkDown Array = %@, Total Objects = %lu",markdownArray, (unsigned long)[markdownArray count]); } [expectation fulfill]; }]; @@ -689,7 +689,7 @@ - (void)testDownloadAsset { [expectation fulfill]; }else{ - NSLog(@"%@",connectionError); +// NSLog(@"%@",connectionError); [expectation fulfill]; } }]; @@ -1076,7 +1076,7 @@ - (void)testFetchEntryEqualToField { if (error) { XCTFail(@"~ ERR: %@", error.userInfo); }else { - NSLog(@"result %@", [result getResult]); +// NSLog(@"result %@", [result getResult]); [self testProductCount:[result getResult]]; [[result getResult] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { @@ -1760,7 +1760,7 @@ - (void)testSearch { if (error) { XCTFail(@"~ ERR: %@", error.userInfo); }else { - NSLog(@"result %@", [result getResult]); +// NSLog(@"result %@", [result getResult]); [self testProductCount:[result getResult]]; [[result getResult] enumerateObjectsUsingBlock:^(Entry *entry, NSUInteger idx, BOOL * _Nonnull stop) { @@ -1806,7 +1806,7 @@ - (void)testMatchRgex { if (error) { XCTFail(@"~ ERR: %@", error.userInfo); }else { - NSLog(@"result %@", [result getResult]); +// NSLog(@"result %@", [result getResult]); // [self testProductCount:[result getResult]]; [[result getResult] enumerateObjectsUsingBlock:^(Entry *entry, NSUInteger idx, BOOL * _Nonnull stop) { @@ -2367,7 +2367,7 @@ - (void)testaddParamForAsset { }else { if ([assetObj.url length] > 0) { if ( [assetObj.properties objectForKey:@"dimension"]){ - NSLog(@"%@",assetObj.properties); +// NSLog(@"%@",assetObj.properties); XCTAssert(YES, @"Pass"); }else{ XCTFail(@"wrong asset object"); diff --git a/ContentstackTest/GlobalFieldTest.m b/ContentstackTest/GlobalFieldTest.m new file mode 100644 index 0000000..f0c684b --- /dev/null +++ b/ContentstackTest/GlobalFieldTest.m @@ -0,0 +1,174 @@ +// +// GlobalFieldTest.m +// Contentstack +// +// Created by Reeshika Hosmani on 02/06/25. +// Copyright © 2025 Contentstack. All rights reserved. +// + +#import +#import +#import "CSIOInternalHeaders.h" +#import "GlobalField.h" +#import "ContentstackDefinitions.h" + +@interface GlobalFieldTest : XCTestCase{ + Stack *csStack; + Config *config; +} + +@property (nonatomic, strong) Stack *stack; +@property (nonatomic, strong) GlobalField *globalField; + +@end + +@implementation GlobalFieldTest + +- (void)setUp { + [super setUp]; + NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"config" ofType:@"json"]; + NSData *data = [NSData dataWithContentsOfFile:path]; + NSDictionary *configdict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; + self.stack = [Contentstack stackWithAPIKey:configdict[@"api_key"] + accessToken:configdict[@"delivery_token"] + environmentName:configdict[@"environment"]]; + // Initialize global field + self.globalField = [self.stack globalFieldWithName:@"global_field_uid"]; +} + +- (void)tearDown { + self.stack = nil; + self.globalField = nil; + [super tearDown]; +} + +- (void)testFetchGlobalField { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch GlobalField"]; + + [self.globalField includeBranch]; + + // Call fetch method + [self.globalField fetch:^(ResponseType type, NSError * _Nullable error) { + if (error) { + XCTFail(@"Error fetching global field: %@", error); + } + + // Verify no error + XCTAssertNil(error, @"Error should be nil"); + + // Verify globalField properties + XCTAssertNotNil(self.globalField.title, @"Title should not be nil"); + XCTAssertNotNil(self.globalField.uid, @"UID should not be nil"); + XCTAssertNotNil(self.globalField.Description, @"Description should not be nil"); + + // Verify data types + XCTAssertTrue([self.globalField.title isKindOfClass:[NSString class]], @"Title should be NSString"); + XCTAssertTrue([self.globalField.uid isKindOfClass:[NSString class]], @"UID should be NSString"); + XCTAssertTrue([self.globalField.description isKindOfClass:[NSString class]], @"Description should be NSString"); + + [expectation fulfill]; + }]; + + // Wait for expectation + [self waitForExpectationsWithTimeout:10.0 handler:^(NSError * _Nullable error) { + if (error) { + NSLog(@"Test timeout error: %@", error); + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testFetchGlobalFieldWithInvalidName { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch Invalid GlobalField"]; + + // Create globalField with invalid name + GlobalField *invalidGlobalField = [self.stack globalFieldWithName:@"invalid_global_field"]; + + // Call fetch method + [invalidGlobalField fetch:^(ResponseType type, NSError * _Nullable error) { + // Verify error is not nil + XCTAssertNotNil(error, @"Error should not be nil"); + + [expectation fulfill]; + }]; + + // Wait for expectation + [self waitForExpectationsWithTimeout:10.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testFetchGlobalFieldWithIncludeBranch { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch GlobalField with Branch"]; + + // Include branch + [self.globalField includeBranch]; + + // Call fetch method + [self.globalField fetch:^(ResponseType type, NSError * _Nullable error) { + // Verify no error + XCTAssertNil(error, @"Error should be nil"); + + // Verify branch is included + XCTAssertNotNil(self.globalField.branch, @"Branch should not be nil"); + + [expectation fulfill]; + }]; + + // Wait for expectation + [self waitForExpectationsWithTimeout:10.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testFetchGlobalFieldWithIncludeSchema { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch GlobalField with Schema"]; + + // Include schema + [self.globalField includeGlobalFieldSchema]; + + // Call fetch method + [self.globalField fetch:^(ResponseType type, NSError * _Nullable error) { + // Verify no error + XCTAssertNil(error, @"Error should be nil"); + + // Verify schema is included in properties + XCTAssertNotNil(self.globalField.schema, @"Schema should not be nil"); + + [expectation fulfill]; + }]; + + // Wait for expectation + [self waitForExpectationsWithTimeout:10.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +- (void)testFetchAllGlobalFields { + XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch all GlobalFields"]; + + // Call fetch method + [self.globalField fetchAll:^(ResponseType type, NSArray * _Nullable result, NSError * _Nullable error) { + + // Verify no error + XCTAssertNil(error, @"Error should be nil"); + XCTAssertNotNil(result, @"Result is not null"); + [expectation fulfill]; + + }]; + + // Wait for expectation + [self waitForExpectationsWithTimeout:10.0 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Expectation failed with error: %@", error); + } + }]; +} + +@end diff --git a/LICENSE b/LICENSE index e17cc92..3851325 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2012-2024 Contentstack +Copyright (c) 2012-2025 Contentstack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal