diff --git a/Podfile.lock b/Podfile.lock index b85e81be..6d0f54b2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -27,7 +27,7 @@ PODS: - OHHTTPStubs/Swift (6.1.0): - OHHTTPStubs/Default - UIDeviceIdentifier (1.1.4) - - WordPressKit (2.0.0-beta.1): + - WordPressKit (2.0.0-beta.3): - Alamofire (~> 4.7.3) - CocoaLumberjack (= 3.4.2) - NSObject-SafeExpectations (= 0.0.3) @@ -70,7 +70,7 @@ SPEC CHECKSUMS: OCMock: 43565190abc78977ad44a61c0d20d7f0784d35ab OHHTTPStubs: 1e21c7d2c084b8153fc53d48400d8919d2d432d0 UIDeviceIdentifier: 8f8a24b257a4d978c8d40ad1e7355b944ffbfa8c - WordPressKit: f54ffbba343061975b72e64e5d49615c28156147 + WordPressKit: 7978809060775c2301d1c0f83215b34dc4aaba2e WordPressShared: a2fc2db66c210a05d317ae9678b5823dd6a4d708 wpxmlrpc: 6ba55c773cfa27083ae4a2173e69b19f46da98e2 diff --git a/WordPressKit.podspec b/WordPressKit.podspec index 7e773d60..db31af7e 100644 --- a/WordPressKit.podspec +++ b/WordPressKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "WordPressKit" - s.version = "2.0.0-beta.2" + s.version = "2.0.0-beta.3" s.summary = "WordPressKit offers a clean and simple WordPress.com and WordPress.org API." s.description = <<-DESC diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index d4243f37..1366fadc 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 731BA83A21DED358000FDFCD /* site-creation-success.json in Resources */ = {isa = PBXBuildFile; fileRef = 731BA83921DECE93000FDFCD /* site-creation-success.json */; }; 7328420421CD786C00126755 /* WordPressComServiceRemote+SiteCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7328420321CD786C00126755 /* WordPressComServiceRemote+SiteCreation.swift */; }; 7328420621CD798A00126755 /* WordPressComServiceRemoteTests+SiteCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7328420521CD798A00126755 /* WordPressComServiceRemoteTests+SiteCreation.swift */; }; + 7334B2B722035F3200453E4C /* ServiceRemoteRESTTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7334B2B622035F3200453E4C /* ServiceRemoteRESTTests.swift */; }; 736C971021E80D48007A4200 /* SiteVerticalsPromptResponseDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736C970F21E80D48007A4200 /* SiteVerticalsPromptResponseDecodingTests.swift */; }; 73A2F38A21E7F81E00388609 /* WordPressComServiceRemote+SiteVerticalsPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A2F38921E7F81E00388609 /* WordPressComServiceRemote+SiteVerticalsPrompt.swift */; }; 73A2F38D21E7FC8200388609 /* WordPressComServiceRemoteTests+SiteVerticalsPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A2F38C21E7FC8200388609 /* WordPressComServiceRemoteTests+SiteVerticalsPrompt.swift */; }; @@ -98,7 +99,6 @@ 740B23EA1F17FB4200067A2A /* xmlrpc-wp-getpost-success.xml in Resources */ = {isa = PBXBuildFile; fileRef = 740B23E01F17FB4200067A2A /* xmlrpc-wp-getpost-success.xml */; }; 740B23ED1F17FB7E00067A2A /* xmlrpc-bad-username-password-error.xml in Resources */ = {isa = PBXBuildFile; fileRef = 740B23EB1F17FB7E00067A2A /* xmlrpc-bad-username-password-error.xml */; }; 740B23EE1F17FB7E00067A2A /* xmlrpc-malformed-request-xml-error.xml in Resources */ = {isa = PBXBuildFile; fileRef = 740B23EC1F17FB7E00067A2A /* xmlrpc-malformed-request-xml-error.xml */; }; - 74155E251EF87DDF00A06AEA /* ServiceRemoteRESTTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 74155E241EF87DDF00A06AEA /* ServiceRemoteRESTTests.m */; }; 742362D61F10250600BD0A7F /* MenusServiceRemote.h in Headers */ = {isa = PBXBuildFile; fileRef = 742362D41F10250600BD0A7F /* MenusServiceRemote.h */; settings = {ATTRIBUTES = (Public, ); }; }; 742362D71F10250600BD0A7F /* MenusServiceRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = 742362D51F10250600BD0A7F /* MenusServiceRemote.m */; }; 742362DE1F1025B400BD0A7F /* RemoteMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = 742362D81F1025B400BD0A7F /* RemoteMenu.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -507,6 +507,7 @@ 731BA83921DECE93000FDFCD /* site-creation-success.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-creation-success.json"; sourceTree = ""; }; 7328420321CD786C00126755 /* WordPressComServiceRemote+SiteCreation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComServiceRemote+SiteCreation.swift"; sourceTree = ""; }; 7328420521CD798A00126755 /* WordPressComServiceRemoteTests+SiteCreation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComServiceRemoteTests+SiteCreation.swift"; sourceTree = ""; }; + 7334B2B622035F3200453E4C /* ServiceRemoteRESTTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceRemoteRESTTests.swift; sourceTree = ""; }; 736C970F21E80D48007A4200 /* SiteVerticalsPromptResponseDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteVerticalsPromptResponseDecodingTests.swift; sourceTree = ""; }; 73A2F38921E7F81E00388609 /* WordPressComServiceRemote+SiteVerticalsPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComServiceRemote+SiteVerticalsPrompt.swift"; sourceTree = ""; }; 73A2F38B21E7FC2A00388609 /* site-verticals-prompt.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-verticals-prompt.json"; sourceTree = ""; }; @@ -559,7 +560,6 @@ 740B23E01F17FB4200067A2A /* xmlrpc-wp-getpost-success.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "xmlrpc-wp-getpost-success.xml"; sourceTree = ""; }; 740B23EB1F17FB7E00067A2A /* xmlrpc-bad-username-password-error.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "xmlrpc-bad-username-password-error.xml"; sourceTree = ""; }; 740B23EC1F17FB7E00067A2A /* xmlrpc-malformed-request-xml-error.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "xmlrpc-malformed-request-xml-error.xml"; sourceTree = ""; }; - 74155E241EF87DDF00A06AEA /* ServiceRemoteRESTTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServiceRemoteRESTTests.m; sourceTree = ""; }; 742362D41F10250600BD0A7F /* MenusServiceRemote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MenusServiceRemote.h; sourceTree = ""; }; 742362D51F10250600BD0A7F /* MenusServiceRemote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenusServiceRemote.m; sourceTree = ""; }; 742362D81F1025B400BD0A7F /* RemoteMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteMenu.h; sourceTree = ""; }; @@ -1128,14 +1128,14 @@ 931924231F1662FA0069CBCC /* JSONLoader.swift */, 9F3E0BA920873772009CB5BA /* MockServiceRequest.swift */, 74B335D71F06F1CA0053A184 /* MockWordPressComRestApi.swift */, - 74155E241EF87DDF00A06AEA /* ServiceRemoteRESTTests.m */, 93AB06031EE8838400EF8764 /* RemoteTestCase.swift */, 74A923B11F2BE2DF00EC8F92 /* RESTTestable.swift */, - 740B23D51F17F7C100067A2A /* XMLRPCTestable.swift */, + 7334B2B622035F3200453E4C /* ServiceRemoteRESTTests.swift */, FFE247A620C891D1002DF3A2 /* WordPressComOAuthTests.swift */, 74B335D91F06F3D60053A184 /* WordPressComRestApiTests.swift */, 73B3DAD521FBB20D00B2CF18 /* WordPressComRestApiTests+Locale.swift */, 74B335DB1F06F4180053A184 /* WordPressOrgXMLRPCApiTests.swift */, + 740B23D51F17F7C100067A2A /* XMLRPCTestable.swift */, 93BD273E1EE732CC002BB00B /* Accounts */, 826016FE1F9FD59400533B6C /* Activity */, 74B5F0DF1EF82AAB00B411E7 /* Blog */, @@ -2288,11 +2288,11 @@ E1E89C6A1FD6BDB1006E7A33 /* PluginDirectoryTests.swift in Sources */, 9F3E0BAA20873773009CB5BA /* MockServiceRequest.swift in Sources */, 748437EF1F1D4D8B00E8DDAF /* MenusServiceRemoteTests.m in Sources */, + 7334B2B722035F3200453E4C /* ServiceRemoteRESTTests.swift in Sources */, 93AC8EDF1ED32FD000900F5A /* StatsStreakItemTests.m in Sources */, 73D5930121E550F500E4CF84 /* SiteVerticalsResponseDecodingTests.swift in Sources */, 74A44DD41F13C6D8006CD8F4 /* RemoteNotificationTests.swift in Sources */, 74A923B21F2BE2DF00EC8F92 /* RESTTestable.swift in Sources */, - 74155E251EF87DDF00A06AEA /* ServiceRemoteRESTTests.m in Sources */, 74D67F0A1F15C24C0010C5ED /* PeopleServiceRemoteTests.swift in Sources */, 9F3E0BAE20873836009CB5BA /* ReaderTopicServiceRemoteTest+Subscriptions.swift in Sources */, 7328420621CD798A00126755 /* WordPressComServiceRemoteTests+SiteCreation.swift in Sources */, diff --git a/WordPressKit/ActivityServiceRemote.swift b/WordPressKit/ActivityServiceRemote.swift index 88cde629..a7d46f60 100644 --- a/WordPressKit/ActivityServiceRemote.swift +++ b/WordPressKit/ActivityServiceRemote.swift @@ -26,6 +26,7 @@ public class ActivityServiceRemote: ServiceRemoteWordPressComREST { failure: @escaping (Error) -> Void) { let endpoint = "sites/\(siteID)/activity" let path = self.path(forEndpoint: endpoint, withVersion: ._2_0) + let localeKey = self.localeKey(forVersion: ._2_0) let locale = WordPressComLanguageDatabase().deviceLanguage.slug let pageNumber = (offset / count + 1) let parameters: [String: AnyObject] = [ @@ -36,6 +37,7 @@ public class ActivityServiceRemote: ServiceRemoteWordPressComREST { wordPressComRestApi.GET(path, parameters: parameters, + localeKey: localeKey, success: { response, _ in do { let (activities, totalItems) = try self.mapActivitiesResponse(response) @@ -64,6 +66,7 @@ public class ActivityServiceRemote: ServiceRemoteWordPressComREST { failure: @escaping (Error) -> Void) { let endpoint = "sites/\(siteID)/rewind" let path = self.path(forEndpoint: endpoint, withVersion: ._2_0) + let localeKey = self.localeKey(forVersion: ._2_0) let locale = WordPressComLanguageDatabase().deviceLanguage.slug let parameters: [String: AnyObject] = [ "locale": locale as AnyObject @@ -71,6 +74,7 @@ public class ActivityServiceRemote: ServiceRemoteWordPressComREST { wordPressComRestApi.GET(path, parameters: parameters, + localeKey: localeKey, success: { response, _ in guard let rewindStatus = response as? [String: AnyObject] else { failure(ResponseError.decodingFailure) diff --git a/WordPressKit/PlanServiceRemote.swift b/WordPressKit/PlanServiceRemote.swift index e71c174e..d3f5ab44 100644 --- a/WordPressKit/PlanServiceRemote.swift +++ b/WordPressKit/PlanServiceRemote.swift @@ -19,11 +19,13 @@ public class PlanServiceRemote: ServiceRemoteWordPressComREST { public func getWpcomPlans(_ success: @escaping (AvailablePlans) -> Void, failure: @escaping (Error) -> Void) { let endpoint = "plans/mobile" let path = self.path(forEndpoint: endpoint, withVersion: ._2_0) + let localeKey = self.localeKey(forVersion: ._2_0) let locale = WordPressComLanguageDatabase().deviceLanguage.slug let parameters = ["locale": locale] wordPressComRestApi.GET(path, parameters: parameters as [String : AnyObject]?, + localeKey: localeKey, success: { response, _ in diff --git a/WordPressKit/ServiceRemoteWordPressComREST.h b/WordPressKit/ServiceRemoteWordPressComREST.h index 7d7499ff..67414855 100644 --- a/WordPressKit/ServiceRemoteWordPressComREST.h +++ b/WordPressKit/ServiceRemoteWordPressComREST.h @@ -1,4 +1,4 @@ -#import +@import Foundation; @class WordPressComRestApi; @@ -17,7 +17,6 @@ NS_ASSUME_NONNULL_BEGIN */ @interface ServiceRemoteWordPressComREST : NSObject - /** * @brief The API object to use for communications. */ @@ -45,6 +44,15 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)pathForEndpoint:(NSString *)endpoint withVersion:(ServiceRemoteWordPressComRESTApiVersion)apiVersion; +/** + * @brief Returns the locale key corresponding to the specified API version, as this varies. + * + * @param apiVersion The version of the API to use. + * + * @returns The request URL. + */ +- (NSString *)localeKeyForVersion:(ServiceRemoteWordPressComRESTApiVersion)apiVersion; + /** * @brief An anonoymous API object to use for communications where authentication is not needed. * diff --git a/WordPressKit/ServiceRemoteWordPressComREST.m b/WordPressKit/ServiceRemoteWordPressComREST.m index a9d93987..ce1bd15a 100644 --- a/WordPressKit/ServiceRemoteWordPressComREST.m +++ b/WordPressKit/ServiceRemoteWordPressComREST.m @@ -14,13 +14,16 @@ static NSString* const ServiceRemoteWordPressComRESTApiVersionString_1_3 = @"rest/v1.3"; static NSString* const ServiceRemoteWordPressComRESTApiVersionString_2_0 = @"wpcom/v2"; +static NSString* const ServiceRemoteWordPressComRESTLocaleKeyDefault = @"locale"; +static NSString* const ServiceRemoteWordPressComRESTLocaleKey_v2_0 = @"_locale"; + @interface ServiceRemoteWordPressComREST () @end @implementation ServiceRemoteWordPressComREST -- (instancetype)initWithWordPressComRestApi:(WordPressComRestApi *)wordPressComRestApi { - +- (instancetype)initWithWordPressComRestApi:(WordPressComRestApi *)wordPressComRestApi +{ NSParameterAssert([wordPressComRestApi isKindOfClass:[WordPressComRestApi class]]); self = [super init]; @@ -79,11 +82,26 @@ - (NSString *)pathForEndpoint:(NSString *)resourceUrl return [NSString stringWithFormat:@"%@/%@", apiVersionString, resourceUrl]; } -+ (WordPressComRestApi *)anonymousWordPressComRestApiWithUserAgent:(NSString *)userAgent { +- (NSString *)localeKeyForVersion:(ServiceRemoteWordPressComRESTApiVersion)apiVersion +{ + NSString *result = nil; + + switch (apiVersion) { + case ServiceRemoteWordPressComRESTApiVersion_2_0: + result = ServiceRemoteWordPressComRESTLocaleKey_v2_0; + break; - return [[WordPressComRestApi alloc] initWithOAuthToken:nil - userAgent:userAgent - ]; + default: + result = ServiceRemoteWordPressComRESTLocaleKeyDefault; + break; + } + + return result; +} + ++ (WordPressComRestApi *)anonymousWordPressComRestApiWithUserAgent:(NSString *)userAgent +{ + return [[WordPressComRestApi alloc] initWithOAuthToken:nil userAgent:userAgent]; } @end diff --git a/WordPressKit/TimeZoneServiceRemote.swift b/WordPressKit/TimeZoneServiceRemote.swift index cf0b46ef..500493c3 100644 --- a/WordPressKit/TimeZoneServiceRemote.swift +++ b/WordPressKit/TimeZoneServiceRemote.swift @@ -9,9 +9,10 @@ public class TimeZoneServiceRemote: ServiceRemoteWordPressComREST { public func getTimezones(success: @escaping (([TimeZoneGroup]) -> Void), failure: @escaping ((Error) -> Void)) { let endpoint = "timezones" let path = self.path(forEndpoint: endpoint, withVersion: ._2_0) + let localeKey = self.localeKey(forVersion: ._2_0) let locale = WordPressComLanguageDatabase().deviceLanguage.slug let parameters: [String: AnyObject] = ["_locale": locale as AnyObject] - wordPressComRestApi.GET(path, parameters: parameters, success: { (response, _) in + wordPressComRestApi.GET(path, parameters: parameters, localeKey: localeKey, success: { (response, _) in do { let groups = try self.timezoneGroupsFromResponse(response) success(groups) diff --git a/WordPressKit/WordPressComRestApi.swift b/WordPressKit/WordPressComRestApi.swift index ffb3ae88..09234822 100644 --- a/WordPressKit/WordPressComRestApi.swift +++ b/WordPressKit/WordPressComRestApi.swift @@ -24,7 +24,10 @@ import Alamofire case preconditionFailure } -open class WordPressComRestApi: NSObject { +open class WordPressComRestApi: NSObject { + + // MARK: - Properties + @objc public static let ErrorKeyErrorCode: String = "WordPressComRestApiErrorCodeKey" @objc public static let ErrorKeyErrorMessage: String = "WordPressComRestApiErrorMessageKey" @objc public static let SessionTaskKey: String = "WordPressComRestAPI.sessionTask" @@ -41,19 +44,19 @@ open class WordPressComRestApi: NSObject { @objc public let sharedContainerIdentifier: String? - fileprivate let backgroundUploads: Bool + private let backgroundUploads: Bool - static let localeKey = "locale" + static let defaultLocaleKey = "locale" - fileprivate let oAuthToken: String? - fileprivate let userAgent: String? + private let oAuthToken: String? + private let userAgent: String? /** Configure whether or not the user's preferred language locale should be appended. Defaults to true. */ @objc open var appendsPreferredLanguageLocale = true - fileprivate lazy var sessionManager: Alamofire.SessionManager = { + private lazy var sessionManager: Alamofire.SessionManager = { let sessionConfiguration = URLSessionConfiguration.default let sessionManager = self.makeSessionManager(configuration: sessionConfiguration) return sessionManager @@ -67,7 +70,7 @@ open class WordPressComRestApi: NSObject { return result } - fileprivate lazy var uploadSessionManager: Alamofire.SessionManager = { + private lazy var uploadSessionManager: Alamofire.SessionManager = { if self.backgroundUploads { let sessionConfiguration = URLSessionConfiguration.background(withIdentifier: self.backgroundSessionIdentifier) sessionConfiguration.sharedContainerIdentifier = self.sharedContainerIdentifier @@ -78,7 +81,7 @@ open class WordPressComRestApi: NSObject { return self.sessionManager }() - fileprivate func makeSessionManager(configuration sessionConfiguration: URLSessionConfiguration) -> Alamofire.SessionManager { + private func makeSessionManager(configuration sessionConfiguration: URLSessionConfiguration) -> Alamofire.SessionManager { var additionalHeaders: [String : AnyObject] = [:] if let oAuthToken = self.oAuthToken { additionalHeaders["Authorization"] = "Bearer \(oAuthToken)" as AnyObject? @@ -92,6 +95,8 @@ open class WordPressComRestApi: NSObject { return sessionManager } + + // MARK: - WordPressComRestApi @objc convenience public init(oAuthToken: String? = nil, userAgent: String? = nil) { self.init(oAuthToken: oAuthToken, userAgent: userAgent, backgroundUploads: false, backgroundSessionIdentifier: WordPressComRestApi.defaultBackgroundSessionIdentifier) @@ -141,11 +146,12 @@ open class WordPressComRestApi: NSObject { private func request(method: HTTPMethod, urlString: String, parameters: [String: AnyObject]?, + localeKey: String, encoding: ParameterEncoding, success: @escaping SuccessResponseBlock, failure: @escaping FailureReponseBlock) -> Progress? { - guard let URLString = buildRequestURLFor(path: urlString, parameters: parameters) else { + guard let URLString = buildRequestURLFor(path: urlString, parameters: parameters, localeKey: localeKey) else { let error = NSError(domain: String(describing: WordPressComRestApiError.self), code: WordPressComRestApiError.requestSerializationFailed.rawValue, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("Failed to serialize request to the REST API.", comment: "Error message to show when wrong URL format is used to access the REST API")]) @@ -180,7 +186,7 @@ open class WordPressComRestApi: NSObject { } /** - Executes a GET request to the specified endpoint defined on URLString + Executes a GET request to the specified endpoint defined on URLString. If `appendsPreferredLanguageLocale` is enabled, the default locale key will be used to convey the prevailing locale. - parameter URLString: the url string to be added to the baseURL - parameter parameters: the parameters to be encoded on the request @@ -196,11 +202,33 @@ open class WordPressComRestApi: NSObject { success: @escaping SuccessResponseBlock, failure: @escaping FailureReponseBlock) -> Progress? { - return request(method: .get, urlString: URLString, parameters: parameters, encoding: URLEncoding.default, success: success, failure: failure) + return GET(URLString, parameters: parameters, localeKey: WordPressComRestApi.defaultLocaleKey, success: success, failure: failure) } /** - Executes a POST request to the specified endpoint defined on URLString + Executes a GET request to the specified endpoint defined on URLString. + + - parameter URLString: the url string to be added to the baseURL + - parameter parameters: the parameters to be encoded on the request + - parameter localeKey: the key with which to encode the user's locale + - parameter success: callback to be called on successful request + - parameter failure: callback to be called on failed request + + - returns: a NSProgress object that can be used to track the progress of the request and to cancel the request. If the method + returns nil it's because something happened on the request serialization and the network request was not started, but the failure callback + will be invoked with the error specificing the serialization issues. + */ + @objc @discardableResult open func GET(_ URLString: String, + parameters: [String: AnyObject]?, + localeKey: String, + success: @escaping SuccessResponseBlock, + failure: @escaping FailureReponseBlock) -> Progress? { + + return request(method: .get, urlString: URLString, parameters: parameters, localeKey: localeKey, encoding: URLEncoding.default, success: success, failure: failure) + } + + /** + Executes a POST request to the specified endpoint defined on URLString. If `appendsPreferredLanguageLocale` is enabled, the default locale key will be used to convey the prevailing locale. - parameter URLString: the url string to be added to the baseURL - parameter parameters: the parameters to be encoded on the request @@ -216,12 +244,33 @@ open class WordPressComRestApi: NSObject { success: @escaping SuccessResponseBlock, failure: @escaping FailureReponseBlock) -> Progress? { - return request(method: .post, urlString: URLString, parameters: parameters, encoding: JSONEncoding.default, success: success, failure: failure) + return POST(URLString, parameters: parameters, localeKey: WordPressComRestApi.defaultLocaleKey, success: success, failure: failure) + } + + /** + Executes a POST request to the specified endpoint defined on URLString. + + - parameter URLString: the url string to be added to the baseURL + - parameter parameters: the parameters to be encoded on the request + - parameter localeKey: the key with which to encode the user's locale + - parameter success: callback to be called on successful request + - parameter failure: callback to be called on failed request + + - returns: a NSProgress object that can be used to track the progress of the upload and to cancel the upload. If the method + returns nil it's because something happened on the request serialization and the network request was not started, but the failure callback + will be invoked with the error specificing the serialization issues. + */ + @objc @discardableResult open func POST(_ URLString: String, + parameters: [String: AnyObject]?, + localeKey: String, + success: @escaping SuccessResponseBlock, + failure: @escaping FailureReponseBlock) -> Progress? { + + return request(method: .post, urlString: URLString, parameters: parameters, localeKey: localeKey, encoding: JSONEncoding.default, success: success, failure: failure) } /** - Executes a multipart POST using the current serializer, the parameters defined and the fileParts defined in the request - This request will be streamed from disk, so it's ideally to be used for large media post uploads. + Executes a multipart POST using the current serializer, the parameters defined and the fileParts defined in the request. This request will be streamed from disk, so it's ideally to be used for large media post uploads. If `appendsPreferredLanguageLocale` is enabled, the default locale key will be used to convey the prevailing locale. - parameter URLString: the endpoint to connect - parameter parameters: the parameters to use on the request @@ -230,18 +279,45 @@ open class WordPressComRestApi: NSObject { - parameter success: callback to be called on successful request - parameter failure: callback to be called on failed request + - returns: a NSProgress object that can be used to track the progress of the upload and to cancel the upload. If the method + returns nil it's because something happened on the request serialization and the network request was not started, but the failure callback + will be invoked with the error specificing the serialization issues. + */ + @objc @discardableResult open func multipartPOST(_ URLString: String, + parameters: [String: AnyObject]?, + fileParts: [FilePart], + requestEnqueued: RequestEnqueuedBlock? = nil, + success: @escaping SuccessResponseBlock, + failure: @escaping FailureReponseBlock) -> Progress? { + + return multipartPOST(URLString, parameters: parameters, localeKey: WordPressComRestApi.defaultLocaleKey, fileParts: fileParts, requestEnqueued: requestEnqueued, success: success, failure: failure) + } + + /** + Executes a multipart POST using the current serializer, the parameters defined, + locale key specified, and the fileParts defined in the request. This request will be streamed from disk, so it's ideally to be used for large media post uploads. + + - parameter URLString: the endpoint to connect + - parameter parameters: the parameters to use on the request + - parameter localeKey: the key with which to encode the user's locale + - parameter fileParts: the file parameters that are added to the multipart request + - parameter requestEnqueued: callback to be called when the fileparts are serialized and request is added to the background session. Defaults to nil + - parameter success: callback to be called on successful request + - parameter failure: callback to be called on failed request + - returns: a NSProgress object that can be used to track the progress of the upload and to cancel the upload. If the method returns nil it's because something happened on the request serialization and the network request was not started, but the failure callback will be invoked with the error specificing the serialization issues. */ @objc @discardableResult open func multipartPOST(_ URLString: String, parameters: [String: AnyObject]?, + localeKey: String, fileParts: [FilePart], requestEnqueued: RequestEnqueuedBlock? = nil, success: @escaping SuccessResponseBlock, failure: @escaping FailureReponseBlock) -> Progress? { - guard let URLString = buildRequestURLFor(path: URLString, parameters: parameters) else { + guard let URLString = buildRequestURLFor(path: URLString, parameters: parameters, localeKey: localeKey) else { let error = NSError(domain: String(describing: WordPressComRestApiError.self), code: WordPressComRestApiError.requestSerializationFailed.rawValue, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("Failed to serialize request to the REST API.", comment: "Error message to show when wrong URL format is used to access the REST API")]) @@ -309,10 +385,10 @@ open class WordPressComRestApi: NSObject { /// - Parameters: /// - path: the path for the request, which might include `locale` /// - parameters: the request parameters, which could conceivably include `locale` - /// - localeKey: the locale key to search for (`locale` in v1 endpoints, `_locale` for v2) + /// - localeKey: the locale key to search for (e.g., `locale` in v1 endpoints, `_locale` for v2) /// - Returns: a request URL if successful, `nil` otherwise. /// - func buildRequestURLFor(path: String, parameters: [String: AnyObject]? = [:], localeKey: String = WordPressComRestApi.localeKey) -> String? { + func buildRequestURLFor(path: String, parameters: [String: AnyObject]? = [:], localeKey: String) -> String? { let baseURL = URL(string: WordPressComRestApi.apiBaseURLString) @@ -355,6 +431,8 @@ open class WordPressComRestApi: NSObject { } } +// MARK: - FilePart + /// FilePart represents the infomartion needed to encode a file on a multipart form request public final class FilePart: NSObject { @objc let parameterName: String @@ -370,6 +448,8 @@ public final class FilePart: NSObject { } } +// MARK: - Error processing + extension WordPressComRestApi { /// A custom error processor to handle error responses when status codes are betwen 400 and 500 @@ -448,6 +528,8 @@ extension WordPressComRestApi { } } +// MARK: - Anonymous API support + extension WordPressComRestApi { /// Returns an Api object without an oAuthtoken defined and with the userAgent set for the WordPress App user agent @@ -456,6 +538,8 @@ extension WordPressComRestApi { } } +// MARK: - Progress + @objc extension Progress { @objc var sessionTask: URLSessionTask? { diff --git a/WordPressKit/WordPressComServiceRemote+SiteSegments.swift b/WordPressKit/WordPressComServiceRemote+SiteSegments.swift index f3a71bbf..6e16df3d 100644 --- a/WordPressKit/WordPressComServiceRemote+SiteSegments.swift +++ b/WordPressKit/WordPressComServiceRemote+SiteSegments.swift @@ -92,10 +92,12 @@ public extension WordPressComServiceRemote { func retrieveSegments(completion: @escaping SiteSegmentsServiceCompletion) { let endpoint = "segments" let remotePath = path(forEndpoint: endpoint, withVersion: ._2_0) + let localeKey = self.localeKey(forVersion: ._2_0) wordPressComRestApi.GET( remotePath, parameters: nil, + localeKey: localeKey, success: { [weak self] responseObject, httpResponse in DDLogInfo("\(responseObject) | \(String(describing: httpResponse))") diff --git a/WordPressKit/WordPressComServiceRemote+SiteVerticals.swift b/WordPressKit/WordPressComServiceRemote+SiteVerticals.swift index 28209f1f..fc623b2e 100644 --- a/WordPressKit/WordPressComServiceRemote+SiteVerticals.swift +++ b/WordPressKit/WordPressComServiceRemote+SiteVerticals.swift @@ -81,6 +81,7 @@ public extension WordPressComServiceRemote { let endpoint = "verticals" let path = self.path(forEndpoint: endpoint, withVersion: ._2_0) + let localeKey = self.localeKey(forVersion: ._2_0) let requestParameters: [String : AnyObject] do { @@ -95,6 +96,7 @@ public extension WordPressComServiceRemote { wordPressComRestApi.GET( path, parameters: requestParameters, + localeKey: localeKey, success: { [weak self] responseObject, httpResponse in DDLogInfo("\(responseObject) | \(String(describing: httpResponse))") diff --git a/WordPressKit/WordPressComServiceRemote+SiteVerticalsPrompt.swift b/WordPressKit/WordPressComServiceRemote+SiteVerticalsPrompt.swift index 4710f01c..f7a3a4e1 100644 --- a/WordPressKit/WordPressComServiceRemote+SiteVerticalsPrompt.swift +++ b/WordPressKit/WordPressComServiceRemote+SiteVerticalsPrompt.swift @@ -41,6 +41,7 @@ public extension WordPressComServiceRemote { let endpoint = "verticals/prompt" let path = self.path(forEndpoint: endpoint, withVersion: ._2_0) + let localeKey = self.localeKey(forVersion: ._2_0) let requestParameters: [String : AnyObject] = [ "segment_id" : request as AnyObject @@ -49,6 +50,7 @@ public extension WordPressComServiceRemote { wordPressComRestApi.GET( path, parameters: requestParameters, + localeKey: localeKey, success: { [weak self] responseObject, httpResponse in DDLogInfo("\(responseObject) | \(String(describing: httpResponse))") diff --git a/WordPressKitTests/ServiceRemoteRESTTests.m b/WordPressKitTests/ServiceRemoteRESTTests.m deleted file mode 100644 index 6124e4ee..00000000 --- a/WordPressKitTests/ServiceRemoteRESTTests.m +++ /dev/null @@ -1,22 +0,0 @@ -#import -#import -@import WordPressKit; - -@interface ServiceRemoteWordPressComRESTTests : XCTestCase -@end - -@implementation ServiceRemoteWordPressComRESTTests - -#pragma mark - Initialization tests - -- (void)testRegularInitialization -{ - WordPressComRestApi *api = [[WordPressComRestApi alloc] initWithOAuthToken:nil userAgent:nil]; - ServiceRemoteWordPressComREST *service = nil; - - XCTAssertNoThrow(service = [[ServiceRemoteWordPressComREST alloc] initWithWordPressComRestApi:api]); - XCTAssertTrue([service isKindOfClass:[ServiceRemoteWordPressComREST class]]); - XCTAssertTrue(service.wordPressComRestApi == api); -} - -@end diff --git a/WordPressKitTests/ServiceRemoteRESTTests.swift b/WordPressKitTests/ServiceRemoteRESTTests.swift new file mode 100644 index 00000000..5509eaf6 --- /dev/null +++ b/WordPressKitTests/ServiceRemoteRESTTests.swift @@ -0,0 +1,46 @@ +import Foundation +import XCTest + +@testable import WordPressKit + +class ServiceRemoteWordPressComRESTTests: XCTestCase { + + func testRegularInitialization() { + let api = WordPressComRestApi(oAuthToken: nil, userAgent: nil) + + XCTAssertNoThrow(ServiceRemoteWordPressComREST(wordPressComRestApi: api)) + let service = ServiceRemoteWordPressComREST(wordPressComRestApi: api) + + XCTAssertTrue(service.isKind(of: ServiceRemoteWordPressComREST.self)) + XCTAssertEqual(service.wordPressComRestApi, api) + } + + func testLocaleKeyFor_1_x_VersionsReadsLocale() { + let expectedKey = "locale" + + let api = WordPressComRestApi(oAuthToken: nil, userAgent: nil) + let service = ServiceRemoteWordPressComREST(wordPressComRestApi: api) + + let actualKeyV1_0 = service.localeKey(forVersion: ._1_0) + XCTAssertEqual(actualKeyV1_0, expectedKey) + + let actualKeyV1_1 = service.localeKey(forVersion: ._1_1) + XCTAssertEqual(actualKeyV1_1, expectedKey) + + let actualKeyV1_2 = service.localeKey(forVersion: ._1_2) + XCTAssertEqual(actualKeyV1_2, expectedKey) + + let actualKeyV1_3 = service.localeKey(forVersion: ._1_3) + XCTAssertEqual(actualKeyV1_3, expectedKey) + } + + func testLocaleKeyFor_2_x_VersionsReadsUnderscoreLocale() { + let expectedKey = "_locale" + + let api = WordPressComRestApi(oAuthToken: nil, userAgent: nil) + let service = ServiceRemoteWordPressComREST(wordPressComRestApi: api) + + let actualKey = service.localeKey(forVersion: ._2_0) + XCTAssertEqual(actualKey, expectedKey) + } +} diff --git a/WordPressKitTests/WordPressComRestApiTests+Locale.swift b/WordPressKitTests/WordPressComRestApiTests+Locale.swift index 4459982d..2b549f61 100644 --- a/WordPressKitTests/WordPressComRestApiTests+Locale.swift +++ b/WordPressKitTests/WordPressComRestApiTests+Locale.swift @@ -12,7 +12,7 @@ extension WordPressComRestApiTests { let preferredLanguageIdentifier = WordPressComLanguageDatabase().deviceLanguage.slug // When - let localeAppendedPath = WordPressComRestApi().buildRequestURLFor(path: path) + let localeAppendedPath = WordPressComRestApi().buildRequestURLFor(path: path, localeKey: WordPressComRestApi.defaultLocaleKey) // Then XCTAssertNotNil(localeAppendedPath) @@ -37,7 +37,7 @@ extension WordPressComRestApiTests { XCTAssertNotNil(actualQueryItem!) let actualQueryItemKey = actualQueryItem!.name - let expectedQueryItemKey = WordPressComRestApi.localeKey + let expectedQueryItemKey = WordPressComRestApi.defaultLocaleKey XCTAssertEqual(expectedQueryItemKey, actualQueryItemKey) let actualQueryItemValue = actualQueryItem!.value @@ -55,7 +55,7 @@ extension WordPressComRestApiTests { ] // When - let localeAppendedPath = WordPressComRestApi().buildRequestURLFor(path: path, parameters: params) + let localeAppendedPath = WordPressComRestApi().buildRequestURLFor(path: path, parameters: params, localeKey: WordPressComRestApi.defaultLocaleKey) // Then XCTAssertNotNil(localeAppendedPath) @@ -79,7 +79,7 @@ extension WordPressComRestApiTests { let actualQueryString = actualURLComponents?.query XCTAssertNotNil(actualQueryString) - let queryStringIncludesLocale = actualQueryString!.contains(WordPressComRestApi.localeKey) + let queryStringIncludesLocale = actualQueryString!.contains(WordPressComRestApi.defaultLocaleKey) XCTAssertTrue(queryStringIncludesLocale) } @@ -89,7 +89,7 @@ extension WordPressComRestApiTests { let path = "/path/path?locale=\(preferredLanguageIdentifier)" // When - let localeAppendedPath = WordPressComRestApi().buildRequestURLFor(path: path) + let localeAppendedPath = WordPressComRestApi().buildRequestURLFor(path: path, localeKey: WordPressComRestApi.defaultLocaleKey) // Then XCTAssertNotNil(localeAppendedPath) @@ -114,7 +114,7 @@ extension WordPressComRestApiTests { XCTAssertNotNil(actualQueryItem!) let actualQueryItemKey = actualQueryItem!.name - let expectedQueryItemKey = WordPressComRestApi.localeKey + let expectedQueryItemKey = WordPressComRestApi.defaultLocaleKey XCTAssertEqual(expectedQueryItemKey, actualQueryItemKey) let actualQueryItemValue = actualQueryItem!.value @@ -129,11 +129,11 @@ extension WordPressComRestApiTests { let inputPath = "/path/path" let expectedLocaleValue = "foo" let params: [String : AnyObject] = [ - WordPressComRestApi.localeKey: expectedLocaleValue as AnyObject + WordPressComRestApi.defaultLocaleKey: expectedLocaleValue as AnyObject ] // When - let requestURLString = WordPressComRestApi().buildRequestURLFor(path: inputPath, parameters: params) + let requestURLString = WordPressComRestApi().buildRequestURLFor(path: inputPath, parameters: params, localeKey: WordPressComRestApi.defaultLocaleKey) // Then let preferredLanguageIdentifier = WordPressComLanguageDatabase().deviceLanguage.slug @@ -147,7 +147,7 @@ extension WordPressComRestApiTests { // When let api = WordPressComRestApi() api.appendsPreferredLanguageLocale = false - let localeAppendedPath = api.buildRequestURLFor(path: path) + let localeAppendedPath = api.buildRequestURLFor(path: path, localeKey: WordPressComRestApi.defaultLocaleKey) // Then XCTAssertNotNil(localeAppendedPath) @@ -164,4 +164,38 @@ extension WordPressComRestApiTests { let actualQueryItems = actualURLComponents!.queryItems XCTAssertNil(actualQueryItems) } + + func testThatAlternateLocaleKeyIsHonoredWhenSpecified() { + // Given + let path = "/path/path" + let expectedKey = "foo" + + // When + let localeAppendedPath = WordPressComRestApi().buildRequestURLFor(path: path, localeKey: expectedKey) + + // Then + XCTAssertNotNil(localeAppendedPath) + let actualURL = URL(string: localeAppendedPath!, relativeTo: URL(string: WordPressComRestApi.apiBaseURLString)) + XCTAssertNotNil(actualURL) + + let actualURLComponents = URLComponents(url: actualURL!, resolvingAgainstBaseURL: false) + XCTAssertNotNil(actualURLComponents) + + let expectedPath = path + let actualPath = actualURLComponents!.path + XCTAssertEqual(expectedPath, actualPath) + + let actualQueryItems = actualURLComponents!.queryItems + XCTAssertNotNil(actualQueryItems) + + let expectedQueryItemCount = 1 + let actualQueryItemCount = actualQueryItems!.count + XCTAssertEqual(expectedQueryItemCount, actualQueryItemCount) + + let actualQueryItem = actualQueryItems!.first + XCTAssertNotNil(actualQueryItem!) + + let actualQueryItemKey = actualQueryItem!.name + XCTAssertEqual(expectedKey, actualQueryItemKey) + } }