From fd7d14e36ef8f3cad9331edde083ebef09d20530 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 9 Nov 2022 21:25:12 +0000 Subject: [PATCH 01/89] Make schemas publicly visible for better documentation. --- api/docs/oauth/index.yml | 8 +-- api/docs/v2/storefront/index.yaml | 96 +++++++++++++++---------------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/api/docs/oauth/index.yml b/api/docs/oauth/index.yml index b558874078e..d13546f117c 100644 --- a/api/docs/oauth/index.yml +++ b/api/docs/oauth/index.yml @@ -139,7 +139,7 @@ components: - expires_in - refresh_token - created_at - x-internal: true + x-internal: false CreateTokenBody: type: object x-examples: @@ -152,7 +152,7 @@ components: username: spree@example.com password: spree123 scope: admin - x-internal: true + x-internal: false title: 'Create a new token (grant_type: password)' description: '' properties: @@ -186,7 +186,7 @@ components: example-1: grant_type: refresh_token refresh_token: SqJDIwX00fehqHxS6xmb-kzqAlrYe_0EHgekMexVT8k - x-internal: true + x-internal: false title: 'Create a new token (grant_type: client_credentials)' description: '' properties: @@ -213,7 +213,7 @@ components: example-1: grant_type: refresh_token refresh_token: SqJDIwX00fehqHxS6xmb-kzqAlrYe_0EHgekMexVT8k - x-internal: true + x-internal: false title: 'Refresh an existing token (grant_type: refresh_token)' description: '' properties: diff --git a/api/docs/v2/storefront/index.yaml b/api/docs/v2/storefront/index.yaml index 8c8b04cb547..9414666964e 100644 --- a/api/docs/v2/storefront/index.yaml +++ b/api/docs/v2/storefront/index.yaml @@ -1913,7 +1913,7 @@ components: - id - type - attributes - x-internal: true + x-internal: false AddressPayload: example: firstname: John @@ -1926,7 +1926,7 @@ components: state_name: MD country_iso: US type: object - x-internal: true + x-internal: false title: '' x-examples: {} description: '' @@ -1973,7 +1973,7 @@ components: description: 'The Cart provides a central place to collect information about an order, including line items, adjustments, payments, addresses, and shipments. [Read more in Spree docs](https://dev-docs.spreecommerce.org/internals/orders)' type: object title: Cart - x-internal: true + x-internal: false properties: id: type: string @@ -2181,7 +2181,7 @@ components: - attributes - relationships CartIncludes: - x-internal: true + x-internal: false title: Cart Includes anyOf: - $ref: '#/components/schemas/LineItem' @@ -2197,7 +2197,7 @@ components: type: object title: CMS Page description: 'The CMS Page model contains page data for Standard pages, Feature Pages and Homepages.' - x-internal: true + x-internal: false properties: id: type: string @@ -2250,7 +2250,7 @@ components: - attributes - relationships CmsPageIncludes: - x-internal: true + x-internal: false title: CMS Page Includes allOf: - $ref: '#/components/schemas/CmsSection' @@ -2259,7 +2259,7 @@ components: type: object title: CMS Section description: The CMS Section model represents a single section belonging to a CMS Page. CMS Sections can link to other resources through the `linked_resource`. - x-internal: true + x-internal: false properties: id: type: string @@ -2361,7 +2361,7 @@ components: title: Country description: 'Countries within Spree are used as a container for states. Countries can be zone members, and also link to an address. The difference between one country and another on an address record can determine which tax rates and shipping methods are used for the order.[Read more about Countries in Spree](https://dev-docs.spreecommerce.org/internals/addresses#countries)' type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2407,14 +2407,14 @@ components: - attributes - relationships CountryIncludes: - x-internal: true + x-internal: false title: Country Includes oneOf: - $ref: '#/components/schemas/State' CreditCard: title: Credit Card type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2467,12 +2467,12 @@ components: - attributes - relationships CreditCardIncludes: - x-internal: true + x-internal: false title: Credit Card Includes allOf: - $ref: '#/components/schemas/PaymentMethod' Error: - x-internal: true + x-internal: false title: Error type: object properties: @@ -2482,7 +2482,7 @@ components: type: object description: 'The Icon object contains a url attribute pointing to an Active Storage asset. ' title: Icon - x-internal: true + x-internal: false properties: id: type: string @@ -2526,9 +2526,9 @@ components: - type - attributes title: Image - x-internal: true + x-internal: false ImageStyle: - x-internal: true + x-internal: false title: Image Style type: object properties: @@ -2545,7 +2545,7 @@ components: example: 1080 description: Actual height of image ListLinks: - x-internal: true + x-internal: false type: object title: Pagination Links properties: @@ -2566,7 +2566,7 @@ components: description: URL to the first page of the listing ListMeta: type: object - x-internal: true + x-internal: false title: Pagination Meta properties: count: @@ -2584,7 +2584,7 @@ components: LineItem: title: Line Item type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2693,7 +2693,7 @@ components: Menu: type: object title: Menu - x-internal: true + x-internal: false properties: id: type: string @@ -2734,7 +2734,7 @@ components: MenuItem: title: Menu Item type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2823,7 +2823,7 @@ components: - attributes - relationships MenuIncludes: - x-internal: true + x-internal: false title: Menu Includes anyOf: - $ref: '#/components/schemas/MenuItem' @@ -2834,7 +2834,7 @@ components: OptionType: title: Option Type type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2862,7 +2862,7 @@ components: title: Payment type: object description: '' - x-internal: true + x-internal: false properties: id: type: string @@ -2918,7 +2918,7 @@ components: title: Payment Method description: '' type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2948,7 +2948,7 @@ components: Product: type: object title: Product - x-internal: true + x-internal: false properties: id: type: string @@ -3077,7 +3077,7 @@ components: - attributes - relationships ProductIncludes: - x-internal: true + x-internal: false title: Product Includes anyOf: - $ref: '#/components/schemas/OptionType' @@ -3086,7 +3086,7 @@ components: - $ref: '#/components/schemas/Image' - $ref: '#/components/schemas/Taxon' StoreIncludes: - x-internal: true + x-internal: false title: Store Includes anyOf: - $ref: '#/components/schemas/Country' @@ -3121,7 +3121,7 @@ components: - id - type - attributes - x-internal: true + x-internal: false Promotion: type: object title: Promotion @@ -3154,7 +3154,7 @@ components: - id - type - attributes - x-internal: true + x-internal: false Relation: type: object nullable: true @@ -3166,7 +3166,7 @@ components: required: - id - type - x-internal: true + x-internal: false description: '' State: type: object @@ -3181,12 +3181,12 @@ components: type: string example: New York description: State name - x-internal: true + x-internal: false Store: type: object description: Stores are the center of the Spree ecosystem. Each Spree installation can have multiple Stores. Each Store operates on a different domain or subdomain. title: Store - x-internal: true + x-internal: false properties: id: type: string @@ -3301,7 +3301,7 @@ components: Shipment: type: object title: Shipment - x-internal: true + x-internal: false properties: id: type: string @@ -3371,7 +3371,7 @@ components: - attributes - relationships ShipmentIncludes: - x-internal: true + x-internal: false title: Shipment Includes allOf: - $ref: '#/components/schemas/ShippingRate' @@ -3380,7 +3380,7 @@ components: type: object title: Shipping Rate description: '' - x-internal: true + x-internal: false properties: id: type: string @@ -3434,7 +3434,7 @@ components: StockLocation: title: Stock Location type: object - x-internal: true + x-internal: false properties: id: type: string @@ -3455,7 +3455,7 @@ components: Taxon: title: Taxon type: object - x-internal: true + x-internal: false properties: id: type: string @@ -3586,9 +3586,9 @@ components: - type - attributes title: Taxon Image - x-internal: true + x-internal: false TaxonIncludes: - x-internal: true + x-internal: false title: Taxon Includes anyOf: - $ref: '#/components/schemas/Product' @@ -3597,7 +3597,7 @@ components: Taxonomy: type: object title: Taxonomy - x-internal: true + x-internal: false properties: id: type: string @@ -3622,7 +3622,7 @@ components: type: string format: date-time example: '2020-02-16T07:14:54.617Z' - x-internal: true + x-internal: false title: Time Stamp x-examples: example-1: '2020-02-16T07:14:54.617Z' @@ -3630,7 +3630,7 @@ components: type: object title: User description: ' ' - x-internal: true + x-internal: false properties: id: type: string @@ -3687,7 +3687,7 @@ components: description: 'Variant records track the individual variants of a Product. Variants are of two types: master variants and normal variants.' x-examples: {} type: object - x-internal: true + x-internal: false properties: id: type: string @@ -3769,7 +3769,7 @@ components: - relationships WishedItem: type: object - x-internal: true + x-internal: false properties: id: type: string @@ -3803,7 +3803,7 @@ components: - attributes - relationships WishedItemIncludes: - x-internal: true + x-internal: false title: Wished Item Includes allOf: - $ref: '#/components/schemas/Variant' @@ -3811,7 +3811,7 @@ components: description: '' type: object title: Wishlist - x-internal: true + x-internal: false properties: id: type: string @@ -3849,14 +3849,14 @@ components: - attributes - relationships WishlistIncludes: - x-internal: true + x-internal: false title: Wishlist Includes allOf: - $ref: '#/components/schemas/WishedItem' DigitalLink: title: Digital Link type: object - x-internal: true + x-internal: false properties: id: type: string From c947897a24dba56e543702eb3d4b933c1771283c Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Fri, 25 Nov 2022 12:03:37 +0000 Subject: [PATCH 02/89] Fix extension generation. --- cli/lib/spree_cli/templates/extension/lib/%file_name%.rb.tt | 1 + .../{app/models => lib}/%file_name%/configuration.rb.tt | 0 2 files changed, 1 insertion(+) rename cli/lib/spree_cli/templates/extension/{app/models => lib}/%file_name%/configuration.rb.tt (100%) diff --git a/cli/lib/spree_cli/templates/extension/lib/%file_name%.rb.tt b/cli/lib/spree_cli/templates/extension/lib/%file_name%.rb.tt index 144500999d4..f279f52529a 100644 --- a/cli/lib/spree_cli/templates/extension/lib/%file_name%.rb.tt +++ b/cli/lib/spree_cli/templates/extension/lib/%file_name%.rb.tt @@ -2,3 +2,4 @@ require 'spree_core' require 'spree_extension' require '<%=file_name%>/engine' require '<%=file_name%>/version' +require '<%=file_name%>/configuration' diff --git a/cli/lib/spree_cli/templates/extension/app/models/%file_name%/configuration.rb.tt b/cli/lib/spree_cli/templates/extension/lib/%file_name%/configuration.rb.tt similarity index 100% rename from cli/lib/spree_cli/templates/extension/app/models/%file_name%/configuration.rb.tt rename to cli/lib/spree_cli/templates/extension/lib/%file_name%/configuration.rb.tt From 96ee351573a090546b41bd13a0dbd9972f3b6c1c Mon Sep 17 00:00:00 2001 From: Rafal Cymerys Date: Thu, 1 Dec 2022 17:23:50 +0100 Subject: [PATCH 03/89] Bump version to 4.5.0 --- core/lib/spree/core/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/spree/core/version.rb b/core/lib/spree/core/version.rb index dc85763d7fd..a53337784a0 100644 --- a/core/lib/spree/core/version.rb +++ b/core/lib/spree/core/version.rb @@ -1,5 +1,5 @@ module Spree - VERSION = '4.5.0.alpha'.freeze + VERSION = '4.5.0'.freeze def self.version VERSION From 609ca1efb944507072f696f566382b8d13b6247b Mon Sep 17 00:00:00 2001 From: wjwitek Date: Thu, 15 Dec 2022 11:58:45 +0100 Subject: [PATCH 04/89] change functino getting locale to first look at locale saved in user if there is one --- core/lib/spree/core/controller_helpers/locale.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/lib/spree/core/controller_helpers/locale.rb b/core/lib/spree/core/controller_helpers/locale.rb index 0a5b20a7d23..63251e6eddc 100644 --- a/core/lib/spree/core/controller_helpers/locale.rb +++ b/core/lib/spree/core/controller_helpers/locale.rb @@ -22,7 +22,9 @@ def set_locale end def current_locale - @current_locale ||= if params[:locale].present? && supported_locale?(params[:locale]) + @current_locale ||= if spree_current_user && supported_locale?(spree_current_user.saved_locale) + spree_current_user.saved_locale + elsif params[:locale].present? && supported_locale?(params[:locale]) params[:locale] elsif respond_to?(:config_locale, true) && config_locale.present? config_locale From ef8cc8e6a5a5270c519c5390dd44fe64f6cc2fdc Mon Sep 17 00:00:00 2001 From: wjwitek Date: Thu, 15 Dec 2022 12:00:04 +0100 Subject: [PATCH 05/89] add saved_locale to platform's user endpoint --- api/app/serializers/spree/api/v2/platform/user_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/app/serializers/spree/api/v2/platform/user_serializer.rb b/api/app/serializers/spree/api/v2/platform/user_serializer.rb index ec3f116c500..875bfdd1bb1 100644 --- a/api/app/serializers/spree/api/v2/platform/user_serializer.rb +++ b/api/app/serializers/spree/api/v2/platform/user_serializer.rb @@ -5,7 +5,7 @@ module Platform class UserSerializer < BaseSerializer set_type :user - attributes :email, :first_name, :last_name, :created_at, :updated_at, :public_metadata, :private_metadata + attributes :email, :first_name, :last_name, :created_at, :updated_at, :public_metadata, :private_metadata, :saved_locale attribute :average_order_value do |user, params| price_stats(user.report_values_for(:average_order_value, params[:store])) From 0f660fb3c249c38105ee531bb3b5222411ec6614 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Thu, 15 Dec 2022 12:01:02 +0100 Subject: [PATCH 06/89] add saved_locale to storefront's account endpoint --- .../controllers/spree/api/v2/storefront/account_controller.rb | 2 +- api/app/serializers/spree/v2/storefront/user_serializer.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/app/controllers/spree/api/v2/storefront/account_controller.rb b/api/app/controllers/spree/api/v2/storefront/account_controller.rb index 498c38c9bf4..20624e82a27 100644 --- a/api/app/controllers/spree/api/v2/storefront/account_controller.rb +++ b/api/app/controllers/spree/api/v2/storefront/account_controller.rb @@ -43,7 +43,7 @@ def user_create_params end def user_update_params - params.require(:user).permit(permitted_user_attributes) + params.require(:user).permit(permitted_user_attributes, :saved_locale) end end end diff --git a/api/app/serializers/spree/v2/storefront/user_serializer.rb b/api/app/serializers/spree/v2/storefront/user_serializer.rb index 67cdd3d6a3c..2f8a1223dd4 100644 --- a/api/app/serializers/spree/v2/storefront/user_serializer.rb +++ b/api/app/serializers/spree/v2/storefront/user_serializer.rb @@ -4,7 +4,7 @@ module Storefront class UserSerializer < BaseSerializer set_type :user - attributes :email, :first_name, :last_name, :public_metadata + attributes :email, :first_name, :saved_locale, :last_name, :public_metadata attribute :store_credits do |user| user.total_available_store_credit From f377965e08abfe63ab4b8c457f61c58de4caddde Mon Sep 17 00:00:00 2001 From: wjwitek Date: Thu, 15 Dec 2022 12:57:21 +0100 Subject: [PATCH 07/89] change permitted user attributes --- .../controllers/spree/api/v2/storefront/account_controller.rb | 2 +- core/lib/spree/permitted_attributes.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/app/controllers/spree/api/v2/storefront/account_controller.rb b/api/app/controllers/spree/api/v2/storefront/account_controller.rb index 20624e82a27..498c38c9bf4 100644 --- a/api/app/controllers/spree/api/v2/storefront/account_controller.rb +++ b/api/app/controllers/spree/api/v2/storefront/account_controller.rb @@ -43,7 +43,7 @@ def user_create_params end def user_update_params - params.require(:user).permit(permitted_user_attributes, :saved_locale) + params.require(:user).permit(permitted_user_attributes) end end end diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb index 1dc967504d4..0408aff9bbe 100644 --- a/core/lib/spree/permitted_attributes.rb +++ b/core/lib/spree/permitted_attributes.rb @@ -147,7 +147,7 @@ module PermittedAttributes # TODO: Should probably use something like Spree.user_class.attributes @@user_attributes = [:email, :bill_address_id, :ship_address_id, :password, :first_name, :last_name, - :password_confirmation, { public_metadata: {}, private_metadata: {} }] + :password_confirmation, { public_metadata: {}, private_metadata: {} }, :saved_locale] @@variant_attributes = [ :name, :presentation, :cost_price, :discontinue_on, :lock_version, From 5d5ba24223daca3c2f5a65b576feeac3635adee2 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Thu, 15 Dec 2022 17:02:17 +0100 Subject: [PATCH 08/89] add migration and change saved_locale to selected_locale --- api/app/serializers/spree/api/v2/platform/user_serializer.rb | 2 +- api/app/serializers/spree/v2/storefront/user_serializer.rb | 2 +- .../20221215151408_add_selected_locale_to_spree_users.rb | 5 +++++ core/lib/spree/core/controller_helpers/locale.rb | 4 ++-- core/lib/spree/permitted_attributes.rb | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb diff --git a/api/app/serializers/spree/api/v2/platform/user_serializer.rb b/api/app/serializers/spree/api/v2/platform/user_serializer.rb index 875bfdd1bb1..25a4865dbe1 100644 --- a/api/app/serializers/spree/api/v2/platform/user_serializer.rb +++ b/api/app/serializers/spree/api/v2/platform/user_serializer.rb @@ -5,7 +5,7 @@ module Platform class UserSerializer < BaseSerializer set_type :user - attributes :email, :first_name, :last_name, :created_at, :updated_at, :public_metadata, :private_metadata, :saved_locale + attributes :email, :first_name, :last_name, :created_at, :updated_at, :public_metadata, :private_metadata, :selected_locale attribute :average_order_value do |user, params| price_stats(user.report_values_for(:average_order_value, params[:store])) diff --git a/api/app/serializers/spree/v2/storefront/user_serializer.rb b/api/app/serializers/spree/v2/storefront/user_serializer.rb index 2f8a1223dd4..c3f88d400a3 100644 --- a/api/app/serializers/spree/v2/storefront/user_serializer.rb +++ b/api/app/serializers/spree/v2/storefront/user_serializer.rb @@ -4,7 +4,7 @@ module Storefront class UserSerializer < BaseSerializer set_type :user - attributes :email, :first_name, :saved_locale, :last_name, :public_metadata + attributes :email, :first_name, :selected_locale, :last_name, :public_metadata attribute :store_credits do |user| user.total_available_store_credit diff --git a/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb b/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb new file mode 100644 index 00000000000..c1e7546359d --- /dev/null +++ b/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb @@ -0,0 +1,5 @@ +class AddSelectedLocaleToSpreeUsers < ActiveRecord::Migration[7.0] + def change + add_column :spree_users, :selected_locale, :string + end +end diff --git a/core/lib/spree/core/controller_helpers/locale.rb b/core/lib/spree/core/controller_helpers/locale.rb index 63251e6eddc..3602c63d360 100644 --- a/core/lib/spree/core/controller_helpers/locale.rb +++ b/core/lib/spree/core/controller_helpers/locale.rb @@ -22,8 +22,8 @@ def set_locale end def current_locale - @current_locale ||= if spree_current_user && supported_locale?(spree_current_user.saved_locale) - spree_current_user.saved_locale + @current_locale ||= if spree_current_user && supported_locale?(spree_current_user.selected_locale) + spree_current_user.selected_locale elsif params[:locale].present? && supported_locale?(params[:locale]) params[:locale] elsif respond_to?(:config_locale, true) && config_locale.present? diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb index 0408aff9bbe..330fe762e08 100644 --- a/core/lib/spree/permitted_attributes.rb +++ b/core/lib/spree/permitted_attributes.rb @@ -147,7 +147,7 @@ module PermittedAttributes # TODO: Should probably use something like Spree.user_class.attributes @@user_attributes = [:email, :bill_address_id, :ship_address_id, :password, :first_name, :last_name, - :password_confirmation, { public_metadata: {}, private_metadata: {} }, :saved_locale] + :password_confirmation, { public_metadata: {}, private_metadata: {} }, :selected_locale] @@variant_attributes = [ :name, :presentation, :cost_price, :discontinue_on, :lock_version, From 26f9f3fda22db940a823f5218ebc1a0fc28f42de Mon Sep 17 00:00:00 2001 From: Lauri Kolmonen Date: Fri, 16 Dec 2022 09:58:16 +0200 Subject: [PATCH 09/89] Make sure to use Metadata module from Spree namespace to prevent conflicts --- core/app/models/spree/order.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/app/models/spree/order.rb b/core/app/models/spree/order.rb index d0b81db07a2..05a0edb6f0f 100644 --- a/core/app/models/spree/order.rb +++ b/core/app/models/spree/order.rb @@ -24,7 +24,7 @@ class Order < Spree::Base include NumberAsParam include SingleStoreResource include MemoizedData - include Metadata + include Spree::Metadata if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end From ffab5fc0bc5a8f81f729ecb5a4653dc1ae0e6a1b Mon Sep 17 00:00:00 2001 From: wjwitek Date: Mon, 19 Dec 2022 11:08:36 +0100 Subject: [PATCH 10/89] change using user locale to be configurable --- core/lib/spree/core/configuration.rb | 1 + core/lib/spree/core/controller_helpers/locale.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/lib/spree/core/configuration.rb b/core/lib/spree/core/configuration.rb index 54278ba2833..ec68e89990c 100644 --- a/core/lib/spree/core/configuration.rb +++ b/core/lib/spree/core/configuration.rb @@ -65,6 +65,7 @@ class Configuration < Preferences::Configuration preference :show_raw_product_description, :boolean, deprecated: true preference :tax_using_ship_address, :boolean, default: true preference :track_inventory_levels, :boolean, default: true # Determines whether to track on_hand values for variants / products. + preference :use_user_locale, :boolean, default: true # Store credits configurations preference :non_expiring_credit_types, :array, default: [] diff --git a/core/lib/spree/core/controller_helpers/locale.rb b/core/lib/spree/core/controller_helpers/locale.rb index 3602c63d360..ad9cba682ca 100644 --- a/core/lib/spree/core/controller_helpers/locale.rb +++ b/core/lib/spree/core/controller_helpers/locale.rb @@ -22,7 +22,7 @@ def set_locale end def current_locale - @current_locale ||= if spree_current_user && supported_locale?(spree_current_user.selected_locale) + @current_locale ||= if Spree::Config.use_user_locale && spree_current_user && supported_locale?(spree_current_user.selected_locale) spree_current_user.selected_locale elsif params[:locale].present? && supported_locale?(params[:locale]) params[:locale] From 25f0e4edaf0256b18bf8eed8cf9d04975cab34ea Mon Sep 17 00:00:00 2001 From: Rafal Cymerys Date: Mon, 19 Dec 2022 19:23:04 +0100 Subject: [PATCH 11/89] Bump version to 4.6.0.alpha --- core/lib/spree/core/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/spree/core/version.rb b/core/lib/spree/core/version.rb index a53337784a0..6f7011004df 100644 --- a/core/lib/spree/core/version.rb +++ b/core/lib/spree/core/version.rb @@ -1,5 +1,5 @@ module Spree - VERSION = '4.5.0'.freeze + VERSION = '4.6.0.alpha'.freeze def self.version VERSION From cec549d3f0f8e1fe4d292051d9a25a140ecc4d0c Mon Sep 17 00:00:00 2001 From: wjwitek Date: Tue, 20 Dec 2022 09:51:14 +0100 Subject: [PATCH 12/89] add tests --- .../api/v2/platform/user_serializer_spec.rb | 14 +++- .../v2/storefront/user_serializer_spec.rb | 74 +++++++++++++++++++ .../core/controller_helpers/locale_spec.rb | 70 ++++++++++++------ 3 files changed, 132 insertions(+), 26 deletions(-) create mode 100644 api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb diff --git a/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb b/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb index 3e6cfec75a1..bad669aa95e 100644 --- a/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb +++ b/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' describe Spree::Api::V2::Platform::UserSerializer do - include_context 'API v2 serializers params' - subject { described_class.new(user, params: serializer_params) } + include_context 'API v2 serializers params' + let(:user) { create(:user_with_addresses) } it { expect(subject.serializable_hash).to be_kind_of(Hash) } @@ -21,6 +21,7 @@ last_name: user.last_name, average_order_value: [], lifetime_value: [], + selected_locale: nil, store_credits: [], created_at: user.created_at, updated_at: user.updated_at, @@ -61,6 +62,7 @@ last_name: user.last_name, average_order_value: [{ amount: '110.00', currency: 'USD' }, { amount: '110.00', currency: 'EUR' }], lifetime_value: [{ amount: '110.00', currency: 'USD' }, { amount: '110.00', currency: 'EUR' }], + selected_locale: nil, store_credits: [{ amount: '100.00', currency: 'USD' }, { amount: '90.00', currency: 'EUR' }], created_at: user.created_at, updated_at: user.updated_at, @@ -69,4 +71,12 @@ }) end end + + context 'when user has selected non default locale' do + let(:user) { create(:user_with_addresses, selected_locale: 'fr') } + + it do + expect(subject.serializable_hash[:data][:attributes][:selected_locale]).to eq('fr') + end + end end diff --git a/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb b/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb new file mode 100644 index 00000000000..192b4669521 --- /dev/null +++ b/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe Spree::V2::Storefront::UserSerializer do + subject { described_class.new(user, params: serializer_params) } + + include_context 'API v2 serializers params' + + let(:user) { create(:user_with_addresses) } + + it { expect(subject.serializable_hash).to be_kind_of(Hash) } + + it do + expect(subject.serializable_hash).to eq( + { + data: { + id: user.id.to_s, + type: :user, + attributes: { + completed_orders: 0, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + public_metadata: {}, + selected_locale: nil, + store_credits: 0 + }, + relationships: { + default_billing_address: { + data: { + id: user.bill_address.id.to_s, + type: :address + } + }, + default_shipping_address: { + data: { + id: user.ship_address.id.to_s, + type: :address + } + } + } + } + } + ) + end + + context 'when user has orders' do + before do + create(:completed_order_with_totals, user: user, currency: 'USD') + create(:completed_order_with_totals, user: user, currency: 'EUR') + create(:store_credit, amount: '100', store: store, user: user, currency: 'USD') + create(:store_credit, amount: '90', store: store, user: user, currency: 'EUR') + end + + it do + expect(subject.serializable_hash[:data][:attributes]).to eq({ + completed_orders: 2, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + public_metadata: {}, + selected_locale: nil, + store_credits: 0.1e3 + }) + end + end + + context 'when user has selected non default locale' do + let(:user) { create(:user_with_addresses, selected_locale: 'fr') } + + it do + expect(subject.serializable_hash[:data][:attributes][:selected_locale]).to eq('fr') + end + end +end diff --git a/core/spec/lib/spree/core/controller_helpers/locale_spec.rb b/core/spec/lib/spree/core/controller_helpers/locale_spec.rb index 181d996a77d..297d17b2678 100644 --- a/core/spec/lib/spree/core/controller_helpers/locale_spec.rb +++ b/core/spec/lib/spree/core/controller_helpers/locale_spec.rb @@ -16,57 +16,79 @@ def config_locale describe Spree::Core::ControllerHelpers::Locale, type: :controller do controller(FakesController) {} + let(:user) { build(:user, selected_locale: 'pl') } + + before { allow(controller).to receive(:spree_current_user).and_return(user) } + describe '#current_locale' do - context 'store with locale set' do - let!(:store) { create :store, default: true, default_locale: 'fr', supported_locales: 'fr,de' } + context 'taking locale from user and store with locale set' do + before { allow(Spree::Config).to receive(:use_user_locale).and_return(true) } - it 'returns current store default locale' do - expect(controller.current_locale.to_s).to eq('fr') - end + let!(:store) { create :store, default: true, default_locale: 'fr', supported_locales: 'fr,de,pl' } - it 'return supported locale when passed as param' do - controller.params = { locale: 'de' } - expect(controller.current_locale.to_s).to eq('de') + it 'returns locale set within user' do + expect(controller.current_locale.to_s).to eq('pl') end end - context 'config_locale present' do - controller(FakesControllerWithLocale) {} + context 'not taking locale from user' do + before { allow(Spree::Config).to receive(:use_user_locale).and_return(false) } + + context 'store with locale set' do + let!(:store) { create :store, default: true, default_locale: 'fr', supported_locales: 'fr,de' } - let!(:store) { create :store, default: true, default_locale: 'fr' } + it 'returns current store default locale' do + expect(controller.current_locale.to_s).to eq('fr') + end - it 'returns config_locale if present' do - expect(controller.current_locale.to_s).to eq('en') + it 'return supported locale when passed as param' do + controller.params = { locale: 'de' } + expect(controller.current_locale.to_s).to eq('de') + end end - end - context 'store without locale set' do - let!(:store) { create :store, default: true, default_locale: nil } + context 'config_locale present' do + controller(FakesControllerWithLocale) {} - context 'without I18n.default_locale set' do - it 'fallbacks to english' do + let!(:store) { create :store, default: true, default_locale: 'fr' } + + it 'returns config_locale if present' do expect(controller.current_locale.to_s).to eq('en') end end - context 'with I18n.default_locale set' do - before { I18n.default_locale = :de } + context 'store without locale set' do + let!(:store) { create :store, default: true, default_locale: nil } + + context 'without I18n.default_locale set' do + it 'fallbacks to english' do + expect(controller.current_locale.to_s).to eq('en') + end + end - after { I18n.default_locale = :en } + context 'with I18n.default_locale set' do + before { I18n.default_locale = :de } - it 'fallbacks to the default application locale' do - expect(controller.current_locale.to_s).to eq('de') + after { I18n.default_locale = :en } + + it 'fallbacks to the default application locale' do + expect(controller.current_locale.to_s).to eq('de') + end end end end end describe '#supported_locales' do - let!(:store) { create :store, default: true, default_locale: 'de' } + let!(:store) { create :store, default: true, default_locale: 'de', supported_locales: 'de, pl' } it 'returns supported currencies' do expect(controller.supported_locales.to_s).to include('de') end + + it 'returns supported locales' do + expect(controller.supported_locales.to_s).to include('pl') + end end describe '#locale_param' do From eaef3b38e578784a10bf9d01c36753a4d76e97ba Mon Sep 17 00:00:00 2001 From: wjwitek Date: Tue, 20 Dec 2022 09:57:48 +0100 Subject: [PATCH 13/89] reduce complexity --- .../spree/core/controller_helpers/locale.rb | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/core/lib/spree/core/controller_helpers/locale.rb b/core/lib/spree/core/controller_helpers/locale.rb index ad9cba682ca..9b356e0505e 100644 --- a/core/lib/spree/core/controller_helpers/locale.rb +++ b/core/lib/spree/core/controller_helpers/locale.rb @@ -22,15 +22,19 @@ def set_locale end def current_locale - @current_locale ||= if Spree::Config.use_user_locale && spree_current_user && supported_locale?(spree_current_user.selected_locale) - spree_current_user.selected_locale - elsif params[:locale].present? && supported_locale?(params[:locale]) - params[:locale] - elsif respond_to?(:config_locale, true) && config_locale.present? - config_locale - else - current_store&.default_locale || Rails.application.config.i18n.default_locale || I18n.default_locale - end + if Spree::Config.use_user_locale && spree_current_user && supported_locale?(spree_current_user.selected_locale) + @current_locale ||= spree_current_user.selected_locale + else + @current_locale ||= if Spree::Config.use_user_locale && spree_current_user && supported_locale?(spree_current_user.selected_locale) + spree_current_user.selected_locale + elsif params[:locale].present? && supported_locale?(params[:locale]) + params[:locale] + elsif respond_to?(:config_locale, true) && config_locale.present? + config_locale + else + current_store&.default_locale || Rails.application.config.i18n.default_locale || I18n.default_locale + end + end end def supported_locales From 3ccf1a81149a0a341ea4480583510be055e34dc0 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Tue, 20 Dec 2022 10:05:19 +0100 Subject: [PATCH 14/89] fix a mistake --- core/lib/spree/core/controller_helpers/locale.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/lib/spree/core/controller_helpers/locale.rb b/core/lib/spree/core/controller_helpers/locale.rb index 9b356e0505e..34259d379e6 100644 --- a/core/lib/spree/core/controller_helpers/locale.rb +++ b/core/lib/spree/core/controller_helpers/locale.rb @@ -23,11 +23,9 @@ def set_locale def current_locale if Spree::Config.use_user_locale && spree_current_user && supported_locale?(spree_current_user.selected_locale) - @current_locale ||= spree_current_user.selected_locale + @current_locale = spree_current_user.selected_locale else - @current_locale ||= if Spree::Config.use_user_locale && spree_current_user && supported_locale?(spree_current_user.selected_locale) - spree_current_user.selected_locale - elsif params[:locale].present? && supported_locale?(params[:locale]) + @current_locale ||= if params[:locale].present? && supported_locale?(params[:locale]) params[:locale] elsif respond_to?(:config_locale, true) && config_locale.present? config_locale From 9a29bb43fd83d9e5d276141c70bbaa979829d6fe Mon Sep 17 00:00:00 2001 From: wjwitek Date: Tue, 20 Dec 2022 10:40:50 +0100 Subject: [PATCH 15/89] move long conditions to functions --- .../spree/core/controller_helpers/locale.rb | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/core/lib/spree/core/controller_helpers/locale.rb b/core/lib/spree/core/controller_helpers/locale.rb index 34259d379e6..3cc7facfa7e 100644 --- a/core/lib/spree/core/controller_helpers/locale.rb +++ b/core/lib/spree/core/controller_helpers/locale.rb @@ -22,17 +22,27 @@ def set_locale end def current_locale - if Spree::Config.use_user_locale && spree_current_user && supported_locale?(spree_current_user.selected_locale) - @current_locale = spree_current_user.selected_locale - else - @current_locale ||= if params[:locale].present? && supported_locale?(params[:locale]) - params[:locale] - elsif respond_to?(:config_locale, true) && config_locale.present? - config_locale - else - current_store&.default_locale || Rails.application.config.i18n.default_locale || I18n.default_locale - end - end + @current_locale ||= if user_locale? + spree_current_user.selected_locale + elsif params_locale? + params[:locale] + elsif config_locale? + config_locale + else + current_store&.default_locale || Rails.application.config.i18n.default_locale || I18n.default_locale + end + end + + def config_locale? + respond_to?(:config_locale, true) && config_locale.present? + end + + def params_locale? + params[:locale].present? && supported_locale?(params[:locale]) + end + + def user_locale? + Spree::Config.use_user_locale && spree_current_user && supported_locale?(spree_current_user.selected_locale) end def supported_locales From fb9964b68bf40bdaf1c6a3a52d9ac57ff488470a Mon Sep 17 00:00:00 2001 From: wjwitek Date: Tue, 20 Dec 2022 11:47:31 +0100 Subject: [PATCH 16/89] fix test by mocking a method --- .../spree/core/controller_helpers/auth_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/spec/lib/spree/core/controller_helpers/auth_spec.rb b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb index b370a9e0284..c1268400c3e 100644 --- a/core/spec/lib/spree/core/controller_helpers/auth_spec.rb +++ b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb @@ -24,6 +24,11 @@ def index redirect_back_or_default('/') end end + + let(:user) { build(:user, selected_locale: 'pl') } + + before { allow(controller).to receive(:spree_current_user).and_return(user) } + it 'redirects to session url' do session[:spree_user_return_to] = '/redirect' get :index @@ -47,6 +52,11 @@ def index render plain: 'index' end end + + let(:user) { build(:user, selected_locale: 'pl') } + + before { allow(controller).to receive(:spree_current_user).and_return(user) } + it 'sends cookie header' do get :index expect(response.cookies['token']).not_to be_nil @@ -85,6 +95,11 @@ def index redirect_unauthorized_access end end + + let(:user) { build(:user, selected_locale: 'pl') } + + before { allow(controller).to receive(:spree_current_user).and_return(user) } + context 'when logged in' do before do allow(controller).to receive_messages(try_spree_current_user: double('User', id: 1, last_incomplete_spree_order: nil)) From 521d54c0ef39a4607cac6c433ab264e2a88194cd Mon Sep 17 00:00:00 2001 From: wjwitek Date: Tue, 20 Dec 2022 13:26:10 +0100 Subject: [PATCH 17/89] change migration file to work for other user types --- .../20221215151408_add_selected_locale_to_spree_users.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb b/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb index c1e7546359d..3655429b298 100644 --- a/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb +++ b/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb @@ -1,5 +1,8 @@ class AddSelectedLocaleToSpreeUsers < ActiveRecord::Migration[7.0] def change - add_column :spree_users, :selected_locale, :string + if Spree.user_class.present? + users_table_name = Spree.user_class.table_name + add_column users_table_name, :selected_locale, :string unless column_exists?(users_table_name, :selected_locale) + end end end From 319468621e906f34e6a186c096bd4e2f8a28a9f9 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Wed, 21 Dec 2022 08:18:50 +0100 Subject: [PATCH 18/89] remove unnecessary selected_locale --- core/spec/lib/spree/core/controller_helpers/auth_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/spec/lib/spree/core/controller_helpers/auth_spec.rb b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb index c1268400c3e..19f873a0b3a 100644 --- a/core/spec/lib/spree/core/controller_helpers/auth_spec.rb +++ b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb @@ -25,7 +25,7 @@ def index end end - let(:user) { build(:user, selected_locale: 'pl') } + let(:user) { build(:user) } before { allow(controller).to receive(:spree_current_user).and_return(user) } @@ -53,7 +53,7 @@ def index end end - let(:user) { build(:user, selected_locale: 'pl') } + let(:user) { build(:user) } before { allow(controller).to receive(:spree_current_user).and_return(user) } From 3ca1c41b7f6ed47453878b57f328af9a57bbe765 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 21 Dec 2022 09:34:57 +0100 Subject: [PATCH 19/89] revert taxonomy translation --- core/app/models/spree/taxonomy.rb | 4 ---- ...taxonomies_translations_table_for_mobility.rb | 16 ---------------- ...21215121651_enable_taxonomies_without_name.rb | 5 ----- 3 files changed, 25 deletions(-) diff --git a/core/app/models/spree/taxonomy.rb b/core/app/models/spree/taxonomy.rb index e0b2ee14f11..4af1ae54372 100644 --- a/core/app/models/spree/taxonomy.rb +++ b/core/app/models/spree/taxonomy.rb @@ -1,14 +1,10 @@ module Spree class Taxonomy < Spree::Base include Metadata - include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end - TRANSLATABLE_FIELDS = %i[name].freeze - translates *TRANSLATABLE_FIELDS - acts_as_list validates :name, presence: true, uniqueness: { case_sensitive: false, allow_blank: true, scope: :store_id } diff --git a/core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb b/core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb index 76b0af886e9..e69de29bb2d 100644 --- a/core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb +++ b/core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb @@ -1,16 +0,0 @@ -class CreateTaxonomiesTranslationsTableForMobility < ActiveRecord::Migration[6.1] - def change - create_table :spree_taxonomy_translations do |t| - # Translated attribute(s) - t.string :name - - t.string :locale, null: false - t.references :spree_taxonomy, null: false, foreign_key: true, index: false - - t.timestamps null: false - end - add_index :spree_taxonomy_translations, :locale, name: :index_spree_taxonomy_translations_on_locale - add_index :spree_taxonomy_translations, [:spree_taxonomy_id, :locale], name: :index_spree_taxonomy_translations_on_spree_taxonomy_id_locale, unique: true - - end -end diff --git a/core/db/migrate/20221215121651_enable_taxonomies_without_name.rb b/core/db/migrate/20221215121651_enable_taxonomies_without_name.rb index bc924cb4762..e69de29bb2d 100644 --- a/core/db/migrate/20221215121651_enable_taxonomies_without_name.rb +++ b/core/db/migrate/20221215121651_enable_taxonomies_without_name.rb @@ -1,5 +0,0 @@ -class EnableTaxonomiesWithoutName < ActiveRecord::Migration[6.1] - def change - change_column_null :spree_taxonomies, :name, true - end -end From 69752d105afa0102dfc733840a67bedbfb7bf090 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Wed, 21 Dec 2022 10:57:52 +0100 Subject: [PATCH 20/89] allow controller to reveive spree_current_user more than once --- api/spec/controllers/spree/api/v2/base_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/spec/controllers/spree/api/v2/base_controller_spec.rb b/api/spec/controllers/spree/api/v2/base_controller_spec.rb index 94c14869769..a82e19a8d45 100644 --- a/api/spec/controllers/spree/api/v2/base_controller_spec.rb +++ b/api/spec/controllers/spree/api/v2/base_controller_spec.rb @@ -109,7 +109,7 @@ def index shared_examples 'rescues from error' do it do expect(subject).to receive(:index).and_raise(exception) - expect(subject).to receive(:spree_current_user).and_return(user) + expect(subject).to receive(:spree_current_user).at_least(:once).and_return(user) expect_next_instance_of(::Spree::Api::ErrorHandler) do |instance| expect(instance).to receive(:call).with( exception: exception, From ea13d816e29e700a249c0a70ae171befed8477bf Mon Sep 17 00:00:00 2001 From: wjwitek Date: Wed, 21 Dec 2022 11:08:56 +0100 Subject: [PATCH 21/89] change migration to be compatible with rails 6.1 --- .../20221215151408_add_selected_locale_to_spree_users.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb b/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb index 3655429b298..44667330482 100644 --- a/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb +++ b/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb @@ -1,4 +1,4 @@ -class AddSelectedLocaleToSpreeUsers < ActiveRecord::Migration[7.0] +class AddSelectedLocaleToSpreeUsers < ActiveRecord::Migration[6.1] def change if Spree.user_class.present? users_table_name = Spree.user_class.table_name From c391f6964a826408d891b9ac431d16b9a56fe859 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 21 Dec 2022 11:11:44 +0100 Subject: [PATCH 22/89] delete taxonomy translation migration files --- ...213140831_create_taxonomies_translations_table_for_mobility.rb | 0 core/db/migrate/20221215121651_enable_taxonomies_without_name.rb | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb delete mode 100644 core/db/migrate/20221215121651_enable_taxonomies_without_name.rb diff --git a/core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb b/core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/core/db/migrate/20221215121651_enable_taxonomies_without_name.rb b/core/db/migrate/20221215121651_enable_taxonomies_without_name.rb deleted file mode 100644 index e69de29bb2d..00000000000 From 0eb34955e7567f1a979c8661f05f89d67aaea955 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 21 Dec 2022 12:50:54 +0100 Subject: [PATCH 23/89] update methods checking for changes in translated values --- core/app/models/spree/taxon.rb | 7 ++++++- core/app/models/spree/taxonomy.rb | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/app/models/spree/taxon.rb b/core/app/models/spree/taxon.rb index 78d72c44f62..c4885a96c26 100644 --- a/core/app/models/spree/taxon.rb +++ b/core/app/models/spree/taxon.rb @@ -114,13 +114,18 @@ def child_index=(idx) private def sync_taxonomy_name - if saved_change_to_name? && root? + if name_updated? && root? return if taxonomy.name.to_s == name.to_s taxonomy.update(name: name) end end + def name_updated? + translations.any?(&:changes) && + translations.first(&:changes).changes.keys.include?('name') + end + def touch_ancestors_and_taxonomy # Touches all ancestors at once to avoid recursive taxonomy touch, and reduce queries. ancestors.update_all(updated_at: Time.current) diff --git a/core/app/models/spree/taxonomy.rb b/core/app/models/spree/taxonomy.rb index 4af1ae54372..8fa7228bd2f 100644 --- a/core/app/models/spree/taxonomy.rb +++ b/core/app/models/spree/taxonomy.rb @@ -28,7 +28,7 @@ def set_root end def set_root_taxon_name - return unless saved_change_to_name? + return unless name_updated? return if name.to_s == root.name.to_s root.update(name: name) From 02f1ef91664980ce4d204045fd32e3b4e80ae1a9 Mon Sep 17 00:00:00 2001 From: Weronika Witek <74113640+wjwitek@users.noreply.github.com> Date: Wed, 21 Dec 2022 13:10:38 +0100 Subject: [PATCH 24/89] Update api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: RafaƂ Cymerys --- .../serializers/spree/api/v2/platform/user_serializer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb b/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb index bad669aa95e..a78ab0fc341 100644 --- a/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb +++ b/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb @@ -75,7 +75,7 @@ context 'when user has selected non default locale' do let(:user) { create(:user_with_addresses, selected_locale: 'fr') } - it do + it 'returns the selected locale in the serialized hash' do expect(subject.serializable_hash[:data][:attributes][:selected_locale]).to eq('fr') end end From 737c716598ee3e0c4c4339d84801872eabc466cb Mon Sep 17 00:00:00 2001 From: wjwitek Date: Wed, 21 Dec 2022 13:12:47 +0100 Subject: [PATCH 25/89] add description to test --- .../serializers/spree/v2/storefront/user_serializer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb b/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb index 192b4669521..6d471cbfa8a 100644 --- a/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb +++ b/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb @@ -67,7 +67,7 @@ context 'when user has selected non default locale' do let(:user) { create(:user_with_addresses, selected_locale: 'fr') } - it do + it 'returns the selected locale in the serialized hash' do expect(subject.serializable_hash[:data][:attributes][:selected_locale]).to eq('fr') end end From 0941a33a196c3db9f8cc337f459b13838fc2dc85 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Wed, 21 Dec 2022 14:04:41 +0100 Subject: [PATCH 26/89] udate api docs with selected_locale --- api/docs/v2/platform/index.yaml | 10 ++++++++++ api/docs/v2/storefront/index.yaml | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/api/docs/v2/platform/index.yaml b/api/docs/v2/platform/index.yaml index ef253bbcc31..13f74c38c4f 100644 --- a/api/docs/v2/platform/index.yaml +++ b/api/docs/v2/platform/index.yaml @@ -15490,6 +15490,7 @@ paths: email: ivonne.braun@smith.biz first_name: Liberty last_name: Becker + selected_locale: nil created_at: '2022-11-08T19:35:53.726Z' updated_at: '2022-11-08T19:35:53.726Z' public_metadata: {} @@ -15508,6 +15509,7 @@ paths: email: lenita.mayer@kulas.us first_name: Chasidy last_name: Strosin + selected_locale: 'fr' created_at: '2022-11-08T19:35:53.730Z' updated_at: '2022-11-08T19:35:53.730Z' public_metadata: {} @@ -15526,6 +15528,7 @@ paths: email: dewayne@terrybarton.info first_name: Ruben last_name: Schmidt + selected_locale: 'de' created_at: '2022-11-08T19:35:53.732Z' updated_at: '2022-11-08T19:35:53.732Z' public_metadata: {} @@ -15591,6 +15594,7 @@ paths: email: rex_champlin@breitenberg.com first_name: Zenia last_name: King + selected_locale: 'pl' created_at: '2022-11-08T19:35:54.351Z' updated_at: '2022-11-08T19:35:54.351Z' public_metadata: {} @@ -15660,6 +15664,7 @@ paths: email: gaynell@parisian.biz first_name: Irwin last_name: DuBuque + selected_locale: 'en' created_at: '2022-11-08T19:35:54.635Z' updated_at: '2022-11-08T19:35:54.635Z' public_metadata: {} @@ -15730,6 +15735,7 @@ paths: email: john@example.com first_name: Astrid last_name: Kohler + selected_locale: 'fr' created_at: '2022-11-08T19:35:55.180Z' updated_at: '2022-11-08T19:35:55.414Z' public_metadata: {} @@ -21338,6 +21344,8 @@ components: type: string password_confirmation: type: string + selected_locale: + type: string ship_address_id: type: string bill_address_id: @@ -21365,6 +21373,8 @@ components: type: string password_confirmation: type: string + selected_locale: + type: string ship_address_id: type: string bill_address_id: diff --git a/api/docs/v2/storefront/index.yaml b/api/docs/v2/storefront/index.yaml index 8c8b04cb547..5cc436e4c72 100644 --- a/api/docs/v2/storefront/index.yaml +++ b/api/docs/v2/storefront/index.yaml @@ -63,6 +63,9 @@ paths: last_name: type: string example: Snow + selected_locale: + type: string + example: 'en' password: type: string example: spree123 @@ -110,6 +113,9 @@ paths: last_name: type: string example: Snow + selected_locale: + type: string + example: 'fr' bill_address_id: type: string example: '1' @@ -3650,6 +3656,9 @@ components: last_name: type: string example: Doe + selected_locale: + type: string + example: 'fr' store_credits: type: number example: 150.75 @@ -5109,6 +5118,7 @@ components: email: spree@example.com first_name: John last_name: Snow + selected_locale: 'en' store_credits: 0 completed_orders: 0 public_metadata: From 4359548141cde559bee4c17cb5fad48aa45324e1 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 21 Dec 2022 14:36:13 +0100 Subject: [PATCH 27/89] use mobility dirty plugin instead of manual checks for saved changes --- core/app/models/spree/taxon.rb | 7 +------ core/app/models/spree/taxonomy.rb | 2 +- core/config/initializers/mobility.rb | 1 + 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/core/app/models/spree/taxon.rb b/core/app/models/spree/taxon.rb index c4885a96c26..78d72c44f62 100644 --- a/core/app/models/spree/taxon.rb +++ b/core/app/models/spree/taxon.rb @@ -114,18 +114,13 @@ def child_index=(idx) private def sync_taxonomy_name - if name_updated? && root? + if saved_change_to_name? && root? return if taxonomy.name.to_s == name.to_s taxonomy.update(name: name) end end - def name_updated? - translations.any?(&:changes) && - translations.first(&:changes).changes.keys.include?('name') - end - def touch_ancestors_and_taxonomy # Touches all ancestors at once to avoid recursive taxonomy touch, and reduce queries. ancestors.update_all(updated_at: Time.current) diff --git a/core/app/models/spree/taxonomy.rb b/core/app/models/spree/taxonomy.rb index 8fa7228bd2f..4af1ae54372 100644 --- a/core/app/models/spree/taxonomy.rb +++ b/core/app/models/spree/taxonomy.rb @@ -28,7 +28,7 @@ def set_root end def set_root_taxon_name - return unless name_updated? + return unless saved_change_to_name? return if name.to_s == root.name.to_s root.update(name: name) diff --git a/core/config/initializers/mobility.rb b/core/config/initializers/mobility.rb index dd05f7e0bcc..a554d7dafca 100644 --- a/core/config/initializers/mobility.rb +++ b/core/config/initializers/mobility.rb @@ -11,6 +11,7 @@ fallbacks locale_accessors presence + dirty end config.defaults[:fallbacks] = true From bade87b2081173809f6e5c8b9e708ef99f6f3be6 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Tue, 27 Dec 2022 17:06:22 +0100 Subject: [PATCH 28/89] account for mysql rules for select statements --- .../concerns/spree/webhooks/has_webhooks.rb | 8 +------ core/app/sorters/spree/products/sort.rb | 23 +++++++++++++------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/api/app/models/concerns/spree/webhooks/has_webhooks.rb b/api/app/models/concerns/spree/webhooks/has_webhooks.rb index 850a0e233c4..42b5ec23266 100644 --- a/api/app/models/concerns/spree/webhooks/has_webhooks.rb +++ b/api/app/models/concerns/spree/webhooks/has_webhooks.rb @@ -58,13 +58,7 @@ def resource_serializer end def updating_only_ignored_attributes? - if defined?(translations) && - translations.any?(&:saved_changes) && - (translations.first(&:saved_changes).saved_changes.keys - ignored_attributes).any? - false - else - (saved_changes.keys - ignored_attributes).empty? - end + (saved_changes.keys - ignored_attributes).empty? end def ignored_attributes diff --git a/core/app/sorters/spree/products/sort.rb b/core/app/sorters/spree/products/sort.rb index dbc199d28c7..39801651e4e 100644 --- a/core/app/sorters/spree/products/sort.rb +++ b/core/app/sorters/spree/products/sort.rb @@ -51,16 +51,25 @@ def sort_by?(field) # Add translatable fields to SELECT statement to avoid InvalidColumnReference error (Mobility bug workaround) def select_translatable_fields(scope) - translatable_sortable_fields = [] - Product.translatable_fields.each do |field| - translatable_sortable_fields << field if sort_by?(field.to_s) + sql_fields = translatable_sortable_fields + return scope if sql_fields.empty? + + # if sorting by 'sku' or 'price', spree_products.* is already included in SELECT statement + if sort_by?('sku') || sort_by?('price') + scope.i18n.select(sql_fields) + else + scope.i18n.select("#{Product.table_name}.*, #{sql_fields}") end + end - return scope unless translatable_sortable_fields.any? + def translatable_sortable_fields + fields = [] + Product.translatable_fields.each do |field| + fields << field if sort_by?(field.to_s) + end - sql_fields = translatable_sortable_fields.map {|field| "#{Product.translation_table_alias}.#{field}" } - .join(', ') - scope.i18n.select("#{Product.table_name}.*, #{sql_fields}") + fields.map { |field| "#{Product.translation_table_alias}.#{field} AS translation_#{field}" }. + join(', ') end end end From 5cc784775269aa58f543eaf804616402bc8498bb Mon Sep 17 00:00:00 2001 From: nciemniak Date: Tue, 27 Dec 2022 17:26:06 +0100 Subject: [PATCH 29/89] add brakeman warning to ignore list --- api/brakeman.ignore | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 api/brakeman.ignore diff --git a/api/brakeman.ignore b/api/brakeman.ignore new file mode 100644 index 00000000000..b14cffa43ae --- /dev/null +++ b/api/brakeman.ignore @@ -0,0 +1,29 @@ +{ + "ignored_warnings": [ + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "eb7ab081774ab6402753f904f274f78263f865888155d952168e1f2e47f2b114", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/controllers/spree/api/v2/platform/variants_controller.rb", + "line": 20, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "scope.joins(:product).joins(\"LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias}\\n ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id\\n AND #{Product.translation_table_alias}.locale = '#{Mobility.locale.to_s}'\")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::Api::V2::Platform::VariantsController", + "method": "collection" + }, + "user_input": "Product.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + } + ], + "updated": "2022-12-27 17:24:34 +0100", + "brakeman_version": "5.4.0" +} From 7181433600d67808f5fc2ea2216f3e79bf388cc3 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Tue, 27 Dec 2022 17:32:40 +0100 Subject: [PATCH 30/89] add path to brakeman.ignore for circleci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f7fc48736e4..17118c4d0ea 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -136,7 +136,7 @@ jobs: - run: name: Run Brakeman spree/api command: | - bundle exec brakeman -p api/ --skip-files app/controllers/spree/api/v1/ --exit-on-warn --exit-on-error + bundle exec brakeman -p api/ --ignore-config api/brakeman.ignore --skip-files app/controllers/spree/api/v1/ --exit-on-warn --exit-on-error - run: name: Run Brakeman spree/core command: | From 7ab6da4ad590990c94082cebd1dd95459a9023ec Mon Sep 17 00:00:00 2001 From: nciemniak Date: Tue, 27 Dec 2022 17:38:18 +0100 Subject: [PATCH 31/89] add warning to brakeman (spree core) --- core/brakeman.ignore | 137 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 18 deletions(-) diff --git a/core/brakeman.ignore b/core/brakeman.ignore index c82e0794edb..f7e7f600ed0 100644 --- a/core/brakeman.ignore +++ b/core/brakeman.ignore @@ -1,20 +1,121 @@ { - "ignored_warnings": [ - { - "fingerprint": "011b2643940ba1112f7a737e403abe3616ad91764703c801cc35a48d36b721da", - "note": "interpolating table name" - }, - { - "fingerprint": "965d3919f811ab63b7b8d62da528559a7f38dc122c57efea7136e7ec5ef1f062", - "note": "interpolating table name" - }, - { - "fingerprint": "abd8e90e7a7dfbcdcd6d44fd3fb550598aee6d7a9ef2bb132ad1a18a3c50be30", - "note": "interpolating table name" - }, - { - "fingerprint": "efcc57e1a5648d7db59d1beaf5e399d2278539a8667b19c520b305a6ca7e15e8", - "note": "interpolating table name" - } - ] + "ignored_warnings": [ + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "011b2643940ba1112f7a737e403abe3616ad91764703c801cc35a48d36b721da", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 63, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount <= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "1061d418cc95f86403b641d74be4e43a9480329ae327da5a2f73b6869119697c", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/spree/variant.rb", + "line": 125, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(:product).joins(\"LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias}\\n ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id\\n AND #{Product.translation_table_alias}.locale = '#{Mobility.locale.to_s}'\")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::Variant", + "method": "Spree::Variant.product_name_or_sku_cont" + }, + "user_input": "Product.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "965d3919f811ab63b7b8d62da528559a7f38dc122c57efea7136e7ec5ef1f062", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 67, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount >= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::ProductScopes", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "abd8e90e7a7dfbcdcd6d44fd3fb550598aee6d7a9ef2bb132ad1a18a3c50be30", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 63, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount <= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::ProductScopes", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "efcc57e1a5648d7db59d1beaf5e399d2278539a8667b19c520b305a6ca7e15e8", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 67, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount >= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + } + ], + "updated": "2022-12-27 17:37:14 +0100", + "brakeman_version": "5.4.0" } From e63e703bd3fa8fc8e6da5dfd138120a4853d7641 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 28 Dec 2022 14:28:58 +0100 Subject: [PATCH 32/89] remove translatable resource controller parent class --- .../spree/api/v2/platform/taxons_controller.rb | 2 +- .../platform/translatable_resource_controller.rb | 15 --------------- .../spree/api/v2/resource_controller.rb | 2 +- 3 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 api/app/controllers/spree/api/v2/platform/translatable_resource_controller.rb diff --git a/api/app/controllers/spree/api/v2/platform/taxons_controller.rb b/api/app/controllers/spree/api/v2/platform/taxons_controller.rb index 6f9d39d1da9..9ac17f4b5dd 100644 --- a/api/app/controllers/spree/api/v2/platform/taxons_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/taxons_controller.rb @@ -2,7 +2,7 @@ module Spree module Api module V2 module Platform - class TaxonsController < TranslatableResourceController + class TaxonsController < ResourceController include ::Spree::Api::V2::Platform::NestedSetRepositionConcern private diff --git a/api/app/controllers/spree/api/v2/platform/translatable_resource_controller.rb b/api/app/controllers/spree/api/v2/platform/translatable_resource_controller.rb deleted file mode 100644 index 24d75a06e89..00000000000 --- a/api/app/controllers/spree/api/v2/platform/translatable_resource_controller.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Spree - module Api - module V2 - module Platform - class TranslatableResourceController < ResourceController - def scope - super.joins(:translations) - end - end - end - end - end -end - - diff --git a/api/app/controllers/spree/api/v2/resource_controller.rb b/api/app/controllers/spree/api/v2/resource_controller.rb index 9992b2ee6a5..916d12d47e9 100644 --- a/api/app/controllers/spree/api/v2/resource_controller.rb +++ b/api/app/controllers/spree/api/v2/resource_controller.rb @@ -35,7 +35,7 @@ def scope(skip_cancancan: false) base_scope = model_class.for_store(current_store) base_scope = base_scope.accessible_by(current_ability, :show) unless skip_cancancan base_scope = base_scope.includes(scope_includes) if scope_includes.any? && action_name == 'index' - base_scope + model_class.include?(TranslatableResource) ? base_scope.i18n : base_scope end def scope_includes From 4b5b90e8929d703d8a48f71efdb0e43e7eeef5f9 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 28 Dec 2022 15:22:48 +0100 Subject: [PATCH 33/89] refactoring and code climate fixes --- .../spree/api/v2/platform/variants_controller.rb | 6 +++--- core/app/finders/spree/products/find.rb | 7 +++---- core/app/finders/spree/taxons/find.rb | 1 + .../models/concerns/spree/translatable_resource.rb | 4 ++-- core/app/models/spree/product.rb | 2 +- core/app/models/spree/taxon.rb | 2 +- core/app/models/spree/variant.rb | 4 ++-- core/app/sorters/spree/products/sort.rb | 12 +++++------- 8 files changed, 18 insertions(+), 20 deletions(-) diff --git a/api/app/controllers/spree/api/v2/platform/variants_controller.rb b/api/app/controllers/spree/api/v2/platform/variants_controller.rb index 740eee88902..f0f7c7b2099 100644 --- a/api/app/controllers/spree/api/v2/platform/variants_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/variants_controller.rb @@ -17,10 +17,10 @@ def collection # if filtering on products, manually join on product translation to workaround mobility-ransack issue if params.key?(:filter) && params[:filter].keys.any? { |k| k.include? 'product' } @collection ||= scope.joins(:product). - joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} + joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id - AND #{Product.translation_table_alias}.locale = '#{Mobility.locale.to_s}'"). - ransack(params[:filter]).result + AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'"). + ransack(params[:filter]).result else super end diff --git a/core/app/finders/spree/products/find.rb b/core/app/finders/spree/products/find.rb index 22ea785c044..e674c8a8187 100644 --- a/core/app/finders/spree/products/find.rb +++ b/core/app/finders/spree/products/find.rb @@ -145,6 +145,7 @@ def by_concat_taxons(products) def by_name(products) return products unless name? + product_name = name # i18n scope doesn't automatically get set here (mobility gem bug?) set it explicitly @@ -214,12 +215,10 @@ def ordered(products) end when 'name-a-z' products.i18n. - select("#{Product.table_name}.*, #{Product.translation_table_alias}.name"). - order(name: :asc) + select("#{Product.table_name}.*").select(:name).order(name: :asc) when 'name-z-a' products.i18n. - select("#{Product.table_name}.*, #{Product.translation_table_alias}.name"). - order(name: :desc) + select("#{Product.table_name}.*").select(:name).order(name: :desc) when 'newest-first' products.order(available_on: :desc) when 'price-high-to-low' diff --git a/core/app/finders/spree/taxons/find.rb b/core/app/finders/spree/taxons/find.rb index 125d68d34e8..abe327b3cf3 100644 --- a/core/app/finders/spree/taxons/find.rb +++ b/core/app/finders/spree/taxons/find.rb @@ -86,6 +86,7 @@ def by_roots(taxons) def by_name(taxons) return taxons unless name? + taxon_name = name # i18n scope doesn't automatically get set here (mobility gem bug?) set it explicitly diff --git a/core/app/models/concerns/spree/translatable_resource.rb b/core/app/models/concerns/spree/translatable_resource.rb index 123b054b04d..73c0de27b42 100644 --- a/core/app/models/concerns/spree/translatable_resource.rb +++ b/core/app/models/concerns/spree/translatable_resource.rb @@ -14,11 +14,11 @@ def get_field_with_locale(locale, field_name, fallback: false) class_methods do def translatable_fields - self.const_get(:TRANSLATABLE_FIELDS) + const_get(:TRANSLATABLE_FIELDS) end def translation_table_alias - "#{self::Translation.table_name}_#{Mobility.locale.to_s}" + "#{self::Translation.table_name}_#{Mobility.locale}" end end end diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index 25b0cb92ca7..80139391722 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -38,7 +38,7 @@ class Product < Spree::Base purchasable? in_stock? backorderable?] TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_keywords meta_title].freeze - translates *TRANSLATABLE_FIELDS + translates(*TRANSLATABLE_FIELDS) Product::Translation.class_eval { acts_as_paranoid } friendly_id :slug_candidates, use: [:history, :mobility] diff --git a/core/app/models/spree/taxon.rb b/core/app/models/spree/taxon.rb index 78d72c44f62..6589fbf9a19 100644 --- a/core/app/models/spree/taxon.rb +++ b/core/app/models/spree/taxon.rb @@ -56,7 +56,7 @@ class Taxon < Spree::Base scope :for_stores, ->(stores) { joins(:taxonomy).where(spree_taxonomies: { store_id: stores.ids }) } TRANSLATABLE_FIELDS = %i[name description].freeze - translates *TRANSLATABLE_FIELDS + translates(*TRANSLATABLE_FIELDS) # indicate which filters should be used for a taxon # this method should be customized to your own site diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index 07e192893e2..d51d67d7308 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -124,8 +124,8 @@ class Variant < Spree::Base def self.product_name_or_sku_cont(query) joins(:product).joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id - AND #{Product.translation_table_alias}.locale = '#{Mobility.locale.to_s}'"). - where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") + AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'"). + where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") end def self.search_by_product_name_or_sku(query) diff --git a/core/app/sorters/spree/products/sort.rb b/core/app/sorters/spree/products/sort.rb index 39801651e4e..ba0b05b52a5 100644 --- a/core/app/sorters/spree/products/sort.rb +++ b/core/app/sorters/spree/products/sort.rb @@ -51,14 +51,14 @@ def sort_by?(field) # Add translatable fields to SELECT statement to avoid InvalidColumnReference error (Mobility bug workaround) def select_translatable_fields(scope) - sql_fields = translatable_sortable_fields - return scope if sql_fields.empty? + translatable_fields = translatable_sortable_fields + return scope if translatable_fields.empty? # if sorting by 'sku' or 'price', spree_products.* is already included in SELECT statement if sort_by?('sku') || sort_by?('price') - scope.i18n.select(sql_fields) + scope.i18n.select(translatable_fields) else - scope.i18n.select("#{Product.table_name}.*, #{sql_fields}") + scope.i18n.select("#{Product.table_name}.*").select(translatable_fields) end end @@ -67,9 +67,7 @@ def translatable_sortable_fields Product.translatable_fields.each do |field| fields << field if sort_by?(field.to_s) end - - fields.map { |field| "#{Product.translation_table_alias}.#{field} AS translation_#{field}" }. - join(', ') + fields end end end From 21faedadd0f2eaacf454fcf1851aad9ae0673718 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 28 Dec 2022 16:32:39 +0100 Subject: [PATCH 34/89] change how translation fields are passed --- core/app/sorters/spree/products/sort.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/app/sorters/spree/products/sort.rb b/core/app/sorters/spree/products/sort.rb index ba0b05b52a5..0eca2d54dd7 100644 --- a/core/app/sorters/spree/products/sort.rb +++ b/core/app/sorters/spree/products/sort.rb @@ -56,9 +56,9 @@ def select_translatable_fields(scope) # if sorting by 'sku' or 'price', spree_products.* is already included in SELECT statement if sort_by?('sku') || sort_by?('price') - scope.i18n.select(translatable_fields) + scope.i18n.select(*translatable_fields) else - scope.i18n.select("#{Product.table_name}.*").select(translatable_fields) + scope.i18n.select("#{Product.table_name}.*").select(*translatable_fields) end end From eb47edf7851824af0e4737266d40a27b9e71aee8 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 28 Dec 2022 16:33:12 +0100 Subject: [PATCH 35/89] move join product translation code into a method --- .../spree/api/v2/platform/variants_controller.rb | 5 +---- core/app/models/spree/variant.rb | 9 +++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/api/app/controllers/spree/api/v2/platform/variants_controller.rb b/api/app/controllers/spree/api/v2/platform/variants_controller.rb index f0f7c7b2099..93c436b4671 100644 --- a/api/app/controllers/spree/api/v2/platform/variants_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/variants_controller.rb @@ -16,10 +16,7 @@ def spree_permitted_attributes def collection # if filtering on products, manually join on product translation to workaround mobility-ransack issue if params.key?(:filter) && params[:filter].keys.any? { |k| k.include? 'product' } - @collection ||= scope.joins(:product). - joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} - ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id - AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'"). + @collection ||= scope.join_product_translations. ransack(params[:filter]).result else super diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index d51d67d7308..f9abdb32f94 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -122,10 +122,15 @@ class Variant < Spree::Base self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku) def self.product_name_or_sku_cont(query) + join_product_translations. + where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) + OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") + end + + def self.join_product_translations joins(:product).joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id - AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'"). - where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") + AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'") end def self.search_by_product_name_or_sku(query) From 74b012ec891708839eab15fc2f50094555df406e Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 28 Dec 2022 17:15:58 +0100 Subject: [PATCH 36/89] add taxon find spec --- core/spec/finders/spree/taxons/find_spec.rb | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 core/spec/finders/spree/taxons/find_spec.rb diff --git a/core/spec/finders/spree/taxons/find_spec.rb b/core/spec/finders/spree/taxons/find_spec.rb new file mode 100644 index 00000000000..077b04af3d1 --- /dev/null +++ b/core/spec/finders/spree/taxons/find_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +module Spree + describe Taxons::Find do + let!(:taxon_shirts) { create(:taxon, name: 'Shirts') } + let!(:taxon_shorts) { create(:taxon, name: 'Shorts') } + let!(:taxon_shoes) { create(:taxon, name: 'Shoes') } + + describe 'filtering by taxon property' do + subject do + described_class.new( + scope: Spree::Taxon.all, + params: params + ).execute + end + + context 'when filtering by taxon name' do + let(:params) { { filter: { 'name': 'Shirts' } } } + + it 'returns taxon with matching name' do + expect(subject).to contain_exactly(taxon_shirts) + end + end + + end + + end +end From fc6bb5a31176192c0bbe853177881d4e6f20c16a Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 28 Dec 2022 17:33:00 +0100 Subject: [PATCH 37/89] refactor product 'ordered' method --- core/app/finders/spree/products/find.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/core/app/finders/spree/products/find.rb b/core/app/finders/spree/products/find.rb index e674c8a8187..8335a86f908 100644 --- a/core/app/finders/spree/products/find.rb +++ b/core/app/finders/spree/products/find.rb @@ -222,15 +222,9 @@ def ordered(products) when 'newest-first' products.order(available_on: :desc) when 'price-high-to-low' - products. - select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount"). - reorder(''). - send(:descend_by_master_price) + order_by_price(products, :descend_by_master_price) when 'price-low-to-high' - products. - select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount"). - reorder(''). - send(:ascend_by_master_price) + order_by_price(products, :ascend_by_master_price) end end @@ -272,6 +266,13 @@ def taxon_ids(taxons_ids) taxons = store.taxons.where(id: taxons_ids.to_s.split(',')) taxons.map(&:cached_self_and_descendants_ids).flatten.compact.uniq.map(&:to_s) end + + def order_by_price(scope, order_type) + scope. + select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount"). + reorder(''). + send(order_type) + end end end end From ba6b96709997fe1c78ac9cf75583ddcf50c85591 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Thu, 29 Dec 2022 09:33:25 +0100 Subject: [PATCH 38/89] update brakeman.ignore --- api/brakeman.ignore | 26 ++------------------------ core/brakeman.ignore | 33 ++++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/api/brakeman.ignore b/api/brakeman.ignore index b14cffa43ae..e253a812ea0 100644 --- a/api/brakeman.ignore +++ b/api/brakeman.ignore @@ -1,29 +1,7 @@ { "ignored_warnings": [ - { - "warning_type": "SQL Injection", - "warning_code": 0, - "fingerprint": "eb7ab081774ab6402753f904f274f78263f865888155d952168e1f2e47f2b114", - "check_name": "SQL", - "message": "Possible SQL injection", - "file": "app/controllers/spree/api/v2/platform/variants_controller.rb", - "line": 20, - "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", - "code": "scope.joins(:product).joins(\"LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias}\\n ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id\\n AND #{Product.translation_table_alias}.locale = '#{Mobility.locale.to_s}'\")", - "render_path": null, - "location": { - "type": "method", - "class": "Spree::Api::V2::Platform::VariantsController", - "method": "collection" - }, - "user_input": "Product.translation_table_alias", - "confidence": "Weak", - "cwe_id": [ - 89 - ], - "note": "" - } + ], - "updated": "2022-12-27 17:24:34 +0100", + "updated": "2022-12-29 09:29:46 +0100", "brakeman_version": "5.4.0" } diff --git a/core/brakeman.ignore b/core/brakeman.ignore index f7e7f600ed0..24e68c5178a 100644 --- a/core/brakeman.ignore +++ b/core/brakeman.ignore @@ -26,18 +26,18 @@ { "warning_type": "SQL Injection", "warning_code": 0, - "fingerprint": "1061d418cc95f86403b641d74be4e43a9480329ae327da5a2f73b6869119697c", + "fingerprint": "67e80bcfa8898315b0f8642bc61990c8319bc7a468453efc048757a439e53e0b", "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/spree/variant.rb", - "line": 125, + "line": 131, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", - "code": "joins(:product).joins(\"LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias}\\n ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id\\n AND #{Product.translation_table_alias}.locale = '#{Mobility.locale.to_s}'\")", + "code": "joins(:product).joins(\"LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias}\\n ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id\\n AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'\")", "render_path": null, "location": { "type": "method", "class": "Spree::Variant", - "method": "Spree::Variant.product_name_or_sku_cont" + "method": "Spree::Variant.join_product_translations" }, "user_input": "Product.translation_table_alias", "confidence": "Weak", @@ -92,6 +92,29 @@ ], "note": "interpolating table name" }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "e6bb08c012af19589f796e26b4dc2230ea1c1949c7cd6464ca025939513d8b3d", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/spree/variant.rb", + "line": 126, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "join_product_translations.where(\"LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)\\n OR LOWER(sku) LIKE LOWER(:query)\", :query => (\"%#{query}%\"))", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::Variant", + "method": "Spree::Variant.product_name_or_sku_cont" + }, + "user_input": "Product.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, { "warning_type": "SQL Injection", "warning_code": 0, @@ -116,6 +139,6 @@ "note": "interpolating table name" } ], - "updated": "2022-12-27 17:37:14 +0100", + "updated": "2022-12-29 09:32:26 +0100", "brakeman_version": "5.4.0" } From dd503abc55c3e6c67f60baf031e157631c6ec4eb Mon Sep 17 00:00:00 2001 From: Weronika Witek <74113640+wjwitek@users.noreply.github.com> Date: Thu, 29 Dec 2022 10:23:38 +0100 Subject: [PATCH 39/89] Fix getting current user (#11820) * change spree_current_usr to try_spree_current_user * change mock of spree_current_user * remove commented code * change spree_current_user to try_spree_current_user --- .../controllers/spree/api/v2/base_controller.rb | 2 ++ core/lib/spree/core/controller_helpers/locale.rb | 4 ++-- .../lib/spree/core/controller_helpers/auth_spec.rb | 14 +------------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/api/app/controllers/spree/api/v2/base_controller.rb b/api/app/controllers/spree/api/v2/base_controller.rb index 21cafcae516..02760585d36 100644 --- a/api/app/controllers/spree/api/v2/base_controller.rb +++ b/api/app/controllers/spree/api/v2/base_controller.rb @@ -82,6 +82,8 @@ def spree_current_user @spree_current_user ||= doorkeeper_token.resource_owner end + alias try_spree_current_user spree_current_user # for compatibility with spree_legacy_frontend + def spree_authorize!(action, subject, *args) authorize!(action, subject, *args) end diff --git a/core/lib/spree/core/controller_helpers/locale.rb b/core/lib/spree/core/controller_helpers/locale.rb index 3cc7facfa7e..5273a303a2e 100644 --- a/core/lib/spree/core/controller_helpers/locale.rb +++ b/core/lib/spree/core/controller_helpers/locale.rb @@ -23,7 +23,7 @@ def set_locale def current_locale @current_locale ||= if user_locale? - spree_current_user.selected_locale + try_spree_current_user.selected_locale elsif params_locale? params[:locale] elsif config_locale? @@ -42,7 +42,7 @@ def params_locale? end def user_locale? - Spree::Config.use_user_locale && spree_current_user && supported_locale?(spree_current_user.selected_locale) + Spree::Config.use_user_locale && try_spree_current_user && supported_locale?(try_spree_current_user.selected_locale) end def supported_locales diff --git a/core/spec/lib/spree/core/controller_helpers/auth_spec.rb b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb index 19f873a0b3a..ad0655013e4 100644 --- a/core/spec/lib/spree/core/controller_helpers/auth_spec.rb +++ b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb @@ -25,10 +25,6 @@ def index end end - let(:user) { build(:user) } - - before { allow(controller).to receive(:spree_current_user).and_return(user) } - it 'redirects to session url' do session[:spree_user_return_to] = '/redirect' get :index @@ -53,10 +49,6 @@ def index end end - let(:user) { build(:user) } - - before { allow(controller).to receive(:spree_current_user).and_return(user) } - it 'sends cookie header' do get :index expect(response.cookies['token']).not_to be_nil @@ -96,13 +88,9 @@ def index end end - let(:user) { build(:user, selected_locale: 'pl') } - - before { allow(controller).to receive(:spree_current_user).and_return(user) } - context 'when logged in' do before do - allow(controller).to receive_messages(try_spree_current_user: double('User', id: 1, last_incomplete_spree_order: nil)) + allow(controller).to receive_messages(try_spree_current_user: double('User', id: 1, last_incomplete_spree_order: nil, selected_locale: nil)) end it 'redirects forbidden path' do From 2cc296aeabfc4bcbd317952624a92be2eea7da5d Mon Sep 17 00:00:00 2001 From: nciemniak Date: Thu, 29 Dec 2022 11:39:31 +0100 Subject: [PATCH 40/89] remove checks for SpreeGlobalize on product searches --- core/app/models/concerns/spree/product_scopes.rb | 15 ++------------- core/app/models/spree/variant.rb | 7 +------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/core/app/models/concerns/spree/product_scopes.rb b/core/app/models/concerns/spree/product_scopes.rb index dc2dab186f9..340ad930c69 100644 --- a/core/app/models/concerns/spree/product_scopes.rb +++ b/core/app/models/concerns/spree/product_scopes.rb @@ -295,21 +295,10 @@ def self.for_user(user = nil) # .search_by_name if defined?(PgSearch) include PgSearch::Model - - if defined?(SpreeGlobalize) - pg_search_scope :search_by_name, associated_against: { translations: :name }, using: { tsearch: { any_word: true, prefix: true } } - else - pg_search_scope :search_by_name, against: :name, using: { tsearch: { any_word: true, prefix: true } } - end + pg_search_scope :search_by_name, against: :name, using: { tsearch: { any_word: true, prefix: true } } else def self.search_by_name(query) - if defined?(SpreeGlobalize) - joins(:translations).order(:name).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query)", query: "%#{query}%").distinct - elsif defined?(Mobility) - i18n { name.lower.matches("%#{query.downcase}%") } - else - where("LOWER(#{Product.table_name}.name) LIKE LOWER(:query)", query: "%#{query}%") - end + i18n { name.lower.matches("%#{query.downcase}%") } end end search_scopes << :search_by_name diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index f9abdb32f94..2be9a29c3bb 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -134,12 +134,7 @@ def self.join_product_translations end def self.search_by_product_name_or_sku(query) - if defined?(SpreeGlobalize) - joins(product: :translations).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", - query: "%#{query}%") - else - product_name_or_sku_cont(query) - end + product_name_or_sku_cont(query) end def available? From 8fbfd9bee9e3a1688dd2a42fa1585157316e9311 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 4 Jan 2023 16:24:47 +0100 Subject: [PATCH 41/89] Add translations for option types and option values --- core/app/models/spree/option_type.rb | 4 ++++ core/app/models/spree/option_value.rb | 4 ++++ ...03144439_create_option_type_translations.rb | 18 ++++++++++++++++++ ...3151034_create_option_value_translations.rb | 18 ++++++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 core/db/migrate/20230103144439_create_option_type_translations.rb create mode 100644 core/db/migrate/20230103151034_create_option_value_translations.rb diff --git a/core/app/models/spree/option_type.rb b/core/app/models/spree/option_type.rb index 84366de9ef9..244aaac8c94 100644 --- a/core/app/models/spree/option_type.rb +++ b/core/app/models/spree/option_type.rb @@ -2,10 +2,14 @@ module Spree class OptionType < Spree::Base include UniqueName include Metadata + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name presentation].freeze + translates(*TRANSLATABLE_FIELDS) + acts_as_list auto_strip_attributes :name, :presentation diff --git a/core/app/models/spree/option_value.rb b/core/app/models/spree/option_value.rb index ccd0623bdfc..e594d72f2f2 100644 --- a/core/app/models/spree/option_value.rb +++ b/core/app/models/spree/option_value.rb @@ -1,10 +1,14 @@ module Spree class OptionValue < Spree::Base include Metadata + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name presentation].freeze + translates(*TRANSLATABLE_FIELDS) + belongs_to :option_type, class_name: 'Spree::OptionType', touch: true, inverse_of: :option_values acts_as_list scope: :option_type diff --git a/core/db/migrate/20230103144439_create_option_type_translations.rb b/core/db/migrate/20230103144439_create_option_type_translations.rb new file mode 100644 index 00000000000..a1dae72b11a --- /dev/null +++ b/core/db/migrate/20230103144439_create_option_type_translations.rb @@ -0,0 +1,18 @@ +class CreateOptionTypeTranslations < ActiveRecord::Migration[6.1] + def change + create_table :spree_option_type_translations do |t| + + # Translated attribute(s) + t.string :name + t.string :presentation + + t.string :locale, null: false + t.references :spree_option_type, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_option_type_translations, :locale, name: :index_spree_option_type_translations_on_locale + add_index :spree_option_type_translations, [:spree_option_type_id, :locale], name: :unique_option_type_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230103151034_create_option_value_translations.rb b/core/db/migrate/20230103151034_create_option_value_translations.rb new file mode 100644 index 00000000000..61010612d9b --- /dev/null +++ b/core/db/migrate/20230103151034_create_option_value_translations.rb @@ -0,0 +1,18 @@ +class CreateOptionValueTranslations < ActiveRecord::Migration[6.1] + def change + create_table :spree_option_value_translations do |t| + + # Translated attribute(s) + t.string :name + t.string :presentation + + t.string :locale, null: false + t.references :spree_option_value, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_option_value_translations, :locale, name: :index_spree_option_value_translations_on_locale + add_index :spree_option_value_translations, [:spree_option_value_id, :locale], name: :unique_option_value_id_per_locale, unique: true + end +end From 7d58945d945edafd83b0f8b0cf64faa4f9fd2aca Mon Sep 17 00:00:00 2001 From: nciemniak Date: Thu, 5 Jan 2023 11:39:54 +0100 Subject: [PATCH 42/89] method for joining option value translations, plus tests --- .../api/v2/platform/variants_controller.rb | 2 +- .../spree/api/v2/storefront/products_spec.rb | 34 +++++++++++++++---- .../models/concerns/spree/product_scopes.rb | 4 ++- core/app/models/spree/product.rb | 8 +++++ core/app/models/spree/variant.rb | 10 +++--- 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/api/app/controllers/spree/api/v2/platform/variants_controller.rb b/api/app/controllers/spree/api/v2/platform/variants_controller.rb index 93c436b4671..1e3dd715cb9 100644 --- a/api/app/controllers/spree/api/v2/platform/variants_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/variants_controller.rb @@ -16,7 +16,7 @@ def spree_permitted_attributes def collection # if filtering on products, manually join on product translation to workaround mobility-ransack issue if params.key?(:filter) && params[:filter].keys.any? { |k| k.include? 'product' } - @collection ||= scope.join_product_translations. + @collection ||= scope.joins(:product).join_product_translations. ransack(params[:filter]).result else super diff --git a/api/spec/requests/spree/api/v2/storefront/products_spec.rb b/api/spec/requests/spree/api/v2/storefront/products_spec.rb index feb1d1c5b26..013315f7625 100644 --- a/api/spec/requests/spree/api/v2/storefront/products_spec.rb +++ b/api/spec/requests/spree/api/v2/storefront/products_spec.rb @@ -24,6 +24,13 @@ let!(:product_property) { create(:product_property, property: new_property, product: product_with_property, value: 'Some Value') } let!(:product_property2) { create(:product_property, property: property, product: product_with_property, value: 'Some Value 2') } + # create translated resources + let!(:option_type_pl_locale) { Mobility.with_locale(:pl) { create(:option_type) } } + let!(:option_value_pl_locale) { Mobility.with_locale(:pl) { create(:option_value, option_type: option_type_pl_locale) } } + let!(:product_pl_locale) { Mobility.with_locale(:pl) { create(:product, name: 'Produkt Superowy', option_types: [option_type_pl_locale], stores: [store]) } } + let!(:variant_pl_locale) { Mobility.with_locale(:pl) { create(:variant, product: product_pl_locale, option_values: [option_value_pl_locale]) } } + + before { Spree::Api::Config[:api_v2_per_page_limit] = 4 } describe 'products#index' do @@ -151,14 +158,29 @@ end context 'with specified options' do - before { get "/api/v2/storefront/products?filter[options][#{option_type.name}]=#{option_value.name}&include=option_types,variants.option_values" } + context 'with no locale set' do + before { get "/api/v2/storefront/products?filter[options][#{option_type.name}]=#{option_value.name}&include=option_types,variants.option_values" } - it_behaves_like 'returns 200 HTTP status' + it_behaves_like 'returns 200 HTTP status' + + it 'returns products with specified options' do + expect(json_response['data'].first).to have_id(product_with_option.id.to_s) + expect(json_response['included']).to include(have_type('option_type').and(have_attribute(:name).with_value(option_type.name))) + expect(json_response['included']).to include(have_type('option_value').and(have_attribute(:name).with_value(option_value.name))) + end + end + + context 'with locale set to polish' do + before do + store.update_column(:supported_locales, 'en,pl') + get "/api/v2/storefront/products?filter[options][#{option_type_pl_locale.name(locale: :pl)}]=#{option_value_pl_locale.name(locale: :pl)}&include=option_types,variants.option_values&locale=pl" + end - it 'returns products with specified options' do - expect(json_response['data'].first).to have_id(product_with_option.id.to_s) - expect(json_response['included']).to include(have_type('option_type').and(have_attribute(:name).with_value(option_type.name))) - expect(json_response['included']).to include(have_type('option_value').and(have_attribute(:name).with_value(option_value.name))) + it 'returns products with specified options in polish' do + expect(json_response['data'].first).to have_id(product_pl_locale.id.to_s) + expect(json_response['included']).to include(have_type('option_type').and(have_attribute(:name).with_value(option_type_pl_locale.name(locale: :pl)))) + expect(json_response['included']).to include(have_type('option_value').and(have_attribute(:name).with_value(option_value_pl_locale.name(locale: :pl)))) + end end end diff --git a/core/app/models/concerns/spree/product_scopes.rb b/core/app/models/concerns/spree/product_scopes.rb index 340ad930c69..a7685d154a6 100644 --- a/core/app/models/concerns/spree/product_scopes.rb +++ b/core/app/models/concerns/spree/product_scopes.rb @@ -171,7 +171,9 @@ def self.property_conditions(property) group("#{Spree::Product.table_name}.id"). joins(variants_including_master: :option_values). - where(Spree::OptionValue.table_name => { name: value, option_type_id: option_type_id }) + join_option_value_translations. + where(Spree::OptionValue.translation_table_alias => { name: value }, + Spree::OptionValue.table_name => { option_type_id: option_type_id }) end # Finds all products which have either: diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index 80139391722..a5466c5480a 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -294,6 +294,14 @@ def self.like_any(fields, values) where conditions.inject(:or) end + # To be used when joining on option value does not automatically join option value translations + # This method is to be used when you've already joined on the option values table + def self.join_option_value_translations + joins("LEFT OUTER JOIN #{OptionValue::Translation.table_name} #{OptionValue.translation_table_alias} + ON #{OptionValue.translation_table_alias}.spree_option_value_id = #{OptionValue.table_name}.id + AND #{OptionValue.translation_table_alias}.locale = '#{Mobility.locale}'") + end + # Suitable for displaying only variants that has at least one option value. # There may be scenarios where an option type is removed and along with it # all option values. At that point all variants associated with only those diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index 2be9a29c3bb..c3a07be5ef2 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -122,15 +122,17 @@ class Variant < Spree::Base self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku) def self.product_name_or_sku_cont(query) - join_product_translations. + joins(:product).join_product_translations. where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") end + # To be used when joins(:product) does not automatically join product translations + # This method is to be used when you've already joined on the product table def self.join_product_translations - joins(:product).joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} - ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id - AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'") + joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} + ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id + AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'") end def self.search_by_product_name_or_sku(query) From 2a27238c37bc4f271047b2b09536e7f0a67b466d Mon Sep 17 00:00:00 2001 From: nciemniak Date: Thu, 5 Jan 2023 13:35:11 +0100 Subject: [PATCH 43/89] refactor join translation table methods --- .../spree/api/v2/platform/variants_controller.rb | 2 +- core/app/models/concerns/spree/product_scopes.rb | 7 +++++-- .../spree/translatable_resource_scopes.rb | 16 ++++++++++++++++ core/app/models/spree/product.rb | 9 ++------- core/app/models/spree/variant.rb | 11 ++--------- 5 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 core/app/models/concerns/spree/translatable_resource_scopes.rb diff --git a/api/app/controllers/spree/api/v2/platform/variants_controller.rb b/api/app/controllers/spree/api/v2/platform/variants_controller.rb index 1e3dd715cb9..303752deed1 100644 --- a/api/app/controllers/spree/api/v2/platform/variants_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/variants_controller.rb @@ -16,7 +16,7 @@ def spree_permitted_attributes def collection # if filtering on products, manually join on product translation to workaround mobility-ransack issue if params.key?(:filter) && params[:filter].keys.any? { |k| k.include? 'product' } - @collection ||= scope.joins(:product).join_product_translations. + @collection ||= scope.joins(:product).join_translation_table(Product). ransack(params[:filter]).result else super diff --git a/core/app/models/concerns/spree/product_scopes.rb b/core/app/models/concerns/spree/product_scopes.rb index a7685d154a6..d8d60ee9cb2 100644 --- a/core/app/models/concerns/spree/product_scopes.rb +++ b/core/app/models/concerns/spree/product_scopes.rb @@ -151,7 +151,9 @@ def self.property_conditions(property) elsif OptionType.column_for_attribute('id').type == :uuid joins(:option_types).where(spree_option_types: { name: option }).or(Product.joins(:option_types).where(spree_option_types: { id: option })) else - joins(:option_types).where(spree_option_types: { name: option }) + joins(:option_types). + join_translation_table(OptionType). + where(OptionType.translation_table_alias => { name: option }) end end @@ -164,6 +166,7 @@ def self.property_conditions(property) OptionType.where(id: option).or(OptionType.where(name: option))&.first&.id else OptionType.where(name: option)&.first&.id + OptionType.where(name: option)&.first&.id end end @@ -171,7 +174,7 @@ def self.property_conditions(property) group("#{Spree::Product.table_name}.id"). joins(variants_including_master: :option_values). - join_option_value_translations. + join_translation_table(Spree::OptionValue). where(Spree::OptionValue.translation_table_alias => { name: value }, Spree::OptionValue.table_name => { option_type_id: option_type_id }) end diff --git a/core/app/models/concerns/spree/translatable_resource_scopes.rb b/core/app/models/concerns/spree/translatable_resource_scopes.rb new file mode 100644 index 00000000000..d7f2d75ed17 --- /dev/null +++ b/core/app/models/concerns/spree/translatable_resource_scopes.rb @@ -0,0 +1,16 @@ +module Spree + module TranslatableResourceScopes + extend ActiveSupport::Concern + + class_methods do + # To be used when joining on the resource itself does not automatically join on its translations table + # This method is to be used when you've already joined on the translatable table itself + def join_translation_table(translatable_class) + translatable_class_foreign_key = "#{translatable_class.table_name.singularize}_id" + joins("LEFT OUTER JOIN #{translatable_class::Translation.table_name} #{translatable_class.translation_table_alias} + ON #{translatable_class.translation_table_alias}.#{translatable_class_foreign_key} = #{translatable_class.table_name}.id + AND #{translatable_class.translation_table_alias}.locale = '#{Mobility.locale}'") + end + end + end +end diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index a5466c5480a..0974c8ed9ed 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -24,6 +24,7 @@ class Product < Spree::Base include ProductScopes include MultiStoreResource include TranslatableResource + include TranslatableResourceScopes include MemoizedData include Metadata if defined?(Spree::Webhooks) @@ -294,13 +295,7 @@ def self.like_any(fields, values) where conditions.inject(:or) end - # To be used when joining on option value does not automatically join option value translations - # This method is to be used when you've already joined on the option values table - def self.join_option_value_translations - joins("LEFT OUTER JOIN #{OptionValue::Translation.table_name} #{OptionValue.translation_table_alias} - ON #{OptionValue.translation_table_alias}.spree_option_value_id = #{OptionValue.table_name}.id - AND #{OptionValue.translation_table_alias}.locale = '#{Mobility.locale}'") - end + # Suitable for displaying only variants that has at least one option value. # There may be scenarios where an option type is removed and along with it diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index c3a07be5ef2..0e2f44d8465 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -5,6 +5,7 @@ class Variant < Spree::Base include MemoizedData include Metadata + include TranslatableResourceScopes if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end @@ -122,19 +123,11 @@ class Variant < Spree::Base self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku) def self.product_name_or_sku_cont(query) - joins(:product).join_product_translations. + joins(:product).join_translation_table(Product). where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") end - # To be used when joins(:product) does not automatically join product translations - # This method is to be used when you've already joined on the product table - def self.join_product_translations - joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} - ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id - AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'") - end - def self.search_by_product_name_or_sku(query) product_name_or_sku_cont(query) end From de6fc20515604f9e7289b53c301142bba4a6a921 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Thu, 5 Jan 2023 15:27:31 +0100 Subject: [PATCH 44/89] valid create params for failing 'resource created' tests --- .../api/v2/platform/option_types_spec.rb | 8 +++++++- .../api/v2/platform/option_values_spec.rb | 9 ++++++++- .../spree/api/v2/storefront/products_spec.rb | 20 ++++++++++--------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/api/spec/integration/api/v2/platform/option_types_spec.rb b/api/spec/integration/api/v2/platform/option_types_spec.rb index b76c9fe7f31..f98173bad25 100644 --- a/api/spec/integration/api/v2/platform/option_types_spec.rb +++ b/api/spec/integration/api/v2/platform/option_types_spec.rb @@ -12,7 +12,13 @@ let(:id) { create(:option_type).id } let(:option_type) { create(:option_type) } let(:records_list) { create_list(:option_type, 2) } - let(:valid_create_param_value) { build(:option_type).attributes } + let(:valid_create_param_value) do + { + name: 'option type name', + presentation: 'Option Type', + position: 10 + } + end let(:valid_update_param_value) do { name: 'Size-X' diff --git a/api/spec/integration/api/v2/platform/option_values_spec.rb b/api/spec/integration/api/v2/platform/option_values_spec.rb index 72dec896bff..55be093fc08 100644 --- a/api/spec/integration/api/v2/platform/option_values_spec.rb +++ b/api/spec/integration/api/v2/platform/option_values_spec.rb @@ -13,7 +13,14 @@ let(:id) { create(:option_value).id } let(:option_type) { create(:option_value) } let(:records_list) { create_list(:option_value, 2) } - let(:valid_create_param_value) { build(:option_value).attributes } + let(:valid_create_param_value) do + { + name: 'option value', + presentation: 'Option Value', + position: 10, + option_type_id: option_type.id + } + end let(:valid_update_param_value) do { name: 'M' diff --git a/api/spec/requests/spree/api/v2/storefront/products_spec.rb b/api/spec/requests/spree/api/v2/storefront/products_spec.rb index 013315f7625..99b821b5ad1 100644 --- a/api/spec/requests/spree/api/v2/storefront/products_spec.rb +++ b/api/spec/requests/spree/api/v2/storefront/products_spec.rb @@ -8,9 +8,9 @@ let(:product_with_taxon) { create(:product, taxons: [taxon], stores: [store]) } let(:product_with_name) { create(:product, name: 'Test Product', stores: [store]) } let(:product_with_price) { create(:product, price: 13.44, stores: [store]) } - let!(:option_type) { create(:option_type) } + let!(:option_type) { create(:option_type, name: 'test option type') } let!(:option_value) { create(:option_value, option_type: option_type) } - let(:product_with_option) { create(:product, option_types: [option_type], stores: [store]) } + let(:product_with_option) { create(:product, name: 'Product with Option', option_types: [option_type], stores: [store]) } let!(:variant) { create(:variant, product: product_with_option, option_values: [option_value]) } let(:product) { create(:product, stores: [store]) } let!(:deleted_product) { create(:product, deleted_at: Time.current - 1.day, stores: [store]) } @@ -24,12 +24,6 @@ let!(:product_property) { create(:product_property, property: new_property, product: product_with_property, value: 'Some Value') } let!(:product_property2) { create(:product_property, property: property, product: product_with_property, value: 'Some Value 2') } - # create translated resources - let!(:option_type_pl_locale) { Mobility.with_locale(:pl) { create(:option_type) } } - let!(:option_value_pl_locale) { Mobility.with_locale(:pl) { create(:option_value, option_type: option_type_pl_locale) } } - let!(:product_pl_locale) { Mobility.with_locale(:pl) { create(:product, name: 'Produkt Superowy', option_types: [option_type_pl_locale], stores: [store]) } } - let!(:variant_pl_locale) { Mobility.with_locale(:pl) { create(:variant, product: product_pl_locale, option_values: [option_value_pl_locale]) } } - before { Spree::Api::Config[:api_v2_per_page_limit] = 4 } @@ -159,7 +153,9 @@ context 'with specified options' do context 'with no locale set' do - before { get "/api/v2/storefront/products?filter[options][#{option_type.name}]=#{option_value.name}&include=option_types,variants.option_values" } + before { + get "/api/v2/storefront/products?filter[options][#{option_type.name}]=#{option_value.name}&include=option_types,variants.option_values" + } it_behaves_like 'returns 200 HTTP status' @@ -171,6 +167,12 @@ end context 'with locale set to polish' do + # create translated resources + let!(:option_type_pl_locale) { Mobility.with_locale(:pl) { create(:option_type) } } + let!(:option_value_pl_locale) { Mobility.with_locale(:pl) { create(:option_value, option_type: option_type_pl_locale) } } + let!(:product_pl_locale) { Mobility.with_locale(:pl) { create(:product, name: 'Produkt Superowy', option_types: [option_type_pl_locale], stores: [store]) } } + let!(:variant_pl_locale) { Mobility.with_locale(:pl) { create(:variant, product: product_pl_locale, option_values: [option_value_pl_locale]) } } + before do store.update_column(:supported_locales, 'en,pl') get "/api/v2/storefront/products?filter[options][#{option_type_pl_locale.name(locale: :pl)}]=#{option_value_pl_locale.name(locale: :pl)}&include=option_types,variants.option_values&locale=pl" From 062f1d739951469c6270071604ee9165c6f2f9ef Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 9 Jan 2023 09:41:19 +0100 Subject: [PATCH 45/89] update brakeman.ignore --- core/brakeman.ignore | 54 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/core/brakeman.ignore b/core/brakeman.ignore index 24e68c5178a..7c8f15f15a0 100644 --- a/core/brakeman.ignore +++ b/core/brakeman.ignore @@ -23,29 +23,6 @@ ], "note": "interpolating table name" }, - { - "warning_type": "SQL Injection", - "warning_code": 0, - "fingerprint": "67e80bcfa8898315b0f8642bc61990c8319bc7a468453efc048757a439e53e0b", - "check_name": "SQL", - "message": "Possible SQL injection", - "file": "app/models/spree/variant.rb", - "line": 131, - "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", - "code": "joins(:product).joins(\"LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias}\\n ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id\\n AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'\")", - "render_path": null, - "location": { - "type": "method", - "class": "Spree::Variant", - "method": "Spree::Variant.join_product_translations" - }, - "user_input": "Product.translation_table_alias", - "confidence": "Weak", - "cwe_id": [ - 89 - ], - "note": "" - }, { "warning_type": "SQL Injection", "warning_code": 0, @@ -95,13 +72,13 @@ { "warning_type": "SQL Injection", "warning_code": 0, - "fingerprint": "e6bb08c012af19589f796e26b4dc2230ea1c1949c7cd6464ca025939513d8b3d", + "fingerprint": "c2bc48d98076b7c4fc3314c6a85f7bd1132efe5fcc346da4d28df7c25f93633f", "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/spree/variant.rb", - "line": 126, + "line": 127, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", - "code": "join_product_translations.where(\"LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)\\n OR LOWER(sku) LIKE LOWER(:query)\", :query => (\"%#{query}%\"))", + "code": "joins(:product).join_translation_table(Product).where(\"LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)\\n OR LOWER(sku) LIKE LOWER(:query)\", :query => (\"%#{query}%\"))", "render_path": null, "location": { "type": "method", @@ -115,6 +92,29 @@ ], "note": "" }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "e7aa61c87f26af5f63f00e9791954d787a559dab1eadf8926861796237bd431d", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/translatable_resource_scopes.rb", + "line": 10, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(\"LEFT OUTER JOIN #{translatable_class::Translation.table_name} #{translatable_class.translation_table_alias}\\n ON #{translatable_class.translation_table_alias}.#{\"#{translatable_class.table_name.singularize}_id\"} = #{translatable_class.table_name}.id\\n AND #{translatable_class.translation_table_alias}.locale = '#{Mobility.locale}'\")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::TranslatableResourceScopes", + "method": "join_translation_table" + }, + "user_input": "translatable_class.translation_table_alias", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + }, { "warning_type": "SQL Injection", "warning_code": 0, @@ -139,6 +139,6 @@ "note": "interpolating table name" } ], - "updated": "2022-12-29 09:32:26 +0100", + "updated": "2023-01-09 09:40:32 +0100", "brakeman_version": "5.4.0" } From 8f2e005bbc14b4162b334d5de79030ab0d029db3 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 9 Jan 2023 11:58:44 +0100 Subject: [PATCH 46/89] migration for transferring option data to translations table --- ...fer_options_data_to_translatable_tables.rb | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb diff --git a/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb new file mode 100644 index 00000000000..f440f422716 --- /dev/null +++ b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb @@ -0,0 +1,47 @@ +class TransferOptionsDataToTranslatableTables < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + + def up + # Option Types + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_option_type_translations (name, presentation, locale, spree_option_type_id, created_at, updated_at) + SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_option_types; + + UPDATE spree_option_types + SET name=null, presentation=null; + ") + + # Option Values + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_option_value_translations (name, presentation, locale, spree_option_value_id, created_at, updated_at) + SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_option_values; + + UPDATE spree_option_values + SET name=null, presentation=null; + ") + end + + def down + # Option Types + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_types as option_types + SET (name, presentation) = (t_option_types.name, t_option_types.presentation) + FROM spree_option_type_translations AS t_option_types + WHERE t_option_types.spree_option_type_id = option_types.id; + + TRUNCATE TABLE spree_option_type_translations; + ") + + # Option Values + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_values as option_values + SET (name, presentation) = (t_option_values.name, t_option_values.presentation) + FROM spree_option_value_translations AS t_option_values + WHERE t_option_values.spree_option_value_id = option_values.id; + + TRUNCATE TABLE spree_option_value_translations; + ") + end +end From ad486602fa373bfc81e4fa607c9fb205550c38fd Mon Sep 17 00:00:00 2001 From: Rafal Kosla Date: Mon, 9 Jan 2023 12:16:48 +0100 Subject: [PATCH 47/89] WIP Add menus to translatable resources --- core/app/models/spree/menu.rb | 4 ++++ core/app/models/spree/menu_item.rb | 4 ++++ ...reate_menu_translation_table_for_mobility.rb | 15 +++++++++++++++ .../20221219132250_enable_menus_without_name.rb | 5 +++++ ..._menu_item_translation_table_for_mobility.rb | 17 +++++++++++++++++ ...1219145736_enable_menu_items_without_name.rb | 5 +++++ 6 files changed, 50 insertions(+) create mode 100644 core/db/migrate/20221219132220_create_menu_translation_table_for_mobility.rb create mode 100644 core/db/migrate/20221219132250_enable_menus_without_name.rb create mode 100644 core/db/migrate/20221219145723_create_menu_item_translation_table_for_mobility.rb create mode 100644 core/db/migrate/20221219145736_enable_menu_items_without_name.rb diff --git a/core/app/models/spree/menu.rb b/core/app/models/spree/menu.rb index 7705f442dd0..b81f7a9dc3e 100644 --- a/core/app/models/spree/menu.rb +++ b/core/app/models/spree/menu.rb @@ -1,6 +1,7 @@ module Spree class Menu < Spree::Base include SingleStoreResource + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end @@ -13,6 +14,9 @@ class Menu < Spree::Base MENU_LOCATIONS_PARAMETERIZED << parameterize_location end + TRANSLATABLE_FIELDS = %i[name] + translates *TRANSLATABLE_FIELDS + has_many :menu_items, dependent: :destroy, class_name: 'Spree::MenuItem' belongs_to :store, touch: true, class_name: 'Spree::Store' diff --git a/core/app/models/spree/menu_item.rb b/core/app/models/spree/menu_item.rb index 0d3503a46b2..1d2ff2fd448 100644 --- a/core/app/models/spree/menu_item.rb +++ b/core/app/models/spree/menu_item.rb @@ -1,12 +1,16 @@ module Spree class MenuItem < Spree::Base include Spree::DisplayLink + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end acts_as_nested_set dependent: :destroy + TRANSLATABLE_FIELDS = %i[name subtitle destination] + translates *TRANSLATABLE_FIELDS + ITEM_TYPE = %w[Link Container] LINKED_RESOURCE_TYPE = ['Spree::Linkable::Uri', 'Spree::Linkable::Homepage', 'Spree::Product', 'Spree::Taxon', 'Spree::CmsPage'] diff --git a/core/db/migrate/20221219132220_create_menu_translation_table_for_mobility.rb b/core/db/migrate/20221219132220_create_menu_translation_table_for_mobility.rb new file mode 100644 index 00000000000..7a6fb1eba94 --- /dev/null +++ b/core/db/migrate/20221219132220_create_menu_translation_table_for_mobility.rb @@ -0,0 +1,15 @@ +class CreateMenuTranslationTableForMobility < ActiveRecord::Migration[7.0] + def change + create_table :spree_menu_translations do |t| + # Translated attribute(s) + t.string :name + + t.string :locale, null: false + t.references :spree_menu, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + add_index :spree_menu_translations, :locale, name: :index_spree_menu_translations_on_locale + add_index :spree_menu_translations, [:spree_menu_id, :locale], name: :index_spree_menu_translations_on_spree_menu_id_and_locale, unique: true + end +end diff --git a/core/db/migrate/20221219132250_enable_menus_without_name.rb b/core/db/migrate/20221219132250_enable_menus_without_name.rb new file mode 100644 index 00000000000..9fd3c3ddd12 --- /dev/null +++ b/core/db/migrate/20221219132250_enable_menus_without_name.rb @@ -0,0 +1,5 @@ +class EnableMenusWithoutName < ActiveRecord::Migration[7.0] + def change + change_column_null :spree_menus, :name, true + end +end diff --git a/core/db/migrate/20221219145723_create_menu_item_translation_table_for_mobility.rb b/core/db/migrate/20221219145723_create_menu_item_translation_table_for_mobility.rb new file mode 100644 index 00000000000..ba39820bb78 --- /dev/null +++ b/core/db/migrate/20221219145723_create_menu_item_translation_table_for_mobility.rb @@ -0,0 +1,17 @@ +class CreateMenuItemTranslationTableForMobility < ActiveRecord::Migration[7.0] + def change + create_table :spree_menu_item_translations do |t| + # Translated attribute(s) + t.string :name + t.string :subtitle + t.string :destination + + t.string :locale, null: false + t.references :spree_menu_item, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + add_index :spree_menu_item_translations, :locale, name: :index_spree_menu_item_translations_on_locale + add_index :spree_menu_item_translations, [:spree_menu_item_id, :locale], name: :index_2f1dad0e4e37c6c8147f0b351198ff0013d9cc4d, unique: true + end +end diff --git a/core/db/migrate/20221219145736_enable_menu_items_without_name.rb b/core/db/migrate/20221219145736_enable_menu_items_without_name.rb new file mode 100644 index 00000000000..4b3b613fe91 --- /dev/null +++ b/core/db/migrate/20221219145736_enable_menu_items_without_name.rb @@ -0,0 +1,5 @@ +class EnableMenuItemsWithoutName < ActiveRecord::Migration[7.0] + def change + change_column_null :spree_menu_items, :name, true + end +end From b432cb4683d6bd9524b996a88cae039f75045852 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 9 Jan 2023 13:02:31 +0100 Subject: [PATCH 48/89] property and product property translations --- ...53_create_product_property_translations.rb | 17 +++++++ ...0109105943_create_property_translations.rb | 18 +++++++ ...er_property_data_to_translatable_tables.rb | 51 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 core/db/migrate/20230109084253_create_product_property_translations.rb create mode 100644 core/db/migrate/20230109105943_create_property_translations.rb create mode 100644 core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb diff --git a/core/db/migrate/20230109084253_create_product_property_translations.rb b/core/db/migrate/20230109084253_create_product_property_translations.rb new file mode 100644 index 00000000000..9e61866bb01 --- /dev/null +++ b/core/db/migrate/20230109084253_create_product_property_translations.rb @@ -0,0 +1,17 @@ +class CreateProductPropertyTranslations < ActiveRecord::Migration[6.1] + def change + create_table :spree_product_property_translations do |t| + # Translated attribute(s) + t.string :value + t.string :filter_param + + t.string :locale, null: false + t.references :spree_product_property, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_product_property_translations, :locale, name: :index_spree_product_property_translations_on_locale + add_index :spree_product_property_translations, [:spree_product_property_id, :locale], name: :unique_product_property_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230109105943_create_property_translations.rb b/core/db/migrate/20230109105943_create_property_translations.rb new file mode 100644 index 00000000000..ea9be5f03e5 --- /dev/null +++ b/core/db/migrate/20230109105943_create_property_translations.rb @@ -0,0 +1,18 @@ +class CreatePropertyTranslations < ActiveRecord::Migration[7.0] + def change + create_table :spree_property_translations do |t| + # Translated attribute(s) + t.string :name + t.string :presentation + t.string :filter_param + + t.string :locale, null: false + t.references :spree_property, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_property_translations, :locale, name: :index_spree_property_translations_on_locale + add_index :spree_property_translations, [:spree_property_id, :locale], name: :unique_property_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb new file mode 100644 index 00000000000..4c975d562cb --- /dev/null +++ b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb @@ -0,0 +1,51 @@ +class TransferPropertyDataToTranslatableTables < ActiveRecord::Migration[7.0] + DEFAULT_LOCALE = 'en' + + def up + # Properties + change_column_null :spree_properties, :presentation, true + + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_property_translations (name, presentation, filter_param, locale, spree_property_id, created_at, updated_at) + SELECT name, presentation, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_properties; + + UPDATE spree_properties + SET name=null, presentation=null, filter_param=null; + ") + + # Product Properties + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_product_property_translations (value, filter_param, locale, spree_product_property_id, created_at, updated_at) + SELECT value, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_product_properties; + + UPDATE spree_product_properties + SET value=null, filter_param=null; + ") + end + + def down + # Properties + change_column_null :spree_properties, :presentation, false + + ActiveRecord::Base.connection.execute(" + UPDATE spree_properties AS properties + SET (name, presentation, filter_param) = (t_properties.name, t_properties.presentation, t_properties.filter_param) + FROM spree_property_translations AS t_properties + WHERE t_properties.spree_property_id = properties.id; + + TRUNCATE TABLE spree_property_translations; + ") + + # Product Properties + ActiveRecord::Base.connection.execute(" + UPDATE spree_product_properties AS product_properties + SET (value, filter_param) = (t_product_properties.value, t_product_properties.filter_param) + FROM spree_product_property_translations AS t_product_properties + WHERE t_product_properties.spree_product_property_id = product_properties.id; + + TRUNCATE TABLE spree_product_property_translations; + ") + end +end From c3df8d53056214ebc99187eb14d26a8e8cea309f Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 9 Jan 2023 13:03:46 +0100 Subject: [PATCH 49/89] make option type sample compatible w mobility --- sample/db/samples/option_types.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sample/db/samples/option_types.rb b/sample/db/samples/option_types.rb index 919d55e8e00..33f14de05e8 100644 --- a/sample/db/samples/option_types.rb +++ b/sample/db/samples/option_types.rb @@ -17,5 +17,7 @@ ] option_types_attributes.each do |attrs| - Spree::OptionType.where(attrs).first_or_create! + unless Spree::OptionType.where(attrs).exists? + Spree::OptionType.create!(attrs) + end end From 05cdc4e62f623289ea278e70bf199f33c99528d8 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 9 Jan 2023 13:04:01 +0100 Subject: [PATCH 50/89] change rails migration version to 6.1 --- core/db/migrate/20230109105943_create_property_translations.rb | 2 +- ...30109110840_transfer_property_data_to_translatable_tables.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/db/migrate/20230109105943_create_property_translations.rb b/core/db/migrate/20230109105943_create_property_translations.rb index ea9be5f03e5..6db3a20aad7 100644 --- a/core/db/migrate/20230109105943_create_property_translations.rb +++ b/core/db/migrate/20230109105943_create_property_translations.rb @@ -1,4 +1,4 @@ -class CreatePropertyTranslations < ActiveRecord::Migration[7.0] +class CreatePropertyTranslations < ActiveRecord::Migration[6.1] def change create_table :spree_property_translations do |t| # Translated attribute(s) diff --git a/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb index 4c975d562cb..3d1dcb682a3 100644 --- a/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb +++ b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb @@ -1,4 +1,4 @@ -class TransferPropertyDataToTranslatableTables < ActiveRecord::Migration[7.0] +class TransferPropertyDataToTranslatableTables < ActiveRecord::Migration[6.1] DEFAULT_LOCALE = 'en' def up From 62cdf6bbd89a2b14e9ef7f3c945f48bad90a9ce1 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 9 Jan 2023 13:33:39 +0100 Subject: [PATCH 51/89] add translations to property models + update relevant product scope methods --- .../models/concerns/spree/product_scopes.rb | 23 +++++++++++-------- core/app/models/spree/product_property.rb | 4 ++++ core/app/models/spree/property.rb | 4 ++++ sample/db/samples/properties.rb | 4 +++- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/core/app/models/concerns/spree/product_scopes.rb b/core/app/models/concerns/spree/product_scopes.rb index d8d60ee9cb2..effe62f8caa 100644 --- a/core/app/models/concerns/spree/product_scopes.rb +++ b/core/app/models/concerns/spree/product_scopes.rb @@ -32,15 +32,16 @@ def self.add_simple_scopes(scopes) end def self.property_conditions(property) - properties = Property.table_name + properties_table = Property.table_name + property_translations_table = Property.translation_table_alias case property - when Property then { "#{properties}.id" => property.id } - when Integer then { "#{properties}.id" => property } + when Property then { "#{properties_table}.id" => property.id } + when Integer then { "#{properties_table}.id" => property } else if Property.column_for_attribute('id').type == :uuid - ["#{properties.name} = ? OR #{properties.id} = ?", property, property] + ["#{property_translations_table.name} = ? OR #{properties_table.id} = ?", property, property] else - { "#{properties}.name" => property } + { "#{property_translations_table}.name" => property } end end end @@ -126,21 +127,25 @@ def self.property_conditions(property) # a scope that finds all products having property specified by name, object or id add_search_scope :with_property do |property| - joins(:properties).where(property_conditions(property)) + joins(:properties).join_translation_table(Property).where(property_conditions(property)) end # a simple test for product with a certain property-value pairing # note that it can test for properties with NULL values, but not for absent values add_search_scope :with_property_value do |property, value| joins(:properties). - where("#{ProductProperty.table_name}.value = ?", value). + join_translation_table(Property). + join_translation_table(ProductProperty). + where("#{ProductProperty.translation_table_alias}.value = ?", value). where(property_conditions(property)) end add_search_scope :with_property_values do |property_filter_param, property_values| joins(product_properties: :property). - where(Property.table_name => { filter_param: property_filter_param }). - where(ProductProperty.table_name => { filter_param: property_values.map(&:parameterize) }) + join_translation_table(Property). + join_translation_table(ProductProperty). + where(Property.translation_table_alias => { filter_param: property_filter_param }). + where(ProductProperty.translation_table_alias => { filter_param: property_values.map(&:parameterize) }) end add_search_scope :with_option do |option| diff --git a/core/app/models/spree/product_property.rb b/core/app/models/spree/product_property.rb index 785cb7bba0f..0c363efdc30 100644 --- a/core/app/models/spree/product_property.rb +++ b/core/app/models/spree/product_property.rb @@ -1,6 +1,10 @@ module Spree class ProductProperty < Spree::Base include Spree::FilterParam + include TranslatableResource + + TRANSLATABLE_FIELDS = %i[value filter_param].freeze + translates(*TRANSLATABLE_FIELDS) auto_strip_attributes :value diff --git a/core/app/models/spree/property.rb b/core/app/models/spree/property.rb index 026522ea904..c91338d7ef5 100644 --- a/core/app/models/spree/property.rb +++ b/core/app/models/spree/property.rb @@ -2,10 +2,14 @@ module Spree class Property < Spree::Base include Spree::FilterParam include Metadata + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name presentation filter_param].freeze + translates(*TRANSLATABLE_FIELDS) + auto_strip_attributes :name, :presentation has_many :property_prototypes, class_name: 'Spree::PropertyPrototype' diff --git a/sample/db/samples/properties.rb b/sample/db/samples/properties.rb index cd4fd5156b6..1c503d4bf54 100644 --- a/sample/db/samples/properties.rb +++ b/sample/db/samples/properties.rb @@ -14,7 +14,9 @@ } properties.each do |name, presentation| - Spree::Property.where(name: name, presentation: presentation).first_or_create! + unless Spree::Property.where(name: name, presentation: presentation).exists? + Spree::Property.create!(name: name, presentation: presentation) + end end Spree::Property.where(name: %w[brand manufacturer]).update(filterable: true) From 22289fb033c5156a9e6fe7c4d116577612fb34ef Mon Sep 17 00:00:00 2001 From: Rafal Cymerys Date: Mon, 9 Jan 2023 17:49:20 +0100 Subject: [PATCH 52/89] Run tests with Ruby 3.1 --- .circleci/config.yml | 75 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f7fc48736e4..db34ab4615d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,6 +17,12 @@ defaults_3_0: &defaults_3_0 <<: *defaults docker: - image: &ruby_3_0_image circleci/ruby:3.0-node-browsers + - image: &redis_image circleci/redis:6.2-alpine + +defaults_3_1: &defaults_3_1 + <<: *defaults + docker: + - image: &ruby_3_1_image cimg/ruby:3.1.3-browsers - image: *redis_image run_tests_2_7: &run_tests_2_7 @@ -77,6 +83,35 @@ run_tests_3_0: &run_tests_3_0 - store_test_results: path: /tmp/test-results +run_tests_3_1: &run_tests_3_1 + <<: *defaults_3_1 + parallelism: 3 + steps: + - checkout + - restore_cache: + keys: + - spree-bundle-v10-ruby-3-1-{{ .Branch }} + - spree-bundle-v10-ruby-3-1 + - run: + name: Install libvips + command: sudo apt-get update && sudo apt-get install libvips42 + - run: + name: Ensure Bundle Install + command: | + bundle install + ./bin/build-ci.rb install + - run: + name: Run rspec in parallel + command: ./bin/build-ci.rb test + - store_artifacts: + path: /tmp/test-artifacts + destination: test-artifacts + - store_artifacts: + path: /tmp/test-results + destination: raw-test-output + - store_test_results: + path: /tmp/test-results + jobs: bundle_ruby_2_7: <<: *defaults @@ -100,7 +135,7 @@ jobs: - ~/spree/vendor/bundle bundle_ruby_3_0: - <<: *defaults_3_0 + <<: *defaults steps: - checkout - restore_cache: @@ -120,8 +155,29 @@ jobs: paths: - ~/spree/vendor/bundle + bundle_ruby_3_1: + <<: *defaults_3_1 + steps: + - checkout + - restore_cache: + keys: + - spree-bundle-v10-ruby-3-1-{{ .Branch }} + - spree-bundle-v10-ruby-3-1 + - run: + name: Install libvips + command: sudo apt-get update && sudo apt-get install libvips42 + - run: + name: Bundle Install + command: | + bundle check || bundle install + ./bin/build-ci.rb install + - save_cache: + key: spree-bundle-v10-ruby-3-1-{{ .Branch }}-{{ checksum "Gemfile.lock" }} + paths: + - ~/spree/vendor/bundle + brakeman: - <<: *defaults_3_0 + <<: *defaults_3_1 steps: - checkout - restore_cache: @@ -169,6 +225,15 @@ jobs: - image: *postgres_image - image: *redis_image + tests_ruby_3_1_rails_7_0_postgres: + <<: *run_tests_3_1 + environment: + <<: *postgres_environment + docker: + - image: *ruby_3_1_image + - image: *postgres_image + - image: *redis_image + tests_ruby_2_7_rails_6_1_postgres: <<: *tests_ruby_2_7_rails_7_0_postgres environment: @@ -255,9 +320,13 @@ workflows: jobs: - bundle_ruby_2_7 - bundle_ruby_3_0 + - bundle_ruby_3_1 - brakeman: requires: - - bundle_ruby_3_0 + - bundle_ruby_3_1 + - tests_ruby_3_1_rails_7_0_postgres: + requires: + - bundle_ruby_3_1 - tests_ruby_3_0_rails_7_0_postgres: requires: - bundle_ruby_3_0 From 1c62811e1e03a7bcd9ef38cd5fa429974822a094 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 9 Jan 2023 18:04:35 +0100 Subject: [PATCH 53/89] apply auto_strip to translation class, plus other test fixes --- core/app/models/spree/product_property.rb | 4 +++- core/app/models/spree/property.rb | 4 +++- core/spec/models/spree/property_spec.rb | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/app/models/spree/product_property.rb b/core/app/models/spree/product_property.rb index 0c363efdc30..79befe46dc1 100644 --- a/core/app/models/spree/product_property.rb +++ b/core/app/models/spree/product_property.rb @@ -6,7 +6,9 @@ class ProductProperty < Spree::Base TRANSLATABLE_FIELDS = %i[value filter_param].freeze translates(*TRANSLATABLE_FIELDS) - auto_strip_attributes :value + self::Translation.class_eval do + auto_strip_attributes :value + end acts_as_list scope: :product diff --git a/core/app/models/spree/property.rb b/core/app/models/spree/property.rb index c91338d7ef5..73e2f55a49f 100644 --- a/core/app/models/spree/property.rb +++ b/core/app/models/spree/property.rb @@ -10,7 +10,9 @@ class Property < Spree::Base TRANSLATABLE_FIELDS = %i[name presentation filter_param].freeze translates(*TRANSLATABLE_FIELDS) - auto_strip_attributes :name, :presentation + self::Translation.class_eval do + auto_strip_attributes :name, :presentation + end has_many :property_prototypes, class_name: 'Spree::PropertyPrototype' has_many :prototypes, through: :property_prototypes, class_name: 'Spree::Prototype' diff --git a/core/spec/models/spree/property_spec.rb b/core/spec/models/spree/property_spec.rb index 377f21832f1..ed837dc3fb4 100644 --- a/core/spec/models/spree/property_spec.rb +++ b/core/spec/models/spree/property_spec.rb @@ -84,8 +84,10 @@ let(:product_property_2) { create(:product_property, property: property, product: product_2, value: 'Test Test') } before do - product_property.update_column(:filter_param, nil) - product_property.update_column(:value, 'some value') + product_property.translations.each do |t| + t.update_column(:filter_param, nil) + t.update_column(:value, 'some_value') + end end context 'filterable property' do From 10245fed2b67cce371ae4f4b8ee7d203b6c7b7b2 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 9 Jan 2023 18:34:59 +0100 Subject: [PATCH 54/89] fix broken tests due to Mobility quirks --- core/app/models/spree/product.rb | 23 ++++++++++++++++++----- core/app/models/spree/product_property.rb | 8 ++++++-- core/spec/models/spree/product_spec.rb | 2 +- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index 0974c8ed9ed..d0975f64b2e 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -40,7 +40,8 @@ class Product < Spree::Base TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_keywords meta_title].freeze translates(*TRANSLATABLE_FIELDS) - Product::Translation.class_eval { acts_as_paranoid } + + self::Translation.class_eval { acts_as_paranoid } friendly_id :slug_candidates, use: [:history, :mobility] acts_as_paranoid @@ -313,14 +314,26 @@ def empty_option_values? end def property(property_name) - product_properties.joins(:property).find_by(spree_properties: { name: property_name }).try(:value) + product_properties.joins(:property). + join_translation_table(Property). + find_by(Property.translation_table_alias => { name: property_name }).try(:value) end def set_property(property_name, property_value, property_presentation = property_name) ApplicationRecord.transaction do - # Works around spree_i18n #301 - property = Property.create_with(presentation: property_presentation).find_or_create_by(name: property_name) - product_property = ProductProperty.where(product: self, property: property).first_or_initialize + # Manual first_or_create to work around Mobility bug + property = if Property.where(name: property_name).exists? + Property.where(name: property_name).first + else + Property.create(name: property_name, presentation: property_presentation) + end + + product_property = if ProductProperty.where(product: self, property: property).exists? + ProductProperty.where(product: self, property: property).first + else + ProductProperty.create(product: self, property: property) + end + product_property.value = property_value product_property.save! end diff --git a/core/app/models/spree/product_property.rb b/core/app/models/spree/product_property.rb index 79befe46dc1..58dec2bf853 100644 --- a/core/app/models/spree/product_property.rb +++ b/core/app/models/spree/product_property.rb @@ -2,6 +2,7 @@ module Spree class ProductProperty < Spree::Base include Spree::FilterParam include TranslatableResource + include TranslatableResourceScopes TRANSLATABLE_FIELDS = %i[value filter_param].freeze translates(*TRANSLATABLE_FIELDS) @@ -37,8 +38,11 @@ def property_name=(name) `ProductProperty#property_name=` is deprecated and will be removed in Spree 5.0. DEPRECATION if name.present? - # don't use `find_by :name` to workaround globalize/globalize#423 bug - self.property = Property.where(name: name).first_or_create(presentation: name) + self.property = if Property.where(name: name).exists? + Property.where(name: name).first + else + Property.create(name: name, presentation: name) + end end end diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb index 455354d39de..dc7078fd17e 100644 --- a/core/spec/models/spree/product_spec.rb +++ b/core/spec/models/spree/product_spec.rb @@ -433,7 +433,7 @@ class Extension < Spree::Base # Regression test for #2455 it "does not overwrite properties' presentation names" do - Spree::Property.where(name: 'foo').first_or_create!(presentation: "Foo's Presentation Name") + Spree::Property.create!(name: 'foo', presentation: "Foo's Presentation Name") product.set_property('foo', 'value1') product.set_property('bar', 'value2') expect(Spree::Property.where(name: 'foo').first.presentation).to eq("Foo's Presentation Name") From 307d6ef8f4eed826b9a8570caea2ae65e7718cdb Mon Sep 17 00:00:00 2001 From: nciemniak Date: Tue, 10 Jan 2023 16:39:58 +0100 Subject: [PATCH 55/89] update filter by brand to work with mobility --- core/lib/spree/core/product_filters.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/lib/spree/core/product_filters.rb b/core/lib/spree/core/product_filters.rb index de24f226d8d..2c586c4c854 100644 --- a/core/lib/spree/core/product_filters.rb +++ b/core/lib/spree/core/product_filters.rb @@ -98,18 +98,21 @@ def self.price_filter conds.each do |new_scope| scope = scope.or(new_scope) end - Spree::Product.with_property('brand').where(scope) + Product.with_property('brand').join_translation_table(ProductProperty).where(scope) end def self.brand_filter brand_property = Spree::Property.find_by(name: 'brand') brands = brand_property ? Spree::ProductProperty.where(property_id: brand_property.id).pluck(:value).uniq.map(&:to_s) : [] - pp = Spree::ProductProperty.arel_table - conds = Hash[*brands.map { |b| [b, pp[:value].eq(b)] }.flatten] + + conditions = brands.map do |brand| + [brand, { ProductProperty.translation_table_alias => { value: brand } }] + end.to_h + { name: I18n.t('spree.taxonomy_brands_name'), scope: :brand_any, - conds: conds, + conds: conditions, labels: brands.sort.map { |k| [k, k] } } end From c451f8dd5672f5dd4a20721630abc72c0cdcd789 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Tue, 10 Jan 2023 17:31:23 +0100 Subject: [PATCH 56/89] bump version to allow for integration tests --- core/lib/spree/core/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/spree/core/version.rb b/core/lib/spree/core/version.rb index dc85763d7fd..6f7011004df 100644 --- a/core/lib/spree/core/version.rb +++ b/core/lib/spree/core/version.rb @@ -1,5 +1,5 @@ module Spree - VERSION = '4.5.0.alpha'.freeze + VERSION = '4.6.0.alpha'.freeze def self.version VERSION From 7742c4738381b43d0eeff542e3ebd89f22b9bfa1 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Tue, 10 Jan 2023 17:38:44 +0100 Subject: [PATCH 57/89] code climate style tweaks --- core/app/models/spree/product.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index d0975f64b2e..99367998d0e 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -296,8 +296,6 @@ def self.like_any(fields, values) where conditions.inject(:or) end - - # Suitable for displaying only variants that has at least one option value. # There may be scenarios where an option type is removed and along with it # all option values. At that point all variants associated with only those @@ -315,8 +313,8 @@ def empty_option_values? def property(property_name) product_properties.joins(:property). - join_translation_table(Property). - find_by(Property.translation_table_alias => { name: property_name }).try(:value) + join_translation_table(Property). + find_by(Property.translation_table_alias => { name: property_name }).try(:value) end def set_property(property_name, property_value, property_presentation = property_name) From 74eddad4bcc4484020147176be531600b494223c Mon Sep 17 00:00:00 2001 From: Rafal Cymerys Date: Tue, 10 Jan 2023 17:55:28 +0100 Subject: [PATCH 58/89] Run specs with ruby 3.2 --- .circleci/config.yml | 73 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index db34ab4615d..3724b809eb5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,6 +25,12 @@ defaults_3_1: &defaults_3_1 - image: &ruby_3_1_image cimg/ruby:3.1.3-browsers - image: *redis_image +defaults_3_2: &defaults_3_2 + <<: *defaults + docker: + - image: &ruby_3_2_image cimg/ruby:3.2.0-browsers + - image: *redis_image + run_tests_2_7: &run_tests_2_7 <<: *defaults parallelism: 3 @@ -112,6 +118,35 @@ run_tests_3_1: &run_tests_3_1 - store_test_results: path: /tmp/test-results +run_tests_3_2: &run_tests_3_2 + <<: *defaults_3_2 + parallelism: 3 + steps: + - checkout + - restore_cache: + keys: + - spree-bundle-v10-ruby-3-2-{{ .Branch }} + - spree-bundle-v10-ruby-3-2 + - run: + name: Install libvips + command: sudo apt-get update && sudo apt-get install libvips42 + - run: + name: Ensure Bundle Install + command: | + bundle install + ./bin/build-ci.rb install + - run: + name: Run rspec in parallel + command: ./bin/build-ci.rb test + - store_artifacts: + path: /tmp/test-artifacts + destination: test-artifacts + - store_artifacts: + path: /tmp/test-results + destination: raw-test-output + - store_test_results: + path: /tmp/test-results + jobs: bundle_ruby_2_7: <<: *defaults @@ -176,8 +211,29 @@ jobs: paths: - ~/spree/vendor/bundle + bundle_ruby_3_2: + <<: *defaults_3_2 + steps: + - checkout + - restore_cache: + keys: + - spree-bundle-v10-ruby-3-2-{{ .Branch }} + - spree-bundle-v10-ruby-3-2 + - run: + name: Install libvips + command: sudo apt-get update && sudo apt-get install libvips42 + - run: + name: Bundle Install + command: | + bundle check || bundle install + ./bin/build-ci.rb install + - save_cache: + key: spree-bundle-v10-ruby-3-1-{{ .Branch }}-{{ checksum "Gemfile.lock" }} + paths: + - ~/spree/vendor/bundle + brakeman: - <<: *defaults_3_1 + <<: *defaults_3_2 steps: - checkout - restore_cache: @@ -234,6 +290,15 @@ jobs: - image: *postgres_image - image: *redis_image + tests_ruby_3_2_rails_7_0_postgres: + <<: *run_tests_3_2 + environment: + <<: *postgres_environment + docker: + - image: *ruby_3_2_image + - image: *postgres_image + - image: *redis_image + tests_ruby_2_7_rails_6_1_postgres: <<: *tests_ruby_2_7_rails_7_0_postgres environment: @@ -321,9 +386,13 @@ workflows: - bundle_ruby_2_7 - bundle_ruby_3_0 - bundle_ruby_3_1 + - bundle_ruby_3_2 - brakeman: requires: - - bundle_ruby_3_1 + - bundle_ruby_3_2 + - tests_ruby_3_2_rails_7_0_postgres: + requires: + - bundle_ruby_3_2 - tests_ruby_3_1_rails_7_0_postgres: requires: - bundle_ruby_3_1 From 8f4ceda4e24a2c9f9819e7158030cd0691f4cf2d Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 11 Jan 2023 10:40:14 +0100 Subject: [PATCH 59/89] Stub i18n.locale to fix flaky webhooks tests (#11830) * stub locale instead of setting it in tests --- core/spec/helpers/base_helper_spec.rb | 6 ++++-- .../spree/core/controller_helpers/locale_spec.rb | 16 +++++++++------- core/spec/lib/spree/localized_number_spec.rb | 13 ++++++++----- core/spec/models/spree/payment_spec.rb | 8 +++----- emails/spec/mailers/spree/order_mailer_spec.rb | 4 +++- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/core/spec/helpers/base_helper_spec.rb b/core/spec/helpers/base_helper_spec.rb index 93e71ef925f..31aef6bba97 100644 --- a/core/spec/helpers/base_helper_spec.rb +++ b/core/spec/helpers/base_helper_spec.rb @@ -151,7 +151,7 @@ def link_to_tracking_html(options = {}) context 'when try_spree_current_user defined' do before do - I18n.locale = I18n.default_locale + allow(I18n).to receive(:locale).and_return(I18n.default_locale) allow_any_instance_of(described_class).to receive(:try_spree_current_user).and_return(user) end @@ -183,7 +183,9 @@ def link_to_tracking_html(options = {}) context 'when try_spree_current_user is undefined' do let(:current_currency) { 'USD' } - before { I18n.locale = I18n.default_locale } + before do + allow(I18n).to receive(:locale).and_return(I18n.default_locale) + end it 'returns base cache key' do expect(base_cache_key).to eq [:en, 'USD', nil, nil] diff --git a/core/spec/lib/spree/core/controller_helpers/locale_spec.rb b/core/spec/lib/spree/core/controller_helpers/locale_spec.rb index 297d17b2678..5796d8c7896 100644 --- a/core/spec/lib/spree/core/controller_helpers/locale_spec.rb +++ b/core/spec/lib/spree/core/controller_helpers/locale_spec.rb @@ -67,9 +67,9 @@ def config_locale end context 'with I18n.default_locale set' do - before { I18n.default_locale = :de } - - after { I18n.default_locale = :en } + before do + allow(I18n).to receive(:default_locale).and_return(:de) + end it 'fallbacks to the default application locale' do expect(controller.current_locale.to_s).to eq('de') @@ -95,15 +95,17 @@ def config_locale let!(:store) { create :store, default: true, default_locale: 'en', supported_locales: 'en,de,fr' } context 'same as store default locale' do - before { I18n.locale = :en } + before do + allow(I18n).to receive(:locale).and_return(:en) + end it { expect(controller.locale_param).to eq(nil) } end context 'different than store locale' do - before { I18n.locale = :de } - - after { I18n.locale = :en } + before do + allow(I18n).to receive(:locale).and_return(:de) + end it { expect(controller.locale_param).to eq('de') } end diff --git a/core/spec/lib/spree/localized_number_spec.rb b/core/spec/lib/spree/localized_number_spec.rb index 584132fbe12..8db888d2c9f 100644 --- a/core/spec/lib/spree/localized_number_spec.rb +++ b/core/spec/lib/spree/localized_number_spec.rb @@ -4,12 +4,11 @@ context '.parse' do before do I18n.enforce_available_locales = false - I18n.locale = I18n.default_locale + allow(I18n).to receive(:locale).and_return(I18n.default_locale) I18n.backend.store_translations(:de, number: { currency: { format: { delimiter: '.', separator: ',' } } }) end after do - I18n.locale = I18n.default_locale I18n.enforce_available_locales = true end @@ -21,21 +20,25 @@ context 'with decimal comma' do it 'captures the proper amount for a formatted price' do - I18n.locale = :de + allow(I18n).to receive(:locale).and_return(:de) + allow(I18n.config).to receive(:locale).and_return(:de) + expect(subject.class.parse('1.599,99')).to eq 1599.99 end end context 'with a numeric price' do it 'uses the price as is' do - I18n.locale = :de + allow(I18n).to receive(:locale).and_return(:de) + allow(I18n.config).to receive(:locale).and_return(:de) + expect(subject.class.parse(1599.99)).to eq 1599.99 end end context 'string argument' do it 'is not modified' do - I18n.locale = :de + allow(I18n).to receive(:locale).and_return(:de) number = '1.599,99' number_bak = number.dup subject.class.parse(number) diff --git a/core/spec/models/spree/payment_spec.rb b/core/spec/models/spree/payment_spec.rb index 37dba7872c0..9dcf0e24702 100644 --- a/core/spec/models/spree/payment_spec.rb +++ b/core/spec/models/spree/payment_spec.rb @@ -855,12 +855,10 @@ context 'when the locale uses a coma as a decimal separator' do before do I18n.backend.store_translations(:fr, number: { currency: { format: { delimiter: ' ', separator: ',' } } }) - I18n.locale = :fr - subject.amount = amount - end + allow(I18n).to receive(:locale).and_return(:fr) + allow(I18n.config).to receive(:locale).and_return(:fr) - after do - I18n.locale = I18n.default_locale + subject.amount = amount end context 'amount is a decimal' do diff --git a/emails/spec/mailers/spree/order_mailer_spec.rb b/emails/spec/mailers/spree/order_mailer_spec.rb index 17e1eb21d48..0c84696fa80 100644 --- a/emails/spec/mailers/spree/order_mailer_spec.rb +++ b/emails/spec/mailers/spree/order_mailer_spec.rb @@ -196,7 +196,9 @@ end context 'via I18n' do - before { I18n.locale = :'pt-BR' } + before do + allow(I18n).to receive(:locale).and_return(:'pt-BR') + end it_behaves_like 'translates emails' end From 8b99d9f95d3779490f74f0ece60ac54c65977464 Mon Sep 17 00:00:00 2001 From: Rafal Cymerys Date: Wed, 11 Jan 2023 13:40:10 +0100 Subject: [PATCH 60/89] Add fix for YAML deserialization in log entry --- core/app/models/spree/log_entry.rb | 6 +++++- core/spec/models/spree/log_entry_spec.rb | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 core/spec/models/spree/log_entry_spec.rb diff --git a/core/app/models/spree/log_entry.rb b/core/app/models/spree/log_entry.rb index 20da2fe73a6..e3102553320 100644 --- a/core/app/models/spree/log_entry.rb +++ b/core/app/models/spree/log_entry.rb @@ -15,7 +15,11 @@ def save_anyway end def parsed_details - @details ||= YAML.safe_load(details, [ActiveMerchant::Billing::Response]) + @details ||= if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.1.0') + YAML.safe_load(details, permitted_classes: [ActiveMerchant::Billing::Response]) + else + YAML.safe_load(details, [ActiveMerchant::Billing::Response]) + end end end end diff --git a/core/spec/models/spree/log_entry_spec.rb b/core/spec/models/spree/log_entry_spec.rb new file mode 100644 index 00000000000..260dfc7d168 --- /dev/null +++ b/core/spec/models/spree/log_entry_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Spree::LogEntry, type: :model do + let(:log_entry) { create(:log_entry, details: details) } + let(:details) { "--- !ruby/object:ActiveMerchant::Billing::Response\nparams: {}\nmessage: 'Bogus Gateway: Forced success'\nsuccess: true\ntest: true\nauthorization: \nfraud_review: \nerror_code: \nemv_authorization: \nnetwork_transaction_id: \navs_result:\n code: \n message: \n street_match: \n postal_match: \ncvv_result:\n code: \n message: \n" } + + describe '#parsed_details' do + subject { log_entry.parsed_details } + + it 'deserializes log entry with billing response' do + expect(subject).to be_instance_of(ActiveMerchant::Billing::Response) + expect(subject.message).to eq('Bogus Gateway: Forced success') + end + end +end From 5af3f480f216039a2dc32450f9350da7109c6651 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Fri, 13 Jan 2023 11:39:21 +0100 Subject: [PATCH 61/89] Account for possibility of existing translations from globalize --- ...3144439_create_option_type_translations.rb | 21 ++++++++++------- ...151034_create_option_value_translations.rb | 21 ++++++++++------- ...53_create_product_property_translations.rb | 21 ++++++++++------- ...fer_options_data_to_translatable_tables.rb | 11 +++++++-- ...0109105943_create_property_translations.rb | 23 +++++++++++-------- ...er_property_data_to_translatable_tables.rb | 8 +++++-- 6 files changed, 68 insertions(+), 37 deletions(-) diff --git a/core/db/migrate/20230103144439_create_option_type_translations.rb b/core/db/migrate/20230103144439_create_option_type_translations.rb index a1dae72b11a..359fb2bc674 100644 --- a/core/db/migrate/20230103144439_create_option_type_translations.rb +++ b/core/db/migrate/20230103144439_create_option_type_translations.rb @@ -1,18 +1,23 @@ class CreateOptionTypeTranslations < ActiveRecord::Migration[6.1] def change - create_table :spree_option_type_translations do |t| + if ActiveRecord::Base.connection.table_exists? 'spree_option_type_translations' + remove_index :spree_option_type_translations, name: "index_spree_option_type_translations_on_spree_option_type_id", if_exists: true + else + create_table :spree_option_type_translations do |t| - # Translated attribute(s) - t.string :name - t.string :presentation + # Translated attribute(s) + t.string :name + t.string :presentation - t.string :locale, null: false - t.references :spree_option_type, null: false, foreign_key: true, index: false + t.string :locale, null: false + t.references :spree_option_type, null: false, foreign_key: true, index: false - t.timestamps + t.timestamps + end + + add_index :spree_option_type_translations, :locale, name: :index_spree_option_type_translations_on_locale end - add_index :spree_option_type_translations, :locale, name: :index_spree_option_type_translations_on_locale add_index :spree_option_type_translations, [:spree_option_type_id, :locale], name: :unique_option_type_id_per_locale, unique: true end end diff --git a/core/db/migrate/20230103151034_create_option_value_translations.rb b/core/db/migrate/20230103151034_create_option_value_translations.rb index 61010612d9b..2acb37a3113 100644 --- a/core/db/migrate/20230103151034_create_option_value_translations.rb +++ b/core/db/migrate/20230103151034_create_option_value_translations.rb @@ -1,18 +1,23 @@ class CreateOptionValueTranslations < ActiveRecord::Migration[6.1] def change - create_table :spree_option_value_translations do |t| + if ActiveRecord::Base.connection.table_exists? 'spree_option_value_translations' + remove_index :spree_option_value_translations, name: "index_spree_option_value_translations_on_spree_option_value_id", if_exists: true + else + create_table :spree_option_value_translations do |t| - # Translated attribute(s) - t.string :name - t.string :presentation + # Translated attribute(s) + t.string :name + t.string :presentation - t.string :locale, null: false - t.references :spree_option_value, null: false, foreign_key: true, index: false + t.string :locale, null: false + t.references :spree_option_value, null: false, foreign_key: true, index: false - t.timestamps + t.timestamps + end + + add_index :spree_option_value_translations, :locale, name: :index_spree_option_value_translations_on_locale end - add_index :spree_option_value_translations, :locale, name: :index_spree_option_value_translations_on_locale add_index :spree_option_value_translations, [:spree_option_value_id, :locale], name: :unique_option_value_id_per_locale, unique: true end end diff --git a/core/db/migrate/20230109084253_create_product_property_translations.rb b/core/db/migrate/20230109084253_create_product_property_translations.rb index 9e61866bb01..e100182a897 100644 --- a/core/db/migrate/20230109084253_create_product_property_translations.rb +++ b/core/db/migrate/20230109084253_create_product_property_translations.rb @@ -1,17 +1,22 @@ class CreateProductPropertyTranslations < ActiveRecord::Migration[6.1] def change - create_table :spree_product_property_translations do |t| - # Translated attribute(s) - t.string :value - t.string :filter_param + if ActiveRecord::Base.connection.table_exists? 'spree_product_property_translations' + remove_index :spree_product_property_translations, column: :spree_product_property_id, if_exists: true + else + create_table :spree_product_property_translations do |t| + # Translated attribute(s) + t.string :value + t.string :filter_param - t.string :locale, null: false - t.references :spree_product_property, null: false, foreign_key: true, index: false + t.string :locale, null: false + t.references :spree_product_property, null: false, foreign_key: true, index: false - t.timestamps + t.timestamps + end + + add_index :spree_product_property_translations, :locale, name: :index_spree_product_property_translations_on_locale end - add_index :spree_product_property_translations, :locale, name: :index_spree_product_property_translations_on_locale add_index :spree_product_property_translations, [:spree_product_property_id, :locale], name: :unique_product_property_id_per_locale, unique: true end end diff --git a/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb index f440f422716..c02765469ae 100644 --- a/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb +++ b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb @@ -2,8 +2,12 @@ class TransferOptionsDataToTranslatableTables < ActiveRecord::Migration[6.1] DEFAULT_LOCALE = 'en' def up + # Only transfer data if translation tables are being newly created / no translations exist + # Otherwise, assume translation data is already in place from spree_globalize + # Option Types - ActiveRecord::Base.connection.execute(" + if not Spree::OptionType::Translation.exists? + ActiveRecord::Base.connection.execute(" INSERT INTO spree_option_type_translations (name, presentation, locale, spree_option_type_id, created_at, updated_at) SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at FROM spree_option_types; @@ -11,9 +15,11 @@ def up UPDATE spree_option_types SET name=null, presentation=null; ") + end # Option Values - ActiveRecord::Base.connection.execute(" + if not Spree::OptionValue::Translation.exists? + ActiveRecord::Base.connection.execute(" INSERT INTO spree_option_value_translations (name, presentation, locale, spree_option_value_id, created_at, updated_at) SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at FROM spree_option_values; @@ -21,6 +27,7 @@ def up UPDATE spree_option_values SET name=null, presentation=null; ") + end end def down diff --git a/core/db/migrate/20230109105943_create_property_translations.rb b/core/db/migrate/20230109105943_create_property_translations.rb index 6db3a20aad7..5bff726be47 100644 --- a/core/db/migrate/20230109105943_create_property_translations.rb +++ b/core/db/migrate/20230109105943_create_property_translations.rb @@ -1,18 +1,23 @@ class CreatePropertyTranslations < ActiveRecord::Migration[6.1] def change - create_table :spree_property_translations do |t| - # Translated attribute(s) - t.string :name - t.string :presentation - t.string :filter_param + if ActiveRecord::Base.connection.table_exists? 'spree_property_translations' + remove_index :spree_property_translations, column: :spree_property_id, if_exists: true + else + create_table :spree_property_translations do |t| + # Translated attribute(s) + t.string :name + t.string :presentation + t.string :filter_param - t.string :locale, null: false - t.references :spree_property, null: false, foreign_key: true, index: false + t.string :locale, null: false + t.references :spree_property, null: false, foreign_key: true, index: false - t.timestamps + t.timestamps + end + + add_index :spree_property_translations, :locale, name: :index_spree_property_translations_on_locale end - add_index :spree_property_translations, :locale, name: :index_spree_property_translations_on_locale add_index :spree_property_translations, [:spree_property_id, :locale], name: :unique_property_id_per_locale, unique: true end end diff --git a/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb index 3d1dcb682a3..804e5857032 100644 --- a/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb +++ b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb @@ -5,7 +5,8 @@ def up # Properties change_column_null :spree_properties, :presentation, true - ActiveRecord::Base.connection.execute(" + if not Spree::Property::Translation.exists? + ActiveRecord::Base.connection.execute(" INSERT INTO spree_property_translations (name, presentation, filter_param, locale, spree_property_id, created_at, updated_at) SELECT name, presentation, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at FROM spree_properties; @@ -13,9 +14,11 @@ def up UPDATE spree_properties SET name=null, presentation=null, filter_param=null; ") + end # Product Properties - ActiveRecord::Base.connection.execute(" + if not Spree::ProductProperty::Translation.exists? + ActiveRecord::Base.connection.execute(" INSERT INTO spree_product_property_translations (value, filter_param, locale, spree_product_property_id, created_at, updated_at) SELECT value, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at FROM spree_product_properties; @@ -23,6 +26,7 @@ def up UPDATE spree_product_properties SET value=null, filter_param=null; ") + end end def down From 62ee13ce0e4a04e6d277841145d7fefc1fe5c9d0 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Fri, 13 Jan 2023 11:44:31 +0100 Subject: [PATCH 62/89] update brakeman.ignore --- core/brakeman.ignore | 56 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/core/brakeman.ignore b/core/brakeman.ignore index 7c8f15f15a0..a072ae9c045 100644 --- a/core/brakeman.ignore +++ b/core/brakeman.ignore @@ -7,7 +7,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/concerns/spree/product_scopes.rb", - "line": 63, + "line": 64, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "where(\"#{price_table_name}.amount <= ?\", price)", "render_path": null, @@ -23,6 +23,52 @@ ], "note": "interpolating table name" }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "1f02952550c2f54d044c9577a45e7ba7c7990c8b8a59d1dac83a96790237f507", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 139, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(:properties).join_translation_table(Property).join_translation_table(ProductProperty).where(\"#{ProductProperty.translation_table_alias}.value = ?\", value)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::ProductScopes", + "method": null + }, + "user_input": "ProductProperty.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "7928c0813a0bf084ead091b4554ef6abea9ae9c7167936f5c62da9e328b9f736", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 139, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(:properties).join_translation_table(Property).join_translation_table(ProductProperty).where(\"#{ProductProperty.translation_table_alias}.value = ?\", value)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree", + "method": null + }, + "user_input": "ProductProperty.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, { "warning_type": "SQL Injection", "warning_code": 0, @@ -30,7 +76,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/concerns/spree/product_scopes.rb", - "line": 67, + "line": 68, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "where(\"#{price_table_name}.amount >= ?\", price)", "render_path": null, @@ -53,7 +99,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/concerns/spree/product_scopes.rb", - "line": 63, + "line": 64, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "where(\"#{price_table_name}.amount <= ?\", price)", "render_path": null, @@ -122,7 +168,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/concerns/spree/product_scopes.rb", - "line": 67, + "line": 68, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "where(\"#{price_table_name}.amount >= ?\", price)", "render_path": null, @@ -139,6 +185,6 @@ "note": "interpolating table name" } ], - "updated": "2023-01-09 09:40:32 +0100", + "updated": "2023-01-13 11:44:19 +0100", "brakeman_version": "5.4.0" } From a4f6eb5b1983b569f1811466bc3d83ae5a02723d Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 16 Jan 2023 12:47:53 +0100 Subject: [PATCH 63/89] Mobility Gem integration for products and taxons (#11823) Co-authored-by: aryt Co-authored-by: adrianryt Co-authored-by: Rafal Kosla --- .circleci/config.yml | 2 +- .../api/v2/platform/variants_controller.rb | 10 ++ .../spree/api/v2/resource_controller.rb | 2 +- .../api/v2/collection_options_helpers.rb | 2 +- api/brakeman.ignore | 7 + .../api/v2/platform/taxons_spec.rb | 8 +- .../spree/webhooks/has_webhooks_spec.rb | 5 +- .../spree/api/v2/storefront/products_spec.rb | 12 +- .../spree/api/v2/storefront/taxons_spec.rb | 2 +- core/app/finders/spree/products/find.rb | 32 ++-- core/app/finders/spree/taxons/find.rb | 9 +- .../models/concerns/spree/product_scopes.rb | 18 +- .../concerns/spree/translatable_resource.rb | 25 +++ core/app/models/spree/product.rb | 16 +- core/app/models/spree/taxon.rb | 6 +- core/app/models/spree/variant.rb | 17 +- core/app/sorters/spree/products/sort.rb | 23 +++ core/brakeman.ignore | 160 ++++++++++++++++-- core/config/initializers/mobility.rb | 18 ++ core/config/locales/en.yml | 1 + ...translations_for_mobility_table_backend.rb | 25 +++ ...translations_for_mobility_table_backend.rb | 7 + ...120222_change_product_name_null_to_true.rb | 5 + ...translations_for_mobility_table_backend.rb | 24 +++ ...18100948_change_taxon_name_null_to_true.rb | 5 + ...2070609_add_locale_to_friendly_id_slugs.rb | 11 ++ ...translations_for_mobility_table_backend.rb | 5 + ..._add_deleted_at_to_product_translations.rb | 6 + ...ness_constraint_to_product_translations.rb | 5 + ...142344_backfill_friendly_id_slug_locale.rb | 15 ++ ...add_additional_taxon_translation_fields.rb | 8 + ...t_and_taxon_data_to_translatable_tables.rb | 82 +++++++++ core/lib/spree/core.rb | 1 + core/lib/spree/core/product_duplicator.rb | 2 +- core/spec/finders/spree/taxons/find_spec.rb | 28 +++ core/spec/models/spree/product_spec.rb | 28 ++- core/spree_core.gemspec | 4 + sample/db/samples/product_properties.rb | 1 - sample/db/samples/products.rb | 15 +- sample/db/samples/taxons.rb | 43 ++++- sample/lib/spree_sample.rb | 1 + 41 files changed, 614 insertions(+), 82 deletions(-) create mode 100644 api/brakeman.ignore create mode 100644 core/app/models/concerns/spree/translatable_resource.rb create mode 100644 core/config/initializers/mobility.rb create mode 100644 core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb create mode 100644 core/db/migrate/20220715083542_create_spree_product_meta_description_and_meta_keywords_and_meta_title_translations_for_mobility_table_backend.rb create mode 100644 core/db/migrate/20220715120222_change_product_name_null_to_true.rb create mode 100644 core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb create mode 100644 core/db/migrate/20220718100948_change_taxon_name_null_to_true.rb create mode 100644 core/db/migrate/20220802070609_add_locale_to_friendly_id_slugs.rb create mode 100644 core/db/migrate/20220802073225_create_spree_product_slug_translations_for_mobility_table_backend.rb create mode 100644 core/db/migrate/20221219123957_add_deleted_at_to_product_translations.rb create mode 100644 core/db/migrate/20221220133432_add_uniqueness_constraint_to_product_translations.rb create mode 100644 core/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb create mode 100644 core/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb create mode 100644 core/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb create mode 100644 core/spec/finders/spree/taxons/find_spec.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index 3724b809eb5..9d5509b4012 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -248,7 +248,7 @@ jobs: - run: name: Run Brakeman spree/api command: | - bundle exec brakeman -p api/ --skip-files app/controllers/spree/api/v1/ --exit-on-warn --exit-on-error + bundle exec brakeman -p api/ --ignore-config api/brakeman.ignore --skip-files app/controllers/spree/api/v1/ --exit-on-warn --exit-on-error - run: name: Run Brakeman spree/core command: | diff --git a/api/app/controllers/spree/api/v2/platform/variants_controller.rb b/api/app/controllers/spree/api/v2/platform/variants_controller.rb index d3ca47e8d01..93c436b4671 100644 --- a/api/app/controllers/spree/api/v2/platform/variants_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/variants_controller.rb @@ -12,6 +12,16 @@ def model_class def spree_permitted_attributes super + [:option_value_ids, :price, :currency] end + + def collection + # if filtering on products, manually join on product translation to workaround mobility-ransack issue + if params.key?(:filter) && params[:filter].keys.any? { |k| k.include? 'product' } + @collection ||= scope.join_product_translations. + ransack(params[:filter]).result + else + super + end + end end end end diff --git a/api/app/controllers/spree/api/v2/resource_controller.rb b/api/app/controllers/spree/api/v2/resource_controller.rb index 9992b2ee6a5..916d12d47e9 100644 --- a/api/app/controllers/spree/api/v2/resource_controller.rb +++ b/api/app/controllers/spree/api/v2/resource_controller.rb @@ -35,7 +35,7 @@ def scope(skip_cancancan: false) base_scope = model_class.for_store(current_store) base_scope = base_scope.accessible_by(current_ability, :show) unless skip_cancancan base_scope = base_scope.includes(scope_includes) if scope_includes.any? && action_name == 'index' - base_scope + model_class.include?(TranslatableResource) ? base_scope.i18n : base_scope end def scope_includes diff --git a/api/app/helpers/spree/api/v2/collection_options_helpers.rb b/api/app/helpers/spree/api/v2/collection_options_helpers.rb index 768313af084..a39157739ce 100644 --- a/api/app/helpers/spree/api/v2/collection_options_helpers.rb +++ b/api/app/helpers/spree/api/v2/collection_options_helpers.rb @@ -23,7 +23,7 @@ def collection_meta(collection) # leaving this method in public scope so it's still possible to modify # those params to support non-standard non-JSON API parameters def collection_permitted_params - params.permit(:format, :page, :per_page, :sort, :include, fields: {}, filter: {}) + params.permit(:format, :page, :per_page, :sort, :include, :locale, fields: {}, filter: {}) end private diff --git a/api/brakeman.ignore b/api/brakeman.ignore new file mode 100644 index 00000000000..e253a812ea0 --- /dev/null +++ b/api/brakeman.ignore @@ -0,0 +1,7 @@ +{ + "ignored_warnings": [ + + ], + "updated": "2022-12-29 09:29:46 +0100", + "brakeman_version": "5.4.0" +} diff --git a/api/spec/integration/api/v2/platform/taxons_spec.rb b/api/spec/integration/api/v2/platform/taxons_spec.rb index d16dea858de..48f2474562b 100644 --- a/api/spec/integration/api/v2/platform/taxons_spec.rb +++ b/api/spec/integration/api/v2/platform/taxons_spec.rb @@ -16,7 +16,13 @@ let!(:taxon_b) { create(:taxon, name: 'Shorts', taxonomy: taxonomy) } let(:records_list) { create_list(:taxon, 2, taxonomy: taxonomy) } - let(:valid_create_param_value) { build(:taxon, taxonomy: taxonomy).attributes } + let(:valid_create_param_value) do { + parent_id: taxonomy.root.id, + position: 0, + name: 'new_taxon', + taxonomy_id: taxonomy.id + } + end let(:valid_update_param_value) do { name: 'T-Shirts', diff --git a/api/spec/models/concerns/spree/webhooks/has_webhooks_spec.rb b/api/spec/models/concerns/spree/webhooks/has_webhooks_spec.rb index 7fe74735e28..3d36b83591f 100644 --- a/api/spec/models/concerns/spree/webhooks/has_webhooks_spec.rb +++ b/api/spec/models/concerns/spree/webhooks/has_webhooks_spec.rb @@ -88,7 +88,10 @@ context 'when only timestamps change' do let(:event_name) { 'product.update' } - before { product.save } + before { + product.save + product.translations.first.save + } context 'on created_at change' do it do diff --git a/api/spec/requests/spree/api/v2/storefront/products_spec.rb b/api/spec/requests/spree/api/v2/storefront/products_spec.rb index 8beefd0afb2..feb1d1c5b26 100644 --- a/api/spec/requests/spree/api/v2/storefront/products_spec.rb +++ b/api/spec/requests/spree/api/v2/storefront/products_spec.rb @@ -365,7 +365,9 @@ it 'returns products sorted by name' do expect(json_response['data'].count).to eq store.products.available.count - expect(json_response['data'].pluck(:id)).to eq store.products.available.order(:name).map(&:id).map(&:to_s) + expect(json_response['data'].pluck(:id)).to eq store.products.i18n.available + .select("#{Spree::Product.table_name}.*, #{Spree::Product::Translation.table_name}_en.name") + .order(:name).map(&:id).map(&:to_s) end end @@ -376,7 +378,9 @@ it 'returns products sorted by name with descending order' do expect(json_response['data'].count).to eq store.products.available.count - expect(json_response['data'].pluck(:id)).to eq store.products.available.order(name: :desc).map(&:id).map(&:to_s) + expect(json_response['data'].pluck(:id)).to eq store.products.available + .select("#{Spree::Product.table_name}.*, #{Spree::Product::Translation.table_name}_en.name") + .order(name: :desc).map(&:id).map(&:to_s) end end end @@ -727,7 +731,9 @@ end context 'when filter by product property is applied' do - before { get "/api/v2/storefront/products?filter[properties][#{property.filter_param}]=#{product_property.filter_param}" } + before { + get "/api/v2/storefront/products?filter[properties][#{property.filter_param}]=#{product_property.filter_param}" + } it 'returns list of all available filters for products' do expect(json_response['meta']['filters']['option_types'].count).to eq 2 diff --git a/api/spec/requests/spree/api/v2/storefront/taxons_spec.rb b/api/spec/requests/spree/api/v2/storefront/taxons_spec.rb index 39ddd0d2d4d..eabd678c841 100644 --- a/api/spec/requests/spree/api/v2/storefront/taxons_spec.rb +++ b/api/spec/requests/spree/api/v2/storefront/taxons_spec.rb @@ -109,7 +109,7 @@ it_behaves_like 'returns 200 HTTP status' - it 'returns taxonx by name' do + it 'returns taxons by name' do expect(json_response['data'].size).to eq(1) expect(json_response['data'].last['attributes']['name']).to eq(taxons.last.name) end diff --git a/core/app/finders/spree/products/find.rb b/core/app/finders/spree/products/find.rb index 320f6d47c09..fc06af2bd77 100644 --- a/core/app/finders/spree/products/find.rb +++ b/core/app/finders/spree/products/find.rb @@ -146,7 +146,12 @@ def by_concat_taxons(products) def by_name(products) return products unless name? - products.like_any([:name], [name]) + product_name = name + + # i18n scope doesn't automatically get set here (mobility gem bug?) set it explicitly + products.i18n do + name.matches("%#{product_name}%") + end end def by_options(products) @@ -209,21 +214,19 @@ def ordered(products) products end when 'name-a-z' - products.order(name: :asc) + # workaround for Mobility issue #596 - explicitly select fields to avoid error when selecting distinct + products.i18n. + select("#{Product.table_name}.*").select(:name).order(name: :asc) when 'name-z-a' - products.order(name: :desc) + # workaround for Mobility issue #596 + products.i18n. + select("#{Product.table_name}.*").select(:name).order(name: :desc) when 'newest-first' products.order(available_on: :desc) when 'price-high-to-low' - products. - select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount"). - reorder(''). - send(:descend_by_master_price) + order_by_price(products, :descend_by_master_price) when 'price-low-to-high' - products. - select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount"). - reorder(''). - send(:ascend_by_master_price) + order_by_price(products, :ascend_by_master_price) end end @@ -265,6 +268,13 @@ def taxon_ids(taxons_ids) taxons = store.taxons.where(id: taxons_ids.to_s.split(',')) taxons.map(&:cached_self_and_descendants_ids).flatten.compact.uniq.map(&:to_s) end + + def order_by_price(scope, order_type) + scope. + select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount"). + reorder(''). + send(order_type) + end end end end diff --git a/core/app/finders/spree/taxons/find.rb b/core/app/finders/spree/taxons/find.rb index 0c3ac27f0dd..6858455c8b2 100644 --- a/core/app/finders/spree/taxons/find.rb +++ b/core/app/finders/spree/taxons/find.rb @@ -50,10 +50,6 @@ def name? name.present? end - def name_matcher - Spree::Taxon.arel_table[:name].matches("%#{name}%") - end - def by_ids(taxons) return taxons unless ids? @@ -91,7 +87,10 @@ def by_roots(taxons) def by_name(taxons) return taxons unless name? - taxons.where(name_matcher) + taxon_name = name + + # i18n mobility scope doesn't automatically get set for query blocks (Mobility issue #599) - set it explicitly + taxons.i18n { name.matches("%#{taxon_name}%") } end end end diff --git a/core/app/models/concerns/spree/product_scopes.rb b/core/app/models/concerns/spree/product_scopes.rb index 4db73faaf84..340ad930c69 100644 --- a/core/app/models/concerns/spree/product_scopes.rb +++ b/core/app/models/concerns/spree/product_scopes.rb @@ -295,19 +295,10 @@ def self.for_user(user = nil) # .search_by_name if defined?(PgSearch) include PgSearch::Model - - if defined?(SpreeGlobalize) - pg_search_scope :search_by_name, associated_against: { translations: :name }, using: { tsearch: { any_word: true, prefix: true } } - else - pg_search_scope :search_by_name, against: :name, using: { tsearch: { any_word: true, prefix: true } } - end + pg_search_scope :search_by_name, against: :name, using: { tsearch: { any_word: true, prefix: true } } else def self.search_by_name(query) - if defined?(SpreeGlobalize) - joins(:translations).order(:name).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query)", query: "%#{query}%").distinct - else - where("LOWER(#{Product.table_name}.name) LIKE LOWER(:query)", query: "%#{query}%") - end + i18n { name.lower.matches("%#{query.downcase}%") } end end search_scopes << :search_by_name @@ -339,7 +330,10 @@ def self.get_taxons(*ids_or_records_or_names) case t when ApplicationRecord then t else - Taxon.where(Taxon.arel_table[:name].eq(t)).or(Taxon.where(Taxon.arel_table[:id].eq(t))).or(Taxon.where(Taxon.arel_table[:permalink].matches("%/#{t}/"))).or(Taxon.where(Taxon.arel_table[:permalink].matches("#{t}/"))).first + Taxon.where(name: t). + or(Taxon.where(Taxon.arel_table[:id].eq(t))). + or(Taxon.where(Taxon.arel_table[:permalink].matches("%/#{t}/"))). + or(Taxon.where(Taxon.arel_table[:permalink].matches("#{t}/"))).first end end.compact.flatten.uniq end diff --git a/core/app/models/concerns/spree/translatable_resource.rb b/core/app/models/concerns/spree/translatable_resource.rb new file mode 100644 index 00000000000..73c0de27b42 --- /dev/null +++ b/core/app/models/concerns/spree/translatable_resource.rb @@ -0,0 +1,25 @@ +module Spree + module TranslatableResource + extend ActiveSupport::Concern + + included do + extend Mobility + default_scope { i18n } + + def get_field_with_locale(locale, field_name, fallback: false) + # method will return nil if no translation is present due to fallback: false setting + public_send(field_name, locale: locale, fallback: fallback) + end + end + + class_methods do + def translatable_fields + const_get(:TRANSLATABLE_FIELDS) + end + + def translation_table_alias + "#{self::Translation.table_name}_#{Mobility.locale}" + end + end + end +end diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index e4983fe0c34..80139391722 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -23,6 +23,7 @@ class Product < Spree::Base extend FriendlyId include ProductScopes include MultiStoreResource + include TranslatableResource include MemoizedData include Metadata if defined?(Spree::Webhooks) @@ -32,12 +33,15 @@ class Product < Spree::Base include Spree::VendorConcern end - MEMOIZED_METHODS = %w(total_on_hand taxonomy_ids taxon_and_ancestors category + MEMOIZED_METHODS = %w[total_on_hand taxonomy_ids taxon_and_ancestors category default_variant_id tax_category default_variant - purchasable? in_stock? backorderable?) + purchasable? in_stock? backorderable?] - friendly_id :slug_candidates, use: :history + TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_keywords meta_title].freeze + translates(*TRANSLATABLE_FIELDS) + Product::Translation.class_eval { acts_as_paranoid } + friendly_id :slug_candidates, use: [:history, :mobility] acts_as_paranoid auto_strip_attributes :name @@ -415,7 +419,11 @@ def normalize_slug def punch_slug # punch slug with date prefix to allow reuse of original - update_column :slug, "#{Time.current.to_i}_#{slug}"[0..254] unless frozen? + return if frozen? + + translations.with_deleted.each do |t| + t.update_column :slug, "#{Time.current.to_i}_#{t.slug}"[0..254] + end end def update_slug_history diff --git a/core/app/models/spree/taxon.rb b/core/app/models/spree/taxon.rb index d4be621e824..6589fbf9a19 100644 --- a/core/app/models/spree/taxon.rb +++ b/core/app/models/spree/taxon.rb @@ -3,6 +3,7 @@ module Spree class Taxon < Spree::Base + include TranslatableResource include Metadata if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks @@ -27,7 +28,7 @@ class Taxon < Spree::Base has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', dependent: :destroy has_many :promotion_rules, through: :promotion_rule_taxons, class_name: 'Spree::PromotionRule' - validates :name, presence: true, uniqueness: { scope: [:parent_id, :taxonomy_id], allow_blank: true, case_sensitive: false } + validates :name, presence: true, uniqueness: { scope: [:parent_id, :taxonomy_id], case_sensitive: false } validates :taxonomy, presence: true validates :permalink, uniqueness: { case_sensitive: false, scope: [:parent_id, :taxonomy_id] } validates :hide_from_nav, inclusion: { in: [true, false] } @@ -54,6 +55,9 @@ class Taxon < Spree::Base scope :for_stores, ->(stores) { joins(:taxonomy).where(spree_taxonomies: { store_id: stores.ids }) } + TRANSLATABLE_FIELDS = %i[name description].freeze + translates(*TRANSLATABLE_FIELDS) + # indicate which filters should be used for a taxon # this method should be customized to your own site def applicable_filters diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index 02e79e40954..2be9a29c3bb 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -122,16 +122,19 @@ class Variant < Spree::Base self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku) def self.product_name_or_sku_cont(query) - joins(:product).where("LOWER(#{Product.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") + join_product_translations. + where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) + OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") + end + + def self.join_product_translations + joins(:product).joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} + ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id + AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'") end def self.search_by_product_name_or_sku(query) - if defined?(SpreeGlobalize) - joins(product: :translations).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", - query: "%#{query}%") - else - product_name_or_sku_cont(query) - end + product_name_or_sku_cont(query) end def available? diff --git a/core/app/sorters/spree/products/sort.rb b/core/app/sorters/spree/products/sort.rb index dab39cbc0e7..0eca2d54dd7 100644 --- a/core/app/sorters/spree/products/sort.rb +++ b/core/app/sorters/spree/products/sort.rb @@ -11,6 +11,8 @@ def call products = by_price(products) products = by_sku(products) + products = select_translatable_fields(products) + products.distinct end @@ -46,6 +48,27 @@ def by_sku(scope) def sort_by?(field) sort.detect { |s| s[0] == field } end + + # Add translatable fields to SELECT statement to avoid InvalidColumnReference error (Mobility bug workaround) + def select_translatable_fields(scope) + translatable_fields = translatable_sortable_fields + return scope if translatable_fields.empty? + + # if sorting by 'sku' or 'price', spree_products.* is already included in SELECT statement + if sort_by?('sku') || sort_by?('price') + scope.i18n.select(*translatable_fields) + else + scope.i18n.select("#{Product.table_name}.*").select(*translatable_fields) + end + end + + def translatable_sortable_fields + fields = [] + Product.translatable_fields.each do |field| + fields << field if sort_by?(field.to_s) + end + fields + end end end end diff --git a/core/brakeman.ignore b/core/brakeman.ignore index c82e0794edb..24e68c5178a 100644 --- a/core/brakeman.ignore +++ b/core/brakeman.ignore @@ -1,20 +1,144 @@ { - "ignored_warnings": [ - { - "fingerprint": "011b2643940ba1112f7a737e403abe3616ad91764703c801cc35a48d36b721da", - "note": "interpolating table name" - }, - { - "fingerprint": "965d3919f811ab63b7b8d62da528559a7f38dc122c57efea7136e7ec5ef1f062", - "note": "interpolating table name" - }, - { - "fingerprint": "abd8e90e7a7dfbcdcd6d44fd3fb550598aee6d7a9ef2bb132ad1a18a3c50be30", - "note": "interpolating table name" - }, - { - "fingerprint": "efcc57e1a5648d7db59d1beaf5e399d2278539a8667b19c520b305a6ca7e15e8", - "note": "interpolating table name" - } - ] + "ignored_warnings": [ + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "011b2643940ba1112f7a737e403abe3616ad91764703c801cc35a48d36b721da", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 63, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount <= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "67e80bcfa8898315b0f8642bc61990c8319bc7a468453efc048757a439e53e0b", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/spree/variant.rb", + "line": 131, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(:product).joins(\"LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias}\\n ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id\\n AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'\")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::Variant", + "method": "Spree::Variant.join_product_translations" + }, + "user_input": "Product.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "965d3919f811ab63b7b8d62da528559a7f38dc122c57efea7136e7ec5ef1f062", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 67, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount >= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::ProductScopes", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "abd8e90e7a7dfbcdcd6d44fd3fb550598aee6d7a9ef2bb132ad1a18a3c50be30", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 63, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount <= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::ProductScopes", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "e6bb08c012af19589f796e26b4dc2230ea1c1949c7cd6464ca025939513d8b3d", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/spree/variant.rb", + "line": 126, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "join_product_translations.where(\"LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)\\n OR LOWER(sku) LIKE LOWER(:query)\", :query => (\"%#{query}%\"))", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::Variant", + "method": "Spree::Variant.product_name_or_sku_cont" + }, + "user_input": "Product.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "efcc57e1a5648d7db59d1beaf5e399d2278539a8667b19c520b305a6ca7e15e8", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 67, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount >= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + } + ], + "updated": "2022-12-29 09:32:26 +0100", + "brakeman_version": "5.4.0" } diff --git a/core/config/initializers/mobility.rb b/core/config/initializers/mobility.rb new file mode 100644 index 00000000000..a554d7dafca --- /dev/null +++ b/core/config/initializers/mobility.rb @@ -0,0 +1,18 @@ +Mobility.configure do |config| + config.plugins do + ransack + backend :table + active_record + reader + writer + backend_reader + query + cache + fallbacks + locale_accessors + presence + dirty + end + + config.defaults[:fallbacks] = true +end diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml index db521ee52d8..6e73298ae8b 100644 --- a/core/config/locales/en.yml +++ b/core/config/locales/en.yml @@ -1112,6 +1112,7 @@ en: notice_messages: icon_removed: Image has been successfully removed prices_saved: Prices successfully saved + translations_saved: Translations successfully saved product_cloned: Product has been cloned product_deleted: Product has been deleted product_not_cloned: "Product could not be cloned. Reason: %{error}" diff --git a/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb b/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb new file mode 100644 index 00000000000..fd00e191c8b --- /dev/null +++ b/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb @@ -0,0 +1,25 @@ +class CreateProductNameAndDescriptionTranslationsForMobilityTableBackend < ActiveRecord::Migration[6.1] + def change + # create translation table only if spree_globalize has not already created it + if ActiveRecord::Base.connection.table_exists? 'spree_product_translations' + # replacing this with index on spree_product_id and locale + remove_index :spree_product_translations, name: "index_spree_product_translations_on_spree_product_id", if_exists: true + else + create_table :spree_product_translations do |t| + + # Translated attribute(s) + t.string :name + t.text :description + + t.string :locale, null: false + t.references :spree_product, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + + add_index :spree_product_translations, :locale, name: :index_spree_product_translations_on_locale + end + + add_index :spree_product_translations, [:spree_product_id, :locale], name: :unique_product_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20220715083542_create_spree_product_meta_description_and_meta_keywords_and_meta_title_translations_for_mobility_table_backend.rb b/core/db/migrate/20220715083542_create_spree_product_meta_description_and_meta_keywords_and_meta_title_translations_for_mobility_table_backend.rb new file mode 100644 index 00000000000..5d712d9cb08 --- /dev/null +++ b/core/db/migrate/20220715083542_create_spree_product_meta_description_and_meta_keywords_and_meta_title_translations_for_mobility_table_backend.rb @@ -0,0 +1,7 @@ +class CreateSpreeProductMetaDescriptionAndMetaKeywordsAndMetaTitleTranslationsForMobilityTableBackend < ActiveRecord::Migration[6.1] + def change + add_column :spree_product_translations, :meta_description, :text, if_not_exists: true + add_column :spree_product_translations, :meta_keywords, :string, if_not_exists: true + add_column :spree_product_translations, :meta_title, :string, if_not_exists: true + end +end diff --git a/core/db/migrate/20220715120222_change_product_name_null_to_true.rb b/core/db/migrate/20220715120222_change_product_name_null_to_true.rb new file mode 100644 index 00000000000..007da64c9b2 --- /dev/null +++ b/core/db/migrate/20220715120222_change_product_name_null_to_true.rb @@ -0,0 +1,5 @@ +class ChangeProductNameNullToTrue < ActiveRecord::Migration[6.1] + def change + change_column_null :spree_products, :name, true + end +end diff --git a/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb b/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb new file mode 100644 index 00000000000..4b654a69f81 --- /dev/null +++ b/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb @@ -0,0 +1,24 @@ +class CreateSpreeTaxonNameAndDescriptionTranslationsForMobilityTableBackend < ActiveRecord::Migration[6.1] + def change + # create translation table only if spree_globalize has not already created it + if ActiveRecord::Base.connection.table_exists? 'spree_taxon_translations' + # replacing this with index on spree_taxon_id and locale + remove_index :spree_taxon_translations, name: "index_spree_taxon_translations_on_spree_taxon_id", if_exists: true + else + create_table :spree_taxon_translations do |t| + # Translated attribute(s) + t.string :name + t.text :description + + t.string :locale, null: false + t.references :spree_taxon, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + + add_index :spree_taxon_translations, :locale, name: :index_spree_taxon_translations_on_locale + end + + add_index :spree_taxon_translations, [:spree_taxon_id, :locale], name: :index_spree_taxon_translations_on_spree_taxon_id_and_locale, unique: true + end +end diff --git a/core/db/migrate/20220718100948_change_taxon_name_null_to_true.rb b/core/db/migrate/20220718100948_change_taxon_name_null_to_true.rb new file mode 100644 index 00000000000..01339156307 --- /dev/null +++ b/core/db/migrate/20220718100948_change_taxon_name_null_to_true.rb @@ -0,0 +1,5 @@ +class ChangeTaxonNameNullToTrue < ActiveRecord::Migration[6.1] + def change + change_column_null :spree_taxons, :name, true + end +end diff --git a/core/db/migrate/20220802070609_add_locale_to_friendly_id_slugs.rb b/core/db/migrate/20220802070609_add_locale_to_friendly_id_slugs.rb new file mode 100644 index 00000000000..1c2ba8aebef --- /dev/null +++ b/core/db/migrate/20220802070609_add_locale_to_friendly_id_slugs.rb @@ -0,0 +1,11 @@ +class AddLocaleToFriendlyIdSlugs < ActiveRecord::Migration[6.1] + def change + add_column :friendly_id_slugs, :locale, :string, null: :false, after: :scope + + remove_index :friendly_id_slugs, [:slug, :sluggable_type] + add_index :friendly_id_slugs, [:slug, :sluggable_type, :locale], length: { slug: 140, sluggable_type: 50, locale: 2 } + remove_index :friendly_id_slugs, [:slug, :sluggable_type, :scope] + add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope, :locale], length: { slug: 70, sluggable_type: 50, scope: 70, locale: 2 }, unique: true, name: :index_friendly_id_slugs_unique + add_index :friendly_id_slugs, :locale + end +end diff --git a/core/db/migrate/20220802073225_create_spree_product_slug_translations_for_mobility_table_backend.rb b/core/db/migrate/20220802073225_create_spree_product_slug_translations_for_mobility_table_backend.rb new file mode 100644 index 00000000000..4d8f9724590 --- /dev/null +++ b/core/db/migrate/20220802073225_create_spree_product_slug_translations_for_mobility_table_backend.rb @@ -0,0 +1,5 @@ +class CreateSpreeProductSlugTranslationsForMobilityTableBackend < ActiveRecord::Migration[6.1] + def change + add_column :spree_product_translations, :slug, :string + end +end diff --git a/core/db/migrate/20221219123957_add_deleted_at_to_product_translations.rb b/core/db/migrate/20221219123957_add_deleted_at_to_product_translations.rb new file mode 100644 index 00000000000..28e74325e76 --- /dev/null +++ b/core/db/migrate/20221219123957_add_deleted_at_to_product_translations.rb @@ -0,0 +1,6 @@ +class AddDeletedAtToProductTranslations < ActiveRecord::Migration[6.1] + def change + add_column :spree_product_translations, :deleted_at, :datetime + add_index :spree_product_translations, :deleted_at + end +end diff --git a/core/db/migrate/20221220133432_add_uniqueness_constraint_to_product_translations.rb b/core/db/migrate/20221220133432_add_uniqueness_constraint_to_product_translations.rb new file mode 100644 index 00000000000..b100e67c0a8 --- /dev/null +++ b/core/db/migrate/20221220133432_add_uniqueness_constraint_to_product_translations.rb @@ -0,0 +1,5 @@ +class AddUniquenessConstraintToProductTranslations < ActiveRecord::Migration[6.1] + def change + add_index :spree_product_translations, [:locale, :slug], unique: true, name: 'unique_slug_per_locale' + end +end diff --git a/core/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb b/core/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb new file mode 100644 index 00000000000..c2c168d94c1 --- /dev/null +++ b/core/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb @@ -0,0 +1,15 @@ +class BackfillFriendlyIdSlugLocale < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + + def up + ActiveRecord::Base.connection.execute(" + UPDATE friendly_id_slugs SET locale = '#{DEFAULT_LOCALE}' + ") + end + + def down + ActiveRecord::Base.connection.execute(" + UPDATE friendly_id_slugs SET locale = NULL + ") + end +end diff --git a/core/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb b/core/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb new file mode 100644 index 00000000000..f59a7ac10b2 --- /dev/null +++ b/core/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb @@ -0,0 +1,8 @@ +class AddAdditionalTaxonTranslationFields < ActiveRecord::Migration[6.1] + def change + add_column :spree_taxon_translations, :meta_title, :string, if_not_exists: true + add_column :spree_taxon_translations, :meta_description, :string, if_not_exists: true + add_column :spree_taxon_translations, :meta_keywords, :string, if_not_exists: true + add_column :spree_taxon_translations, :permalink, :string, if_not_exists: true + end +end diff --git a/core/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb b/core/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb new file mode 100644 index 00000000000..8c5b0247692 --- /dev/null +++ b/core/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb @@ -0,0 +1,82 @@ +class TransferProductAndTaxonDataToTranslatableTables < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + PRODUCTS_TABLE = 'spree_products' + PRODUCT_TRANSLATIONS_TABLE = 'spree_product_translations' + TAXONS_TABLE = 'spree_taxons' + TAXON_TRANSLATIONS_TABLE = 'spree_taxon_translations' + + def up + # Only transfer data if translation tables are being newly created / no translations exist + # Otherwise, assume translation data is already in place from spree_globalize + + # Products + if not Spree::Product::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO #{PRODUCT_TRANSLATIONS_TABLE} (name, description, locale, spree_product_id, created_at, updated_at, meta_description, meta_keywords, meta_title, slug) + SELECT name, description, '#{DEFAULT_LOCALE}' as locale, id, created_at, updated_at, meta_description, meta_keywords, meta_title, slug FROM #{PRODUCTS_TABLE}; + ") + ActiveRecord::Base.connection.execute(" + UPDATE #{PRODUCTS_TABLE} + SET name=null, description=null, meta_description=null, meta_keywords=null, meta_title=null, slug=null; + ") + end + + # Taxons + if not Spree::Taxon::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO #{TAXON_TRANSLATIONS_TABLE} (name, description, meta_title, meta_description, meta_keywords, permalink, locale, spree_taxon_id, created_at, updated_at) + SELECT name, description, meta_title, meta_description, meta_keywords, permalink, '#{DEFAULT_LOCALE}' as locale, id, created_at, updated_at FROM #{TAXONS_TABLE}; + ") + ActiveRecord::Base.connection.execute(" + UPDATE #{TAXONS_TABLE} + SET name=null, description=null, meta_title=null, meta_description=null, meta_keywords=null, permalink=null; + ") + end + end + + def down + ActiveRecord::Base.connection.execute(" + UPDATE #{PRODUCTS_TABLE} as products + SET (name, + description, + meta_description, + meta_keywords, + meta_title, + slug) = + (t_products.name, + t_products.description, + t_products.meta_description, + t_products.meta_keywords, + t_products.meta_title, + t_products.slug) + FROM #{PRODUCT_TRANSLATIONS_TABLE} AS t_products + WHERE t_products.spree_product_id = products.id + ") + + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE #{PRODUCT_TRANSLATIONS_TABLE} + ") + + ActiveRecord::Base.connection.execute(" + UPDATE #{TAXONS_TABLE} as taxons + SET (name, + description, + meta_title, + meta_description, + meta_keywords, + permalink) = + (t_taxons.name, + t_taxons.description, + t_taxons.meta_title, + t_taxons.meta_description, + t_taxons.meta_keywords, + t_taxons.permalink) + FROM #{TAXON_TRANSLATIONS_TABLE} AS t_taxons + WHERE t_taxons.spree_taxon_id = taxons.id + ") + + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE #{TAXON_TRANSLATIONS_TABLE} + ") + end +end diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb index 79c5b4858a8..31a04ed58bf 100644 --- a/core/lib/spree/core.rb +++ b/core/lib/spree/core.rb @@ -13,6 +13,7 @@ require 'friendly_id' require 'kaminari' require 'monetize' +require 'mobility' require 'paranoia' require 'ransack' require 'state_machines-activerecord' diff --git a/core/lib/spree/core/product_duplicator.rb b/core/lib/spree/core/product_duplicator.rb index f5ae3c0b036..91b308c58af 100644 --- a/core/lib/spree/core/product_duplicator.rb +++ b/core/lib/spree/core/product_duplicator.rb @@ -26,7 +26,7 @@ def duplicate def duplicate_product product.dup.tap do |new_product| - new_product.name = "COPY OF #{product.name}" + product.translations.each { |t| new_product.send(:name=, "COPY OF #{t.name}", locale: t.locale) } new_product.taxons = product.taxons new_product.stores = product.stores new_product.created_at = nil diff --git a/core/spec/finders/spree/taxons/find_spec.rb b/core/spec/finders/spree/taxons/find_spec.rb new file mode 100644 index 00000000000..077b04af3d1 --- /dev/null +++ b/core/spec/finders/spree/taxons/find_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +module Spree + describe Taxons::Find do + let!(:taxon_shirts) { create(:taxon, name: 'Shirts') } + let!(:taxon_shorts) { create(:taxon, name: 'Shorts') } + let!(:taxon_shoes) { create(:taxon, name: 'Shoes') } + + describe 'filtering by taxon property' do + subject do + described_class.new( + scope: Spree::Taxon.all, + params: params + ).execute + end + + context 'when filtering by taxon name' do + let(:params) { { filter: { 'name': 'Shirts' } } } + + it 'returns taxon with matching name' do + expect(subject).to contain_exactly(taxon_shirts) + end + end + + end + + end +end diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb index 21c0eeed039..455354d39de 100644 --- a/core/spec/models/spree/product_spec.rb +++ b/core/spec/models/spree/product_spec.rb @@ -69,6 +69,18 @@ class Extension < Spree::Base expect(clone.images.size).to eq(product.images.size) end + context 'when translations exist for another locale' do + before do + Mobility.with_locale(:fr) { product.name = "french name" } + product.save! + end + + it 'duplicates translations for all locales' do + clone = product.duplicate + expect(clone.name(locale: :fr)).to eq ('COPY OF ' + product.name(locale: :fr)) + end + end + it 'calls #duplicate_extra' do expect_any_instance_of(Spree::Product).to receive(:duplicate_extra). with(product) @@ -236,7 +248,21 @@ class Extension < Spree::Base context 'when product destroyed' do it 'renames slug' do - expect { product.destroy }.to change(product, :slug) + product.destroy + expect(product.translations.with_deleted.where(locale: :en).first.slug).to match(/[0-9]+_product-[0-9]+/) + end + + context 'when more than one translation exists' do + before { + product.send(:slug=, "french_slug", locale: :fr) + product.save! + } + + it 'renames slug for all translations' do + product.destroy + expect(product.translations.with_deleted.where(locale: :en).first.slug).to match(/[0-9]+_product-[0-9]+/) + expect(product.translations.with_deleted.where(locale: :fr).first.slug).to match(/[0-9]+_french_slug/) + end end context 'when slug is already at or near max length' do diff --git a/core/spree_core.gemspec b/core/spree_core.gemspec index c8f197ec06c..57a37a7523b 100644 --- a/core/spree_core.gemspec +++ b/core/spree_core.gemspec @@ -54,4 +54,8 @@ Gem::Specification.new do |s| s.add_dependency 'image_processing', '~> 1.2' s.add_dependency 'active_storage_validations', '~> 0.9', '<= 0.9.5' s.add_dependency 'activerecord-typedstore' + s.add_dependency 'mobility', '~> 1.2.9' + # Upgrade mobility-ransack version to 1.2.0 after this issue is resolved + s.add_dependency 'mobility-ransack', '~> 1.0.1' + s.add_dependency 'friendly_id-mobility', '~> 1.0.3' end diff --git a/sample/db/samples/product_properties.rb b/sample/db/samples/product_properties.rb index ac603d68b44..4b2695cebda 100644 --- a/sample/db/samples/product_properties.rb +++ b/sample/db/samples/product_properties.rb @@ -43,7 +43,6 @@ 'gender' => 'Men\'s' } ] - Spree::Taxon.find_by!(name: 'Men').children.find_by!(name: 'T-shirts').products.each do |product| men_tshirts_properties.sample.each do |prop_name, prop_value| product.set_property(prop_name, prop_value, prop_name.gsub('_', ' ').capitalize) diff --git a/sample/db/samples/products.rb b/sample/db/samples/products.rb index 7beac2b2fdb..671f446fd1c 100644 --- a/sample/db/samples/products.rb +++ b/sample/db/samples/products.rb @@ -19,17 +19,20 @@ PRODUCTS.each do |(parent_name, taxon_name, product_name)| parent = Spree::Taxon.find_by!(name: parent_name) taxon = parent.children.find_by!(name: taxon_name) - Spree::Product.where(name: product_name.titleize).first_or_create! do |product| + next if Spree::Product.where(name: product_name.titleize).any? + + Spree::Product.create!(name: product_name.titleize) do |product| product.price = rand(10...100) + 0.99 product.description = FFaker::Lorem.paragraph product.available_on = Time.zone.now product.make_active_at = Time.zone.now product.status = 'active' - if parent_name == 'Women' and %w[Dresses Skirts].include?(taxon_name) - product.option_types = [color, length, size] - else - product.option_types = [color, size] - end + product.option_types = + if parent_name == 'Women' && %w[Dresses Skirts].include?(taxon_name) + [color, length, size] + else + [color, size] + end product.shipping_category = default_shipping_category product.tax_category = clothing_tax_category product.sku = [taxon_name.delete(' '), product_name.delete(' '), product.price].join('_') diff --git a/sample/db/samples/taxons.rb b/sample/db/samples/taxons.rb index 40bcf624751..c3f0bb66f4e 100644 --- a/sample/db/samples/taxons.rb +++ b/sample/db/samples/taxons.rb @@ -16,34 +16,65 @@ categories_taxon = Spree::Taxon.where(name: I18n.t('spree.taxonomy_categories_name')).first_or_create! TAXON_NAMES.each do |taxon_name| - taxon = categories_taxon.children.where(name: taxon_name).first_or_create! + taxon = + if not categories_taxon.children.where(name: taxon_name).exists? + Spree::Taxon.create!(name: taxon_name, parent_id: categories_taxon.id, taxonomy: categories) + else + categories_taxon.children.where(name: taxon_name).first + end taxon.taxonomy = categories taxon.save! end CHILDREN_TAXON_NAMES.each do |(parent_name, taxon_name)| parent = Spree::Taxon.where(name: parent_name).first - taxon = parent.children.where(name: taxon_name).first_or_create! + taxon = + if parent.children.where(name: taxon_name).any? + parent.children.where(name: taxon_name).first + else + Spree::Taxon.create!(name: taxon_name, parent_id: parent.id, taxonomy: categories) + end taxon.taxonomy = categories taxon.save! end -taxon = categories_taxon.children.where(name: 'New').first_or_create! +taxon = + if categories_taxon.children.where(name: 'New').any? + categories_taxon.children.where(name: 'New').first + else + Spree::Taxon.create!(name: 'New', parent_id: categories_taxon.id, taxonomy: categories) + end taxon.taxonomy = categories taxon.save! ADDITIONAL_TAXONS.each do |taxon_name| - taxon = categories_taxon.children.where(name: taxon_name).first_or_create! + taxon = + if categories_taxon.children.where(name: taxon_name).any? + categories_taxon.children.where(name: taxon_name).first + else + Spree::Taxon.create!(name: taxon_name, parent_id: categories_taxon.id, taxonomy: categories) + end taxon.taxonomy = categories taxon.save! end SPECIAL_TAXONS.each do |parent_name, taxon_name| - parent = categories_taxon.children.where(name: parent_name.to_s).first_or_create! + parent = + if categories_taxon.children.where(name: parent_name.to_s).any? + categories_taxon.children.where(name: parent_name.to_s).first + else + Spree::Taxon.create!(name: parent_name.to_s, parent_id: categories_taxon.id, taxonomy: categories) + end parent.taxonomy = categories parent.save! - taxon = parent.children.where(name: taxon_name).first_or_create! + taxon = + if parent.children.where(name: taxon_name).any? + parent.children.where(name: taxon_name).first + else + Spree::Taxon.create!(name: taxon_name, parent_id: parent.id, taxonomy: categories) + end + taxon.taxonomy = categories taxon.save! end diff --git a/sample/lib/spree_sample.rb b/sample/lib/spree_sample.rb index 4421947d90d..ff014991b53 100644 --- a/sample/lib/spree_sample.rb +++ b/sample/lib/spree_sample.rb @@ -5,6 +5,7 @@ module SpreeSample class Engine < Rails::Engine engine_name 'spree_sample' + config.active_record.yaml_column_permitted_classes = [Symbol] # Needs to be here so we can access it inside the tests def self.load_samples Spree::Sample.load_sample('addresses') From 4b997b77834b7fc4b3c0d3cc04e0a4ed594bead2 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Tue, 17 Jan 2023 11:40:43 +0100 Subject: [PATCH 64/89] update migrations for mysql compatibility --- core/app/finders/spree/products/find.rb | 6 ++-- core/app/sorters/spree/products/sort.rb | 2 +- ...fer_options_data_to_translatable_tables.rb | 36 ++++++++++--------- ...er_property_data_to_translatable_tables.rb | 36 ++++++++++--------- 4 files changed, 43 insertions(+), 37 deletions(-) diff --git a/core/app/finders/spree/products/find.rb b/core/app/finders/spree/products/find.rb index fc06af2bd77..116d44e4304 100644 --- a/core/app/finders/spree/products/find.rb +++ b/core/app/finders/spree/products/find.rb @@ -148,10 +148,8 @@ def by_name(products) product_name = name - # i18n scope doesn't automatically get set here (mobility gem bug?) set it explicitly - products.i18n do - name.matches("%#{product_name}%") - end + # i18n mobility scope doesn't automatically get set for query blocks (Mobility issue #599) - set it explicitly + products.i18n { name.matches("%#{product_name}%") } end def by_options(products) diff --git a/core/app/sorters/spree/products/sort.rb b/core/app/sorters/spree/products/sort.rb index 0eca2d54dd7..511dabd8692 100644 --- a/core/app/sorters/spree/products/sort.rb +++ b/core/app/sorters/spree/products/sort.rb @@ -49,7 +49,7 @@ def sort_by?(field) sort.detect { |s| s[0] == field } end - # Add translatable fields to SELECT statement to avoid InvalidColumnReference error (Mobility bug workaround) + # Add translatable fields to SELECT statement to avoid InvalidColumnReference error (workaround for Mobility issue #596) def select_translatable_fields(scope) translatable_fields = translatable_sortable_fields return scope if translatable_fields.empty? diff --git a/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb index c02765469ae..ff7eb328fea 100644 --- a/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb +++ b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb @@ -8,25 +8,27 @@ def up # Option Types if not Spree::OptionType::Translation.exists? ActiveRecord::Base.connection.execute(" - INSERT INTO spree_option_type_translations (name, presentation, locale, spree_option_type_id, created_at, updated_at) - SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at - FROM spree_option_types; - - UPDATE spree_option_types - SET name=null, presentation=null; - ") + INSERT INTO spree_option_type_translations (name, presentation, locale, spree_option_type_id, created_at, updated_at) + SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_option_types; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_types + SET name=null, presentation=null; + ") end # Option Values if not Spree::OptionValue::Translation.exists? ActiveRecord::Base.connection.execute(" - INSERT INTO spree_option_value_translations (name, presentation, locale, spree_option_value_id, created_at, updated_at) - SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at - FROM spree_option_values; - - UPDATE spree_option_values - SET name=null, presentation=null; - ") + INSERT INTO spree_option_value_translations (name, presentation, locale, spree_option_value_id, created_at, updated_at) + SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_option_values; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_values + SET name=null, presentation=null; + ") end end @@ -37,7 +39,8 @@ def down SET (name, presentation) = (t_option_types.name, t_option_types.presentation) FROM spree_option_type_translations AS t_option_types WHERE t_option_types.spree_option_type_id = option_types.id; - + ") + ActiveRecord::Base.connection.execute(" TRUNCATE TABLE spree_option_type_translations; ") @@ -47,7 +50,8 @@ def down SET (name, presentation) = (t_option_values.name, t_option_values.presentation) FROM spree_option_value_translations AS t_option_values WHERE t_option_values.spree_option_value_id = option_values.id; - + ") + ActiveRecord::Base.connection.execute(" TRUNCATE TABLE spree_option_value_translations; ") end diff --git a/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb index 804e5857032..5fb7814dc00 100644 --- a/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb +++ b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb @@ -7,25 +7,27 @@ def up if not Spree::Property::Translation.exists? ActiveRecord::Base.connection.execute(" - INSERT INTO spree_property_translations (name, presentation, filter_param, locale, spree_property_id, created_at, updated_at) - SELECT name, presentation, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at - FROM spree_properties; - - UPDATE spree_properties - SET name=null, presentation=null, filter_param=null; - ") + INSERT INTO spree_property_translations (name, presentation, filter_param, locale, spree_property_id, created_at, updated_at) + SELECT name, presentation, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_properties; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_properties + SET name=null, presentation=null, filter_param=null; + ") end # Product Properties if not Spree::ProductProperty::Translation.exists? ActiveRecord::Base.connection.execute(" - INSERT INTO spree_product_property_translations (value, filter_param, locale, spree_product_property_id, created_at, updated_at) - SELECT value, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at - FROM spree_product_properties; - - UPDATE spree_product_properties - SET value=null, filter_param=null; - ") + INSERT INTO spree_product_property_translations (value, filter_param, locale, spree_product_property_id, created_at, updated_at) + SELECT value, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_product_properties; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_product_properties + SET value=null, filter_param=null; + ") end end @@ -38,7 +40,8 @@ def down SET (name, presentation, filter_param) = (t_properties.name, t_properties.presentation, t_properties.filter_param) FROM spree_property_translations AS t_properties WHERE t_properties.spree_property_id = properties.id; - + ") + ActiveRecord::Base.connection.execute(" TRUNCATE TABLE spree_property_translations; ") @@ -48,7 +51,8 @@ def down SET (value, filter_param) = (t_product_properties.value, t_product_properties.filter_param) FROM spree_product_property_translations AS t_product_properties WHERE t_product_properties.spree_product_property_id = product_properties.id; - + ") + ActiveRecord::Base.connection.execute(" TRUNCATE TABLE spree_product_property_translations; ") end From ad8f3aa4e1d020212e25341b1c84567306e689e0 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 18 Jan 2023 11:15:20 +0100 Subject: [PATCH 65/89] taxonomy translations + data transfer abstraction class --- core/app/models/spree/taxonomy.rb | 4 ++ ...0117115531_create_taxonomy_translations.rb | 21 ++++++++++ ...20230117120430_allow_null_taxonomy_name.rb | 5 +++ ...er_taxonomy_data_to_translatable_tables.rb | 11 +++++ core/lib/spree/core.rb | 1 + core/lib/spree/translation_migrations.rb | 40 +++++++++++++++++++ 6 files changed, 82 insertions(+) create mode 100644 core/db/migrate/20230117115531_create_taxonomy_translations.rb create mode 100644 core/db/migrate/20230117120430_allow_null_taxonomy_name.rb create mode 100644 core/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb create mode 100644 core/lib/spree/translation_migrations.rb diff --git a/core/app/models/spree/taxonomy.rb b/core/app/models/spree/taxonomy.rb index 4af1ae54372..f41d598f5a6 100644 --- a/core/app/models/spree/taxonomy.rb +++ b/core/app/models/spree/taxonomy.rb @@ -1,10 +1,14 @@ module Spree class Taxonomy < Spree::Base + include TranslatableResource include Metadata if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name] + translates(*TRANSLATABLE_FIELDS) + acts_as_list validates :name, presence: true, uniqueness: { case_sensitive: false, allow_blank: true, scope: :store_id } diff --git a/core/db/migrate/20230117115531_create_taxonomy_translations.rb b/core/db/migrate/20230117115531_create_taxonomy_translations.rb new file mode 100644 index 00000000000..8cff141f324 --- /dev/null +++ b/core/db/migrate/20230117115531_create_taxonomy_translations.rb @@ -0,0 +1,21 @@ +class CreateTaxonomyTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists? 'spree_taxonomy_translations' + remove_index :spree_taxonomy_translations, column: :spree_taxonomy_id, if_exists: true + else + create_table :spree_taxonomy_translations do |t| + # Translated attribute(s) + t.string :name + + t.string :locale, null: false + t.references :spree_taxonomy, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + + add_index :spree_taxonomy_translations, :locale, name: :index_spree_taxonomy_translations_on_locale + end + + add_index :spree_taxonomy_translations, [:spree_taxonomy_id, :locale], name: :index_spree_taxonomy_translations_on_spree_taxonomy_id_locale, unique: true + end +end diff --git a/core/db/migrate/20230117120430_allow_null_taxonomy_name.rb b/core/db/migrate/20230117120430_allow_null_taxonomy_name.rb new file mode 100644 index 00000000000..743d40de420 --- /dev/null +++ b/core/db/migrate/20230117120430_allow_null_taxonomy_name.rb @@ -0,0 +1,5 @@ +class AllowNullTaxonomyName < ActiveRecord::Migration[6.1] + def change + change_column_null :spree_taxonomies, :name, true + end +end diff --git a/core/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb b/core/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb new file mode 100644 index 00000000000..a3ef2371236 --- /dev/null +++ b/core/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb @@ -0,0 +1,11 @@ +class TransferTaxonomyDataToTranslatableTables < ActiveRecord::Migration[6.1] + TRANSLATION_MIGRATION = Spree::TranslationMigrations.new(Spree::Taxonomy, 'en') + + def up + TRANSLATION_MIGRATION.transfer_translation_data + end + + def down + TRANSLATION_MIGRATION.revert_translation_data_transfer + end +end diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb index 31a04ed58bf..c95b077704a 100644 --- a/core/lib/spree/core.rb +++ b/core/lib/spree/core.rb @@ -117,6 +117,7 @@ class DestroyWithOrdersError < StandardError; end require 'spree/core/number_generator' require 'spree/migrations' +require 'spree/translation_migrations' require 'spree/core/engine' require 'spree/i18n' diff --git a/core/lib/spree/translation_migrations.rb b/core/lib/spree/translation_migrations.rb new file mode 100644 index 00000000000..19380712b29 --- /dev/null +++ b/core/lib/spree/translation_migrations.rb @@ -0,0 +1,40 @@ +module Spree + class TranslationMigrations + def initialize(resource_class, default_locale) + @resource_class = resource_class + @translations_table = resource_class::Translation.table_name + @translatable_fields = resource_class.translatable_fields.join(', ') + @foreign_key = "#{resource_class.table_name.singularize}_id" + @default_locale = default_locale + end + + def transfer_translation_data + nullify_translatable_fields = @resource_class.translatable_fields.map { |f| "#{f}=null" }.join(', ') + + unless @resource_class::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO #{@translations_table} (#{@translatable_fields}, #{@foreign_key}, locale, created_at, updated_at) + SELECT #{@translatable_fields}, id, '#{@default_locale}' as locale, created_at, updated_at FROM #{@resource_class.table_name}; + ") + ActiveRecord::Base.connection.execute(" + UPDATE #{@resource_class.table_name} + SET #{nullify_translatable_fields}; + ") + end + end + + def revert_translation_data_transfer + translation_table_fields = @resource_class.translatable_fields.map { |f| "#{@translations_table}.#{f}" }.join(', ') + row_expression = @resource_class.translatable_fields.count == 1 ? 'ROW' : '' + + ActiveRecord::Base.connection.execute(" + UPDATE #{@resource_class.table_name} + SET (#{@translatable_fields}) = #{row_expression}(#{translation_table_fields}) + FROM #{@translations_table} + WHERE #{@translations_table}.#{@foreign_key} = #{@resource_class.table_name}.id + ") + + ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{@translations_table}") + end + end +end From c703f6f932f6b63626ef3670ffcc22381d49f490 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 18 Jan 2023 12:03:40 +0100 Subject: [PATCH 66/89] fix broken taxonomy related tests --- api/spec/integration/api/v2/platform/taxonomies_spec.rb | 7 ++++++- core/app/models/spree/product.rb | 9 +++++++-- core/app/models/spree/taxon.rb | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/api/spec/integration/api/v2/platform/taxonomies_spec.rb b/api/spec/integration/api/v2/platform/taxonomies_spec.rb index 6ec35dfb59b..728ff598fbe 100644 --- a/api/spec/integration/api/v2/platform/taxonomies_spec.rb +++ b/api/spec/integration/api/v2/platform/taxonomies_spec.rb @@ -11,7 +11,12 @@ let(:id) { create(:taxonomy, store: store).id } let(:records_list) { create_list(:taxonomy, 2) } - let(:valid_create_param_value) { build(:taxonomy).attributes } + let(:valid_create_param_value) do + { + name: 'First Taxonomy', + store: Spree::Store.default + } + end let(:valid_update_param_value) do { name: 'Categories', diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index 99367998d0e..be71054244c 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -355,11 +355,16 @@ def master end def brand - @brand ||= taxons.joins(:taxonomy).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_brands_name) }) + @brand ||= taxons.joins(:taxonomy). + join_translation_table(Taxonomy). + find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_brands_name) }) end def category - @category ||= taxons.joins(:taxonomy).order(depth: :desc).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_categories_name) }) + @category ||= taxons.joins(:taxonomy). + join_translation_table(Taxonomy). + order(depth: :desc). + find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_categories_name) }) end def taxons_for_store(store) diff --git a/core/app/models/spree/taxon.rb b/core/app/models/spree/taxon.rb index 6589fbf9a19..a17ce3cb2cc 100644 --- a/core/app/models/spree/taxon.rb +++ b/core/app/models/spree/taxon.rb @@ -4,6 +4,7 @@ module Spree class Taxon < Spree::Base include TranslatableResource + include TranslatableResourceScopes include Metadata if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks From b33abc2484c84608453ffddedc467de4b5957b9a Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 18 Jan 2023 14:41:07 +0100 Subject: [PATCH 67/89] update brakeman ignore --- core/brakeman.ignore | 71 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/core/brakeman.ignore b/core/brakeman.ignore index a072ae9c045..146de579c7f 100644 --- a/core/brakeman.ignore +++ b/core/brakeman.ignore @@ -69,6 +69,29 @@ ], "note": "" }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "857c335935a00f584137f31dbcb1a4532af5c8bb5cf53a86058b4af98c6597dc", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "lib/spree/translation_migrations.rb", + "line": 21, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "ActiveRecord::Base.connection.execute(\"\\n UPDATE #{resource_class.table_name}\\n SET #{resource_class.translatable_fields.map do\n \"#{f}=null\"\n end.join(\", \")};\\n \")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::TranslationMigrations", + "method": "transfer_translation_data" + }, + "user_input": "resource_class.translatable_fields.map do\n \"#{f}=null\"\n end.join(\", \")", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + }, { "warning_type": "SQL Injection", "warning_code": 0, @@ -115,6 +138,29 @@ ], "note": "interpolating table name" }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "c1c97347a2d74ea41d46519e3bfbd94c511a1bd9c285f3f2a1fa0cb7e624d232", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "lib/spree/translation_migrations.rb", + "line": 32, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "ActiveRecord::Base.connection.execute(\"\\n UPDATE #{resource_class.table_name}\\n SET (#{resource_class.translatable_fields.join(\", \")}) = #{(\"ROW\" or \"\")}(#{resource_class.translatable_fields.map do\n \"#{resource_class::Translation.table_name}.#{f}\"\n end.join(\", \")})\\n FROM #{resource_class::Translation.table_name}\\n WHERE #{resource_class::Translation.table_name}.#{\"#{resource_class.table_name.singularize}_id\"} = #{resource_class.table_name}.id\\n \")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::TranslationMigrations", + "method": "revert_translation_data_transfer" + }, + "user_input": "resource_class.translatable_fields.join(\", \")", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + }, { "warning_type": "SQL Injection", "warning_code": 0, @@ -183,8 +229,31 @@ 89 ], "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "f14dd62fac0dd1e9d5532dd5efc770e2eb873a8db80faf366b6295378634754a", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "lib/spree/translation_migrations.rb", + "line": 16, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "ActiveRecord::Base.connection.execute(\"\\n INSERT INTO #{resource_class::Translation.table_name} (#{resource_class.translatable_fields.join(\", \")}, #{\"#{resource_class.table_name.singularize}_id\"}, locale, created_at, updated_at)\\n SELECT #{resource_class.translatable_fields.join(\", \")}, id, '#{default_locale}' as locale, created_at, updated_at FROM #{resource_class.table_name};\\n \")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::TranslationMigrations", + "method": "transfer_translation_data" + }, + "user_input": "resource_class.translatable_fields.join(\", \")", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" } ], - "updated": "2023-01-13 11:44:19 +0100", + "updated": "2023-01-18 14:40:41 +0100", "brakeman_version": "5.4.0" } From 05dcff85b8a066cac30ec18948878ca28c850ed9 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 18 Jan 2023 15:49:58 +0100 Subject: [PATCH 68/89] menu translations --- core/app/models/spree/menu.rb | 4 ++++ .../20230118144224_create_menu_translations.rb | 16 ++++++++++++++++ .../20230118144642_allow_null_menu_name.rb | 5 +++++ ..._transfer_menu_data_to_translatable_tables.rb | 11 +++++++++++ 4 files changed, 36 insertions(+) create mode 100644 core/db/migrate/20230118144224_create_menu_translations.rb create mode 100644 core/db/migrate/20230118144642_allow_null_menu_name.rb create mode 100644 core/db/migrate/20230118144800_transfer_menu_data_to_translatable_tables.rb diff --git a/core/app/models/spree/menu.rb b/core/app/models/spree/menu.rb index 7705f442dd0..4cd8c1b96d6 100644 --- a/core/app/models/spree/menu.rb +++ b/core/app/models/spree/menu.rb @@ -1,10 +1,14 @@ module Spree class Menu < Spree::Base include SingleStoreResource + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name] + translates(*TRANSLATABLE_FIELDS) + MENU_LOCATIONS = ['Header', 'Footer'] MENU_LOCATIONS_PARAMETERIZED = [] diff --git a/core/db/migrate/20230118144224_create_menu_translations.rb b/core/db/migrate/20230118144224_create_menu_translations.rb new file mode 100644 index 00000000000..5ec30605bdd --- /dev/null +++ b/core/db/migrate/20230118144224_create_menu_translations.rb @@ -0,0 +1,16 @@ +class CreateMenuTranslations < ActiveRecord::Migration[6.1] + def change + create_table :spree_menu_translations do |t| + # Translated attribute(s) + t.string :name + + t.string :locale, null: false + t.references :spree_menu, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + + add_index :spree_menu_translations, :locale, name: :index_spree_menu_translations_on_locale + add_index :spree_menu_translations, [:spree_menu_id, :locale], name: :index_spree_menu_translations_on_spree_menu_id_and_locale, unique: true + end +end diff --git a/core/db/migrate/20230118144642_allow_null_menu_name.rb b/core/db/migrate/20230118144642_allow_null_menu_name.rb new file mode 100644 index 00000000000..0c5e217504d --- /dev/null +++ b/core/db/migrate/20230118144642_allow_null_menu_name.rb @@ -0,0 +1,5 @@ +class AllowNullMenuName < ActiveRecord::Migration[6.1] + def change + change_column_null :spree_menus, :name, true + end +end diff --git a/core/db/migrate/20230118144800_transfer_menu_data_to_translatable_tables.rb b/core/db/migrate/20230118144800_transfer_menu_data_to_translatable_tables.rb new file mode 100644 index 00000000000..8e31b2bc417 --- /dev/null +++ b/core/db/migrate/20230118144800_transfer_menu_data_to_translatable_tables.rb @@ -0,0 +1,11 @@ +class TransferMenuDataToTranslatableTables < ActiveRecord::Migration[6.1] + TRANSLATION_MIGRATION = Spree::TranslationMigrations.new(Spree::Menu, 'en') + + def up + TRANSLATION_MIGRATION.transfer_translation_data + end + + def down + TRANSLATION_MIGRATION.revert_translation_data_transfer + end +end From 895d415d1b9ced6a0b4b350f086d277d483d761c Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 18 Jan 2023 16:31:28 +0100 Subject: [PATCH 69/89] fix broken menu tests --- api/spec/integration/api/v2/platform/menus_spec.rb | 9 ++++++++- .../requests/spree/api/v2/platform/resources_spec.rb | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/api/spec/integration/api/v2/platform/menus_spec.rb b/api/spec/integration/api/v2/platform/menus_spec.rb index 700c699d6dd..eb2db184f5a 100644 --- a/api/spec/integration/api/v2/platform/menus_spec.rb +++ b/api/spec/integration/api/v2/platform/menus_spec.rb @@ -33,7 +33,14 @@ create(:menu_item, menu: menu_2) create(:menu_item, menu: menu_2) end - let(:valid_create_param_value) { build(:menu, name: 'Main Menu').attributes } + let(:valid_create_param_value) do + { + name: 'Special Menu', + locale: 'en', + location: 'Header', + store: Spree::Store.default + } + end let(:valid_update_param_value) do { name: 'Main Menu', diff --git a/api/spec/requests/spree/api/v2/platform/resources_spec.rb b/api/spec/requests/spree/api/v2/platform/resources_spec.rb index 933ed100f3d..cf67cc2b55c 100644 --- a/api/spec/requests/spree/api/v2/platform/resources_spec.rb +++ b/api/spec/requests/spree/api/v2/platform/resources_spec.rb @@ -327,7 +327,15 @@ context '#ensure_current_store' do context 'single store resource' do let(:execute) { post '/api/v2/platform/menus', params: menu_resource_params, headers: bearer_token } - let(:menu_resource_params) { { menu: build(:menu, name: 'Ensure-MenuTest', location: 'Header', locale: 'en').attributes.symbolize_keys } } + let(:menu_attribute_hash) do + { + :id => 77, + :name => 'Ensure-MenuTest', + :location => 'Header', + :locale => 'en' + } + end + let(:menu_resource_params) { { menu: menu_attribute_hash } } before { execute } From 3f66724774a05d0a44854232d1e2115fbb1ab6ea Mon Sep 17 00:00:00 2001 From: nciemniak Date: Thu, 19 Jan 2023 11:21:51 +0100 Subject: [PATCH 70/89] account for issue with Rails if_exists on dropping indexes --- ..._description_translations_for_mobility_table_backend.rb | 6 ++++-- ..._description_translations_for_mobility_table_backend.rb | 7 +++++-- .../20230103144439_create_option_type_translations.rb | 5 ++++- .../20230103151034_create_option_value_translations.rb | 5 ++++- .../20230109084253_create_product_property_translations.rb | 5 ++++- .../migrate/20230109105943_create_property_translations.rb | 7 +++++-- .../migrate/20230117115531_create_taxonomy_translations.rb | 7 +++++-- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb b/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb index fd00e191c8b..1e732aeb245 100644 --- a/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb +++ b/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb @@ -2,8 +2,10 @@ class CreateProductNameAndDescriptionTranslationsForMobilityTableBackend < Activ def change # create translation table only if spree_globalize has not already created it if ActiveRecord::Base.connection.table_exists? 'spree_product_translations' - # replacing this with index on spree_product_id and locale - remove_index :spree_product_translations, name: "index_spree_product_translations_on_spree_product_id", if_exists: true + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_product_translations, :spree_product_id) + remove_index :spree_product_translations, name: "index_spree_product_translations_on_spree_product_id", if_exists: true + end else create_table :spree_product_translations do |t| diff --git a/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb b/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb index 4b654a69f81..1ffa0f77376 100644 --- a/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb +++ b/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb @@ -2,8 +2,11 @@ class CreateSpreeTaxonNameAndDescriptionTranslationsForMobilityTableBackend < Ac def change # create translation table only if spree_globalize has not already created it if ActiveRecord::Base.connection.table_exists? 'spree_taxon_translations' - # replacing this with index on spree_taxon_id and locale - remove_index :spree_taxon_translations, name: "index_spree_taxon_translations_on_spree_taxon_id", if_exists: true + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_taxon_translations, :spree_taxon_id) + # replacing this with index on spree_taxon_id and locale + remove_index :spree_taxon_translations, name: "index_spree_taxon_translations_on_spree_taxon_id", if_exists: true + end else create_table :spree_taxon_translations do |t| # Translated attribute(s) diff --git a/core/db/migrate/20230103144439_create_option_type_translations.rb b/core/db/migrate/20230103144439_create_option_type_translations.rb index 359fb2bc674..5f798dc909a 100644 --- a/core/db/migrate/20230103144439_create_option_type_translations.rb +++ b/core/db/migrate/20230103144439_create_option_type_translations.rb @@ -1,7 +1,10 @@ class CreateOptionTypeTranslations < ActiveRecord::Migration[6.1] def change if ActiveRecord::Base.connection.table_exists? 'spree_option_type_translations' - remove_index :spree_option_type_translations, name: "index_spree_option_type_translations_on_spree_option_type_id", if_exists: true + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_option_type_translations, :spree_option_type_id) + remove_index :spree_option_type_translations, name: "index_spree_option_type_translations_on_spree_option_type_id", if_exists: true + end else create_table :spree_option_type_translations do |t| diff --git a/core/db/migrate/20230103151034_create_option_value_translations.rb b/core/db/migrate/20230103151034_create_option_value_translations.rb index 2acb37a3113..b04a264de7c 100644 --- a/core/db/migrate/20230103151034_create_option_value_translations.rb +++ b/core/db/migrate/20230103151034_create_option_value_translations.rb @@ -1,7 +1,10 @@ class CreateOptionValueTranslations < ActiveRecord::Migration[6.1] def change if ActiveRecord::Base.connection.table_exists? 'spree_option_value_translations' - remove_index :spree_option_value_translations, name: "index_spree_option_value_translations_on_spree_option_value_id", if_exists: true + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_option_value_translations, :spree_option_value_id) + remove_index :spree_option_value_translations, column: :spree_option_value_id, if_exists: true + end else create_table :spree_option_value_translations do |t| diff --git a/core/db/migrate/20230109084253_create_product_property_translations.rb b/core/db/migrate/20230109084253_create_product_property_translations.rb index e100182a897..511ee403281 100644 --- a/core/db/migrate/20230109084253_create_product_property_translations.rb +++ b/core/db/migrate/20230109084253_create_product_property_translations.rb @@ -1,7 +1,10 @@ class CreateProductPropertyTranslations < ActiveRecord::Migration[6.1] def change if ActiveRecord::Base.connection.table_exists? 'spree_product_property_translations' - remove_index :spree_product_property_translations, column: :spree_product_property_id, if_exists: true + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_product_property_translations, :spree_product_property_id) + remove_index :spree_product_property_translations, column: :spree_product_property_id, if_exists: true + end else create_table :spree_product_property_translations do |t| # Translated attribute(s) diff --git a/core/db/migrate/20230109105943_create_property_translations.rb b/core/db/migrate/20230109105943_create_property_translations.rb index 5bff726be47..8dde187338e 100644 --- a/core/db/migrate/20230109105943_create_property_translations.rb +++ b/core/db/migrate/20230109105943_create_property_translations.rb @@ -1,7 +1,10 @@ class CreatePropertyTranslations < ActiveRecord::Migration[6.1] def change - if ActiveRecord::Base.connection.table_exists? 'spree_property_translations' - remove_index :spree_property_translations, column: :spree_property_id, if_exists: true + if ActiveRecord::Base.connection.table_exists?('spree_property_translations') + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_property_translations, :spree_property_id) + remove_index :spree_property_translations, column: :spree_property_id, if_exists: true + end else create_table :spree_property_translations do |t| # Translated attribute(s) diff --git a/core/db/migrate/20230117115531_create_taxonomy_translations.rb b/core/db/migrate/20230117115531_create_taxonomy_translations.rb index 8cff141f324..42305b860b8 100644 --- a/core/db/migrate/20230117115531_create_taxonomy_translations.rb +++ b/core/db/migrate/20230117115531_create_taxonomy_translations.rb @@ -1,7 +1,10 @@ class CreateTaxonomyTranslations < ActiveRecord::Migration[6.1] def change - if ActiveRecord::Base.connection.table_exists? 'spree_taxonomy_translations' - remove_index :spree_taxonomy_translations, column: :spree_taxonomy_id, if_exists: true + if ActiveRecord::Base.connection.table_exists?('spree_taxonomy_translations') + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_taxonomy_translations, :spree_taxonomy_id) + remove_index :spree_taxonomy_translations, column: :spree_taxonomy_id, if_exists: true + end else create_table :spree_taxonomy_translations do |t| # Translated attribute(s) From ec416ae1e6225a6740549fd7daffd6e86b2e7bfa Mon Sep 17 00:00:00 2001 From: nciemniak Date: Thu, 19 Jan 2023 11:23:05 +0100 Subject: [PATCH 71/89] Add translations for Option Type, Option Value, Property, & Product Property (#11831) Co-authored-by: aryt Co-authored-by: adrianryt Co-authored-by: Rafal Kosla --- .../api/v2/platform/variants_controller.rb | 2 +- .../api/v2/platform/option_types_spec.rb | 8 +- .../api/v2/platform/option_values_spec.rb | 9 ++- .../spree/api/v2/storefront/products_spec.rb | 40 ++++++++-- core/app/finders/spree/products/find.rb | 6 +- .../models/concerns/spree/product_scopes.rb | 32 +++++--- .../spree/translatable_resource_scopes.rb | 16 ++++ core/app/models/spree/option_type.rb | 4 + core/app/models/spree/option_value.rb | 4 + core/app/models/spree/product.rb | 24 ++++-- core/app/models/spree/product_property.rb | 16 +++- core/app/models/spree/property.rb | 8 +- core/app/models/spree/variant.rb | 9 +-- core/app/sorters/spree/products/sort.rb | 2 +- core/brakeman.ignore | 76 +++++++++++++++---- ...28_transfer_data_to_translatable_tables.rb | 66 ++++++++++++++++ ...3144439_create_option_type_translations.rb | 23 ++++++ ...151034_create_option_value_translations.rb | 23 ++++++ ...53_create_product_property_translations.rb | 22 ++++++ ...fer_options_data_to_translatable_tables.rb | 58 ++++++++++++++ ...0109105943_create_property_translations.rb | 23 ++++++ ...er_property_data_to_translatable_tables.rb | 59 ++++++++++++++ core/lib/spree/core/product_filters.rb | 11 ++- core/spec/models/spree/product_spec.rb | 2 +- core/spec/models/spree/property_spec.rb | 6 +- sample/db/samples/option_types.rb | 4 +- sample/db/samples/properties.rb | 4 +- 27 files changed, 490 insertions(+), 67 deletions(-) create mode 100644 core/app/models/concerns/spree/translatable_resource_scopes.rb create mode 100644 core/db/migrate/20220804073928_transfer_data_to_translatable_tables.rb create mode 100644 core/db/migrate/20230103144439_create_option_type_translations.rb create mode 100644 core/db/migrate/20230103151034_create_option_value_translations.rb create mode 100644 core/db/migrate/20230109084253_create_product_property_translations.rb create mode 100644 core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb create mode 100644 core/db/migrate/20230109105943_create_property_translations.rb create mode 100644 core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb diff --git a/api/app/controllers/spree/api/v2/platform/variants_controller.rb b/api/app/controllers/spree/api/v2/platform/variants_controller.rb index 93c436b4671..303752deed1 100644 --- a/api/app/controllers/spree/api/v2/platform/variants_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/variants_controller.rb @@ -16,7 +16,7 @@ def spree_permitted_attributes def collection # if filtering on products, manually join on product translation to workaround mobility-ransack issue if params.key?(:filter) && params[:filter].keys.any? { |k| k.include? 'product' } - @collection ||= scope.join_product_translations. + @collection ||= scope.joins(:product).join_translation_table(Product). ransack(params[:filter]).result else super diff --git a/api/spec/integration/api/v2/platform/option_types_spec.rb b/api/spec/integration/api/v2/platform/option_types_spec.rb index b76c9fe7f31..f98173bad25 100644 --- a/api/spec/integration/api/v2/platform/option_types_spec.rb +++ b/api/spec/integration/api/v2/platform/option_types_spec.rb @@ -12,7 +12,13 @@ let(:id) { create(:option_type).id } let(:option_type) { create(:option_type) } let(:records_list) { create_list(:option_type, 2) } - let(:valid_create_param_value) { build(:option_type).attributes } + let(:valid_create_param_value) do + { + name: 'option type name', + presentation: 'Option Type', + position: 10 + } + end let(:valid_update_param_value) do { name: 'Size-X' diff --git a/api/spec/integration/api/v2/platform/option_values_spec.rb b/api/spec/integration/api/v2/platform/option_values_spec.rb index 72dec896bff..55be093fc08 100644 --- a/api/spec/integration/api/v2/platform/option_values_spec.rb +++ b/api/spec/integration/api/v2/platform/option_values_spec.rb @@ -13,7 +13,14 @@ let(:id) { create(:option_value).id } let(:option_type) { create(:option_value) } let(:records_list) { create_list(:option_value, 2) } - let(:valid_create_param_value) { build(:option_value).attributes } + let(:valid_create_param_value) do + { + name: 'option value', + presentation: 'Option Value', + position: 10, + option_type_id: option_type.id + } + end let(:valid_update_param_value) do { name: 'M' diff --git a/api/spec/requests/spree/api/v2/storefront/products_spec.rb b/api/spec/requests/spree/api/v2/storefront/products_spec.rb index feb1d1c5b26..99b821b5ad1 100644 --- a/api/spec/requests/spree/api/v2/storefront/products_spec.rb +++ b/api/spec/requests/spree/api/v2/storefront/products_spec.rb @@ -8,9 +8,9 @@ let(:product_with_taxon) { create(:product, taxons: [taxon], stores: [store]) } let(:product_with_name) { create(:product, name: 'Test Product', stores: [store]) } let(:product_with_price) { create(:product, price: 13.44, stores: [store]) } - let!(:option_type) { create(:option_type) } + let!(:option_type) { create(:option_type, name: 'test option type') } let!(:option_value) { create(:option_value, option_type: option_type) } - let(:product_with_option) { create(:product, option_types: [option_type], stores: [store]) } + let(:product_with_option) { create(:product, name: 'Product with Option', option_types: [option_type], stores: [store]) } let!(:variant) { create(:variant, product: product_with_option, option_values: [option_value]) } let(:product) { create(:product, stores: [store]) } let!(:deleted_product) { create(:product, deleted_at: Time.current - 1.day, stores: [store]) } @@ -24,6 +24,7 @@ let!(:product_property) { create(:product_property, property: new_property, product: product_with_property, value: 'Some Value') } let!(:product_property2) { create(:product_property, property: property, product: product_with_property, value: 'Some Value 2') } + before { Spree::Api::Config[:api_v2_per_page_limit] = 4 } describe 'products#index' do @@ -151,14 +152,37 @@ end context 'with specified options' do - before { get "/api/v2/storefront/products?filter[options][#{option_type.name}]=#{option_value.name}&include=option_types,variants.option_values" } + context 'with no locale set' do + before { + get "/api/v2/storefront/products?filter[options][#{option_type.name}]=#{option_value.name}&include=option_types,variants.option_values" + } - it_behaves_like 'returns 200 HTTP status' + it_behaves_like 'returns 200 HTTP status' + + it 'returns products with specified options' do + expect(json_response['data'].first).to have_id(product_with_option.id.to_s) + expect(json_response['included']).to include(have_type('option_type').and(have_attribute(:name).with_value(option_type.name))) + expect(json_response['included']).to include(have_type('option_value').and(have_attribute(:name).with_value(option_value.name))) + end + end - it 'returns products with specified options' do - expect(json_response['data'].first).to have_id(product_with_option.id.to_s) - expect(json_response['included']).to include(have_type('option_type').and(have_attribute(:name).with_value(option_type.name))) - expect(json_response['included']).to include(have_type('option_value').and(have_attribute(:name).with_value(option_value.name))) + context 'with locale set to polish' do + # create translated resources + let!(:option_type_pl_locale) { Mobility.with_locale(:pl) { create(:option_type) } } + let!(:option_value_pl_locale) { Mobility.with_locale(:pl) { create(:option_value, option_type: option_type_pl_locale) } } + let!(:product_pl_locale) { Mobility.with_locale(:pl) { create(:product, name: 'Produkt Superowy', option_types: [option_type_pl_locale], stores: [store]) } } + let!(:variant_pl_locale) { Mobility.with_locale(:pl) { create(:variant, product: product_pl_locale, option_values: [option_value_pl_locale]) } } + + before do + store.update_column(:supported_locales, 'en,pl') + get "/api/v2/storefront/products?filter[options][#{option_type_pl_locale.name(locale: :pl)}]=#{option_value_pl_locale.name(locale: :pl)}&include=option_types,variants.option_values&locale=pl" + end + + it 'returns products with specified options in polish' do + expect(json_response['data'].first).to have_id(product_pl_locale.id.to_s) + expect(json_response['included']).to include(have_type('option_type').and(have_attribute(:name).with_value(option_type_pl_locale.name(locale: :pl)))) + expect(json_response['included']).to include(have_type('option_value').and(have_attribute(:name).with_value(option_value_pl_locale.name(locale: :pl)))) + end end end diff --git a/core/app/finders/spree/products/find.rb b/core/app/finders/spree/products/find.rb index fc06af2bd77..116d44e4304 100644 --- a/core/app/finders/spree/products/find.rb +++ b/core/app/finders/spree/products/find.rb @@ -148,10 +148,8 @@ def by_name(products) product_name = name - # i18n scope doesn't automatically get set here (mobility gem bug?) set it explicitly - products.i18n do - name.matches("%#{product_name}%") - end + # i18n mobility scope doesn't automatically get set for query blocks (Mobility issue #599) - set it explicitly + products.i18n { name.matches("%#{product_name}%") } end def by_options(products) diff --git a/core/app/models/concerns/spree/product_scopes.rb b/core/app/models/concerns/spree/product_scopes.rb index 340ad930c69..effe62f8caa 100644 --- a/core/app/models/concerns/spree/product_scopes.rb +++ b/core/app/models/concerns/spree/product_scopes.rb @@ -32,15 +32,16 @@ def self.add_simple_scopes(scopes) end def self.property_conditions(property) - properties = Property.table_name + properties_table = Property.table_name + property_translations_table = Property.translation_table_alias case property - when Property then { "#{properties}.id" => property.id } - when Integer then { "#{properties}.id" => property } + when Property then { "#{properties_table}.id" => property.id } + when Integer then { "#{properties_table}.id" => property } else if Property.column_for_attribute('id').type == :uuid - ["#{properties.name} = ? OR #{properties.id} = ?", property, property] + ["#{property_translations_table.name} = ? OR #{properties_table.id} = ?", property, property] else - { "#{properties}.name" => property } + { "#{property_translations_table}.name" => property } end end end @@ -126,21 +127,25 @@ def self.property_conditions(property) # a scope that finds all products having property specified by name, object or id add_search_scope :with_property do |property| - joins(:properties).where(property_conditions(property)) + joins(:properties).join_translation_table(Property).where(property_conditions(property)) end # a simple test for product with a certain property-value pairing # note that it can test for properties with NULL values, but not for absent values add_search_scope :with_property_value do |property, value| joins(:properties). - where("#{ProductProperty.table_name}.value = ?", value). + join_translation_table(Property). + join_translation_table(ProductProperty). + where("#{ProductProperty.translation_table_alias}.value = ?", value). where(property_conditions(property)) end add_search_scope :with_property_values do |property_filter_param, property_values| joins(product_properties: :property). - where(Property.table_name => { filter_param: property_filter_param }). - where(ProductProperty.table_name => { filter_param: property_values.map(&:parameterize) }) + join_translation_table(Property). + join_translation_table(ProductProperty). + where(Property.translation_table_alias => { filter_param: property_filter_param }). + where(ProductProperty.translation_table_alias => { filter_param: property_values.map(&:parameterize) }) end add_search_scope :with_option do |option| @@ -151,7 +156,9 @@ def self.property_conditions(property) elsif OptionType.column_for_attribute('id').type == :uuid joins(:option_types).where(spree_option_types: { name: option }).or(Product.joins(:option_types).where(spree_option_types: { id: option })) else - joins(:option_types).where(spree_option_types: { name: option }) + joins(:option_types). + join_translation_table(OptionType). + where(OptionType.translation_table_alias => { name: option }) end end @@ -164,6 +171,7 @@ def self.property_conditions(property) OptionType.where(id: option).or(OptionType.where(name: option))&.first&.id else OptionType.where(name: option)&.first&.id + OptionType.where(name: option)&.first&.id end end @@ -171,7 +179,9 @@ def self.property_conditions(property) group("#{Spree::Product.table_name}.id"). joins(variants_including_master: :option_values). - where(Spree::OptionValue.table_name => { name: value, option_type_id: option_type_id }) + join_translation_table(Spree::OptionValue). + where(Spree::OptionValue.translation_table_alias => { name: value }, + Spree::OptionValue.table_name => { option_type_id: option_type_id }) end # Finds all products which have either: diff --git a/core/app/models/concerns/spree/translatable_resource_scopes.rb b/core/app/models/concerns/spree/translatable_resource_scopes.rb new file mode 100644 index 00000000000..d7f2d75ed17 --- /dev/null +++ b/core/app/models/concerns/spree/translatable_resource_scopes.rb @@ -0,0 +1,16 @@ +module Spree + module TranslatableResourceScopes + extend ActiveSupport::Concern + + class_methods do + # To be used when joining on the resource itself does not automatically join on its translations table + # This method is to be used when you've already joined on the translatable table itself + def join_translation_table(translatable_class) + translatable_class_foreign_key = "#{translatable_class.table_name.singularize}_id" + joins("LEFT OUTER JOIN #{translatable_class::Translation.table_name} #{translatable_class.translation_table_alias} + ON #{translatable_class.translation_table_alias}.#{translatable_class_foreign_key} = #{translatable_class.table_name}.id + AND #{translatable_class.translation_table_alias}.locale = '#{Mobility.locale}'") + end + end + end +end diff --git a/core/app/models/spree/option_type.rb b/core/app/models/spree/option_type.rb index 84366de9ef9..244aaac8c94 100644 --- a/core/app/models/spree/option_type.rb +++ b/core/app/models/spree/option_type.rb @@ -2,10 +2,14 @@ module Spree class OptionType < Spree::Base include UniqueName include Metadata + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name presentation].freeze + translates(*TRANSLATABLE_FIELDS) + acts_as_list auto_strip_attributes :name, :presentation diff --git a/core/app/models/spree/option_value.rb b/core/app/models/spree/option_value.rb index ccd0623bdfc..e594d72f2f2 100644 --- a/core/app/models/spree/option_value.rb +++ b/core/app/models/spree/option_value.rb @@ -1,10 +1,14 @@ module Spree class OptionValue < Spree::Base include Metadata + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name presentation].freeze + translates(*TRANSLATABLE_FIELDS) + belongs_to :option_type, class_name: 'Spree::OptionType', touch: true, inverse_of: :option_values acts_as_list scope: :option_type diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index 80139391722..99367998d0e 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -24,6 +24,7 @@ class Product < Spree::Base include ProductScopes include MultiStoreResource include TranslatableResource + include TranslatableResourceScopes include MemoizedData include Metadata if defined?(Spree::Webhooks) @@ -39,7 +40,8 @@ class Product < Spree::Base TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_keywords meta_title].freeze translates(*TRANSLATABLE_FIELDS) - Product::Translation.class_eval { acts_as_paranoid } + + self::Translation.class_eval { acts_as_paranoid } friendly_id :slug_candidates, use: [:history, :mobility] acts_as_paranoid @@ -310,14 +312,26 @@ def empty_option_values? end def property(property_name) - product_properties.joins(:property).find_by(spree_properties: { name: property_name }).try(:value) + product_properties.joins(:property). + join_translation_table(Property). + find_by(Property.translation_table_alias => { name: property_name }).try(:value) end def set_property(property_name, property_value, property_presentation = property_name) ApplicationRecord.transaction do - # Works around spree_i18n #301 - property = Property.create_with(presentation: property_presentation).find_or_create_by(name: property_name) - product_property = ProductProperty.where(product: self, property: property).first_or_initialize + # Manual first_or_create to work around Mobility bug + property = if Property.where(name: property_name).exists? + Property.where(name: property_name).first + else + Property.create(name: property_name, presentation: property_presentation) + end + + product_property = if ProductProperty.where(product: self, property: property).exists? + ProductProperty.where(product: self, property: property).first + else + ProductProperty.create(product: self, property: property) + end + product_property.value = property_value product_property.save! end diff --git a/core/app/models/spree/product_property.rb b/core/app/models/spree/product_property.rb index 785cb7bba0f..58dec2bf853 100644 --- a/core/app/models/spree/product_property.rb +++ b/core/app/models/spree/product_property.rb @@ -1,8 +1,15 @@ module Spree class ProductProperty < Spree::Base include Spree::FilterParam + include TranslatableResource + include TranslatableResourceScopes - auto_strip_attributes :value + TRANSLATABLE_FIELDS = %i[value filter_param].freeze + translates(*TRANSLATABLE_FIELDS) + + self::Translation.class_eval do + auto_strip_attributes :value + end acts_as_list scope: :product @@ -31,8 +38,11 @@ def property_name=(name) `ProductProperty#property_name=` is deprecated and will be removed in Spree 5.0. DEPRECATION if name.present? - # don't use `find_by :name` to workaround globalize/globalize#423 bug - self.property = Property.where(name: name).first_or_create(presentation: name) + self.property = if Property.where(name: name).exists? + Property.where(name: name).first + else + Property.create(name: name, presentation: name) + end end end diff --git a/core/app/models/spree/property.rb b/core/app/models/spree/property.rb index 026522ea904..73e2f55a49f 100644 --- a/core/app/models/spree/property.rb +++ b/core/app/models/spree/property.rb @@ -2,11 +2,17 @@ module Spree class Property < Spree::Base include Spree::FilterParam include Metadata + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end - auto_strip_attributes :name, :presentation + TRANSLATABLE_FIELDS = %i[name presentation filter_param].freeze + translates(*TRANSLATABLE_FIELDS) + + self::Translation.class_eval do + auto_strip_attributes :name, :presentation + end has_many :property_prototypes, class_name: 'Spree::PropertyPrototype' has_many :prototypes, through: :property_prototypes, class_name: 'Spree::Prototype' diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index 2be9a29c3bb..0e2f44d8465 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -5,6 +5,7 @@ class Variant < Spree::Base include MemoizedData include Metadata + include TranslatableResourceScopes if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end @@ -122,17 +123,11 @@ class Variant < Spree::Base self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku) def self.product_name_or_sku_cont(query) - join_product_translations. + joins(:product).join_translation_table(Product). where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") end - def self.join_product_translations - joins(:product).joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} - ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id - AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'") - end - def self.search_by_product_name_or_sku(query) product_name_or_sku_cont(query) end diff --git a/core/app/sorters/spree/products/sort.rb b/core/app/sorters/spree/products/sort.rb index 0eca2d54dd7..511dabd8692 100644 --- a/core/app/sorters/spree/products/sort.rb +++ b/core/app/sorters/spree/products/sort.rb @@ -49,7 +49,7 @@ def sort_by?(field) sort.detect { |s| s[0] == field } end - # Add translatable fields to SELECT statement to avoid InvalidColumnReference error (Mobility bug workaround) + # Add translatable fields to SELECT statement to avoid InvalidColumnReference error (workaround for Mobility issue #596) def select_translatable_fields(scope) translatable_fields = translatable_sortable_fields return scope if translatable_fields.empty? diff --git a/core/brakeman.ignore b/core/brakeman.ignore index 24e68c5178a..a072ae9c045 100644 --- a/core/brakeman.ignore +++ b/core/brakeman.ignore @@ -7,7 +7,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/concerns/spree/product_scopes.rb", - "line": 63, + "line": 64, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "where(\"#{price_table_name}.amount <= ?\", price)", "render_path": null, @@ -26,20 +26,43 @@ { "warning_type": "SQL Injection", "warning_code": 0, - "fingerprint": "67e80bcfa8898315b0f8642bc61990c8319bc7a468453efc048757a439e53e0b", + "fingerprint": "1f02952550c2f54d044c9577a45e7ba7c7990c8b8a59d1dac83a96790237f507", "check_name": "SQL", "message": "Possible SQL injection", - "file": "app/models/spree/variant.rb", - "line": 131, + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 139, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", - "code": "joins(:product).joins(\"LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias}\\n ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id\\n AND #{Product.translation_table_alias}.locale = '#{Mobility.locale}'\")", + "code": "joins(:properties).join_translation_table(Property).join_translation_table(ProductProperty).where(\"#{ProductProperty.translation_table_alias}.value = ?\", value)", "render_path": null, "location": { "type": "method", - "class": "Spree::Variant", - "method": "Spree::Variant.join_product_translations" + "class": "Spree::ProductScopes", + "method": null }, - "user_input": "Product.translation_table_alias", + "user_input": "ProductProperty.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "7928c0813a0bf084ead091b4554ef6abea9ae9c7167936f5c62da9e328b9f736", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 139, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(:properties).join_translation_table(Property).join_translation_table(ProductProperty).where(\"#{ProductProperty.translation_table_alias}.value = ?\", value)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree", + "method": null + }, + "user_input": "ProductProperty.translation_table_alias", "confidence": "Weak", "cwe_id": [ 89 @@ -53,7 +76,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/concerns/spree/product_scopes.rb", - "line": 67, + "line": 68, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "where(\"#{price_table_name}.amount >= ?\", price)", "render_path": null, @@ -76,7 +99,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/concerns/spree/product_scopes.rb", - "line": 63, + "line": 64, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "where(\"#{price_table_name}.amount <= ?\", price)", "render_path": null, @@ -95,13 +118,13 @@ { "warning_type": "SQL Injection", "warning_code": 0, - "fingerprint": "e6bb08c012af19589f796e26b4dc2230ea1c1949c7cd6464ca025939513d8b3d", + "fingerprint": "c2bc48d98076b7c4fc3314c6a85f7bd1132efe5fcc346da4d28df7c25f93633f", "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/spree/variant.rb", - "line": 126, + "line": 127, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", - "code": "join_product_translations.where(\"LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)\\n OR LOWER(sku) LIKE LOWER(:query)\", :query => (\"%#{query}%\"))", + "code": "joins(:product).join_translation_table(Product).where(\"LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)\\n OR LOWER(sku) LIKE LOWER(:query)\", :query => (\"%#{query}%\"))", "render_path": null, "location": { "type": "method", @@ -115,6 +138,29 @@ ], "note": "" }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "e7aa61c87f26af5f63f00e9791954d787a559dab1eadf8926861796237bd431d", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/translatable_resource_scopes.rb", + "line": 10, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(\"LEFT OUTER JOIN #{translatable_class::Translation.table_name} #{translatable_class.translation_table_alias}\\n ON #{translatable_class.translation_table_alias}.#{\"#{translatable_class.table_name.singularize}_id\"} = #{translatable_class.table_name}.id\\n AND #{translatable_class.translation_table_alias}.locale = '#{Mobility.locale}'\")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::TranslatableResourceScopes", + "method": "join_translation_table" + }, + "user_input": "translatable_class.translation_table_alias", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + }, { "warning_type": "SQL Injection", "warning_code": 0, @@ -122,7 +168,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "app/models/concerns/spree/product_scopes.rb", - "line": 67, + "line": 68, "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "where(\"#{price_table_name}.amount >= ?\", price)", "render_path": null, @@ -139,6 +185,6 @@ "note": "interpolating table name" } ], - "updated": "2022-12-29 09:32:26 +0100", + "updated": "2023-01-13 11:44:19 +0100", "brakeman_version": "5.4.0" } diff --git a/core/db/migrate/20220804073928_transfer_data_to_translatable_tables.rb b/core/db/migrate/20220804073928_transfer_data_to_translatable_tables.rb new file mode 100644 index 00000000000..e5aac575e64 --- /dev/null +++ b/core/db/migrate/20220804073928_transfer_data_to_translatable_tables.rb @@ -0,0 +1,66 @@ +class TransferDataToTranslatableTables < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + PRODUCTS_TABLE = 'spree_products' + PRODUCT_TRANSLATIONS_TABLE = 'spree_product_translations' + TAXONS_TABLE = 'spree_taxons' + TAXON_TRANSLATIONS_TABLE = 'spree_taxon_translations' + + def up + # Products + ActiveRecord::Base.connection.execute(" + INSERT INTO #{PRODUCT_TRANSLATIONS_TABLE} (name, description, locale, spree_product_id, created_at, updated_at, meta_description, meta_keywords, meta_title, slug) + SELECT name, description, '#{DEFAULT_LOCALE}' as locale, id, created_at, updated_at, meta_description, meta_keywords, meta_title, slug FROM #{PRODUCTS_TABLE} + ") + ActiveRecord::Base.connection.execute(" + UPDATE #{PRODUCTS_TABLE} + SET name=null, description=null, meta_description=null, meta_keywords=null, meta_title=null, slug=null; + ") + #Taxons + ActiveRecord::Base.connection.execute(" + INSERT INTO #{TAXON_TRANSLATIONS_TABLE} (name, description, locale, spree_taxon_id, created_at, updated_at) + SELECT name, description, '#{DEFAULT_LOCALE}' as locale, id, created_at, updated_at FROM #{TAXONS_TABLE} + ") + ActiveRecord::Base.connection.execute(" + UPDATE #{TAXONS_TABLE} + SET name=null, description=null + ") + end + + def down + ActiveRecord::Base.connection.execute(" + UPDATE #{PRODUCTS_TABLE} as products + SET (name, + description, + meta_description, + meta_keywords, + meta_title, + slug) = + (t_products.name, + t_products.description, + t_products.meta_description, + t_products.meta_keywords, + t_products.meta_title, + t_products.slug) + FROM #{PRODUCT_TRANSLATIONS_TABLE} AS t_products + WHERE t_products.spree_product_id = products.id + ") + + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE #{PRODUCT_TRANSLATIONS_TABLE} + ") + + ActiveRecord::Base.connection.execute(" + UPDATE #{TAXONS_TABLE} as taxons + SET (name, + description) = + (t_taxons.name, + t_taxons.description) + FROM #{TAXON_TRANSLATIONS_TABLE} AS t_taxons + WHERE t_taxons.spree_taxon_id = taxons.id + ") + + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE #{TAXON_TRANSLATIONS_TABLE} + ") + end +end diff --git a/core/db/migrate/20230103144439_create_option_type_translations.rb b/core/db/migrate/20230103144439_create_option_type_translations.rb new file mode 100644 index 00000000000..359fb2bc674 --- /dev/null +++ b/core/db/migrate/20230103144439_create_option_type_translations.rb @@ -0,0 +1,23 @@ +class CreateOptionTypeTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists? 'spree_option_type_translations' + remove_index :spree_option_type_translations, name: "index_spree_option_type_translations_on_spree_option_type_id", if_exists: true + else + create_table :spree_option_type_translations do |t| + + # Translated attribute(s) + t.string :name + t.string :presentation + + t.string :locale, null: false + t.references :spree_option_type, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_option_type_translations, :locale, name: :index_spree_option_type_translations_on_locale + end + + add_index :spree_option_type_translations, [:spree_option_type_id, :locale], name: :unique_option_type_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230103151034_create_option_value_translations.rb b/core/db/migrate/20230103151034_create_option_value_translations.rb new file mode 100644 index 00000000000..2acb37a3113 --- /dev/null +++ b/core/db/migrate/20230103151034_create_option_value_translations.rb @@ -0,0 +1,23 @@ +class CreateOptionValueTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists? 'spree_option_value_translations' + remove_index :spree_option_value_translations, name: "index_spree_option_value_translations_on_spree_option_value_id", if_exists: true + else + create_table :spree_option_value_translations do |t| + + # Translated attribute(s) + t.string :name + t.string :presentation + + t.string :locale, null: false + t.references :spree_option_value, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_option_value_translations, :locale, name: :index_spree_option_value_translations_on_locale + end + + add_index :spree_option_value_translations, [:spree_option_value_id, :locale], name: :unique_option_value_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230109084253_create_product_property_translations.rb b/core/db/migrate/20230109084253_create_product_property_translations.rb new file mode 100644 index 00000000000..e100182a897 --- /dev/null +++ b/core/db/migrate/20230109084253_create_product_property_translations.rb @@ -0,0 +1,22 @@ +class CreateProductPropertyTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists? 'spree_product_property_translations' + remove_index :spree_product_property_translations, column: :spree_product_property_id, if_exists: true + else + create_table :spree_product_property_translations do |t| + # Translated attribute(s) + t.string :value + t.string :filter_param + + t.string :locale, null: false + t.references :spree_product_property, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_product_property_translations, :locale, name: :index_spree_product_property_translations_on_locale + end + + add_index :spree_product_property_translations, [:spree_product_property_id, :locale], name: :unique_product_property_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb new file mode 100644 index 00000000000..ff7eb328fea --- /dev/null +++ b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb @@ -0,0 +1,58 @@ +class TransferOptionsDataToTranslatableTables < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + + def up + # Only transfer data if translation tables are being newly created / no translations exist + # Otherwise, assume translation data is already in place from spree_globalize + + # Option Types + if not Spree::OptionType::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_option_type_translations (name, presentation, locale, spree_option_type_id, created_at, updated_at) + SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_option_types; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_types + SET name=null, presentation=null; + ") + end + + # Option Values + if not Spree::OptionValue::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_option_value_translations (name, presentation, locale, spree_option_value_id, created_at, updated_at) + SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_option_values; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_values + SET name=null, presentation=null; + ") + end + end + + def down + # Option Types + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_types as option_types + SET (name, presentation) = (t_option_types.name, t_option_types.presentation) + FROM spree_option_type_translations AS t_option_types + WHERE t_option_types.spree_option_type_id = option_types.id; + ") + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE spree_option_type_translations; + ") + + # Option Values + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_values as option_values + SET (name, presentation) = (t_option_values.name, t_option_values.presentation) + FROM spree_option_value_translations AS t_option_values + WHERE t_option_values.spree_option_value_id = option_values.id; + ") + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE spree_option_value_translations; + ") + end +end diff --git a/core/db/migrate/20230109105943_create_property_translations.rb b/core/db/migrate/20230109105943_create_property_translations.rb new file mode 100644 index 00000000000..5bff726be47 --- /dev/null +++ b/core/db/migrate/20230109105943_create_property_translations.rb @@ -0,0 +1,23 @@ +class CreatePropertyTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists? 'spree_property_translations' + remove_index :spree_property_translations, column: :spree_property_id, if_exists: true + else + create_table :spree_property_translations do |t| + # Translated attribute(s) + t.string :name + t.string :presentation + t.string :filter_param + + t.string :locale, null: false + t.references :spree_property, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_property_translations, :locale, name: :index_spree_property_translations_on_locale + end + + add_index :spree_property_translations, [:spree_property_id, :locale], name: :unique_property_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb new file mode 100644 index 00000000000..5fb7814dc00 --- /dev/null +++ b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb @@ -0,0 +1,59 @@ +class TransferPropertyDataToTranslatableTables < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + + def up + # Properties + change_column_null :spree_properties, :presentation, true + + if not Spree::Property::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_property_translations (name, presentation, filter_param, locale, spree_property_id, created_at, updated_at) + SELECT name, presentation, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_properties; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_properties + SET name=null, presentation=null, filter_param=null; + ") + end + + # Product Properties + if not Spree::ProductProperty::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_product_property_translations (value, filter_param, locale, spree_product_property_id, created_at, updated_at) + SELECT value, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_product_properties; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_product_properties + SET value=null, filter_param=null; + ") + end + end + + def down + # Properties + change_column_null :spree_properties, :presentation, false + + ActiveRecord::Base.connection.execute(" + UPDATE spree_properties AS properties + SET (name, presentation, filter_param) = (t_properties.name, t_properties.presentation, t_properties.filter_param) + FROM spree_property_translations AS t_properties + WHERE t_properties.spree_property_id = properties.id; + ") + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE spree_property_translations; + ") + + # Product Properties + ActiveRecord::Base.connection.execute(" + UPDATE spree_product_properties AS product_properties + SET (value, filter_param) = (t_product_properties.value, t_product_properties.filter_param) + FROM spree_product_property_translations AS t_product_properties + WHERE t_product_properties.spree_product_property_id = product_properties.id; + ") + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE spree_product_property_translations; + ") + end +end diff --git a/core/lib/spree/core/product_filters.rb b/core/lib/spree/core/product_filters.rb index de24f226d8d..2c586c4c854 100644 --- a/core/lib/spree/core/product_filters.rb +++ b/core/lib/spree/core/product_filters.rb @@ -98,18 +98,21 @@ def self.price_filter conds.each do |new_scope| scope = scope.or(new_scope) end - Spree::Product.with_property('brand').where(scope) + Product.with_property('brand').join_translation_table(ProductProperty).where(scope) end def self.brand_filter brand_property = Spree::Property.find_by(name: 'brand') brands = brand_property ? Spree::ProductProperty.where(property_id: brand_property.id).pluck(:value).uniq.map(&:to_s) : [] - pp = Spree::ProductProperty.arel_table - conds = Hash[*brands.map { |b| [b, pp[:value].eq(b)] }.flatten] + + conditions = brands.map do |brand| + [brand, { ProductProperty.translation_table_alias => { value: brand } }] + end.to_h + { name: I18n.t('spree.taxonomy_brands_name'), scope: :brand_any, - conds: conds, + conds: conditions, labels: brands.sort.map { |k| [k, k] } } end diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb index 455354d39de..dc7078fd17e 100644 --- a/core/spec/models/spree/product_spec.rb +++ b/core/spec/models/spree/product_spec.rb @@ -433,7 +433,7 @@ class Extension < Spree::Base # Regression test for #2455 it "does not overwrite properties' presentation names" do - Spree::Property.where(name: 'foo').first_or_create!(presentation: "Foo's Presentation Name") + Spree::Property.create!(name: 'foo', presentation: "Foo's Presentation Name") product.set_property('foo', 'value1') product.set_property('bar', 'value2') expect(Spree::Property.where(name: 'foo').first.presentation).to eq("Foo's Presentation Name") diff --git a/core/spec/models/spree/property_spec.rb b/core/spec/models/spree/property_spec.rb index 377f21832f1..ed837dc3fb4 100644 --- a/core/spec/models/spree/property_spec.rb +++ b/core/spec/models/spree/property_spec.rb @@ -84,8 +84,10 @@ let(:product_property_2) { create(:product_property, property: property, product: product_2, value: 'Test Test') } before do - product_property.update_column(:filter_param, nil) - product_property.update_column(:value, 'some value') + product_property.translations.each do |t| + t.update_column(:filter_param, nil) + t.update_column(:value, 'some_value') + end end context 'filterable property' do diff --git a/sample/db/samples/option_types.rb b/sample/db/samples/option_types.rb index 919d55e8e00..33f14de05e8 100644 --- a/sample/db/samples/option_types.rb +++ b/sample/db/samples/option_types.rb @@ -17,5 +17,7 @@ ] option_types_attributes.each do |attrs| - Spree::OptionType.where(attrs).first_or_create! + unless Spree::OptionType.where(attrs).exists? + Spree::OptionType.create!(attrs) + end end diff --git a/sample/db/samples/properties.rb b/sample/db/samples/properties.rb index cd4fd5156b6..1c503d4bf54 100644 --- a/sample/db/samples/properties.rb +++ b/sample/db/samples/properties.rb @@ -14,7 +14,9 @@ } properties.each do |name, presentation| - Spree::Property.where(name: name, presentation: presentation).first_or_create! + unless Spree::Property.where(name: name, presentation: presentation).exists? + Spree::Property.create!(name: name, presentation: presentation) + end end Spree::Property.where(name: %w[brand manufacturer]).update(filterable: true) From 9ce001034cf6201a96e6b298f623938bb2a4e10d Mon Sep 17 00:00:00 2001 From: wjwitek Date: Tue, 31 Jan 2023 10:36:12 +0100 Subject: [PATCH 72/89] add keyserver and apt-get update --- .circleci/config.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9d5509b4012..67f79b52e41 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,9 +40,12 @@ run_tests_2_7: &run_tests_2_7 keys: - spree-bundle-v10-ruby-2-7-{{ .Branch }} - spree-bundle-v10-ruby-2-7 + - run: + name: Add keyserver + command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips - run: name: Ensure Bundle Install command: | @@ -69,9 +72,12 @@ run_tests_3_0: &run_tests_3_0 keys: - spree-bundle-v10-ruby-3-0-{{ .Branch }} - spree-bundle-v10-ruby-3-0 + - run: + name: Add keyserver + command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips - run: name: Ensure Bundle Install command: | @@ -156,9 +162,12 @@ jobs: keys: - spree-bundle-v10-ruby-2-7-{{ .Branch }} - spree-bundle-v10-ruby-2-7 + - run: + name: Add keyserver + command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips - run: name: Bundle Install command: | @@ -177,9 +186,12 @@ jobs: keys: - spree-bundle-v10-ruby-3-0-{{ .Branch }} - spree-bundle-v10-ruby-3-0 + - run: + name: Add keyserver + command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips - run: name: Bundle Install command: | @@ -325,9 +337,12 @@ jobs: keys: - spree-bundle-v10-ruby-3-0-{{ .Branch }} - spree-bundle-v10-ruby-3-0 + - run: + name: Add keyserver + command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips - run: name: Ensure Bundle Install command: | From db6424b8d78090447b4631294b9d1f01003aa4fa Mon Sep 17 00:00:00 2001 From: wjwitek Date: Mon, 2 Jan 2023 10:45:18 +0100 Subject: [PATCH 73/89] First working version of feed generation. --- .../google_export_options_controller.rb | 15 +++ api/config/routes.rb | 3 + core/app/models/spree/google_export_option.rb | 19 ++++ core/app/services/spree/export.rb | 103 ++++++++++++++++++ ...2350_create_spree_google_export_options.rb | 16 +++ core/lib/spree/core/dependencies.rb | 5 +- 6 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 api/app/controllers/spree/api/v2/platform/google_export_options_controller.rb create mode 100644 core/app/models/spree/google_export_option.rb create mode 100644 core/app/services/spree/export.rb create mode 100644 core/db/migrate/20221229132350_create_spree_google_export_options.rb diff --git a/api/app/controllers/spree/api/v2/platform/google_export_options_controller.rb b/api/app/controllers/spree/api/v2/platform/google_export_options_controller.rb new file mode 100644 index 00000000000..8cd84b71817 --- /dev/null +++ b/api/app/controllers/spree/api/v2/platform/google_export_options_controller.rb @@ -0,0 +1,15 @@ +module Spree + module Api + module V2 + module Platform + class GoogleExportOptionsController < ResourceController + def show + @options = GoogleExportOption.find_by store: params[:store_id] + + send_data @options.export, filename: 'products.rss', type: 'text/xml' + end + end + end + end + end +end diff --git a/api/config/routes.rb b/api/config/routes.rb index e702fb4279a..7cd76d1050d 100644 --- a/api/config/routes.rb +++ b/api/config/routes.rb @@ -206,6 +206,9 @@ resources :events, only: :index resources :subscribers end + + # google export API + get '/rss/:store_id', to: 'google_export_options#show' end end end diff --git a/core/app/models/spree/google_export_option.rb b/core/app/models/spree/google_export_option.rb new file mode 100644 index 00000000000..b3fd52a4259 --- /dev/null +++ b/core/app/models/spree/google_export_option.rb @@ -0,0 +1,19 @@ +module Spree + class GoogleExportOption < Base + def export + Spree::Dependencies.export.constantize.new.export_google_rss(self) + end + + def true_keys + keys = [] + + attributes.each do |key, value| + if value.instance_of?(TrueClass) + keys.append(key.to_sym) + end + end + + keys + end + end +end diff --git a/core/app/services/spree/export.rb b/core/app/services/spree/export.rb new file mode 100644 index 00000000000..f26172b1359 --- /dev/null +++ b/core/app/services/spree/export.rb @@ -0,0 +1,103 @@ +require 'nokogiri' + +module Spree + class Export + prepend Spree::ServiceModule::Base + + def export_google_rss(options) + store = Spree::Store.find(options.store) + @options = options + + builder = Nokogiri::XML::Builder.new do |xml| + xml.rss('xmlns:g' => 'http://base.google.com/ns/1.0', 'version' => '2.0') do + xml.channel do + store_information(xml, store) + Spree::Product.find_each do |product| + next unless validate_product(product) + + product.variants.each do |variant| + next unless validate_variant(variant, product) + + xml.item do + required_product_information(xml, variant, product) + optional_information(xml, product) + end + end + end + end + end + end + + builder.to_xml + end + + def required_product_information(xml, variant, product) + xml['g'].id variant.id + xml['g'].title variant.sku + xml['g'].description product.description + xml['g'].link product.slug + xml['g'].image_link get_image_link(variant, product) + xml['g'].price format_price(variant) + xml['g'].availability get_availability(product) + xml['g'].availability_date product.available_on.xmlschema + end + + def optional_information(xml, product) + @options.true_keys.each do |key| + if @options.send(key) && !product.property(key.to_s).nil? + xml['g'].send(key, product.property(key.to_s)) + end + end + end + + def validate_product(product) + unless product.deleted_at.nil? + false + end + true + end + + def validate_variant(variant, product) + unless variant.deleted_at.nil? + false + end + # TODO: make variant sku and description fallback to product's if they are nil + if variant.id.nil? || variant.sku.nil? || variant.description.nil? || variant.slug.nil? || get_image_link(variant, product).nil? + false + end + true + end + + def get_image_link(variant, product) + # try getting image from variant + img = variant.images.first&.plp_url + + # if no image specified for variant try getting product image + if img.nil? + img = product.images.first&.plp_url + end + + img + end + + def get_availability(product) + if product.available_on.past? + 'in stock' + elsif !product.available_on.nil? + 'backorder' + else + 'out of stock' + end + end + + def format_price(variant) + "#{variant.cost_price} #{variant.cost_currency}" + end + + def store_information(xml, store) + xml.title store.name + xml.link store.url + xml.description store.meta_description + end + end +end diff --git a/core/db/migrate/20221229132350_create_spree_google_export_options.rb b/core/db/migrate/20221229132350_create_spree_google_export_options.rb new file mode 100644 index 00000000000..10ebbececbc --- /dev/null +++ b/core/db/migrate/20221229132350_create_spree_google_export_options.rb @@ -0,0 +1,16 @@ +class CreateSpreeGoogleExportOptions < ActiveRecord::Migration[6.0] + def change + create_table :spree_google_export_options do |t| + t.integer :store + + keys = [:material, :brand, :gender, :condition, :gtin, :mpn, :adult, :multipack, :is_bundle, :color, :pattern, + :size, :item_group_id] + + keys.each do |key| + t.boolean key, default: false + end + + t.timestamps + end + end +end diff --git a/core/lib/spree/core/dependencies.rb b/core/lib/spree/core/dependencies.rb index d5ac71e56ae..07d38111e87 100644 --- a/core/lib/spree/core/dependencies.rb +++ b/core/lib/spree/core/dependencies.rb @@ -21,7 +21,7 @@ class Dependencies :order_approve_service, :order_cancel_service, :shipment_change_state_service, :shipment_update_service, :shipment_create_service, :shipment_add_item_service, :shipment_remove_item_service, :payment_create_service, :address_create_service, :address_update_service, - :checkout_select_shipping_method_service + :checkout_select_shipping_method_service, :export ].freeze attr_accessor *INJECTION_POINTS @@ -110,6 +110,9 @@ def set_default_services # errors @error_handler = 'Spree::ErrorReporter' + + # export + @export = 'Spree::Export' end def set_default_finders From 519ace474dd2eb9e52af539f2a4d48dbe09a1139 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Wed, 4 Jan 2023 12:36:06 +0100 Subject: [PATCH 74/89] Refactor service, model and controller. --- .../google_export_options_controller.rb | 15 -- .../api/v2/storefront/stores_controller.rb | 14 ++ api/config/routes.rb | 6 +- ...xport_option.rb => google_feed_setting.rb} | 10 +- core/app/services/spree/export.rb | 103 -------------- core/app/services/spree/export/google_rss.rb | 133 ++++++++++++++++++ ...2350_create_spree_google_feed_settings.rb} | 6 +- core/lib/spree/core/dependencies.rb | 4 +- 8 files changed, 160 insertions(+), 131 deletions(-) delete mode 100644 api/app/controllers/spree/api/v2/platform/google_export_options_controller.rb rename core/app/models/spree/{google_export_option.rb => google_feed_setting.rb} (53%) delete mode 100644 core/app/services/spree/export.rb create mode 100644 core/app/services/spree/export/google_rss.rb rename core/db/migrate/{20221229132350_create_spree_google_export_options.rb => 20221229132350_create_spree_google_feed_settings.rb} (64%) diff --git a/api/app/controllers/spree/api/v2/platform/google_export_options_controller.rb b/api/app/controllers/spree/api/v2/platform/google_export_options_controller.rb deleted file mode 100644 index 8cd84b71817..00000000000 --- a/api/app/controllers/spree/api/v2/platform/google_export_options_controller.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Spree - module Api - module V2 - module Platform - class GoogleExportOptionsController < ResourceController - def show - @options = GoogleExportOption.find_by store: params[:store_id] - - send_data @options.export, filename: 'products.rss', type: 'text/xml' - end - end - end - end - end -end diff --git a/api/app/controllers/spree/api/v2/storefront/stores_controller.rb b/api/app/controllers/spree/api/v2/storefront/stores_controller.rb index 6699fee62ee..fb480b110e9 100644 --- a/api/app/controllers/spree/api/v2/storefront/stores_controller.rb +++ b/api/app/controllers/spree/api/v2/storefront/stores_controller.rb @@ -3,12 +3,26 @@ module Api module V2 module Storefront class StoresController < ::Spree::Api::V2::ResourceController + before_action :update_options + def current render_serialized_payload { serialize_resource(current_store) } end + def export_google_rss_feed + send_data export_google_rss, filename: 'products.rss', type: 'text/xml' + end + private + def update_options + @settings = Spree::GoogleFeedSetting.find_by(spree_store_id: current_store) + end + + def export_google_rss + Spree::Dependencies.export_google_rss_service.constantize.new.call(@settings) + end + def model_class Spree::Store end diff --git a/api/config/routes.rb b/api/config/routes.rb index 7cd76d1050d..07959c553d2 100644 --- a/api/config/routes.rb +++ b/api/config/routes.rb @@ -63,6 +63,9 @@ end get '/digitals/:token', to: 'digitals#download', as: 'digital' + + # google export API + get '/rss/google', to: 'stores#export_google_rss_feed' end namespace :platform do @@ -206,9 +209,6 @@ resources :events, only: :index resources :subscribers end - - # google export API - get '/rss/:store_id', to: 'google_export_options#show' end end end diff --git a/core/app/models/spree/google_export_option.rb b/core/app/models/spree/google_feed_setting.rb similarity index 53% rename from core/app/models/spree/google_export_option.rb rename to core/app/models/spree/google_feed_setting.rb index b3fd52a4259..ce312be93cc 100644 --- a/core/app/models/spree/google_export_option.rb +++ b/core/app/models/spree/google_feed_setting.rb @@ -1,10 +1,10 @@ module Spree - class GoogleExportOption < Base - def export - Spree::Dependencies.export.constantize.new.export_google_rss(self) - end + class GoogleFeedSetting < Base + belongs_to :store, class_name: 'Spree::Store', foreign_key: 'spree_store_id' + + validates :store, presence: true - def true_keys + def enabled_keys keys = [] attributes.each do |key, value| diff --git a/core/app/services/spree/export.rb b/core/app/services/spree/export.rb deleted file mode 100644 index f26172b1359..00000000000 --- a/core/app/services/spree/export.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'nokogiri' - -module Spree - class Export - prepend Spree::ServiceModule::Base - - def export_google_rss(options) - store = Spree::Store.find(options.store) - @options = options - - builder = Nokogiri::XML::Builder.new do |xml| - xml.rss('xmlns:g' => 'http://base.google.com/ns/1.0', 'version' => '2.0') do - xml.channel do - store_information(xml, store) - Spree::Product.find_each do |product| - next unless validate_product(product) - - product.variants.each do |variant| - next unless validate_variant(variant, product) - - xml.item do - required_product_information(xml, variant, product) - optional_information(xml, product) - end - end - end - end - end - end - - builder.to_xml - end - - def required_product_information(xml, variant, product) - xml['g'].id variant.id - xml['g'].title variant.sku - xml['g'].description product.description - xml['g'].link product.slug - xml['g'].image_link get_image_link(variant, product) - xml['g'].price format_price(variant) - xml['g'].availability get_availability(product) - xml['g'].availability_date product.available_on.xmlschema - end - - def optional_information(xml, product) - @options.true_keys.each do |key| - if @options.send(key) && !product.property(key.to_s).nil? - xml['g'].send(key, product.property(key.to_s)) - end - end - end - - def validate_product(product) - unless product.deleted_at.nil? - false - end - true - end - - def validate_variant(variant, product) - unless variant.deleted_at.nil? - false - end - # TODO: make variant sku and description fallback to product's if they are nil - if variant.id.nil? || variant.sku.nil? || variant.description.nil? || variant.slug.nil? || get_image_link(variant, product).nil? - false - end - true - end - - def get_image_link(variant, product) - # try getting image from variant - img = variant.images.first&.plp_url - - # if no image specified for variant try getting product image - if img.nil? - img = product.images.first&.plp_url - end - - img - end - - def get_availability(product) - if product.available_on.past? - 'in stock' - elsif !product.available_on.nil? - 'backorder' - else - 'out of stock' - end - end - - def format_price(variant) - "#{variant.cost_price} #{variant.cost_currency}" - end - - def store_information(xml, store) - xml.title store.name - xml.link store.url - xml.description store.meta_description - end - end -end diff --git a/core/app/services/spree/export/google_rss.rb b/core/app/services/spree/export/google_rss.rb new file mode 100644 index 00000000000..74364a37cef --- /dev/null +++ b/core/app/services/spree/export/google_rss.rb @@ -0,0 +1,133 @@ +require 'nokogiri' + +module Spree + module Export + class GoogleRss + def call(options) + @store = Spree::Store.find(options.spree_store_id) + @options = options + + builder = Nokogiri::XML::Builder.new do |xml| + xml.rss('xmlns:g' => 'http://base.google.com/ns/1.0', 'version' => '2.0') do + xml.channel do + add_store_information_to_xml(xml) + Spree::Product.find_each do |product| + product.variants.active.each do |variant| + add_variant_information_to_xml(xml, product, variant) + end + end + end + end + end + + builder.to_xml + end + + private + + def add_store_information_to_xml(xml) + xml.title @store.name + xml.link @store.url + xml.description @store.meta_description + end + + def add_variant_information_to_xml(xml, product, variant) + return if get_image_link(variant, product).nil? + + xml.item do + xml['g'].id variant.id + xml['g'].title format_title(product, variant) + xml['g'].description get_description(product, variant) + xml['g'].link "#{@store.url}/#{product.slug}" + xml['g'].image_link get_image_link(variant, product) + xml['g'].price format_price(variant) + xml['g'].availability get_availability(product) + xml['g'].availability_date product.available_on.xmlschema + + add_optional_information(xml, product) + end + end + + def format_title(product, variant) + # Title of a variant is created by joining title of a product and variant's option_values, as they are + # what differentiaties it from other variants. + title = product.name + variant.option_values.find_each do |option_value| + title << " - #{option_value.name}" + end + title + end + + def get_description(product, variant) + return product.description unless product.description.nil? + + format_title(product, variant) + end + + def get_image_link(variant, product) + # try getting image from variant + img = variant.images.first&.plp_url + + # if no image specified for variant try getting product image + if img.nil? + img = product.images.first&.plp_url + end + + img + end + + def format_price(variant) + "#{variant.cost_price} #{variant.cost_currency}" + end + + def get_availability(product) + return 'in stock' if product.available_on.past? + return 'backorder' unless product.available_on.nil? + + 'out of stock' + end + + def add_optional_information(xml, product) + @options.enabled_keys.each do |key| + if @options.send(key) && !product.property(key.to_s).nil? + xml['g'].send(key, product.property(key.to_s)) + end + end + end + + # example of modifing optional information + # + # By default, this code assumes that any information that is not required by Google + # (see https://support.google.com/merchants/answer/160589?hl=en) is stored in Spree::Products's properties. + # If it's in other column you can modify add_optional_information like this: + # def add_optional_information(xml, product) + # @options.enabled_keys.each do |key| + # if @options.send(key) && !product.property(key.to_s).nil? + # xml['g'].send(key, product.property(key.to_s)) + # end + # end + # if !product.column_name.nil? + # xml['g'].attribute product.column_name + # end + # end + # + # If the column is part of variant or for example is option value, you will need to add variant to function's + # arguments and modify code analogically as above. + # + # def add_optional_information(xml, product, variant) + # @options.enabled_keys.each do |key| + # if @options.send(key) && !product.property(key.to_s).nil? + # xml['g'].send(key, product.property(key.to_s)) + # end + # end + # if !variant.column_name.nil? + # xml['g'].attribute variant.column_name + # end + # size_value = variant.option_value("size") + # if !size_value.nil? + # xml['g'].size size_value + # end + # end + end + end +end diff --git a/core/db/migrate/20221229132350_create_spree_google_export_options.rb b/core/db/migrate/20221229132350_create_spree_google_feed_settings.rb similarity index 64% rename from core/db/migrate/20221229132350_create_spree_google_export_options.rb rename to core/db/migrate/20221229132350_create_spree_google_feed_settings.rb index 10ebbececbc..90709a247de 100644 --- a/core/db/migrate/20221229132350_create_spree_google_export_options.rb +++ b/core/db/migrate/20221229132350_create_spree_google_feed_settings.rb @@ -1,7 +1,7 @@ -class CreateSpreeGoogleExportOptions < ActiveRecord::Migration[6.0] +class CreateSpreeGoogleFeedSettings < ActiveRecord::Migration[6.0] def change - create_table :spree_google_export_options do |t| - t.integer :store + create_table :spree_google_feed_settings do |t| + t.references :spree_store keys = [:material, :brand, :gender, :condition, :gtin, :mpn, :adult, :multipack, :is_bundle, :color, :pattern, :size, :item_group_id] diff --git a/core/lib/spree/core/dependencies.rb b/core/lib/spree/core/dependencies.rb index 07d38111e87..13a000d21ba 100644 --- a/core/lib/spree/core/dependencies.rb +++ b/core/lib/spree/core/dependencies.rb @@ -21,7 +21,7 @@ class Dependencies :order_approve_service, :order_cancel_service, :shipment_change_state_service, :shipment_update_service, :shipment_create_service, :shipment_add_item_service, :shipment_remove_item_service, :payment_create_service, :address_create_service, :address_update_service, - :checkout_select_shipping_method_service, :export + :checkout_select_shipping_method_service, :export_google_rss_service ].freeze attr_accessor *INJECTION_POINTS @@ -112,7 +112,7 @@ def set_default_services @error_handler = 'Spree::ErrorReporter' # export - @export = 'Spree::Export' + @export_google_rss_service = 'Spree::Export::GoogleRss' end def set_default_finders From c3a1133bd20b2474ede731e68ea299701117bc1c Mon Sep 17 00:00:00 2001 From: wjwitek Date: Mon, 9 Jan 2023 16:28:43 +0100 Subject: [PATCH 75/89] Tests for DataFeed model and feed generation service. --- .../v2/storefront/store_controller_spec.rb | 2 + core/app/services/spree/export/google_rss.rb | 6 +- .../factories/google_feed_setting_factory.rb | 6 + .../factories/product_factory.rb | 6 + .../factories/store_factory.rb | 1 + .../factories/variant_factory.rb | 4 + .../models/spree/google_feed_setting_spec.rb | 16 +++ .../services/spree/export/google_rss_spec.rb | 112 ++++++++++++++++++ 8 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 api/spec/controllers/spree/api/v2/storefront/store_controller_spec.rb create mode 100644 core/lib/spree/testing_support/factories/google_feed_setting_factory.rb create mode 100644 core/spec/models/spree/google_feed_setting_spec.rb create mode 100644 core/spec/services/spree/export/google_rss_spec.rb diff --git a/api/spec/controllers/spree/api/v2/storefront/store_controller_spec.rb b/api/spec/controllers/spree/api/v2/storefront/store_controller_spec.rb new file mode 100644 index 00000000000..07e8df11f82 --- /dev/null +++ b/api/spec/controllers/spree/api/v2/storefront/store_controller_spec.rb @@ -0,0 +1,2 @@ +# frozen_string_literal: true + diff --git a/core/app/services/spree/export/google_rss.rb b/core/app/services/spree/export/google_rss.rb index 74364a37cef..b0886eb7c0a 100644 --- a/core/app/services/spree/export/google_rss.rb +++ b/core/app/services/spree/export/google_rss.rb @@ -42,7 +42,7 @@ def add_variant_information_to_xml(xml, product, variant) xml['g'].image_link get_image_link(variant, product) xml['g'].price format_price(variant) xml['g'].availability get_availability(product) - xml['g'].availability_date product.available_on.xmlschema + xml['g'].availability_date product.available_on&.xmlschema unless product.available_on.nil? add_optional_information(xml, product) end @@ -77,11 +77,11 @@ def get_image_link(variant, product) end def format_price(variant) - "#{variant.cost_price} #{variant.cost_currency}" + "#{variant.price} #{variant.cost_currency}" end def get_availability(product) - return 'in stock' if product.available_on.past? + return 'in stock' if product.available_on&.past? return 'backorder' unless product.available_on.nil? 'out of stock' diff --git a/core/lib/spree/testing_support/factories/google_feed_setting_factory.rb b/core/lib/spree/testing_support/factories/google_feed_setting_factory.rb new file mode 100644 index 00000000000..e727e567d43 --- /dev/null +++ b/core/lib/spree/testing_support/factories/google_feed_setting_factory.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :google_feed_setting, class: Spree::GoogleFeedSetting do + id { 1 } + brand { true } + end +end diff --git a/core/lib/spree/testing_support/factories/product_factory.rb b/core/lib/spree/testing_support/factories/product_factory.rb index a88dc4d8ff5..a65f6b5dd78 100644 --- a/core/lib/spree/testing_support/factories/product_factory.rb +++ b/core/lib/spree/testing_support/factories/product_factory.rb @@ -52,6 +52,12 @@ factory :product_with_option_types do after(:create) { |product| create(:product_option_type, product: product) } end + factory :product_with_properties do + after(:create) do |product| + create(:property, :brand, id: 10) + create(:product_property, product: product, property_id: 10, value: 'Epsilon') + end + end end end end diff --git a/core/lib/spree/testing_support/factories/store_factory.rb b/core/lib/spree/testing_support/factories/store_factory.rb index 7358afe0262..e19dfb097ee 100644 --- a/core/lib/spree/testing_support/factories/store_factory.rb +++ b/core/lib/spree/testing_support/factories/store_factory.rb @@ -12,6 +12,7 @@ facebook { 'spreecommerce' } twitter { 'spreecommerce' } instagram { 'spreecommerce' } + meta_description { 'Sample store description.' } trait :with_favicon do transient do diff --git a/core/lib/spree/testing_support/factories/variant_factory.rb b/core/lib/spree/testing_support/factories/variant_factory.rb index 3e8d11d63f6..c9aca8a764d 100644 --- a/core/lib/spree/testing_support/factories/variant_factory.rb +++ b/core/lib/spree/testing_support/factories/variant_factory.rb @@ -25,6 +25,10 @@ factory :variant do # on_hand 5 product { |p| p.association(:product, stores: [create(:store)]) } + + factory :with_image_variant do + images { create_list(:image, 1) } + end end factory :master_variant do diff --git a/core/spec/models/spree/google_feed_setting_spec.rb b/core/spec/models/spree/google_feed_setting_spec.rb new file mode 100644 index 00000000000..577aad9f556 --- /dev/null +++ b/core/spec/models/spree/google_feed_setting_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe Spree::GoogleFeedSetting, type: :model do + let(:store) { create(:store) } + let(:google_feed_setting) { create(:google_feed_setting, store: store) } + + describe '#enabled_keys' do + it 'returns enabled key' do + expect(google_feed_setting.enabled_keys).to include(:brand) + end + + it 'does not return not enabled key' do + expect(google_feed_setting.enabled_keys).not_to include(:size) + end + end +end diff --git a/core/spec/services/spree/export/google_rss_spec.rb b/core/spec/services/spree/export/google_rss_spec.rb new file mode 100644 index 00000000000..0e12f04cde5 --- /dev/null +++ b/core/spec/services/spree/export/google_rss_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' +require 'pry' + +module Spree + describe Export::GoogleRss do + subject { described_class.new } + + let(:store) { create(:store) } + let(:setting) { create(:google_feed_setting, store: store) } + let(:product) { create(:product, stores: [store]) } + let!(:variant) { create(:with_image_variant, product: product) } + let(:result) { subject.call(setting) } + + context 'store header is generated correctly' do + before do + allow(subject).to receive(:store).and_return(store) + end + + it 'store name' do + expect(result).to include("#{store.name}").once + end + + it 'store url' do + expect(result).to include("#{store.url}").once + end + + it 'store description' do + expect(result).to include("#{store.meta_description}").once + end + end + + context 'required item attributes are generated correctly' do + before do + allow(subject).to receive(:store).and_return(store) + end + + it 'id' do + expect(result).to include("#{variant.id}").once + end + + it 'title' do + expect(result).to include("#{product.name} - #{variant.option_values.first.name}").once + end + + it 'description' do + expect(result).to include("#{product.description}").once + end + + it 'link' do + expect(result).to include("#{store.url}/#{product.slug}").once + end + + it 'image link' do + expect(result).to include("#{variant.images.first.plp_url}").once + end + + it 'price' do + expect(result).to include("#{variant.price} #{variant.cost_currency}").once + end + + context 'availability date is in the past' do + it 'product is in stock' do + expect(result).to include('in stock') + end + + it 'product availability date is the same' do + expect(result).to include("#{product.available_on.xmlschema}") + end + end + + context 'availability date is in the future' do + let(:product) { create(:product, stores: [store], available_on: 1.year.from_now) } + + it 'product is on backorder' do + expect(result).to include('backorder') + end + end + + context 'availability date is nil' do + let(:product) { create(:product, stores: [store], available_on: nil) } + + it 'product is out of stock' do + expect(result).to include('out of stock') + end + + it 'product availability date is nil' do + expect(result).not_to include('') + end + end + end + + context 'optional item attributes are generated correctly' do + let(:product) { create(:product_with_properties) } + + before do + allow(subject).to receive(:store).and_return(store) + end + + it 'adds brand to item attributes' do + expect(result).to include("#{product.property('brand')}") + end + + context 'brand option is set to false' do + let(:setting) { create(:google_feed_setting, store: store, brand: false) } + + it 'does not add brand to item attributes' do + expect(result).not_to include('') + end + end + end + end +end From a26e56915b29deb9417076efeb7a5b8b5c9d2b85 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Fri, 13 Jan 2023 08:00:14 +0100 Subject: [PATCH 76/89] Refactor services to be clearer. --- .../api/v2/storefront/stores_controller.rb | 9 ++- .../v2/storefront/store_controller_spec.rb | 2 - core/app/models/spree/google_feed_setting.rb | 2 +- core/app/services/spree/export/google_rss.rb | 33 ++++++---- .../services/spree/export/google_rss_spec.rb | 60 +++++++++---------- 5 files changed, 56 insertions(+), 50 deletions(-) delete mode 100644 api/spec/controllers/spree/api/v2/storefront/store_controller_spec.rb diff --git a/api/app/controllers/spree/api/v2/storefront/stores_controller.rb b/api/app/controllers/spree/api/v2/storefront/stores_controller.rb index fb480b110e9..009e68678f5 100644 --- a/api/app/controllers/spree/api/v2/storefront/stores_controller.rb +++ b/api/app/controllers/spree/api/v2/storefront/stores_controller.rb @@ -3,23 +3,22 @@ module Api module V2 module Storefront class StoresController < ::Spree::Api::V2::ResourceController - before_action :update_options - def current render_serialized_payload { serialize_resource(current_store) } end def export_google_rss_feed - send_data export_google_rss, filename: 'products.rss', type: 'text/xml' + send_data export_google_rss_service.value[:file], filename: 'products.rss', type: 'text/xml' end private - def update_options + def update_settings @settings = Spree::GoogleFeedSetting.find_by(spree_store_id: current_store) end - def export_google_rss + def export_google_rss_service + update_settings Spree::Dependencies.export_google_rss_service.constantize.new.call(@settings) end diff --git a/api/spec/controllers/spree/api/v2/storefront/store_controller_spec.rb b/api/spec/controllers/spree/api/v2/storefront/store_controller_spec.rb deleted file mode 100644 index 07e8df11f82..00000000000 --- a/api/spec/controllers/spree/api/v2/storefront/store_controller_spec.rb +++ /dev/null @@ -1,2 +0,0 @@ -# frozen_string_literal: true - diff --git a/core/app/models/spree/google_feed_setting.rb b/core/app/models/spree/google_feed_setting.rb index ce312be93cc..ede387c2c5d 100644 --- a/core/app/models/spree/google_feed_setting.rb +++ b/core/app/models/spree/google_feed_setting.rb @@ -8,7 +8,7 @@ def enabled_keys keys = [] attributes.each do |key, value| - if value.instance_of?(TrueClass) + if value == true keys.append(key.to_sym) end end diff --git a/core/app/services/spree/export/google_rss.rb b/core/app/services/spree/export/google_rss.rb index b0886eb7c0a..6529f48110d 100644 --- a/core/app/services/spree/export/google_rss.rb +++ b/core/app/services/spree/export/google_rss.rb @@ -3,15 +3,18 @@ module Spree module Export class GoogleRss - def call(options) - @store = Spree::Store.find(options.spree_store_id) - @options = options + prepend Spree::ServiceModule::Base + + def call(settings) + @settings = settings + + return failure(store, error: "Store with id: #{settings.spree_store_id} does not exist.") if store.nil? builder = Nokogiri::XML::Builder.new do |xml| xml.rss('xmlns:g' => 'http://base.google.com/ns/1.0', 'version' => '2.0') do xml.channel do add_store_information_to_xml(xml) - Spree::Product.find_each do |product| + store.products.active.each do |product| product.variants.active.each do |variant| add_variant_information_to_xml(xml, product, variant) end @@ -20,15 +23,21 @@ def call(options) end end - builder.to_xml + success(file: builder.to_xml) end private + def store + return @store if defined? @store + + @store ||= Spree::Store.find_by(id: @settings.spree_store_id) + end + def add_store_information_to_xml(xml) - xml.title @store.name - xml.link @store.url - xml.description @store.meta_description + xml.title store.name + xml.link store.url + xml.description store.meta_description end def add_variant_information_to_xml(xml, product, variant) @@ -38,7 +47,7 @@ def add_variant_information_to_xml(xml, product, variant) xml['g'].id variant.id xml['g'].title format_title(product, variant) xml['g'].description get_description(product, variant) - xml['g'].link "#{@store.url}/#{product.slug}" + xml['g'].link "#{store.url}/#{product.slug}" xml['g'].image_link get_image_link(variant, product) xml['g'].price format_price(variant) xml['g'].availability get_availability(product) @@ -88,14 +97,14 @@ def get_availability(product) end def add_optional_information(xml, product) - @options.enabled_keys.each do |key| - if @options.send(key) && !product.property(key.to_s).nil? + @settings.enabled_keys.each do |key| + if @settings.send(key) && !product.property(key.to_s).nil? xml['g'].send(key, product.property(key.to_s)) end end end - # example of modifing optional information + # example of modifying optional information # # By default, this code assumes that any information that is not required by Google # (see https://support.google.com/merchants/answer/160589?hl=en) is stored in Spree::Products's properties. diff --git a/core/spec/services/spree/export/google_rss_spec.rb b/core/spec/services/spree/export/google_rss_spec.rb index 0e12f04cde5..7e3b9ba4e60 100644 --- a/core/spec/services/spree/export/google_rss_spec.rb +++ b/core/spec/services/spree/export/google_rss_spec.rb @@ -16,16 +16,16 @@ module Spree allow(subject).to receive(:store).and_return(store) end - it 'store name' do - expect(result).to include("#{store.name}").once + it 'include store name' do + expect(result.value[:file]).to include("#{store.name}").once end - it 'store url' do - expect(result).to include("#{store.url}").once + it 'includes store url' do + expect(result.value[:file]).to include("#{store.url}").once end - it 'store description' do - expect(result).to include("#{store.meta_description}").once + it 'includes store description' do + expect(result.value[:file]).to include("#{store.meta_description}").once end end @@ -34,57 +34,57 @@ module Spree allow(subject).to receive(:store).and_return(store) end - it 'id' do - expect(result).to include("#{variant.id}").once + it 'includes id' do + expect(result.value[:file]).to include("#{variant.id}").once end - it 'title' do - expect(result).to include("#{product.name} - #{variant.option_values.first.name}").once + it 'includes title' do + expect(result.value[:file]).to include("#{product.name} - #{variant.option_values.first.name}").once end - it 'description' do - expect(result).to include("#{product.description}").once + it 'includes description' do + expect(result.value[:file]).to include("#{product.description}").once end - it 'link' do - expect(result).to include("#{store.url}/#{product.slug}").once + it 'includes link' do + expect(result.value[:file]).to include("#{store.url}/#{product.slug}").once end - it 'image link' do - expect(result).to include("#{variant.images.first.plp_url}").once + it 'includes image link' do + expect(result.value[:file]).to include("#{variant.images.first.plp_url}").once end - it 'price' do - expect(result).to include("#{variant.price} #{variant.cost_currency}").once + it 'includes price' do + expect(result.value[:file]).to include("#{variant.price} #{variant.cost_currency}").once end context 'availability date is in the past' do - it 'product is in stock' do - expect(result).to include('in stock') + it 'shows that product is in stock' do + expect(result.value[:file]).to include('in stock') end - it 'product availability date is the same' do - expect(result).to include("#{product.available_on.xmlschema}") + it 'shows that product availability date is the same' do + expect(result.value[:file]).to include("#{product.available_on.xmlschema}") end end context 'availability date is in the future' do let(:product) { create(:product, stores: [store], available_on: 1.year.from_now) } - it 'product is on backorder' do - expect(result).to include('backorder') + it 'shows that product is on backorder' do + expect(result.value[:file]).to include('backorder') end end context 'availability date is nil' do let(:product) { create(:product, stores: [store], available_on: nil) } - it 'product is out of stock' do - expect(result).to include('out of stock') + it 'shows that product is out of stock' do + expect(result.value[:file]).to include('out of stock') end - it 'product availability date is nil' do - expect(result).not_to include('') + it 'shows that product availability date is nil' do + expect(result.value[:file]).not_to include('') end end end @@ -97,14 +97,14 @@ module Spree end it 'adds brand to item attributes' do - expect(result).to include("#{product.property('brand')}") + expect(result.value[:file]).to include("#{product.property('brand')}") end context 'brand option is set to false' do let(:setting) { create(:google_feed_setting, store: store, brand: false) } it 'does not add brand to item attributes' do - expect(result).not_to include('') + expect(result.value[:file]).not_to include('') end end end From 74043ce8f4a275b6c89ecf27eeaef72ff06844d6 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Mon, 16 Jan 2023 15:08:35 +0100 Subject: [PATCH 77/89] Add sample data for DataFeed --- core/spec/services/spree/export/google_rss_spec.rb | 3 +-- sample/db/samples/google_feed_settings.rb | 7 +++++++ sample/lib/spree_sample.rb | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 sample/db/samples/google_feed_settings.rb diff --git a/core/spec/services/spree/export/google_rss_spec.rb b/core/spec/services/spree/export/google_rss_spec.rb index 7e3b9ba4e60..f6502dd7a4c 100644 --- a/core/spec/services/spree/export/google_rss_spec.rb +++ b/core/spec/services/spree/export/google_rss_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' -require 'pry' module Spree describe Export::GoogleRss do @@ -90,7 +89,7 @@ module Spree end context 'optional item attributes are generated correctly' do - let(:product) { create(:product_with_properties) } + let(:product) { create(:product_with_properties, stores: [store]) } before do allow(subject).to receive(:store).and_return(store) diff --git a/sample/db/samples/google_feed_settings.rb b/sample/db/samples/google_feed_settings.rb new file mode 100644 index 00000000000..700788d3463 --- /dev/null +++ b/sample/db/samples/google_feed_settings.rb @@ -0,0 +1,7 @@ +# settings for default store +Spree::GoogleFeedSetting.create!( + store: Spree::Store.default, + brand: true, + material: true +) + diff --git a/sample/lib/spree_sample.rb b/sample/lib/spree_sample.rb index ff014991b53..f0993697e06 100644 --- a/sample/lib/spree_sample.rb +++ b/sample/lib/spree_sample.rb @@ -32,6 +32,7 @@ def self.load_samples Spree::Sample.load_sample('reimbursements') Spree::Sample.load_sample('return_authorization_reasons') Spree::Sample.load_sample('stores') + Spree::Sample.load_sample('google_feed_settings') Spree::Sample.load_sample('cms_standard_pages') Spree::Sample.load_sample('cms_feature_pages') From f59adcdd64fe060dd011398086f94bc3ab03d150 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Tue, 17 Jan 2023 10:56:56 +0100 Subject: [PATCH 78/89] add basic api for google feed settings --- .../platform/google_feed_settings_controller.rb | 16 ++++++++++++++++ .../platform/google_feed_setting_serializer.rb | 11 +++++++++++ api/config/routes.rb | 3 +++ 3 files changed, 30 insertions(+) create mode 100644 api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb create mode 100644 api/app/serializers/spree/api/v2/platform/google_feed_setting_serializer.rb diff --git a/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb b/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb new file mode 100644 index 00000000000..d697aa7d328 --- /dev/null +++ b/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb @@ -0,0 +1,16 @@ +module Spree + module Api + module V2 + module Platform + class GoogleFeedSettingsController < ResourceController + private + + def model_class + Spree::GoogleFeedSetting + end + end + end + end + end +end + diff --git a/api/app/serializers/spree/api/v2/platform/google_feed_setting_serializer.rb b/api/app/serializers/spree/api/v2/platform/google_feed_setting_serializer.rb new file mode 100644 index 00000000000..8e4e418bcc6 --- /dev/null +++ b/api/app/serializers/spree/api/v2/platform/google_feed_setting_serializer.rb @@ -0,0 +1,11 @@ +module Spree + module Api + module V2 + module Platform + class GoogleFeedSettingSerializer < BaseSerializer + include ResourceSerializerConcern + end + end + end + end +end diff --git a/api/config/routes.rb b/api/config/routes.rb index 07959c553d2..1c8559ec2ff 100644 --- a/api/config/routes.rb +++ b/api/config/routes.rb @@ -199,6 +199,9 @@ # Store API resources :stores + # Google Feed Setting API + resources :google_feed_settings + # Configurations API resources :payment_methods resources :shipping_categories From 1027a07f7eb3b08a867c42f7b33320989b69fbf4 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Wed, 18 Jan 2023 09:34:59 +0100 Subject: [PATCH 79/89] Split DataFeed generation into multiple services. --- .../google_feed_settings_controller.rb | 1 - .../api/v2/storefront/stores_controller.rb | 13 +- api/config/routes.rb | 4 +- core/app/models/spree/data_feed_setting.rb | 16 ++ core/app/models/spree/google_feed_setting.rb | 19 --- .../data_feeds/google/optional_attributes.rb | 23 +++ .../google/optional_sub_attributes.rb | 21 +++ .../spree/data_feeds/google/products_list.rb | 15 ++ .../data_feeds/google/required_attributes.rb | 67 +++++++++ .../services/spree/data_feeds/google/rss.rb | 107 +++++++++++++ core/app/services/spree/export/google_rss.rb | 142 ------------------ ...9132350_create_spree_data_feed_settings.rb | 12 ++ ...32350_create_spree_google_feed_settings.rb | 16 -- core/lib/spree/core/dependencies.rb | 11 +- .../factories/data_feed_setting_factory.rb | 7 + .../factories/google_feed_setting_factory.rb | 6 - .../models/spree/data_feed_setting_spec.rb | 8 + .../models/spree/google_feed_setting_spec.rb | 16 -- .../google/rss_spec.rb} | 16 +- sample/db/samples/data_feed_settings.rb | 5 + sample/db/samples/google_feed_settings.rb | 7 - sample/lib/spree_sample.rb | 2 +- 22 files changed, 302 insertions(+), 232 deletions(-) create mode 100644 core/app/models/spree/data_feed_setting.rb delete mode 100644 core/app/models/spree/google_feed_setting.rb create mode 100644 core/app/services/spree/data_feeds/google/optional_attributes.rb create mode 100644 core/app/services/spree/data_feeds/google/optional_sub_attributes.rb create mode 100644 core/app/services/spree/data_feeds/google/products_list.rb create mode 100644 core/app/services/spree/data_feeds/google/required_attributes.rb create mode 100644 core/app/services/spree/data_feeds/google/rss.rb delete mode 100644 core/app/services/spree/export/google_rss.rb create mode 100644 core/db/migrate/20221229132350_create_spree_data_feed_settings.rb delete mode 100644 core/db/migrate/20221229132350_create_spree_google_feed_settings.rb create mode 100644 core/lib/spree/testing_support/factories/data_feed_setting_factory.rb delete mode 100644 core/lib/spree/testing_support/factories/google_feed_setting_factory.rb create mode 100644 core/spec/models/spree/data_feed_setting_spec.rb delete mode 100644 core/spec/models/spree/google_feed_setting_spec.rb rename core/spec/services/spree/{export/google_rss_spec.rb => data_feeds/google/rss_spec.rb} (87%) create mode 100644 sample/db/samples/data_feed_settings.rb delete mode 100644 sample/db/samples/google_feed_settings.rb diff --git a/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb b/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb index d697aa7d328..0460ae59d83 100644 --- a/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb @@ -13,4 +13,3 @@ def model_class end end end - diff --git a/api/app/controllers/spree/api/v2/storefront/stores_controller.rb b/api/app/controllers/spree/api/v2/storefront/stores_controller.rb index 009e68678f5..a6a0eac3978 100644 --- a/api/app/controllers/spree/api/v2/storefront/stores_controller.rb +++ b/api/app/controllers/spree/api/v2/storefront/stores_controller.rb @@ -7,19 +7,18 @@ def current render_serialized_payload { serialize_resource(current_store) } end - def export_google_rss_feed - send_data export_google_rss_service.value[:file], filename: 'products.rss', type: 'text/xml' + def data_feeds_google_rss_feed + send_data data_feeds_google_rss_service.value[:file], filename: 'products.rss', type: 'text/xml' end private - def update_settings - @settings = Spree::GoogleFeedSetting.find_by(spree_store_id: current_store) + def settings + @settings ||= Spree::DataFeedSetting.find_by!(spree_store_id: current_store, uuid: params[:unique_url], enabled: true) end - def export_google_rss_service - update_settings - Spree::Dependencies.export_google_rss_service.constantize.new.call(@settings) + def data_feeds_google_rss_service + Spree::Dependencies.data_feeds_google_rss_service.constantize.new.call(settings) end def model_class diff --git a/api/config/routes.rb b/api/config/routes.rb index 1c8559ec2ff..df9a9a08002 100644 --- a/api/config/routes.rb +++ b/api/config/routes.rb @@ -64,8 +64,8 @@ get '/digitals/:token', to: 'digitals#download', as: 'digital' - # google export API - get '/rss/google', to: 'stores#export_google_rss_feed' + # export API + get '/data_feeds/google/:unique_url', to: 'stores#data_feeds_google_rss_feed' end namespace :platform do diff --git a/core/app/models/spree/data_feed_setting.rb b/core/app/models/spree/data_feed_setting.rb new file mode 100644 index 00000000000..89d5330d40d --- /dev/null +++ b/core/app/models/spree/data_feed_setting.rb @@ -0,0 +1,16 @@ +module Spree + class DataFeedSetting < Base + belongs_to :store, class_name: 'Spree::Store', foreign_key: 'spree_store_id' + + before_create :generate_uuid + + with_options presence: true do + validates :store + validates :uuid, uniqueness: true, on: :update + end + + def generate_uuid + write_attribute(:uuid, SecureRandom.uuid) + end + end +end diff --git a/core/app/models/spree/google_feed_setting.rb b/core/app/models/spree/google_feed_setting.rb deleted file mode 100644 index ede387c2c5d..00000000000 --- a/core/app/models/spree/google_feed_setting.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Spree - class GoogleFeedSetting < Base - belongs_to :store, class_name: 'Spree::Store', foreign_key: 'spree_store_id' - - validates :store, presence: true - - def enabled_keys - keys = [] - - attributes.each do |key, value| - if value == true - keys.append(key.to_sym) - end - end - - keys - end - end -end diff --git a/core/app/services/spree/data_feeds/google/optional_attributes.rb b/core/app/services/spree/data_feeds/google/optional_attributes.rb new file mode 100644 index 00000000000..09592353527 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/optional_attributes.rb @@ -0,0 +1,23 @@ +module Spree + module DataFeeds + module Google + class OptionalAttributes + prepend Spree::ServiceModule::Base + + def call(input) + information = {} + + input[:product].property_ids.each do |key| + name = Spree::Property.find(key)&.name + value = input[:product].property(name) + unless value.nil? + information[name] = value + end + end + + success(information: information) + end + end + end + end +end diff --git a/core/app/services/spree/data_feeds/google/optional_sub_attributes.rb b/core/app/services/spree/data_feeds/google/optional_sub_attributes.rb new file mode 100644 index 00000000000..0f767a538c9 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/optional_sub_attributes.rb @@ -0,0 +1,21 @@ +module Spree + module DataFeeds + module Google + class OptionalSubAttributes + prepend Spree::ServiceModule::Base + + def call(input) + information = {} + + # This is a place where you can put attributes that have sub-attributes, example for shipping: + # + # information['shipping'] = {} + # information['shipping']['price'] = calculate_shipping(input[:product]) + # information['shipping']['country'] = input[:store].default_country + + success(information: information) + end + end + end + end +end diff --git a/core/app/services/spree/data_feeds/google/products_list.rb b/core/app/services/spree/data_feeds/google/products_list.rb new file mode 100644 index 00000000000..56dd7d1c742 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/products_list.rb @@ -0,0 +1,15 @@ +module Spree + module DataFeeds + module Google + class ProductsList + prepend Spree::ServiceModule::Base + + def call(store) + products = store.products.active + return success(products: products) + end + end + end + end +end + diff --git a/core/app/services/spree/data_feeds/google/required_attributes.rb b/core/app/services/spree/data_feeds/google/required_attributes.rb new file mode 100644 index 00000000000..d5cdfb085e6 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/required_attributes.rb @@ -0,0 +1,67 @@ +module Spree + module DataFeeds + module Google + class RequiredAttributes + prepend Spree::ServiceModule::Base + + def call(input) + information = {} + + return failure(nil, error: 'No image link') if get_image_link(input[:variant], input[:product]).nil? + + information['id'] = input[:variant].id + information['title'] = format_title(input[:product], input[:variant]) + information['description'] = get_description(input[:product], input[:variant]) + information['link'] = "#{input[:store].url}/#{input[:product].slug}" + information['image_link'] = get_image_link(input[:variant], input[:product]) + information['price'] = format_price(input[:variant]) + information['availability'] = get_availability(input[:product]) + information['availability_date'] = input[:product].available_on&.xmlschema unless input[:product].available_on.nil? + + success(information: information) + end + + private + + def format_title(product, variant) + # Title of a variant is created by joining title of a product and variant's option_values, as they are + # what differentiates it from other variants. + title = product.name + variant.option_values.find_each do |option_value| + title << " - #{option_value.name}" + end + title + end + + def get_description(product, variant) + return product.description unless product.description.nil? + + format_title(product, variant) + end + + def get_image_link(variant, product) + # try getting image from variant + img = variant.images.first&.plp_url + + # if no image specified for variant try getting product image + if img.nil? + img = product.images.first&.plp_url + end + + img + end + + def format_price(variant) + "#{variant.price} #{variant.cost_currency}" + end + + def get_availability(product) + return 'in stock' if product.available? && product.available_on&.past? + return 'backorder' if product.backorderable? && product.backordered? && product.available_on&.future? + + 'out of stock' + end + end + end + end +end diff --git a/core/app/services/spree/data_feeds/google/rss.rb b/core/app/services/spree/data_feeds/google/rss.rb new file mode 100644 index 00000000000..059d6415459 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/rss.rb @@ -0,0 +1,107 @@ +require 'nokogiri' + +module Spree + module DataFeeds + module Google + class Rss + prepend Spree::ServiceModule::Base + + def call(settings) + @settings = settings + + return failure(store, error: "Store with id: #{settings.spree_store_id} does not exist.") if store.nil? + + builder = Nokogiri::XML::Builder.new do |xml| + xml.rss('xmlns:g' => 'http://base.google.com/ns/1.0', 'version' => '2.0') do + xml.channel do + add_store_information_to_xml(xml) + result = products_list.call(store) + if result.success? + result.value[:products].find_each do |product| + product.variants.active.find_each do |variant| + add_variant_information_to_xml(xml, product, variant) + end + end + end + end + end + end + + success(file: builder.to_xml) + end + + private + + def store + return @store if defined? @store + + @store ||= Spree::Store.find_by(id: @settings.spree_store_id) + end + + def add_store_information_to_xml(xml) + xml.title store.name + xml.link store.url + xml.description store.meta_description + end + + def add_variant_information_to_xml(xml, product, variant) + input = { product: product, variant: variant, settings: @settings, store: store } + result = required_attributes.call(input) + + if result.success + xml.item do + result.value[:information]&.each do |key, value| + xml['g'].send(key, value) + end + + add_optional_information(xml, product, variant) + add_optional_sub_attributes(xml, product, variant) + end + end + end + + def add_optional_information(xml, product, variant) + input = { product: product, variant: variant, settings: @settings, store: store } + result = optional_attributes.call(input) + if result.success? + information = result.value[:information] + information.each do |key, value| + xml['g'].send(key, value) + end + end + end + + def add_optional_sub_attributes(xml, product, variant) + input = { product: product, variant: variant, settings: @settings, store: store } + result = optional_sub_attributes.call(input) + if result.success? + information = result.value[:information] + information.each do |key, value| + xml['g'].send(key) do + value.each do |subkey, subvalue| + xml['g'].send(subkey, subvalue) + end + end + end + end + end + + def optional_attributes + Spree::Dependencies.data_feeds_google_optional_attributes_service.constantize.new + end + + def required_attributes + Spree::Dependencies.data_feeds_google_required_attributes_service.constantize.new + end + + def optional_sub_attributes + Spree::Dependencies.data_feeds_google_optional_sub_attributes_service.constantize.new + end + + def products_list + Spree::Dependencies.data_feeds_google_products_list.constantize.new + end + end + end + end +end diff --git a/core/app/services/spree/export/google_rss.rb b/core/app/services/spree/export/google_rss.rb deleted file mode 100644 index 6529f48110d..00000000000 --- a/core/app/services/spree/export/google_rss.rb +++ /dev/null @@ -1,142 +0,0 @@ -require 'nokogiri' - -module Spree - module Export - class GoogleRss - prepend Spree::ServiceModule::Base - - def call(settings) - @settings = settings - - return failure(store, error: "Store with id: #{settings.spree_store_id} does not exist.") if store.nil? - - builder = Nokogiri::XML::Builder.new do |xml| - xml.rss('xmlns:g' => 'http://base.google.com/ns/1.0', 'version' => '2.0') do - xml.channel do - add_store_information_to_xml(xml) - store.products.active.each do |product| - product.variants.active.each do |variant| - add_variant_information_to_xml(xml, product, variant) - end - end - end - end - end - - success(file: builder.to_xml) - end - - private - - def store - return @store if defined? @store - - @store ||= Spree::Store.find_by(id: @settings.spree_store_id) - end - - def add_store_information_to_xml(xml) - xml.title store.name - xml.link store.url - xml.description store.meta_description - end - - def add_variant_information_to_xml(xml, product, variant) - return if get_image_link(variant, product).nil? - - xml.item do - xml['g'].id variant.id - xml['g'].title format_title(product, variant) - xml['g'].description get_description(product, variant) - xml['g'].link "#{store.url}/#{product.slug}" - xml['g'].image_link get_image_link(variant, product) - xml['g'].price format_price(variant) - xml['g'].availability get_availability(product) - xml['g'].availability_date product.available_on&.xmlschema unless product.available_on.nil? - - add_optional_information(xml, product) - end - end - - def format_title(product, variant) - # Title of a variant is created by joining title of a product and variant's option_values, as they are - # what differentiaties it from other variants. - title = product.name - variant.option_values.find_each do |option_value| - title << " - #{option_value.name}" - end - title - end - - def get_description(product, variant) - return product.description unless product.description.nil? - - format_title(product, variant) - end - - def get_image_link(variant, product) - # try getting image from variant - img = variant.images.first&.plp_url - - # if no image specified for variant try getting product image - if img.nil? - img = product.images.first&.plp_url - end - - img - end - - def format_price(variant) - "#{variant.price} #{variant.cost_currency}" - end - - def get_availability(product) - return 'in stock' if product.available_on&.past? - return 'backorder' unless product.available_on.nil? - - 'out of stock' - end - - def add_optional_information(xml, product) - @settings.enabled_keys.each do |key| - if @settings.send(key) && !product.property(key.to_s).nil? - xml['g'].send(key, product.property(key.to_s)) - end - end - end - - # example of modifying optional information - # - # By default, this code assumes that any information that is not required by Google - # (see https://support.google.com/merchants/answer/160589?hl=en) is stored in Spree::Products's properties. - # If it's in other column you can modify add_optional_information like this: - # def add_optional_information(xml, product) - # @options.enabled_keys.each do |key| - # if @options.send(key) && !product.property(key.to_s).nil? - # xml['g'].send(key, product.property(key.to_s)) - # end - # end - # if !product.column_name.nil? - # xml['g'].attribute product.column_name - # end - # end - # - # If the column is part of variant or for example is option value, you will need to add variant to function's - # arguments and modify code analogically as above. - # - # def add_optional_information(xml, product, variant) - # @options.enabled_keys.each do |key| - # if @options.send(key) && !product.property(key.to_s).nil? - # xml['g'].send(key, product.property(key.to_s)) - # end - # end - # if !variant.column_name.nil? - # xml['g'].attribute variant.column_name - # end - # size_value = variant.option_value("size") - # if !size_value.nil? - # xml['g'].size size_value - # end - # end - end - end -end diff --git a/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb b/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb new file mode 100644 index 00000000000..d1a48251773 --- /dev/null +++ b/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb @@ -0,0 +1,12 @@ +class CreateSpreeDataFeedSettings < ActiveRecord::Migration[6.0] + def change + create_table :spree_data_feed_settings do |t| + t.references :spree_store + + t.string :uuid, unique: true + t.boolean :enabled, default: true + + t.timestamps + end + end +end diff --git a/core/db/migrate/20221229132350_create_spree_google_feed_settings.rb b/core/db/migrate/20221229132350_create_spree_google_feed_settings.rb deleted file mode 100644 index 90709a247de..00000000000 --- a/core/db/migrate/20221229132350_create_spree_google_feed_settings.rb +++ /dev/null @@ -1,16 +0,0 @@ -class CreateSpreeGoogleFeedSettings < ActiveRecord::Migration[6.0] - def change - create_table :spree_google_feed_settings do |t| - t.references :spree_store - - keys = [:material, :brand, :gender, :condition, :gtin, :mpn, :adult, :multipack, :is_bundle, :color, :pattern, - :size, :item_group_id] - - keys.each do |key| - t.boolean key, default: false - end - - t.timestamps - end - end -end diff --git a/core/lib/spree/core/dependencies.rb b/core/lib/spree/core/dependencies.rb index 13a000d21ba..e3b707c5a49 100644 --- a/core/lib/spree/core/dependencies.rb +++ b/core/lib/spree/core/dependencies.rb @@ -21,7 +21,8 @@ class Dependencies :order_approve_service, :order_cancel_service, :shipment_change_state_service, :shipment_update_service, :shipment_create_service, :shipment_add_item_service, :shipment_remove_item_service, :payment_create_service, :address_create_service, :address_update_service, - :checkout_select_shipping_method_service, :export_google_rss_service + :checkout_select_shipping_method_service, :data_feeds_google_rss_service, :data_feeds_google_optional_attributes_service, + :data_feeds_google_required_attributes_service, :data_feeds_google_optional_sub_attributes_service, :data_feeds_google_products_list ].freeze attr_accessor *INJECTION_POINTS @@ -111,8 +112,12 @@ def set_default_services # errors @error_handler = 'Spree::ErrorReporter' - # export - @export_google_rss_service = 'Spree::Export::GoogleRss' + # data feeds for Google + @data_feeds_google_rss_service = 'Spree::DataFeeds::Google::Rss' + @data_feeds_google_optional_attributes_service = 'Spree::DataFeeds::Google::OptionalAttributes' + @data_feeds_google_required_attributes_service = 'Spree::DataFeeds::Google::RequiredAttributes' + @data_feeds_google_optional_sub_attributes_service = 'Spree::DataFeeds::Google::OptionalSubAttributes' + @data_feeds_google_products_list = 'Spree::DataFeeds::Google::ProductsList' end def set_default_finders diff --git a/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb b/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb new file mode 100644 index 00000000000..a0675d74d3f --- /dev/null +++ b/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :data_feed_setting, class: Spree::DataFeedSetting do + id { 1 } + enabled { true } + store { create(:store) } + end +end diff --git a/core/lib/spree/testing_support/factories/google_feed_setting_factory.rb b/core/lib/spree/testing_support/factories/google_feed_setting_factory.rb deleted file mode 100644 index e727e567d43..00000000000 --- a/core/lib/spree/testing_support/factories/google_feed_setting_factory.rb +++ /dev/null @@ -1,6 +0,0 @@ -FactoryBot.define do - factory :google_feed_setting, class: Spree::GoogleFeedSetting do - id { 1 } - brand { true } - end -end diff --git a/core/spec/models/spree/data_feed_setting_spec.rb b/core/spec/models/spree/data_feed_setting_spec.rb new file mode 100644 index 00000000000..b998555a258 --- /dev/null +++ b/core/spec/models/spree/data_feed_setting_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +describe Spree::DataFeedSetting, type: :model do + let(:store) { create(:store) } + let(:data_feed_setting) { create(:data_feed_setting, store: store) } + + # TODO +end diff --git a/core/spec/models/spree/google_feed_setting_spec.rb b/core/spec/models/spree/google_feed_setting_spec.rb deleted file mode 100644 index 577aad9f556..00000000000 --- a/core/spec/models/spree/google_feed_setting_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe Spree::GoogleFeedSetting, type: :model do - let(:store) { create(:store) } - let(:google_feed_setting) { create(:google_feed_setting, store: store) } - - describe '#enabled_keys' do - it 'returns enabled key' do - expect(google_feed_setting.enabled_keys).to include(:brand) - end - - it 'does not return not enabled key' do - expect(google_feed_setting.enabled_keys).not_to include(:size) - end - end -end diff --git a/core/spec/services/spree/export/google_rss_spec.rb b/core/spec/services/spree/data_feeds/google/rss_spec.rb similarity index 87% rename from core/spec/services/spree/export/google_rss_spec.rb rename to core/spec/services/spree/data_feeds/google/rss_spec.rb index f6502dd7a4c..45a172e89d9 100644 --- a/core/spec/services/spree/export/google_rss_spec.rb +++ b/core/spec/services/spree/data_feeds/google/rss_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' module Spree - describe Export::GoogleRss do + describe DataFeeds::Google::Rss do subject { described_class.new } let(:store) { create(:store) } - let(:setting) { create(:google_feed_setting, store: store) } + let(:setting) { create(:data_feed_setting, store: store) } let(:product) { create(:product, stores: [store]) } let!(:variant) { create(:with_image_variant, product: product) } let(:result) { subject.call(setting) } @@ -57,7 +57,7 @@ module Spree expect(result.value[:file]).to include("#{variant.price} #{variant.cost_currency}").once end - context 'availability date is in the past' do + context 'product is set to available' do it 'shows that product is in stock' do expect(result.value[:file]).to include('in stock') end @@ -67,7 +67,7 @@ module Spree end end - context 'availability date is in the future' do + context 'product is set to backorderable' do let(:product) { create(:product, stores: [store], available_on: 1.year.from_now) } it 'shows that product is on backorder' do @@ -98,14 +98,6 @@ module Spree it 'adds brand to item attributes' do expect(result.value[:file]).to include("#{product.property('brand')}") end - - context 'brand option is set to false' do - let(:setting) { create(:google_feed_setting, store: store, brand: false) } - - it 'does not add brand to item attributes' do - expect(result.value[:file]).not_to include('') - end - end end end end diff --git a/sample/db/samples/data_feed_settings.rb b/sample/db/samples/data_feed_settings.rb new file mode 100644 index 00000000000..6e223ca0c07 --- /dev/null +++ b/sample/db/samples/data_feed_settings.rb @@ -0,0 +1,5 @@ +# settings for default store +Spree::DataFeedSetting.create!( + store: Spree::Store.default +) + diff --git a/sample/db/samples/google_feed_settings.rb b/sample/db/samples/google_feed_settings.rb deleted file mode 100644 index 700788d3463..00000000000 --- a/sample/db/samples/google_feed_settings.rb +++ /dev/null @@ -1,7 +0,0 @@ -# settings for default store -Spree::GoogleFeedSetting.create!( - store: Spree::Store.default, - brand: true, - material: true -) - diff --git a/sample/lib/spree_sample.rb b/sample/lib/spree_sample.rb index f0993697e06..c9f87743c4a 100644 --- a/sample/lib/spree_sample.rb +++ b/sample/lib/spree_sample.rb @@ -32,7 +32,7 @@ def self.load_samples Spree::Sample.load_sample('reimbursements') Spree::Sample.load_sample('return_authorization_reasons') Spree::Sample.load_sample('stores') - Spree::Sample.load_sample('google_feed_settings') + Spree::Sample.load_sample('data_feed_settings') Spree::Sample.load_sample('cms_standard_pages') Spree::Sample.load_sample('cms_feature_pages') From 14d41610429777b595284c7727ed51acad944f33 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Thu, 26 Jan 2023 09:48:46 +0100 Subject: [PATCH 80/89] move data feeds api to seperate controller --- .../api/v2/data_feeds/google_controller.rb | 24 +++++++++++++++++++ .../api/v2/storefront/stores_controller.rb | 12 ---------- api/config/routes.rb | 8 ++++--- 3 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 api/app/controllers/spree/api/v2/data_feeds/google_controller.rb diff --git a/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb b/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb new file mode 100644 index 00000000000..0ddb072c021 --- /dev/null +++ b/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb @@ -0,0 +1,24 @@ +module Spree + module Api + module V2 + module DataFeeds + class GoogleController < ::Spree::Api::V2::BaseController + def rss_feed + send_data data_feeds_google_rss_service.value[:file], filename: 'products.rss', type: 'text/xml' + end + + private + + def settings + @settings ||= Spree::DataFeedSetting.find_by!(spree_store_id: current_store, uuid: params[:unique_url], enabled: true) + end + + def data_feeds_google_rss_service + Spree::Dependencies.data_feeds_google_rss_service.constantize.new.call(settings) + end + end + end + end + end +end + diff --git a/api/app/controllers/spree/api/v2/storefront/stores_controller.rb b/api/app/controllers/spree/api/v2/storefront/stores_controller.rb index a6a0eac3978..6699fee62ee 100644 --- a/api/app/controllers/spree/api/v2/storefront/stores_controller.rb +++ b/api/app/controllers/spree/api/v2/storefront/stores_controller.rb @@ -7,20 +7,8 @@ def current render_serialized_payload { serialize_resource(current_store) } end - def data_feeds_google_rss_feed - send_data data_feeds_google_rss_service.value[:file], filename: 'products.rss', type: 'text/xml' - end - private - def settings - @settings ||= Spree::DataFeedSetting.find_by!(spree_store_id: current_store, uuid: params[:unique_url], enabled: true) - end - - def data_feeds_google_rss_service - Spree::Dependencies.data_feeds_google_rss_service.constantize.new.call(settings) - end - def model_class Spree::Store end diff --git a/api/config/routes.rb b/api/config/routes.rb index df9a9a08002..dc8e070ec21 100644 --- a/api/config/routes.rb +++ b/api/config/routes.rb @@ -63,9 +63,6 @@ end get '/digitals/:token', to: 'digitals#download', as: 'digital' - - # export API - get '/data_feeds/google/:unique_url', to: 'stores#data_feeds_google_rss_feed' end namespace :platform do @@ -213,6 +210,11 @@ resources :subscribers end end + + namespace :data_feeds do + # google data feed API + get '/google/:unique_url', to: 'google#rss_feed' + end end end end From d82fcff96804ae33d6c543c0532f6a3a0ef54713 Mon Sep 17 00:00:00 2001 From: wjwitek Date: Thu, 26 Jan 2023 16:04:28 +0100 Subject: [PATCH 81/89] Add functionalities for admin dashboard: nested attributes for store, name and provider columns. --- .../spree/api/v2/data_feeds/google_controller.rb | 2 +- core/app/models/spree/data_feed_setting.rb | 8 ++++++++ core/app/models/spree/store.rb | 3 +++ .../app/services/spree/data_feeds/google/products_list.rb | 3 +-- .../20221229132350_create_spree_data_feed_settings.rb | 2 ++ core/lib/spree/permitted_attributes.rb | 3 ++- .../factories/data_feed_setting_factory.rb | 2 ++ sample/db/samples/data_feed_settings.rb | 4 +++- 8 files changed, 22 insertions(+), 5 deletions(-) diff --git a/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb b/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb index 0ddb072c021..c14713c0468 100644 --- a/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb +++ b/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb @@ -10,7 +10,7 @@ def rss_feed private def settings - @settings ||= Spree::DataFeedSetting.find_by!(spree_store_id: current_store, uuid: params[:unique_url], enabled: true) + @settings ||= Spree::DataFeedSetting.find_by!(spree_store_id: current_store, uuid: params[:unique_url], provider: 'google', enabled: true) end def data_feeds_google_rss_service diff --git a/core/app/models/spree/data_feed_setting.rb b/core/app/models/spree/data_feed_setting.rb index 89d5330d40d..fabec42b041 100644 --- a/core/app/models/spree/data_feed_setting.rb +++ b/core/app/models/spree/data_feed_setting.rb @@ -6,9 +6,17 @@ class DataFeedSetting < Base with_options presence: true do validates :store + validates :name, uniqueness: true + validates :provider validates :uuid, uniqueness: true, on: :update end + def formatted_url + "#{store.formatted_url}/api/v2/data_feeds/#{provider}/#{uuid}.rss" + end + + private + def generate_uuid write_attribute(:uuid, SecureRandom.uuid) end diff --git a/core/app/models/spree/store.rb b/core/app/models/spree/store.rb index 76d13541c3a..0e834e0879d 100644 --- a/core/app/models/spree/store.rb +++ b/core/app/models/spree/store.rb @@ -55,6 +55,9 @@ class Store < Spree::Base has_many :wishlists, class_name: 'Spree::Wishlist' + has_many :data_feed_settings, class_name: 'Spree::DataFeedSetting', foreign_key: 'spree_store_id' + accepts_nested_attributes_for :data_feed_settings, update_only: true + belongs_to :default_country, class_name: 'Spree::Country' belongs_to :checkout_zone, class_name: 'Spree::Zone' diff --git a/core/app/services/spree/data_feeds/google/products_list.rb b/core/app/services/spree/data_feeds/google/products_list.rb index 56dd7d1c742..0279c240ce3 100644 --- a/core/app/services/spree/data_feeds/google/products_list.rb +++ b/core/app/services/spree/data_feeds/google/products_list.rb @@ -6,10 +6,9 @@ class ProductsList def call(store) products = store.products.active - return success(products: products) + success(products: products) end end end end end - diff --git a/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb b/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb index d1a48251773..f5d791da0a9 100644 --- a/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb +++ b/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb @@ -3,6 +3,8 @@ def change create_table :spree_data_feed_settings do |t| t.references :spree_store + t.string :name + t.string :provider t.string :uuid, unique: true t.boolean :enabled, default: true diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb index 330fe762e08..0a3e5a90a0c 100644 --- a/core/lib/spree/permitted_attributes.rb +++ b/core/lib/spree/permitted_attributes.rb @@ -134,7 +134,8 @@ module PermittedAttributes :new_order_notifications_email, :checkout_zone_id, :seo_robots, :digital_asset_authorized_clicks, :digital_asset_authorized_days, :limit_digital_download_count, :limit_digital_download_days, :digital_asset_link_expire_time, - { mailer_logo_attributes: {}, favicon_image_attributes: {}, logo_attributes: {} }] + { mailer_logo_attributes: {}, favicon_image_attributes: {}, logo_attributes: {} }, + { data_feed_settings_attributes: [:enabled, :id] }] @@store_credit_attributes = %i[amount currency category_id memo] diff --git a/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb b/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb index a0675d74d3f..ab965c207c6 100644 --- a/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb +++ b/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb @@ -3,5 +3,7 @@ id { 1 } enabled { true } store { create(:store) } + provider { 'google' } + name { 'test' } end end diff --git a/sample/db/samples/data_feed_settings.rb b/sample/db/samples/data_feed_settings.rb index 6e223ca0c07..1e0c1ab1b6f 100644 --- a/sample/db/samples/data_feed_settings.rb +++ b/sample/db/samples/data_feed_settings.rb @@ -1,5 +1,7 @@ # settings for default store Spree::DataFeedSetting.create!( - store: Spree::Store.default + store: Spree::Store.default, + name: 'Default Google Data Feed', + provider: 'google' ) From 930e3b411d61a92515e0240eb1ae7b6e22e58517 Mon Sep 17 00:00:00 2001 From: Rafal Cymerys Date: Thu, 2 Feb 2023 20:31:23 +0100 Subject: [PATCH 82/89] Remove unnecessary build configurations --- .circleci/config.yml | 160 +++---------------------------------------- 1 file changed, 8 insertions(+), 152 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 67f79b52e41..bfacdd2c074 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,18 +13,6 @@ defaults: &defaults - image: &ruby_2_7_image circleci/ruby:2.7-node-browsers - image: &redis_image circleci/redis:6.2-alpine -defaults_3_0: &defaults_3_0 - <<: *defaults - docker: - - image: &ruby_3_0_image circleci/ruby:3.0-node-browsers - - image: &redis_image circleci/redis:6.2-alpine - -defaults_3_1: &defaults_3_1 - <<: *defaults - docker: - - image: &ruby_3_1_image cimg/ruby:3.1.3-browsers - - image: *redis_image - defaults_3_2: &defaults_3_2 <<: *defaults docker: @@ -63,67 +51,6 @@ run_tests_2_7: &run_tests_2_7 - store_test_results: path: /tmp/test-results -run_tests_3_0: &run_tests_3_0 - <<: *defaults_3_0 - parallelism: 3 - steps: - - checkout - - restore_cache: - keys: - - spree-bundle-v10-ruby-3-0-{{ .Branch }} - - spree-bundle-v10-ruby-3-0 - - run: - name: Add keyserver - command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - - run: - name: Install libvips - command: sudo apt-get update && sudo apt-get install libvips - - run: - name: Ensure Bundle Install - command: | - bundle install - ./bin/build-ci.rb install - - run: - name: Run rspec in parallel - command: ./bin/build-ci.rb test - - store_artifacts: - path: /tmp/test-artifacts - destination: test-artifacts - - store_artifacts: - path: /tmp/test-results - destination: raw-test-output - - store_test_results: - path: /tmp/test-results - -run_tests_3_1: &run_tests_3_1 - <<: *defaults_3_1 - parallelism: 3 - steps: - - checkout - - restore_cache: - keys: - - spree-bundle-v10-ruby-3-1-{{ .Branch }} - - spree-bundle-v10-ruby-3-1 - - run: - name: Install libvips - command: sudo apt-get update && sudo apt-get install libvips42 - - run: - name: Ensure Bundle Install - command: | - bundle install - ./bin/build-ci.rb install - - run: - name: Run rspec in parallel - command: ./bin/build-ci.rb test - - store_artifacts: - path: /tmp/test-artifacts - destination: test-artifacts - - store_artifacts: - path: /tmp/test-results - destination: raw-test-output - - store_test_results: - path: /tmp/test-results - run_tests_3_2: &run_tests_3_2 <<: *defaults_3_2 parallelism: 3 @@ -178,51 +105,6 @@ jobs: paths: - ~/spree/vendor/bundle - bundle_ruby_3_0: - <<: *defaults - steps: - - checkout - - restore_cache: - keys: - - spree-bundle-v10-ruby-3-0-{{ .Branch }} - - spree-bundle-v10-ruby-3-0 - - run: - name: Add keyserver - command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - - run: - name: Install libvips - command: sudo apt-get update && sudo apt-get install libvips - - run: - name: Bundle Install - command: | - bundle check || bundle install - ./bin/build-ci.rb install - - save_cache: - key: spree-bundle-v10-ruby-3-0-{{ .Branch }}-{{ checksum "Gemfile.lock" }} - paths: - - ~/spree/vendor/bundle - - bundle_ruby_3_1: - <<: *defaults_3_1 - steps: - - checkout - - restore_cache: - keys: - - spree-bundle-v10-ruby-3-1-{{ .Branch }} - - spree-bundle-v10-ruby-3-1 - - run: - name: Install libvips - command: sudo apt-get update && sudo apt-get install libvips42 - - run: - name: Bundle Install - command: | - bundle check || bundle install - ./bin/build-ci.rb install - - save_cache: - key: spree-bundle-v10-ruby-3-1-{{ .Branch }}-{{ checksum "Gemfile.lock" }} - paths: - - ~/spree/vendor/bundle - bundle_ruby_3_2: <<: *defaults_3_2 steps: @@ -284,24 +166,6 @@ jobs: environment: POSTGRES_USER: postgres - tests_ruby_3_0_rails_7_0_postgres: - <<: *run_tests_3_0 - environment: - <<: *postgres_environment - docker: - - image: *ruby_3_0_image - - image: *postgres_image - - image: *redis_image - - tests_ruby_3_1_rails_7_0_postgres: - <<: *run_tests_3_1 - environment: - <<: *postgres_environment - docker: - - image: *ruby_3_1_image - - image: *postgres_image - - image: *redis_image - tests_ruby_3_2_rails_7_0_postgres: <<: *run_tests_3_2 environment: @@ -317,8 +181,8 @@ jobs: <<: *postgres_environment RAILS_VERSION: '~> 6.1.0' - tests_ruby_3_0_rails_7_0_mysql: - <<: *run_tests_3_0 + tests_ruby_3_2_rails_7_0_mysql: + <<: *run_tests_3_2 environment: <<: *environment DB: mysql @@ -327,7 +191,7 @@ jobs: COVERAGE: true COVERAGE_DIR: /tmp/workspace/simplecov docker: - - image: *ruby_3_0_image + - image: *ruby_3_2_image - image: *redis_image - image: circleci/mysql:8-ram command: [--default-authentication-plugin=mysql_native_password] @@ -335,8 +199,8 @@ jobs: - checkout - restore_cache: keys: - - spree-bundle-v10-ruby-3-0-{{ .Branch }} - - spree-bundle-v10-ruby-3-0 + - spree-bundle-v10-ruby-3-2-{{ .Branch }} + - spree-bundle-v10-ruby-3-2 - run: name: Add keyserver command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B @@ -399,8 +263,6 @@ workflows: main: jobs: - bundle_ruby_2_7 - - bundle_ruby_3_0 - - bundle_ruby_3_1 - bundle_ruby_3_2 - brakeman: requires: @@ -408,21 +270,15 @@ workflows: - tests_ruby_3_2_rails_7_0_postgres: requires: - bundle_ruby_3_2 - - tests_ruby_3_1_rails_7_0_postgres: - requires: - - bundle_ruby_3_1 - - tests_ruby_3_0_rails_7_0_postgres: - requires: - - bundle_ruby_3_0 - tests_ruby_2_7_rails_7_0_postgres: requires: - bundle_ruby_2_7 - tests_ruby_2_7_rails_6_1_postgres: requires: - bundle_ruby_2_7 - - tests_ruby_3_0_rails_7_0_mysql: + - tests_ruby_3_2_rails_7_0_mysql: requires: - - bundle_ruby_3_0 + - bundle_ruby_3_2 - send_test_coverage: requires: - - tests_ruby_3_0_rails_7_0_mysql + - tests_ruby_3_2_rails_7_0_mysql From ed30ad6173436fea801840429d7f82853c289fd6 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 6 Feb 2023 12:48:59 +0100 Subject: [PATCH 83/89] taxonomies: rewrite first_or_create for mobility compatibility --- sample/db/samples/taxonomies.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sample/db/samples/taxonomies.rb b/sample/db/samples/taxonomies.rb index 56e245c0c7f..06d29cceb4d 100644 --- a/sample/db/samples/taxonomies.rb +++ b/sample/db/samples/taxonomies.rb @@ -8,5 +8,9 @@ ] taxonomies.each do |taxonomy_attrs| - Spree::Taxonomy.where(taxonomy_attrs).first_or_create! + if Spree::Taxonomy.where(taxonomy_attrs).any? + Spree::Taxonomy.where(taxonomy_attrs).first + else + Spree::Taxonomy.create!(taxonomy_attrs) + end end From e5b9c3547c3f841e04ae027d431eef7c4c176a99 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Mon, 6 Feb 2023 14:47:32 +0100 Subject: [PATCH 84/89] menus: rewrite first_or_create for mobility compatibility --- sample/db/samples/menus.rb | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/sample/db/samples/menus.rb b/sample/db/samples/menus.rb index b1346a7b07a..4131ce474ba 100644 --- a/sample/db/samples/menus.rb +++ b/sample/db/samples/menus.rb @@ -1,17 +1,28 @@ Spree::Store.all.each do |store| store.supported_locales_list.each do |locale| - Spree::Menu.where( + main_menu_attrs = { name: 'Main Menu', location: 'header', - store_id: store, + store: store, locale: locale - ).first_or_create! - - Spree::Menu.where( + } + footer_menu_attrs = { name: 'Footer Menu', location: 'footer', - store_id: store, + store: store, locale: locale - ).first_or_create! + } + + if Spree::Menu.where(main_menu_attrs).any? + Spree::Menu.where(main_menu_attrs).first + else + Spree::Menu.create!(main_menu_attrs) + end + + if Spree::Menu.where(footer_menu_attrs).any? + Spree::Menu.where(footer_menu_attrs).first + else + Spree::Menu.create!(footer_menu_attrs) + end end end From 81525b48b998aff93e4328aee8ccda18e989e1bd Mon Sep 17 00:00:00 2001 From: Weronika Witek <74113640+wjwitek@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:08:52 +0100 Subject: [PATCH 85/89] change port 3000 to 4000 in api docs and store sample (#11846) --- api/docs/oauth/index.yml | 2 +- api/docs/v2/platform/index.yaml | 2 +- api/docs/v2/storefront/index.yaml | 114 +++++++++++++++--------------- sample/db/samples/stores.rb | 6 +- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/api/docs/oauth/index.yml b/api/docs/oauth/index.yml index d13546f117c..36a9ef3dece 100644 --- a/api/docs/oauth/index.yml +++ b/api/docs/oauth/index.yml @@ -2,7 +2,7 @@ openapi: 3.0.3 servers: - url: 'https://demo.spreecommerce.org' description: demo - - url: 'http://localhost:3000' + - url: 'http://localhost:4000' description: localhost info: version: 1.0.0 diff --git a/api/docs/v2/platform/index.yaml b/api/docs/v2/platform/index.yaml index 13f74c38c4f..81092a35fb9 100644 --- a/api/docs/v2/platform/index.yaml +++ b/api/docs/v2/platform/index.yaml @@ -17675,7 +17675,7 @@ servers: - url: http://{defaultHost} variables: defaultHost: - default: localhost:3000 + default: localhost:4000 tags: - name: Addresses - name: Adjustments diff --git a/api/docs/v2/storefront/index.yaml b/api/docs/v2/storefront/index.yaml index 6374b429ada..4db0ed4f6e1 100644 --- a/api/docs/v2/storefront/index.yaml +++ b/api/docs/v2/storefront/index.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 servers: - url: 'https://demo.spreecommerce.org' description: demo - - url: 'http://localhost:3000' + - url: 'http://localhost:4000' description: localhost info: version: 2.0.0 @@ -2540,7 +2540,7 @@ components: properties: url: type: string - example: 'http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJbWQyWVhKcFlXNTBjeTltWm1sMmRURlNORFpZWjJaSFpYUkdZMjk2WWsxM1RHWXZNVGs1T1RCak5XVmlNamN4TlRnd1pqVTBabUpqTWpCbFkyVXhZMlZpTTJFd05ERTJZemMzT0dKaE5tSTFNREkyT0dKaFpqa3paV1JtWTJWaE16aGxaQVk2QmtWVSIsImV4cCI6IjIwMTgtMDYtMjRUMTM6NTk6NTguOTY5WiIsInB1ciI6ImJsb2Jfa2V5In19--5e9ff358dc747f73754e332678c5762114ac6f3f/ror_jr_spaghetti.jpeg?content_type=image%2Fjpeg&disposition=inline%3B+filename%3D%22ror_jr_spaghetti.jpeg%22%3B+filename%2A%3DUTF-8%27%27ror_jr_spaghetti.jpeg' + example: 'http://localhost:4000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJbWQyWVhKcFlXNTBjeTltWm1sMmRURlNORFpZWjJaSFpYUkdZMjk2WWsxM1RHWXZNVGs1T1RCak5XVmlNamN4TlRnd1pqVTBabUpqTWpCbFkyVXhZMlZpTTJFd05ERTJZemMzT0dKaE5tSTFNREkyT0dKaFpqa3paV1JtWTJWaE16aGxaQVk2QmtWVSIsImV4cCI6IjIwMTgtMDYtMjRUMTM6NTk6NTguOTY5WiIsInB1ciI6ImJsb2Jfa2V5In19--5e9ff358dc747f73754e332678c5762114ac6f3f/ror_jr_spaghetti.jpeg?content_type=image%2Fjpeg&disposition=inline%3B+filename%3D%22ror_jr_spaghetti.jpeg%22%3B+filename%2A%3DUTF-8%27%27ror_jr_spaghetti.jpeg' description: Absolute URL of the uploaded image in selected style (width/height) width: type: integer @@ -4812,11 +4812,11 @@ components: total_count: 1 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/account/orders' - next: 'http://localhost:3000/api/v2/storefront/account/orders?page=1' - prev: 'http://localhost:3000/api/v2/storefront/account/orders?page=1' - last: 'http://localhost:3000/api/v2/storefront/account/orders?page=1' - first: 'http://localhost:3000/api/v2/storefront/account/orders?page=1' + self: 'http://localhost:4000/api/v2/storefront/account/orders' + next: 'http://localhost:4000/api/v2/storefront/account/orders?page=1' + prev: 'http://localhost:4000/api/v2/storefront/account/orders?page=1' + last: 'http://localhost:4000/api/v2/storefront/account/orders?page=1' + first: 'http://localhost:4000/api/v2/storefront/account/orders?page=1' Including all related objects: value: data: @@ -5087,11 +5087,11 @@ components: total_count: 1 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items,variants,variants.images,billing_address,shipping_address,user,payments,shipments,promotions' - next: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' - prev: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' - last: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' - first: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' + self: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items,variants,variants.images,billing_address,shipping_address,user,payments,shipments,promotions' + next: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' + prev: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' + last: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' + first: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' User: description: 200 Success - Returns the `user` object. content: @@ -5262,11 +5262,11 @@ components: total_count: 3 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/account/addresses' - next: 'http://localhost:3000/api/v2/storefront/account/addresses?page=1' - prev: 'http://localhost:3000/api/v2/storefront/account/addresses?page=1' - last: 'http://localhost:3000/api/v2/storefront/account/addresses?page=1' - first: 'http://localhost:3000/api/v2/storefront/account/addresses?page=1' + self: 'http://localhost:4000/api/v2/storefront/account/addresses' + next: 'http://localhost:4000/api/v2/storefront/account/addresses?page=1' + prev: 'http://localhost:4000/api/v2/storefront/account/addresses?page=1' + last: 'http://localhost:4000/api/v2/storefront/account/addresses?page=1' + first: 'http://localhost:4000/api/v2/storefront/account/addresses?page=1' Address: description: 200 Success - Returns the `address` object. content: @@ -5710,11 +5710,11 @@ components: total_count: 6 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/cms_pages' - next: 'http://localhost:3000/api/v2/storefront/cms_pages?page=1' - prev: 'http://localhost:3000/api/v2/storefront/cms_pages?page=1' - last: 'http://localhost:3000/api/v2/storefront/cms_pages?page=1' - first: 'http://localhost:3000/api/v2/storefront/cms_pages?page=1' + self: 'http://localhost:4000/api/v2/storefront/cms_pages' + next: 'http://localhost:4000/api/v2/storefront/cms_pages?page=1' + prev: 'http://localhost:4000/api/v2/storefront/cms_pages?page=1' + last: 'http://localhost:4000/api/v2/storefront/cms_pages?page=1' + first: 'http://localhost:4000/api/v2/storefront/cms_pages?page=1' CMS Pages with Included Sections: value: data: @@ -6209,11 +6209,11 @@ components: total_count: 6 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections' - next: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections&page=1' - prev: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections&page=1' - last: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections&page=1' - first: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections&page=1' + self: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections' + next: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections&page=1' + prev: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections&page=1' + last: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections&page=1' + first: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections&page=1' CreditCard: description: 200 Success - Returns the `credit_card` object. content: @@ -6323,11 +6323,11 @@ components: total_count: 2 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/account/credit_cards' - next: 'http://localhost:3000/api/v2/storefront/account/credit_cards?page=0' - prev: 'http://localhost:3000/api/v2/storefront/account/credit_cards?page=1' - last: 'http://localhost:3000/api/v2/storefront/account/credit_cards?page=0' - first: 'http://localhost:3000/api/v2/storefront/account/credit_cards?page=1' + self: 'http://localhost:4000/api/v2/storefront/account/credit_cards' + next: 'http://localhost:4000/api/v2/storefront/account/credit_cards?page=0' + prev: 'http://localhost:4000/api/v2/storefront/account/credit_cards?page=1' + last: 'http://localhost:4000/api/v2/storefront/account/credit_cards?page=0' + first: 'http://localhost:4000/api/v2/storefront/account/credit_cards?page=1' Product: description: 200 Success - Returns the `product` object. content: @@ -8556,11 +8556,11 @@ components: - value: Zeta filter_param: zeta links: - self: 'http://localhost:3000/api/v2/storefront/products' - next: 'http://localhost:3000/api/v2/storefront/products?page=2' - prev: 'http://localhost:3000/api/v2/storefront/products?page=1' - last: 'http://localhost:3000/api/v2/storefront/products?page=5' - first: 'http://localhost:3000/api/v2/storefront/products?page=1' + self: 'http://localhost:4000/api/v2/storefront/products' + next: 'http://localhost:4000/api/v2/storefront/products?page=2' + prev: 'http://localhost:4000/api/v2/storefront/products?page=1' + last: 'http://localhost:4000/api/v2/storefront/products?page=5' + first: 'http://localhost:4000/api/v2/storefront/products?page=1' Store: description: 200 Success - Returns the `store` object. content: @@ -11916,11 +11916,11 @@ components: total_count: 26 total_pages: 2 links: - self: 'http://localhost:3000/api/v2/storefront/taxons' - next: 'http://localhost:3000/api/v2/storefront/taxons?page=2' - prev: 'http://localhost:3000/api/v2/storefront/taxons?page=1' - last: 'http://localhost:3000/api/v2/storefront/taxons?page=2' - first: 'http://localhost:3000/api/v2/storefront/taxons?page=1' + self: 'http://localhost:4000/api/v2/storefront/taxons' + next: 'http://localhost:4000/api/v2/storefront/taxons?page=2' + prev: 'http://localhost:4000/api/v2/storefront/taxons?page=1' + last: 'http://localhost:4000/api/v2/storefront/taxons?page=2' + first: 'http://localhost:4000/api/v2/storefront/taxons?page=1' Country: description: 200 Success - Returns the `country` object. content: @@ -15222,11 +15222,11 @@ components: total_count: 240 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/countries' - next: 'http://localhost:3000/api/v2/storefront/countries?page=1&per_page=240' - prev: 'http://localhost:3000/api/v2/storefront/countries?page=1&per_page=240' - last: 'http://localhost:3000/api/v2/storefront/countries?page=1&per_page=240' - first: 'http://localhost:3000/api/v2/storefront/countries?page=1&per_page=240' + self: 'http://localhost:4000/api/v2/storefront/countries' + next: 'http://localhost:4000/api/v2/storefront/countries?page=1&per_page=240' + prev: 'http://localhost:4000/api/v2/storefront/countries?page=1&per_page=240' + last: 'http://localhost:4000/api/v2/storefront/countries?page=1&per_page=240' + first: 'http://localhost:4000/api/v2/storefront/countries?page=1&per_page=240' Shipment: description: '200 Success - Returns an array containing several `shipment` objects, along with the included array containing all available `shipping_rate` and `stock_location` objects. ' content: @@ -16229,11 +16229,11 @@ components: total_count: 2 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/menus' - next: 'http://localhost:3000/api/v2/storefront/menus?page=1' - prev: 'http://localhost:3000/api/v2/storefront/menus?page=1' - last: 'http://localhost:3000/api/v2/storefront/menus?page=1' - first: 'http://localhost:3000/api/v2/storefront/menus?page=1' + self: 'http://localhost:4000/api/v2/storefront/menus' + next: 'http://localhost:4000/api/v2/storefront/menus?page=1' + prev: 'http://localhost:4000/api/v2/storefront/menus?page=1' + last: 'http://localhost:4000/api/v2/storefront/menus?page=1' + first: 'http://localhost:4000/api/v2/storefront/menus?page=1' Wishlist: description: 200 Success - Returns the `wishlist` object. content: @@ -16371,11 +16371,11 @@ components: total_count: 3 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/wishlists?is_variant_included=150' - next: 'http://localhost:3000/api/v2/storefront/wishlists?page=1' - prev: 'http://localhost:3000/api/v2/storefront/wishlists?page=1' - last: 'http://localhost:3000/api/v2/storefront/wishlists?page=1' - first: 'http://localhost:3000/api/v2/storefront/wishlists?page=1' + self: 'http://localhost:4000/api/v2/storefront/wishlists?is_variant_included=150' + next: 'http://localhost:4000/api/v2/storefront/wishlists?page=1' + prev: 'http://localhost:4000/api/v2/storefront/wishlists?page=1' + last: 'http://localhost:4000/api/v2/storefront/wishlists?page=1' + first: 'http://localhost:4000/api/v2/storefront/wishlists?page=1' WishedItem: description: 200 Success - Returns an array of `wished_item` objects. content: diff --git a/sample/db/samples/stores.rb b/sample/db/samples/stores.rb index 533e7dd343d..e459689a654 100644 --- a/sample/db/samples/stores.rb +++ b/sample/db/samples/stores.rb @@ -3,12 +3,12 @@ default_store.default_country = Spree::Country.find_by(iso: 'US') default_store.supported_currencies = 'CAD,USD' default_store.supported_locales = 'en,fr' -default_store.url = Rails.env.development? ? 'localhost:3000' : 'demo.spreecommerce.org' +default_store.url = Rails.env.development? ? 'localhost:4000' : 'demo.spreecommerce.org' default_store.save! eu_store = Spree::Store.find_or_initialize_by(code: 'eustore') eu_store.name = 'EU Store' -eu_store.url = Rails.env.development? ? 'eu.lvh.me:3000' : 'demo-eu.spreecommerce.org' +eu_store.url = Rails.env.development? ? 'eu.lvh.me:4000' : 'demo-eu.spreecommerce.org' eu_store.mail_from_address = 'eustore@example.com' eu_store.default_currency = 'EUR' eu_store.default_locale = 'de' @@ -19,7 +19,7 @@ uk_store = Spree::Store.find_or_initialize_by(code: 'ukstore') uk_store.name = 'UK Store' -uk_store.url = Rails.env.development? ? 'uk.lvh.me:3000' : 'demo-uk.spreecommerce.org' +uk_store.url = Rails.env.development? ? 'uk.lvh.me:4000' : 'demo-uk.spreecommerce.org' uk_store.mail_from_address = 'ukstore@example.com' uk_store.default_currency = 'GBP' uk_store.default_locale = 'en' From 8bec027c66b83b749114f0430c5f3fd1a678ee12 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Tue, 7 Feb 2023 13:38:09 +0100 Subject: [PATCH 86/89] add menu item translations --- .../integration/api/v2/platform/menus_spec.rb | 2 +- core/app/models/spree/menu_item.rb | 12 + ...206153517_create_menu_item_translations.rb | 18 + ...0230206153936_allow_null_menu_item_name.rb | 5 + ...r_menu_item_data_to_translatable_tables.rb | 11 + sample/db/samples/menu_items.rb | 348 +++++++++--------- 6 files changed, 217 insertions(+), 179 deletions(-) create mode 100644 core/db/migrate/20230206153517_create_menu_item_translations.rb create mode 100644 core/db/migrate/20230206153936_allow_null_menu_item_name.rb create mode 100644 core/db/migrate/20230206154053_transfer_menu_item_data_to_translatable_tables.rb diff --git a/api/spec/integration/api/v2/platform/menus_spec.rb b/api/spec/integration/api/v2/platform/menus_spec.rb index eb2db184f5a..c6b13eca750 100644 --- a/api/spec/integration/api/v2/platform/menus_spec.rb +++ b/api/spec/integration/api/v2/platform/menus_spec.rb @@ -26,7 +26,7 @@ record.save! end - menu_1 = Spree::Menu.first + menu_1 = Spree::MenuItem.first menu_2 = Spree::Menu.last create(:menu_item, menu: menu_1) create(:menu_item, menu: menu_1) diff --git a/core/app/models/spree/menu_item.rb b/core/app/models/spree/menu_item.rb index 0d3503a46b2..b0fadbe13f1 100644 --- a/core/app/models/spree/menu_item.rb +++ b/core/app/models/spree/menu_item.rb @@ -1,10 +1,14 @@ module Spree class MenuItem < Spree::Base + include TranslatableResource include Spree::DisplayLink if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name subtitle destination] + translates(*TRANSLATABLE_FIELDS) + acts_as_nested_set dependent: :destroy ITEM_TYPE = %w[Link Container] @@ -38,6 +42,14 @@ def code?(item_code = nil) end end + def self.first_or_create!(attrs) + if where(attrs).any? + where(attrs).first + else + create!(attrs) + end + end + private def reset_link_attributes diff --git a/core/db/migrate/20230206153517_create_menu_item_translations.rb b/core/db/migrate/20230206153517_create_menu_item_translations.rb new file mode 100644 index 00000000000..29ac2dfb9ec --- /dev/null +++ b/core/db/migrate/20230206153517_create_menu_item_translations.rb @@ -0,0 +1,18 @@ +class CreateMenuItemTranslations < ActiveRecord::Migration[6.1] + def change + create_table :spree_menu_item_translations do |t| + # translatable fields + t.string :name + t.string :subtitle + t.string :destination + + t.string :locale, null: false + t.references :spree_menu_item, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_menu_item_translations, :locale, name: :index_spree_menu_item_translations_on_locale + add_index :spree_menu_item_translations, [:spree_menu_item_id, :locale], name: :index_spree_menu_item_translations_on_spree_menu_id_and_locale, unique: true + end +end diff --git a/core/db/migrate/20230206153936_allow_null_menu_item_name.rb b/core/db/migrate/20230206153936_allow_null_menu_item_name.rb new file mode 100644 index 00000000000..bf230ed9a67 --- /dev/null +++ b/core/db/migrate/20230206153936_allow_null_menu_item_name.rb @@ -0,0 +1,5 @@ +class AllowNullMenuItemName < ActiveRecord::Migration[6.1] + def change + change_column_null :spree_menu_items, :name, true + end +end diff --git a/core/db/migrate/20230206154053_transfer_menu_item_data_to_translatable_tables.rb b/core/db/migrate/20230206154053_transfer_menu_item_data_to_translatable_tables.rb new file mode 100644 index 00000000000..a30368e235e --- /dev/null +++ b/core/db/migrate/20230206154053_transfer_menu_item_data_to_translatable_tables.rb @@ -0,0 +1,11 @@ +class TransferMenuItemDataToTranslatableTables < ActiveRecord::Migration[6.1] + TRANSLATION_MIGRATION = Spree::TranslationMigrations.new(Spree::MenuItem, 'en') + + def up + TRANSLATION_MIGRATION.transfer_translation_data + end + + def down + TRANSLATION_MIGRATION.revert_translation_data_transfer + end +end diff --git a/sample/db/samples/menu_items.rb b/sample/db/samples/menu_items.rb index 4d8020a1d1d..d2848d4b80a 100644 --- a/sample/db/samples/menu_items.rb +++ b/sample/db/samples/menu_items.rb @@ -114,35 +114,35 @@ # Root Items # ############## woman_taxon = Spree::Taxon.find_by!(permalink: 'categories/women') - menu_root_women = Spree::MenuItem.where( - name: root_name_a, - item_type: 'Link', - linked_resource_type: 'Spree::Taxon', - menu_id: menu, - parent_id: menu.root.id - ).first_or_create! + menu_root_women = Spree::MenuItem.first_or_create!({ + name: root_name_a, + item_type: 'Link', + linked_resource_type: 'Spree::Taxon', + menu_id: menu.id, + parent_id: menu.root.id + }) menu_root_women.linked_resource_id = woman_taxon.id menu_root_women.save! men_taxon = Spree::Taxon.find_by!(permalink: 'categories/men') - menu_root_men = Spree::MenuItem.where( - name: root_name_b, - item_type: 'Link', - linked_resource_type: 'Spree::Taxon', - menu_id: menu, - parent_id: menu.root.id - ).first_or_create! + menu_root_men = Spree::MenuItem.first_or_create!({ + name: root_name_b, + item_type: 'Link', + linked_resource_type: 'Spree::Taxon', + menu_id: menu.id, + parent_id: menu.root.id + }) menu_root_men.linked_resource_id = men_taxon.id menu_root_men.save! sw_taxon = Spree::Taxon.find_by!(permalink: 'categories/sportswear') - menu_root_sw = Spree::MenuItem.where( - name: root_name_c, - item_type: 'Link', - linked_resource_type: 'Spree::Taxon', - menu_id: menu, - parent_id: menu.root.id - ).first_or_create! + menu_root_sw = Spree::MenuItem.first_or_create!({ + name: root_name_c, + item_type: 'Link', + linked_resource_type: 'Spree::Taxon', + menu_id: menu.id, + parent_id: menu.root.id + }) menu_root_sw.linked_resource_id = sw_taxon.id menu_root_sw.save! @@ -151,57 +151,52 @@ ############## # Categories # ############## - menu_cat_women = Spree::MenuItem.where( - name: categories, - item_type: 'Container', - code: 'category', - menu_id: menu, - parent_id: menu_root_women - ).first_or_create! - - menu_cat_men = Spree::MenuItem.where( - name: categories, - item_type: 'Container', - code: 'category', - menu_id: menu, - parent_id: menu_root_men - ).first_or_create! - - menu_cat_sw = Spree::MenuItem.where( - name: categories, - item_type: 'Container', - code: 'category', - menu_id: menu, - parent_id: menu_root_sw - ).first_or_create! + menu_cat_women = Spree::MenuItem.first_or_create!({ + name: categories, + item_type: 'Container', + code: 'category', + menu_id: menu.id, + parent_id: menu_root_women + }) + menu_cat_men = Spree::MenuItem.first_or_create!({ + name: categories, + item_type: 'Container', + code: 'category', + menu_id: menu.id, + parent_id: menu_root_men + }) + menu_cat_sw = Spree::MenuItem.first_or_create!({ + name: categories, + item_type: 'Container', + code: 'category', + menu_id: menu.id, + parent_id: menu_root_sw + }) ############## # Promotions # ############## - menu_promo_women = Spree::MenuItem.where( - name: 'Promos', - item_type: 'Container', - code: 'promo', - menu_id: menu, - parent_id: menu_root_women - ).first_or_create! - - menu_promo_men = Spree::MenuItem.where( - name: 'Promos', - item_type: 'Container', - code: 'promo', - menu_id: menu, - parent_id: menu_root_men - ).first_or_create! - - menu_promo_sw = Spree::MenuItem.where( - name: 'Promos', - item_type: 'Container', - code: 'promo', - menu_id: menu, - parent_id: menu_root_sw - ).first_or_create! - + menu_promo_women = Spree::MenuItem.first_or_create!({ + name: 'Promos', + item_type: 'Container', + code: 'promo', + menu_id: menu.id, + parent_id: menu_root_women + }) + menu_promo_men = Spree::MenuItem.first_or_create!({ + name: 'Promos', + item_type: 'Container', + code: 'promo', + menu_id: menu.id, + parent_id: menu_root_men + }) + menu_promo_sw = Spree::MenuItem.first_or_create!({ + name: 'Promos', + item_type: 'Container', + code: 'promo', + menu_id: menu.id, + parent_id: menu_root_sw + }) promos = [menu_promo_women, menu_promo_men, menu_promo_sw] else promos = [] @@ -215,25 +210,25 @@ ##################### promos.each do |promo| - summer_promo = Spree::MenuItem.where( - name: promo_a_name, - subtitle: promo_a_subtitle, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: promo - ).first_or_create! + summer_promo = Spree::MenuItem.first_or_create!({ + name: promo_a_name, + subtitle: promo_a_subtitle, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: promo + }) summer_promo.linked_resource_id = summer.id summer_promo.save! - special_offer = Spree::MenuItem.where( - name: promo_b_name, - subtitle: promo_b_subtitle, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: promo - ).first_or_create! + special_offer = Spree::MenuItem.first_or_create!({ + name: promo_b_name, + subtitle: promo_b_subtitle, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: promo + }) special_offer.linked_resource_id = offers.id special_offer.save! end @@ -249,68 +244,65 @@ end women_skirts_t = Spree::Taxon.find_by!(permalink: 'categories/women/skirts') - women_skirts = Spree::MenuItem.where( - name: skirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_skirts = Spree::MenuItem.first_or_create!({ + name: skirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_skirts.linked_resource_id = women_skirts_t.id women_skirts.save! women_dresses_t = Spree::Taxon.find_by!(permalink: 'categories/women/dresses') - women_dresses = Spree::MenuItem.where( - name: dresses, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_dresses = Spree::MenuItem.first_or_create!({ + name: dresses, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_dresses.linked_resource_id = women_dresses_t.id women_dresses.save! women_s_b_t = Spree::Taxon.find_by!(permalink: 'categories/women/shirts-and-blouses') - women_s_b = Spree::MenuItem.where( - name: shirts_and_blouses, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! - women_s_b.linked_resource_id = women_s_b_t.id - women_s_b.save! - + women_s_b = Spree::MenuItem.first_or_create!({ + name: shirts_and_blouses, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_sweaters_t = Spree::Taxon.find_by!(permalink: 'categories/women/sweaters') - women_sweaters = Spree::MenuItem.where( - name: sweaters, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_sweaters = Spree::MenuItem.first_or_create!({ + name: sweaters, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_sweaters.linked_resource_id = women_sweaters_t.id women_sweaters.save! women_tops_tees_t = Spree::Taxon.find_by!(permalink: 'categories/women/tops-and-t-shirts') - women_tops_tees = Spree::MenuItem.where( - name: tops_and_t_shirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_tops_tees = Spree::MenuItem.first_or_create!({ + name: tops_and_t_shirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_tops_tees.linked_resource_id = women_tops_tees_t.id women_tops_tees.save! women_j_c_t = Spree::Taxon.find_by!(permalink: 'categories/women/jackets-and-coats') - women_j_c = Spree::MenuItem.where( - name: jackets_and_coats, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_j_c = Spree::MenuItem.first_or_create!({ + name: jackets_and_coats, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_j_c.linked_resource_id = women_j_c_t.id women_j_c.save! @@ -325,46 +317,46 @@ end men_shirts_t = Spree::Taxon.find_by!(permalink: 'categories/men/shirts') - men_shirts = Spree::MenuItem.where( - name: shirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: men_link_parent_id - ).first_or_create! + men_shirts = Spree::MenuItem.first_or_create!({ + name: shirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: men_link_parent_id + }) men_shirts.linked_resource_id = men_shirts_t.id men_shirts.save! men_t_shirts_t = Spree::Taxon.find_by!(permalink: 'categories/men/t-shirts') - men_t_shirts = Spree::MenuItem.where( - name: t_shirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: men_link_parent_id - ).first_or_create! + men_t_shirts = Spree::MenuItem.first_or_create!({ + name: t_shirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: men_link_parent_id + }) men_t_shirts.linked_resource_id = men_t_shirts_t.id men_t_shirts.save! men_sweaters_t = Spree::Taxon.find_by!(permalink: 'categories/men/sweaters') - men_sweaters = Spree::MenuItem.where( - name: sweaters, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: men_link_parent_id - ).first_or_create! + men_sweaters = Spree::MenuItem.first_or_create!({ + name: sweaters, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: men_link_parent_id + }) men_sweaters.linked_resource_id = men_sweaters_t.id men_sweaters.save! men_j_c_t = Spree::Taxon.find_by!(permalink: 'categories/men/jackets-and-coats') - men_j_c = Spree::MenuItem.where( - name: jackets_and_coats, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: men_link_parent_id - ).first_or_create! + men_j_c = Spree::MenuItem.first_or_create!({ + name: jackets_and_coats, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: men_link_parent_id + }) men_j_c.linked_resource_id = men_j_c_t.id men_j_c.save! @@ -379,35 +371,35 @@ end sw_tops_t = Spree::Taxon.find_by!(permalink: 'categories/sportswear/tops') - sw_tops = Spree::MenuItem.where( - name: tops, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: sw_link_parent_id - ).first_or_create! + sw_tops = Spree::MenuItem.first_or_create!({ + name: tops, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: sw_link_parent_id + }) sw_tops.linked_resource_id = sw_tops_t.id sw_tops.save! sw_sweatshirts_t = Spree::Taxon.find_by!(permalink: 'categories/sportswear/sweatshirts') - sw_sweatshirts = Spree::MenuItem.where( - name: sweat_shirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: sw_link_parent_id - ).first_or_create! + sw_sweatshirts = Spree::MenuItem.first_or_create!({ + name: sweat_shirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: sw_link_parent_id + }) sw_sweatshirts.linked_resource_id = sw_sweatshirts_t.id sw_sweatshirts.save! sw_pants_t = Spree::Taxon.find_by!(permalink: 'categories/sportswear/pants') - sw_pants = Spree::MenuItem.where( - name: pants, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: sw_link_parent_id - ).first_or_create! + sw_pants = Spree::MenuItem.first_or_create!({ + name: pants, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: sw_link_parent_id + }) sw_pants.linked_resource_id = sw_pants_t.id sw_pants.save! end From 077540ef185a4af749c2acc7870b87c7dfd3ca35 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Wed, 8 Feb 2023 12:27:19 +0100 Subject: [PATCH 87/89] fix tests and cleanup --- api/spec/integration/api/v2/platform/menu_items_spec.rb | 9 ++++++++- api/spec/integration/api/v2/platform/menus_spec.rb | 2 +- core/app/models/spree/menu_item.rb | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/api/spec/integration/api/v2/platform/menu_items_spec.rb b/api/spec/integration/api/v2/platform/menu_items_spec.rb index 28f247a4bd1..cadb3c9aae2 100644 --- a/api/spec/integration/api/v2/platform/menu_items_spec.rb +++ b/api/spec/integration/api/v2/platform/menu_items_spec.rb @@ -16,7 +16,14 @@ let!(:menu_item_three) { create(:menu_item, menu: menu) } let(:records_list) { create_list(:menu_item, 4, menu: menu) } - let(:valid_create_param_value) { build(:menu_item, menu: menu).attributes } + let(:valid_create_param_value) do + { + name: 'Special Menu Item', + item_type: 'Link', + linked_resource_type: 'Spree::Linkable::Uri', + menu_id: menu.id + } + end let(:valid_update_param_value) do { menu_item: { diff --git a/api/spec/integration/api/v2/platform/menus_spec.rb b/api/spec/integration/api/v2/platform/menus_spec.rb index c6b13eca750..eb2db184f5a 100644 --- a/api/spec/integration/api/v2/platform/menus_spec.rb +++ b/api/spec/integration/api/v2/platform/menus_spec.rb @@ -26,7 +26,7 @@ record.save! end - menu_1 = Spree::MenuItem.first + menu_1 = Spree::Menu.first menu_2 = Spree::Menu.last create(:menu_item, menu: menu_1) create(:menu_item, menu: menu_1) diff --git a/core/app/models/spree/menu_item.rb b/core/app/models/spree/menu_item.rb index b0fadbe13f1..1372e908b09 100644 --- a/core/app/models/spree/menu_item.rb +++ b/core/app/models/spree/menu_item.rb @@ -42,6 +42,7 @@ def code?(item_code = nil) end end + # Override first_or_create! to work around Mobility issue with the original first_or_create! method def self.first_or_create!(attrs) if where(attrs).any? where(attrs).first From fa8ced80c706fdab244ef9e0f141c77c35a0c89e Mon Sep 17 00:00:00 2001 From: nciemniak Date: Thu, 9 Feb 2023 13:48:20 +0100 Subject: [PATCH 88/89] make translations accessible for deleted products --- core/app/models/spree/product.rb | 6 +++++- core/spec/models/spree/product_spec.rb | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index be71054244c..47dcf3f63a8 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -41,7 +41,11 @@ class Product < Spree::Base TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_keywords meta_title].freeze translates(*TRANSLATABLE_FIELDS) - self::Translation.class_eval { acts_as_paranoid } + self::Translation.class_eval do + acts_as_paranoid + # deleted translation values also need to be accessible for index views listing deleted resources + default_scope { unscope(where: :deleted_at) } + end friendly_id :slug_candidates, use: [:history, :mobility] acts_as_paranoid diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb index dc7078fd17e..9081b32d264 100644 --- a/core/spec/models/spree/product_spec.rb +++ b/core/spec/models/spree/product_spec.rb @@ -352,6 +352,12 @@ class Extension < Spree::Base expect(@product.slugs.with_deleted).not_to be_empty end + it 'keeps translations when product is destroyed' do + @product.destroy + + expect(@product.name).not_to be_empty + end + it 'updates the history when the product is restored' do @product.destroy From 5a9138cc23f7565b5d2225a45ded030a2a124ea8 Mon Sep 17 00:00:00 2001 From: nciemniak Date: Fri, 10 Feb 2023 13:37:39 +0100 Subject: [PATCH 89/89] fix bad merge --- core/app/models/spree/menu.rb | 3 --- core/app/models/spree/menu_item.rb | 4 ---- 2 files changed, 7 deletions(-) diff --git a/core/app/models/spree/menu.rb b/core/app/models/spree/menu.rb index bac3dd15d27..4cd8c1b96d6 100644 --- a/core/app/models/spree/menu.rb +++ b/core/app/models/spree/menu.rb @@ -17,9 +17,6 @@ class Menu < Spree::Base MENU_LOCATIONS_PARAMETERIZED << parameterize_location end - TRANSLATABLE_FIELDS = %i[name] - translates *TRANSLATABLE_FIELDS - has_many :menu_items, dependent: :destroy, class_name: 'Spree::MenuItem' belongs_to :store, touch: true, class_name: 'Spree::Store' diff --git a/core/app/models/spree/menu_item.rb b/core/app/models/spree/menu_item.rb index 6f994fd8dd1..1372e908b09 100644 --- a/core/app/models/spree/menu_item.rb +++ b/core/app/models/spree/menu_item.rb @@ -2,7 +2,6 @@ module Spree class MenuItem < Spree::Base include TranslatableResource include Spree::DisplayLink - include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end @@ -12,9 +11,6 @@ class MenuItem < Spree::Base acts_as_nested_set dependent: :destroy - TRANSLATABLE_FIELDS = %i[name subtitle destination] - translates *TRANSLATABLE_FIELDS - ITEM_TYPE = %w[Link Container] LINKED_RESOURCE_TYPE = ['Spree::Linkable::Uri', 'Spree::Linkable::Homepage', 'Spree::Product', 'Spree::Taxon', 'Spree::CmsPage']