diff --git a/.buildkite/create-xcframeworks.sh b/.buildkite/create-xcframeworks.sh new file mode 100755 index 000000000..27651d7f6 --- /dev/null +++ b/.buildkite/create-xcframeworks.sh @@ -0,0 +1,27 @@ +ROOT="./.build/xcframeworks" + +rm -rf $ROOT + +for SDK in iphoneos iphonesimulator +do +xcodebuild archive \ + -workspace WordPressKit.xcworkspace \ + -scheme WordPressKit \ + -archivePath "$ROOT/WordPressKit-$SDK.xcarchive" \ + -sdk $SDK \ + SKIP_INSTALL=NO \ + BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ + DEBUG_INFORMATION_FORMAT=DWARF +done + +xcodebuild -create-xcframework \ + -framework "$ROOT/WordPressKit-iphoneos.xcarchive/Products/Library/Frameworks/WordPressKit.framework" \ + -framework "$ROOT/WordPressKit-iphonesimulator.xcarchive/Products/Library/Frameworks/WordPressKit.framework" \ + -output "$ROOT/WordPressKit.xcframework" + +cd $ROOT +zip -r -X WordPressKit.zip *.xcframework +rm -rf *.xcframework + +swift package compute-checksum WordPressKit.zip +cd - diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index e8fd0b659..10e07855c 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -24,16 +24,6 @@ steps: env: *common_env plugins: *common_plugins - ################# - # Validate Podspec - ################# - - label: "🔬 Validate Podspec" - key: "validate" - command: | - validate_podspec --patch-cocoapods - env: *common_env - plugins: *common_plugins - ################# # Linters ################# diff --git a/.gitignore b/.gitignore index 0785d714a..a37d21958 100644 --- a/.gitignore +++ b/.gitignore @@ -43,13 +43,13 @@ playground.xcworkspace # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins -# Package.resolved + # *.xcodeproj # # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project -# .swiftpm - +.swiftpm +Package.resolved .build/ # CocoaPods diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index a25dc7e56..000000000 --- a/Package.resolved +++ /dev/null @@ -1,122 +0,0 @@ -{ - "pins" : [ - { - "identity" : "alamofire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Alamofire/Alamofire", - "state" : { - "revision" : "f455c2975872ccd2d9c81594c658af65716e9b9a", - "version" : "5.9.1" - } - }, - { - "identity" : "collectionconcurrencykit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git", - "state" : { - "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95", - "version" : "0.2.0" - } - }, - { - "identity" : "cryptoswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", - "state" : { - "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", - "version" : "1.8.1" - } - }, - { - "identity" : "ohhttpstubs", - "kind" : "remoteSourceControl", - "location" : "https://github.com/AliSoftware/OHHTTPStubs", - "state" : { - "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", - "version" : "9.1.0" - } - }, - { - "identity" : "sourcekitten", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jpsim/SourceKitten.git", - "state" : { - "revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", - "version" : "0.34.1" - } - }, - { - "identity" : "swift-argument-parser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser.git", - "state" : { - "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", - "version" : "1.2.3" - } - }, - { - "identity" : "swift-syntax", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", - "state" : { - "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", - "version" : "509.0.2" - } - }, - { - "identity" : "swiftlint", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/SwiftLint", - "state" : { - "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee", - "version" : "0.54.0" - } - }, - { - "identity" : "swiftytexttable", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scottrhoyt/SwiftyTextTable.git", - "state" : { - "revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", - "version" : "0.9.0" - } - }, - { - "identity" : "swxmlhash", - "kind" : "remoteSourceControl", - "location" : "https://github.com/drmohundro/SWXMLHash.git", - "state" : { - "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f", - "version" : "7.0.2" - } - }, - { - "identity" : "wordpress-ios-shared", - "kind" : "remoteSourceControl", - "location" : "https://github.com/wordpress-mobile/WordPress-iOS-Shared.git", - "state" : { - "branch" : "mokagio/swiftlint-read-as-dependency", - "revision" : "422950b28f01d7cc11218e7d70a6cd65004d23ae" - } - }, - { - "identity" : "wpxmlrpc", - "kind" : "remoteSourceControl", - "location" : "https://github.com/wordpress-mobile/wpxmlrpc", - "state" : { - "revision" : "bfc413d336bdeaab89e62dc483380baa99b2257e", - "version" : "0.10.0" - } - }, - { - "identity" : "yams", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jpsim/Yams.git", - "state" : { - "revision" : "8a835d918245ca22f36663dd3862138805d7f707", - "version" : "5.1.0" - } - } - ], - "version" : 2 -} diff --git a/Package.swift b/Package.swift index 027d1c018..cc9d23401 100644 --- a/Package.swift +++ b/Package.swift @@ -1,50 +1,18 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 5.10 import PackageDescription let package = Package( name: "WordPressKit", - platforms: [.iOS(.v13)], + platforms: [.iOS(.v15)], products: [ - .library(name: "APIInterface", targets: ["APIInterface"]), - .library(name: "CoreAPI", targets: ["CoreAPI"]), - ], - dependencies: [ - // .package(url: "https://github.com/wordpress-mobile/WordPress-iOS-Shared.git", from: "2.3.1"), - // See https://github.com/wordpress-mobile/WordPress-iOS-Shared/pull/354 - .package(url: "https://github.com/wordpress-mobile/WordPress-iOS-Shared.git", branch: "mokagio/swiftlint-read-as-dependency"), - .package(url: "https://github.com/wordpress-mobile/wpxmlrpc", from: "0.10.0"), - // Test dependencies - .package(url: "https://github.com/AliSoftware/OHHTTPStubs", from: "9.1.0"), - .package(url: "https://github.com/Alamofire/Alamofire", from: "5.8.1"), + .library(name: "WordPressKit", targets: ["WordPressKit"]), ], targets: [ - .target(name: "APIInterface"), - .target( - name: "CoreAPI", - dependencies: [ - .target(name: "APIInterface"), - .product( - name: "WordPressShared", - package: "WordPress-iOS-Shared", - // Constrain to iOS only to avoid having to explicitly set a macOS version because of this library's requirements. - condition: .when(platforms: [.iOS]) - ), - "wpxmlrpc" - ] - ), - .testTarget( - name: "CoreAPITests", - dependencies: [ - .target(name: "CoreAPI"), - .product(name: "OHHTTPStubs", package: "OHHTTPStubs"), - .product(name: "OHHTTPStubsSwift", package: "OHHTTPStubs"), - "Alamofire", - ], - path: "Tests/CoreAPITests", - resources: [ - .process("Stubs") // Relative to path - ] + .binaryTarget( + name: "WordPressKit", + url: "https://github.com/user-attachments/files/16531883/WordPressKit.zip", + checksum: "ca916824c64a6061814a69a37bc9a6560aafcc35559983e945fdfa5c3bbcc23d" ), ] ) diff --git a/Podfile b/Podfile index 2b74b5084..c4ea2de3e 100644 --- a/Podfile +++ b/Podfile @@ -8,6 +8,7 @@ use_frameworks! APP_IOS_DEPLOYMENT_TARGET = Gem::Version.new('13.0') platform :ios, APP_IOS_DEPLOYMENT_TARGET +workspace './WordPressKit.xcworkspace' def swiftlint_version require 'yaml' @@ -15,31 +16,6 @@ def swiftlint_version YAML.load_file('.swiftlint.yml')['swiftlint_version'] end -def wordpresskit_pods - pod 'WordPressShared', '~> 2.0.0-beta.2' - pod 'NSObject-SafeExpectations', '~> 0.0.4' - pod 'wpxmlrpc', '~> 0.10.0' - pod 'UIDeviceIdentifier', '~> 2.0' -end - -## WordPress Kit -## ============= -## -target 'WordPressKit' do - project 'WordPressKit.xcodeproj' - wordpresskit_pods -end - -target 'WordPressKitTests' do - project 'WordPressKit.xcodeproj' - wordpresskit_pods - - pod 'OHHTTPStubs', '~> 9.0' - pod 'OHHTTPStubs/Swift', '~> 9.0' - pod 'OCMock', '~> 3.4' - pod 'Alamofire', '~> 5.0' -end - abstract_target 'Tools' do pod 'SwiftLint', swiftlint_version end diff --git a/Podfile.lock b/Podfile.lock index 83a6a8535..fd73c6a23 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,59 +1,16 @@ PODS: - - Alamofire (5.8.1) - - NSObject-SafeExpectations (0.0.6) - - OCMock (3.9.3) - - OHHTTPStubs (9.1.0): - - OHHTTPStubs/Default (= 9.1.0) - - OHHTTPStubs/Core (9.1.0) - - OHHTTPStubs/Default (9.1.0): - - OHHTTPStubs/Core - - OHHTTPStubs/JSON - - OHHTTPStubs/NSURLSession - - OHHTTPStubs/OHPathHelpers - - OHHTTPStubs/JSON (9.1.0): - - OHHTTPStubs/Core - - OHHTTPStubs/NSURLSession (9.1.0): - - OHHTTPStubs/Core - - OHHTTPStubs/OHPathHelpers (9.1.0) - - OHHTTPStubs/Swift (9.1.0): - - OHHTTPStubs/Default - SwiftLint (0.54.0) - - UIDeviceIdentifier (2.3.0) - - WordPressShared (2.0.1) - - wpxmlrpc (0.10.0) DEPENDENCIES: - - Alamofire (~> 5.0) - - NSObject-SafeExpectations (~> 0.0.4) - - OCMock (~> 3.4) - - OHHTTPStubs (~> 9.0) - - OHHTTPStubs/Swift (~> 9.0) - SwiftLint (= 0.54.0) - - UIDeviceIdentifier (~> 2.0) - - WordPressShared (~> 2.0.0-beta.2) - - wpxmlrpc (~> 0.10.0) SPEC REPOS: trunk: - - Alamofire - - NSObject-SafeExpectations - - OCMock - - OHHTTPStubs - SwiftLint - - UIDeviceIdentifier - - WordPressShared - - wpxmlrpc SPEC CHECKSUMS: - Alamofire: 3ca42e259043ee0dc5c0cdd76c4bc568b8e42af7 - NSObject-SafeExpectations: c01c8881cbd97efad6f668286b913cd0b7d62ab5 - OCMock: 300b1b1b9155cb6378660b981c2557448830bdc6 - OHHTTPStubs: 90eac6d8f2c18317baeca36698523dc67c513831 SwiftLint: c1de071d9d08c8aba837545f6254315bc900e211 - UIDeviceIdentifier: 442b65b4ff1832d4ca9c2a157815cb29ad981b17 - WordPressShared: f93f99269258b46dad04f4e4dbf540ce2e5c1e66 - wpxmlrpc: 68db063041e85d186db21f674adf08d9c70627fd -PODFILE CHECKSUM: 64af6d71574c7a92d01a9446aa874e066917ebe5 +PODFILE CHECKSUM: c0da9313733b88a1d938ba6a329dd46b895c7dea COCOAPODS: 1.15.2 diff --git a/Sources/CoreAPI/HTTPRequestBuilder.swift b/Sources/CoreAPI/HTTPRequestBuilder.swift index 1cf8170bb..c230b7eff 100644 --- a/Sources/CoreAPI/HTTPRequestBuilder.swift +++ b/Sources/CoreAPI/HTTPRequestBuilder.swift @@ -1,12 +1,12 @@ import Foundation -import wpxmlrpc +@_implementationOnly import wpxmlrpc /// A builder type that appends HTTP request data to a URL. /// /// Calling this class's url related functions (the ones that changes path, query, etc) does not modify the /// original URL string. The URL will be perserved in the final result that's returned by the `build` function. -final class HTTPRequestBuilder { - enum Method: String, CaseIterable { +public final class HTTPRequestBuilder { + public enum Method: String, CaseIterable { case get = "GET" case post = "POST" case put = "PUT" diff --git a/Sources/CoreAPI/NonceRetrieval.swift b/Sources/CoreAPI/NonceRetrieval.swift index b9e85a0c4..14e13b15d 100644 --- a/Sources/CoreAPI/NonceRetrieval.swift +++ b/Sources/CoreAPI/NonceRetrieval.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared enum NonceRetrievalMethod { case newPostScrap diff --git a/Sources/CoreAPI/WordPressAPIError.swift b/Sources/CoreAPI/WordPressAPIError.swift index a4680b208..cd8493f0b 100644 --- a/Sources/CoreAPI/WordPressAPIError.swift +++ b/Sources/CoreAPI/WordPressAPIError.swift @@ -1,6 +1,6 @@ import Foundation -public enum WordPressAPIError: Error where EndpointError: LocalizedError { +@frozen public enum WordPressAPIError: Error where EndpointError: LocalizedError { static var unknownErrorMessage: String { NSLocalizedString( "wordpress-api.error.unknown", diff --git a/Sources/CoreAPI/WordPressComRestApi.swift b/Sources/CoreAPI/WordPressComRestApi.swift index a8ebeb262..8b96fb321 100644 --- a/Sources/CoreAPI/WordPressComRestApi.swift +++ b/Sources/CoreAPI/WordPressComRestApi.swift @@ -2,7 +2,6 @@ import APIInterface #endif import Foundation -import WordPressShared // MARK: - WordPressComRestApiError @@ -176,7 +175,7 @@ open class WordPressComRestApi: NSObject { } } - @objc func setInvalidTokenHandler(_ handler: @escaping () -> Void) { + @objc open func setInvalidTokenHandler(_ handler: @escaping () -> Void) { invalidTokenHandler = handler } @@ -363,7 +362,7 @@ open class WordPressComRestApi: NSObject { return configuration } - func perform( + open func perform( _ method: HTTPRequestBuilder.Method, URLString: String, parameters: [String: AnyObject]? = nil, @@ -374,7 +373,7 @@ open class WordPressComRestApi: NSObject { } } - func perform( + open func perform( _ method: HTTPRequestBuilder.Method, URLString: String, parameters: [String: AnyObject]? = nil, diff --git a/Sources/CoreAPI/WordPressOrgRestApi.swift b/Sources/CoreAPI/WordPressOrgRestApi.swift index 337ea2980..30adcb2d1 100644 --- a/Sources/CoreAPI/WordPressOrgRestApi.swift +++ b/Sources/CoreAPI/WordPressOrgRestApi.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public struct WordPressOrgRestApiError: LocalizedError, Decodable, HTTPURLResponseProviding { public enum CodingKeys: String, CodingKey { diff --git a/Sources/CoreAPI/WordPressOrgXMLRPCApi.swift b/Sources/CoreAPI/WordPressOrgXMLRPCApi.swift index 2698c89f5..a997efd18 100644 --- a/Sources/CoreAPI/WordPressOrgXMLRPCApi.swift +++ b/Sources/CoreAPI/WordPressOrgXMLRPCApi.swift @@ -1,5 +1,5 @@ import Foundation -import wpxmlrpc +@_implementationOnly import wpxmlrpc /// Class to connect to the XMLRPC API on self hosted sites. open class WordPressOrgXMLRPCApi: NSObject { @@ -28,6 +28,10 @@ open class WordPressOrgXMLRPCApi: NSObject { /// @objc public static let minimumSupportedVersion = "4.0" + @objc public static var errorDomain: String { + wpxmlrpc.WPXMLRPCFaultErrorDomain + } + private lazy var urlSession: URLSession = makeSession(configuration: .default) private lazy var uploadURLSession: URLSession = { backgroundUploads diff --git a/Sources/WordPressKit/Logging/WPKitLogging.h b/Sources/WordPressKit/Logging/WPKitLogging.h index 71b827aaf..76ed27760 100644 --- a/Sources/WordPressKit/Logging/WPKitLogging.h +++ b/Sources/WordPressKit/Logging/WPKitLogging.h @@ -1,11 +1,19 @@ #import -@import WordPressShared; - NS_ASSUME_NONNULL_BEGIN -FOUNDATION_EXTERN id _Nullable WPKitGetLoggingDelegate(void); -FOUNDATION_EXTERN void WPKitSetLoggingDelegate(id _Nullable logger); +@protocol WordPressKitLoggingDelegate + +- (void)logError:(NSString *)str; +- (void)logWarning:(NSString *)str; +- (void)logInfo:(NSString *)str; +- (void)logDebug:(NSString *)str; +- (void)logVerbose:(NSString *)str; + +@end + +FOUNDATION_EXTERN id _Nullable WPKitGetLoggingDelegate(void); +FOUNDATION_EXTERN void WPKitSetLoggingDelegate(id _Nullable logger); FOUNDATION_EXTERN void WPKitLogError(NSString *str, ...) NS_FORMAT_FUNCTION(1, 2); FOUNDATION_EXTERN void WPKitLogWarning(NSString *str, ...) NS_FORMAT_FUNCTION(1, 2); diff --git a/Sources/WordPressKit/Logging/WPKitLogging.m b/Sources/WordPressKit/Logging/WPKitLogging.m index 59757186b..d8a0607c9 100644 --- a/Sources/WordPressKit/Logging/WPKitLogging.m +++ b/Sources/WordPressKit/Logging/WPKitLogging.m @@ -1,20 +1,20 @@ #import "WPKitLogging.h" -static id wordPressKitLogger = nil; +static id wordPressKitLogger = nil; -id _Nullable WPKitGetLoggingDelegate(void) +id _Nullable WPKitGetLoggingDelegate(void) { return wordPressKitLogger; } -void WPKitSetLoggingDelegate(id _Nullable logger) +void WPKitSetLoggingDelegate(id _Nullable logger) { wordPressKitLogger = logger; } #define WPKitLogv(logFunc) \ ({ \ - id logger = WPKitGetLoggingDelegate(); \ + id logger = WPKitGetLoggingDelegate(); \ if (logger == NULL) { \ NSLog(@"[WordPressKit] Warning: please call `WPKitSetLoggingDelegate` to set a error logger."); \ return; \ diff --git a/Sources/WordPressKit/Models/AccountSettings.swift b/Sources/WordPressKit/Models/AccountSettings.swift index 7cf0ab200..91993fa40 100644 --- a/Sources/WordPressKit/Models/AccountSettings.swift +++ b/Sources/WordPressKit/Models/AccountSettings.swift @@ -53,7 +53,7 @@ public struct AccountSettings { } } -public enum AccountSettingsChange { +@frozen public enum AccountSettingsChange { case firstName(String) case lastName(String) case displayName(String) diff --git a/Sources/WordPressKit/Models/Activity.swift b/Sources/WordPressKit/Models/Activity.swift index cd6afb3a6..66bb996b9 100644 --- a/Sources/WordPressKit/Models/Activity.swift +++ b/Sources/WordPressKit/Models/Activity.swift @@ -182,7 +182,7 @@ public class ActivityGroup { public let name: String public let count: Int - init(_ groupKey: String, dictionary: [String: AnyObject]) throws { + public init(_ groupKey: String, dictionary: [String: AnyObject]) throws { guard let groupName = dictionary["name"] as? String else { throw Error.missingName } @@ -293,7 +293,7 @@ public class RestoreStatus { } public extension RestoreStatus { - enum Status: String { + @frozen enum Status: String { case queued case finished case running diff --git a/Sources/WordPressKit/Models/Atomic/AtomicLogs.swift b/Sources/WordPressKit/Models/Atomic/AtomicLogs.swift index bc04ca686..c0fcb7e31 100644 --- a/Sources/WordPressKit/Models/Atomic/AtomicLogs.swift +++ b/Sources/WordPressKit/Models/Atomic/AtomicLogs.swift @@ -9,7 +9,7 @@ public final class AtomicErrorLogEntry: Decodable { public let line: Int? public let timestamp: Date? - public enum Severity: String { + @frozen public enum Severity: String { case user = "User" case warning = "Warning" case deprecated = "Deprecated" diff --git a/Sources/WordPressKit/Models/Blaze/BlazeCampaign.swift b/Sources/WordPressKit/Models/Blaze/BlazeCampaign.swift index be303fc49..5ab9af988 100644 --- a/Sources/WordPressKit/Models/Blaze/BlazeCampaign.swift +++ b/Sources/WordPressKit/Models/Blaze/BlazeCampaign.swift @@ -44,7 +44,7 @@ public final class BlazeCampaign: Codable { case creativeHTML = "creativeHtml" } - public enum Status: String, Codable { + @frozen public enum Status: String, Codable { case scheduled case created case rejected diff --git a/Sources/WordPressKit/Models/JetpackBackup.swift b/Sources/WordPressKit/Models/JetpackBackup.swift index 2b8b0832c..6bd56dc2e 100644 --- a/Sources/WordPressKit/Models/JetpackBackup.swift +++ b/Sources/WordPressKit/Models/JetpackBackup.swift @@ -26,4 +26,15 @@ public struct JetpackBackup: Decodable { case url case validUntil } + + public init(backupPoint: Date, downloadID: Int, rewindID: String, startedAt: Date, progress: Int?, downloadCount: Int?, url: String?, validUntil: Date?) { + self.backupPoint = backupPoint + self.downloadID = downloadID + self.rewindID = rewindID + self.startedAt = startedAt + self.progress = progress + self.downloadCount = downloadCount + self.url = url + self.validUntil = validUntil + } } diff --git a/Sources/WordPressKit/Models/Plugins/PluginDirectoryEntry.swift b/Sources/WordPressKit/Models/Plugins/PluginDirectoryEntry.swift index e377de162..3bd0dd59e 100644 --- a/Sources/WordPressKit/Models/Plugins/PluginDirectoryEntry.swift +++ b/Sources/WordPressKit/Models/Plugins/PluginDirectoryEntry.swift @@ -77,7 +77,7 @@ extension PluginDirectoryEntry: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let decodedName = try container.decode(String.self, forKey: .name) - name = decodedName.stringByDecodingXMLCharacters() + name = decodedName.wpkit_stringByDecodingXMLCharacters() slug = try container.decode(String.self, forKey: .slug) version = try? container.decode(String.self, forKey: .version) lastUpdated = try? container.decode(Date.self, forKey: .lastUpdated) @@ -115,7 +115,7 @@ extension PluginDirectoryEntry: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(name.stringByEncodingXMLCharacters(), forKey: .name) + try container.encode(name.wpkit_stringByEncodingXMLCharacters(), forKey: .name) try container.encode(slug, forKey: .slug) try container.encodeIfPresent(version, forKey: .version) try container.encodeIfPresent(lastUpdated, forKey: .lastUpdated) diff --git a/Sources/WordPressKit/Models/Plugins/PluginState.swift b/Sources/WordPressKit/Models/Plugins/PluginState.swift index 62f0dbea6..8123998d3 100644 --- a/Sources/WordPressKit/Models/Plugins/PluginState.swift +++ b/Sources/WordPressKit/Models/Plugins/PluginState.swift @@ -1,7 +1,7 @@ import Foundation public struct PluginState: Equatable, Codable { - public enum UpdateState: Equatable, Codable { + @frozen public enum UpdateState: Equatable, Codable { public static func ==(lhs: PluginState.UpdateState, rhs: PluginState.UpdateState) -> Bool { switch (lhs, rhs) { case (.updated, .updated): diff --git a/Sources/WordPressKit/Models/RemoteBlog.swift b/Sources/WordPressKit/Models/RemoteBlog.swift index 3351e0594..99bea7dc9 100644 --- a/Sources/WordPressKit/Models/RemoteBlog.swift +++ b/Sources/WordPressKit/Models/RemoteBlog.swift @@ -1,5 +1,5 @@ import Foundation -import NSObject_SafeExpectations +@_implementationOnly import NSObject_SafeExpectations /// This class encapsulates all of the *remote* Blog properties @objcMembers public class RemoteBlog: NSObject { diff --git a/Sources/WordPressKit/Models/RemoteBlogOptionsHelper.swift b/Sources/WordPressKit/Models/RemoteBlogOptionsHelper.swift index 184960f36..a5eca5414 100644 --- a/Sources/WordPressKit/Models/RemoteBlogOptionsHelper.swift +++ b/Sources/WordPressKit/Models/RemoteBlogOptionsHelper.swift @@ -8,6 +8,9 @@ import Foundation if response.number(forKey: "jetpack")?.boolValue == true { options["jetpack_client_id"] = response.number(forKey: "ID") } + if response.number(forKey: "is_wpcom_staging_site")?.boolValue == true { + options["is_wpcom_staging_site"] = true + } if response["options"] != nil { options["post_thumbnail"] = response.value(forKeyPath: "options.featured_images_enabled") @@ -28,6 +31,7 @@ import Foundation "blog_public", "max_upload_size", "is_wpcom_atomic", + "is_wpcom_staging_site", "is_wpforteams_site", "show_on_front", "page_on_front", @@ -68,8 +72,8 @@ import Foundation public class func remoteBlogSettings(fromXMLRPCDictionaryOptions options: NSDictionary) -> RemoteBlogSettings { let remoteSettings = RemoteBlogSettings() - remoteSettings.name = options.string(forKeyPath: "blog_title.value")?.stringByDecodingXMLCharacters() - remoteSettings.tagline = options.string(forKeyPath: "blog_tagline.value")?.stringByDecodingXMLCharacters() + remoteSettings.name = options.string(forKeyPath: "blog_title.value")?.wpkit_stringByDecodingXMLCharacters() + remoteSettings.tagline = options.string(forKeyPath: "blog_tagline.value")?.wpkit_stringByDecodingXMLCharacters() if options["blog_public"] != nil { remoteSettings.privacy = options.number(forKeyPath: "blog_public.value") } diff --git a/Sources/WordPressKit/Models/RemoteNotification.swift b/Sources/WordPressKit/Models/RemoteNotification.swift index 689235732..f97bfa2c0 100644 --- a/Sources/WordPressKit/Models/RemoteNotification.swift +++ b/Sources/WordPressKit/Models/RemoteNotification.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared // MARK: - RemoteNotification // diff --git a/Sources/WordPressKit/Models/RemoteNotificationSettings.swift b/Sources/WordPressKit/Models/RemoteNotificationSettings.swift index 1c8cba2e1..bb9f6632e 100644 --- a/Sources/WordPressKit/Models/RemoteNotificationSettings.swift +++ b/Sources/WordPressKit/Models/RemoteNotificationSettings.swift @@ -21,7 +21,7 @@ open class RemoteNotificationSettings { /// Represents a communication channel that may post notifications to the user. /// - public enum Channel: Equatable { + @frozen public enum Channel: Equatable { case blog(blogId: Int) case other case wordPressCom diff --git a/Sources/WordPressKit/Models/RemotePerson.swift b/Sources/WordPressKit/Models/RemotePerson.swift index 3ce0c80e5..3306be67d 100644 --- a/Sources/WordPressKit/Models/RemotePerson.swift +++ b/Sources/WordPressKit/Models/RemotePerson.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared // MARK: - Defines all of the peroperties a Person may have // diff --git a/Sources/WordPressKit/Models/RemoteReaderPost.m b/Sources/WordPressKit/Models/RemoteReaderPost.m index ee0032eda..889a903c7 100644 --- a/Sources/WordPressKit/Models/RemoteReaderPost.m +++ b/Sources/WordPressKit/Models/RemoteReaderPost.m @@ -3,7 +3,6 @@ #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; // REST Post dictionary keys NSString * const PostRESTKeyAttachments = @"attachments"; @@ -95,7 +94,7 @@ - (instancetype)initWithDictionary:(NSDictionary *)dict; self.authorID = [authorDict numberForKey:PostRESTKeyID]; self.author = [self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyNiceName]]; // typically the author's screen name self.authorAvatarURL = [self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyAvatarURL]]; - self.authorDisplayName = [[self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyName]] stringByDecodingXMLCharacters]; // Typically the author's given name + self.authorDisplayName = [[self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyName]] wpkit_stringByDecodingXMLCharacters]; // Typically the author's given name self.authorEmail = [self authorEmailFromAuthorDictionary:authorDict]; self.authorURL = [self stringOrEmptyString:[authorDict stringForKey:PostRESTKeyURL]]; self.siteIconURL = [self stringOrEmptyString:[dict stringForKeyPath:@"meta.data.site.icon.img"]]; @@ -205,8 +204,8 @@ - (RemoteReaderCrossPostMeta *)crossPostMetaFromPostDictionary:(NSDictionary *)d } else if ([[obj stringForKey:CrossPostMetaKey] isEqualToString:CrossPostMetaXPostOrigin]) { NSString *value = [obj stringForKey:CrossPostMetaValue]; NSArray *IDS = [value componentsSeparatedByString:@":"]; - meta.siteID = [[IDS firstObject] numericValue]; - meta.postID = [[IDS lastObject] numericValue]; + meta.siteID = [[IDS firstObject] wpkit_numericValue]; + meta.postID = [[IDS lastObject] wpkit_numericValue]; crossPostMetaFound = YES; } @@ -271,8 +270,8 @@ - (NSDictionary *)primaryAndSecondaryTagsFromPostDictionary:(NSDictionary *)dict primaryTagSlug = editorialSlug; } - primaryTag = [primaryTag stringByDecodingXMLCharacters]; - secondaryTag = [secondaryTag stringByDecodingXMLCharacters]; + primaryTag = [primaryTag wpkit_stringByDecodingXMLCharacters]; + secondaryTag = [secondaryTag wpkit_stringByDecodingXMLCharacters]; return @{ TagKeyPrimary:primaryTag, @@ -473,7 +472,7 @@ - (NSDate *)sortDateFromPostDictionary:(NSDictionary *)dict sortDate = editorialDate; } - return [DateUtils dateFromISOString:sortDate]; + return [WPKitDateUtils dateFromISOString:sortDate]; } /** @@ -525,7 +524,7 @@ - (NSString *)featuredMediaImageFromPostDictionary:(NSDictionary *)dict { - (NSString *)suitableImageFromPostContent:(NSDictionary *)dict { NSString *content = [dict stringForKey:PostRESTKeyContent]; - NSString *imageToDisplay = [DisplayableImageHelper searchPostContentForImageToDisplay:content]; + NSString *imageToDisplay = [WPKitDisplayableImageHelper searchPostContentForImageToDisplay:content]; return [self stringOrEmptyString:imageToDisplay]; } @@ -651,7 +650,7 @@ - (BOOL)siteIsPrivateFromPostDictionary:(NSDictionary *)dict - (NSArray *)slugsFromDiscoverPostTaxonomies:(NSArray *)discoverPostTaxonomies { - return [discoverPostTaxonomies wp_map:^id(NSDictionary *dict) { + return [discoverPostTaxonomies wpkit_map:^id(NSDictionary *dict) { return [dict stringForKey:PostRESTKeySlug]; }]; } @@ -689,7 +688,7 @@ - (NSString *)formatSummary:(NSString *)summary */ - (NSString *)createSummaryFromContent:(NSString *)string { - return [string summarized]; + return [string wpkit_summarized]; } /** @@ -700,7 +699,7 @@ - (NSString *)createSummaryFromContent:(NSString *)string */ - (NSString *)makePlainText:(NSString *)string { - return [string summarized]; + return [string wpkit_summarized]; } /** @@ -711,7 +710,7 @@ - (NSString *)makePlainText:(NSString *)string */ - (NSString *)titleFromSummary:(NSString *)summary { - return [summary stringByEllipsizingWithMaxLength:ReaderPostTitleLength preserveWords:YES]; + return [summary wpkit_stringByEllipsizingWithMaxLength:ReaderPostTitleLength preserveWords:YES]; } diff --git a/Sources/WordPressKit/Models/RemoteReaderSiteInfo.swift b/Sources/WordPressKit/Models/RemoteReaderSiteInfo.swift index cde802405..01807e4b2 100644 --- a/Sources/WordPressKit/Models/RemoteReaderSiteInfo.swift +++ b/Sources/WordPressKit/Models/RemoteReaderSiteInfo.swift @@ -1,5 +1,5 @@ import Foundation -import NSObject_SafeExpectations +@_implementationOnly import NSObject_SafeExpectations // Site Topic Keys private let SiteDictionaryFeedIDKey = "feed_ID" diff --git a/Sources/WordPressKit/Models/Stats/Emails/StatsEmailsSummaryData.swift b/Sources/WordPressKit/Models/Stats/Emails/StatsEmailsSummaryData.swift index fe9fb03a0..55267e0c7 100644 --- a/Sources/WordPressKit/Models/Stats/Emails/StatsEmailsSummaryData.swift +++ b/Sources/WordPressKit/Models/Stats/Emails/StatsEmailsSummaryData.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public struct StatsEmailsSummaryData: Decodable, Equatable { public let posts: [Post] diff --git a/Sources/WordPressKit/Models/Stats/Insights/StatsLastPostInsight.swift b/Sources/WordPressKit/Models/Stats/Insights/StatsLastPostInsight.swift index 6241a72d7..9319c951d 100644 --- a/Sources/WordPressKit/Models/Stats/Insights/StatsLastPostInsight.swift +++ b/Sources/WordPressKit/Models/Stats/Insights/StatsLastPostInsight.swift @@ -1,3 +1,5 @@ +import Foundation + public struct StatsLastPostInsight: Equatable, Decodable { public let title: String public let url: URL @@ -79,7 +81,7 @@ extension StatsLastPostInsight { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - title = try container.decode(String.self, forKey: .title).trimmingCharacters(in: .whitespaces).stringByDecodingXMLCharacters() + title = try container.decode(String.self, forKey: .title).trimmingCharacters(in: .whitespaces).wpkit_stringByDecodingXMLCharacters() url = try container.decode(URL.self, forKey: .url) let dateString = try container.decode(String.self, forKey: .publishedDate) guard let date = StatsLastPostInsight.dateFormatter.date(from: dateString) else { diff --git a/Sources/WordPressKit/Models/Stats/Insights/StatsTagsAndCategoriesInsight.swift b/Sources/WordPressKit/Models/Stats/Insights/StatsTagsAndCategoriesInsight.swift index 8daaad19e..4af5e2a42 100644 --- a/Sources/WordPressKit/Models/Stats/Insights/StatsTagsAndCategoriesInsight.swift +++ b/Sources/WordPressKit/Models/Stats/Insights/StatsTagsAndCategoriesInsight.swift @@ -13,7 +13,7 @@ extension StatsTagsAndCategoriesInsight: StatsInsightData { } public struct StatsTagAndCategory: Codable { - public enum Kind: String, Codable { + @frozen public enum Kind: String, Codable { case tag case category case folder diff --git a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift index 5ed9ee163..aece6030d 100644 --- a/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift +++ b/Sources/WordPressKit/Models/Stats/StatsSubscribersSummaryData.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public struct StatsSubscribersSummaryData: Equatable { public let history: [SubscriberData] diff --git a/Sources/WordPressKit/Models/Stats/Time Interval/StatsSummaryTimeIntervalData.swift b/Sources/WordPressKit/Models/Stats/Time Interval/StatsSummaryTimeIntervalData.swift index d24447fc3..acefd4b72 100644 --- a/Sources/WordPressKit/Models/Stats/Time Interval/StatsSummaryTimeIntervalData.swift +++ b/Sources/WordPressKit/Models/Stats/Time Interval/StatsSummaryTimeIntervalData.swift @@ -1,11 +1,11 @@ -public enum StatsPeriodUnit: Int { +@frozen public enum StatsPeriodUnit: Int { case day case week case month case year } -public enum StatsSummaryType: Int { +@frozen public enum StatsSummaryType: Int { case views case visitors case likes diff --git a/Sources/WordPressKit/Models/Stats/Time Interval/StatsTopAuthorsTimeIntervalData.swift b/Sources/WordPressKit/Models/Stats/Time Interval/StatsTopAuthorsTimeIntervalData.swift index e5e592f81..63b90b311 100644 --- a/Sources/WordPressKit/Models/Stats/Time Interval/StatsTopAuthorsTimeIntervalData.swift +++ b/Sources/WordPressKit/Models/Stats/Time Interval/StatsTopAuthorsTimeIntervalData.swift @@ -32,7 +32,7 @@ public struct StatsTopAuthor { public struct StatsTopPost { - public enum Kind { + @frozen public enum Kind { case unknown case post case page diff --git a/Sources/WordPressKit/Models/WPTimeZone.swift b/Sources/WordPressKit/Models/WPTimeZone.swift index 8974acef8..9654d2ad4 100644 --- a/Sources/WordPressKit/Models/WPTimeZone.swift +++ b/Sources/WordPressKit/Models/WPTimeZone.swift @@ -30,6 +30,11 @@ public struct NamedTimeZone: WPTimeZone { public let label: String public let value: String + public init(label: String, value: String) { + self.label = label + self.value = value + } + public var gmtOffset: Float? { return nil } diff --git a/Sources/WordPressKit/Services/AccountServiceRemoteREST+SocialService.swift b/Sources/WordPressKit/Services/AccountServiceRemoteREST+SocialService.swift index 02432a1f6..c487ba4a1 100644 --- a/Sources/WordPressKit/Services/AccountServiceRemoteREST+SocialService.swift +++ b/Sources/WordPressKit/Services/AccountServiceRemoteREST+SocialService.swift @@ -1,6 +1,6 @@ import Foundation -public enum SocialServiceName: String { +@frozen public enum SocialServiceName: String { case google case apple } diff --git a/Sources/WordPressKit/Services/AccountServiceRemoteREST.m b/Sources/WordPressKit/Services/AccountServiceRemoteREST.m index 2d938c78e..a8290c2ba 100644 --- a/Sources/WordPressKit/Services/AccountServiceRemoteREST.m +++ b/Sources/WordPressKit/Services/AccountServiceRemoteREST.m @@ -1,7 +1,6 @@ #import "AccountServiceRemoteREST.h" #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; static NSString * const UserDictionaryIDKey = @"ID"; static NSString * const UserDictionaryUsernameKey = @"username"; @@ -377,7 +376,7 @@ - (RemoteUser *)remoteUserFromDictionary:(NSDictionary *)dictionary remoteUser.displayName = [dictionary stringForKey:UserDictionaryDisplaynameKey]; remoteUser.primaryBlogID = [dictionary numberForKey:UserDictionaryPrimaryBlogKey]; remoteUser.avatarURL = [dictionary stringForKey:UserDictionaryAvatarURLKey]; - remoteUser.dateCreated = [NSDate dateWithISO8601String:[dictionary stringForKey:UserDictionaryDateKey]]; + remoteUser.dateCreated = [NSDate wpkit_dateWithISO8601String:[dictionary stringForKey:UserDictionaryDateKey]]; remoteUser.emailVerified = [[dictionary numberForKey:UserDictionaryEmailVerifiedKey] boolValue]; return remoteUser; @@ -386,7 +385,7 @@ - (RemoteUser *)remoteUserFromDictionary:(NSDictionary *)dictionary - (NSArray *)remoteBlogsFromJSONArray:(NSArray *)jsonBlogs { NSArray *blogs = jsonBlogs; - return [blogs wp_map:^id(NSDictionary *jsonBlog) { + return [blogs wpkit_map:^id(NSDictionary *jsonBlog) { return [[RemoteBlog alloc] initWithJSONDictionary:jsonBlog]; }]; return blogs; diff --git a/Sources/WordPressKit/Services/AccountSettingsRemote.swift b/Sources/WordPressKit/Services/AccountSettingsRemote.swift index e029ab5d3..7ed7751bb 100644 --- a/Sources/WordPressKit/Services/AccountSettingsRemote.swift +++ b/Sources/WordPressKit/Services/AccountSettingsRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public class AccountSettingsRemote: ServiceRemoteWordPressComREST { @objc public static let remotes = NSMapTable(keyOptions: NSPointerFunctions.Options(), valueOptions: NSPointerFunctions.Options.weakMemory) @@ -178,7 +177,7 @@ public class AccountSettingsRemote: ServiceRemoteWordPressComREST { throw ResponseError.decodingFailure } - let aboutMeText = aboutMe.decodingXMLCharacters() + let aboutMeText = aboutMe.wpkit_stringByDecodingXMLCharacters() return AccountSettings(firstName: firstName, lastName: lastName, diff --git a/Sources/WordPressKit/Services/ActivityServiceRemote.swift b/Sources/WordPressKit/Services/ActivityServiceRemote.swift index d81ab3273..317462bb4 100644 --- a/Sources/WordPressKit/Services/ActivityServiceRemote.swift +++ b/Sources/WordPressKit/Services/ActivityServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared open class ActivityServiceRemote: ServiceRemoteWordPressComREST { diff --git a/Sources/WordPressKit/Services/AnnouncementServiceRemote.swift b/Sources/WordPressKit/Services/AnnouncementServiceRemote.swift index 343680b37..fe5e24f29 100644 --- a/Sources/WordPressKit/Services/AnnouncementServiceRemote.swift +++ b/Sources/WordPressKit/Services/AnnouncementServiceRemote.swift @@ -1,9 +1,9 @@ import Foundation /// Retrieves feature announcements from the related endpoint -public class AnnouncementServiceRemote: ServiceRemoteWordPressComREST { +open class AnnouncementServiceRemote: ServiceRemoteWordPressComREST { - public func getAnnouncements(appId: String, + open func getAnnouncements(appId: String, appVersion: String, locale: String, completion: @escaping (Result<[Announcement], Error>) -> Void) { @@ -86,6 +86,18 @@ public struct Announcement: Codable { public let isLocalized: Bool public let responseLocale: String public let features: [Feature] + + public init(appVersionName: String, minimumAppVersion: String, maximumAppVersion: String, appVersionTargets: [String], detailsUrl: String, announcementVersion: String, isLocalized: Bool, responseLocale: String, features: [Feature]) { + self.appVersionName = appVersionName + self.minimumAppVersion = minimumAppVersion + self.maximumAppVersion = maximumAppVersion + self.appVersionTargets = appVersionTargets + self.detailsUrl = detailsUrl + self.announcementVersion = announcementVersion + self.isLocalized = isLocalized + self.responseLocale = responseLocale + self.features = features + } } public struct Feature: Codable { @@ -94,6 +106,14 @@ public struct Feature: Codable { public let icons: [FeatureIcon]? public let iconUrl: String public let iconBase64: String? + + public init(title: String, subtitle: String, icons: [FeatureIcon]?, iconUrl: String, iconBase64: String?) { + self.title = title + self.subtitle = subtitle + self.icons = icons + self.iconUrl = iconUrl + self.iconBase64 = iconBase64 + } } public struct FeatureIcon: Codable { diff --git a/Sources/WordPressKit/Services/AutomatedTransferService.swift b/Sources/WordPressKit/Services/AutomatedTransferService.swift index 168783e89..13b87ad68 100644 --- a/Sources/WordPressKit/Services/AutomatedTransferService.swift +++ b/Sources/WordPressKit/Services/AutomatedTransferService.swift @@ -3,11 +3,11 @@ import Foundation /// Class encapsualting all requests related to performing Automated Transfer operations. public class AutomatedTransferService: ServiceRemoteWordPressComREST { - public enum ResponseError: Error { + @frozen public enum ResponseError: Error { case decodingFailure } - public enum AutomatedTransferEligibilityError: Error { + @frozen public enum AutomatedTransferEligibilityError: Error { case unverifiedEmail case excessiveDiskSpaceUsage case noBusinessPlan diff --git a/Sources/WordPressKit/Services/BlogJetpackSettingsServiceRemote.swift b/Sources/WordPressKit/Services/BlogJetpackSettingsServiceRemote.swift index 7ac03196a..d12342a2a 100644 --- a/Sources/WordPressKit/Services/BlogJetpackSettingsServiceRemote.swift +++ b/Sources/WordPressKit/Services/BlogJetpackSettingsServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public class BlogJetpackSettingsServiceRemote: ServiceRemoteWordPressComREST { diff --git a/Sources/WordPressKit/Services/BlogServiceRemoteREST.m b/Sources/WordPressKit/Services/BlogServiceRemoteREST.m index b8b3f140e..69b22af9b 100644 --- a/Sources/WordPressKit/Services/BlogServiceRemoteREST.m +++ b/Sources/WordPressKit/Services/BlogServiceRemoteREST.m @@ -4,7 +4,6 @@ #import "RemotePostType.h" #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; #pragma mark - Parsing Keys static NSString * const RemoteBlogNameKey = @"name"; @@ -143,7 +142,7 @@ - (void)syncPostTypesWithSuccess:(PostTypesHandler)success success:^(NSDictionary *responseObject, NSHTTPURLResponse *httpResponse) { NSAssert([responseObject isKindOfClass:[NSDictionary class]], @"Response should be a dictionary."); - NSArray *postTypes = [[responseObject arrayForKey:RemotePostTypesKey] wp_map:^id(NSDictionary *json) { + NSArray *postTypes = [[responseObject arrayForKey:RemotePostTypesKey] wpkit_map:^id(NSDictionary *json) { return [self remotePostTypeWithDictionary:json]; }]; if (!postTypes.count) { @@ -338,7 +337,7 @@ - (NSString *)pathForSettings - (NSArray *)usersFromJSONArray:(NSArray *)jsonUsers { - return [jsonUsers wp_map:^RemoteUser *(NSDictionary *jsonUser) { + return [jsonUsers wpkit_map:^RemoteUser *(NSDictionary *jsonUser) { return [self userFromJSONDictionary:jsonUser]; }]; } diff --git a/Sources/WordPressKit/Services/BlogServiceRemoteXMLRPC.m b/Sources/WordPressKit/Services/BlogServiceRemoteXMLRPC.m index 9870919dd..3d7ff90a9 100644 --- a/Sources/WordPressKit/Services/BlogServiceRemoteXMLRPC.m +++ b/Sources/WordPressKit/Services/BlogServiceRemoteXMLRPC.m @@ -3,7 +3,6 @@ #import "RemotePostType.h" #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; static NSString * const RemotePostTypeNameKey = @"name"; static NSString * const RemotePostTypeLabelKey = @"label"; @@ -47,7 +46,7 @@ - (void)getAllAuthorsWithRemoteUsers:(NSMutableArray *)remoteUsers [self.api callMethod:@"wp.getUsers" parameters:parameters success:^(id responseObject, NSHTTPURLResponse *response) { - NSArray *responseUsers = [[responseObject allObjects] wp_map:^id(NSDictionary *xmlrpcUser) { + NSArray *responseUsers = [[responseObject allObjects] wpkit_map:^id(NSDictionary *xmlrpcUser) { return [self remoteUserFromXMLRPCDictionary:xmlrpcUser]; }]; @@ -80,7 +79,7 @@ - (void)syncPostTypesWithSuccess:(PostTypesHandler)success failure:(void (^)(NSE success:^(id responseObject, NSHTTPURLResponse *response) { NSAssert([responseObject isKindOfClass:[NSDictionary class]], @"Response should be a dictionary."); - NSArray *postTypes = [[responseObject allObjects] wp_map:^id(NSDictionary *json) { + NSArray *postTypes = [[responseObject allObjects] wpkit_map:^id(NSDictionary *json) { return [self remotePostTypeFromXMLRPCDictionary:json]; }]; if (!postTypes.count) { diff --git a/Sources/WordPressKit/Services/CommentServiceRemoteREST.m b/Sources/WordPressKit/Services/CommentServiceRemoteREST.m index 0f13fddd6..4b0f37616 100644 --- a/Sources/WordPressKit/Services/CommentServiceRemoteREST.m +++ b/Sources/WordPressKit/Services/CommentServiceRemoteREST.m @@ -4,7 +4,6 @@ #import "RemoteUser.h" @import NSObject_SafeExpectations; -@import WordPressShared; @implementation CommentServiceRemoteREST @@ -464,7 +463,7 @@ - (void)getLikesForCommentID:(NSNumber *)commentID - (NSArray *)remoteCommentsFromJSONArray:(NSArray *)jsonComments { - return [jsonComments wp_map:^id(NSDictionary *jsonComment) { + return [jsonComments wpkit_map:^id(NSDictionary *jsonComment) { return [self remoteCommentFromJSONDictionary:jsonComment]; }]; } @@ -530,7 +529,7 @@ - (NSString *)remoteStatusWithStatus:(NSString *)status commentID:(NSNumber *)commentID siteID:(NSNumber *)siteID { - return [jsonUsers wp_map:^id(NSDictionary *jsonUser) { + return [jsonUsers wpkit_map:^id(NSDictionary *jsonUser) { return [[RemoteLikeUser alloc] initWithDictionary:jsonUser commentID:commentID siteID:siteID]; }]; } diff --git a/Sources/WordPressKit/Services/CommentServiceRemoteXMLRPC.m b/Sources/WordPressKit/Services/CommentServiceRemoteXMLRPC.m index dfe05b5c0..891c6c7c3 100644 --- a/Sources/WordPressKit/Services/CommentServiceRemoteXMLRPC.m +++ b/Sources/WordPressKit/Services/CommentServiceRemoteXMLRPC.m @@ -3,7 +3,6 @@ #import "RemoteComment.h" @import wpxmlrpc; -@import WordPressShared; @import NSObject_SafeExpectations; @implementation CommentServiceRemoteXMLRPC @@ -204,7 +203,7 @@ - (void)trashComment:(RemoteComment *)comment - (NSArray *)remoteCommentsFromXMLRPCArray:(NSArray *)xmlrpcArray { - return [xmlrpcArray wp_map:^id(NSDictionary *xmlrpcComment) { + return [xmlrpcArray wpkit_map:^id(NSDictionary *xmlrpcComment) { return [self remoteCommentFromXMLRPCDictionary:xmlrpcComment]; }]; } diff --git a/Sources/WordPressKit/Services/EditorServiceRemote.swift b/Sources/WordPressKit/Services/EditorServiceRemote.swift index 1d5ba5f87..c6a7bf4d4 100644 --- a/Sources/WordPressKit/Services/EditorServiceRemote.swift +++ b/Sources/WordPressKit/Services/EditorServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public class EditorServiceRemote: ServiceRemoteWordPressComREST { public func postDesignateMobileEditor(_ siteID: Int, editor: EditorSettings.Mobile, success: @escaping (EditorSettings) -> Void, failure: @escaping (Error) -> Void) { diff --git a/Sources/WordPressKit/Services/GravatarServiceRemote.swift b/Sources/WordPressKit/Services/GravatarServiceRemote.swift index 2f12cb9e2..9604fc0cf 100644 --- a/Sources/WordPressKit/Services/GravatarServiceRemote.swift +++ b/Sources/WordPressKit/Services/GravatarServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared /// This ServiceRemote encapsulates all of the interaction with the Gravatar endpoint. /// diff --git a/Sources/WordPressKit/Services/JetpackBackupServiceRemote.swift b/Sources/WordPressKit/Services/JetpackBackupServiceRemote.swift index ce4429156..3e6138978 100644 --- a/Sources/WordPressKit/Services/JetpackBackupServiceRemote.swift +++ b/Sources/WordPressKit/Services/JetpackBackupServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared open class JetpackBackupServiceRemote: ServiceRemoteWordPressComREST { diff --git a/Sources/WordPressKit/Services/JetpackScanServiceRemote.swift b/Sources/WordPressKit/Services/JetpackScanServiceRemote.swift index 0682d7b3c..4ae9f7252 100644 --- a/Sources/WordPressKit/Services/JetpackScanServiceRemote.swift +++ b/Sources/WordPressKit/Services/JetpackScanServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public class JetpackScanServiceRemote: ServiceRemoteWordPressComREST { // MARK: - Scanning diff --git a/Sources/WordPressKit/Services/MediaServiceRemoteREST.m b/Sources/WordPressKit/Services/MediaServiceRemoteREST.m index 5c50b0854..90f38edf6 100644 --- a/Sources/WordPressKit/Services/MediaServiceRemoteREST.m +++ b/Sources/WordPressKit/Services/MediaServiceRemoteREST.m @@ -1,7 +1,7 @@ #import "MediaServiceRemoteREST.h" #import "RemoteMedia.h" #import "WPKit-Swift.h" -@import WordPressShared; + @import NSObject_SafeExpectations; const NSInteger WPRestErrorCodeMediaNew = 10; @@ -386,7 +386,7 @@ -(void)getVideoPressToken:(NSString *)videoPressID + (NSArray *)remoteMediaFromJSONArray:(NSArray *)jsonMedia { - return [jsonMedia wp_map:^id(NSDictionary *json) { + return [jsonMedia wpkit_map:^id(NSDictionary *json) { return [self remoteMediaFromJSONDictionary:json]; }]; } diff --git a/Sources/WordPressKit/Services/MediaServiceRemoteXMLRPC.m b/Sources/WordPressKit/Services/MediaServiceRemoteXMLRPC.m index 0d0059eac..5f01cf01a 100644 --- a/Sources/WordPressKit/Services/MediaServiceRemoteXMLRPC.m +++ b/Sources/WordPressKit/Services/MediaServiceRemoteXMLRPC.m @@ -2,7 +2,6 @@ #import "RemoteMedia.h" #import "WPKit-Swift.h" -@import WordPressShared; @import NSObject_SafeExpectations; @implementation MediaServiceRemoteXMLRPC @@ -65,7 +64,7 @@ - (void)getMediaLibraryStartOffset:(NSUInteger)offset pageLoad(pageMedia); } NSUInteger newOffset = offset + pageSize; - [self getMediaLibraryStartOffset:newOffset media:resultMedia pageLoad:pageLoad success: success failure: failure]; + [self getMediaLibraryStartOffset:newOffset media:resultMedia pageLoad:pageLoad success: success failure: failure]; } failure:^(NSError *error, NSHTTPURLResponse *httpResponse) { if (failure) { @@ -105,7 +104,7 @@ - (NSURLCredential *)findCredentialForHost:(NSString *)host port:(NSInteger)port [[[NSURLCredentialStorage sharedCredentialStorage] allCredentials] enumerateKeysAndObjectsUsingBlock:^(NSURLProtectionSpace *ps, NSDictionary *dict, BOOL *stop) { [dict enumerateKeysAndObjectsUsingBlock:^(id key, NSURLCredential *credential, BOOL *stop) { if ([[ps host] isEqualToString:host] && [ps port] == port) - + { foundCredential = credential; *stop = YES; @@ -118,9 +117,9 @@ - (NSURLCredential *)findCredentialForHost:(NSString *)host port:(NSInteger)port return foundCredential; } -/** +/** Adds a basic auth header to a request if a credential is stored for that specific host. - + The credentials will only be added if a set of credentials for the request host are stored on the shared credential storage @param request The request to where the authentication information will be added. */ @@ -175,7 +174,7 @@ - (void)uploadMedia:(RemoteMedia *)media NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadServerResponse userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"The server returned an empty response. This usually means you need to increase the memory limit for your site.", @"")}]; if (failure) { failure(error); - } + } } else { localProgress.completedUnitCount=localProgress.totalUnitCount; RemoteMedia * remoteMedia = [self remoteMediaFromXMLRPCDictionary:response]; @@ -183,13 +182,13 @@ - (void)uploadMedia:(RemoteMedia *)media success(remoteMedia); } } - } failure:^(NSError *error, NSHTTPURLResponse *httpResponse) { + } failure:^(NSError *error, NSHTTPURLResponse *httpResponse) { if (failure) { failure(error); } }]; - + if (progress) { *progress = localProgress; } @@ -199,10 +198,44 @@ - (void)updateMedia:(RemoteMedia *)media success:(void (^)(RemoteMedia *remoteMedia))success failure:(void (^)(NSError *error))failure { - //HACK: Sergio Estevao: 2016-04-06 this option doens't exist on XML-RPC so we will always say that all was good - if (success) { - success(media); + NSParameterAssert([media.mediaID longLongValue] > 0); + + NSMutableDictionary *content = [NSMutableDictionary dictionary]; + + if (media.title != nil) { + content[@"post_title"] = media.title; + } + + if (media.caption != nil) { + content[@"post_excerpt"] = media.caption; } + + if (media.descriptionText != nil) { + content[@"post_content"] = media.descriptionText; + } + + NSArray *extraDefaults = @[media.mediaID]; + NSArray *parameters = [self XMLRPCArgumentsWithExtraDefaults:extraDefaults andExtra:content]; + + [self.api callMethod:@"wp.editPost" + parameters:parameters + success:^(id responseObject, NSHTTPURLResponse *httpResponse) { + BOOL updated = [responseObject boolValue]; + if (updated) { + if (success) { + success(media); + } + } else { + if (failure) { + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:nil]; + failure(error); + } + } + } failure:^(NSError *error, NSHTTPURLResponse *httpResponse) { + if (failure) { + failure(error); + } + }]; } - (void)deleteMedia:(RemoteMedia *)media @@ -264,7 +297,7 @@ -(void)getVideoPressToken:(NSString *)videoPressID - (NSArray *)remoteMediaFromXMLRPCArray:(NSArray *)xmlrpcArray { - return [xmlrpcArray wp_map:^id(NSDictionary *xmlrpcMedia) { + return [xmlrpcArray wpkit_map:^id(NSDictionary *xmlrpcMedia) { return [self remoteMediaFromXMLRPCDictionary:xmlrpcMedia]; }]; } @@ -286,11 +319,11 @@ - (RemoteMedia *)remoteMediaFromXMLRPCDictionary:(NSDictionary*)xmlRPC link = [xmlRPC stringForKeyPath:@"link"]; } remoteMedia.file = [link lastPathComponent] ?: [[xmlRPC objectForKeyPath:@"file"] lastPathComponent]; - + if ([xmlRPC stringForKeyPath:@"metadata.sizes.large.file"] != nil) { remoteMedia.largeURL = [NSURL URLWithString: [NSString stringWithFormat:@"%@%@", remoteMedia.url.URLByDeletingLastPathComponent, [xmlRPC stringForKeyPath:@"metadata.sizes.large.file"]]]; } - + if ([xmlRPC stringForKeyPath:@"metadata.sizes.medium.file"] != nil) { remoteMedia.mediumURL = [NSURL URLWithString: [NSString stringWithFormat:@"%@%@", remoteMedia.url.URLByDeletingLastPathComponent, [xmlRPC stringForKeyPath:@"metadata.sizes.medium.file"]]]; } diff --git a/Sources/WordPressKit/Services/MenusServiceRemote.m b/Sources/WordPressKit/Services/MenusServiceRemote.m index e7c0b1453..bed046cd5 100644 --- a/Sources/WordPressKit/Services/MenusServiceRemote.m +++ b/Sources/WordPressKit/Services/MenusServiceRemote.m @@ -1,6 +1,6 @@ #import "MenusServiceRemote.h" #import "WPKit-Swift.h" -@import WordPressShared; + @import NSObject_SafeExpectations; NS_ASSUME_NONNULL_BEGIN @@ -194,7 +194,7 @@ - (void)getMenusForSiteID:(NSNumber *)siteID - (nullable NSArray *)remoteMenusFromJSONArray:(nullable NSArray *)jsonMenus { - return [jsonMenus wp_map:^id(NSDictionary *dictionary) { + return [jsonMenus wpkit_map:^id(NSDictionary *dictionary) { return [self menuFromJSONDictionary:dictionary]; }]; } @@ -202,7 +202,7 @@ - (nullable NSArray *)remoteMenusFromJSONArray:(nullable NSArray - (nullable NSArray *)menuItemsFromJSONDictionaries:(nullable NSArray *)dictionaries parent:(nullable RemoteMenuItem *)parent { NSParameterAssert([dictionaries isKindOfClass:[NSArray class]]); - return [dictionaries wp_map:^id(NSDictionary *dictionary) { + return [dictionaries wpkit_map:^id(NSDictionary *dictionary) { RemoteMenuItem *item = [self menuItemFromJSONDictionary:dictionary]; item.parentItem = parent; @@ -213,7 +213,7 @@ - (nullable NSArray *)menuItemsFromJSONDictionaries:(nullable NSArray *)jsonLocations { - return [jsonLocations wp_map:^id(NSDictionary *dictionary) { + return [jsonLocations wpkit_map:^id(NSDictionary *dictionary) { return [self menuLocationFromJSONDictionary:dictionary]; }]; } diff --git a/Sources/WordPressKit/Services/NotificationSettingsServiceRemote.swift b/Sources/WordPressKit/Services/NotificationSettingsServiceRemote.swift index 5e33682a9..334bb71f7 100644 --- a/Sources/WordPressKit/Services/NotificationSettingsServiceRemote.swift +++ b/Sources/WordPressKit/Services/NotificationSettingsServiceRemote.swift @@ -1,6 +1,4 @@ import Foundation -import UIDeviceIdentifier -import WordPressShared /// The purpose of this class is to encapsulate all of the interaction with the Notifications REST endpoints. /// Here we'll deal mostly with the Settings / Push Notifications API. @@ -77,10 +75,10 @@ open class NotificationSettingsServiceRemote: ServiceRemoteWordPressComREST { "device_family": "apple", "app_secret_key": pushNotificationAppId, "device_name": device.name, - "device_model": UIDeviceHardware.platform(), + "device_model": device.platform, "os_version": device.systemVersion, - "app_version": Bundle.main.bundleVersion(), - "device_uuid": device.wordPressIdentifier() + "app_version": Bundle.main.wpkit_bundleVersion(), + "device_uuid": device.identifierForVendor?.uuidString ] wordPressComRESTAPI.post(requestUrl, diff --git a/Sources/WordPressKit/Services/PeopleServiceRemote.swift b/Sources/WordPressKit/Services/PeopleServiceRemote.swift index dd7d62af4..e23b26ae3 100644 --- a/Sources/WordPressKit/Services/PeopleServiceRemote.swift +++ b/Sources/WordPressKit/Services/PeopleServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared /// Encapsulates all of the People Management WordPress.com Methods /// @@ -558,7 +557,7 @@ private extension PeopleServiceRemote { let firstName = user["first_name"] as? String let lastName = user["last_name"] as? String let avatarURL = (user["avatar_URL"] as? NSString) - .flatMap { URL(string: $0.byUrlEncoding())} + .flatMap { URL(string: $0.wpkit_stringByUrlEncoding())} let linkedUserID = user["linked_user_ID"] as? Int ?? ID let isSuperAdmin = user["is_super_admin"] as? Bool ?? false diff --git a/Sources/WordPressKit/Services/Plans/PlanServiceRemote.swift b/Sources/WordPressKit/Services/Plans/PlanServiceRemote.swift index d654f394b..b31f8e21d 100644 --- a/Sources/WordPressKit/Services/Plans/PlanServiceRemote.swift +++ b/Sources/WordPressKit/Services/Plans/PlanServiceRemote.swift @@ -1,7 +1,6 @@ import Foundation -import WordPressShared -public class PlanServiceRemote: ServiceRemoteWordPressComREST { +open class PlanServiceRemote: ServiceRemoteWordPressComREST { public typealias AvailablePlans = (plans: [RemoteWpcomPlan], groups: [RemotePlanGroup], features: [RemotePlanFeature]) typealias EndpointResponse = [String: AnyObject] @@ -192,7 +191,7 @@ public class PlanServiceRemote: ServiceRemoteWordPressComREST { } /// Retrieves Zendesk meta data: plan and Jetpack addons, if available - public func getZendeskMetadata(siteID: Int, completion: @escaping (Result) -> Void) { + open func getZendeskMetadata(siteID: Int, completion: @escaping (Result) -> Void) { let endpoint = "me/sites" let path = self.path(forEndpoint: endpoint, withVersion: ._1_1) let parameters = ["fields": "ID, zendesk_site_meta"] as [String: AnyObject] diff --git a/Sources/WordPressKit/Services/Plans/PlanServiceRemote_ApiVersion1_3.swift b/Sources/WordPressKit/Services/Plans/PlanServiceRemote_ApiVersion1_3.swift index b9fefa665..f64d3f9f7 100644 --- a/Sources/WordPressKit/Services/Plans/PlanServiceRemote_ApiVersion1_3.swift +++ b/Sources/WordPressKit/Services/Plans/PlanServiceRemote_ApiVersion1_3.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared @objc public class PlanServiceRemote_ApiVersion1_3: ServiceRemoteWordPressComREST { diff --git a/Sources/WordPressKit/Services/PostServiceRemoteExtended.swift b/Sources/WordPressKit/Services/PostServiceRemoteExtended.swift index f5a6f1cb8..700c1b333 100644 --- a/Sources/WordPressKit/Services/PostServiceRemoteExtended.swift +++ b/Sources/WordPressKit/Services/PostServiceRemoteExtended.swift @@ -23,7 +23,7 @@ public protocol PostServiceRemoteExtended: PostServiceRemote { func deletePost(withID postID: Int) async throws } -public enum PostServiceRemoteError: Error { +@frozen public enum PostServiceRemoteError: Error { /// 409 (Conflict) case conflict /// 404 (Not Found) diff --git a/Sources/WordPressKit/Services/PostServiceRemoteREST.m b/Sources/WordPressKit/Services/PostServiceRemoteREST.m index e4455a613..f6ecf6765 100644 --- a/Sources/WordPressKit/Services/PostServiceRemoteREST.m +++ b/Sources/WordPressKit/Services/PostServiceRemoteREST.m @@ -3,7 +3,7 @@ #import "RemotePostCategory.h" #import "RemoteUser.h" #import "WPKit-Swift.h" -@import WordPressShared; + @import NSObject_SafeExpectations; NSString * const PostRemoteStatusPublish = @"publish"; @@ -429,7 +429,7 @@ - (NSDictionary *)dictionaryWithRemoteOptions:(id )opt #pragma mark - Private methods - (NSArray *)remotePostsFromJSONArray:(NSArray *)jsonPosts { - return [jsonPosts wp_map:^id(NSDictionary *jsonPost) { + return [jsonPosts wpkit_map:^id(NSDictionary *jsonPost) { return [self remotePostFromJSONDictionary:jsonPost]; }]; } @@ -461,7 +461,7 @@ + (RemotePost *)remotePostFromJSONDictionary:(NSDictionary *)jsonPost { post.suggestedSlug = [jsonPost stringForKeyPath:@"other_URLs.suggested_slug"]; post.status = jsonPost[@"status"]; post.password = jsonPost[@"password"]; - if ([post.password isEmpty]) { + if ([post.password wpkit_isEmpty]) { post.password = nil; } post.parentID = [jsonPost numberForKeyPath:@"parent.ID"]; @@ -513,9 +513,9 @@ + (RemotePost *)remotePostFromJSONDictionary:(NSDictionary *)jsonPost { post.pathForDisplayImage = post.postThumbnailPath; } else { // parse contents for a suitable image - post.pathForDisplayImage = [DisplayableImageHelper searchPostContentForImageToDisplay:post.content]; + post.pathForDisplayImage = [WPKitDisplayableImageHelper searchPostContentForImageToDisplay:post.content]; if ([post.pathForDisplayImage length] == 0) { - post.pathForDisplayImage = [DisplayableImageHelper searchPostAttachmentsForImageToDisplay:[jsonPost dictionaryForKey:@"attachments"] existingInContent:post.content]; + post.pathForDisplayImage = [WPKitDisplayableImageHelper searchPostAttachmentsForImageToDisplay:[jsonPost dictionaryForKey:@"attachments"] existingInContent:post.content]; } } @@ -595,7 +595,7 @@ - (NSDictionary *)parametersWithRemotePost:(RemotePost *)post } - (NSArray *)metadataForPost:(RemotePost *)post { - return [post.metadata wp_map:^id(NSDictionary *meta) { + return [post.metadata wpkit_map:^id(NSDictionary *meta) { NSNumber *metaID = [meta objectForKey:@"id"]; NSString *metaValue = [meta objectForKey:@"value"]; NSString *metaKey = [meta objectForKey:@"key"]; @@ -616,7 +616,7 @@ - (NSArray *)metadataForPost:(RemotePost *)post { } + (NSArray *)remoteCategoriesFromJSONArray:(NSArray *)jsonCategories { - return [jsonCategories wp_map:^id(NSDictionary *jsonCategory) { + return [jsonCategories wpkit_map:^id(NSDictionary *jsonCategory) { return [self remoteCategoryFromJSONDictionary:jsonCategory]; }]; } @@ -646,7 +646,7 @@ + (NSArray *)tagNamesFromJSONDictionary:(NSDictionary *)jsonTags { postID:(NSNumber *)postID siteID:(NSNumber *)siteID { - return [jsonUsers wp_map:^id(NSDictionary *jsonUser) { + return [jsonUsers wpkit_map:^id(NSDictionary *jsonUser) { return [[RemoteLikeUser alloc] initWithDictionary:jsonUser postID:postID siteID:siteID]; }]; } diff --git a/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC+Extended.swift b/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC+Extended.swift index 904941fda..098e9e646 100644 --- a/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC+Extended.swift +++ b/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC+Extended.swift @@ -1,5 +1,5 @@ import Foundation -import wpxmlrpc +@_implementationOnly import wpxmlrpc extension PostServiceRemoteXMLRPC: PostServiceRemoteExtended { public func post(withID postID: Int) async throws -> RemotePost { @@ -20,7 +20,7 @@ extension PostServiceRemoteXMLRPC: PostServiceRemoteExtended { let dictionary = try makeParameters(from: RemotePostCreateParametersXMLRPCEncoder(parameters: parameters)) let parameters = xmlrpcArguments(withExtra: dictionary) as [AnyObject] let response = try await api.call(method: "wp.newPost", parameters: parameters).get() - guard let postID = (response.body as? NSObject)?.numericValue() else { + guard let postID = (response.body as? NSObject)?.wpkit_numericValue() else { throw URLError(.unknown) // Should never happen } return try await post(withID: postID.intValue) diff --git a/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC.m b/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC.m index e5454bc82..d6432e203 100644 --- a/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC.m +++ b/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC.m @@ -4,7 +4,6 @@ #import "NSMutableDictionary+Helpers.h" #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; const NSInteger HTTP404ErrorCode = 404; NSString * const WordPressAppErrorDomain = @"org.wordpress.iphone"; @@ -90,8 +89,8 @@ - (void)createPost:(RemotePost *)post [self.api callMethod:@"metaWeblog.newPost" parameters:parameters success:^(id responseObject, NSHTTPURLResponse *httpResponse) { - if ([responseObject respondsToSelector:@selector(numericValue)]) { - post.postID = [responseObject numericValue]; + if ([responseObject respondsToSelector:@selector(wpkit_numericValue)]) { + post.postID = [responseObject wpkit_numericValue]; if (!post.date) { // Set the temporary date until we get it from the server so it sorts properly on the list @@ -288,7 +287,7 @@ - (NSDictionary *)dictionaryWithRemoteOptions:(id )opt #pragma mark - Private methods - (NSArray *)remotePostsFromXMLRPCArray:(NSArray *)xmlrpcArray { - return [xmlrpcArray wp_map:^id(NSDictionary *xmlrpcPost) { + return [xmlrpcArray wpkit_map:^id(NSDictionary *xmlrpcPost) { return [self remotePostFromXMLRPCDictionary:xmlrpcPost]; }]; } @@ -313,7 +312,7 @@ + (RemotePost *)remotePostFromXMLRPCDictionary:(NSDictionary *)xmlrpcDictionary post.authorID = [xmlrpcDictionary numberForKey:@"post_author"]; post.status = [self statusForPostStatus:xmlrpcDictionary[@"post_status"] andDate:post.date]; post.password = xmlrpcDictionary[@"post_password"]; - if ([post.password isEmpty]) { + if ([post.password wpkit_isEmpty]) { post.password = nil; } post.parentID = [xmlrpcDictionary numberForKey:@"post_parent"]; @@ -337,7 +336,7 @@ + (RemotePost *)remotePostFromXMLRPCDictionary:(NSDictionary *)xmlrpcDictionary post.pathForDisplayImage = post.postThumbnailPath; } else { // parse content for a suitable image. - post.pathForDisplayImage = [DisplayableImageHelper searchPostContentForImageToDisplay:post.content]; + post.pathForDisplayImage = [WPKitDisplayableImageHelper searchPostContentForImageToDisplay:post.content]; } return post; @@ -359,9 +358,9 @@ + (NSArray *)tagsFromXMLRPCTermsArray:(NSArray *)terms { } + (NSArray *)remoteCategoriesFromXMLRPCTermsArray:(NSArray *)terms { - return [[terms wp_filter:^BOOL(NSDictionary *category) { + return [[terms wpkit_filter:^BOOL(NSDictionary *category) { return [[category stringForKey:@"taxonomy"] isEqualToString:@"category"]; - }] wp_map:^id(NSDictionary *category) { + }] wpkit_map:^id(NSDictionary *category) { return [self remoteCategoryFromXMLRPCDictionary:category]; }]; } @@ -411,7 +410,7 @@ - (NSDictionary *)parametersWithRemotePost:(RemotePost *)post postParams[@"date_created_gmt"] = [NSDate date]; } if (post.categories) { - NSArray *categoryNames = [post.categories wp_map:^id(RemotePostCategory *category) { + NSArray *categoryNames = [post.categories wpkit_map:^id(RemotePostCategory *category) { return category.name; }]; diff --git a/Sources/WordPressKit/Services/QR Login/QRLoginServiceRemote.swift b/Sources/WordPressKit/Services/QR Login/QRLoginServiceRemote.swift index db2335278..cad2fe17d 100644 --- a/Sources/WordPressKit/Services/QR Login/QRLoginServiceRemote.swift +++ b/Sources/WordPressKit/Services/QR Login/QRLoginServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared open class QRLoginServiceRemote: ServiceRemoteWordPressComREST { /// Validates the incoming QR Login token and retrieves the requesting browser, and location @@ -47,7 +46,7 @@ open class QRLoginServiceRemote: ServiceRemoteWordPressComREST { } } -public enum QRLoginError { +@frozen public enum QRLoginError { case invalidData case expired diff --git a/Sources/WordPressKit/Services/ReaderPostServiceRemote.m b/Sources/WordPressKit/Services/ReaderPostServiceRemote.m index 6cafe5f1a..46da67291 100644 --- a/Sources/WordPressKit/Services/ReaderPostServiceRemote.m +++ b/Sources/WordPressKit/Services/ReaderPostServiceRemote.m @@ -4,7 +4,6 @@ #import "ReaderTopicServiceRemote.h" #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; NSString * const PostRESTKeyPosts = @"posts"; @@ -30,7 +29,7 @@ - (void)fetchPostsFromEndpoint:(NSURL *)endpoint NSNumber *numberToFetch = @(count); NSMutableDictionary *params = [@{ ParamKeyNumber:numberToFetch, - ParamKeyBefore: [DateUtils isoStringFromDate:date], + ParamKeyBefore: [WPKitDateUtils isoStringFromDate:date], ParamKeyOrder: ParamKeyDescending, ParamKeyMeta: ParamKeyMetaValue } mutableCopy]; @@ -179,7 +178,7 @@ - (NSString *)endpointUrlForSearchPhrase:(NSString *)phrase { NSAssert([phrase length] > 0, @"A search phrase is required."); - NSString *endpoint = [NSString stringWithFormat:@"read/search?q=%@", [phrase stringByUrlEncoding]]; + NSString *endpoint = [NSString stringWithFormat:@"read/search?q=%@", [phrase wpkit_stringByUrlEncoding]]; NSString *absolutePath = [self pathForEndpoint:endpoint withVersion:WordPressComRESTAPIVersion_1_2]; NSURL *url = [NSURL URLWithString:absolutePath relativeToURL:self.wordPressComRESTAPI.baseURL]; return [url absoluteString]; @@ -219,7 +218,7 @@ - (void)fetchPostsFromEndpoint:(NSURL *)endpoint __block CGFloat offset = [[params numberForKey:ParamKeyOffset] floatValue]; NSString *algorithm = [responseObject stringForKey:ParamsKeyAlgorithm]; NSArray *jsonPosts = [responseObject arrayForKey:PostRESTKeyPosts]; - NSArray *posts = [jsonPosts wp_map:^id(NSDictionary *jsonPost) { + NSArray *posts = [jsonPosts wpkit_map:^id(NSDictionary *jsonPost) { if (rankByOffset) { RemoteReaderPost *post = [self formatPostDictionary:jsonPost offset:offset]; offset++; diff --git a/Sources/WordPressKit/Services/ReaderServiceDeliveryFrequency.swift b/Sources/WordPressKit/Services/ReaderServiceDeliveryFrequency.swift index 96090898e..b7d060a98 100644 --- a/Sources/WordPressKit/Services/ReaderServiceDeliveryFrequency.swift +++ b/Sources/WordPressKit/Services/ReaderServiceDeliveryFrequency.swift @@ -5,7 +5,7 @@ import Foundation /// - daily: daily frequency /// - instantly: instantly frequency /// - weekly: weekly frequency -public enum ReaderServiceDeliveryFrequency: String { +@frozen public enum ReaderServiceDeliveryFrequency: String { case daily case instantly case weekly diff --git a/Sources/WordPressKit/Services/ReaderSiteSearchServiceRemote.swift b/Sources/WordPressKit/Services/ReaderSiteSearchServiceRemote.swift index ae0329692..03d4739cf 100644 --- a/Sources/WordPressKit/Services/ReaderSiteSearchServiceRemote.swift +++ b/Sources/WordPressKit/Services/ReaderSiteSearchServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public class ReaderSiteSearchServiceRemote: ServiceRemoteWordPressComREST { diff --git a/Sources/WordPressKit/Services/ReaderSiteServiceRemote.m b/Sources/WordPressKit/Services/ReaderSiteServiceRemote.m index 44d278eb9..1aa121cae 100644 --- a/Sources/WordPressKit/Services/ReaderSiteServiceRemote.m +++ b/Sources/WordPressKit/Services/ReaderSiteServiceRemote.m @@ -1,7 +1,6 @@ #import "ReaderSiteServiceRemote.h" #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; static NSString* const ReaderSiteServiceRemoteURLKey = @"url"; static NSString* const ReaderSiteServiceRemoteSourceKey = @"source"; diff --git a/Sources/WordPressKit/Services/ReaderTopicServiceRemote.m b/Sources/WordPressKit/Services/ReaderTopicServiceRemote.m index b78003d65..9078367e2 100644 --- a/Sources/WordPressKit/Services/ReaderTopicServiceRemote.m +++ b/Sources/WordPressKit/Services/ReaderTopicServiceRemote.m @@ -1,7 +1,6 @@ #import "ReaderTopicServiceRemote.h" #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; static NSString * const TopicMenuSectionDefaultKey = @"default"; static NSString * const TopicMenuSectionSubscribedKey = @"subscribed"; @@ -268,7 +267,7 @@ - (NSString *)slugForTopicName:(NSString *)topicName regexNonAlphaNumNonDash = [NSRegularExpression regularExpressionWithPattern:@"[^\\p{L}\\p{Nd}\\-]+" options:NSRegularExpressionCaseInsensitive error:&error]; }); - topicName = [[topicName lowercaseString] trim]; + topicName = [[topicName lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; // remove html entities topicName = [regexHtmlEntities stringByReplacingMatchesInString:topicName @@ -300,9 +299,9 @@ - (NSString *)slugForTopicName:(NSString *)topicName - (NSArray *)normalizeMenuTopicsList:(NSArray *)rawTopics subscribed:(BOOL)subscribed recommended:(BOOL)recommended { - return [[rawTopics wp_filter:^BOOL(id obj) { + return [[rawTopics wpkit_filter:^BOOL(id obj) { return [obj isKindOfClass:[NSDictionary class]]; - }] wp_map:^id(NSDictionary *topic) { + }] wpkit_map:^id(NSDictionary *topic) { return [self normalizeMenuTopicDictionary:topic subscribed:subscribed recommended:recommended]; }]; } diff --git a/Sources/WordPressKit/Services/SharingServiceRemote.swift b/Sources/WordPressKit/Services/SharingServiceRemote.swift index 55433b6fb..8be0157a8 100644 --- a/Sources/WordPressKit/Services/SharingServiceRemote.swift +++ b/Sources/WordPressKit/Services/SharingServiceRemote.swift @@ -1,6 +1,5 @@ import Foundation -import NSObject_SafeExpectations -import WordPressShared +@_implementationOnly import NSObject_SafeExpectations /// SharingServiceRemote is responsible for wrangling the REST API calls related to /// publiczice services, publicize connections, and keyring connections. @@ -114,8 +113,8 @@ open class SharingServiceRemote: ServiceRemoteWordPressComREST { let dict = dict as AnyObject let externalUsers = dict.array(forKey: ConnectionDictionaryKeys.additionalExternalUsers) ?? [] conn.additionalExternalUsers = self.externalUsersForKeyringConnection(externalUsers as NSArray) - conn.dateExpires = DateUtils.date(fromISOString: dict.string(forKey: ConnectionDictionaryKeys.expires)) - conn.dateIssued = DateUtils.date(fromISOString: dict.string(forKey: ConnectionDictionaryKeys.issued)) + conn.dateExpires = WPKitDateUtils.date(fromISOString: dict.string(forKey: ConnectionDictionaryKeys.expires)) + conn.dateIssued = WPKitDateUtils.date(fromISOString: dict.string(forKey: ConnectionDictionaryKeys.issued)) conn.externalDisplay = dict.string(forKey: ConnectionDictionaryKeys.externalDisplay) ?? conn.externalDisplay conn.externalID = dict.string(forKey: ConnectionDictionaryKeys.externalID) ?? conn.externalID conn.externalName = dict.string(forKey: ConnectionDictionaryKeys.externalName) ?? conn.externalName @@ -370,11 +369,11 @@ open class SharingServiceRemote: ServiceRemoteWordPressComREST { conn.service = dict.string(forKey: ConnectionDictionaryKeys.service) ?? conn.service if let expirationDateAsString = dict.string(forKey: ConnectionDictionaryKeys.expires) { - conn.dateExpires = DateUtils.date(fromISOString: expirationDateAsString) + conn.dateExpires = WPKitDateUtils.date(fromISOString: expirationDateAsString) } if let issueDateAsString = dict.string(forKey: ConnectionDictionaryKeys.issued) { - conn.dateIssued = DateUtils.date(fromISOString: issueDateAsString) + conn.dateIssued = WPKitDateUtils.date(fromISOString: issueDateAsString) } if let sharedDictNumber = dict.number(forKey: ConnectionDictionaryKeys.shared) { diff --git a/Sources/WordPressKit/Services/SiteDesignServiceRemote.swift b/Sources/WordPressKit/Services/SiteDesignServiceRemote.swift index cab54481f..fe208c939 100644 --- a/Sources/WordPressKit/Services/SiteDesignServiceRemote.swift +++ b/Sources/WordPressKit/Services/SiteDesignServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public struct SiteDesignRequest { public enum TemplateGroup: String { diff --git a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift index 6cf675a63..02b9ce944 100644 --- a/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift +++ b/Sources/WordPressKit/Services/StatsServiceRemoteV2.swift @@ -99,7 +99,7 @@ open class StatsServiceRemoteV2: ServiceRemoteWordPressComREST { /// e.g. if you want data spanning 11-17 Feb 2019, you should pass in a period of `.week` and an /// ending date of `Feb 17 2019`. /// - limit: Limit of how many objects you want returned for your query. Default is `10`. `0` means no limit. - public func getData(for period: StatsPeriodUnit, + open func getData(for period: StatsPeriodUnit, unit: StatsPeriodUnit? = nil, endingOn: Date, limit: Int = 10, diff --git a/Sources/WordPressKit/Services/TaxonomyServiceRemoteREST.m b/Sources/WordPressKit/Services/TaxonomyServiceRemoteREST.m index c35291d92..4dbc4ee10 100644 --- a/Sources/WordPressKit/Services/TaxonomyServiceRemoteREST.m +++ b/Sources/WordPressKit/Services/TaxonomyServiceRemoteREST.m @@ -4,7 +4,6 @@ #import "RemotePostCategory.h" #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; NS_ASSUME_NONNULL_BEGIN @@ -283,7 +282,7 @@ - (void)updateTaxonomyWithType:(NSString *)typeIdentifier - (NSArray *)remoteCategoriesWithJSONArray:(NSArray *)jsonArray { - return [jsonArray wp_map:^id(NSDictionary *jsonCategory) { + return [jsonArray wpkit_map:^id(NSDictionary *jsonCategory) { return [self remoteCategoryWithJSONDictionary:jsonCategory]; }]; } @@ -299,7 +298,7 @@ - (RemotePostCategory *)remoteCategoryWithJSONDictionary:(NSDictionary *)jsonCat - (NSArray *)remoteTagsWithJSONArray:(NSArray *)jsonArray { - return [jsonArray wp_map:^id(NSDictionary *jsonTag) { + return [jsonArray wpkit_map:^id(NSDictionary *jsonTag) { return [self remoteTagWithJSONDictionary:jsonTag]; }]; } diff --git a/Sources/WordPressKit/Services/TaxonomyServiceRemoteXMLRPC.m b/Sources/WordPressKit/Services/TaxonomyServiceRemoteXMLRPC.m index 9bf54e74e..6e11502df 100644 --- a/Sources/WordPressKit/Services/TaxonomyServiceRemoteXMLRPC.m +++ b/Sources/WordPressKit/Services/TaxonomyServiceRemoteXMLRPC.m @@ -2,7 +2,7 @@ #import "RemotePostTag.h" #import "RemoteTaxonomyPaging.h" #import "WPKit-Swift.h" -@import WordPressShared; + @import NSObject_SafeExpectations; NS_ASSUME_NONNULL_BEGIN @@ -41,7 +41,7 @@ - (void)createCategory:(RemotePostCategory *)category success:^(NSString *responseString) { RemotePostCategory *newCategory = [RemotePostCategory new]; NSString *categoryID = responseString; - newCategory.categoryID = [categoryID numericValue]; + newCategory.categoryID = [categoryID wpkit_numericValue]; if (success) { success(newCategory); } @@ -96,7 +96,7 @@ - (void)createTag:(RemotePostTag *)tag success:^(NSString *responseString) { RemotePostTag *newTag = [RemotePostTag new]; NSString *tagID = responseString; - newTag.tagID = [tagID numericValue]; + newTag.tagID = [tagID wpkit_numericValue]; newTag.name = tag.name; newTag.tagDescription = tag.tagDescription; newTag.slug = tag.slug; @@ -187,7 +187,7 @@ - (void)createTaxonomyWithType:(NSString *)typeIdentifier [self.api callMethod:@"wp.newTerm" parameters:xmlrpcParameters success:^(id responseObject, NSHTTPURLResponse *httpResponse) { - if (![responseObject respondsToSelector:@selector(numericValue)]) { + if (![responseObject respondsToSelector:@selector(wpkit_numericValue)]) { NSString *message = [NSString stringWithFormat:@"Invalid response creating taxonomy of type: %@", typeIdentifier]; [self handleResponseErrorWithMessage:message method:@"wp.newTerm" failure:failure]; return; @@ -286,7 +286,7 @@ - (void)editTaxonomyWithType:(NSString *)typeIdentifier - (NSArray *)remoteCategoriesFromXMLRPCArray:(NSArray *)xmlrpcArray { - return [xmlrpcArray wp_map:^id(NSDictionary *xmlrpcCategory) { + return [xmlrpcArray wpkit_map:^id(NSDictionary *xmlrpcCategory) { return [self remoteCategoryFromXMLRPCDictionary:xmlrpcCategory]; }]; } @@ -302,7 +302,7 @@ - (RemotePostCategory *)remoteCategoryFromXMLRPCDictionary:(NSDictionary *)xmlrp - (NSArray *)remoteTagsFromXMLRPCArray:(NSArray *)xmlrpcArray { - return [xmlrpcArray wp_map:^id(NSDictionary *xmlrpcTag) { + return [xmlrpcArray wpkit_map:^id(NSDictionary *xmlrpcTag) { return [self remoteTagFromXMLRPCDictionary:xmlrpcTag]; }]; } diff --git a/Sources/WordPressKit/Services/TimeZoneServiceRemote.swift b/Sources/WordPressKit/Services/TimeZoneServiceRemote.swift index 42e4239fa..500ac5663 100644 --- a/Sources/WordPressKit/Services/TimeZoneServiceRemote.swift +++ b/Sources/WordPressKit/Services/TimeZoneServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared public class TimeZoneServiceRemote: ServiceRemoteWordPressComREST { public enum ResponseError: Error { diff --git a/Sources/WordPressKit/Services/TransactionsServiceRemote.swift b/Sources/WordPressKit/Services/TransactionsServiceRemote.swift index 2ba8bf85f..84eaaf297 100644 --- a/Sources/WordPressKit/Services/TransactionsServiceRemote.swift +++ b/Sources/WordPressKit/Services/TransactionsServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import WordPressShared @objc public class TransactionsServiceRemote: ServiceRemoteWordPressComREST { diff --git a/Sources/WordPressKit/Services/WordPressComServiceRemote+SiteCreation.swift b/Sources/WordPressKit/Services/WordPressComServiceRemote+SiteCreation.swift index c65f80e5f..b2fb66149 100644 --- a/Sources/WordPressKit/Services/WordPressComServiceRemote+SiteCreation.swift +++ b/Sources/WordPressKit/Services/WordPressComServiceRemote+SiteCreation.swift @@ -169,7 +169,7 @@ public enum SiteCreationError: Error { /// - success: the site creation request succeeded with the accompanying result. /// - failure: the site creation request failed due to the accompanying error. /// -public enum SiteCreationResult { +@frozen public enum SiteCreationResult { case success(SiteCreationResponse) case failure(SiteCreationError) } diff --git a/Sources/WordPressKit/Services/WordPressComServiceRemote+SiteSegments.swift b/Sources/WordPressKit/Services/WordPressComServiceRemote+SiteSegments.swift index 179d0a215..63763e0ed 100644 --- a/Sources/WordPressKit/Services/WordPressComServiceRemote+SiteSegments.swift +++ b/Sources/WordPressKit/Services/WordPressComServiceRemote+SiteSegments.swift @@ -79,7 +79,7 @@ public enum SiteSegmentsError: Error { /// - success: the site segments request succeeded with the accompanying result. /// - failure: the site segments request failed due to the accompanying error. /// -public enum SiteSegmentsResult { +@frozen public enum SiteSegmentsResult { case success([SiteSegment]) case failure(SiteSegmentsError) } diff --git a/Sources/WordPressKit/Services/WordPressComServiceRemote.m b/Sources/WordPressKit/Services/WordPressComServiceRemote.m index 28555c7ea..0db615a57 100644 --- a/Sources/WordPressKit/Services/WordPressComServiceRemote.m +++ b/Sources/WordPressKit/Services/WordPressComServiceRemote.m @@ -1,7 +1,6 @@ #import "WordPressComServiceRemote.h" #import "WPKit-Swift.h" @import NSObject_SafeExpectations; -@import WordPressShared; @implementation WordPressComServiceRemote @@ -245,7 +244,7 @@ - (NSError *)errorWithLocalizedMessage:(NSError *)error { - (NSString *)errorMessageForError:(NSError *)error { NSString *errorCode = [error.userInfo stringForKey:WordPressComRestApi.ErrorKeyErrorCode]; - NSString *errorMessage = [[error.userInfo stringForKey:NSLocalizedDescriptionKey] stringByStrippingHTML]; + NSString *errorMessage = [[error.userInfo stringForKey:NSLocalizedDescriptionKey] wpkit_stringByStrippingHTML]; if ([errorCode isEqualToString:@"username_only_lowercase_letters_and_numbers"]) { return NSLocalizedString(@"Sorry, usernames can only contain lowercase letters (a-z) and numbers.", nil); diff --git a/Sources/WordPressKit/Utility/UIDevice+Extensions.swift b/Sources/WordPressKit/Utility/UIDevice+Extensions.swift new file mode 100644 index 000000000..808427df5 --- /dev/null +++ b/Sources/WordPressKit/Utility/UIDevice+Extensions.swift @@ -0,0 +1,11 @@ +import UIKit + +extension UIDevice { + var platform: String { + var size = 0 + sysctlbyname("hw.machine", nil, &size, nil, 0) + var machine = [CChar](repeating: 0, count: size) + sysctlbyname("hw.machine", &machine, &size, nil, 0) + return String(cString: machine) + } +} diff --git a/Sources/WordPressKit/Utility/ZendeskMetadata.swift b/Sources/WordPressKit/Utility/ZendeskMetadata.swift index 4090f0d4f..3b772a010 100644 --- a/Sources/WordPressKit/Utility/ZendeskMetadata.swift +++ b/Sources/WordPressKit/Utility/ZendeskMetadata.swift @@ -20,6 +20,11 @@ public struct ZendeskMetadata: Decodable { case plan = "plan" case jetpackAddons = "addon" } + + public init(plan: String, jetpackAddons: [String]) { + self.plan = plan + self.jetpackAddons = jetpackAddons + } } /// Errors generated by the metadata decoding process diff --git a/Sources/WordPressKit/WordPressKit.h b/Sources/WordPressKit/WordPressKit.h index 482fdb30e..675703670 100644 --- a/Sources/WordPressKit/WordPressKit.h +++ b/Sources/WordPressKit/WordPressKit.h @@ -55,3 +55,11 @@ FOUNDATION_EXPORT const unsigned char WordPressKitVersionString[]; #import #import + +/// Inline WordPressShared +#import +#import +#import +#import +#import +#import diff --git a/Sources/WordPressShared/Dictionary+Helpers.swift b/Sources/WordPressShared/Dictionary+Helpers.swift new file mode 100644 index 000000000..76d50ad54 --- /dev/null +++ b/Sources/WordPressShared/Dictionary+Helpers.swift @@ -0,0 +1,25 @@ +import Foundation + +// MARK: - Dictionary Helper Methods +// +extension Dictionary { + /// This method attempts to convert a given value into a String, if it's not already the + /// case. Initial implementation supports only NSNumber. This is meant for bulletproof parsing, + /// in which a String value might be serialized, backend side, as a Number. + /// + /// - Parameter key: The key to retrieve. + /// + /// - Returns: Value as a String (when possible!) + /// + func valueAsString(forKey key: Key) -> String? { + let value = self[key] + switch value { + case let string as String: + return string + case let number as NSNumber: + return number.description + default: + return nil + } + } +} diff --git a/Sources/WordPressShared/DisplayableImageHelper.h b/Sources/WordPressShared/DisplayableImageHelper.h new file mode 100644 index 000000000..06e2a7e1c --- /dev/null +++ b/Sources/WordPressShared/DisplayableImageHelper.h @@ -0,0 +1,38 @@ +#import + +/** + Helper for searching a post's content or attachments for an image suitable for + using as the displayed image in the post list. + */ +@interface WPKitDisplayableImageHelper : NSObject + +/** + Get the url path of the image to display for a post. + + @param attachmentsDict A dictionary representing a posts attachments from the REST API. + @param content The post content. The attachment url must exist in the content. + @return The url path for the featured image or nil + */ ++ (NSString *)searchPostAttachmentsForImageToDisplay:(NSDictionary *)attachmentsDict existingInContent:(NSString *)content; + +/** + Search the passed string for an image that is a good candidate to feature. + + @details Loops over all img tags in the passed html content, extracts the URL from the + src attribute and checks for an acceptable width. The image URL with the best + width is returned. + @param content The content string to search. + @return The URL path for the image or an empty string. + */ ++ (NSString *)searchPostContentForImageToDisplay:(NSString *)content; + +/** + Find attachments ids in post content + + @param content The content string to search + + @return A set with all the attachment id that where found in galleries + */ ++ (NSSet *)searchPostContentForAttachmentIdsInGalleries:(NSString *)content; + +@end diff --git a/Sources/WordPressShared/DisplayableImageHelper.m b/Sources/WordPressShared/DisplayableImageHelper.m new file mode 100644 index 000000000..d8d360c17 --- /dev/null +++ b/Sources/WordPressShared/DisplayableImageHelper.m @@ -0,0 +1,281 @@ +#import "DisplayableImageHelper.h" +#import "NSString+Helpers.h" + +static const NSInteger FeaturedImageMinimumWidth = 150; + +static NSString * const AttachmentsDictionaryKeyWidth = @"width"; +static NSString * const AttachmentsDictionaryKeyURL = @"URL"; +static NSString * const AttachmentsDictionaryKeyMimeType = @"mime_type"; + +@implementation WPKitDisplayableImageHelper + ++ (NSInteger)widthOfAttachment:(NSDictionary *)attachment { + NSInteger result = 0; + id obj = [attachment objectForKey:AttachmentsDictionaryKeyWidth]; + if ([obj isKindOfClass:NSNumber.class]) { + NSNumber *number = (NSNumber *)obj; + result = [number integerValue]; + } else if ([obj isKindOfClass:NSString.class]) { + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + NSNumber *number= [numberFormatter numberFromString:(NSString *)obj]; + result = [number integerValue]; + } + return result; +} + ++ (NSString *)searchPostAttachmentsForImageToDisplay:(NSDictionary *)attachmentsDict existingInContent:(NSString *)content +{ + NSArray *attachments = [attachmentsDict allValues]; + if ([attachments count] == 0) { + return nil; + } + + NSString *imageToDisplay; + + attachments = [self filteredAttachmentsArray:attachments]; + + for (NSDictionary *attachment in attachments) { + NSInteger width = [self widthOfAttachment:attachment]; + if (width < FeaturedImageMinimumWidth) { + // The remaining images are too small so just stop now. + break; + } + id obj = attachment[AttachmentsDictionaryKeyURL]; + if ([obj isKindOfClass:NSString.class]) { + NSString *maybeImage = (NSString *)obj; + if ([content containsString:maybeImage]) { + imageToDisplay = maybeImage; + break; + } + } + } + + return imageToDisplay; +} + ++ (NSArray *)filteredAttachmentsArray:(NSArray *)attachments +{ + NSString *key = AttachmentsDictionaryKeyMimeType; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K BEGINSWITH %@", key, @"image"]; + attachments = [attachments filteredArrayUsingPredicate:predicate]; + attachments = [self sortAttachmentsArray:attachments]; + return attachments; +} + ++ (NSArray *)sortAttachmentsArray:(NSArray *)attachments +{ + return [attachments sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *attachmentA, NSDictionary *attachmentB) { + NSInteger widthA = [self widthOfAttachment:attachmentA]; + NSInteger widthB = [self widthOfAttachment:attachmentB]; + + if (widthA < widthB) { + return NSOrderedDescending; + } else if (widthA > widthB) { + return NSOrderedAscending; + } else { + return NSOrderedSame; + } + }]; +} + ++ (NSString *)searchPostContentForImageToDisplay:(NSString *)content +{ + NSString *imageSrc = @""; + // If there is no image tag in the content, just bail. + if (!content || [content rangeOfString:@""; + regex = [NSRegularExpression regularExpressionWithPattern:imgPattern options:NSRegularExpressionCaseInsensitive error:&error]; + }); + + // Find all the image tags in the content passed. + NSArray *matches = [regex matchesInString:content options:0 range:NSMakeRange(0, [content length])]; + + for (NSTextCheckingResult *match in matches) { + NSString *tag = [content substringWithRange:match.range]; + NSString *src = [self extractSrcFromImgTag:tag]; + + // Ignore WordPress emoji images + if ([src rangeOfString:@"/images/core/emoji/"].location != NSNotFound || + [src rangeOfString:@"/wp-includes/images/smilies/"].location != NSNotFound || + [src rangeOfString:@"/wp-content/mu-plugins/wpcom-smileys/"].location != NSNotFound) { + continue; + } + + // Ignore .svg images since we can't display them in a UIImageView + if ([src rangeOfString:@".svg"].location != NSNotFound) { + continue; + } + + // Check the tag for a good width + NSInteger width = MAX([self widthFromElementAttribute:tag], [self widthFromQueryString:src]); + if (width > FeaturedImageMinimumWidth) { + imageSrc = src; + break; + } + } + if (imageSrc.length == 0) { + imageSrc = [self searchContentBySizeClassForImageToFeature:content]; + } + + return imageSrc; +} + ++ (NSSet *)searchPostContentForAttachmentIdsInGalleries:(NSString *)content +{ + NSMutableSet *resultSet = [NSMutableSet set]; + // If there is no gallery shortcode in the content, just bail. + if (!content || [content rangeOfString:@"[gallery "].location == NSNotFound) { + return resultSet; + } + + // Get all the things + static NSRegularExpression *regexGallery; + static dispatch_once_t onceTokenRegexGallery; + dispatch_once(&onceTokenRegexGallery, ^{ + NSError *error; + NSString *galleryPattern = @"\\[gallery[^]]+ids=\"([0-9,]*)\"[^]]*\\]"; + regexGallery = [NSRegularExpression regularExpressionWithPattern:galleryPattern options:NSRegularExpressionCaseInsensitive error:&error]; + }); + + // Find all the gallery shortcodes in the content passed. + NSArray *matches = [regexGallery matchesInString:content options:0 range:NSMakeRange(0, [content length])]; + + for (NSTextCheckingResult *match in matches) { + if (match.numberOfRanges < 2) { + continue; + } + NSString *tag = [content substringWithRange:[match rangeAtIndex:1]]; + NSSet *tagIds = [self idsFromGallery:tag]; + [resultSet unionSet:tagIds]; + } + return resultSet; +} + +/** + Extract the path to an image from an image tag. + + @param tag An image tag. + @return The value of the src param. + */ ++ (NSString *)extractSrcFromImgTag:(NSString *)tag +{ + static NSRegularExpression *regex; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSError *error; + NSString *srcPattern = @"src\\s*=\\s*(?:'|\")(.*?)(?:'|\")"; + regex = [NSRegularExpression regularExpressionWithPattern:srcPattern options:NSRegularExpressionCaseInsensitive error:&error]; + }); + + NSRange srcRng = [regex rangeOfFirstMatchInString:tag options:0 range:NSMakeRange(0, [tag length])]; + NSString *src = [tag substringWithRange:srcRng]; + NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:@"\"'="]; + NSRange quoteRng = [src rangeOfCharacterFromSet:charSet]; + src = [src substringFromIndex:quoteRng.location]; + src = [src stringByTrimmingCharactersInSet:charSet]; + return src; +} + +/** + Search the passed string for an image that is a good candidate to feature. + @param content The content string to search. + @return The url path for the image or an empty string. + */ ++ (NSString *)searchContentBySizeClassForImageToFeature:(NSString *)content +{ + NSString *str = @""; + // If there is no image tag in the content, just bail. + if (!content || [content rangeOfString:@" + +@interface NSBundle (WPKitVersionNumberHelper) + +- (NSString *)wpkit_bundleVersion; + +@end diff --git a/Sources/WordPressShared/NSBundle+VersionNumberHelper.m b/Sources/WordPressShared/NSBundle+VersionNumberHelper.m new file mode 100644 index 000000000..736c2bdf1 --- /dev/null +++ b/Sources/WordPressShared/NSBundle+VersionNumberHelper.m @@ -0,0 +1,11 @@ +#import "NSBundle+VersionNumberHelper.h" + +@implementation NSBundle (WPKitVersionNumberHelper) + +- (NSString *)wpkit_bundleVersion +{ + NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; + return infoDictionary[(NSString *)kCFBundleVersionKey] ?: [NSString new]; +} + +@end diff --git a/Sources/WordPressShared/NSDate+Helpers.swift b/Sources/WordPressShared/NSDate+Helpers.swift new file mode 100644 index 000000000..9dabadc60 --- /dev/null +++ b/Sources/WordPressShared/NSDate+Helpers.swift @@ -0,0 +1,257 @@ +import Foundation + +extension Date { + /// Private Date Formatters + /// + fileprivate struct DateFormatters { + static let iso8601: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + formatter.timeZone = TimeZone(secondsFromGMT: 0) + return formatter + }() + + static let iso8601WithMilliseconds: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" + formatter.timeZone = TimeZone(secondsFromGMT: 0) + return formatter + }() + + static let rfc1123: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss z" + formatter.timeZone = TimeZone(secondsFromGMT: 0) + return formatter + }() + + static let mediumDate: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + return formatter + }() + + static let mediumDateTime: DateFormatter = { + let formatter = DateFormatter() + formatter.doesRelativeDateFormatting = true + formatter.dateStyle = .medium + formatter.timeStyle = .short + return formatter + }() + + static let mediumUTCDateTime: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .short + formatter.timeZone = TimeZone(secondsFromGMT: 0) + return formatter + }() + + static let longUTCDate: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .long + formatter.timeStyle = .none + formatter.timeZone = TimeZone(secondsFromGMT: 0) + return formatter + }() + + static let shortDateTime: DateFormatter = { + let formatter = DateFormatter() + formatter.doesRelativeDateFormatting = true + formatter.dateStyle = .short + formatter.timeStyle = .short + return formatter + }() + } + + /// Returns a NSDate Instance, given it's ISO8601 String Representation + /// + static func dateWithISO8601String(_ string: String) -> Date? { + return DateFormatters.iso8601.date(from: string) + } + + /// Returns a NSDate Instance, given it's ISO8601 String Representation with milliseconds + /// + static func dateWithISO8601WithMillisecondsString(_ string: String) -> Date? { + return DateFormatters.iso8601WithMilliseconds.date(from: string) + } + + /// Returns a NSDate instance with only its Year / Month / Weekday / Day set. Removes the time! + /// + func normalizedDate() -> Date { + + var calendar = Calendar.current + calendar.timeZone = TimeZone.autoupdatingCurrent + + let flags: NSCalendar.Unit = [.day, .weekOfYear, .month, .year] + + let components = (calendar as NSCalendar).components(flags, from: self) + + var normalized = DateComponents() + normalized.year = components.year + normalized.month = components.month + normalized.weekday = components.weekday + normalized.day = components.day + + return calendar.date(from: normalized) ?? self + } + + /// Formats the current NSDate instance using the RFC1123 Standard + /// + func toStringAsRFC1123() -> String { + return DateFormatters.rfc1123.string(from: self) + } + + @available(*, deprecated, renamed: "toMediumString", message: "Removed to help drop the deprecated `FormatterKit` dependency – @jkmassel, Mar 2021") + func mediumString(timeZone: TimeZone? = nil) -> String { + toMediumString(inTimeZone: timeZone) + } + + /// Formats the current date as relative date if it's within a week of + /// today, or with DateFormatter.Style.medium otherwise. + /// - Parameter timeZone: An optional time zone used to adjust the date formatters. **NOTE**: This has no affect on relative time stamps. + /// + /// - Example: 22 hours from now + /// - Example: 5 minutes ago + /// - Example: 8 hours ago + /// - Example: 2 days ago + /// - Example: Jan 22, 2017 + /// + func toMediumString(inTimeZone timeZone: TimeZone? = nil) -> String { + let relativeFormatter = RelativeDateTimeFormatter() + relativeFormatter.dateTimeStyle = .named + + let absoluteFormatter = DateFormatters.mediumDate + + if let timeZone = timeZone { + absoluteFormatter.timeZone = timeZone + } + + let components = Calendar.current.dateComponents([.day], from: self, to: Date()) + if let days = components.day, abs(days) < 7 { + return relativeFormatter.localizedString(fromTimeInterval: timeIntervalSinceNow) + } else { + return absoluteFormatter.string(from: self) + } + } + + /// Formats the current date as a medium relative date/time. + /// That is, it uses the `DateFormatter` `dateStyle` `.medium` and `timeStyle` `.short`. + /// + /// - Parameter timeZone: An optional time zone used to adjust the date formatters. + func mediumStringWithTime(timeZone: TimeZone? = nil) -> String { + let formatter = DateFormatters.mediumDateTime + if let timeZone = timeZone { + formatter.timeZone = timeZone + } + return formatter.string(from: self) + } + + /// Formats the current date as (non relative) long date (no time) in UTC. + /// + /// - Example: January 6th, 2018 + /// + func longUTCStringWithoutTime() -> String { + return DateFormatters.longUTCDate.string(from: self) + } + + /// Formats the current date as (non relattive) medium date/time in UTC. + /// + /// - Example: Jan 28, 2017, 1:51 PM + /// + func mediumStringWithUTCTime() -> String { + return DateFormatters.mediumUTCDateTime.string(from: self) + } + + /// Formats the current date as a short relative date/time. + /// + /// - Example: Tomorrow, 6:45 AM + /// - Example: Today, 8:09 AM + /// - Example: Yesterday, 11:36 PM + /// - Example: 1/28/17, 1:51 PM + /// - Example: 1/22/17, 2:18 AM + /// + func shortStringWithTime() -> String { + return DateFormatters.shortDateTime.string(from: self) + } + + @available(*, deprecated, message: "Not used, as far as I can tell – @jkmassel, Jan 2021") + fileprivate func toStringForPageSections() -> String { + let interval = timeIntervalSinceNow + + if interval > 0 && interval < 86400 { + return NSLocalizedString("later today", comment: "Later today") + } else { + let formatter = RelativeDateTimeFormatter() + formatter.unitsStyle = .short + formatter.dateTimeStyle = .named + + return formatter.localizedString(fromTimeInterval: interval) + } + } + + /// Returns the date components object. + /// + func dateAndTimeComponents() -> DateComponents { + return Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], + from: self) + } +} + +extension NSDate { + @objc public static func wpkit_dateWithISO8601String(_ string: String) -> NSDate? { + return Date.DateFormatters.iso8601.date(from: string) as NSDate? + } + + /// Formats the current date as relative date if it's within a week of + /// today, or with NSDateFormatterMediumStyle otherwise. + /// + /// - Example: 22 hours from now + /// - Example: 5 minutes ago + /// - Example: 8 hours ago + /// - Example: 2 days ago + /// - Example: Jan 22, 2017 + /// + @objc func mediumString() -> String { + return (self as Date).toMediumString() + } + + /// Formats the current date as a medium relative date/time. + /// + /// - Example: Tomorrow, 6:45 AM + /// - Example: Today, 8:09 AM + /// - Example: Yesterday, 11:36 PM + /// - Example: Jan 28, 2017, 1:51 PM + /// - Example: Jan 22, 2017, 2:18 AM + /// + @objc func mediumStringWithTime() -> String { + return (self as Date).mediumStringWithTime() + } + + /// Formats the current date as a short relative date/time. + /// + /// - Example: Tomorrow, 6:45 AM + /// - Example: Today, 8:09 AM + /// - Example: Yesterday, 11:36 PM + /// - Example: 1/28/17, 1:51 PM + /// - Example: 1/22/17, 2:18 AM + /// + @objc func shortStringWithTime() -> String { + return (self as Date).shortStringWithTime() + } + + @available(*, deprecated, message: "Scheduled for removal with FormatterKit – if it's still used, we'll rewrite it with modern APIs") + @objc func toStringForPageSections() -> String { + return (self as Date).toStringForPageSections() + } + + /// Returns the date components object. + /// + @objc func dateAndTimeComponents() -> NSDateComponents { + return (self as Date).dateAndTimeComponents() as NSDateComponents + } +} diff --git a/Sources/WordPressShared/NSMutableData+Helpers.swift b/Sources/WordPressShared/NSMutableData+Helpers.swift new file mode 100644 index 000000000..6f5966eee --- /dev/null +++ b/Sources/WordPressShared/NSMutableData+Helpers.swift @@ -0,0 +1,16 @@ +import Foundation + +/// Encapsulates all of the NSMutableData Helper Methods. +/// +extension NSMutableData { + + /// Encodes a raw String into UTF8, and appends it to the current instance. + /// + /// - Parameter string: The raw String to be UTF8-Encoded, and appended + /// + @objc func appendString(_ string: String) { + if let data = string.data(using: String.Encoding.utf8) { + append(data) + } + } +} diff --git a/Sources/WordPressShared/NSString+Helpers.h b/Sources/WordPressShared/NSString+Helpers.h new file mode 100644 index 000000000..a6f28ced6 --- /dev/null +++ b/Sources/WordPressShared/NSString+Helpers.h @@ -0,0 +1,18 @@ +#import + +@interface NSString (WPKitHelpers) + +- (NSString *)wpkit_stringByUrlEncoding; +- (NSString *)wpkit_stringByStrippingHTML; +- (NSString *)wpkit_stringByEllipsizingWithMaxLength:(NSInteger)lengthlimit preserveWords:(BOOL)preserveWords; +- (bool)wpkit_isEmpty; + +@end + +@interface NSString (WPKitNumericValueHack) +- (NSNumber *)wpkit_numericValue; +@end + +@interface NSObject (WPKitNumericValueHack) +- (NSNumber *)wpkit_numericValue; +@end diff --git a/Sources/WordPressShared/NSString+Helpers.m b/Sources/WordPressShared/NSString+Helpers.m new file mode 100644 index 000000000..43930bd7e --- /dev/null +++ b/Sources/WordPressShared/NSString+Helpers.m @@ -0,0 +1,197 @@ +#import "NSString+Helpers.h" +#import +#import "NSString+XMLExtensions.h" + +static NSString *const Ellipsis = @"\u2026"; + +@implementation NSString (WPKitHelpers) + +#pragma mark Helpers + +/** + Parses an WordPress core emoji IMG tag and returns the corresponding emoji character. + */ ++ (NSString *)emojiFromCoreEmojiImageTag:(NSString *)tag +{ + if ([tag rangeOfString:@" 0) { + NSTextCheckingResult *match = [matches firstObject]; + if (match.numberOfRanges == 2) { + NSRange range = [match rangeAtIndex:1]; + return [tag substringWithRange:range]; + } + } + + matches = [filenameRegex matchesInString:tag options:0 range:sourceRange]; + if ([matches count] > 0) { + NSTextCheckingResult *match = [matches firstObject]; + if (match.numberOfRanges == 2) { + NSRange range = [match rangeAtIndex:1]; + NSString *filename = [tag substringWithRange:range]; + return [self emojiCharacterFromCoreEmojiFilename:filename]; + } + } + + return nil; +} + +/** + Processes the filename of an core emoji image from `s.w.org/images/core/emoji` + and returns the unicode character for the emoji. + Filenames can be formatted as a single hex value, or for emoji comprised of + Unicode pairs, as two hex values separated by a dash. + */ ++ (NSString *)emojiCharacterFromCoreEmojiFilename:(NSString *)filename +{ + NSArray *components = [filename componentsSeparatedByString:@"-"]; + NSMutableArray *marr = [NSMutableArray array]; + for (NSString *string in components) { + NSString *unicodeChar = [NSString unicodeCharacterFromHexString:string]; + if (unicodeChar) { + [marr addObject:unicodeChar]; + } + } + + return [marr componentsJoinedByString:@""]; +} + ++ (NSString *)unicodeCharacterFromHexString:(NSString *)hexString +{ + NSScanner *scanner = [NSScanner scannerWithString:hexString]; + unsigned long long hex = 0; + BOOL success = [scanner scanHexLongLong:&hex]; + if (!success) { + return nil; + } + return [[NSString alloc] initWithBytes:&hex length:4 encoding:NSUTF32LittleEndianStringEncoding]; +} + +// Taken from AFNetworking's AFPercentEscapedQueryStringPairMemberFromStringWithEncoding +- (NSString *)wpkit_stringByUrlEncoding +{ + NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; + NSString *charactersToLeaveUnescaped = @"[]."; + [allowedCharacterSet addCharactersInString:charactersToLeaveUnescaped]; + return [self stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; +} + +/* + * Uses a RegEx to strip all HTML tags from a string and unencode entites + */ +- (NSString *)wpkit_stringByStrippingHTML +{ + return [self stringByReplacingOccurrencesOfString:@"<[^>]+>" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, self.length)]; +} + +// A method to truncate a string at a predetermined length and append ellipsis to the end + +- (NSString *)wpkit_stringByEllipsizingWithMaxLength:(NSInteger)lengthlimit preserveWords:(BOOL)preserveWords +{ + NSInteger currentLength = [self length]; + NSString *result = @""; + NSString *temp = @""; + + if (currentLength <= lengthlimit) { //If the string is already within limits + return self; + } else if (lengthlimit > 0) { //If the string is longer than the limit, and the limit is larger than 0. + + NSInteger newLimitWithoutEllipsis = lengthlimit - [Ellipsis length]; + + if (preserveWords) { + + NSArray *wordsSeperated = [self tokenize]; + + if ([wordsSeperated count] == 1) { // If this is a long word then we disregard preserveWords property. + return [NSString stringWithFormat:@"%@%@", [self substringToIndex:newLimitWithoutEllipsis], Ellipsis]; + } + + for (NSString *word in wordsSeperated) { + + if ([temp isEqualToString:@""]) { + temp = word; + } else { + temp = [NSString stringWithFormat:@"%@%@", temp, word]; + } + + if ([temp length] <= newLimitWithoutEllipsis) { + result = [temp copy]; + } else { + return [NSString stringWithFormat:@"%@%@",result,Ellipsis]; + } + } + } else { + return [NSString stringWithFormat:@"%@%@", [self substringToIndex:newLimitWithoutEllipsis], Ellipsis]; + } + + } else { //if the limit is 0. + return @""; + } + + return self; +} + +- (NSArray *)tokenize +{ + CFLocaleRef locale = CFLocaleCopyCurrent(); + CFRange stringRange = CFRangeMake(0, [self length]); + + CFStringTokenizerRef tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, + (CFStringRef)self, + stringRange, + kCFStringTokenizerUnitWordBoundary, + locale); + + CFStringTokenizerTokenType tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer); + + NSMutableArray *tokens = [NSMutableArray new]; + + while (tokenType != kCFStringTokenizerTokenNone) { + stringRange = CFStringTokenizerGetCurrentTokenRange(tokenizer); + NSString *token = [self substringWithRange:NSMakeRange(stringRange.location, stringRange.length)]; + [tokens addObject:token]; + tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer); + } + + CFRelease(locale); + CFRelease(tokenizer); + + return tokens; +} + +- (bool)wpkit_isEmpty { + return self.length == 0; +} + +@end + +@implementation NSString (WPKitNumericValueHack) + +- (NSNumber *)wpkit_numericValue { + return [NSNumber numberWithUnsignedLongLong:[self longLongValue]]; +} + +@end + +@implementation NSObject (WPKitNumericValueHack) +- (NSNumber *)wpkit_numericValue { + if ([self isKindOfClass:[NSNumber class]]) { + return (NSNumber *)self; + } + return nil; +} +@end diff --git a/Sources/WordPressShared/NSString+Summary.swift b/Sources/WordPressShared/NSString+Summary.swift new file mode 100644 index 000000000..ca8f8798b --- /dev/null +++ b/Sources/WordPressShared/NSString+Summary.swift @@ -0,0 +1,79 @@ +import Foundation + +/// This is an extension to NSString that provides logic to summarize HTML content, +/// and convert HTML into plain text. +/// +extension NSString { + + static let PostDerivedSummaryLength = 150 + + /// Create a summary for the post based on the post's content. + /// + /// - Returns: A summary for the post. + /// + @objc + public func wpkit_summarized() -> String { + let characterSet = CharacterSet(charactersIn: "\n") + + return (self as String).strippingGutenbergContentForExcerpt() + .strippingShortcodes() + .makePlainText() + .trimmingCharacters(in: characterSet) + .wpkit_stringByEllipsizing(withMaxLength: NSString.PostDerivedSummaryLength, preserveWords: true) + } +} + +private extension String { + func makePlainText() -> String { + let characterSet = NSCharacterSet.whitespacesAndNewlines + + return self.wpkit_stringByStrippingHTML() + .wpkit_stringByDecodingXMLCharacters() + .trimmingCharacters(in: characterSet) + } + + /// Creates a new string by stripping all shortcodes from this string. + /// + func strippingShortcodes() -> String { + let pattern = "\\[[^\\]]+\\]" + + return removingMatches(pattern: pattern, options: .caseInsensitive) + } + + /// This method is the main entry point to generate excerpts for Gutenberg content. + /// + func strippingGutenbergContentForExcerpt() -> String { + return strippingGutenbergGalleries().strippingGutenbergVideoPress() + } + + /// Strips Gutenberg galleries from strings. + /// + func strippingGutenbergGalleries() -> String { + let pattern = "(?s)" + + return removingMatches(pattern: pattern, options: .caseInsensitive) + } + + /// Strips VideoPress references from Gutenberg VideoPress and Video blocks. + /// + func strippingGutenbergVideoPress() -> String { + let pattern = "(?s)\n?" + + return removingMatches(pattern: pattern, options: .caseInsensitive) + } + + /// Creates a new string by removing all matches of the specified regex. + /// + func removingMatches(pattern: String, options: NSRegularExpression.Options = []) -> String { + let range = NSRange(location: 0, length: self.utf16.count) + let regex: NSRegularExpression + + do { + regex = try NSRegularExpression(pattern: pattern, options: options) + } catch { + return self + } + + return regex.stringByReplacingMatches(in: self, options: .reportCompletion, range: range, withTemplate: "") + } +} diff --git a/Sources/WordPressShared/NSString+XMLExtensions.h b/Sources/WordPressShared/NSString+XMLExtensions.h new file mode 100644 index 000000000..f2de4533e --- /dev/null +++ b/Sources/WordPressShared/NSString+XMLExtensions.h @@ -0,0 +1,10 @@ +#import + +@interface NSString (WPKitXMLExtensions) + ++ (NSString *)wpkit_encodeXMLCharactersIn : (NSString *)source; ++ (NSString *)wpkit_decodeXMLCharactersIn : (NSString *)source; +- (NSString *)wpkit_stringByDecodingXMLCharacters; +- (NSString *)wpkit_stringByEncodingXMLCharacters; + +@end diff --git a/Sources/WordPressShared/NSString+XMLExtensions.m b/Sources/WordPressShared/NSString+XMLExtensions.m new file mode 100644 index 000000000..19ee5fb74 --- /dev/null +++ b/Sources/WordPressShared/NSString+XMLExtensions.m @@ -0,0 +1,400 @@ +// Adapted from MWFeedParser +// https://github.com/mwaterfall/MWFeedParser Copyright (c) 2010 Michael Waterfall + +#import "NSString+XMLExtensions.h" + + +typedef struct { + __unsafe_unretained NSString *escapeSequence; + unichar uchar; +} HTMLEscapeMap; + + +// Taken from http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters +// Ordered by uchar lowest to highest for bsearching +static HTMLEscapeMap gAsciiHTMLEscapeMap[] = { + // A.2.2. Special characters + { @""", 34 }, + { @"&", 38 }, + { @"'", 39 }, + { @"<", 60 }, + { @">", 62 }, + + // A.2.1. Latin-1 characters + { @" ", 160 }, + { @"¡", 161 }, + { @"¢", 162 }, + { @"£", 163 }, + { @"¤", 164 }, + { @"¥", 165 }, + { @"¦", 166 }, + { @"§", 167 }, + { @"¨", 168 }, + { @"©", 169 }, + { @"ª", 170 }, + { @"«", 171 }, + { @"¬", 172 }, + { @"­", 173 }, + { @"®", 174 }, + { @"¯", 175 }, + { @"°", 176 }, + { @"±", 177 }, + { @"²", 178 }, + { @"³", 179 }, + { @"´", 180 }, + { @"µ", 181 }, + { @"¶", 182 }, + { @"·", 183 }, + { @"¸", 184 }, + { @"¹", 185 }, + { @"º", 186 }, + { @"»", 187 }, + { @"¼", 188 }, + { @"½", 189 }, + { @"¾", 190 }, + { @"¿", 191 }, + { @"À", 192 }, + { @"Á", 193 }, + { @"Â", 194 }, + { @"Ã", 195 }, + { @"Ä", 196 }, + { @"Å", 197 }, + { @"Æ", 198 }, + { @"Ç", 199 }, + { @"È", 200 }, + { @"É", 201 }, + { @"Ê", 202 }, + { @"Ë", 203 }, + { @"Ì", 204 }, + { @"Í", 205 }, + { @"Î", 206 }, + { @"Ï", 207 }, + { @"Ð", 208 }, + { @"Ñ", 209 }, + { @"Ò", 210 }, + { @"Ó", 211 }, + { @"Ô", 212 }, + { @"Õ", 213 }, + { @"Ö", 214 }, + { @"×", 215 }, + { @"Ø", 216 }, + { @"Ù", 217 }, + { @"Ú", 218 }, + { @"Û", 219 }, + { @"Ü", 220 }, + { @"Ý", 221 }, + { @"Þ", 222 }, + { @"ß", 223 }, + { @"à", 224 }, + { @"á", 225 }, + { @"â", 226 }, + { @"ã", 227 }, + { @"ä", 228 }, + { @"å", 229 }, + { @"æ", 230 }, + { @"ç", 231 }, + { @"è", 232 }, + { @"é", 233 }, + { @"ê", 234 }, + { @"ë", 235 }, + { @"ì", 236 }, + { @"í", 237 }, + { @"î", 238 }, + { @"ï", 239 }, + { @"ð", 240 }, + { @"ñ", 241 }, + { @"ò", 242 }, + { @"ó", 243 }, + { @"ô", 244 }, + { @"õ", 245 }, + { @"ö", 246 }, + { @"÷", 247 }, + { @"ø", 248 }, + { @"ù", 249 }, + { @"ú", 250 }, + { @"û", 251 }, + { @"ü", 252 }, + { @"ý", 253 }, + { @"þ", 254 }, + { @"ÿ", 255 }, + + // A.2.2. Special characters cont'd + { @"Œ", 338 }, + { @"œ", 339 }, + { @"Š", 352 }, + { @"š", 353 }, + { @"Ÿ", 376 }, + + // A.2.3. Symbols + { @"ƒ", 402 }, + + // A.2.2. Special characters cont'd + { @"ˆ", 710 }, + { @"˜", 732 }, + + // A.2.3. Symbols cont'd + { @"Α", 913 }, + { @"Β", 914 }, + { @"Γ", 915 }, + { @"Δ", 916 }, + { @"Ε", 917 }, + { @"Ζ", 918 }, + { @"Η", 919 }, + { @"Θ", 920 }, + { @"Ι", 921 }, + { @"Κ", 922 }, + { @"Λ", 923 }, + { @"Μ", 924 }, + { @"Ν", 925 }, + { @"Ξ", 926 }, + { @"Ο", 927 }, + { @"Π", 928 }, + { @"Ρ", 929 }, + { @"Σ", 931 }, + { @"Τ", 932 }, + { @"Υ", 933 }, + { @"Φ", 934 }, + { @"Χ", 935 }, + { @"Ψ", 936 }, + { @"Ω", 937 }, + { @"α", 945 }, + { @"β", 946 }, + { @"γ", 947 }, + { @"δ", 948 }, + { @"ε", 949 }, + { @"ζ", 950 }, + { @"η", 951 }, + { @"θ", 952 }, + { @"ι", 953 }, + { @"κ", 954 }, + { @"λ", 955 }, + { @"μ", 956 }, + { @"ν", 957 }, + { @"ξ", 958 }, + { @"ο", 959 }, + { @"π", 960 }, + { @"ρ", 961 }, + { @"ς", 962 }, + { @"σ", 963 }, + { @"τ", 964 }, + { @"υ", 965 }, + { @"φ", 966 }, + { @"χ", 967 }, + { @"ψ", 968 }, + { @"ω", 969 }, + { @"ϑ", 977 }, + { @"ϒ", 978 }, + { @"ϖ", 982 }, + + // A.2.2. Special characters cont'd + { @" ", 8194 }, + { @" ", 8195 }, + { @" ", 8201 }, + { @"‌", 8204 }, + { @"‍", 8205 }, + { @"‎", 8206 }, + { @"‏", 8207 }, + { @"–", 8211 }, + { @"—", 8212 }, + { @"‘", 8216 }, + { @"’", 8217 }, + { @"‚", 8218 }, + { @"“", 8220 }, + { @"”", 8221 }, + { @"„", 8222 }, + { @"†", 8224 }, + { @"‡", 8225 }, + // A.2.3. Symbols cont'd + { @"•", 8226 }, + { @"…", 8230 }, + + // A.2.2. Special characters cont'd + { @"‰", 8240 }, + + // A.2.3. Symbols cont'd + { @"′", 8242 }, + { @"″", 8243 }, + + // A.2.2. Special characters cont'd + { @"‹", 8249 }, + { @"›", 8250 }, + + // A.2.3. Symbols cont'd + { @"‾", 8254 }, + { @"⁄", 8260 }, + + // A.2.2. Special characters cont'd + { @"€", 8364 }, + + // A.2.3. Symbols cont'd + { @"ℑ", 8465 }, + { @"℘", 8472 }, + { @"ℜ", 8476 }, + { @"™", 8482 }, + { @"ℵ", 8501 }, + { @"←", 8592 }, + { @"↑", 8593 }, + { @"→", 8594 }, + { @"↓", 8595 }, + { @"↔", 8596 }, + { @"↵", 8629 }, + { @"⇐", 8656 }, + { @"⇑", 8657 }, + { @"⇒", 8658 }, + { @"⇓", 8659 }, + { @"⇔", 8660 }, + { @"∀", 8704 }, + { @"∂", 8706 }, + { @"∃", 8707 }, + { @"∅", 8709 }, + { @"∇", 8711 }, + { @"∈", 8712 }, + { @"∉", 8713 }, + { @"∋", 8715 }, + { @"∏", 8719 }, + { @"∑", 8721 }, + { @"−", 8722 }, + { @"∗", 8727 }, + { @"√", 8730 }, + { @"∝", 8733 }, + { @"∞", 8734 }, + { @"∠", 8736 }, + { @"∧", 8743 }, + { @"∨", 8744 }, + { @"∩", 8745 }, + { @"∪", 8746 }, + { @"∫", 8747 }, + { @"∴", 8756 }, + { @"∼", 8764 }, + { @"≅", 8773 }, + { @"≈", 8776 }, + { @"≠", 8800 }, + { @"≡", 8801 }, + { @"≤", 8804 }, + { @"≥", 8805 }, + { @"⊂", 8834 }, + { @"⊃", 8835 }, + { @"⊄", 8836 }, + { @"⊆", 8838 }, + { @"⊇", 8839 }, + { @"⊕", 8853 }, + { @"⊗", 8855 }, + { @"⊥", 8869 }, + { @"⋅", 8901 }, + { @"⌈", 8968 }, + { @"⌉", 8969 }, + { @"⌊", 8970 }, + { @"⌋", 8971 }, + { @"⟨", 9001 }, + { @"⟩", 9002 }, + { @"◊", 9674 }, + { @"♠", 9824 }, + { @"♣", 9827 }, + { @"♥", 9829 }, + { @"♦", 9830 } +}; + + +@implementation NSString (WPKitXMLExtensions) + ++ (NSString *)wpkit_encodeXMLCharactersIn : (NSString *)source { + if (![source isKindOfClass:[NSString class]] || !source) + return @""; + + NSString *result = [NSString stringWithString:source]; + + // NOTE: we use unicode entities instead of & > < since some weird hosts (powweb, fatcow, and cousins) + // have a weird PHP/libxml2 combination that ignores regular entities + if ([result rangeOfString:@"&"].location != NSNotFound) + result = [[result componentsSeparatedByString:@"&"] componentsJoinedByString:@"&"]; + + if ([result rangeOfString:@"<"].location != NSNotFound) + result = [[result componentsSeparatedByString:@"<"] componentsJoinedByString:@"<"]; + + if ([result rangeOfString:@">"].location != NSNotFound) + result = [[result componentsSeparatedByString:@">"] componentsJoinedByString:@">"]; + + return result; +} + + ++ (NSString *) wpkit_decodeXMLCharactersIn:(NSString *)original { + if (![original isKindOfClass:[NSString class]] || !original) + return @""; + + NSString *source = [NSString stringWithString:original]; + + NSRange range = NSMakeRange(0, [source length]); + NSRange subrange = [source rangeOfString:@"&" options:NSBackwardsSearch range:range]; + + // if no ampersands, we've got a quick way out + if (subrange.length == 0) return source; + NSMutableString *finalString = [NSMutableString stringWithString:source]; + do { + NSRange semiColonRange = NSMakeRange(subrange.location, NSMaxRange(range) - subrange.location); + semiColonRange = [source rangeOfString:@";" options:0 range:semiColonRange]; + range = NSMakeRange(0, subrange.location); + // if we don't find a semicolon in the range, we don't have a sequence + if (semiColonRange.location == NSNotFound) { + continue; + } + NSRange escapeRange = NSMakeRange(subrange.location, semiColonRange.location - subrange.location + 1); + NSString *escapeString = [source substringWithRange:escapeRange]; + NSUInteger length = [escapeString length]; + // a squence must be longer than 3 (<) and less than 11 (ϑ) + if (length > 3 && length < 11) { + if ([escapeString characterAtIndex:1] == '#') { + unichar char2 = [escapeString characterAtIndex:2]; + if (char2 == 'x' || char2 == 'X') { + // Hex escape squences £ + NSString *hexSequence = [escapeString substringWithRange:NSMakeRange(3, length - 4)]; + NSScanner *scanner = [NSScanner scannerWithString:hexSequence]; + unsigned value; + if ([scanner scanHexInt:&value] && + value < USHRT_MAX && + value > 0 + && [scanner scanLocation] == length - 4) { + unichar uchar = value; + NSString *charString = [NSString stringWithCharacters:&uchar length:1]; + [finalString replaceCharactersInRange:escapeRange withString:charString]; + } + + } else { + // Decimal Sequences { + NSString *numberSequence = [escapeString substringWithRange:NSMakeRange(2, length - 3)]; + NSScanner *scanner = [NSScanner scannerWithString:numberSequence]; + int value; + if ([scanner scanInt:&value] && + value < USHRT_MAX && + value > 0 + && [scanner scanLocation] == length - 3) { + unichar uchar = value; + NSString *charString = [NSString stringWithCharacters:&uchar length:1]; + [finalString replaceCharactersInRange:escapeRange withString:charString]; + } + } + } else { + // "standard" sequences + for (unsigned i = 0; i < sizeof(gAsciiHTMLEscapeMap) / sizeof(HTMLEscapeMap); ++i) { + if ([escapeString isEqualToString:gAsciiHTMLEscapeMap[i].escapeSequence]) { + [finalString replaceCharactersInRange:escapeRange withString:[NSString stringWithCharacters:&gAsciiHTMLEscapeMap[i].uchar length:1]]; + break; + } + } + } + } + + } while ((subrange = [source rangeOfString:@"&" options:NSBackwardsSearch range:range]).length != 0); + + return finalString; +} + +- (NSString *)wpkit_stringByDecodingXMLCharacters { + return [NSString wpkit_decodeXMLCharactersIn:self]; +} +- (NSString *)wpkit_stringByEncodingXMLCharacters { + return [NSString wpkit_encodeXMLCharactersIn:self]; +} + + +@end diff --git a/Sources/WordPressShared/Secret.swift b/Sources/WordPressShared/Secret.swift new file mode 100644 index 000000000..66b5a8f03 --- /dev/null +++ b/Sources/WordPressShared/Secret.swift @@ -0,0 +1,49 @@ +import Foundation + +/// Wraps a value that contains sensitive information to prevent accidental logging +/// +/// Usage example +/// +/// ``` +/// let password = Secret("my secret password") +/// print(password) // Prints "--redacted--" +/// print(password.secretValue) // Prints "my secret password" +/// ``` +/// +public struct Secret { + public let secretValue: T + + public init(_ secretValue: T) { + self.secretValue = secretValue + } +} + +extension Secret: RawRepresentable { + public typealias RawValue = T + + public init?(rawValue: Self.RawValue) { + self.init(rawValue) + } + + public var rawValue: T { + return secretValue + } +} + +extension Secret: CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { + private static var redacted: String { + return "--redacted--" + } + + public var description: String { + return Secret.redacted + } + + public var debugDescription: String { + return Secret.redacted + } + + public var customMirror: Mirror { + return Mirror(reflecting: Secret.redacted) + } +} diff --git a/Sources/WordPressShared/String+Helpers.swift b/Sources/WordPressShared/String+Helpers.swift new file mode 100644 index 000000000..cc73aa2f4 --- /dev/null +++ b/Sources/WordPressShared/String+Helpers.swift @@ -0,0 +1,167 @@ +import Foundation + +extension String { + func stringByDecodingXMLCharacters() -> String { + return NSString.wpkit_decodeXMLCharacters(in: self) + } + + func stringByEncodingXMLCharacters() -> String { + return NSString.wpkit_encodeXMLCharacters(in: self) + } + + func trim() -> String { + return trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + } + + /// Returns `self` if not empty, or `nil` otherwise + /// + func nonEmptyString() -> String? { + return isEmpty ? nil : self + } + + /// Returns a string without the character at the specified index. + /// This is a non-mutating version of `String.remove(at:)`. + func removing(at index: Index) -> String { + var copy = self + copy.remove(at: index) + return copy + } + + /// Returns a count of valid text characters. + /// - Note : This implementation is influenced by `-wordCount` in `NSString+Helpers`. + var characterCount: Int { + var charCount = 0 + + if isEmpty == false { + let textRange = startIndex.. String { + var copy = self + copy.removePrefix(prefix) + return copy + } + + /// Removes the prefix from the string that matches the given pattern, if any. + /// + /// Calling this method might invalidate any existing indices for use with this string. + /// + /// - Parameters: + /// - pattern: The regular expression pattern to search for. Avoid using `^`. + /// - options: The options applied to the regular expression during matching. + /// + /// - Throws: an error if it the pattern is not a valid regular expression. + /// + mutating func removePrefix(pattern: String, options: NSRegularExpression.Options = []) throws { + let regexp = try NSRegularExpression(pattern: "^\(pattern)", options: options) + let fullRange = NSRange(location: 0, length: (self as NSString).length) + if let match = regexp.firstMatch(in: self, options: [], range: fullRange) { + let matchRange = match.range + self = (self as NSString).replacingCharacters(in: matchRange, with: "") + } + } + + /// Returns a string without the prefix that matches the given pattern, if it exists. + /// + /// - Parameters: + /// - pattern: The regular expression pattern to search for. Avoid using `^`. + /// - options: The options applied to the regular expression during matching. + /// + /// - Throws: an error if it the pattern is not a valid regular expression. + /// + func removingPrefix(pattern: String, options: NSRegularExpression.Options = []) throws -> String { + var copy = self + try copy.removePrefix(pattern: pattern, options: options) + return copy + } +} + +// MARK: - Suffix removal + +extension String { + /// Removes the given suffix from the string, if exists. + /// + /// Calling this method might invalidate any existing indices for use with this string. + /// + /// - Parameters: + /// - suffix: A possible suffix to remove from this string. + /// + mutating func removeSuffix(_ suffix: String) { + if let suffixRange = range(of: suffix, options: [.backwards]), suffixRange.upperBound == endIndex { + removeSubrange(suffixRange) + } + } + + /// Returns a string with the given suffix removed, if it exists. + /// + /// - Parameters: + /// - suffix: A possible suffix to remove from this string. + /// + func removingSuffix(_ suffix: String) -> String { + var copy = self + copy.removeSuffix(suffix) + return copy + } + + /// Removes the suffix from the string that matches the given pattern, if any. + /// + /// Calling this method might invalidate any existing indices for use with this string. + /// + /// - Parameters: + /// - pattern: The regular expression pattern to search for. Avoid using `$`. + /// - options: The options applied to the regular expression during matching. + /// + /// - Throws: an error if it the pattern is not a valid regular expression. + /// + mutating func removeSuffix(pattern: String, options: NSRegularExpression.Options = []) throws { + let regexp = try NSRegularExpression(pattern: "\(pattern)$", options: options) + let fullRange = NSRange(location: 0, length: (self as NSString).length) + if let match = regexp.firstMatch(in: self, options: [], range: fullRange) { + let matchRange = match.range + self = (self as NSString).replacingCharacters(in: matchRange, with: "") + } + } + + /// Returns a string without the suffix that matches the given pattern, if it exists. + /// + /// - Parameters: + /// - pattern: The regular expression pattern to search for. Avoid using `$`. + /// - options: The options applied to the regular expression during matching. + /// + /// - Throws: an error if it the pattern is not a valid regular expression. + /// + func removingSuffix(pattern: String, options: NSRegularExpression.Options = []) throws -> String { + var copy = self + try copy.removeSuffix(pattern: pattern, options: options) + return copy + } +} diff --git a/Sources/WordPressShared/WPKitDateUtils.h b/Sources/WordPressShared/WPKitDateUtils.h new file mode 100644 index 000000000..4cd6a3372 --- /dev/null +++ b/Sources/WordPressShared/WPKitDateUtils.h @@ -0,0 +1,8 @@ +#import + +@interface WPKitDateUtils : NSObject + ++ (NSDate *)dateFromISOString:(NSString *)isoString; ++ (NSString *)isoStringFromDate:(NSDate *)date; + +@end diff --git a/Sources/WordPressShared/WPKitDateUtils.m b/Sources/WordPressShared/WPKitDateUtils.m new file mode 100644 index 000000000..02f561cf0 --- /dev/null +++ b/Sources/WordPressShared/WPKitDateUtils.m @@ -0,0 +1,37 @@ +#import "WPKitDateUtils.h" + +@implementation WPKitDateUtils + ++ (NSDate *)dateFromISOString:(NSString *)dateString +{ + NSArray *formats = @[@"yyyy-MM-dd'T'HH:mm:ssZZZZZ", @"yyyy-MM-dd HH:mm:ss"]; + NSDate *date = nil; + if ([dateString length] == 25) { + NSRange rng = [dateString rangeOfString:@":" options:NSBackwardsSearch range:NSMakeRange(20, 5)]; + if (rng.location != NSNotFound) { + dateString = [dateString stringByReplacingCharactersInRange:rng withString:@""]; + } + } + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; + for (NSString *dateFormat in formats) { + [dateFormatter setDateFormat:dateFormat]; + date = [dateFormatter dateFromString:dateString]; + if (date){ + return date; + } + } + return date; +} + ++ (NSString *)isoStringFromDate:(NSDate *)date +{ + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"]; + return [dateFormatter stringFromDate:date]; +} + +@end diff --git a/Sources/WordPressShared/WPMapFilterReduce.h b/Sources/WordPressShared/WPMapFilterReduce.h new file mode 100644 index 000000000..c7968cf91 --- /dev/null +++ b/Sources/WordPressShared/WPMapFilterReduce.h @@ -0,0 +1,22 @@ +#import + +typedef id (^WPKitMapBlock)(id obj); +typedef BOOL (^WPKitFilterBlock)(id obj); + +@interface NSArray (WPKitMapFilterReduce) + +/** + Transforms values in an array + + The resulting array will include the results of calling mapBlock for each of + the receiver array objects. If mapBlock returns nil that value will be missing + from the resulting array. + */ +- (NSArray *)wpkit_map:(WPKitMapBlock)mapBlock; + +/** + Filters an array to only include values that satisfy the filter block + */ +- (NSArray *)wpkit_filter:(WPKitFilterBlock)filterBlock; + +@end diff --git a/Sources/WordPressShared/WPMapFilterReduce.m b/Sources/WordPressShared/WPMapFilterReduce.m new file mode 100644 index 000000000..1e8b76839 --- /dev/null +++ b/Sources/WordPressShared/WPMapFilterReduce.m @@ -0,0 +1,28 @@ +#import "WPMapFilterReduce.h" + +@implementation NSArray (WPKitMapFilterReduce) + +- (NSArray *)wpkit_map:(WPKitMapBlock)mapBlock +{ + NSMutableArray *results = [NSMutableArray arrayWithCapacity:self.count]; + for (id obj in self) { + id objectToAdd = mapBlock(obj); + if (objectToAdd) { + [results addObject:objectToAdd]; + } + } + return [NSArray arrayWithArray:results]; +} + +- (NSArray *)wpkit_filter:(WPKitFilterBlock)filterBlock +{ + NSMutableArray *results = [NSMutableArray arrayWithCapacity:self.count]; + for (id obj in self) { + if (filterBlock(obj)) { + [results addObject:obj]; + } + } + return [NSArray arrayWithArray:results]; +} + +@end diff --git a/Sources/WordPressShared/WordPressComLanguageDatabase.swift b/Sources/WordPressShared/WordPressComLanguageDatabase.swift new file mode 100644 index 000000000..d1239f174 --- /dev/null +++ b/Sources/WordPressShared/WordPressComLanguageDatabase.swift @@ -0,0 +1,360 @@ +import Foundation + +/// This helper class allows us to map WordPress.com LanguageID's into human readable language strings. +/// +class WordPressComLanguageDatabase: NSObject { + // MARK: - Properties + + /// Languages considered 'popular' + /// + let popular: [Language] + + /// Every supported language + /// + let all: [Language] + + /// Returns both, Popular and All languages, grouped + /// + let grouped: [[Language]] + + // MARK: - Methods + + /// Designated Initializer: will load the languages contained within the `Languages.json` file. + /// + override init() { + // Parse the json file + let raw = languagesJSON.data(using: .utf8)! + let parsed = try! JSONSerialization.jsonObject(with: raw, options: [.mutableContainers, .mutableLeaves]) as? NSDictionary + + // Parse All + Popular: All doesn't contain Popular. Otherwise the json would have dupe data. Right? + let parsedAll = Language.fromArray(parsed![Keys.all] as! [[String: Any]]) + let parsedPopular = Language.fromArray(parsed![Keys.popular] as! [[String: Any]]) + let merged = parsedAll + parsedPopular + + // Done! + popular = parsedPopular + all = merged.sorted { $0.name < $1.name } + grouped = [popular] + [all] + } + + /// Returns the Human Readable name for a given Language Identifier + /// + /// - Parameter languageId: The Identifier of the language. + /// + /// - Returns: A string containing the language name, or an empty string, in case it wasn't found. + /// + @objc func nameForLanguageWithId(_ languageId: Int) -> String { + return find(id: languageId)?.name ?? "" + } + + /// Returns the Language with a given Language Identifier + /// + /// - Parameter id: The Identifier of the language. + /// + /// - Returns: The language with the matching Identifier, or nil, in case it wasn't found. + /// + func find(id: Int) -> Language? { + return all.first(where: { $0.id == id }) + } + + /// Returns the current device language as the corresponding WordPress.com language ID. + /// If the language is not supported, it returns 1 (English). + /// + /// This is a wrapper for Objective-C, Swift code should use deviceLanguage directly. + /// + @objc(deviceLanguageId) + func deviceLanguageIdNumber() -> NSNumber { + return NSNumber(value: deviceLanguage.id) + } + + /// Returns the slug string for the current device language. + /// If the language is not supported, it returns "en" (English). + /// + /// This is a wrapper for Objective-C, Swift code should use deviceLanguage directly. + /// + @objc(deviceLanguageSlug) + func deviceLanguageSlugString() -> String { + return deviceLanguage.slug + } + + /// Returns the current device language as the corresponding WordPress.com language. + /// If the language is not supported, it returns English. + /// + var deviceLanguage: Language { + let variants = LanguageTagVariants(string: deviceLanguageCode) + for variant in variants { + if let match = self.languageWithSlug(variant) { + return match + } + } + return languageWithSlug("en")! + } + + /// Searches for a WordPress.com language that matches a language tag. + /// + fileprivate func languageWithSlug(_ slug: String) -> Language? { + let search = languageCodeReplacements[slug] ?? slug + + // Use lazy evaluation so we stop filtering as soon as we got the first match + return all.lazy.filter({ $0.slug == search }).first + } + + /// Overrides the device language. For testing purposes only. + /// + @objc func _overrideDeviceLanguageCode(_ code: String) { + deviceLanguageCode = code.lowercased() + } + + // MARK: - Nested Classes + + /// Represents a Language supported by WordPress.com + /// + class Language: Equatable { + /// Language Unique Identifier + /// + let id: Int + + /// Human readable Language name + /// + let name: String + + /// Language's Slug String + /// + let slug: String + + /// Localized description for the current language + /// + var description: String { + return (Locale.current as NSLocale).displayName(forKey: NSLocale.Key.identifier, value: slug) ?? name + } + + /// Designated initializer. Will fail if any of the required properties is missing + /// + init?(dict: [String: Any]) { + guard let unwrappedId = (dict[Keys.identifier] as? NSNumber)?.intValue, + let unwrappedSlug = dict[Keys.slug] as? String, + let unwrappedName = dict[Keys.name] as? String else { + id = Int.min + name = String() + slug = String() + return nil + } + + id = unwrappedId + name = unwrappedName + slug = unwrappedSlug + } + + /// Given an array of raw languages, will return a parsed array. + /// + static func fromArray(_ array: [[String: Any]] ) -> [Language] { + return array.compactMap { + return Language(dict: $0) + } + } + + static func == (lhs: Language, rhs: Language) -> Bool { + return lhs.id == rhs.id + } + } + + // MARK: - Private Variables + + /// The device's current preferred language, or English if there's no preferred language. + /// + fileprivate lazy var deviceLanguageCode: String = { + return NSLocale.preferredLanguages.first?.lowercased() ?? "en" + }() + + // MARK: - Private Constants + fileprivate let filename = "Languages" + + // (@koke 2016-04-29) I'm not sure how correct this mapping is, but it matches + // what we do for the app translations, so they will at least be consistent + fileprivate let languageCodeReplacements: [String: String] = [ + "zh-hans": "zh-cn", + "zh-hant": "zh-tw" + ] + + // MARK: - Private Nested Structures + + /// Keys used to parse the raw languages. + /// + fileprivate struct Keys { + static let popular = "popular" + static let all = "all" + static let identifier = "i" + static let slug = "s" + static let name = "n" + } +} + +/// Provides a sequence of language tags from the specified string, from more to less specific +/// For instance, "zh-Hans-HK" will yield `["zh-Hans-HK", "zh-Hans", "zh"]` +/// +private struct LanguageTagVariants: Sequence { + let string: String + + func makeIterator() -> AnyIterator { + var components = string.components(separatedBy: "-") + return AnyIterator { + guard !components.isEmpty else { + return nil + } + + let current = components.joined(separator: "-") + components.removeLast() + + return current + } + } +} + +private let languagesJSON = """ +{ + "popular" : [ + { "i": 1, "s": "en", "n": "English" }, + { "i": 19, "s": "es", "n": "Español" }, + { "i": 438, "s": "pt-br", "n": "Português do Brasil" }, + { "i": 15, "s": "de", "n": "Deutsch" }, + { "i": 24, "s": "fr", "n": "Français" }, + { "i": 29, "s": "he", "n": "עברית" }, + { "i": 36, "s": "ja", "n": "日本語" }, + { "i": 35, "s": "it", "n": "Italiano" }, + { "i": 49, "s": "nl", "n": "Nederlands" }, + { "i": 62, "s": "ru", "n": "Русский" }, + { "i": 78, "s": "tr", "n": "Türkçe" }, + { "i": 33, "s": "id", "n": "Bahasa Indonesia" }, + { "i": 449, "s": "zh-cn", "n": "中文(简体)" }, + { "i": 452, "s": "zh-tw", "n": "中文(繁體)" }, + { "i": 40, "s": "ko", "n": "한국어" } + ], + "all" : [ + { "i": 2, "s": "af", "n": "Afrikaans" }, + { "i": 418, "s": "als", "n": "Alemannisch" }, + { "i": 481, "s": "am", "n": "Amharic" }, + { "i": 3, "s": "ar", "n": "العربية" }, + { "i": 419, "s": "arc", "n": "ܕܥܒܪܸܝܛ" }, + { "i": 4, "s": "as", "n": "অসমীয়া" }, + { "i": 420, "s": "ast", "n": "Asturianu" }, + { "i": 421, "s": "av", "n": "Авар" }, + { "i": 422, "s": "ay", "n": "Aymar" }, + { "i": 79, "s": "az", "n": "Azərbaycan" }, + { "i": 423, "s": "ba", "n": "Башҡорт" }, + { "i": 5, "s": "be", "n": "Беларуская" }, + { "i": 6, "s": "bg", "n": "Български" }, + { "i": 7, "s": "bm", "n": "Bamanankan" }, + { "i": 8, "s": "bn", "n": "বাংলা" }, + { "i": 9, "s": "bo", "n": "བོད་ཡིག" }, + { "i": 424, "s": "br", "n": "Brezhoneg" }, + { "i": 454, "s": "bs", "n": "Bosanski" }, + { "i": 10, "s": "ca", "n": "Català" }, + { "i": 425, "s": "ce", "n": "Нохчийн" }, + { "i": 11, "s": "cs", "n": "Česky" }, + { "i": 12, "s": "csb", "n": "Kaszëbsczi" }, + { "i": 426, "s": "cv", "n": "Чӑваш" }, + { "i": 13, "s": "cy", "n": "Cymraeg" }, + { "i": 14, "s": "da", "n": "Dansk" }, + { "i": 427, "s": "dv", "n": "Divehi" }, + { "i": 16, "s": "dz", "n": "ཇོང་ཁ" }, + { "i": 17, "s": "el", "n": "Ελληνικά" }, + { "i": 468, "s": "el-po", "n": "Greek-polytonic" }, + { "i": 18, "s": "eo", "n": "Esperanto" }, + { "i": 20, "s": "et", "n": "Eesti" }, + { "i": 429, "s": "eu", "n": "Euskara" }, + { "i": 21, "s": "fa", "n": "فارسی" }, + { "i": 22, "s": "fi", "n": "Suomi" }, + { "i": 473, "s": "fil", "n": "Filipino" }, + { "i": 23, "s": "fo", "n": "Føroyskt" }, + { "i": 478, "s": "fr-be", "n": "Français de Belgique" }, + { "i": 475, "s": "fr-ca", "n": "Français (Canada)" }, + { "i": 474, "s": "fr-ch", "n": "Français de Suisse" }, + { "i": 25, "s": "fur", "n": "Furlan" }, + { "i": 26, "s": "fy", "n": "Frysk" }, + { "i": 27, "s": "ga", "n": "Gaeilge" }, + { "i": 476, "s": "gd", "n": "Gàidhlig" }, + { "i": 457, "s": "gl", "n": "Galego" }, + { "i": 430, "s": "gn", "n": "Avañeẽ" }, + { "i": 28, "s": "gu", "n": "ગુજરાતી" }, + { "i": 30, "s": "hi", "n": "हिन्दी" }, + { "i": 431, "s": "hr", "n": "Hrvatski" }, + { "i": 31, "s": "hu", "n": "Magyar" }, + { "i": 467, "s": "hy", "n": "Armenian" }, + { "i": 32, "s": "ia", "n": "Interlingua" }, + { "i": 432, "s": "ii", "n": "ꆇꉙ" }, + { "i": 469, "s": "ilo", "n": "Ilokano" }, + { "i": 34, "s": "is", "n": "Íslenska" }, + { "i": 37, "s": "ka", "n": "ქართული" }, + { "i": 462, "s": "kk", "n": "Қазақ тілі" }, + { "i": 38, "s": "km", "n": "ភាសាខ្មែរ" }, + { "i": 39, "s": "kn", "n": "ಕನ್ನಡ" }, + { "i": 433, "s": "ks", "n": "कश्मीरी - (كشميري)" }, + { "i": 41, "s": "ku", "n": "Kurdî / كوردي" }, + { "i": 434, "s": "kv", "n": "Коми" }, + { "i": 479, "s": "ky", "n": "кыргыз тили" }, + { "i": 42, "s": "la", "n": "Latina" }, + { "i": 43, "s": "li", "n": "Limburgs" }, + { "i": 44, "s": "lo", "n": "ລາວ" }, + { "i": 45, "s": "lt", "n": "Lietuvių" }, + { "i": 453, "s": "lv", "n": "Latviešu valoda" }, + { "i": 435, "s": "mk", "n": "Македонски" }, + { "i": 46, "s": "ml", "n": "മലയാളം" }, + { "i": 472, "s": "mn", "n": "монгол хэл" }, + { "i": 461, "s": "mr", "n": "मराठी Marāṭhī" }, + { "i": 47, "s": "ms", "n": "Bahasa Melayu" }, + { "i": 465, "s": "mt", "n": "Malti" }, + { "i": 464, "s": "mwl", "n": "Mirandés" }, + { "i": 436, "s": "nah", "n": "Nahuatl" }, + { "i": 437, "s": "nap", "n": "Nnapulitano" }, + { "i": 48, "s": "nds", "n": "Plattdüütsch" }, + { "i": 456, "s": "ne", "n": "Nepali" }, + { "i": 50, "s": "nn", "n": "Norsk (nynorsk)" }, + { "i": 51, "s": "no", "n": "Norsk (bokmål)" }, + { "i": 52, "s": "non", "n": "Norrǿna" }, + { "i": 53, "s": "nv", "n": "Diné bizaad" }, + { "i": 54, "s": "oc", "n": "Occitan" }, + { "i": 55, "s": "or", "n": "ଓଡ଼ିଆ" }, + { "i": 56, "s": "os", "n": "Иронау" }, + { "i": 57, "s": "pa", "n": "ਪੰਜਾਬੀ" }, + { "i": 58, "s": "pl", "n": "Polski" }, + { "i": 59, "s": "ps", "n": "پښتو" }, + { "i": 60, "s": "pt", "n": "Português" }, + { "i": 439, "s": "qu", "n": "Runa Simi" }, + { "i": 61, "s": "ro", "n": "Română" }, + { "i": 483, "s": "rup", "n": "Armãneashce" }, + { "i": 63, "s": "sc", "n": "Sardu" }, + { "i": 440, "s": "sd", "n": "سنڌي" }, + { "i": 471, "s": "si", "n": "Sinhala" }, + { "i": 64, "s": "sk", "n": "Slovenčina" }, + { "i": 65, "s": "sl", "n": "Slovenščina" }, + { "i": 459, "s": "so", "n": "Somali" }, + { "i": 66, "s": "sq", "n": "Shqip" }, + { "i": 67, "s": "sr", "n": "Српски / Srpski" }, + { "i": 441, "s": "su", "n": "Basa Sunda" }, + { "i": 68, "s": "sv", "n": "Svenska" }, + { "i": 69, "s": "ta", "n": "தமிழ்" }, + { "i": 70, "s": "te", "n": "తెలుగు" }, + { "i": 71, "s": "th", "n": "ไทย" }, + { "i": 480, "s": "tir", "n": "Tigrigna" }, + { "i": 455, "s": "tl", "n": "Tagalog" }, + { "i": 72, "s": "tt", "n": "Tatarça" }, + { "i": 442, "s": "ty", "n": "Reo Mā`ohi" }, + { "i": 443, "s": "udm", "n": "Удмурт" }, + { "i": 444, "s": "ug", "n": "Uyghur"}, + { "i": 73, "s": "uk", "n": "Українська" }, + { "i": 74, "s": "ur", "n": "اردو" }, + { "i": 458, "s": "uz", "n": "Uzbek" }, + { "i": 463, "s": "va", "n": "valencià" }, + { "i": 445, "s": "vec", "n": "Vèneto" }, + { "i": 446, "s": "vi", "n": "Tiếng Việt" }, + { "i": 75, "s": "wa", "n": "Walon" }, + { "i": 447, "s": "xal", "n": "Хальмг" }, + { "i": 76, "s": "yi", "n": "ייִדיש" }, + { "i": 477, "s": "yo", "n": "èdè Yorùbá" }, + { "i": 448, "s": "za", "n": "Zhuang (Cuengh)" }, + { "i": 77, "s": "zh", "n": "中文" }, + { "i": 450, "s": "zh-hk", "n": "中文(繁體)" }, + { "i": 451, "s": "zh-sg", "n": "中文(简体)" } + ] +} +""" diff --git a/Tests/CoreAPITests/MultipartFormTests.swift b/Tests/CoreAPITests/MultipartFormTests.swift index c966ff9db..88d786d91 100644 --- a/Tests/CoreAPITests/MultipartFormTests.swift +++ b/Tests/CoreAPITests/MultipartFormTests.swift @@ -1,5 +1,4 @@ import Foundation -import Alamofire import XCTest import CryptoKit #if SWIFT_PACKAGE @@ -25,14 +24,6 @@ class MutliparFormDataTests: XCTestCase { return Form(fields: fields) } - func formDataUsingAlamofire() throws -> Data { - let formData = MultipartFormData(boundary: "testboundary") - for field in fields { - formData.append(field.content.data(using: .utf8)!, withName: field.name) - } - return try formData.encode() - } - func formData() throws -> Data { try fields .map { @@ -46,40 +37,33 @@ class MutliparFormDataTests: XCTestCase { func testRandomForm() throws { let tempDir = FileManager.default.temporaryDirectory let testData = tempDir.appendingPathComponent("test-form.json") - let afOutput = tempDir.appendingPathComponent("test-form.af.txt") let wpOutput = tempDir.appendingPathComponent("test-form.wp.txt") - let form = Form.random() + let form = Form(fields: [ + .init(name: "key-1", content: "a"), + .init(name: "key-2", content: "b"), + ]) try JSONEncoder().encode(form).write(to: testData) - let afEncoded = try form.formDataUsingAlamofire() - try afEncoded.write(to: afOutput) - let encoded = try form.formData() try encoded.write(to: wpOutput) add(XCTAttachment(contentsOfFile: testData)) - add(XCTAttachment(contentsOfFile: afOutput)) add(XCTAttachment(contentsOfFile: wpOutput)) - XCTAssertEqual(afEncoded, encoded) + let expected = "--testboundary\r\nContent-Disposition: form-data; name=\"key-1\"\r\n\r\na\r\n--testboundary\r\nContent-Disposition: form-data; name=\"key-2\"\r\n\r\nb\r\n--testboundary--\r\n".data(using: .utf8) + XCTAssertEqual(expected, encoded) } func testPlainText() throws { - let af = MultipartFormData() - af.append("hello".data(using: .utf8)!, withName: "world") - af.append("foo".data(using: .utf8)!, withName: "bar") - af.append("the".data(using: .utf8)!, withName: "end") - let afEncoded = try af.encode() - let fields = [ MultipartFormField(text: "hello", name: "world"), MultipartFormField(text: "foo", name: "bar"), MultipartFormField(text: "the", name: "end"), ] - let encoded = try fields.multipartFormDataStream(boundary: af.boundary).readToEnd() - - XCTAssertEqual(afEncoded, encoded) + let encoded = try fields.multipartFormDataStream(boundary: "wpkit.boundary.9d4adfc909a08bfa").readToEnd() + let expected = "--wpkit.boundary.9d4adfc909a08bfa\r\nContent-Disposition: form-data; name=\"world\"\r\n\r\nhello\r\n--wpkit.boundary.9d4adfc909a08bfa\r\nContent-Disposition: form-data; name=\"bar\"\r\n\r\nfoo\r\n--wpkit.boundary.9d4adfc909a08bfa\r\nContent-Disposition: form-data; name=\"end\"\r\n\r\nthe\r\n--wpkit.boundary.9d4adfc909a08bfa--\r\n".data(using: .utf8) + XCTAssertEqual(expected, encoded) } func testEmptyForm() throws { @@ -88,13 +72,12 @@ class MutliparFormDataTests: XCTestCase { } func testOneField() throws { - let af = MultipartFormData() - af.append("hello".data(using: .utf8)!, withName: "world") - let afEncoded = try af.encode() - - let formData = try [MultipartFormField(text: "hello", name: "world")].multipartFormDataStream(boundary: af.boundary).readToEnd() + let formData = try [MultipartFormField(text: "hello", name: "world")] + .multipartFormDataStream(boundary: "wpkit.boundary.9d4adfc909a08bfa") + .readToEnd() - XCTAssertEqual(afEncoded, formData) + let expected = "--wpkit.boundary.9d4adfc909a08bfa\r\nContent-Disposition: form-data; name=\"world\"\r\n\r\nhello\r\n--wpkit.boundary.9d4adfc909a08bfa--\r\n".data(using: .utf8)! + XCTAssertEqual(expected, formData) } func testUploadSmallFile() throws { diff --git a/Tests/CoreAPITests/NonceRetrievalTests.swift b/Tests/CoreAPITests/NonceRetrievalTests.swift index c27c8cd1d..eb0f54a9d 100644 --- a/Tests/CoreAPITests/NonceRetrievalTests.swift +++ b/Tests/CoreAPITests/NonceRetrievalTests.swift @@ -1,12 +1,9 @@ import Foundation import XCTest import OHHTTPStubs -#if SWIFT_PACKAGE -@testable import CoreAPI import OHHTTPStubsSwift -#else + @testable import WordPressKit -#endif class NonceRetrievalTests: XCTestCase { diff --git a/Tests/CoreAPITests/WordPressComOAuthClientTests.swift b/Tests/CoreAPITests/WordPressComOAuthClientTests.swift index 37e1c0af2..f5feb7ce2 100644 --- a/Tests/CoreAPITests/WordPressComOAuthClientTests.swift +++ b/Tests/CoreAPITests/WordPressComOAuthClientTests.swift @@ -1,12 +1,9 @@ import Foundation import XCTest import OHHTTPStubs -#if SWIFT_PACKAGE -@testable import CoreAPI import OHHTTPStubsSwift -#else + @testable import WordPressKit -#endif class WordPressComOAuthClientTests: XCTestCase { diff --git a/Tests/CoreAPITests/WordPressComRestApiTests+Locale.swift b/Tests/CoreAPITests/WordPressComRestApiTests+Locale.swift index 0435003a5..77a12077a 100644 --- a/Tests/CoreAPITests/WordPressComRestApiTests+Locale.swift +++ b/Tests/CoreAPITests/WordPressComRestApiTests+Locale.swift @@ -1,12 +1,8 @@ import XCTest import OHHTTPStubs -import WordPressShared -#if SWIFT_PACKAGE -@testable import CoreAPI import OHHTTPStubsSwift -#else + @testable import WordPressKit -#endif extension WordPressComRestApiTests { diff --git a/Tests/CoreAPITests/WordPressComRestApiTests.swift b/Tests/CoreAPITests/WordPressComRestApiTests.swift index a7f65b69c..83cca1894 100644 --- a/Tests/CoreAPITests/WordPressComRestApiTests.swift +++ b/Tests/CoreAPITests/WordPressComRestApiTests.swift @@ -1,13 +1,8 @@ import XCTest import OHHTTPStubs -import WordPressShared -#if SWIFT_PACKAGE -import APIInterface -@testable import CoreAPI import OHHTTPStubsSwift -#else + @testable import WordPressKit -#endif class WordPressComRestApiTests: XCTestCase { diff --git a/Tests/CoreAPITests/WordPressOrgAPITests.swift b/Tests/CoreAPITests/WordPressOrgAPITests.swift index 6b3a6b311..28c95fdc5 100644 --- a/Tests/CoreAPITests/WordPressOrgAPITests.swift +++ b/Tests/CoreAPITests/WordPressOrgAPITests.swift @@ -1,11 +1,8 @@ import XCTest import OHHTTPStubs -#if SWIFT_PACKAGE -@testable import CoreAPI import OHHTTPStubsSwift -#else + @testable import WordPressKit -#endif class WordPressOrgAPITests: XCTestCase { diff --git a/Tests/CoreAPITests/WordPressOrgRestApiTests.swift b/Tests/CoreAPITests/WordPressOrgRestApiTests.swift index 595d2c5d0..d70cacb75 100644 --- a/Tests/CoreAPITests/WordPressOrgRestApiTests.swift +++ b/Tests/CoreAPITests/WordPressOrgRestApiTests.swift @@ -1,11 +1,8 @@ import XCTest import OHHTTPStubs -#if SWIFT_PACKAGE -@testable import CoreAPI import OHHTTPStubsSwift -#else + @testable import WordPressKit -#endif class WordPressOrgRestApiTests: XCTestCase { diff --git a/Tests/CoreAPITests/WordPressOrgXMLRPCApiTests.swift b/Tests/CoreAPITests/WordPressOrgXMLRPCApiTests.swift index b198c5d09..332b6651c 100644 --- a/Tests/CoreAPITests/WordPressOrgXMLRPCApiTests.swift +++ b/Tests/CoreAPITests/WordPressOrgXMLRPCApiTests.swift @@ -1,12 +1,9 @@ import XCTest import OHHTTPStubs import wpxmlrpc -#if SWIFT_PACKAGE -@testable import CoreAPI import OHHTTPStubsSwift -#else + @testable import WordPressKit -#endif class WordPressOrgXMLRPCApiTests: XCTestCase { diff --git a/Tests/CoreAPITests/WordPressOrgXMLRPCValidatorTests.swift b/Tests/CoreAPITests/WordPressOrgXMLRPCValidatorTests.swift index 3a79b643d..0490d90de 100644 --- a/Tests/CoreAPITests/WordPressOrgXMLRPCValidatorTests.swift +++ b/Tests/CoreAPITests/WordPressOrgXMLRPCValidatorTests.swift @@ -1,11 +1,8 @@ import XCTest import OHHTTPStubs -#if SWIFT_PACKAGE -@testable import CoreAPI import OHHTTPStubsSwift -#else + @testable import WordPressKit -#endif final class WordPressOrgXMLRPCValidatorTests: XCTestCase { diff --git a/Tests/WordPressKitTests/Tests/ActivityServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/ActivityServiceRemoteTests.swift index 328a64e85..7661bc5f4 100644 --- a/Tests/WordPressKitTests/Tests/ActivityServiceRemoteTests.swift +++ b/Tests/WordPressKitTests/Tests/ActivityServiceRemoteTests.swift @@ -1,6 +1,8 @@ import Foundation import OHHTTPStubs +import OHHTTPStubsSwift import XCTest + @testable import WordPressKit class ActivityServiceRemoteTests: RemoteTestCase, RESTTestable { diff --git a/Tests/WordPressKitTests/Tests/AnnouncementServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/AnnouncementServiceRemoteTests.swift index bbcac5fed..e5fcf5f92 100644 --- a/Tests/WordPressKitTests/Tests/AnnouncementServiceRemoteTests.swift +++ b/Tests/WordPressKitTests/Tests/AnnouncementServiceRemoteTests.swift @@ -1,6 +1,7 @@ import Foundation import XCTest import OHHTTPStubs +import OHHTTPStubsSwift @testable import WordPressKit diff --git a/Tests/WordPressKitTests/Tests/BlockEditorSettingsServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/BlockEditorSettingsServiceRemoteTests.swift index e81a8afc4..39f377086 100644 --- a/Tests/WordPressKitTests/Tests/BlockEditorSettingsServiceRemoteTests.swift +++ b/Tests/WordPressKitTests/Tests/BlockEditorSettingsServiceRemoteTests.swift @@ -1,5 +1,7 @@ import XCTest import OHHTTPStubs +import OHHTTPStubsSwift + @testable import WordPressKit class BlockEditorSettingsServiceRemoteTests: XCTestCase { diff --git a/Tests/WordPressKitTests/Tests/BloggingPromptsServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/BloggingPromptsServiceRemoteTests.swift index 3aa2d3b18..844bd900e 100644 --- a/Tests/WordPressKitTests/Tests/BloggingPromptsServiceRemoteTests.swift +++ b/Tests/WordPressKitTests/Tests/BloggingPromptsServiceRemoteTests.swift @@ -1,5 +1,6 @@ import XCTest import OHHTTPStubs +import OHHTTPStubsSwift @testable import WordPressKit diff --git a/Tests/WordPressKitTests/Tests/CommentServiceRemoteREST+APIv2Tests.swift b/Tests/WordPressKitTests/Tests/CommentServiceRemoteREST+APIv2Tests.swift index 1cdb540ec..5d9692bba 100644 --- a/Tests/WordPressKitTests/Tests/CommentServiceRemoteREST+APIv2Tests.swift +++ b/Tests/WordPressKitTests/Tests/CommentServiceRemoteREST+APIv2Tests.swift @@ -1,6 +1,7 @@ import Foundation import XCTest import OHHTTPStubs +import OHHTTPStubsSwift @testable import WordPressKit diff --git a/Tests/WordPressKitTests/Tests/DashboardServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/DashboardServiceRemoteTests.swift index 8e6e60c03..401fe472e 100644 --- a/Tests/WordPressKitTests/Tests/DashboardServiceRemoteTests.swift +++ b/Tests/WordPressKitTests/Tests/DashboardServiceRemoteTests.swift @@ -1,5 +1,6 @@ import XCTest import OHHTTPStubs +import OHHTTPStubsSwift @testable import WordPressKit diff --git a/Tests/WordPressKitTests/Tests/DomainsServiceRemoteRESTTests.swift b/Tests/WordPressKitTests/Tests/DomainsServiceRemoteRESTTests.swift index 5e5f0289b..0f3870f26 100644 --- a/Tests/WordPressKitTests/Tests/DomainsServiceRemoteRESTTests.swift +++ b/Tests/WordPressKitTests/Tests/DomainsServiceRemoteRESTTests.swift @@ -1,6 +1,7 @@ import Foundation import XCTest import OHHTTPStubs +import OHHTTPStubsSwift @testable import WordPressKit diff --git a/Tests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift b/Tests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift index 85d7d87a5..da2091a97 100644 --- a/Tests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift +++ b/Tests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift @@ -2,6 +2,7 @@ import Foundation import XCTest import UniformTypeIdentifiers import OHHTTPStubs +import OHHTTPStubsSwift import wpxmlrpc /// This type acts like a WordPress Media Library. It can be used in test cases to stub loading media library content diff --git a/Tests/WordPressKitTests/Tests/Models/Stats/V2/Insights/StatsDotComFollowersInsightTests.swift b/Tests/WordPressKitTests/Tests/Models/Stats/V2/Insights/StatsDotComFollowersInsightTests.swift index fe9a1847d..440a9ac07 100644 --- a/Tests/WordPressKitTests/Tests/Models/Stats/V2/Insights/StatsDotComFollowersInsightTests.swift +++ b/Tests/WordPressKitTests/Tests/Models/Stats/V2/Insights/StatsDotComFollowersInsightTests.swift @@ -115,7 +115,7 @@ private extension StatsDotComFollowersInsightTests { guard var components = URLComponents(string: urlString) else { return nil } components.query = "d=mm&s=60" - return try? components.asURL() + return components.url } } diff --git a/Tests/WordPressKitTests/Tests/PluginDirectoryTests.swift b/Tests/WordPressKitTests/Tests/PluginDirectoryTests.swift index 886717782..13ad32aba 100644 --- a/Tests/WordPressKitTests/Tests/PluginDirectoryTests.swift +++ b/Tests/WordPressKitTests/Tests/PluginDirectoryTests.swift @@ -1,5 +1,7 @@ import XCTest import OHHTTPStubs +import OHHTTPStubsSwift + @testable import WordPressKit class PluginDirectoryTests: XCTestCase { diff --git a/Tests/WordPressKitTests/Tests/ReaderPostServiceRemote+FetchEndpointTests.swift b/Tests/WordPressKitTests/Tests/ReaderPostServiceRemote+FetchEndpointTests.swift index 3d96bf21e..90fa182a8 100644 --- a/Tests/WordPressKitTests/Tests/ReaderPostServiceRemote+FetchEndpointTests.swift +++ b/Tests/WordPressKitTests/Tests/ReaderPostServiceRemote+FetchEndpointTests.swift @@ -1,6 +1,7 @@ import Foundation import XCTest import OHHTTPStubs +import OHHTTPStubsSwift @testable import WordPressKit diff --git a/Tests/WordPressKitTests/Tests/ReaderPostServiceRemoteTests.m b/Tests/WordPressKitTests/Tests/ReaderPostServiceRemoteTests.m index e48c73713..89ada39b3 100644 --- a/Tests/WordPressKitTests/Tests/ReaderPostServiceRemoteTests.m +++ b/Tests/WordPressKitTests/Tests/ReaderPostServiceRemoteTests.m @@ -3,7 +3,6 @@ #import "ReaderPostServiceRemote.h" #import "RemoteReaderPost.h" #import "WPKit-Swift.h" -@import WordPressShared; @interface ReaderPostServiceRemoteTests : XCTestCase diff --git a/Tests/WordPressKitTests/Tests/ReaderSiteServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/ReaderSiteServiceRemoteTests.swift index ca9e80278..17379702b 100644 --- a/Tests/WordPressKitTests/Tests/ReaderSiteServiceRemoteTests.swift +++ b/Tests/WordPressKitTests/Tests/ReaderSiteServiceRemoteTests.swift @@ -1,5 +1,7 @@ import XCTest import OHHTTPStubs +import OHHTTPStubsSwift + @testable import WordPressKit class ReaderSiteServiceRemoteTests: XCTestCase { diff --git a/Tests/WordPressKitTests/Tests/RemoteReaderPostTests.m b/Tests/WordPressKitTests/Tests/RemoteReaderPostTests.m index 50a3c7510..c5aedf61c 100644 --- a/Tests/WordPressKitTests/Tests/RemoteReaderPostTests.m +++ b/Tests/WordPressKitTests/Tests/RemoteReaderPostTests.m @@ -4,7 +4,8 @@ #import "ReaderPostServiceRemote.h" #import "RemoteReaderPost.h" #import "WPKit-Swift.h" -@import WordPressShared; + +@import WordPressKit; @interface RemoteReaderPost () @@ -219,7 +220,7 @@ - (void)testSortDateFromDictionary { RemoteReaderPost *remoteReaderPost = [RemoteReaderPost alloc]; NSDate *now = [NSDate dateWithTimeIntervalSince1970:0]; - NSString *dateStr = [DateUtils isoStringFromDate:now]; + NSString *dateStr = [WPKitDateUtils isoStringFromDate:now]; NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setObject:dateStr forKey:@"date"]; diff --git a/Tests/WordPressKitTests/Tests/RemoteTestCase.swift b/Tests/WordPressKitTests/Tests/RemoteTestCase.swift index a55165b72..954f07c8c 100644 --- a/Tests/WordPressKitTests/Tests/RemoteTestCase.swift +++ b/Tests/WordPressKitTests/Tests/RemoteTestCase.swift @@ -2,6 +2,8 @@ import BuildkiteTestCollector import Foundation import XCTest import OHHTTPStubs +import OHHTTPStubsSwift + @testable import WordPressKit /// Base class for all remote unit tests. @@ -67,7 +69,7 @@ extension RemoteTestCase { if contentType != .NoContentType { headers = ["Content-Type" as NSObject: contentType.rawValue as AnyObject] } - return OHHTTPStubs.fixture(filePath: stubPath, status: status, headers: headers) + return OHHTTPStubsSwift.fixture(filePath: stubPath, status: status, headers: headers) } } diff --git a/Tests/WordPressKitTests/Tests/SelfHostedPluginManagementClientTests.swift b/Tests/WordPressKitTests/Tests/SelfHostedPluginManagementClientTests.swift index 5d278b7ea..3258f87da 100644 --- a/Tests/WordPressKitTests/Tests/SelfHostedPluginManagementClientTests.swift +++ b/Tests/WordPressKitTests/Tests/SelfHostedPluginManagementClientTests.swift @@ -1,6 +1,7 @@ import Foundation import XCTest import OHHTTPStubs +import OHHTTPStubsSwift @testable import WordPressKit diff --git a/Tests/WordPressKitTests/Tests/TransactionsServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/TransactionsServiceRemoteTests.swift index 1f98ea0c5..8926780c4 100644 --- a/Tests/WordPressKitTests/Tests/TransactionsServiceRemoteTests.swift +++ b/Tests/WordPressKitTests/Tests/TransactionsServiceRemoteTests.swift @@ -1,5 +1,4 @@ import XCTest -import WordPressShared @testable import WordPressKit class TransactionsServiceRemoteTests: RemoteTestCase, RESTTestable { diff --git a/Tests/WordPressKitTests/Tests/Utilities/FeatureFlagRemoteTests.swift b/Tests/WordPressKitTests/Tests/Utilities/FeatureFlagRemoteTests.swift index 602d8957e..f9e46c638 100644 --- a/Tests/WordPressKitTests/Tests/Utilities/FeatureFlagRemoteTests.swift +++ b/Tests/WordPressKitTests/Tests/Utilities/FeatureFlagRemoteTests.swift @@ -1,5 +1,7 @@ import XCTest import OHHTTPStubs +import OHHTTPStubsSwift + @testable import WordPressKit class FeatureFlagRemoteTests: RemoteTestCase, RESTTestable { diff --git a/Tests/WordPressKitTests/Tests/Utilities/LoggingTests.m b/Tests/WordPressKitTests/Tests/Utilities/LoggingTests.m index 60c4553b3..a884af946 100644 --- a/Tests/WordPressKitTests/Tests/Utilities/LoggingTests.m +++ b/Tests/WordPressKitTests/Tests/Utilities/LoggingTests.m @@ -2,7 +2,7 @@ @import WordPressKit; -@interface CaptureLogs : NSObject +@interface CaptureLogs : NSObject @property (nonatomic, strong) NSMutableArray *infoLogs; @property (nonatomic, strong) NSMutableArray *errorLogs; @@ -30,6 +30,19 @@ - (void)logError:(NSString *)str [self.errorLogs addObject:str]; } +- (void)logDebug:(nonnull NSString *)str { + +} + +- (void)logVerbose:(nonnull NSString *)str { + +} + +- (void)logWarning:(nonnull NSString *)str { + +} + + @end @interface ObjCLoggingTest : XCTestCase diff --git a/Tests/WordPressKitTests/Tests/Utilities/LoggingTests.swift b/Tests/WordPressKitTests/Tests/Utilities/LoggingTests.swift index 0981ed16b..8aee74b89 100644 --- a/Tests/WordPressKitTests/Tests/Utilities/LoggingTests.swift +++ b/Tests/WordPressKitTests/Tests/Utilities/LoggingTests.swift @@ -2,7 +2,7 @@ import XCTest @testable import WordPressKit -private class CaptureLogs: NSObject, WordPressLoggingDelegate { +private class CaptureLogs: NSObject, WordPressKitLoggingDelegate { private(set) var verboseLogs = [String]() private(set) var debugLogs = [String]() private(set) var infoLogs = [String]() diff --git a/Tests/WordPressKitTests/Tests/Utilities/URLSessionHelperTests.swift b/Tests/WordPressKitTests/Tests/Utilities/URLSessionHelperTests.swift index ebe0b154c..8f2096ada 100644 --- a/Tests/WordPressKitTests/Tests/Utilities/URLSessionHelperTests.swift +++ b/Tests/WordPressKitTests/Tests/Utilities/URLSessionHelperTests.swift @@ -2,6 +2,7 @@ import Foundation import CryptoKit import XCTest import OHHTTPStubs +import OHHTTPStubsSwift @testable import WordPressKit diff --git a/Tests/WordPressKitTests/Tests/WordPressComServiceRemoteRestTests.swift b/Tests/WordPressKitTests/Tests/WordPressComServiceRemoteRestTests.swift index 37d1f508a..72f3781bf 100644 --- a/Tests/WordPressKitTests/Tests/WordPressComServiceRemoteRestTests.swift +++ b/Tests/WordPressKitTests/Tests/WordPressComServiceRemoteRestTests.swift @@ -1,6 +1,7 @@ import XCTest import WordPressKit import OHHTTPStubs +import OHHTTPStubsSwift class WordPressComServiceRemoteRestTests: XCTestCase { diff --git a/WordPressKit.podspec b/WordPressKit.podspec deleted file mode 100644 index 85eefdbe7..000000000 --- a/WordPressKit.podspec +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -Pod::Spec.new do |s| - s.name = 'WordPressKit' - s.version = '17.2.0' - - s.summary = 'WordPressKit offers a clean and simple WordPress.com and WordPress.org API.' - s.description = <<-DESC - This framework encapsulates all of the networking calls and entity parsers required to interact - with WordPress.com and WordPress.org endpoints. - DESC - - s.homepage = 'https://github.com/wordpress-mobile/WordPressKit-iOS' - s.license = { type: 'GPLv2', file: 'LICENSE' } - s.author = { 'The WordPress Mobile Team' => 'mobile@wordpress.org' } - - s.platform = :ios, '13.0' - s.swift_version = '5.0' - - s.source = { git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', tag: s.version.to_s } - s.source_files = 'Sources/**/*.{h,m,swift}' - # When headers are not specified, then all headers are considered public. - # The only thing left to do is to explicitly specify those that should be private. - s.private_header_files = 'Sources/WordPressKit/Private/*.h' - - s.dependency 'NSObject-SafeExpectations', '~> 0.0.4' - s.dependency 'wpxmlrpc', '~> 0.10' - s.dependency 'UIDeviceIdentifier', '~> 2.0' - - # Use a loose restriction that allows both production and beta versions, up to the next major version. - # If you want to update which of these is used, specify it in the host app. - s.dependency 'WordPressShared', '~> 2.0-beta' -end diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index a83083cf5..23851528f 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -20,11 +20,36 @@ 0C1C08412B9CD79900E52F8C /* PostServiceRemoteExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08402B9CD79900E52F8C /* PostServiceRemoteExtended.swift */; }; 0C1C08432B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08422B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift */; }; 0C1C08452B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08442B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift */; }; + 0C363D422C41B455004E241D /* OCMock in Frameworks */ = {isa = PBXBuildFile; productRef = 0C363D412C41B455004E241D /* OCMock */; }; + 0C363D452C41B468004E241D /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 0C363D442C41B468004E241D /* OHHTTPStubs */; }; + 0C363D472C41B468004E241D /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C363D462C41B468004E241D /* OHHTTPStubsSwift */; }; 0C674E302BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */; }; + 0C938A062C416789009BA7B2 /* Secret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A052C416789009BA7B2 /* Secret.swift */; }; + 0C938A092C4167BC009BA7B2 /* NSString+XMLExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C938A072C4167BB009BA7B2 /* NSString+XMLExtensions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0C938A0A2C4167BC009BA7B2 /* NSString+XMLExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A082C4167BB009BA7B2 /* NSString+XMLExtensions.m */; }; + 0C938A0C2C416850009BA7B2 /* String+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A0B2C416850009BA7B2 /* String+Helpers.swift */; }; + 0C938A112C4168FB009BA7B2 /* NSString+Helpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C938A0F2C416883009BA7B2 /* NSString+Helpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0C938A122C4168FB009BA7B2 /* NSString+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A0D2C416876009BA7B2 /* NSString+Helpers.m */; }; + 0C938A142C416954009BA7B2 /* NSMutableData+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A132C416954009BA7B2 /* NSMutableData+Helpers.swift */; }; + 0C938A172C41698C009BA7B2 /* WPKitDateUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C938A152C41698C009BA7B2 /* WPKitDateUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0C938A182C41698C009BA7B2 /* WPKitDateUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A162C41698C009BA7B2 /* WPKitDateUtils.m */; }; + 0C938A1A2C416A8E009BA7B2 /* WordPressComLanguageDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A192C416A8E009BA7B2 /* WordPressComLanguageDatabase.swift */; }; + 0C938A1C2C416AE4009BA7B2 /* NSDate+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A1B2C416AE4009BA7B2 /* NSDate+Helpers.swift */; }; + 0C938A1E2C416AFC009BA7B2 /* Dictionary+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A1D2C416AFC009BA7B2 /* Dictionary+Helpers.swift */; }; + 0C938A212C416B41009BA7B2 /* NSBundle+VersionNumberHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C938A1F2C416B41009BA7B2 /* NSBundle+VersionNumberHelper.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0C938A222C416B41009BA7B2 /* NSBundle+VersionNumberHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A202C416B41009BA7B2 /* NSBundle+VersionNumberHelper.m */; }; + 0C938A252C416C35009BA7B2 /* WPMapFilterReduce.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A232C416C35009BA7B2 /* WPMapFilterReduce.m */; }; + 0C938A262C416C35009BA7B2 /* WPMapFilterReduce.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C938A242C416C35009BA7B2 /* WPMapFilterReduce.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0C938A282C416D0E009BA7B2 /* NSString+Summary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A272C416D0E009BA7B2 /* NSString+Summary.swift */; }; + 0C938A2B2C416DE0009BA7B2 /* DisplayableImageHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A292C416DE0009BA7B2 /* DisplayableImageHelper.m */; }; + 0C938A2C2C416DE0009BA7B2 /* DisplayableImageHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C938A2A2C416DE0009BA7B2 /* DisplayableImageHelper.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0C9CD7992B9A107E0045BE03 /* RemotePostParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9CD7982B9A107E0045BE03 /* RemotePostParameters.swift */; }; 0CB1905E2A2A5E83004D3E80 /* BlazeCampaign.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB1905D2A2A5E83004D3E80 /* BlazeCampaign.swift */; }; 0CB190612A2A6A13004D3E80 /* blaze-campaigns-search.json in Resources */ = {isa = PBXBuildFile; fileRef = 0CB1905F2A2A6943004D3E80 /* blaze-campaigns-search.json */; }; 0CB190652A2A7569004D3E80 /* BlazeCampaignsSearchResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB190642A2A7569004D3E80 /* BlazeCampaignsSearchResponse.swift */; }; + 0CCD4C5C2C41700B00B53F9A /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCD4C5B2C41700B00B53F9A /* UIDevice+Extensions.swift */; }; + 0CCD4C5F2C41711800B53F9A /* NSObject-SafeExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = 0CCD4C5E2C41711800B53F9A /* NSObject-SafeExpectations */; }; + 0CCD4C622C41712800B53F9A /* wpxmlrpc in Frameworks */ = {isa = PBXBuildFile; productRef = 0CCD4C612C41712800B53F9A /* wpxmlrpc */; }; 0CED1FE82B617CF300E6DD52 /* AtomicSiteServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CED1FE72B617CF300E6DD52 /* AtomicSiteServiceRemote.swift */; }; 0CED1FEB2B617D7D00E6DD52 /* AtomicLogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CED1FEA2B617D7D00E6DD52 /* AtomicLogs.swift */; }; 1769DEAA24729AFF00F42EFC /* HomepageSettingsServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */; }; @@ -45,7 +70,6 @@ 1DF972BF29B107E7007A72BC /* videopress-private-video.json in Resources */ = {isa = PBXBuildFile; fileRef = 1DF972BC29B107E7007A72BC /* videopress-private-video.json */; }; 1DF972C029B107E7007A72BC /* videopress-public-video.json in Resources */ = {isa = PBXBuildFile; fileRef = 1DF972BD29B107E7007A72BC /* videopress-public-video.json */; }; 1DF972C129B107E7007A72BC /* videopress-site-default-video.json in Resources */ = {isa = PBXBuildFile; fileRef = 1DF972BE29B107E7007A72BC /* videopress-site-default-video.json */; }; - 240315B0A1D6C2B981572B5B /* Pods_WordPressKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED05C8FF3E61D93CE5BA527E /* Pods_WordPressKitTests.framework */; }; 24ADA24E24F9B32D001B5DAE /* FeatureFlagSerializationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24ADA24D24F9B32D001B5DAE /* FeatureFlagSerializationTest.swift */; }; 264E09B52AD259FF004B5A5F /* WordPressComOAuthRequestChallenge.json in Resources */ = {isa = PBXBuildFile; fileRef = 264E09B42AD259FF004B5A5F /* WordPressComOAuthRequestChallenge.json */; }; 264E09B72AD25ED9004B5A5F /* WordPressComOAuthAuthenticateSignature.json in Resources */ = {isa = PBXBuildFile; fileRef = 264E09B62AD25ED9004B5A5F /* WordPressComOAuthAuthenticateSignature.json */; }; @@ -559,7 +583,6 @@ 9F3E0BAE20873836009CB5BA /* ReaderTopicServiceRemoteTest+Subscriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3E0BAD20873835009CB5BA /* ReaderTopicServiceRemoteTest+Subscriptions.swift */; }; 9F4E52002088E38200424676 /* ObjectValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4E51FF2088E38200424676 /* ObjectValidation.swift */; }; 9FCDD09720A5EF75004F0BF7 /* ReaderTopicServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FCDD09620A5EF75004F0BF7 /* ReaderTopicServiceError.swift */; }; - A0EEB8CB04BEA5F9083EBACE /* Pods_WordPressKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EFF80A6E6EE37118CB1DA158 /* Pods_WordPressKit.framework */; }; AB49D09325D1A85D0084905B /* PostServiceRemoteRESTLikesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB49D09225D1A85D0084905B /* PostServiceRemoteRESTLikesTests.swift */; }; AB49D09725D1AC0A0084905B /* post-likes-success.json in Resources */ = {isa = PBXBuildFile; fileRef = AB49D09625D1AC0A0084905B /* post-likes-success.json */; }; AB49D0B325D1B4D80084905B /* post-likes-failure.json in Resources */ = {isa = PBXBuildFile; fileRef = AB49D0B225D1B4D80084905B /* post-likes-failure.json */; }; @@ -732,7 +755,6 @@ FEFFD99326C141A800F34231 /* RemoteShareAppContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFFD99226C141A800F34231 /* RemoteShareAppContent.swift */; }; FEFFD99726C158F400F34231 /* share-app-content-success.json in Resources */ = {isa = PBXBuildFile; fileRef = FEFFD99626C158F400F34231 /* share-app-content-success.json */; }; FEFFD99B26C1598F00F34231 /* ShareAppContentServiceRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFFD99A26C1598F00F34231 /* ShareAppContentServiceRemoteTests.swift */; }; - FF20AD2220B8471A00082398 /* WordPressKit.podspec in Resources */ = {isa = PBXBuildFile; fileRef = FF20AD2120B8471A00082398 /* WordPressKit.podspec */; }; FFA4D4AA2423B10A00BF5180 /* WordPressOrgRestApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA4D4A82423B10A00BF5180 /* WordPressOrgRestApiTests.swift */; }; FFA4D4AD2423B1FE00BF5180 /* wp-admin-post-new.html in Resources */ = {isa = PBXBuildFile; fileRef = FFA4D4AC2423B1FE00BF5180 /* wp-admin-post-new.html */; }; FFA4D4B02423B33800BF5180 /* wp-forbidden.json in Resources */ = {isa = PBXBuildFile; fileRef = FFA4D4AE2423B33800BF5180 /* wp-forbidden.json */; }; @@ -776,11 +798,32 @@ 0C1C08422B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostServiceRemoteREST+Extended.swift"; sourceTree = ""; }; 0C1C08442B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostServiceRemoteXMLRPC+Extended.swift"; sourceTree = ""; }; 0C3A2A412A2E7BA500FD91D6 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + 0C6183C62C420A3700289E73 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackAIServiceRemote.swift; sourceTree = ""; }; + 0C938A052C416789009BA7B2 /* Secret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secret.swift; sourceTree = ""; }; + 0C938A072C4167BB009BA7B2 /* NSString+XMLExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+XMLExtensions.h"; sourceTree = ""; }; + 0C938A082C4167BB009BA7B2 /* NSString+XMLExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+XMLExtensions.m"; sourceTree = ""; }; + 0C938A0B2C416850009BA7B2 /* String+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Helpers.swift"; sourceTree = ""; }; + 0C938A0D2C416876009BA7B2 /* NSString+Helpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+Helpers.m"; sourceTree = ""; }; + 0C938A0F2C416883009BA7B2 /* NSString+Helpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+Helpers.h"; sourceTree = ""; }; + 0C938A132C416954009BA7B2 /* NSMutableData+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableData+Helpers.swift"; sourceTree = ""; }; + 0C938A152C41698C009BA7B2 /* WPKitDateUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WPKitDateUtils.h; sourceTree = ""; }; + 0C938A162C41698C009BA7B2 /* WPKitDateUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WPKitDateUtils.m; sourceTree = ""; }; + 0C938A192C416A8E009BA7B2 /* WordPressComLanguageDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressComLanguageDatabase.swift; sourceTree = ""; }; + 0C938A1B2C416AE4009BA7B2 /* NSDate+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+Helpers.swift"; sourceTree = ""; }; + 0C938A1D2C416AFC009BA7B2 /* Dictionary+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Helpers.swift"; sourceTree = ""; }; + 0C938A1F2C416B41009BA7B2 /* NSBundle+VersionNumberHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSBundle+VersionNumberHelper.h"; sourceTree = ""; }; + 0C938A202C416B41009BA7B2 /* NSBundle+VersionNumberHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+VersionNumberHelper.m"; sourceTree = ""; }; + 0C938A232C416C35009BA7B2 /* WPMapFilterReduce.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WPMapFilterReduce.m; sourceTree = ""; }; + 0C938A242C416C35009BA7B2 /* WPMapFilterReduce.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WPMapFilterReduce.h; sourceTree = ""; }; + 0C938A272C416D0E009BA7B2 /* NSString+Summary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSString+Summary.swift"; sourceTree = ""; }; + 0C938A292C416DE0009BA7B2 /* DisplayableImageHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DisplayableImageHelper.m; sourceTree = ""; }; + 0C938A2A2C416DE0009BA7B2 /* DisplayableImageHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DisplayableImageHelper.h; sourceTree = ""; }; 0C9CD7982B9A107E0045BE03 /* RemotePostParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemotePostParameters.swift; sourceTree = ""; }; 0CB1905D2A2A5E83004D3E80 /* BlazeCampaign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaign.swift; sourceTree = ""; }; 0CB1905F2A2A6943004D3E80 /* blaze-campaigns-search.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blaze-campaigns-search.json"; sourceTree = ""; }; 0CB190642A2A7569004D3E80 /* BlazeCampaignsSearchResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignsSearchResponse.swift; sourceTree = ""; }; + 0CCD4C5B2C41700B00B53F9A /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = ""; }; 0CED1FE72B617CF300E6DD52 /* AtomicSiteServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicSiteServiceRemote.swift; sourceTree = ""; }; 0CED1FEA2B617D7D00E6DD52 /* AtomicLogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicLogs.swift; sourceTree = ""; }; 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageSettingsServiceRemote.swift; sourceTree = ""; }; @@ -805,7 +848,6 @@ 264E09B42AD259FF004B5A5F /* WordPressComOAuthRequestChallenge.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = WordPressComOAuthRequestChallenge.json; sourceTree = ""; }; 264E09B62AD25ED9004B5A5F /* WordPressComOAuthAuthenticateSignature.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = WordPressComOAuthAuthenticateSignature.json; sourceTree = ""; }; 264E09B82AD2709A004B5A5F /* WordPressComOAuthNeedsWebauthnMFA.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = WordPressComOAuthNeedsWebauthnMFA.json; sourceTree = ""; }; - 264F5C834541BBF2018F4964 /* Pods-WordPressKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressKitTests/Pods-WordPressKitTests.debug.xcconfig"; sourceTree = ""; }; 3236F77724AE34B40088E8F3 /* ReaderTopicServiceRemote+Interests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReaderTopicServiceRemote+Interests.swift"; sourceTree = ""; }; 3236F79924AE406D0088E8F3 /* ReaderTopicServiceRemote+InterestsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReaderTopicServiceRemote+InterestsTests.swift"; sourceTree = ""; }; 3236F79B24AE413A0088E8F3 /* reader-interests-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reader-interests-success.json"; sourceTree = ""; }; @@ -969,8 +1011,6 @@ 4AE278492B2FC6C600E4D9B1 /* HTTPHeaderValueParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPHeaderValueParserTests.swift; sourceTree = ""; }; 4AE7E36A2B9A995500C8CED5 /* AnnouncementServiceRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementServiceRemoteTests.swift; sourceTree = ""; }; 4AE7E36C2B9A9BC400C8CED5 /* site-zendesk-metadata-success.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-zendesk-metadata-success.json"; sourceTree = ""; }; - 6C2A33D76FD1052D6F30466D /* Pods-WordPressKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressKit/Pods-WordPressKit.debug.xcconfig"; sourceTree = ""; }; - 6F2E0CC4FA01B5475A378DA2 /* Pods-WordPressKitTests.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressKitTests.release-alpha.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressKitTests/Pods-WordPressKitTests.release-alpha.xcconfig"; sourceTree = ""; }; 730E869E21E44EFD00753E1A /* WordPressComServiceRemote+SiteVerticals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComServiceRemote+SiteVerticals.swift"; sourceTree = ""; }; 731BA83521DECD61000FDFCD /* SiteCreationRequestEncodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteCreationRequestEncodingTests.swift; sourceTree = ""; }; 731BA83721DECD97000FDFCD /* SiteCreationResponseDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteCreationResponseDecodingTests.swift; sourceTree = ""; }; @@ -1344,7 +1384,6 @@ B5A4822A20AC6C0B009D95F6 /* WPKitLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPKitLogging.swift; sourceTree = ""; }; B5A4822C20AC6C19009D95F6 /* WPKitLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WPKitLogging.m; sourceTree = ""; }; B5A4822D20AC6C1A009D95F6 /* WPKitLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WPKitLogging.h; sourceTree = ""; }; - B76472D20711B6BE2ACDC332 /* Pods-WordPressKitTests.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressKitTests.release-internal.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressKitTests/Pods-WordPressKitTests.release-internal.xcconfig"; sourceTree = ""; }; BA0637EC2492382200AF8419 /* PluginStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginStateTests.swift; sourceTree = ""; }; BA2A78F924A486D300BB6F53 /* SitePluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitePluginTests.swift; sourceTree = ""; }; BA3F138D24A09C87006367A3 /* plugin-install-generic-error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "plugin-install-generic-error.json"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.json; }; @@ -1365,8 +1404,6 @@ BA9A7F7E24C6895600925E81 /* plugin-directory-jetpack-beta.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "plugin-directory-jetpack-beta.json"; sourceTree = ""; }; BAB0E36324AD599700B3D22C /* MockPluginStateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPluginStateProvider.swift; sourceTree = ""; }; BAFA775524ADAB3C000F0D3A /* MockPluginDirectoryEntryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPluginDirectoryEntryProvider.swift; sourceTree = ""; }; - BEEC8B5D92DA614468900BD7 /* Pods-WordPressKit.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressKit.release-alpha.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressKit/Pods-WordPressKit.release-alpha.xcconfig"; sourceTree = ""; }; - C5953994B3865AF409BA4210 /* Pods-WordPressKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressKitTests/Pods-WordPressKitTests.release.xcconfig"; sourceTree = ""; }; C738CAEE28622325001BE107 /* QRLoginServiceRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRLoginServiceRemoteTests.swift; sourceTree = ""; }; C738CAF0286224ED001BE107 /* qrlogin-validate-200.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "qrlogin-validate-200.json"; sourceTree = ""; }; C738CAF2286226D6001BE107 /* qrlogin-validate-400.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "qrlogin-validate-400.json"; sourceTree = ""; }; @@ -1384,7 +1421,6 @@ C92EFF6C25E741E900E0308D /* common-starter-site-designs-malformed.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "common-starter-site-designs-malformed.json"; sourceTree = ""; }; C92EFF7225E7444400E0308D /* common-starter-site-designs-empty-designs.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "common-starter-site-designs-empty-designs.json"; sourceTree = ""; }; C9F991B827D5A52600135131 /* domain-service-invalid-query.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "domain-service-invalid-query.json"; sourceTree = ""; }; - CA5ABD95F40077D001644BCC /* Pods-WordPressKit.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressKit.release-internal.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressKit/Pods-WordPressKit.release-internal.xcconfig"; sourceTree = ""; }; CEAD827925E421DE00758DF2 /* reader-post-comments-subscribe-failure.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reader-post-comments-subscribe-failure.json"; sourceTree = ""; }; D813437521F6D70D0060D99A /* SiteSegmentsResponseDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteSegmentsResponseDecodingTests.swift; sourceTree = ""; }; D813437721F6D7DC0060D99A /* site-segments-single.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-segments-single.json"; sourceTree = ""; }; @@ -1407,7 +1443,6 @@ E1E89C671FD6B2E9006E7A33 /* plugin-directory-jetpack.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "plugin-directory-jetpack.json"; sourceTree = ""; }; E1E89C691FD6BDB1006E7A33 /* PluginDirectoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDirectoryTests.swift; sourceTree = ""; }; E1EF5D5C1F9F329900B6D53E /* SitePluginCapabilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitePluginCapabilities.swift; sourceTree = ""; }; - E33F1EA3284E0454909D1967 /* Pods-WordPressKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressKit/Pods-WordPressKit.release.xcconfig"; sourceTree = ""; }; E61A51A521B172A900A5F902 /* RemoteWpcomPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteWpcomPlan.swift; sourceTree = ""; }; E670CD712277A85000E75735 /* plans-me-sites-success.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "plans-me-sites-success.json"; sourceTree = ""; }; E689431D21B0A1A800C5E4A7 /* plans-mobile-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "plans-mobile-success.json"; sourceTree = ""; }; @@ -1419,8 +1454,6 @@ E6C1E8471EF21FC100D139D9 /* is-passwordless-account-no-account-found.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "is-passwordless-account-no-account-found.json"; sourceTree = ""; }; E6C1E8481EF21FC100D139D9 /* is-passwordless-account-success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "is-passwordless-account-success.json"; sourceTree = ""; }; E6D0EE611F7EF9CE0064D3FC /* AccountServiceRemoteREST+SocialService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountServiceRemoteREST+SocialService.swift"; sourceTree = ""; }; - ED05C8FF3E61D93CE5BA527E /* Pods_WordPressKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WordPressKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EFF80A6E6EE37118CB1DA158 /* Pods_WordPressKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WordPressKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F181EA0127184D3C00F26141 /* ProductServiceRemote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductServiceRemote.swift; sourceTree = ""; }; F194E1222417ED9F00874408 /* AtomicAuthenticationServiceRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicAuthenticationServiceRemoteTests.swift; sourceTree = ""; }; F194E1242417EE7E00874408 /* atomic-get-auth-cookie-success.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "atomic-get-auth-cookie-success.json"; sourceTree = ""; }; @@ -1497,7 +1530,6 @@ FEFFD99226C141A800F34231 /* RemoteShareAppContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteShareAppContent.swift; sourceTree = ""; }; FEFFD99626C158F400F34231 /* share-app-content-success.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "share-app-content-success.json"; sourceTree = ""; }; FEFFD99A26C1598F00F34231 /* ShareAppContentServiceRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppContentServiceRemoteTests.swift; sourceTree = ""; }; - FF20AD2120B8471A00082398 /* WordPressKit.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = WordPressKit.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; FFA4D4A82423B10A00BF5180 /* WordPressOrgRestApiTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressOrgRestApiTests.swift; sourceTree = ""; }; FFA4D4AC2423B1FE00BF5180 /* wp-admin-post-new.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "wp-admin-post-new.html"; sourceTree = ""; }; FFA4D4AE2423B33800BF5180 /* wp-forbidden.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "wp-forbidden.json"; sourceTree = ""; }; @@ -1522,7 +1554,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A0EEB8CB04BEA5F9083EBACE /* Pods_WordPressKit.framework in Frameworks */, + 0CCD4C5F2C41711800B53F9A /* NSObject-SafeExpectations in Frameworks */, + 0CCD4C622C41712800B53F9A /* wpxmlrpc in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1530,9 +1563,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0C363D452C41B468004E241D /* OHHTTPStubs in Frameworks */, + 0C363D472C41B468004E241D /* OHHTTPStubsSwift in Frameworks */, 3FB8642C2888089F003A86BE /* BuildkiteTestCollector in Frameworks */, 9368C7851EC5EF1B0092CE8E /* WordPressKit.framework in Frameworks */, - 240315B0A1D6C2B981572B5B /* Pods_WordPressKitTests.framework in Frameworks */, + 0C363D422C41B455004E241D /* OCMock in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1570,6 +1605,32 @@ path = Assistant; sourceTree = ""; }; + 0C938A042C4166AC009BA7B2 /* WordPressShared */ = { + isa = PBXGroup; + children = ( + 0C938A052C416789009BA7B2 /* Secret.swift */, + 0C938A072C4167BB009BA7B2 /* NSString+XMLExtensions.h */, + 0C938A082C4167BB009BA7B2 /* NSString+XMLExtensions.m */, + 0C938A272C416D0E009BA7B2 /* NSString+Summary.swift */, + 0C938A2A2C416DE0009BA7B2 /* DisplayableImageHelper.h */, + 0C938A292C416DE0009BA7B2 /* DisplayableImageHelper.m */, + 0C938A0B2C416850009BA7B2 /* String+Helpers.swift */, + 0C938A0F2C416883009BA7B2 /* NSString+Helpers.h */, + 0C938A0D2C416876009BA7B2 /* NSString+Helpers.m */, + 0C938A132C416954009BA7B2 /* NSMutableData+Helpers.swift */, + 0C938A152C41698C009BA7B2 /* WPKitDateUtils.h */, + 0C938A162C41698C009BA7B2 /* WPKitDateUtils.m */, + 0C938A192C416A8E009BA7B2 /* WordPressComLanguageDatabase.swift */, + 0C938A1B2C416AE4009BA7B2 /* NSDate+Helpers.swift */, + 0C938A1D2C416AFC009BA7B2 /* Dictionary+Helpers.swift */, + 0C938A1F2C416B41009BA7B2 /* NSBundle+VersionNumberHelper.h */, + 0C938A202C416B41009BA7B2 /* NSBundle+VersionNumberHelper.m */, + 0C938A242C416C35009BA7B2 /* WPMapFilterReduce.h */, + 0C938A232C416C35009BA7B2 /* WPMapFilterReduce.m */, + ); + path = WordPressShared; + sourceTree = ""; + }; 3297E1DC2564649D00287D21 /* Scan */ = { isa = PBXGroup; children = ( @@ -1590,15 +1651,6 @@ path = "Jetpack Scan"; sourceTree = ""; }; - 38C6ABE94A27A12C9C4AD19D /* Frameworks */ = { - isa = PBXGroup; - children = ( - EFF80A6E6EE37118CB1DA158 /* Pods_WordPressKit.framework */, - ED05C8FF3E61D93CE5BA527E /* Pods_WordPressKitTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 3F3195AB266FF91100397EE7 /* Plans */ = { isa = PBXGroup; children = ( @@ -1973,6 +2025,7 @@ 465F88A1263B325C00F4C950 /* ChecksumUtil.swift */, 3F3195AC266FF94B00397EE7 /* ZendeskMetadata.swift */, 4AE278432B2FAF6200E4D9B1 /* HTTPProtocolHelpers.swift */, + 0CCD4C5B2C41700B00B53F9A /* UIDevice+Extensions.swift */, ); path = Utility; sourceTree = ""; @@ -2091,6 +2144,7 @@ 3FE2E9762BC395C2002CA2E1 /* CoreAPI */, 3FE2E9532BB3F4ED002CA2E1 /* APIInterface */, 3FE2E9462BB12020002CA2E1 /* WordPressKit */, + 0C938A042C4166AC009BA7B2 /* WordPressShared */, ); path = Sources; sourceTree = ""; @@ -2264,15 +2318,13 @@ 9368C7711EC5EF1B0092CE8E = { isa = PBXGroup; children = ( + 0C6183C62C420A3700289E73 /* Package.swift */, 3FE2E9432BB11413002CA2E1 /* Sources */, 3FE2E9442BB11592002CA2E1 /* Tests */, FFE247CD20CB1245002DF3A2 /* LICENSE */, FFE247CC20CB118A002DF3A2 /* README.md */, 0C3A2A412A2E7BA500FD91D6 /* CHANGELOG.md */, - FF20AD2120B8471A00082398 /* WordPressKit.podspec */, 9368C77C1EC5EF1B0092CE8E /* Products */, - 38C6ABE94A27A12C9C4AD19D /* Frameworks */, - E5EA953F7DD505CCED2E44CD /* Pods */, ); sourceTree = ""; }; @@ -2614,21 +2666,6 @@ path = "QR Login"; sourceTree = ""; }; - E5EA953F7DD505CCED2E44CD /* Pods */ = { - isa = PBXGroup; - children = ( - 6C2A33D76FD1052D6F30466D /* Pods-WordPressKit.debug.xcconfig */, - E33F1EA3284E0454909D1967 /* Pods-WordPressKit.release.xcconfig */, - BEEC8B5D92DA614468900BD7 /* Pods-WordPressKit.release-alpha.xcconfig */, - CA5ABD95F40077D001644BCC /* Pods-WordPressKit.release-internal.xcconfig */, - 264F5C834541BBF2018F4964 /* Pods-WordPressKitTests.debug.xcconfig */, - C5953994B3865AF409BA4210 /* Pods-WordPressKitTests.release.xcconfig */, - 6F2E0CC4FA01B5475A378DA2 /* Pods-WordPressKitTests.release-alpha.xcconfig */, - B76472D20711B6BE2ACDC332 /* Pods-WordPressKitTests.release-internal.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; F3FF8A1A279C86AF00E5C90F /* Models */ = { isa = PBXGroup; children = ( @@ -2729,10 +2766,12 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 0C938A212C416B41009BA7B2 /* NSBundle+VersionNumberHelper.h in Headers */, 9368C78C1EC5EF1B0092CE8E /* WordPressKit.h in Headers */, 93C674F11EE8351E00BFAF05 /* NSMutableDictionary+Helpers.h in Headers */, 93BD273C1EE73282002BB00B /* AccountServiceRemoteREST.h in Headers */, 3FD635042BC3F05400CEDF5E /* WordPressComRESTAPIVersionedPathBuilder.h in Headers */, + 0C938A172C41698C009BA7B2 /* WPKitDateUtils.h in Headers */, 93BD27711EE737A8002BB00B /* ServiceRemoteWordPressXMLRPC.h in Headers */, 3FA4258F2BCCFDA6007539BF /* WordPressComRestApiErrorDomain.h in Headers */, 3FE2E9672BBEB8D2002CA2E1 /* WordPressComRESTAPIVersion.h in Headers */, @@ -2763,6 +2802,7 @@ 740B23B71F17EC7300067A2A /* PostServiceRemote.h in Headers */, C7971977267901D70072F984 /* SelfHostedPluginManagementClient.swift in Headers */, 740B23B81F17EC7300067A2A /* PostServiceRemoteREST.h in Headers */, + 0C938A092C4167BC009BA7B2 /* NSString+XMLExtensions.h in Headers */, C7971974267901D30072F984 /* JetpackPluginManagementClient.swift in Headers */, 74BA04F31F06DC0A00ED5CD8 /* CommentServiceRemoteREST.h in Headers */, C7971971267901D20072F984 /* PluginManagementClient.swift in Headers */, @@ -2770,6 +2810,7 @@ 9311A6851F22625A00704AC9 /* RemoteTaxonomyPaging.h in Headers */, 9311A68A1F22625A00704AC9 /* TaxonomyServiceRemoteXMLRPC.h in Headers */, 9311A6881F22625A00704AC9 /* TaxonomyServiceRemoteREST.h in Headers */, + 0C938A112C4168FB009BA7B2 /* NSString+Helpers.h in Headers */, 93188D1E1F2262BF0028ED4D /* RemotePostTag.h in Headers */, 9311A6871F22625A00704AC9 /* TaxonomyServiceRemote.h in Headers */, 9309994D1F1657C600F006A1 /* ThemeServiceRemote.h in Headers */, @@ -2777,8 +2818,10 @@ 740B23C41F17EE8000067A2A /* RemotePost.h in Headers */, 740B23C21F17EE8000067A2A /* RemotePostCategory.h in Headers */, B5A4822F20AC6C1A009D95F6 /* WPKitLogging.h in Headers */, + 0C938A262C416C35009BA7B2 /* WPMapFilterReduce.h in Headers */, 9309995B1F16616A00F006A1 /* RemoteTheme.h in Headers */, 1A4F98672279A87D00D86E8E /* WPKit-Swift.h in Headers */, + 0C938A2C2C416DE0009BA7B2 /* DisplayableImageHelper.h in Headers */, 93F50A371F226B9300B5BEBA /* WordPressComServiceRemote.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2790,18 +2833,21 @@ isa = PBXNativeTarget; buildConfigurationList = 9368C78F1EC5EF1B0092CE8E /* Build configuration list for PBXNativeTarget "WordPressKit" */; buildPhases = ( - 50C31E8D1B97D5FA0D543935 /* [CP] Check Pods Manifest.lock */, 9368C7781EC5EF1B0092CE8E /* Headers */, 9368C7761EC5EF1B0092CE8E /* Sources */, 9368C7771EC5EF1B0092CE8E /* Frameworks */, 9368C7791EC5EF1B0092CE8E /* Resources */, - 3F391E242B577AD7007975C4 /* SwiftLint */, + 0C2F2A2C2C41F82B000A153E /* SwiftLint */, ); buildRules = ( ); dependencies = ( ); name = WordPressKit; + packageProductDependencies = ( + 0CCD4C5E2C41711800B53F9A /* NSObject-SafeExpectations */, + 0CCD4C612C41712800B53F9A /* wpxmlrpc */, + ); productName = WordPressKit; productReference = 9368C77B1EC5EF1B0092CE8E /* WordPressKit.framework */; productType = "com.apple.product-type.framework"; @@ -2810,11 +2856,9 @@ isa = PBXNativeTarget; buildConfigurationList = 9368C7921EC5EF1B0092CE8E /* Build configuration list for PBXNativeTarget "WordPressKitTests" */; buildPhases = ( - 07D73601D3D9744A82A5C64A /* [CP] Check Pods Manifest.lock */, 9368C7801EC5EF1B0092CE8E /* Sources */, 9368C7811EC5EF1B0092CE8E /* Frameworks */, 9368C7821EC5EF1B0092CE8E /* Resources */, - B07A9DD36A28DB40846D1682 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -2824,6 +2868,9 @@ name = WordPressKitTests; packageProductDependencies = ( 3FB8642B2888089F003A86BE /* BuildkiteTestCollector */, + 0C363D412C41B455004E241D /* OCMock */, + 0C363D442C41B468004E241D /* OHHTTPStubs */, + 0C363D462C41B468004E241D /* OHHTTPStubsSwift */, ); productName = WordPressKitTests; productReference = 9368C7841EC5EF1B0092CE8E /* WordPressKitTests.xctest */; @@ -2862,6 +2909,10 @@ mainGroup = 9368C7711EC5EF1B0092CE8E; packageReferences = ( 3FB8642A2888089F003A86BE /* XCRemoteSwiftPackageReference "test-collector-swift" */, + 0CCD4C5D2C41711800B53F9A /* XCRemoteSwiftPackageReference "NSObject-SafeExpectations" */, + 0CCD4C602C41712800B53F9A /* XCRemoteSwiftPackageReference "wpxmlrpc" */, + 0C363D402C41B455004E241D /* XCRemoteSwiftPackageReference "ocmock" */, + 0C363D432C41B468004E241D /* XCRemoteSwiftPackageReference "OHHTTPStubs" */, ); productRefGroup = 9368C77C1EC5EF1B0092CE8E /* Products */; projectDirPath = ""; @@ -2879,7 +2930,6 @@ buildActionMask = 2147483647; files = ( FFE247CE20CB1245002DF3A2 /* LICENSE in Resources */, - FF20AD2220B8471A00082398 /* WordPressKit.podspec in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3210,25 +3260,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 07D73601D3D9744A82A5C64A /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-WordPressKitTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 3F391E242B577AD7007975C4 /* SwiftLint */ = { + 0C2F2A2C2C41F82B000A153E /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -3247,54 +3279,6 @@ shellPath = /bin/sh; shellScript = "./Pods/SwiftLint/swiftlint lint\n"; }; - 50C31E8D1B97D5FA0D543935 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-WordPressKit-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - B07A9DD36A28DB40846D1682 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-WordPressKitTests/Pods-WordPressKitTests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/NSObject-SafeExpectations/NSObject_SafeExpectations.framework", - "${BUILT_PRODUCTS_DIR}/UIDeviceIdentifier/UIDeviceIdentifier.framework", - "${BUILT_PRODUCTS_DIR}/WordPressShared/WordPressShared.framework", - "${BUILT_PRODUCTS_DIR}/wpxmlrpc/wpxmlrpc.framework", - "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", - "${BUILT_PRODUCTS_DIR}/OCMock/OCMock.framework", - "${BUILT_PRODUCTS_DIR}/OHHTTPStubs/OHHTTPStubs.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NSObject_SafeExpectations.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UIDeviceIdentifier.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WordPressShared.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wpxmlrpc.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OHHTTPStubs.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-WordPressKitTests/Pods-WordPressKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -3333,6 +3317,7 @@ 436D56332118D7AA00CEAA33 /* TransactionsServiceRemote.swift in Sources */, 93BD27721EE737A9002BB00B /* ServiceRemoteWordPressXMLRPC.m in Sources */, 4A05E79A2B2FDC3200C25E3B /* WordPressOrgRestApi.swift in Sources */, + 0C938A1E2C416AFC009BA7B2 /* Dictionary+Helpers.swift in Sources */, C76F456825B9F30E00BFEC87 /* JetpackScanHistory.swift in Sources */, E1D6B556200E46F300325669 /* WPTimeZone.swift in Sources */, 93F50A3F1F227C8900B5BEBA /* UsersServiceRemoteXMLRPC.swift in Sources */, @@ -3341,6 +3326,7 @@ 9F3E0B9E208733C3009CB5BA /* ReaderServiceDeliveryFrequency.swift in Sources */, 74E2295E1F1E777B0085F7F2 /* RemoteSharingButton.swift in Sources */, 4A05E7962B2FCB6400C25E3B /* NonceRetrieval.swift in Sources */, + 0C938A222C416B41009BA7B2 /* NSBundle+VersionNumberHelper.m in Sources */, 93BD27701EE737A8002BB00B /* ServiceRemoteWordPressComREST.m in Sources */, E61A51A621B172A900A5F902 /* RemoteWpcomPlan.swift in Sources */, 3FD634F62BC3AD6200CEDF5E /* AppTransportSecuritySettings.swift in Sources */, @@ -3363,6 +3349,7 @@ 9AB6D647218705E90008F274 /* RemoteDiff.swift in Sources */, 93BD277C1EE73944002BB00B /* HTTPAuthenticationAlertController.swift in Sources */, 7433BC011EFC4505002D9E92 /* PlanServiceRemote.swift in Sources */, + 0C938A062C416789009BA7B2 /* Secret.swift in Sources */, 4041405E220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift in Sources */, 74650F721F0EA1A700188EDB /* GravatarServiceRemote.swift in Sources */, B5969E1D20A49AC4005E9DF1 /* NSString+MD5.m in Sources */, @@ -3381,6 +3368,7 @@ 4AE278442B2FAF6200E4D9B1 /* HTTPProtocolHelpers.swift in Sources */, FA68CD152993C6CD00FA4C29 /* BlazeServiceRemote.swift in Sources */, 4081976F221DDE9B00A298E4 /* StatsTopPostsTimeIntervalData.swift in Sources */, + 0C938A122C4168FB009BA7B2 /* NSString+Helpers.m in Sources */, 9311A68B1F22625A00704AC9 /* TaxonomyServiceRemoteXMLRPC.m in Sources */, C797196E2679007B0072F984 /* SelfHostedPluginManagementClient.swift in Sources */, 40414060220F9F1F00CF7C5B /* StatsAllTimesInsight.swift in Sources */, @@ -3392,12 +3380,14 @@ F4B0F4732ACAF498003ABC61 /* DomainsServiceRemote+AllDomains.swift in Sources */, E13EE1471F33258E00C15787 /* PluginServiceRemote.swift in Sources */, 93BD276A1EE736A8002BB00B /* RemoteUser.m in Sources */, + 0C938A1C2C416AE4009BA7B2 /* NSDate+Helpers.swift in Sources */, 742362D71F10250600BD0A7F /* MenusServiceRemote.m in Sources */, E6D0EE621F7EF9CE0064D3FC /* AccountServiceRemoteREST+SocialService.swift in Sources */, 8BB5F62127A99A2000B2FFAF /* DashboardServiceRemote.swift in Sources */, 74DA56351F06EAF000FE9BF4 /* MediaServiceRemoteXMLRPC.m in Sources */, C797196C2679007B0072F984 /* PluginManagementClient.swift in Sources */, C785325625B5F46C006CEAFB /* JetpackThreatFixStatus.swift in Sources */, + 0C938A252C416C35009BA7B2 /* WPMapFilterReduce.m in Sources */, 0CB190652A2A7569004D3E80 /* BlazeCampaignsSearchResponse.swift in Sources */, 93F50A381F226B9300B5BEBA /* WordPressComServiceRemote.m in Sources */, 0CED1FEB2B617D7D00E6DD52 /* AtomicLogs.swift in Sources */, @@ -3423,6 +3413,7 @@ 7328420421CD786C00126755 /* WordPressComServiceRemote+SiteCreation.swift in Sources */, 32FC1D28255C91ED00CD0A7B /* JetpackScan.swift in Sources */, FE5096612A2F852E00DDD071 /* RemotePublicizeInfo.swift in Sources */, + 0C938A0C2C416850009BA7B2 /* String+Helpers.swift in Sources */, 826016F31F9FA17B00533B6C /* Activity.swift in Sources */, 4A68E3D329406AA0004AC3DC /* RemoteMenu.swift in Sources */, 40819783221F5C8200A298E4 /* StatsPostDetails.swift in Sources */, @@ -3447,6 +3438,7 @@ 93C674F21EE8351E00BFAF05 /* NSMutableDictionary+Helpers.m in Sources */, 4624222D2548BA0F002B8A12 /* RemoteSiteDesign.swift in Sources */, 74D67F061F1528470010C5ED /* PeopleServiceRemote.swift in Sources */, + 0C938A2B2C416DE0009BA7B2 /* DisplayableImageHelper.m in Sources */, 98DC787522BAEBF200267279 /* StatsAllAnnualInsight.swift in Sources */, 740B23C31F17EE8000067A2A /* RemotePostCategory.m in Sources */, 8B2F4BF124ACE3C30056C08A /* RemoteReaderInterest.swift in Sources */, @@ -3469,6 +3461,7 @@ 404057CE221C38130060250C /* StatsTopVideosTimeIntervalData.swift in Sources */, 7E0D64FF22D855700092AD10 /* EditorServiceRemote.swift in Sources */, 0C1C08412B9CD79900E52F8C /* PostServiceRemoteExtended.swift in Sources */, + 0C938A1A2C416A8E009BA7B2 /* WordPressComLanguageDatabase.swift in Sources */, 9AF4F2FF2183346B00570E4B /* RemoteRevision.swift in Sources */, FE6C673A2BB739950083ECAB /* Decodable+Dictionary.swift in Sources */, 17D936252475D8AB008B2205 /* RemoteHomepageType.swift in Sources */, @@ -3478,6 +3471,7 @@ 0C1C08432B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift in Sources */, 3FFCC0492BAB98130051D229 /* DateFormatter+WordPressCom.swift in Sources */, 74A44DD01F13C64B006CD8F4 /* RemoteNotification.swift in Sources */, + 0C938A182C41698C009BA7B2 /* WPKitDateUtils.m in Sources */, E1D6B558200E473A00325669 /* TimeZoneServiceRemote.swift in Sources */, 1769DEAA24729AFF00F42EFC /* HomepageSettingsServiceRemote.swift in Sources */, 93BD273D1EE73282002BB00B /* AccountServiceRemoteREST.m in Sources */, @@ -3488,6 +3482,7 @@ 3FE2E94F2BB29A1B002CA2E1 /* FilePart.m in Sources */, F41D98EA2B48602B004EC050 /* SessionDetails.swift in Sources */, 436D563C2118E18D00CEAA33 /* WPState.swift in Sources */, + 0C938A142C416954009BA7B2 /* NSMutableData+Helpers.swift in Sources */, 439A44DA2107C93000795ED7 /* RemotePlan_ApiVersion1_3.swift in Sources */, 93BD27811EE73944002BB00B /* WordPressOrgXMLRPCApi.swift in Sources */, 439A44D62107C66A00795ED7 /* JSONDecoderExtension.swift in Sources */, @@ -3503,7 +3498,10 @@ 74A44DCD1F13C533006CD8F4 /* PushAuthenticationServiceRemote.swift in Sources */, 0CB1905E2A2A5E83004D3E80 /* BlazeCampaign.swift in Sources */, 74B5F0D81EF8299B00B411E7 /* BlogServiceRemoteREST.m in Sources */, + 0C938A282C416D0E009BA7B2 /* NSString+Summary.swift in Sources */, + 0C938A0A2C4167BC009BA7B2 /* NSString+XMLExtensions.m in Sources */, 9FCDD09720A5EF75004F0BF7 /* ReaderTopicServiceError.swift in Sources */, + 0CCD4C5C2C41700B00B53F9A /* UIDevice+Extensions.swift in Sources */, 74A44DD11F13C64B006CD8F4 /* RemoteNotificationSettings.swift in Sources */, FEF7419D28085D89002C4203 /* RemoteBloggingPrompt.swift in Sources */, 74DA56331F06EAF000FE9BF4 /* MediaServiceRemoteREST.m in Sources */, @@ -3817,7 +3815,6 @@ }; 9368C7901EC5EF1B0092CE8E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6C2A33D76FD1052D6F30466D /* Pods-WordPressKit.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}"; APPLICATION_EXTENSION_API_ONLY = NO; @@ -3850,7 +3847,6 @@ }; 9368C7911EC5EF1B0092CE8E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E33F1EA3284E0454909D1967 /* Pods-WordPressKit.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}"; APPLICATION_EXTENSION_API_ONLY = NO; @@ -3882,7 +3878,6 @@ }; 9368C7931EC5EF1B0092CE8E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 264F5C834541BBF2018F4964 /* Pods-WordPressKitTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}"; CLANG_ENABLE_MODULES = YES; @@ -3907,7 +3902,6 @@ }; 9368C7941EC5EF1B0092CE8E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C5953994B3865AF409BA4210 /* Pods-WordPressKitTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}"; CLANG_ENABLE_MODULES = YES; @@ -3991,7 +3985,6 @@ }; 93D436181EC638A100626832 /* Release-Internal */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CA5ABD95F40077D001644BCC /* Pods-WordPressKit.release-internal.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}"; APPLICATION_EXTENSION_API_ONLY = NO; @@ -4023,7 +4016,6 @@ }; 93D436191EC638A100626832 /* Release-Internal */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B76472D20711B6BE2ACDC332 /* Pods-WordPressKitTests.release-internal.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}"; CLANG_ENABLE_MODULES = YES; @@ -4107,7 +4099,6 @@ }; 93D4361B1EC638A800626832 /* Release-Alpha */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BEEC8B5D92DA614468900BD7 /* Pods-WordPressKit.release-alpha.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}"; APPLICATION_EXTENSION_API_ONLY = NO; @@ -4139,7 +4130,6 @@ }; 93D4361C1EC638A800626832 /* Release-Alpha */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6F2E0CC4FA01B5475A378DA2 /* Pods-WordPressKitTests.release-alpha.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}"; CLANG_ENABLE_MODULES = YES; @@ -4200,6 +4190,38 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 0C363D402C41B455004E241D /* XCRemoteSwiftPackageReference "ocmock" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/erikdoe/ocmock"; + requirement = { + kind = revision; + revision = 2c0bfd373289f4a7716db5d6db471640f91a6507; + }; + }; + 0C363D432C41B468004E241D /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/AliSoftware/OHHTTPStubs"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 9.1.0; + }; + }; + 0CCD4C5D2C41711800B53F9A /* XCRemoteSwiftPackageReference "NSObject-SafeExpectations" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/wordpress-mobile/NSObject-SafeExpectations"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.0.6; + }; + }; + 0CCD4C602C41712800B53F9A /* XCRemoteSwiftPackageReference "wpxmlrpc" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/wordpress-mobile/wpxmlrpc"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.10.0; + }; + }; 3FB8642A2888089F003A86BE /* XCRemoteSwiftPackageReference "test-collector-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/buildkite/test-collector-swift"; @@ -4211,6 +4233,31 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 0C363D412C41B455004E241D /* OCMock */ = { + isa = XCSwiftPackageProductDependency; + package = 0C363D402C41B455004E241D /* XCRemoteSwiftPackageReference "ocmock" */; + productName = OCMock; + }; + 0C363D442C41B468004E241D /* OHHTTPStubs */ = { + isa = XCSwiftPackageProductDependency; + package = 0C363D432C41B468004E241D /* XCRemoteSwiftPackageReference "OHHTTPStubs" */; + productName = OHHTTPStubs; + }; + 0C363D462C41B468004E241D /* OHHTTPStubsSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 0C363D432C41B468004E241D /* XCRemoteSwiftPackageReference "OHHTTPStubs" */; + productName = OHHTTPStubsSwift; + }; + 0CCD4C5E2C41711800B53F9A /* NSObject-SafeExpectations */ = { + isa = XCSwiftPackageProductDependency; + package = 0CCD4C5D2C41711800B53F9A /* XCRemoteSwiftPackageReference "NSObject-SafeExpectations" */; + productName = "NSObject-SafeExpectations"; + }; + 0CCD4C612C41712800B53F9A /* wpxmlrpc */ = { + isa = XCSwiftPackageProductDependency; + package = 0CCD4C602C41712800B53F9A /* XCRemoteSwiftPackageReference "wpxmlrpc" */; + productName = wpxmlrpc; + }; 3FB8642B2888089F003A86BE /* BuildkiteTestCollector */ = { isa = XCSwiftPackageProductDependency; package = 3FB8642A2888089F003A86BE /* XCRemoteSwiftPackageReference "test-collector-swift" */; diff --git a/WordPressKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/WordPressKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 406567528..919434a62 100644 --- a/WordPressKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/WordPressKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/WordPressKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPressKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..2de2a748d --- /dev/null +++ b/WordPressKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,50 @@ +{ + "originHash" : "b3d469737e2fd0687b17812621644caa41a5cfacdd9f16988404acb4c8907fc3", + "pins" : [ + { + "identity" : "nsobject-safeexpectations", + "kind" : "remoteSourceControl", + "location" : "https://github.com/wordpress-mobile/NSObject-SafeExpectations", + "state" : { + "revision" : "eb84d994ab13a153888a19e5b99f536aafa77434", + "version" : "0.0.6" + } + }, + { + "identity" : "ocmock", + "kind" : "remoteSourceControl", + "location" : "https://github.com/erikdoe/ocmock", + "state" : { + "revision" : "2c0bfd373289f4a7716db5d6db471640f91a6507" + } + }, + { + "identity" : "ohhttpstubs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AliSoftware/OHHTTPStubs", + "state" : { + "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version" : "9.1.0" + } + }, + { + "identity" : "test-collector-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/buildkite/test-collector-swift", + "state" : { + "revision" : "631a2400dbe876141a3ef8c7400885907fec7f89", + "version" : "0.4.1" + } + }, + { + "identity" : "wpxmlrpc", + "kind" : "remoteSourceControl", + "location" : "https://github.com/wordpress-mobile/wpxmlrpc", + "state" : { + "revision" : "bfc413d336bdeaab89e62dc483380baa99b2257e", + "version" : "0.10.0" + } + } + ], + "version" : 3 +} diff --git a/WordPressKit.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPressKit.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 0cf274e7b..000000000 --- a/WordPressKit.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,15 +0,0 @@ -{ - "originHash" : "f28a54e288718485642adbe7b780a05abf05f95f0f435b3f87b6d3684b95d268", - "pins" : [ - { - "identity" : "test-collector-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/buildkite/test-collector-swift", - "state" : { - "revision" : "6e46839e1a4507ee047acd0896e29b9b278d9e3a", - "version" : "0.4.0" - } - } - ], - "version" : 3 -}