Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit b7b4ad2

Browse files
authored
Merge pull request #43 from wordpress-mobile/feature/custom-domains
Support for custom domains.
2 parents 90ce712 + 4e5eeff commit b7b4ad2

24 files changed

+925
-122
lines changed

Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ PODS:
1010
- FormatterKit/TimeIntervalFormatter (1.8.2):
1111
- FormatterKit/Resources
1212
- NSObject-SafeExpectations (0.0.3)
13-
- OCMock (3.4.2)
13+
- OCMock (3.4.3)
1414
- OHHTTPStubs (6.1.0):
1515
- OHHTTPStubs/Default (= 6.1.0)
1616
- OHHTTPStubs/Core (6.1.0)
@@ -27,7 +27,7 @@ PODS:
2727
- OHHTTPStubs/Swift (6.1.0):
2828
- OHHTTPStubs/Default
2929
- UIDeviceIdentifier (0.5.0)
30-
- WordPressKit (1.4.4):
30+
- WordPressKit (1.4.5-beta.1):
3131
- Alamofire (~> 4.7.3)
3232
- CocoaLumberjack (= 3.4.2)
3333
- NSObject-SafeExpectations (= 0.0.3)
@@ -67,10 +67,10 @@ SPEC CHECKSUMS:
6767
CocoaLumberjack: db7cc9e464771f12054c22ff6947c5a58d43a0fd
6868
FormatterKit: 4b8f29acc9b872d5d12a63efb560661e8f2e1b98
6969
NSObject-SafeExpectations: b989b68a8a9b7b9f2b264a8b52ba9d7aab8f3129
70-
OCMock: ebe9ee1dca7fbed0ff9193ac0b3e2d8862ea56f6
70+
OCMock: 43565190abc78977ad44a61c0d20d7f0784d35ab
7171
OHHTTPStubs: 1e21c7d2c084b8153fc53d48400d8919d2d432d0
7272
UIDeviceIdentifier: a959a6d4f51036b4180dd31fb26483a820f1cc46
73-
WordPressKit: 04d8efc0e7b65e467cb848f199721408bcb68f15
73+
WordPressKit: 6aa14988654e00eb678771b696727944f71cc896
7474
WordPressShared: f55be10963c8f6dbbc8e896450805ba1dd5353f7
7575
wpxmlrpc: bfc572f62ce7ee897f6f38b098d2ba08732ecef4
7676

WordPressKit.podspec

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "WordPressKit"
3-
s.version = "1.4.4"
4-
3+
s.version = "1.4.5-beta.1"
54
s.summary = "WordPressKit offers a clean and simple WordPress.com and WordPress.org API."
65

76
s.description = <<-DESC

WordPressKit.xcodeproj/project.pbxproj

Lines changed: 76 additions & 0 deletions
Large diffs are not rendered by default.

