Skip to content

Commit

Permalink
Merge branch 'release/19.8' into merge/19.7.1-hotfix-into-release-19.8
Browse files Browse the repository at this point in the history
  • Loading branch information
pmusolino authored Aug 6, 2024
2 parents 1ab07be + 6c88236 commit 57b1813
Show file tree
Hide file tree
Showing 180 changed files with 2,457 additions and 1,923 deletions.
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ Closes: #

---
- [ ] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary.
- [ ] This PR includes refactoring; smoke testing of the entire section is needed.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<!--
Contains editorialized release notes. Raw release notes should go into `RELEASE-NOTES.txt`.
-->
## 19.8
Experience seamless updates with our latest release! Now enjoy background updates for your order list and dashboard analytics cards. We've enhanced the dynamic dashboard to display unavailable analytics views when not logged into WPCom. Plus, we've fixed large text support in the Barcode Scanner for better accessibility. Update now for a smoother experience!

## 19.7
Exciting updates in our WooCommerce mobile app! Manage Google Ads campaigns with plugin version 2.7.7 or later. Enjoy consistent order status across various lists. Easily create paid campaigns with our new call to action in Google Campaign analytics. Plus, we've improved Blaze and Product Creation AI features for a smoother experience. Update now!

Expand Down
4 changes: 2 additions & 2 deletions Fakes/Fakes/Networking.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1235,13 +1235,13 @@ extension Networking.Product {
bundleMinSize: .fake(),
bundleMaxSize: .fake(),
bundledItems: .fake(),
password: .fake(),
compositeComponents: .fake(),
subscription: .fake(),
minAllowedQuantity: .fake(),
maxAllowedQuantity: .fake(),
groupOfQuantity: .fake(),
combineVariationQuantities: .fake(),
password: .fake()
combineVariationQuantities: .fake()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import KeychainAccess
import WordPressShared
#endif

struct ApplicationPasswordStorage {
public struct ApplicationPasswordStorage {
/// Stores the application password
///
private let keychain: Keychain

init(keychain: Keychain = Keychain(service: WooConstants.keychainServiceName)) {
public init(keychain: Keychain = Keychain(service: WooConstants.keychainServiceName)) {
self.keychain = keychain
}

/// Returns the saved application password if available
///
var applicationPassword: ApplicationPassword? {
public var applicationPassword: ApplicationPassword? {
guard let password = keychain.password,
let username = keychain.username,
let uuid = keychain.uuid else {
Expand All @@ -29,15 +29,15 @@ struct ApplicationPasswordStorage {
///
/// - Parameter password: `ApplicationPasword` to be saved
///
func saveApplicationPassword(_ password: ApplicationPassword) {
public func saveApplicationPassword(_ password: ApplicationPassword) {
keychain.username = password.wpOrgUsername
keychain.password = password.password.secretValue
keychain.uuid = password.uuid
}

/// Removes the currently saved password from storage
///
func removeApplicationPassword() {
public func removeApplicationPassword() {
// Delete password from keychain
keychain.username = nil
keychain.password = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import WordPressShared
#endif

public struct ApplicationPassword: Decodable {
public struct ApplicationPassword: Codable, Equatable {
/// WordPress org username that the application password belongs to
///
public let wpOrgUsername: String
Expand All @@ -24,7 +24,12 @@ public struct ApplicationPassword: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

guard let wpOrgUsername = decoder.userInfo[.wpOrgUsername] as? String else {
// Search for `wpOrgUsername` either in the decoder or in the user info
let wpOrgUsername: String? = {
return try? container.decodeIfPresent(String.self, forKey: .wpOrgUsername) ?? decoder.userInfo[.wpOrgUsername] as? String
}()

guard let wpOrgUsername else {
throw ApplicationPasswordDecodingError.missingWpOrgUsername
}

Expand All @@ -33,7 +38,15 @@ public struct ApplicationPassword: Decodable {
self.init(wpOrgUsername: wpOrgUsername, password: Secret(password), uuid: uuid)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(wpOrgUsername, forKey: .wpOrgUsername)
try container.encodeIfPresent(password.secretValue, forKey: .password)
try container.encodeIfPresent(uuid, forKey: .uuid)
}

enum CodingKeys: String, CodingKey {
case wpOrgUsername
case password
case uuid
}
Expand All @@ -44,3 +57,6 @@ public struct ApplicationPassword: Decodable {
enum ApplicationPasswordDecodingError: Error {
case missingWpOrgUsername
}

// Add equatable conformance to Secret when possible
extension Secret: Equatable where Self.RawValue: Equatable {}
Original file line number Diff line number Diff line change
Expand Up @@ -1928,6 +1928,7 @@ extension Networking.Product {
bundleMinSize: NullableCopiableProp<Decimal> = .copy,
bundleMaxSize: NullableCopiableProp<Decimal> = .copy,
bundledItems: CopiableProp<[ProductBundleItem]> = .copy,
password: NullableCopiableProp<String> = .copy,
compositeComponents: CopiableProp<[ProductCompositeComponent]> = .copy,
subscription: NullableCopiableProp<ProductSubscription> = .copy,
minAllowedQuantity: NullableCopiableProp<String> = .copy,
Expand Down Expand Up @@ -2004,6 +2005,7 @@ extension Networking.Product {
let bundleMinSize = bundleMinSize ?? self.bundleMinSize
let bundleMaxSize = bundleMaxSize ?? self.bundleMaxSize
let bundledItems = bundledItems ?? self.bundledItems
let password = password ?? self.password
let compositeComponents = compositeComponents ?? self.compositeComponents
let subscription = subscription ?? self.subscription
let minAllowedQuantity = minAllowedQuantity ?? self.minAllowedQuantity
Expand Down Expand Up @@ -2081,13 +2083,13 @@ extension Networking.Product {
bundleMinSize: bundleMinSize,
bundleMaxSize: bundleMaxSize,
bundledItems: bundledItems,
password: password,
compositeComponents: compositeComponents,
subscription: subscription,
minAllowedQuantity: minAllowedQuantity,
maxAllowedQuantity: maxAllowedQuantity,
groupOfQuantity: groupOfQuantity,
combineVariationQuantities: combineVariationQuantities,
password: password
combineVariationQuantities: combineVariationQuantities
)
}
}
Expand Down
4 changes: 2 additions & 2 deletions Networking/Networking/Model/Product/AIProduct.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,12 @@ public extension Product {
bundleMinSize: nil,
bundleMaxSize: nil,
bundledItems: [],
password: nil,
compositeComponents: [],
subscription: nil,
minAllowedQuantity: nil,
maxAllowedQuantity: nil,
groupOfQuantity: nil,
combineVariationQuantities: nil,
password: nil)
combineVariationQuantities: nil)
}
}
27 changes: 17 additions & 10 deletions Networking/Networking/Model/Product/Product.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,13 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable
bundleMinSize: Decimal?,
bundleMaxSize: Decimal?,
bundledItems: [ProductBundleItem],
password: String?,
compositeComponents: [ProductCompositeComponent],
subscription: ProductSubscription?,
minAllowedQuantity: String?,
maxAllowedQuantity: String?,
groupOfQuantity: String?,
combineVariationQuantities: Bool?,
password: String?) {
combineVariationQuantities: Bool?) {
self.siteID = siteID
self.productID = productID
self.name = name
Expand Down Expand Up @@ -331,13 +331,13 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable
self.bundleMinSize = bundleMinSize
self.bundleMaxSize = bundleMaxSize
self.bundledItems = bundledItems
self.password = password
self.compositeComponents = compositeComponents
self.subscription = subscription
self.minAllowedQuantity = minAllowedQuantity.refinedMinMaxQuantityEmptyValue
self.groupOfQuantity = groupOfQuantity.refinedMinMaxQuantityEmptyValue
self.maxAllowedQuantity = maxAllowedQuantity
self.combineVariationQuantities = combineVariationQuantities
self.password = password
}

/// The public initializer for Product.
Expand Down Expand Up @@ -538,6 +538,9 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable
let bundleMaxSize = container.failsafeDecodeIfPresent(decimalForKey: .bundleMaxSize)
let bundledItems = try container.decodeIfPresent([ProductBundleItem].self, forKey: .bundledItems) ?? []

// Password
let password = container.failsafeDecodeIfPresent(stringForKey: .password)

// Composite Product properties
let compositeComponents = try container.decodeIfPresent([ProductCompositeComponent].self, forKey: .compositeComponents) ?? []

Expand All @@ -554,9 +557,6 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable
combineVariationQuantities = combineVariationQuantitiesString == Values.combineVariationQuantitiesTrueValue
}

// Password
let password = container.failsafeDecodeIfPresent(stringForKey: .password)

self.init(siteID: siteID,
productID: productID,
name: name,
Expand Down Expand Up @@ -626,13 +626,13 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable
bundleMinSize: bundleMinSize,
bundleMaxSize: bundleMaxSize,
bundledItems: bundledItems,
password: password,
compositeComponents: compositeComponents,
subscription: subscription,
minAllowedQuantity: minAllowedQuantity,
maxAllowedQuantity: maxAllowedQuantity,
groupOfQuantity: groupOfQuantity,
combineVariationQuantities: combineVariationQuantities,
password: password)
combineVariationQuantities: combineVariationQuantities)
}

public func encode(to encoder: Encoder) throws {
Expand Down Expand Up @@ -759,6 +759,13 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable
}
}

public extension Product {
/// Default product URL {site_url}?post_type=product&p={product_id} works for all sites.
func alternativePermalink(with siteURL: String) -> String {
String(format: "%@?post_type=product&p=%d", siteURL, productID)
}
}

/// Defines all of the Product CodingKeys
///
private extension Product {
Expand Down Expand Up @@ -846,14 +853,14 @@ private extension Product {
case bundleMaxSize = "bundle_max_size"
case bundledItems = "bundled_items"

case password = "post_password"

case compositeComponents = "composite_components"

case minAllowedQuantity = "min_quantity"
case maxAllowedQuantity = "max_quantity"
case groupOfQuantity = "group_of_quantity"
case combineVariations = "combine_variations"

case password = "post_password"
}

enum MetadataKeys {
Expand Down
19 changes: 1 addition & 18 deletions Networking/Networking/Network/AlamofireNetwork.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,7 @@ public class AlamofireNetwork: Network {
///
public required init(credentials: Credentials?, sessionManager: Alamofire.Session? = nil) {
self.requestConverter = RequestConverter(credentials: credentials)

// When `DefaultRequestAuthenticator` receives a `credential.applicationPassword` case it doesn't use the information provided in the credential object.
// Instead, it fetches it from the Keychain, as it is assumes that the application password credentials are already saved.
// This does not work in the watch app, because the keychains are not shared/connected.
// To solve this issue without doing a major refactor on how credentials are stored and used,
// We are providing an specific application password use case only when running this code in the watch.
let applicationPasswordUseCase: ApplicationPasswordUseCase? = {
#if os(watchOS)
if let credentials, case let .applicationPassword(username, password, siteAddress) = credentials {
let appPassword = ApplicationPassword(wpOrgUsername: username, password: .init(password), uuid: UUID().uuidString.lowercased())
return OneTimeApplicationPasswordUseCase(applicationPassword: appPassword, siteAddress: siteAddress)
}
#endif
return nil
}()

self.requestAuthenticator = RequestProcessor(requestAuthenticator: DefaultRequestAuthenticator(credentials: credentials,
applicationPasswordUseCase: applicationPasswordUseCase))
self.requestAuthenticator = RequestProcessor(requestAuthenticator: DefaultRequestAuthenticator(credentials: credentials))
if let sessionManager {
self.alamofireSession = sessionManager
}
Expand Down
3 changes: 3 additions & 0 deletions Networking/NetworkingTests/Mapper/ProductMapperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ final class ProductMapperTests: XCTestCase {
XCTAssertEqual(product.name, "Book the Green Room")
XCTAssertEqual(product.slug, "book-the-green-room")
XCTAssertEqual(product.permalink, "https://example.com/product/book-the-green-room/")
XCTAssertEqual(product.alternativePermalink(with: "https://example.com"),
"https://example.com?post_type=product&p=\(dummyProductID)")

let dateCreated = DateFormatter.Defaults.dateTimeFormatter.date(from: "2019-02-19T17:33:31")
let dateModified = DateFormatter.Defaults.dateTimeFormatter.date(from: "2019-02-19T17:48:01")
Expand Down Expand Up @@ -102,6 +104,7 @@ final class ProductMapperTests: XCTestCase {
XCTAssertEqual(product.menuOrder, 0)
XCTAssertEqual(product.productType, ProductType(rawValue: "booking"))
XCTAssertTrue(product.isSampleItem)
XCTAssertEqual(product.password, "Fortuna Major")
}
}

Expand Down
14 changes: 8 additions & 6 deletions Networking/NetworkingTests/Remote/ProductsRemoteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ final class ProductsRemoteTests: XCTestCase {
bundleStockStatus: nil,
bundleStockQuantity: nil,
bundledItems: [],
password: nil,
compositeComponents: [],
subscription: nil,
minAllowedQuantity: nil,
Expand Down Expand Up @@ -224,6 +225,7 @@ final class ProductsRemoteTests: XCTestCase {
bundleStockStatus: nil,
bundleStockQuantity: nil,
bundledItems: [],
password: nil,
compositeComponents: [],
subscription: nil,
minAllowedQuantity: nil,
Expand Down Expand Up @@ -296,7 +298,7 @@ final class ProductsRemoteTests: XCTestCase {

/// Verifies that loadAllProducts properly relays Networking Layer errors.
///
func testLoadAllProductsProperlyRelaysNetwokingErrors() {
func test_loadAllProducts_properly_relays_netwoking_errors() {
let remote = ProductsRemote(network: network)
let expectation = self.expectation(description: "Load all products returns error")

Expand All @@ -318,7 +320,7 @@ final class ProductsRemoteTests: XCTestCase {

/// Verifies that loadProduct properly parses the `product` sample response.
///
func testLoadSingleProductProperlyReturnsParsedProduct() throws {
func test_loadSingleProduct_properly_returns_parsed_product() throws {
// Given
let remote = ProductsRemote(network: network)
let expectation = self.expectation(description: "Load single product")
Expand All @@ -344,7 +346,7 @@ final class ProductsRemoteTests: XCTestCase {

/// Verifies that loadProduct properly parses the `product-external` sample response.
///
func testLoadSingleExternalProductProperlyReturnsParsedProduct() throws {
func test_loadSingleExternalProduct_properly_returns_parsed_product() throws {
// Given
let remote = ProductsRemote(network: network)
network.simulateResponse(requestUrlSuffix: "products/\(sampleProductID)", filename: "product-external")
Expand All @@ -370,7 +372,7 @@ final class ProductsRemoteTests: XCTestCase {

/// Verifies that loadProduct properly relays any Networking Layer errors.
///
func testLoadSingleProductProperlyRelaysNetwokingErrors() throws {
func test_loadSingleProduct_properly_relays_netwoking_errors() throws {
// Given
let remote = ProductsRemote(network: network)
let expectation = self.expectation(description: "Load single product returns error")
Expand Down Expand Up @@ -522,7 +524,7 @@ final class ProductsRemoteTests: XCTestCase {

/// Verifies that updateProduct properly parses the `product-update` sample response.
///
func testUpdateProductProperlyReturnsParsedProduct() {
func test_updateProduct_properly_returns_parsed_product() {
// Given
let remote = ProductsRemote(network: network)
network.simulateResponse(requestUrlSuffix: "products/\(sampleProductID)", filename: "product-update")
Expand All @@ -548,7 +550,7 @@ final class ProductsRemoteTests: XCTestCase {

/// Verifies that updateProduct properly relays Networking Layer errors.
///
func testUpdateProductProperlyRelaysNetwokingErrors() {
func test_updateProduct_properly_relays_netwoking_errors() {
// Given
let remote = ProductsRemote(network: network)

Expand Down
1 change: 1 addition & 0 deletions Networking/NetworkingTests/Responses/product-update.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"description": "Learn something!",
"short_description": "<blockquote>HEllloo!!!!</blockquote>",
"sku": "94115",
"post_password": "Caput Draconis",
"price": "",
"regular_price": "12.00",
"sale_price": "10.00",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"description": "<p>This is the party room!</p>\n",
"short_description": "[contact-form]\n<p>The green room&#8217;s max capacity is 30 people. Reserving the date / time of your event is free. We can also accommodate large groups, with seating for 85 board game players at a time. If you have a large group, let us know and we&#8217;ll send you our large group rate.</p>\n<p>GROUP RATES</p>\n<p>Reserve your event for up to 30 guests for $100.</p>\n",
"sku": "",
"post_password": "Fortuna Major",
"price": "0",
"regular_price": "",
"sale_price": "",
Expand Down
1 change: 1 addition & 0 deletions Networking/NetworkingTests/Responses/product.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"description": "<p>This is the party room!</p>\n",
"short_description": "[contact-form]\n<p>The green room&#8217;s max capacity is 30 people. Reserving the date / time of your event is free. We can also accommodate large groups, with seating for 85 board game players at a time. If you have a large group, let us know and we&#8217;ll send you our large group rate.</p>\n<p>GROUP RATES</p>\n<p>Reserve your event for up to 30 guests for $100.</p>\n",
"sku": "",
"post_password": "Fortuna Major",
"price": "0",
"regular_price": "",
"sale_price": "",
Expand Down
Loading

0 comments on commit 57b1813

Please sign in to comment.