Skip to content

Commit f07849f

Browse files
committed
Networking layer changes for media remote endpoints that use WordPress site API.
1 parent 5c0f294 commit f07849f

File tree

9 files changed

+792
-51
lines changed

9 files changed

+792
-51
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,14 @@
5050
02A26F1B2744F5FC008E4EDB /* wc-site-settings-partial.json in Resources */ = {isa = PBXBuildFile; fileRef = 02A26F192744F5FC008E4EDB /* wc-site-settings-partial.json */; };
5151
02A26F1C2744F5FC008E4EDB /* wp-site-settings.json in Resources */ = {isa = PBXBuildFile; fileRef = 02A26F1A2744F5FC008E4EDB /* wp-site-settings.json */; };
5252
02AAD53F250092A400BA1E26 /* product-add-or-delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 02AAD53E250092A300BA1E26 /* product-add-or-delete.json */; };
53+
02AF07EA27492DBC00B2D81E /* WordPressMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AF07E927492DBC00B2D81E /* WordPressMedia.swift */; };
54+
02AF07EC27492FDD00B2D81E /* media-library-from-wordpress-site.json in Resources */ = {isa = PBXBuildFile; fileRef = 02AF07EB27492FDD00B2D81E /* media-library-from-wordpress-site.json */; };
55+
02AF07EE27493AE700B2D81E /* media-upload-to-wordpress-site.json in Resources */ = {isa = PBXBuildFile; fileRef = 02AF07ED27493AE700B2D81E /* media-upload-to-wordpress-site.json */; };
5356
02BA23C922EEF62C009539E7 /* order-stats-v4-wcadmin-deactivated.json in Resources */ = {isa = PBXBuildFile; fileRef = 02BA23C722EEF62C009539E7 /* order-stats-v4-wcadmin-deactivated.json */; };
5457
02BA23CA22EEF62C009539E7 /* order-stats-v4-wcadmin-activated.json in Resources */ = {isa = PBXBuildFile; fileRef = 02BA23C822EEF62C009539E7 /* order-stats-v4-wcadmin-activated.json */; };
5558
02BDB83523EA98C800BCC63E /* String+HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BDB83423EA98C800BCC63E /* String+HTML.swift */; };
5659
02BDB83723EA9C4D00BCC63E /* String+HTMLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BDB83623EA9C4D00BCC63E /* String+HTMLTests.swift */; };
60+
02BE0A7B274B695F001176D2 /* WordPressMediaMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BE0A7A274B695F001176D2 /* WordPressMediaMapper.swift */; };
5761
02C11276274285FF00F4F0B4 /* WooCommerceAvailabilityMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C11275274285FF00F4F0B4 /* WooCommerceAvailabilityMapper.swift */; };
5862
02C112782742862600F4F0B4 /* WordPressSiteSettingsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C112772742862600F4F0B4 /* WordPressSiteSettingsMapper.swift */; };
5963
02C1CEF424C6A02B00703EBA /* ProductVariationMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C1CEF324C6A02B00703EBA /* ProductVariationMapper.swift */; };
@@ -632,10 +636,14 @@
632636
02A26F192744F5FC008E4EDB /* wc-site-settings-partial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "wc-site-settings-partial.json"; sourceTree = "<group>"; };
633637
02A26F1A2744F5FC008E4EDB /* wp-site-settings.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "wp-site-settings.json"; sourceTree = "<group>"; };
634638
02AAD53E250092A300BA1E26 /* product-add-or-delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-add-or-delete.json"; sourceTree = "<group>"; };
639+
02AF07E927492DBC00B2D81E /* WordPressMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressMedia.swift; sourceTree = "<group>"; };
640+
02AF07EB27492FDD00B2D81E /* media-library-from-wordpress-site.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-library-from-wordpress-site.json"; sourceTree = "<group>"; };
641+
02AF07ED27493AE700B2D81E /* media-upload-to-wordpress-site.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-upload-to-wordpress-site.json"; sourceTree = "<group>"; };
635642
02BA23C722EEF62C009539E7 /* order-stats-v4-wcadmin-deactivated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-v4-wcadmin-deactivated.json"; sourceTree = "<group>"; };
636643
02BA23C822EEF62C009539E7 /* order-stats-v4-wcadmin-activated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "order-stats-v4-wcadmin-activated.json"; sourceTree = "<group>"; };
637644
02BDB83423EA98C800BCC63E /* String+HTML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+HTML.swift"; sourceTree = "<group>"; };
638645
02BDB83623EA9C4D00BCC63E /* String+HTMLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+HTMLTests.swift"; sourceTree = "<group>"; };
646+
02BE0A7A274B695F001176D2 /* WordPressMediaMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressMediaMapper.swift; sourceTree = "<group>"; };
639647
02C11275274285FF00F4F0B4 /* WooCommerceAvailabilityMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooCommerceAvailabilityMapper.swift; sourceTree = "<group>"; };
640648
02C112772742862600F4F0B4 /* WordPressSiteSettingsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressSiteSettingsMapper.swift; sourceTree = "<group>"; };
641649
02C1CEF324C6A02B00703EBA /* ProductVariationMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductVariationMapper.swift; sourceTree = "<group>"; };
@@ -1173,6 +1181,7 @@
11731181
children = (
11741182
020D07B723D852BB00FD9580 /* Media.swift */,
11751183
020D07B923D8542000FD9580 /* UploadableMedia.swift */,
1184+
02AF07E927492DBC00B2D81E /* WordPressMedia.swift */,
11761185
);
11771186
path = Media;
11781187
sourceTree = "<group>";
@@ -1634,7 +1643,9 @@
16341643
B505F6D420BEE4E600BB1B69 /* me.json */,
16351644
93D8BBFE226BC1DA00AD2EB3 /* me-settings.json */,
16361645
02F096C12406691100C0C1D5 /* media-library.json */,
1646+
02AF07EB27492FDD00B2D81E /* media-library-from-wordpress-site.json */,
16371647
020D07C123D858BB00FD9580 /* media-upload.json */,
1648+
02AF07ED27493AE700B2D81E /* media-upload-to-wordpress-site.json */,
16381649
B58D10C92114D22E00107ED4 /* new-order-note.json */,
16391650
022902D322E2436400059692 /* no_stats_permission_error.json */,
16401651
B5A24178217F98F600595DEF /* notifications-load-all.json */,
@@ -1854,6 +1865,7 @@
18541865
FE28F6E326842848004465C7 /* UserMapper.swift */,
18551866
077F39D326A58DE700ABEADC /* SystemStatusMapper.swift */,
18561867
02C11275274285FF00F4F0B4 /* WooCommerceAvailabilityMapper.swift */,
1868+
02BE0A7A274B695F001176D2 /* WordPressMediaMapper.swift */,
18571869
02C112772742862600F4F0B4 /* WordPressSiteSettingsMapper.swift */,
18581870
);
18591871
path = Mapper;
@@ -2136,6 +2148,7 @@
21362148
45D685FA23D0C3CF005F87D0 /* product-search-sku.json in Resources */,
21372149
CCF48B2C2628AE160034EA83 /* shipping-label-account-settings.json in Resources */,
21382150
31054734262E36AB00C5C02B /* wcpay-payment-intent-error.json in Resources */,
2151+
02AF07EE27493AE700B2D81E /* media-upload-to-wordpress-site.json in Resources */,
21392152
45ED4F12239E8C57004F1BE3 /* taxes-classes.json in Resources */,
21402153
B5A2417B217F98FC00595DEF /* broken-notifications.json in Resources */,
21412154
3158A4A32729F42500C3CFA8 /* wcpay-account-dev-test.json in Resources */,
@@ -2145,6 +2158,7 @@
21452158
4599FC5C24A6276F0056157A /* product-tags-all.json in Resources */,
21462159
74A7B4BE217A841400E85A8B /* broken-settings-general.json in Resources */,
21472160
026CF624237D839B009563D4 /* product-variations-load-all.json in Resources */,
2161+
02AF07EC27492FDD00B2D81E /* media-library-from-wordpress-site.json in Resources */,
21482162
CC9A253C26442C71005DE56E /* shipping-label-eligibility-success.json in Resources */,
21492163
B5A24179217F98F600595DEF /* notifications-load-all.json in Resources */,
21502164
0282DD91233A120A006A5FDB /* products-search-photo.json in Resources */,
@@ -2439,6 +2453,7 @@
24392453
26B2F74524C5573F0065CCC8 /* LeaderboardListMapper.swift in Sources */,
24402454
24F98C542502E8DD00F49B68 /* Bundle+Woo.swift in Sources */,
24412455
31D27C812602889C002EDB1D /* SitePluginsRemote.swift in Sources */,
2456+
02BE0A7B274B695F001176D2 /* WordPressMediaMapper.swift in Sources */,
24422457
45150A9A268340D2006922EA /* Country.swift in Sources */,
24432458
450106852399A7CB00E24722 /* TaxClass.swift in Sources */,
24442459
CC0786612677B2DA00BA9AC1 /* ShippingLabelPurchaseMapper.swift in Sources */,
@@ -2482,6 +2497,7 @@
24822497
74A1D26F21189EA100931DFA /* SiteVisitStatsRemote.swift in Sources */,
24832498
029BA4F4255D72EC006171FD /* ShippingLabelPrintData.swift in Sources */,
24842499
24F98C562502EA4800F49B68 /* FeatureFlag.swift in Sources */,
2500+
02AF07EA27492DBC00B2D81E /* WordPressMedia.swift in Sources */,
24852501
B557DA1D20979E7D005962F4 /* Order.swift in Sources */,
24862502
74159625224D4045003C21CF /* SiteSettingGroup.swift in Sources */,
24872503
26B2F74D24C696E70065CCC8 /* LeaderboardRowContent.swift in Sources */,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// Mapper: WordPressMedia
2+
///
3+
struct WordPressMediaMapper: Mapper {
4+
/// (Attempts) to convert data into a WordPressMedia.
5+
func map(response: Data) throws -> WordPressMedia {
6+
let decoder = JSONDecoder()
7+
decoder.dateDecodingStrategy = .formatted(Constants.dateFormatterForDecoding)
8+
return try decoder.decode(WordPressMedia.self, from: response)
9+
}
10+
}
11+
12+
/// Mapper: WordPressMedia List
13+
///
14+
struct WordPressMediaListMapper: Mapper {
15+
/// (Attempts) to convert data into a WordPressMedia list.
16+
func map(response: Data) throws -> [WordPressMedia] {
17+
let decoder = JSONDecoder()
18+
decoder.dateDecodingStrategy = .formatted(Constants.dateFormatterForDecoding)
19+
return try decoder.decode([WordPressMedia].self, from: response)
20+
}
21+
}
22+
23+
private enum Constants {
24+
static let dateFormatterForDecoding = DateFormatter.Defaults.dateTimeFormatter
25+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import Codegen
2+
3+
/// Media from WordPress Site API
4+
public struct WordPressMedia: Equatable {
5+
public let mediaID: Int64
6+
public let date: Date
7+
public let slug: String
8+
public let mimeType: String
9+
public let src: String
10+
public let alt: String?
11+
public let details: MediaDetails?
12+
public let title: MediaTitle?
13+
14+
/// Media initializer.
15+
public init(mediaID: Int64,
16+
date: Date,
17+
slug: String,
18+
mimeType: String,
19+
src: String,
20+
alt: String?,
21+
details: MediaDetails?,
22+
title: MediaTitle?) {
23+
self.mediaID = mediaID
24+
self.date = date
25+
self.slug = slug
26+
self.mimeType = mimeType
27+
self.src = src
28+
self.alt = alt
29+
self.details = details
30+
self.title = title
31+
}
32+
}
33+
34+
extension WordPressMedia: Decodable {
35+
/// Decodable Initializer.
36+
public init(from decoder: Decoder) throws {
37+
let container = try decoder.container(keyedBy: CodingKeys.self)
38+
39+
let mediaID = try container.decode(Int64.self, forKey: .mediaID)
40+
let date = try container.decodeIfPresent(Date.self, forKey: .date) ?? Date()
41+
let slug = try container.decodeIfPresent(String.self, forKey: .slug) ?? ""
42+
let mimeType = try container.decodeIfPresent(String.self, forKey: .mimeType) ?? ""
43+
let src = try container.decodeIfPresent(String.self, forKey: .src) ?? ""
44+
let alt = try container.decodeIfPresent(String.self, forKey: .alt)
45+
let details = try container.decodeIfPresent(MediaDetails.self, forKey: .details)
46+
let title = try container.decodeIfPresent(MediaTitle.self, forKey: .title)
47+
48+
self.init(mediaID: mediaID,
49+
date: date,
50+
slug: slug,
51+
mimeType: mimeType,
52+
src: src,
53+
alt: alt,
54+
details: details,
55+
title: title)
56+
}
57+
}
58+
59+
public extension WordPressMedia {
60+
/// Details about a WordPress site media.
61+
struct MediaDetails: Decodable, Equatable {
62+
public let width: Double
63+
public let height: Double
64+
public let fileName: String
65+
public let sizes: [String: MediaSizeDetails]
66+
67+
enum CodingKeys: String, CodingKey {
68+
case width
69+
case height
70+
case fileName = "file"
71+
case sizes
72+
}
73+
}
74+
75+
/// Details about a size of WordPress site media (e.g. `thumbnail`, `medium`, `2048x2048`).
76+
struct MediaSizeDetails: Decodable, Equatable {
77+
public let fileName: String
78+
public let src: String
79+
public let width: Double
80+
public let height: Double
81+
82+
enum CodingKeys: String, CodingKey {
83+
case fileName = "file"
84+
case src = "source_url"
85+
case width
86+
case height
87+
}
88+
}
89+
90+
/// Title of the WordPress site media.
91+
struct MediaTitle: Decodable, Equatable {
92+
/// `GET` media list request's `title` field only contains `rendered`, while `POST` media request includes both `raw` and `rendered`.
93+
let rendered: String
94+
95+
enum CodingKeys: String, CodingKey {
96+
case rendered
97+
}
98+
}
99+
}
100+
101+
private extension WordPressMedia {
102+
enum CodingKeys: String, CodingKey {
103+
case mediaID = "id"
104+
case date = "date_gmt"
105+
case slug
106+
case mimeType = "mime_type"
107+
case src = "source_url"
108+
case alt = "alt_text"
109+
case details = "media_details"
110+
case title
111+
}
112+
}

Networking/Networking/Remote/MediaRemote.swift

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class MediaRemote: Remote {
1616
pageNumber: Int = Default.pageNumber,
1717
pageSize: Int = 25,
1818
context: String = Default.context,
19-
completion: @escaping (_ mediaItems: [Media]?, _ error: Error?) -> Void) {
19+
completion: @escaping (Result<[Media], Error>) -> Void) {
2020
let parameters: [String: Any] = [
2121
ParameterKey.contextKey: context,
2222
ParameterKey.pageSize: pageSize,
@@ -35,6 +35,36 @@ public class MediaRemote: Remote {
3535
enqueue(request, mapper: mapper, completion: completion)
3636
}
3737

38+
/// Loads an array of media from the site's WP Media Library via WordPress site API.
39+
/// API reference: https://developer.wordpress.org/rest-api/reference/media/#list-media
40+
///
41+
/// - Parameters:
42+
/// - siteID: Site for which we'll load the media from.
43+
/// - pageNumber: The index of the page of media data to load from, starting from 1.
44+
/// - pageSize: The number of media items to return.
45+
/// - completion: Closure to be executed upon completion.
46+
public func loadMediaLibraryFromWordPressSite(siteID: Int64,
47+
pageNumber: Int = Default.pageNumber,
48+
pageSize: Int = 25,
49+
context: String = Default.context,
50+
completion: @escaping (Result<[WordPressMedia], Error>) -> Void) {
51+
let parameters: [String: Any] = [
52+
ParameterKey.pageSize: pageSize,
53+
ParameterKey.pageNumber: pageNumber,
54+
ParameterKey.fieldsWordPressSite: Default.wordPressMediaFields,
55+
ParameterKey.mimeType: "image"
56+
]
57+
58+
let path = "sites/\(siteID)/media"
59+
let request = DotcomRequest(wordpressApiVersion: .wpMark2,
60+
method: .get,
61+
path: path,
62+
parameters: parameters)
63+
let mapper = WordPressMediaListMapper()
64+
65+
enqueue(request, mapper: mapper, completion: completion)
66+
}
67+
3868
/// Uploads an array of media in the local file system.
3969
/// API reference: https://developer.wordpress.com/docs/api/1.1/post/sites/%24site/media/new/
4070
///
@@ -46,8 +76,7 @@ public class MediaRemote: Remote {
4676
/// - mediaItems: An array of uploadable media items.
4777
/// - completion: Closure to be executed upon completion.
4878
///
49-
public func uploadMedia(siteID: Int64,
50-
isSiteJetpackCPConnected: Bool,
79+
public func uploadMedia(for siteID: Int64,
5180
productID: Int64,
5281
context: String? = Default.context,
5382
mediaItems: [UploadableMedia],
@@ -80,6 +109,40 @@ public class MediaRemote: Remote {
80109
}
81110
}, completion: completion)
82111
}
112+
113+
/// Uploads an array of media in the local file system to the WordPress site.via WordPress site API
114+
/// API reference: https://developer.wordpress.org/rest-api/reference/media/#create-a-media-item
115+
///
116+
/// - Parameters:
117+
/// - siteID: Site for which we'll upload the media to.
118+
/// - productID: Product for which the media items are first added to.
119+
/// - mediaItems: An array of uploadable media items.
120+
/// - completion: Closure to be executed upon completion.
121+
public func uploadMediaToWordPressSite(siteID: Int64,
122+
productID: Int64,
123+
mediaItems: [UploadableMedia],
124+
completion: @escaping (Result<WordPressMedia, Error>) -> Void) {
125+
let formParameters: [String: String] = [
126+
"post": "\(productID)",
127+
ParameterKey.fieldsWordPressSite: Default.wordPressMediaFields,
128+
]
129+
let path = "sites/\(siteID)/media"
130+
let request = DotcomRequest(wordpressApiVersion: .wpMark2, method: .post, path: path, parameters: nil)
131+
let mapper = WordPressMediaMapper()
132+
133+
enqueueMultipartFormDataUpload(request, mapper: mapper, multipartFormData: { multipartFormData in
134+
formParameters.forEach { (key, value) in
135+
multipartFormData.append(Data(value.utf8), withName: key)
136+
}
137+
138+
mediaItems.forEach { mediaItem in
139+
multipartFormData.append(mediaItem.localURL,
140+
withName: ParameterValue.mediaUploadName,
141+
fileName: mediaItem.filename,
142+
mimeType: mediaItem.mimeType)
143+
}
144+
}, completion: completion)
145+
}
83146
}
84147

85148

@@ -89,13 +152,19 @@ public extension MediaRemote {
89152
enum Default {
90153
public static let context: String = "display"
91154
public static let pageNumber = 1
155+
fileprivate static let wordPressMediaFields = "id,date_gmt,slug,mime_type,source_url,alt_text,media_details,title"
92156
}
93157

94158
private enum ParameterKey {
95159
static let pageNumber: String = "page"
96160
static let pageSize: String = "number"
97161
static let fields: String = "fields"
162+
static let fieldsWordPressSite: String = "_fields"
98163
static let mimeType: String = "mime_type"
99164
static let contextKey: String = "context"
100165
}
166+
167+
private enum ParameterValue {
168+
static let mediaUploadName: String = "file"
169+
}
101170
}

0 commit comments

Comments
 (0)