WordPressKit/Country.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Foundation
2+
3+
@objc public class Country: NSObject, Codable {
4+
public var code: String?
5+
public var name: String?
6+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Foundation
2+
3+
public struct ValidateDomainContactInformationResponse: Codable {
4+
public struct Messages: Codable {
5+
public var phone: [String]?
6+
public var email: [String]?
7+
public var postalCode: [String]?
8+
public var countryCode: [String]?
9+
public var city: [String]?
10+
public var address1: [String]?
11+
public var firstName: [String]?
12+
public var lastName: [String]?
13+
public var state: [String]?
14+
}
15+
16+
public var success: Bool = false
17+
public var messages: Messages?
18+
19+
public init() {
20+
}
21+
}
22+
23+
public struct DomainContactInformation: Codable {
24+
public var phone: String?
25+
public var email: String?
26+
public var postalCode: String?
27+
public var countryCode: String?
28+
public var city: String?
29+
public var address1: String?
30+
public var firstName: String?
31+
public var lastName: String?
32+
public var fax: String?
33+
public var state: String?
34+
public var organization: String?
35+
36+
public init() {
37+
}
38+
}

WordPressKit/DomainsServiceRemote.swift

Lines changed: 112 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import Foundation
22
import CocoaLumberjack
33

4-
struct DomainSuggestion {
5-
let name: String
6-
let isFree: Bool
7-
8-
init(json: [String: AnyObject]) throws {
9-
// name
10-
guard let domain_name = json["domain_name"] as? String else {
11-
throw DomainsServiceRemote.ResponseError.decodingFailed
4+
public struct DomainSuggestion: Codable {
5+
public let domainName: String
6+
public let productID: Int?
7+
public let supportsPrivacy: Bool?
8+
9+
public init(json: [String: AnyObject]) throws {
10+
guard let domain = json["domain_name"] as? String else {
11+
throw DomainsServiceRemote.ResponseError.decodingFailed
1212
}
13-
name = domain_name
1413

15-
// isFree
16-
if let is_free = json["is_free"] as? Int {
17-
isFree = is_free == 1
18-
} else {
19-
isFree = false
20-
}
14+
self.domainName = domain
15+
self.productID = json["product_id"] as? Int ?? nil
16+
self.supportsPrivacy = json["supports_privacy"] as? Bool ?? nil
2117
}
2218
}
2319

@@ -26,6 +22,24 @@ public class DomainsServiceRemote: ServiceRemoteWordPressComREST {
2622
case decodingFailed
2723
}
2824

25+
public enum DomainSuggestionType {
26+
case noWordpressDotCom
27+
case includeWordPressDotCom
28+
case onlyWordPressDotCom
29+
30+
fileprivate func parameters() -> [String: AnyObject] {
31+
switch self {
32+
case .noWordpressDotCom:
33+
return ["include_wordpressdotcom": false as AnyObject]
34+
case .includeWordPressDotCom:
35+
return ["include_wordpressdotcom": true as AnyObject,
36+
"only_wordpressdotcom": false as AnyObject]
37+
case .onlyWordPressDotCom:
38+
return ["only_wordpressdotcom": true as AnyObject]
39+
}
40+
}
41+
}
42+
2943
public func getDomainsForSite(_ siteID: Int, success: @escaping ([RemoteDomain]) -> Void, failure: @escaping (Error) -> Void) {
3044
let endpoint = "sites/\(siteID)/domains"
3145
let path = self.path(forEndpoint: endpoint, withVersion: ._1_1)
@@ -45,108 +59,97 @@ public class DomainsServiceRemote: ServiceRemoteWordPressComREST {
4559
})
4660
}
4761

48-
/* from https://opengrok.a8c.com/source/xref/trunk/public.api/rest/wpcom-json-endpoints/class.wpcom-store-domains-api-endpoints.php
49-
"'description' => 'Get a list of suggested domain names that are available for registration based on a given term or domain name.',
50-
154 // 'group' => 'domains',
51-
155 'group' => '__do_not_document',
52-
156 'stat' => 'domains:suggestions',
53-
157 'force' => 'wpcom',
54-
158 'method' => 'GET',
55-
159 'path' => '/domains/suggestions',
56-
160 'query_parameters' => array(
57-
161 'context' => false,
58-
162 'query' => '(string) Term (e.g "flowers") or domain name (e.g. "flowers.com") to search alternative domain names from',
59-
163 'quantity' => '(int=5) Maximum number of suggestions to return (limited to 36)',
60-
164 'include_wordpressdotcom' => '(bool) Whether or not to include wordpress.com subdomains',
61-
165 'include_dotblogsubdomain'=> '(bool) Whether or not to include .blog subdomains',
62-
166 'vendor' => '(string) Suggestions vendor to use (domainsbot available)',
63-
167 'tlds' => "(array) List of TLDs to restrict the results to, e.g. [ 'com', 'net' ]",
64-
168 'vertical' => '(string) Optional. A vertical code (e.g. a8c.8) to improve domain suggestions',
65-
169 'recommendations_only' => '(bool) Optional. Determines whether exact matches are included in results, or only recommendations for similar domains.',
66-
170 'tld_weight_overrides' => '(array) Optional. List of identifiers which will be used in Domain_Suggestions to override the weights of certain TLDs.',
67-
171 ),
68-
172 'example_request' => 'http://public-api.wordpress.com/rest/v1/domains/suggestions?query=flowers&quantity=5',
69-
173 'response_format' => array( 'List of domain names available for registration' ),
70-
174 'example_response' => '[
71-
175 {
72-
176 "domain_name": "silkflowers.me",
73-
177 "cost": "$25.00"
74-
178 },
75-
179 {
76-
180 "domain_name": "bestflowers.me",
77-
181 "cost": "$25.00"
78-
182 },
79-
183 {
80-
184 "domain_name": "bestflowers.co",
81-
185 "cost": "$25.00"
82-
186 },
83-
187 {
84-
188 "domain_name": "wholesaleflowers.me",
85-
189 "cost": "$25.00"
86-
190 },
87-
191 {
88-
192 "domain_name": "wholesaleflowers.org",
89-
193 "cost": "$18.00"
90-
194 }
91-
195 ]'
92-
/// an actual response
93-
(
94-
{
95-
cost = Free;
96-
"domain_name" = "testsuggest.wordpress.com";
97-
"is_free" = 1;
98-
},
99-
{
100-
cost = "C$29.15";
101-
"domain_name" = "testsuggest.blog";
102-
"product_id" = 76;
103-
"product_slug" = "dotblog_domain";
104-
relevance = 1;
105-
"supports_privacy" = 1;
106-
},
107-
{
108-
cost = "C$24.00";
109-
"domain_name" = "testsuggest.com";
110-
"product_id" = 6;
111-
"product_slug" = "domain_reg";
112-
relevance = 1;
113-
"supports_privacy" = 1;
114-
},
115-
{
116-
cost = "C$24.00";
117-
"domain_name" = "testsuggest.ca";
118-
"product_id" = 83;
119-
"product_slug" = "dotca_domain";
120-
relevance = 1;
121-
"supports_privacy" = 0;
122-
},
123-
{
124-
cost = "C$29.15";
125-
"domain_name" = "demosuggest.blog";
126-
"product_id" = 76;
127-
"product_slug" = "dotblog_domain";
128-
relevance = "0.968";
129-
"supports_privacy" = 1;
62+
@objc public func getStates(for countryCode: String,
63+
success: @escaping ([State]) -> Void,
64+
failure: @escaping (Error) -> Void) {
65+
let endPoint = "domains/supported-states/\(countryCode)"
66+
let servicePath = path(forEndpoint: endPoint, withVersion: ._1_1)
67+
68+
wordPressComRestApi.GET(
69+
servicePath,
70+
parameters: nil,
71+
success: {
72+
response, _ in
73+
do {
74+
guard let json = response as? [AnyObject] else {
75+
throw ResponseError.decodingFailed
76+
}
77+
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
78+
let decodedResult = try JSONDecoder.apiDecoder.decode([State].self, from: data)
79+
success(decodedResult)
80+
} catch {
81+
DDLogError("Error parsing State list for country code (\(error)): \(response)")
82+
failure(error)
83+
}
84+
}, failure: { error, _ in
85+
failure(error)
86+
})
13087
}
131-
)
132-
*/
133-
public func getDomainSuggestions(base query: String, success: @escaping ([String]) -> Void, failure: @escaping (Error) -> Void) {
134-
let endPoint = "domains/suggestions"
88+
89+
public func getDomainContactInformation(success: @escaping (DomainContactInformation) -> Void,
90+
failure: @escaping (Error) -> Void) {
91+
let endPoint = "me/domain-contact-information"
92+
let servicePath = path(forEndpoint: endPoint, withVersion: ._1_1)
93+
94+
wordPressComRestApi.GET(
95+
servicePath,
96+
parameters: nil,
97+
success: { (response, _) in
98+
do {
99+
let data = try JSONSerialization.data(withJSONObject: response, options: .prettyPrinted)
100+
let decodedResult = try JSONDecoder.apiDecoder.decode(DomainContactInformation.self, from: data)
101+
success(decodedResult)
102+
} catch {
103+
DDLogError("Error parsing DomainContactInformation (\(error)): \(response)")
104+
failure(error)
105+
}
106+
}) { (error, _) in
107+
failure(error)
108+
}
109+
}
110+
111+
public func validateDomainContactInformation(contactInformation: [String: String],
112+
domainNames: [String],
113+
success: @escaping (ValidateDomainContactInformationResponse) -> Void,
114+
failure: @escaping (Error) -> Void) {
115+
let endPoint = "me/domain-contact-information/validate"
135116
let servicePath = path(forEndpoint: endPoint, withVersion: ._1_1)
136-
let parameters: [String: AnyObject] = ["query": query as AnyObject,
137-
"include_wordpressdotcom": true as AnyObject,
138-
"only_wordpressdotcom": true as AnyObject]
117+
118+
let parameters: [String: AnyObject] = ["contact_information": contactInformation as AnyObject,
119+
"domain_names": domainNames as AnyObject]
120+
wordPressComRestApi.POST(
121+
servicePath,
122+
parameters: parameters,
123+
success: { response,_ in
124+
do {
125+
let data = try JSONSerialization.data(withJSONObject: response, options: .prettyPrinted)
126+
let decodedResult = try JSONDecoder.apiDecoder.decode(ValidateDomainContactInformationResponse.self, from: data)
127+
success(decodedResult)
128+
} catch {
129+
DDLogError("Error parsing ValidateDomainContactInformationResponse (\(error)): \(response)")
130+
failure(error)
131+
}
132+
}) { (error, response) in
133+
failure(error)
134+
}
135+
}
139136

137+
public func getDomainSuggestions(base query: String,
138+
domainSuggestionType: DomainSuggestionType = .onlyWordPressDotCom,
139+
success: @escaping ([DomainSuggestion]) -> Void,
140+
failure: @escaping (Error) -> Void) {
141+
let endPoint = "domains/suggestions"
142+
let servicePath = path(forEndpoint: endPoint, withVersion: ._1_1)
143+
var parameters: [String: AnyObject] = domainSuggestionType.parameters()
144+
parameters["query"] = query as AnyObject
145+
140146
wordPressComRestApi.GET(servicePath,
141147
parameters: parameters,
142148
success: {
143149
response, _ in
144150
do {
145151
let suggestions = try map(suggestions: response)
146-
let domains = suggestions.map { suggestion -> String in
147-
return suggestion.name
148-
}
149-
success(domains)
152+
success(suggestions)
150153
} catch {
151154
DDLogError("Error parsing domains response (\(error)): \(response)")
152155
failure(error)
@@ -162,6 +165,7 @@ private func map(suggestions response: AnyObject) throws -> [DomainSuggestion] {
162165
guard let jsonSuggestions = response as? [[String: AnyObject]] else {
163166
throw DomainsServiceRemote.ResponseError.decodingFailed
164167
}
168+
165169
var suggestions: [DomainSuggestion] = []
166170
for jsonSuggestion in jsonSuggestions {
167171
do {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Foundation
2+
3+
extension JSONDecoder {
4+
5+
static var apiDecoder: JSONDecoder {
6+
let decoder = JSONDecoder()
7+
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats
8+
decoder.keyDecodingStrategy = .convertFromSnakeCase
9+
return decoder
10+
}
11+
}
12+
13+
extension JSONDecoder.DateDecodingStrategy {
14+
15+
enum DateFormat: String, CaseIterable {
16+
case noTime = "yyyy-mm-dd"
17+
case dateWithTime = "yyyy-MM-dd HH:mm:ss"
18+
case iso8601 = "yyyy-MM-dd'T'HH:mm:ssZ"
19+
20+
var formatter: DateFormatter {
21+
let dateFormatter = DateFormatter()
22+
dateFormatter.dateFormat = rawValue
23+
return dateFormatter
24+
}
25+
}
26+
27+
static var supportMultipleDateFormats: JSONDecoder.DateDecodingStrategy {
28+
return JSONDecoder.DateDecodingStrategy.custom({ (decoder) -> Date in
29+
let container = try decoder.singleValueContainer()
30+
let dateStr = try container.decode(String.self)
31+
32+
var date: Date?
33+
34+
for format in DateFormat.allCases {
35+
date = format.formatter.date(from: dateStr)
36+
if date != nil {
37+
break
38+
}
39+
}
40+
41+
guard let calculatedDate = date else {
42+
throw DecodingError.dataCorruptedError(
43+
in: container,
44+
debugDescription: "Cannot decode date string \(dateStr)"
45+
)
46+
}
47+
48+
return calculatedDate
49+
})
50+
}
51+
}

0 commit comments

Comments
 (0)