diff --git a/.buildkite/create-xcframeworks.sh b/.buildkite/create-xcframeworks.sh
new file mode 100755
index 00000000..27651d7f
--- /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 65acbc0f..09d1a91e 100644
--- a/.buildkite/pipeline.yml
+++ b/.buildkite/pipeline.yml
@@ -1,12 +1,16 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json
+---
+
+agents:
+ queue: mac
+
+env:
+ IMAGE_ID: xcode-15.3-v3
+
# Nodes with values to reuse in the pipeline.
common_params:
plugins: &common_plugins
- - automattic/a8c-ci-toolkit#3.1.0
- # Common environment values to use with the `env` key.
- env: &common_env
- # -v3 contains a workaround for a Simulator boot issue
- # See paaHJt-6gL-p2#comment-8712
- IMAGE_ID: xcode-15.3-v3
+ - automattic/a8c-ci-toolkit#3.5.1
# This is the default pipeline – it will build and test the app
steps:
@@ -21,17 +25,6 @@ steps:
- fastlane/test_output/report.html
- fastlane/test_output/report.junit
- .build/derived-data/Logs/**/*.xcactivitylog
- env: *common_env
- plugins: *common_plugins
-
- #################
- # Validate Podspec
- #################
- - label: "🔬 Validate Podspec"
- key: "validate"
- command: |
- validate_podspec --patch-cocoapods
- env: *common_env
plugins: *common_plugins
#################
@@ -46,26 +39,3 @@ steps:
context: SwiftLint
agents:
queue: linter
-
- - label: "🧹 Lint"
- key: "lint"
- command: |
- lint_pod
- env: *common_env
- plugins: *common_plugins
-
- #################
- # Publish the Podspec (if we're building a tag)
- #################
- - label: "⬆️ Publish Podspec"
- key: "publish"
- command: .buildkite/publish-pod.sh
- env: *common_env
- plugins: *common_plugins
- depends_on:
- - "test"
- - "validate"
- - "lint"
- if: build.tag != null
- agents:
- queue: "mac"
diff --git a/.gitignore b/.gitignore
index 0785d714..a37d2195 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 a25dc7e5..00000000
--- 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 027d1c01..80b5bbb8 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/20825728/WordPressKit.zip",
+ checksum: "097a2e55e4ec66b4d8c37bc49181df33c4b62ea9d130fac4de057a0867b68a69"
),
]
)
diff --git a/Podfile b/Podfile
index 2b74b508..c4ea2de3 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 83a6a853..fd73c6a2 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/README.md b/README.md
index b2d11daf..2932a982 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,23 @@
# WordPressKit for iOS
+
+> [!CAUTION]
+> This library is no longer fit for external contribution and community usage.
+>
+> [WordPress iOS](https://github.com/wordpress-mobile/WordPress-iOS) is its only consumer.
+> The repo exists standalone rather than as part of the WordPress codebase because it's convenient to fetch this lump of code that rarely changes as a binary XCFramework dependency.
+> See [#816](https://github.com/wordpress-mobile/WordPressKit-iOS/pull/816).
+
+## XCFramework release instructions
+
+- Run `.buildkite/create-xcframeworks.sh` to create binaries
+- Upload `.build/xcframeworks/WordPressKit.zip` to this repo
+- Update binary target in `Package.swift` with a new URL and checksum*
+- Push the new changes to your branch
+- After testing is done, merge changes into `trunk`
+
+*_checksum is echoed at the end of the `create-xcframeworks.sh` script_
+
## About
WordPressKit offers a clean and simple WordPress.com and WordPress.org API.
diff --git a/Sources/CoreAPI/HTTPRequestBuilder.swift b/Sources/CoreAPI/HTTPRequestBuilder.swift
index 1cf8170b..c230b7ef 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 b9e85a0c..14e13b15 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 a4680b20..cd8493f0 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 a8ebeb26..92d09222 100644
--- a/Sources/CoreAPI/WordPressComRestApi.swift
+++ b/Sources/CoreAPI/WordPressComRestApi.swift
@@ -2,7 +2,6 @@
import APIInterface
#endif
import Foundation
-import WordPressShared
// MARK: - WordPressComRestApiError
@@ -104,6 +103,8 @@ open class WordPressComRestApi: NSObject {
private var invalidTokenHandler: (() -> Void)?
+ private var useEphemeralSession: Bool
+
/**
Configure whether or not the user's preferred language locale should be appended. Defaults to true.
*/
@@ -140,7 +141,8 @@ open class WordPressComRestApi: NSObject {
backgroundSessionIdentifier: String = WordPressComRestApi.defaultBackgroundSessionIdentifier,
sharedContainerIdentifier: String? = nil,
localeKey: String = WordPressComRestApi.LocaleKeyDefault,
- baseURL: URL = WordPressComRestApi.apiBaseURL) {
+ baseURL: URL = WordPressComRestApi.apiBaseURL,
+ useEphemeralSession: Bool = false) {
self.oAuthToken = oAuthToken
self.userAgent = userAgent
self.backgroundUploads = backgroundUploads
@@ -148,6 +150,7 @@ open class WordPressComRestApi: NSObject {
self.sharedContainerIdentifier = sharedContainerIdentifier
self.localeKey = localeKey
self.baseURL = baseURL
+ self.useEphemeralSession = useEphemeralSession
super.init()
}
@@ -176,7 +179,7 @@ open class WordPressComRestApi: NSObject {
}
}
- @objc func setInvalidTokenHandler(_ handler: @escaping () -> Void) {
+ @objc open func setInvalidTokenHandler(_ handler: @escaping () -> Void) {
invalidTokenHandler = handler
}
@@ -348,7 +351,14 @@ open class WordPressComRestApi: NSObject {
}()
private func sessionConfiguration(background: Bool) -> URLSessionConfiguration {
- let configuration = background ? URLSessionConfiguration.background(withIdentifier: self.backgroundSessionIdentifier) : URLSessionConfiguration.default
+ let configuration: URLSessionConfiguration
+ if background {
+ configuration = .background(withIdentifier: self.backgroundSessionIdentifier)
+ } else if useEphemeralSession {
+ configuration = .ephemeral
+ } else {
+ configuration = .default
+ }
var additionalHeaders: [String: AnyObject] = [:]
if let oAuthToken = self.oAuthToken {
@@ -363,10 +373,10 @@ open class WordPressComRestApi: NSObject {
return configuration
}
- func perform(
+ open func perform(
_ method: HTTPRequestBuilder.Method,
URLString: String,
- parameters: [String: AnyObject]? = nil,
+ parameters: [String: Any]? = nil,
fulfilling progress: Progress? = nil
) async -> APIResult {
await perform(method, URLString: URLString, parameters: parameters, fulfilling: progress) {
@@ -374,10 +384,10 @@ open class WordPressComRestApi: NSObject {
}
}
- func perform(
+ open func perform(
_ method: HTTPRequestBuilder.Method,
URLString: String,
- parameters: [String: AnyObject]? = nil,
+ parameters: [String: Any]? = nil,
fulfilling progress: Progress? = nil,
jsonDecoder: JSONDecoder? = nil,
type: T.Type = T.self
@@ -391,7 +401,7 @@ open class WordPressComRestApi: NSObject {
private func perform(
_ method: HTTPRequestBuilder.Method,
URLString: String,
- parameters: [String: AnyObject]?,
+ parameters: [String: Any]?,
fulfilling progress: Progress?,
decoder: @escaping (Data) throws -> T
) async -> APIResult {
diff --git a/Sources/CoreAPI/WordPressOrgRestApi.swift b/Sources/CoreAPI/WordPressOrgRestApi.swift
index 337ea298..30adcb2d 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 2698c89f..a997efd1 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 71b827aa..76ed2776 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 59757186..d8a0607c 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 7cf0ab20..91993fa4 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 cd6afb3a..66bb996b 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 bc04ca68..c0fcb7e3 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 be303fc4..5ab9af98 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 2b8b0832..6bd56dc2 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 e377de16..3bd0dd59 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 62f0dbea..8123998d 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 3351e059..2880832a 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 {
@@ -37,9 +37,12 @@ import NSObject_SafeExpectations
/// Features available for the current blog's plan.
public var planActiveFeatures = [String]()
- /// Indicates whether it's a jetpack site, or not.
+ /// Indicates whether the site is a Jetpack site or not.
public var jetpack: Bool = false
+ /// Indicates whether the site is connected to WP.com via `jetpack-connection`.
+ public var jetpackConnection: Bool = false
+
/// Boolean indicating whether the current user has Admin privileges, or not.
public var isAdmin: Bool = false
@@ -58,6 +61,8 @@ import NSObject_SafeExpectations
/// Blog's total disk quota space used.
public var quotaSpaceUsed: NSNumber?
+ public var isDeleted: Bool
+
/// Parses details from a JSON dictionary, as returned by the WordPress.com REST API.
@objc(initWithJSONDictionary:)
public init(jsonDictionary json: NSDictionary) {
@@ -68,6 +73,7 @@ import NSObject_SafeExpectations
self.url = json.string(forKey: "URL") ?? ""
self.xmlrpc = json.string(forKeyPath: "meta.links.xmlrpc")
self.jetpack = json.number(forKey: "jetpack")?.boolValue ?? false
+ self.jetpackConnection = json.number(forKey: "jetpack_connection")?.boolValue ?? false
self.icon = json.string(forKeyPath: "icon.img")
self.capabilities = json.object(forKey: "capabilities") as? [String: Bool] ?? [:]
self.isAdmin = json.number(forKeyPath: "capabilities.manage_options")?.boolValue ?? false
@@ -79,6 +85,7 @@ import NSObject_SafeExpectations
self.planActiveFeatures = (json.array(forKeyPath: "plan.features.active") as? [String]) ?? []
self.quotaSpaceAllowed = json.number(forKeyPath: "quota.space_allowed")
self.quotaSpaceUsed = json.number(forKeyPath: "quota.space_used")
+ self.isDeleted = json.number(forKey: "is_deleted")?.boolValue == true
}
}
diff --git a/Sources/WordPressKit/Models/RemoteBlogJetpackModulesSettings.swift b/Sources/WordPressKit/Models/RemoteBlogJetpackModulesSettings.swift
index 0c3ce7da..d1af811a 100644
--- a/Sources/WordPressKit/Models/RemoteBlogJetpackModulesSettings.swift
+++ b/Sources/WordPressKit/Models/RemoteBlogJetpackModulesSettings.swift
@@ -3,18 +3,11 @@ import Foundation
/// This struct encapsulates the *remote* Jetpack modules settings available for a Blog entity
///
public struct RemoteBlogJetpackModulesSettings {
-
- /// Indicates whether the Jetpack site lazy loads images.
- ///
- public let lazyLoadImages: Bool
-
/// Indicates whether the Jetpack site serves images from our server.
///
public let serveImagesFromOurServers: Bool
- public init(lazyLoadImages: Bool, serveImagesFromOurServers: Bool) {
- self.lazyLoadImages = lazyLoadImages
+ public init(serveImagesFromOurServers: Bool) {
self.serveImagesFromOurServers = serveImagesFromOurServers
}
-
}
diff --git a/Sources/WordPressKit/Models/RemoteBlogOptionsHelper.swift b/Sources/WordPressKit/Models/RemoteBlogOptionsHelper.swift
index 184960f3..a5eca541 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 68923573..f97bfa2c 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 1c8cba2e..bb9f6632 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 3ce0c80e..3306be67 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/RemotePost.h b/Sources/WordPressKit/Models/RemotePost.h
index 7a06fc5c..e74cee04 100644
--- a/Sources/WordPressKit/Models/RemotePost.h
+++ b/Sources/WordPressKit/Models/RemotePost.h
@@ -36,6 +36,7 @@ extern NSString * const PostStatusDeleted;
@property (nonatomic, strong) NSString *postThumbnailPath;
@property (nonatomic, strong) NSString *type;
@property (nonatomic, strong) NSString *format;
+@property (nonatomic, assign) NSInteger order;
/**
* A snapshot of the post at the last autosave.
diff --git a/Sources/WordPressKit/Models/RemoteReaderPost.h b/Sources/WordPressKit/Models/RemoteReaderPost.h
index f25b51a7..fa623cdc 100644
--- a/Sources/WordPressKit/Models/RemoteReaderPost.h
+++ b/Sources/WordPressKit/Models/RemoteReaderPost.h
@@ -17,6 +17,8 @@
@property (nonatomic, strong) NSNumber *commentCount;
@property (nonatomic) BOOL commentsOpen;
@property (nonatomic, strong) NSString *featuredImage;
+@property (nonatomic, strong) NSString *autoSuggestedFeaturedImage;
+@property (nonatomic, strong) NSString *suitableImageFromPostContent;
@property (nonatomic, strong) NSNumber *feedID;
@property (nonatomic, strong) NSNumber *feedItemID;
@property (nonatomic, strong) NSString *globalID;
diff --git a/Sources/WordPressKit/Models/RemoteReaderPost.m b/Sources/WordPressKit/Models/RemoteReaderPost.m
index ee0032ed..ebefeb08 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,10 +94,13 @@ - (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"]];
+ self.siteIconURL = [self stringOrEmptyString:[dict stringForKeyPath:@"site_icon.img"]];
+ if (self.siteIconURL.length == 0) {
+ self.siteIconURL = [self stringOrEmptyString:[dict stringForKeyPath:@"meta.data.site.icon.img"]];
+ }
self.blogName = [self siteNameFromPostDictionary:dict];
self.blogDescription = [self siteDescriptionFromPostDictionary:dict];
self.blogURL = [self siteURLFromPostDictionary:dict];
@@ -107,6 +109,8 @@ - (instancetype)initWithDictionary:(NSDictionary *)dict;
self.content = [self postContentFromPostDictionary:dict];
self.date_created_gmt = [self stringOrEmptyString:[dict stringForKey:PostRESTKeyDate]];
self.featuredImage = [self featuredImageFromPostDictionary:dict];
+ self.autoSuggestedFeaturedImage = [self sanitizeFeaturedImageString:[self featuredMediaImageFromPostDictionary:dict]];
+ self.suitableImageFromPostContent = [self sanitizeFeaturedImageString:[self suitableImageFromPostContent:dict]];
self.feedID = [dict numberForKey:PostRESTKeyFeedID];
self.feedItemID = [dict numberForKey:PostRESTKeyFeedItemID];
self.globalID = [self stringOrEmptyString:[dict stringForKey:PostRESTKeyGlobalID]];
@@ -205,8 +209,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 +275,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 +477,7 @@ - (NSDate *)sortDateFromPostDictionary:(NSDictionary *)dict
sortDate = editorialDate;
}
- return [DateUtils dateFromISOString:sortDate];
+ return [WPKitDateUtils dateFromISOString:sortDate];
}
/**
@@ -492,16 +496,6 @@ - (NSString *)featuredImageFromPostDictionary:(NSDictionary *)dict
featuredImage = [self userSpecifiedFeaturedImageFromPostDictionary:dict];
}
- // If that's not present look for an image in featured media
- if ([featuredImage length] == 0) {
- featuredImage = [self featuredMediaImageFromPostDictionary:dict];
- }
-
- // As a last resource lets look for a suitable image in the post content
- if ([featuredImage length] == 0) {
- featuredImage = [self suitableImageFromPostContent:dict];
- }
-
featuredImage = [self sanitizeFeaturedImageString:featuredImage];
return featuredImage;
@@ -525,7 +519,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 +645,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 +683,7 @@ - (NSString *)formatSummary:(NSString *)summary
*/
- (NSString *)createSummaryFromContent:(NSString *)string
{
- return [string summarized];
+ return [string wpkit_summarized];
}
/**
@@ -700,7 +694,7 @@ - (NSString *)createSummaryFromContent:(NSString *)string
*/
- (NSString *)makePlainText:(NSString *)string
{
- return [string summarized];
+ return [string wpkit_summarized];
}
/**
@@ -711,7 +705,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 cde80240..01807e4b 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 fe9fb03a..4ed6d59f 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]
@@ -85,6 +84,7 @@ extension StatsEmailsSummaryData {
public enum SortField: String {
case opens = "opens"
case postId = "post_id"
+ case postDate = "post_date"
}
public enum SortOrder: String {
diff --git a/Sources/WordPressKit/Models/Stats/Insights/StatsLastPostInsight.swift b/Sources/WordPressKit/Models/Stats/Insights/StatsLastPostInsight.swift
index 6241a72d..9319c951 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 8daaad19..4af5e2a4 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 5ed9ee16..aece6030 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 d24447fc..acefd4b7 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 e5e592f8..63b90b31 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 8974acef..9654d2ad 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 02432a1f..c487ba4a 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 56ed8b60..483893f6 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";
@@ -397,7 +396,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;
@@ -406,10 +405,23 @@ - (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];
+ }] wpkit_filter:^BOOL(RemoteBlog *blog) {
+ // Exclude deleted sites from query result, since the app does not handle deleted sites properly.
+ // I tried to use query arguments `site_visibility=visible` and `site_activity=active`, but neither excludes
+ // deleted sites.
+ if (blog.isDeleted) {
+ return false;
+ }
+
+ // Exclude sites that are connected via Jetpack, but without an active Jetpack connection.
+ if (blog.jetpackConnection && !blog.jetpack) {
+ return false;
+ }
+
+ return true;
}];
- return blogs;
}
@end
diff --git a/Sources/WordPressKit/Services/AccountSettingsRemote.swift b/Sources/WordPressKit/Services/AccountSettingsRemote.swift
index e029ab5d..7ed7751b 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 d81ab327..56a57cd1 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 {
@@ -28,16 +27,22 @@ open class ActivityServiceRemote: ServiceRemoteWordPressComREST {
///
/// - Returns: An array of activities and a boolean indicating if there's more activities to fetch.
///
- open func getActivityForSite(_ siteID: Int,
- offset: Int = 0,
- count: Int,
- after: Date? = nil,
- before: Date? = nil,
- group: [String] = [],
- success: @escaping (_ activities: [Activity], _ hasMore: Bool) -> Void,
- failure: @escaping (Error) -> Void) {
-
+ open func getActivityForSite(
+ _ siteID: Int,
+ offset: Int = 0,
+ count: Int,
+ after: Date? = nil,
+ before: Date? = nil,
+ group: [String] = [],
+ rewindable: Bool? = nil,
+ searchText: String? = nil,
+ success: @escaping (_ activities: [Activity], _ hasMore: Bool) -> Void,
+ failure: @escaping (Error) -> Void
+ ) {
var path = URLComponents(string: "sites/\(siteID)/activity")
+ if rewindable == true, let currentPath = path?.path {
+ path?.path = currentPath.appending("/rewindable")
+ }
path?.queryItems = group.map { URLQueryItem(name: "group[]", value: $0) }
@@ -52,6 +57,9 @@ open class ActivityServiceRemote: ServiceRemoteWordPressComREST {
} else if let on = after ?? before {
path?.queryItems?.append(URLQueryItem(name: "on", value: formatter.string(from: on)))
}
+ if let searchText, !searchText.isEmpty {
+ path?.queryItems?.append(URLQueryItem(name: "text_search", value: searchText))
+ }
guard let endpoint = path?.string else {
return
diff --git a/Sources/WordPressKit/Services/AnnouncementServiceRemote.swift b/Sources/WordPressKit/Services/AnnouncementServiceRemote.swift
index 343680b3..fe5e24f2 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 168783e8..13b87ad6 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 7ac03196..b18c84c7 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 {
@@ -201,13 +200,11 @@ private extension BlogJetpackSettingsServiceRemote {
$0[key] = $1
}
- guard let lazyLoadImagesValue = dictionary[Keys.lazyLoadImages]?[ModuleOptionKeys.active] as? Bool,
- let serveImagesFromOurServersValue = dictionary[Keys.serveImagesFromOurServers]?[ModuleOptionKeys.active] as? Bool else {
+ guard let serveImagesFromOurServersValue = dictionary[Keys.serveImagesFromOurServers]?[ModuleOptionKeys.active] as? Bool else {
throw ResponseError.decodingFailure
}
- return RemoteBlogJetpackModulesSettings(lazyLoadImages: lazyLoadImagesValue,
- serveImagesFromOurServers: serveImagesFromOurServersValue)
+ return RemoteBlogJetpackModulesSettings(serveImagesFromOurServers: serveImagesFromOurServersValue)
}
func dictionaryFromJetpackSettings(_ settings: RemoteBlogJetpackSettings) -> [String: Any] {
@@ -249,7 +246,6 @@ public extension BlogJetpackSettingsServiceRemote {
static let monitorPushNotifications = "wp_note_notifications"
// RemoteBlogJetpackModuleSettings keys
- public static let lazyLoadImages = "lazy-images"
public static let serveImagesFromOurServers = "photon"
}
diff --git a/Sources/WordPressKit/Services/BlogServiceRemoteREST.m b/Sources/WordPressKit/Services/BlogServiceRemoteREST.m
index b8b3f140..69b22af9 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 9870919d..3d7ff90a 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.h b/Sources/WordPressKit/Services/CommentServiceRemoteREST.h
index e4323aad..4089461a 100644
--- a/Sources/WordPressKit/Services/CommentServiceRemoteREST.h
+++ b/Sources/WordPressKit/Services/CommentServiceRemoteREST.h
@@ -30,7 +30,7 @@
*/
- (void)updateCommentWithID:(NSNumber * _Nonnull)commentID
content:(NSString * _Nonnull)content
- success:(void (^ _Nullable)(void))success
+ success:(void (^ _Nullable)(RemoteComment * _Nullable comment))success
failure:(void (^ _Nullable)(NSError * _Nullable error))failure;
/**
diff --git a/Sources/WordPressKit/Services/CommentServiceRemoteREST.m b/Sources/WordPressKit/Services/CommentServiceRemoteREST.m
index 0f13fddd..3a610c20 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
@@ -247,7 +246,7 @@ - (void)syncHierarchicalCommentsForPost:(NSNumber *)postID
- (void)updateCommentWithID:(NSNumber *)commentID
content:(NSString *)content
- success:(void (^)(void))success
+ success:(void (^)(RemoteComment *comment))success
failure:(void (^)(NSError *error))failure
{
NSString *path = [NSString stringWithFormat:@"sites/%@/comments/%@", self.siteID, commentID];
@@ -261,14 +260,15 @@ - (void)updateCommentWithID:(NSNumber *)commentID
[self.wordPressComRESTAPI post:requestUrl
parameters:parameters
success:^(id responseObject, NSHTTPURLResponse *httpResponse) {
- if (success) {
- success();
- }
- } failure:^(NSError *error, NSHTTPURLResponse *httpResponse) {
- if (failure) {
- failure(error);
- }
- }];
+ RemoteComment *comment = [self remoteCommentFromJSONDictionary:responseObject];
+ if (success) {
+ success(comment);
+ }
+ } failure:^(NSError *error, NSHTTPURLResponse *httpResponse) {
+ if (failure) {
+ failure(error);
+ }
+ }];
}
- (void)replyToPostWithID:(NSNumber *)postID
@@ -464,7 +464,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 +530,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 dfe05b5c..891c6c7c 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 1d5ba5f8..c6a7bf4d 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 2f12cb9e..9604fc0c 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 ce442915..3e613897 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 0682d7b3..4ae9f725 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 5c50b085..90f38edf 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 0d0059ea..5f01cf01 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 e7c0b145..bed046cd 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 5e33682a..334bb71f 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 dd7d62af..e23b26ae 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 d654f394..b31f8e21 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 b9fefa66..f64d3f9f 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 f5a6f1cb..700c1b33 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 e4455a61..82e809e6 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"];
@@ -471,6 +471,7 @@ + (RemotePost *)remotePostFromJSONDictionary:(NSDictionary *)jsonPost {
post.postThumbnailPath = [postThumbnail stringForKeyPath:@"URL"];
post.type = jsonPost[@"type"];
post.format = jsonPost[@"format"];
+ post.order = [jsonPost numberForKey:@"menu_order"].integerValue;
post.commentCount = [jsonPost numberForKeyPath:@"discussion.comment_count"] ?: @0;
post.likeCount = [jsonPost numberForKeyPath:@"like_count"] ?: @0;
@@ -513,9 +514,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 +596,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 +617,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 +647,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 904941fd..098e9e64 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 e5454bc8..30a41613 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"];
@@ -323,6 +322,7 @@ + (RemotePost *)remotePostFromXMLRPCDictionary:(NSDictionary *)xmlrpcDictionary
post.postThumbnailPath = [thumbnailDict stringForKey:@"link"];
post.type = xmlrpcDictionary[@"post_type"];
post.format = xmlrpcDictionary[@"post_format"];
+ post.order = [xmlrpcDictionary numberForKey:@"menu_order"].integerValue;
post.metadata = xmlrpcDictionary[@"custom_fields"];
@@ -337,7 +337,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 +359,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 +411,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 db233527..cad2fe17 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+Cards.swift b/Sources/WordPressKit/Services/ReaderPostServiceRemote+Cards.swift
index 36ae18df..6795c9b7 100644
--- a/Sources/WordPressKit/Services/ReaderPostServiceRemote+Cards.swift
+++ b/Sources/WordPressKit/Services/ReaderPostServiceRemote+Cards.swift
@@ -11,6 +11,11 @@ public enum ReaderSortingOption: String, CaseIterable {
}
}
+public enum ReaderStream: String {
+ case discover = "discover"
+ case firstPosts = "first-posts"
+}
+
extension ReaderPostServiceRemote {
/// Returns a collection of RemoteReaderCard using the tags API
/// a Reader Card can represent an item for the reader feed, such as
@@ -46,20 +51,22 @@ extension ReaderPostServiceRemote {
/// - Topics you may like
/// - Blogs you may like and so on
///
+ /// - Parameter stream: The name of the stream. By default, `.discover`.
/// - Parameter topics: an array of String representing the topics
/// - Parameter page: a String that represents a page handle
/// - Parameter sortingOption: a ReaderSortingOption that represents a sorting option
/// - Parameter count: the number of cards to fetch. Warning: This also changes the number of objects returned for recommended sites/tags.
/// - Parameter success: Called when the request succeeds and the data returned is valid
/// - Parameter failure: Called if the request fails for any reason, or the response data is invalid
- public func fetchStreamCards(for topics: [String],
+ public func fetchStreamCards(stream: ReaderStream = .discover,
+ for topics: [String],
page: String? = nil,
sortingOption: ReaderSortingOption = .noSorting,
refreshCount: Int? = nil,
count: Int? = nil,
success: @escaping ([RemoteReaderCard], String?) -> Void,
failure: @escaping (Error) -> Void) {
- let path = "read/streams/discover"
+ let path = "read/streams/\(stream.rawValue)"
guard let requestUrl = cardsEndpoint(with: path,
topics: topics,
page: page,
diff --git a/Sources/WordPressKit/Services/ReaderPostServiceRemote.m b/Sources/WordPressKit/Services/ReaderPostServiceRemote.m
index 6cafe5f1..46da6729 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 96090898..b7d060a9 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 ae032969..03d4739c 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 44d278eb..1aa121ca 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 b78003d6..9078367e 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 55433b6f..8be0157a 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 cab54481..fe208c93 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 6cf675a6..02b9ce94 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/SubscribersServiceRemote.swift b/Sources/WordPressKit/Services/SubscribersServiceRemote.swift
new file mode 100644
index 00000000..b8c6770a
--- /dev/null
+++ b/Sources/WordPressKit/Services/SubscribersServiceRemote.swift
@@ -0,0 +1,286 @@
+import Foundation
+
+public class SubscribersServiceRemote: ServiceRemoteWordPressComREST {
+
+ // MARK: GET Subscribers (Paginated List)
+
+ public struct GetSubscribersParameters: Hashable {
+ public var sortField: SortField?
+ public var sortOrder: SortOrder?
+ public var subscriptionTypeFilter: FilterSubscriptionType?
+ public var paymentTypeFilter: FilterPaymentType?
+
+ @frozen public enum SortField: String, CaseIterable {
+ case dateSubscribed = "date_subscribed"
+ case email = "email"
+ case name = "name"
+ case plan = "plan"
+ case subscriptionStatus = "subscription_status"
+ }
+
+ @frozen public enum SortOrder: String, CaseIterable {
+ case ascending = "asc"
+ case descending = "dsc"
+ }
+
+ @frozen public enum FilterSubscriptionType: String, CaseIterable {
+ case email = "email_subscriber"
+ case reader = "reader_subscriber"
+ case unconfirmed = "unconfirmed_subscriber"
+ case blocked = "blocked_subscriber"
+ }
+
+ @frozen public enum FilterPaymentType: String, CaseIterable {
+ case free
+ case paid
+ }
+
+ public var filters: [String] {
+ [subscriptionTypeFilter?.rawValue, paymentTypeFilter?.rawValue].compactMap { $0 }
+ }
+
+ public init(sortField: SortField? = nil, sortOrder: SortOrder? = nil, subscriptionTypeFilter: FilterSubscriptionType? = nil, paymentTypeFilter: FilterPaymentType? = nil) {
+ self.sortField = sortField
+ self.sortOrder = sortOrder
+ self.subscriptionTypeFilter = subscriptionTypeFilter
+ self.paymentTypeFilter = paymentTypeFilter
+ }
+ }
+
+ public struct GetSubscribersResponse: Decodable {
+ public var total: Int
+ public var pages: Int
+ public var page: Int
+ public var subscribers: [Subscriber]
+
+ public struct Subscriber: Decodable, SubsciberBasicInfoResponse {
+ public let subscriberID: Int
+ public let dotComUserID: Int
+ public let displayName: String?
+ public let avatar: String?
+ public let emailAddress: String?
+ public let dateSubscribed: Date
+ public let isEmailSubscriptionEnabled: Bool
+ public let subscriptionStatus: String?
+
+ public init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: StringCodingKey.self)
+ subscriberID = try container.decode(Int.self, forKey: "subscription_id")
+ dotComUserID = try container.decode(Int.self, forKey: "user_id")
+ displayName = try? container.decodeIfPresent(String.self, forKey: "display_name")
+ avatar = try? container.decodeIfPresent(String.self, forKey: "avatar")
+ emailAddress = try? container.decodeIfPresent(String.self, forKey: "email_address")
+ dateSubscribed = try container.decode(Date.self, forKey: "date_subscribed")
+ isEmailSubscriptionEnabled = try container.decode(Bool.self, forKey: "is_email_subscriber")
+ subscriptionStatus = try? container.decodeIfPresent(String.self, forKey: "subscription_status")
+ }
+ }
+ }
+
+ /// Gets the list of the site subscribers, including WordPress.com users and
+ /// email subscribers.
+ public func getSubscribers(
+ siteID: Int,
+ page: Int? = nil,
+ perPage: Int? = 25,
+ parameters: GetSubscribersParameters = .init(),
+ search: String? = nil,
+ ) async throws -> GetSubscribersResponse {
+ let url = self.path(forEndpoint: "sites/\(siteID)/subscribers", withVersion: ._2_0)
+ var query: [String: Any] = [:]
+ if let page {
+ query["page"] = page
+ }
+ if let perPage {
+ query["per_page"] = perPage
+ }
+ if let sortField = parameters.sortField {
+ query["sort"] = sortField.rawValue
+ }
+ if let sortOrder = parameters.sortOrder {
+ query["sort_order"] = sortOrder.rawValue
+ }
+ if !parameters.filters.isEmpty {
+ query["filters"] = parameters.filters
+ }
+ if let search, !search.isEmpty {
+ query["search"] = search
+ }
+
+ let decoder = JSONDecoder()
+ decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats
+
+ return try await wordPressComRestApi.perform(
+ .get,
+ URLString: url,
+ parameters: query,
+ jsonDecoder: decoder,
+ type: GetSubscribersResponse.self
+ ).get().body
+ }
+
+ // MARK: GET Subscriber (Individual Details)
+
+ public protocol SubsciberBasicInfoResponse {
+ var dotComUserID: Int { get }
+ var subscriberID: Int { get }
+ var displayName: String? { get }
+ var emailAddress: String? { get }
+ var avatar: String? { get }
+ var dateSubscribed: Date { get }
+ }
+
+ public final class GetSubscriberDetailsResponse: Decodable, SubsciberBasicInfoResponse {
+ public let subscriberID: Int
+ public let dotComUserID: Int
+ public let displayName: String?
+ public let avatar: String?
+ public let emailAddress: String?
+ public let siteURL: String?
+ public let dateSubscribed: Date
+ public let isEmailSubscriptionEnabled: Bool
+ public let subscriptionStatus: String?
+ public let country: Country?
+ public let plans: [Plan]?
+
+ public struct Country: Decodable {
+ public var code: String?
+ public var name: String?
+ }
+
+ public struct Plan: Decodable {
+ public let isGift: Bool
+ public let giftId: Int?
+ public let paidSubscriptionId: String?
+ public let status: String
+ public let title: String
+ public let currency: String?
+ public let renewInterval: String?
+ public let inactiveRenewInterval: String?
+ public let renewalPrice: Decimal
+ public let startDate: Date
+ public let endDate: Date
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: StringCodingKey.self)
+ isGift = try container.decode(Bool.self, forKey: "is_gift")
+ giftId = try container.decodeIfPresent(Int.self, forKey: "gift_id")
+ paidSubscriptionId = try container.decodeIfPresent(String.self, forKey: "paid_subscription_id")
+ status = try container.decode(String.self, forKey: "status")
+ title = try container.decode(String.self, forKey: "title")
+ currency = try container.decodeIfPresent(String.self, forKey: "currency")
+ renewInterval = try? container.decodeIfPresent(String.self, forKey: "renew_interval")
+ inactiveRenewInterval = try? container.decodeIfPresent(String.self, forKey: "inactive_renew_interval")
+ renewalPrice = try container.decode(Decimal.self, forKey: "renewal_price")
+ startDate = try container.decode(Date.self, forKey: "start_date")
+ endDate = try container.decode(Date.self, forKey: "end_date")
+ }
+ }
+
+ public init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: StringCodingKey.self)
+ subscriberID = try container.decode(Int.self, forKey: "subscription_id")
+ dotComUserID = try container.decode(Int.self, forKey: "user_id")
+ displayName = try? container.decodeIfPresent(String.self, forKey: "display_name")
+ avatar = try? container.decodeIfPresent(String.self, forKey: "avatar")
+ emailAddress = try? container.decodeIfPresent(String.self, forKey: "email_address")
+ siteURL = try? container.decodeIfPresent(String.self, forKey: "url")
+ dateSubscribed = try container.decode(Date.self, forKey: "date_subscribed")
+ isEmailSubscriptionEnabled = try container.decode(Bool.self, forKey: "is_email_subscriber")
+ subscriptionStatus = try? container.decodeIfPresent(String.self, forKey: "subscription_status")
+ country = try? container.decodeIfPresent(Country.self, forKey: "country")
+ plans = try container.decodeIfPresent([Plan].self, forKey: "plans")
+ }
+ }
+
+ /// Gets stats for the given subscriber.
+ ///
+ /// Example: https://public-api.wordpress.com/wpcom/v2/sites/239619264/subscribers/individual?subscription_id=907116368
+ public func getSubsciberDetails(
+ siteID: Int,
+ subscriberID: Int,
+ type: String = "email"
+ ) async throws -> GetSubscriberDetailsResponse {
+ let url = self.path(forEndpoint: "sites/\(siteID)/subscribers/individual", withVersion: ._2_0)
+ let query: [String: Any] = [
+ "subscription_id": subscriberID,
+ "type": type
+ ]
+
+ let decoder = JSONDecoder()
+ decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats
+
+ return try await wordPressComRestApi.perform(
+ .get,
+ URLString: url,
+ parameters: query,
+ jsonDecoder: decoder,
+ type: GetSubscriberDetailsResponse.self
+ ).get().body
+ }
+
+ public struct GetSubscriberStatsResponse: Decodable {
+ public var emailsSent: Int
+ public var uniqueOpens: Int
+ public var uniqueClicks: Int
+ }
+
+ /// Gets stats for the given subscriber.
+ ///
+ /// Example: https://public-api.wordpress.com/wpcom/v2/sites/239619264/individual-subscriber-stats?subscription_id=907116368
+ public func getSubsciberStats(
+ siteID: Int,
+ subscriberID: Int
+ ) async throws -> GetSubscriberStatsResponse {
+ let url = self.path(forEndpoint: "sites/\(siteID)/individual-subscriber-stats", withVersion: ._2_0)
+ let query: [String: Any] = [
+ "subscription_id": subscriberID
+ ]
+ return try await wordPressComRestApi.perform(
+ .get,
+ URLString: url,
+ parameters: query,
+ jsonDecoder: JSONDecoder.apiDecoder,
+ type: GetSubscriberStatsResponse.self
+ ).get().body
+ }
+
+ // MARK: POST Import Subscribers
+
+ /// Example: URL: https://public-api.wordpress.com/wpcom/v2/sites/216878809/subscribers/import?_envelope=1
+ @discardableResult
+ public func importSubscribers(
+ siteID: Int,
+ emails: [String]
+ ) async throws -> ImportSubscribersResponse {
+ let url = self.path(forEndpoint: "sites/\(siteID)/subscribers/import", withVersion: ._2_0)
+ let parameters: [String: Any] = [
+ "emails": emails,
+ "parse_only": false
+ ]
+ return try await wordPressComRestApi.perform(
+ .post,
+ URLString: url,
+ parameters: parameters,
+ type: ImportSubscribersResponse.self
+ ).get().body
+ }
+
+ public struct ImportSubscribersResponse: Decodable {
+ public let uploadID: Int
+
+ enum CodingKeys: String, CodingKey {
+ case uploadID = "upload_id"
+ }
+ }
+}
+
+extension SubscribersServiceRemote.SubsciberBasicInfoResponse {
+ public var avatarURL: URL? {
+ avatar.flatMap(URL.init)
+ }
+
+ public var isDotComUser: Bool {
+ dotComUserID > 0
+ }
+}
diff --git a/Sources/WordPressKit/Services/TaxonomyServiceRemoteREST.m b/Sources/WordPressKit/Services/TaxonomyServiceRemoteREST.m
index c35291d9..4dbc4ee1 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 9bf54e74..6e11502d 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/ThemeServiceRemote.h b/Sources/WordPressKit/Services/ThemeServiceRemote.h
index 3ee51c4a..939cdb4d 100644
--- a/Sources/WordPressKit/Services/ThemeServiceRemote.h
+++ b/Sources/WordPressKit/Services/ThemeServiceRemote.h
@@ -58,6 +58,7 @@ typedef void(^ThemeServiceRemoteFailureBlock)(NSError *error);
* @details Includes premium themes even if not purchased. Don't call this method if the list
* you want to retrieve is for a specific blog. Use getThemesForBlogId instead.
*
+ * @param search Search term for filtering themes. Cannot be nil.
* @param freeOnly Only fetch free themes, if false all WP themes will be returned
* @param page Results page to return.
* @param success The success handler. Can be nil.
@@ -66,6 +67,7 @@ typedef void(^ThemeServiceRemoteFailureBlock)(NSError *error);
* @returns A progress object that can be used to track progress and/or cancel the task
*/
- (NSProgress *)getWPThemesPage:(NSInteger)page
+ search:(NSString *)search
freeOnly:(BOOL)freeOnly
success:(ThemeServiceRemoteThemesRequestSuccessBlock)success
failure:(ThemeServiceRemoteFailureBlock)failure;
diff --git a/Sources/WordPressKit/Services/ThemeServiceRemote.m b/Sources/WordPressKit/Services/ThemeServiceRemote.m
index 97d6e60e..496c5a77 100644
--- a/Sources/WordPressKit/Services/ThemeServiceRemote.m
+++ b/Sources/WordPressKit/Services/ThemeServiceRemote.m
@@ -13,6 +13,7 @@
static NSString* const ThemeRequestNumberKey = @"number";
static NSInteger const ThemeRequestNumberValue = 50;
static NSString* const ThemeRequestPageKey = @"page";
+static NSString* const ThemeRequestSearchKey = @"search";
@implementation ThemeServiceRemote
@@ -98,20 +99,26 @@ - (NSProgress *)getThemeId:(NSString*)themeId
}
- (NSProgress *)getWPThemesPage:(NSInteger)page
+ search:(NSString *)search
freeOnly:(BOOL)freeOnly
success:(ThemeServiceRemoteThemesRequestSuccessBlock)success
failure:(ThemeServiceRemoteFailureBlock)failure
{
NSParameterAssert(page > 0);
-
+
NSString *requestUrl = [self pathForEndpoint:@"themes"
- withVersion:WordPressComRESTAPIVersion_1_2];
-
- NSDictionary *parameters = @{ThemeRequestTierKey: freeOnly ? ThemeRequestTierFreeValue : ThemeRequestTierAllValue,
- ThemeRequestNumberKey: @(ThemeRequestNumberValue),
- ThemeRequestPageKey: @(page),
- };
-
+ withVersion:WordPressComRESTAPIVersion_2_0];
+
+ NSMutableDictionary *parameters = [@{
+ ThemeRequestTierKey: freeOnly ? ThemeRequestTierFreeValue : ThemeRequestTierAllValue,
+ ThemeRequestNumberKey: @(ThemeRequestNumberValue),
+ ThemeRequestPageKey: @(page)
+ } mutableCopy];
+
+ if (search) {
+ parameters[ThemeRequestSearchKey] = search;
+ }
+
return [self getThemesWithRequestUrl:requestUrl
page:page
parameters:parameters
@@ -143,9 +150,9 @@ - (NSProgress *)getThemesPage:(NSInteger)page
}
- (NSProgress *)getThemesForBlogId:(NSNumber *)blogId
- page:(NSInteger)page
- success:(ThemeServiceRemoteThemesRequestSuccessBlock)success
- failure:(ThemeServiceRemoteFailureBlock)failure
+ page:(NSInteger)page
+ success:(ThemeServiceRemoteThemesRequestSuccessBlock)success
+ failure:(ThemeServiceRemoteFailureBlock)failure
{
NSParameterAssert([blogId isKindOfClass:[NSNumber class]]);
NSParameterAssert(page > 0);
diff --git a/Sources/WordPressKit/Services/TimeZoneServiceRemote.swift b/Sources/WordPressKit/Services/TimeZoneServiceRemote.swift
index 42e4239f..500ac566 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 2ba8bf85..84eaaf29 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 c65f80e5..b2fb6614 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 179d0a21..63763e0e 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 28555c7e..0db615a5 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/StringCodingKey.swift b/Sources/WordPressKit/Utility/StringCodingKey.swift
new file mode 100644
index 00000000..9f4c2bb1
--- /dev/null
+++ b/Sources/WordPressKit/Utility/StringCodingKey.swift
@@ -0,0 +1,27 @@
+import Foundation
+
+struct StringCodingKey: CodingKey, ExpressibleByStringLiteral {
+ private let string: String
+ private var int: Int?
+
+ var stringValue: String { return string }
+
+ init(string: String) {
+ self.string = string
+ }
+
+ init?(stringValue: String) {
+ self.string = stringValue
+ }
+
+ var intValue: Int? { return int }
+
+ init?(intValue: Int) {
+ self.string = String(describing: intValue)
+ self.int = intValue
+ }
+
+ init(stringLiteral value: String) {
+ self.string = value
+ }
+}
diff --git a/Sources/WordPressKit/Utility/UIDevice+Extensions.swift b/Sources/WordPressKit/Utility/UIDevice+Extensions.swift
new file mode 100644
index 00000000..808427df
--- /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 4090f0d4..3b772a01 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 482fdb30..67570367 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 00000000..76d50ad5
--- /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 00000000..06e2a7e1
--- /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 00000000..d8d360c1
--- /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 00000000..736c2bdf
--- /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 00000000..9dabadc6
--- /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 00000000..6f5966ee
--- /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 00000000..a6f28ced
--- /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 00000000..43930bd7
--- /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 00000000..ca8f8798
--- /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 00000000..f2de4533
--- /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 00000000..19ee5fb7
--- /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 00000000..66b5a8f0
--- /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 00000000..cc73aa2f
--- /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 00000000..4cd6a337
--- /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 00000000..02f561cf
--- /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 00000000..c7968cf9
--- /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 00000000..1e8b7683
--- /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 00000000..d1239f17
--- /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 c966ff9d..88d786d9 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 c27c8cd1..eb0f54a9 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 37e1c0af..f5feb7ce 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 0435003a..77a12077 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 a7f65b69..83cca189 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 6b3a6b31..28c95fdc 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 595d2c5d..d70cacb7 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 b198c5d0..332b6651 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 3a79b643..0490d90d 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/Mock Data/site-subscriber-get-details-response-invalid-country.json b/Tests/WordPressKitTests/Mock Data/site-subscriber-get-details-response-invalid-country.json
new file mode 100644
index 00000000..3b92ec64
--- /dev/null
+++ b/Tests/WordPressKitTests/Mock Data/site-subscriber-get-details-response-invalid-country.json
@@ -0,0 +1,15 @@
+{
+ "user_id": 123,
+ "subscription_id": 123,
+ "email_address": "test@example.com",
+ "date_subscribed": "2025-04-17T14:40:00+00:00",
+ "is_email_subscriber": false,
+ "subscription_status": "Subscribed",
+ "avatar": "https://example.com/avatar",
+ "display_name": "Alex",
+ "url": "http://example.wordpress.com",
+ "country": {
+ "code": "",
+ "name": false
+ }
+}
diff --git a/Tests/WordPressKitTests/Mock Data/site-subscriber-get-details-response.json b/Tests/WordPressKitTests/Mock Data/site-subscriber-get-details-response.json
new file mode 100644
index 00000000..b7d637e7
--- /dev/null
+++ b/Tests/WordPressKitTests/Mock Data/site-subscriber-get-details-response.json
@@ -0,0 +1,43 @@
+{
+ "user_id": 123,
+ "subscription_id": 123,
+ "email_address": "test@example.com",
+ "date_subscribed": "2025-04-17T14:40:00+00:00",
+ "is_email_subscriber": false,
+ "subscription_status": "Subscribed",
+ "avatar": "https://example.com/avatar",
+ "display_name": "Alex",
+ "url": "http://example.wordpress.com",
+ "country": {
+ "code": "US",
+ "name": "United States"
+ },
+ "plans": [
+ {
+ "is_gift": false,
+ "gift_id": null,
+ "paid_subscription_id": "12422686",
+ "status": "active",
+ "title": "Newsletter Tier",
+ "currency": "USD",
+ "renew_interval": "1 month",
+ "inactive_renew_interval": null,
+ "renewal_price": 0.5,
+ "start_date": "2025-01-13T18:51:55+00:00",
+ "end_date": "2025-02-13T18:51:55+00:00"
+ },
+ {
+ "is_gift": true,
+ "gift_id": 31,
+ "paid_subscription_id": null,
+ "status": "active",
+ "title": "Newsletter Tier 3",
+ "currency": "USD",
+ "renew_interval": "one-time",
+ "inactive_renew_interval": null,
+ "renewal_price": 0,
+ "start_date": "2025-05-08T14:50:28+00:00",
+ "end_date": "2025-06-07T14:50:28+00:00"
+ }
+ ]
+}
diff --git a/Tests/WordPressKitTests/Mock Data/site-subscriber-stats-response.json b/Tests/WordPressKitTests/Mock Data/site-subscriber-stats-response.json
new file mode 100644
index 00000000..fbb84824
--- /dev/null
+++ b/Tests/WordPressKitTests/Mock Data/site-subscriber-stats-response.json
@@ -0,0 +1,6 @@
+{
+ "emails_sent": 1,
+ "unique_opens": 2,
+ "unique_clicks": 3,
+ "blog_registration_date": "2024-12-04 16:00:32"
+}
diff --git a/Tests/WordPressKitTests/Mock Data/site-subscribers-response.json b/Tests/WordPressKitTests/Mock Data/site-subscribers-response.json
new file mode 100644
index 00000000..e6004902
--- /dev/null
+++ b/Tests/WordPressKitTests/Mock Data/site-subscribers-response.json
@@ -0,0 +1,20 @@
+{
+ "total": 1,
+ "pages": 1,
+ "page": 1,
+ "per_page": 100,
+ "subscribers": [
+ {
+ "user_id": 1,
+ "subscription_id": 2,
+ "email_address": "user@example.com",
+ "date_subscribed": "2025-02-28T19:36:36+00:00",
+ "is_email_subscriber": false,
+ "subscription_status": "Not subscribed",
+ "avatar": "https://0.gravatar.com/avatar/example.jpg",
+ "display_name": "Test",
+ "url": "http://example.wordpress.com"
+ }
+ ],
+ "is_owner_subscribed": true
+}
diff --git a/Tests/WordPressKitTests/Tests/ActivityServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/ActivityServiceRemoteTests.swift
index 328a64e8..7661bc5f 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 bbcac5fe..e5fcf5f9 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 e81a8afc..39f37708 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 3aa2d3b1..844bd900 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 1cdb540e..5d9692bb 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 8e6e60c0..401fe472 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 5e5f0289..0f3870f2 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/JSONLoader.swift b/Tests/WordPressKitTests/Tests/JSONLoader.swift
index e6bda2b9..8fd3b73b 100644
--- a/Tests/WordPressKitTests/Tests/JSONLoader.swift
+++ b/Tests/WordPressKitTests/Tests/JSONLoader.swift
@@ -12,7 +12,7 @@ import Foundation
*/
@objc open func loadFile(_ name: String, type: String) -> JSONDictionary? {
- let path = Bundle(for: Swift.type(of: self)).path(forResource: name, ofType: type)
+ let path = JSONLoader.bundle.path(forResource: name, ofType: type)
if let unwrappedPath = path {
return loadFile(unwrappedPath)
@@ -47,4 +47,15 @@ import Foundation
return nil
}
}
+
+ public static func data(named name: String, ext: String = "json") throws -> Data {
+ guard let url = Bundle(for: JSONLoader.self).url(forResource: name, withExtension: ext) else {
+ throw URLError(.badURL)
+ }
+ return try Data(contentsOf: url)
+ }
+
+ private static var bundle: Bundle {
+ Bundle(for: JSONLoader.self)
+ }
}
diff --git a/Tests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift b/Tests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift
index 85d7d87a..da2091a9 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/MockWordPressComRestApi.swift b/Tests/WordPressKitTests/Tests/MockWordPressComRestApi.swift
index 9b552f7f..8edd0079 100644
--- a/Tests/WordPressKitTests/Tests/MockWordPressComRestApi.swift
+++ b/Tests/WordPressKitTests/Tests/MockWordPressComRestApi.swift
@@ -49,7 +49,7 @@ class MockWordPressComRestApi: WordPressComRestApi {
override func perform(
_ method: HTTPRequestBuilder.Method,
URLString: String,
- parameters: [String: AnyObject]? = nil,
+ parameters: [String: Any]? = nil,
fulfilling progress: Progress? = nil,
jsonDecoder: JSONDecoder? = nil,
type: T.Type = T.self
diff --git a/Tests/WordPressKitTests/Tests/Models/Stats/V2/Insights/StatsDotComFollowersInsightTests.swift b/Tests/WordPressKitTests/Tests/Models/Stats/V2/Insights/StatsDotComFollowersInsightTests.swift
index fe9a1847..440a9ac0 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/PeopleServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/PeopleServiceRemoteTests.swift
index 9ac254eb..755a2068 100644
--- a/Tests/WordPressKitTests/Tests/PeopleServiceRemoteTests.swift
+++ b/Tests/WordPressKitTests/Tests/PeopleServiceRemoteTests.swift
@@ -794,5 +794,4 @@ class PeopleServiceRemoteTests: RemoteTestCase, RESTTestable {
waitForExpectations(timeout: timeout, handler: nil)
}
-
}
diff --git a/Tests/WordPressKitTests/Tests/PluginDirectoryTests.swift b/Tests/WordPressKitTests/Tests/PluginDirectoryTests.swift
index 88671778..13ad32ab 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 3d96bf21..90fa182a 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 e48c7371..89ada39b 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 ca9e8027..17379702 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 50a3c751..565020a1 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 ()
@@ -208,7 +209,7 @@ - (void)testFeaturedImageFromDictionary {
uri, uri, uri]
};
imagePath = [remoteReaderPost featuredImageFromPostDictionary:dict];
- XCTAssertTrue([uri isEqualToString:imagePath], @"Failed to retrieve the image uri from the post content.");
+ XCTAssertTrue(imagePath.length == 0, @"No image should be retrieved from the content");
dict = [self editorialDictionaryWithKey:@"image" value:uri];
imagePath = [remoteReaderPost featuredImageFromPostDictionary:dict];
@@ -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 a55165b7..954f07c8 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 5d278b7e..3258f87d 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/SubscribersServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/SubscribersServiceRemoteTests.swift
new file mode 100644
index 00000000..5e6f4bbd
--- /dev/null
+++ b/Tests/WordPressKitTests/Tests/SubscribersServiceRemoteTests.swift
@@ -0,0 +1,58 @@
+import Foundation
+import XCTest
+@testable import WordPressKit
+
+class SubscribersServiceRemoteTests: RemoteTestCase, RESTTestable {
+ func testDecodeSubscribersResponse() throws {
+ let data = try JSONLoader.data(named: "site-subscribers-response")
+
+ let decoder = JSONDecoder()
+ decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats
+
+ let response = try decoder.decode(SubscribersServiceRemote.GetSubscribersResponse.self, from: data)
+
+ XCTAssertEqual(response.total, 1)
+
+ let subscriber = try XCTUnwrap(response.subscribers.first)
+ XCTAssertEqual(subscriber.dotComUserID, 1)
+ }
+
+ func testDecoderSubscriberDetailsResponse() throws {
+ let data = try JSONLoader.data(named: "site-subscriber-get-details-response")
+
+ let decoder = JSONDecoder()
+ decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats
+
+ let response = try decoder.decode(SubscribersServiceRemote.GetSubscriberDetailsResponse.self, from: data)
+
+ XCTAssertEqual(response.country?.code, "US")
+ XCTAssertEqual(response.country?.name, "United States")
+
+ let plan = try XCTUnwrap(response.plans?.first)
+ XCTAssertFalse(plan.isGift)
+ XCTAssertEqual(plan.status, "active")
+ XCTAssertEqual(plan.paidSubscriptionId, "12422686")
+ }
+
+ func testDecoderSubscriberDetailsInvalidCountry() throws {
+ let data = try JSONLoader.data(named: "site-subscriber-get-details-response-invalid-country")
+
+ let decoder = JSONDecoder()
+ decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats
+
+ let response = try decoder.decode(SubscribersServiceRemote.GetSubscriberDetailsResponse.self, from: data)
+
+ XCTAssertNil(response.country)
+ }
+
+ func testDecoderSubscriberStatsResponse() throws {
+ let data = try JSONLoader.data(named: "site-subscriber-stats-response")
+
+ let decoder = JSONDecoder.apiDecoder
+ let response = try decoder.decode(SubscribersServiceRemote.GetSubscriberStatsResponse.self, from: data)
+
+ XCTAssertEqual(response.emailsSent, 1)
+ XCTAssertEqual(response.uniqueOpens, 2)
+ XCTAssertEqual(response.uniqueClicks, 3)
+ }
+}
diff --git a/Tests/WordPressKitTests/Tests/ThemeServiceRemoteTests.m b/Tests/WordPressKitTests/Tests/ThemeServiceRemoteTests.m
index e0a6ac7f..a2eda517 100644
--- a/Tests/WordPressKitTests/Tests/ThemeServiceRemoteTests.m
+++ b/Tests/WordPressKitTests/Tests/ThemeServiceRemoteTests.m
@@ -196,7 +196,7 @@ - (void)testThatGetThemesWorks
XCTAssertNoThrow(service = [[ThemeServiceRemote alloc] initWithWordPressComRestApi:api]);
NSString *url = [service pathForEndpoint:@"themes"
- withVersion:WordPressComRESTAPIVersion_1_2];
+ withVersion:WordPressComRESTAPIVersion_2_0];
ThemeServiceRemoteThemesRequestSuccessBlock successBlock = ^void (NSArray *themes, BOOL hasMore, NSInteger totalThemeCount) {
NSCAssert([themes count] == expectedThemes, @"Expected %ld themes to be returned", expectedThemes);
@@ -224,9 +224,10 @@ - (void)testThatGetThemesWorks
}];
XCTAssertNoThrow([service getWPThemesPage:1
- freeOnly:NO
- success:successBlock
- failure:nil]);
+ search:nil
+ freeOnly:NO
+ success:successBlock
+ failure:nil]);
}
- (void)testThatGetThemesForBlogIdWorks
@@ -268,9 +269,9 @@ - (void)testThatGetThemesForBlogIdWorks
}];
XCTAssertNoThrow([service getThemesForBlogId:blogId
- page:1
- success:successBlock
- failure:nil]);
+ page:1
+ success:successBlock
+ failure:nil]);
}
- (void)testThatGetThemesForBlogIdThrowsExceptionWithoutBlogId
diff --git a/Tests/WordPressKitTests/Tests/TransactionsServiceRemoteTests.swift b/Tests/WordPressKitTests/Tests/TransactionsServiceRemoteTests.swift
index 1f98ea0c..8926780c 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 602d8957..f9e46c63 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 60c4553b..a884af94 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 0981ed16..8aee74b8 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 ebe0b154..8f2096ad 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 37d1f508..72f3781b 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 b36ad1c1..00000000
--- a/WordPressKit.podspec
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-Pod::Spec.new do |s|
- s.name = 'WordPressKit'
- s.version = '17.3.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 a83083cf..c6177f16 100644
--- a/WordPressKit.xcodeproj/project.pbxproj
+++ b/WordPressKit.xcodeproj/project.pbxproj
@@ -20,11 +20,43 @@
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 */; };
+ 0C8069A72DC03E85008DFC2F /* site-subscribers-response.json in Resources */ = {isa = PBXBuildFile; fileRef = 0C8069A62DC03E85008DFC2F /* site-subscribers-response.json */; };
+ 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 */; };
+ 0CD5D3DD2DCE4F5500B4E679 /* StringCodingKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD5D3DC2DCE4F5500B4E679 /* StringCodingKey.swift */; };
+ 0CD5D3DF2DCE50D900B4E679 /* site-subscriber-get-details-response-invalid-country.json in Resources */ = {isa = PBXBuildFile; fileRef = 0CD5D3DE2DCE50D900B4E679 /* site-subscriber-get-details-response-invalid-country.json */; };
+ 0CE311BD2DCBB52C003AADB3 /* SubscribersServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE311BC2DCBB52C003AADB3 /* SubscribersServiceRemote.swift */; };
+ 0CE311BF2DCBB588003AADB3 /* SubscribersServiceRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE311BE2DCBB588003AADB3 /* SubscribersServiceRemoteTests.swift */; };
+ 0CE311C52DCBB970003AADB3 /* site-subscriber-stats-response.json in Resources */ = {isa = PBXBuildFile; fileRef = 0CE311C42DCBB970003AADB3 /* site-subscriber-stats-response.json */; };
+ 0CE311C72DCBBA01003AADB3 /* site-subscriber-get-details-response.json in Resources */ = {isa = PBXBuildFile; fileRef = 0CE311C62DCBBA01003AADB3 /* site-subscriber-get-details-response.json */; };
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 +77,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 +590,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 +762,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 +805,39 @@
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 = ""; };
+ 0C8069A62DC03E85008DFC2F /* site-subscribers-response.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-subscribers-response.json"; 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 = ""; };
+ 0CD5D3DC2DCE4F5500B4E679 /* StringCodingKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCodingKey.swift; sourceTree = ""; };
+ 0CD5D3DE2DCE50D900B4E679 /* site-subscriber-get-details-response-invalid-country.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-subscriber-get-details-response-invalid-country.json"; sourceTree = ""; };
+ 0CE311BC2DCBB52C003AADB3 /* SubscribersServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribersServiceRemote.swift; sourceTree = ""; };
+ 0CE311BE2DCBB588003AADB3 /* SubscribersServiceRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribersServiceRemoteTests.swift; sourceTree = ""; };
+ 0CE311C42DCBB970003AADB3 /* site-subscriber-stats-response.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-subscriber-stats-response.json"; sourceTree = ""; };
+ 0CE311C62DCBBA01003AADB3 /* site-subscriber-get-details-response.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-subscriber-get-details-response.json"; 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 +862,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 +1025,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 +1398,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 +1418,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 +1435,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 +1457,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 +1468,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 +1544,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 +1568,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 +1577,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 +1619,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 +1665,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 = (
@@ -1896,6 +1962,7 @@
74A44DC91F13C533006CD8F4 /* NotificationSyncServiceRemote.swift */,
4625B96B253A357500C04AAD /* PageLayoutServiceRemote.swift */,
74D67F051F1528470010C5ED /* PeopleServiceRemote.swift */,
+ 0CE311BC2DCBB52C003AADB3 /* SubscribersServiceRemote.swift */,
3F3195AB266FF91100397EE7 /* Plans */,
C79719682679007B0072F984 /* Plugin Management */,
E1BD95141FD5A2B800CD5CE3 /* PluginDirectoryServiceRemote.swift */,
@@ -1973,6 +2040,8 @@
465F88A1263B325C00F4C950 /* ChecksumUtil.swift */,
3F3195AC266FF94B00397EE7 /* ZendeskMetadata.swift */,
4AE278432B2FAF6200E4D9B1 /* HTTPProtocolHelpers.swift */,
+ 0CCD4C5B2C41700B00B53F9A /* UIDevice+Extensions.swift */,
+ 0CD5D3DC2DCE4F5500B4E679 /* StringCodingKey.swift */,
);
path = Utility;
sourceTree = "";
@@ -2023,6 +2092,7 @@
74A44DD31F13C6D8006CD8F4 /* PushAuthenticationServiceRemoteTests.swift */,
74FC6F3A1F191BB400112505 /* NotificationSyncServiceRemoteTests.swift */,
74D67F091F15C24C0010C5ED /* PeopleServiceRemoteTests.swift */,
+ 0CE311BE2DCBB588003AADB3 /* SubscribersServiceRemoteTests.swift */,
7433BC031EFC4556002D9E92 /* PlanServiceRemoteTests.swift */,
E13EE14B1F332C4400C15787 /* PluginServiceRemoteTests.swift */,
E1E89C691FD6BDB1006E7A33 /* PluginDirectoryTests.swift */,
@@ -2091,6 +2161,7 @@
3FE2E9762BC395C2002CA2E1 /* CoreAPI */,
3FE2E9532BB3F4ED002CA2E1 /* APIInterface */,
3FE2E9462BB12020002CA2E1 /* WordPressKit */,
+ 0C938A042C4166AC009BA7B2 /* WordPressShared */,
);
path = Sources;
sourceTree = "";
@@ -2264,15 +2335,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 = "";
};
@@ -2489,6 +2558,10 @@
9AEAA772215E71C000876E62 /* site-quick-start-success.json */,
74D67F0C1F15C2D70010C5ED /* site-roles-auth-failure.json */,
74D67F0D1F15C2D70010C5ED /* site-roles-bad-json-failure.json */,
+ 0C8069A62DC03E85008DFC2F /* site-subscribers-response.json */,
+ 0CE311C42DCBB970003AADB3 /* site-subscriber-stats-response.json */,
+ 0CE311C62DCBBA01003AADB3 /* site-subscriber-get-details-response.json */,
+ 0CD5D3DE2DCE50D900B4E679 /* site-subscriber-get-details-response-invalid-country.json */,
74D67F0E1F15C2D70010C5ED /* site-roles-success.json */,
D8DB404121EF22B500B8238E /* site-segments-multiple.json */,
D813437721F6D7DC0060D99A /* site-segments-single.json */,
@@ -2614,21 +2687,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 +2787,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 +2823,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 +2831,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 +2839,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 +2854,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 +2877,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 +2889,9 @@
name = WordPressKitTests;
packageProductDependencies = (
3FB8642B2888089F003A86BE /* BuildkiteTestCollector */,
+ 0C363D412C41B455004E241D /* OCMock */,
+ 0C363D442C41B468004E241D /* OHHTTPStubs */,
+ 0C363D462C41B468004E241D /* OHHTTPStubsSwift */,
);
productName = WordPressKitTests;
productReference = 9368C7841EC5EF1B0092CE8E /* WordPressKitTests.xctest */;
@@ -2862,6 +2930,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 +2951,6 @@
buildActionMask = 2147483647;
files = (
FFE247CE20CB1245002DF3A2 /* LICENSE in Resources */,
- FF20AD2220B8471A00082398 /* WordPressKit.podspec in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2980,6 +3051,7 @@
C738CAF528622953001BE107 /* qrlogin-validate-expired-401.json in Resources */,
937250EC267A15060086075F /* stats-referrer-mark-as-spam.json in Resources */,
F3FF8A23279C954100E5C90F /* site-email-followers-get-success.json in Resources */,
+ 0C8069A72DC03E85008DFC2F /* site-subscribers-response.json in Resources */,
465F889A263B09BF00F4C950 /* wp-block-editor-v1-settings-success-ThemeJSON.json in Resources */,
BA9A7F7F24C6895600925E81 /* plugin-directory-jetpack-beta.json in Resources */,
E6B0461425E5B6F500DF6F4F /* sites-invites-links-generate.json in Resources */,
@@ -3016,6 +3088,7 @@
74C473CD1EF336BD009918F2 /* site-active-purchases-bad-json-failure.json in Resources */,
436D5645211B801100CEAA33 /* validate-domain-contact-information-response-success.json in Resources */,
FE5096652A309DEE00DDD071 /* jetpack-social-with-publicize.json in Resources */,
+ 0CD5D3DF2DCE50D900B4E679 /* site-subscriber-get-details-response-invalid-country.json in Resources */,
74D67F351F15C3740010C5ED /* site-users-delete-not-member-failure.json in Resources */,
E1E89C681FD6B2E9006E7A33 /* plugin-directory-jetpack.json in Resources */,
74D67F201F15C3240010C5ED /* people-validate-invitation-failure.json in Resources */,
@@ -3064,6 +3137,7 @@
E1787DB0200E564B004CB3AF /* timezones.json in Resources */,
93BD275E1EE73442002BB00B /* me-bad-json-failure.json in Resources */,
FFE247AF20C891E6002DF3A2 /* WordPressComOAuthWrongPasswordFail.json in Resources */,
+ 0CE311C52DCBB970003AADB3 /* site-subscriber-stats-response.json in Resources */,
F194E1252417EE7E00874408 /* atomic-get-auth-cookie-success.json in Resources */,
731BA83A21DED358000FDFCD /* site-creation-success.json in Resources */,
FEFFD99726C158F400F34231 /* share-app-content-success.json in Resources */,
@@ -3090,6 +3164,7 @@
B04D8C052BB7895A002717A2 /* stats-insight-followers.json in Resources */,
74D67F3B1F15C3740010C5ED /* site-viewers-delete-success.json in Resources */,
93BD275F1EE73442002BB00B /* me-sites-auth-failure.json in Resources */,
+ 0CE311C72DCBBA01003AADB3 /* site-subscriber-get-details-response.json in Resources */,
74585BA11F0D6F5300E7E667 /* domain-service-empty.json in Resources */,
74C473C51EF33242009918F2 /* site-active-purchases-two-active-success.json in Resources */,
4A3239682B74319400EFD2A8 /* self-hosted-plugins-install.json in Resources */,
@@ -3210,25 +3285,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 +3304,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 +3342,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 +3351,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 +3374,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 +3393,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 +3405,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 +3438,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 +3463,8 @@
93C674F21EE8351E00BFAF05 /* NSMutableDictionary+Helpers.m in Sources */,
4624222D2548BA0F002B8A12 /* RemoteSiteDesign.swift in Sources */,
74D67F061F1528470010C5ED /* PeopleServiceRemote.swift in Sources */,
+ 0C938A2B2C416DE0009BA7B2 /* DisplayableImageHelper.m in Sources */,
+ 0CE311BD2DCBB52C003AADB3 /* SubscribersServiceRemote.swift in Sources */,
98DC787522BAEBF200267279 /* StatsAllAnnualInsight.swift in Sources */,
740B23C31F17EE8000067A2A /* RemotePostCategory.m in Sources */,
8B2F4BF124ACE3C30056C08A /* RemoteReaderInterest.swift in Sources */,
@@ -3460,6 +3478,7 @@
C7A09A52284104DB003096ED /* QRLoginServiceRemote.swift in Sources */,
4A68E3DD294070A7004AC3DC /* RemoteReaderSite.swift in Sources */,
40AB1ADA200FED25009B533D /* PluginDirectoryFeedPage.swift in Sources */,
+ 0CD5D3DD2DCE4F5500B4E679 /* StringCodingKey.swift in Sources */,
436D56352118D85800CEAA33 /* WPCountry.swift in Sources */,
74A44DCB1F13C533006CD8F4 /* NotificationSettingsServiceRemote.swift in Sources */,
FAD1344525908F5F00A8FEB1 /* JetpackBackupServiceRemote.swift in Sources */,
@@ -3469,6 +3488,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 +3498,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 +3509,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 +3525,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 */,
@@ -3643,6 +3668,7 @@
4A1DEF46293051C600322608 /* LoggingTests.m in Sources */,
930999521F1658F800F006A1 /* ThemeServiceRemoteTests.m in Sources */,
8BE67ED324AD05D3004DB4C9 /* Decodable+DictionaryTests.swift in Sources */,
+ 0CE311BF2DCBB588003AADB3 /* SubscribersServiceRemoteTests.swift in Sources */,
FEE48EF62A4B3602008A48E0 /* BlogServiceRemote+ActiveFeaturesTests.swift in Sources */,
74B335DA1F06F3D60053A184 /* WordPressComRestApiTests.swift in Sources */,
FA87FE0724EB39C4003FBEE3 /* ReaderPostServiceRemote+SubscriptionTests.swift in Sources */,
@@ -3817,7 +3843,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 +3875,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 +3906,6 @@
};
9368C7931EC5EF1B0092CE8E /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 264F5C834541BBF2018F4964 /* Pods-WordPressKitTests.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}";
CLANG_ENABLE_MODULES = YES;
@@ -3907,7 +3930,6 @@
};
9368C7941EC5EF1B0092CE8E /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = C5953994B3865AF409BA4210 /* Pods-WordPressKitTests.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}";
CLANG_ENABLE_MODULES = YES;
@@ -3991,7 +4013,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 +4044,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 +4127,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 +4158,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 +4218,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 +4261,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 40656752..919434a6 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 00000000..2de2a748
--- /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 0cf274e7..00000000
--- 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
-}