spree_core 4.2.0.rc3 → 4.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/spree.js +19 -0
  3. data/app/controllers/spree/base_controller.rb +2 -1
  4. data/app/controllers/spree/errors_controller.rb +11 -0
  5. data/app/finders/spree/orders/find_current.rb +1 -1
  6. data/app/finders/spree/products/find.rb +14 -3
  7. data/app/helpers/spree/base_helper.rb +2 -1
  8. data/app/helpers/spree/currency_helper.rb +34 -0
  9. data/app/helpers/spree/locale_helper.rb +31 -0
  10. data/app/helpers/spree/products_helper.rb +37 -12
  11. data/app/models/concerns/spree/product_scopes.rb +1 -1
  12. data/app/models/concerns/spree/user_payment_source.rb +1 -1
  13. data/app/models/spree/ability.rb +45 -30
  14. data/app/models/spree/app_configuration.rb +2 -2
  15. data/app/models/spree/app_dependencies.rb +3 -1
  16. data/app/models/spree/credit_card.rb +4 -0
  17. data/app/models/spree/image.rb +14 -14
  18. data/app/models/spree/line_item.rb +6 -9
  19. data/app/models/spree/price.rb +1 -1
  20. data/app/models/spree/product.rb +29 -15
  21. data/app/models/spree/promotion/rules/option_value.rb +1 -1
  22. data/app/models/spree/store.rb +38 -9
  23. data/app/models/spree/variant.rb +8 -8
  24. data/app/paginators/spree/shared/paginate.rb +8 -1
  25. data/app/presenters/spree/variant_presenter.rb +2 -5
  26. data/app/services/spree/build_localized_redirect_url.rb +101 -0
  27. data/app/services/spree/cart/estimate_shipping_rates.rb +1 -1
  28. data/app/views/spree/errors/forbidden.html.erb +0 -0
  29. data/app/views/spree/errors/unauthorized.html.erb +0 -0
  30. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +13 -2
  31. data/app/views/spree/shared/_purchased_items_table.html.erb +15 -6
  32. data/app/views/spree/shared/purchased_items_table/_adjustment.html.erb +2 -2
  33. data/app/views/spree/shared/purchased_items_table/_line_item.html.erb +2 -2
  34. data/config/locales/en.yml +8 -54
  35. data/config/routes.rb +2 -1
  36. data/db/default/spree/stores.rb +1 -0
  37. data/db/default/spree/zones.rb +4 -1
  38. data/db/migrate/20191017121054_add_supported_currencies_to_store.rb +1 -0
  39. data/db/migrate/20201012091259_add_filterable_column_to_spree_option_types.rb +6 -2
  40. data/db/migrate/20210120142527_ensure_default_locale_in_spree_stores.rb +5 -0
  41. data/db/migrate/20210205211040_add_supported_locales_to_spree_stores.rb +11 -0
  42. data/db/migrate/20210215202602_migrate_spree_i18n_globalize_config.rb +22 -0
  43. data/lib/generators/spree/install/install_generator.rb +9 -6
  44. data/lib/spree/core.rb +2 -1
  45. data/lib/spree/core/controller_helpers/auth.rb +3 -1
  46. data/lib/spree/core/controller_helpers/common.rb +6 -8
  47. data/lib/spree/core/controller_helpers/currency.rb +54 -0
  48. data/lib/spree/core/controller_helpers/locale.rb +58 -0
  49. data/lib/spree/core/controller_helpers/search.rb +1 -1
  50. data/lib/spree/core/controller_helpers/store.rb +4 -16
  51. data/lib/spree/core/version.rb +3 -1
  52. data/lib/spree/i18n.rb +12 -0
  53. data/lib/spree/permitted_attributes.rb +1 -1
  54. data/lib/spree/service_module.rb +2 -2
  55. data/lib/spree/testing_support/common_rake.rb +1 -1
  56. data/lib/spree/testing_support/controller_requests.rb +10 -10
  57. data/lib/spree/testing_support/factories/stock_location_factory.rb +2 -2
  58. data/lib/spree/testing_support/factories/store_factory.rb +1 -0
  59. data/lib/spree/testing_support/flatpickr_capybara.rb +101 -0
  60. data/lib/spree/testing_support/locale_helpers.rb +78 -0
  61. data/lib/spree/testing_support/next_instance_of.rb +38 -0
  62. data/spree_core.gemspec +1 -1
  63. metadata +20 -9
  64. data/lib/generators/spree/install/templates/config/initializers/spree_storefront.rb +0 -1
  65. data/lib/generators/spree/install/templates/config/spree_storefront.yml +0 -67
  66. data/lib/spree/core/controller_helpers/currency_helpers.rb +0 -15
@@ -57,16 +57,13 @@ module Spree
57
57
  end
58
58
 
59
59
  def update_price
60
- if Spree::Config.show_store_currency_selector == true
61
- currency_price = Spree::Price.where(
62
- currency: order.currency,
63
- variant_id: variant_id
64
- ).first
60
+ currency_price = variant.price_in(order.currency)
65
61
 
66
- self.price = currency_price.price_including_vat_for(tax_zone: tax_zone)
67
- else
68
- self.price = variant.price_including_vat_for(tax_zone: tax_zone)
69
- end
62
+ self.price = if currency_price.amount.present?
63
+ currency_price.price_including_vat_for(tax_zone: tax_zone)
64
+ else
65
+ 0
66
+ end
70
67
  end
71
68
 
72
69
  def copy_tax_category
@@ -27,7 +27,7 @@ module Spree
27
27
  self.whitelisted_ransackable_attributes = ['amount', 'compare_at_amount']
28
28
 
29
29
  def money
30
- Spree::Money.new(amount || 0, currency: currency)
30
+ Spree::Money.new(amount || 0, currency: currency.upcase)
31
31
  end
32
32
 
33
33
  def amount=(amount)
@@ -90,6 +90,10 @@ module Spree
90
90
  after_save :reset_nested_changes
91
91
  after_touch :touch_taxons
92
92
 
93
+ # reset cache on save inside trasaction and transaction commit
94
+ after_save :reset_memoized_data
95
+ after_commit :reset_memoized_data
96
+
93
97
  before_validation :normalize_slug, on: :update
94
98
  before_validation :validate_master
95
99
 
@@ -127,6 +131,13 @@ module Spree
127
131
 
128
132
  alias master_images images
129
133
 
134
+ def reload
135
+ %w(total_on_hand taxonomy_ids taxon_and_ancestors category category default_variant_id tax_category default_variant).each do |v|
136
+ instance_variable_set(:"@#{v}", nil)
137
+ end
138
+ super
139
+ end
140
+
130
141
  # Cant use short form block syntax due to https://github.com/Netflix/fast_jsonapi/issues/259
131
142
  def purchasable?
132
143
  variants_including_master.any?(&:purchasable?)
@@ -160,7 +171,7 @@ module Spree
160
171
  #
161
172
  # @return [Spree::Variant]
162
173
  def default_variant
163
- Rails.cache.fetch(default_variant_cache_key) do
174
+ @default_variant ||= Rails.cache.fetch(default_variant_cache_key) do
164
175
  if Spree::Config[:track_inventory_levels] && variants.in_stock_or_backorderable.any?
165
176
  variants.in_stock_or_backorderable.first
166
177
  else
@@ -172,11 +183,11 @@ module Spree
172
183
  # Returns default Variant ID for Product
173
184
  # @return [Integer]
174
185
  def default_variant_id
175
- default_variant.id
186
+ @default_variant_id ||= default_variant.id
176
187
  end
177
188
 
178
189
  def tax_category
179
- super || TaxCategory.find_by(is_default: true)
190
+ @tax_category ||= super || TaxCategory.find_by(is_default: true)
180
191
  end
181
192
 
182
193
  # Adding properties and option types on creation based on a chosen prototype
@@ -288,11 +299,11 @@ module Spree
288
299
  end
289
300
 
290
301
  def total_on_hand
291
- if any_variants_not_track_inventory?
292
- Float::INFINITY
293
- else
294
- stock_items.sum(:count_on_hand)
295
- end
302
+ @total_on_hand ||= if any_variants_not_track_inventory?
303
+ Float::INFINITY
304
+ else
305
+ stock_items.sum(:count_on_hand)
306
+ end
296
307
  end
297
308
 
298
309
  # Master variant may be deleted (i.e. when the product is deleted)
@@ -303,14 +314,11 @@ module Spree
303
314
  end
304
315
 
305
316
  def brand
306
- taxons.joins(:taxonomy).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_brands_name) })
317
+ @brand ||= taxons.joins(:taxonomy).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_brands_name) })
307
318
  end
308
319
 
309
320
  def category
310
- taxons.joins(:taxonomy).
311
- where(spree_taxonomies: { name: Spree.t(:taxonomy_categories_name) }).
312
- order(depth: :desc).
313
- first
321
+ @category ||= taxons.joins(:taxonomy).order(depth: :desc).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_categories_name) })
314
322
  end
315
323
 
316
324
  private
@@ -440,12 +448,12 @@ module Spree
440
448
  end
441
449
 
442
450
  def taxon_and_ancestors
443
- taxons.map(&:self_and_ancestors).flatten.uniq
451
+ @taxon_and_ancestors ||= taxons.map(&:self_and_ancestors).flatten.uniq
444
452
  end
445
453
 
446
454
  # Get the taxonomy ids of all taxons assigned to this product and their ancestors.
447
455
  def taxonomy_ids
448
- taxon_and_ancestors.map(&:taxonomy_id).flatten.uniq
456
+ @taxonomy_ids ||= taxon_and_ancestors.map(&:taxonomy_id).flatten.uniq
449
457
  end
450
458
 
451
459
  # Iterate through this products taxons and taxonomies and touch their timestamps in a batch
@@ -471,5 +479,11 @@ module Spree
471
479
  errors.add(:discontinue_on, :invalid_date_range)
472
480
  end
473
481
  end
482
+
483
+ def reset_memoized_data
484
+ %w(total_on_hand taxonomy_ids taxon_and_ancestors category default_variant_id tax_category default_variant).each do |v|
485
+ instance_variable_set(:"@#{v}", nil)
486
+ end
487
+ end
474
488
  end
475
489
  end
@@ -31,7 +31,7 @@ module Spree
31
31
  end
32
32
 
33
33
  def actionable?(line_item)
34
- product_id = line_item.product.id
34
+ product_id = line_item.product_id
35
35
  option_values_ids = line_item.variant.option_value_ids
36
36
  eligible_product_ids = preferred_eligible_values.keys
37
37
  eligible_value_ids = preferred_eligible_values[product_id]
@@ -24,6 +24,7 @@ module Spree
24
24
  validates :mailer_logo, content_type: ['image/png', 'image/jpg', 'image/jpeg']
25
25
 
26
26
  before_save :ensure_default_exists_and_is_unique
27
+ before_save :ensure_supported_currencies, :ensure_supported_locales
27
28
  before_destroy :validate_not_default
28
29
 
29
30
  scope :by_url, ->(url) { where('url like ?', "%#{url}%") }
@@ -43,28 +44,39 @@ module Spree
43
44
  end
44
45
  end
45
46
 
47
+ def self.available_locales
48
+ Rails.cache.fetch('stores_available_locales') do
49
+ Spree::Store.all.map(&:supported_locales_list).flatten.uniq
50
+ end
51
+ end
52
+
46
53
  def supported_currencies_list
47
- (read_attribute(:supported_currencies).to_s.split(',') << default_currency).map(&:to_s).map do |code|
54
+ @supported_currencies_list ||= (read_attribute(:supported_currencies).to_s.split(',') << default_currency).sort.map(&:to_s).map do |code|
48
55
  ::Money::Currency.find(code.strip)
49
56
  end.uniq.compact
50
57
  end
51
58
 
59
+ def supported_locales_list
60
+ # TODO: add support of multiple supported languages to a single Store
61
+ @supported_locales_list ||= (read_attribute(:supported_locales).to_s.split(',') << default_locale).compact.uniq.sort
62
+ end
63
+
52
64
  def unique_name
53
- "#{name} (#{code})"
65
+ @unique_name ||= "#{name} (#{code})"
54
66
  end
55
67
 
56
68
  def formatted_url
57
69
  return if url.blank?
58
70
 
59
- if url.match(/http:\/\/|https:\/\//)
60
- url
61
- else
62
- "https://#{url}"
63
- end
71
+ @formatted_url ||= if url.match(/http:\/\/|https:\/\//)
72
+ url
73
+ else
74
+ Rails.env.development? ? "http://#{url}" : "https://#{url}"
75
+ end
64
76
  end
65
77
 
66
78
  def countries_available_for_checkout
67
- checkout_zone_or_default.try(:country_list) || Spree::Country.all
79
+ @countries_available_for_checkout ||= checkout_zone_or_default.try(:country_list) || Spree::Country.all
68
80
  end
69
81
 
70
82
  def states_available_for_checkout(country)
@@ -72,7 +84,7 @@ module Spree
72
84
  end
73
85
 
74
86
  def checkout_zone_or_default
75
- checkout_zone || Spree::Zone.default_checkout_zone
87
+ @checkout_zone_or_default ||= checkout_zone || Spree::Zone.default_checkout_zone
76
88
  end
77
89
 
78
90
  private
@@ -85,6 +97,22 @@ module Spree
85
97
  end
86
98
  end
87
99
 
100
+ def ensure_supported_locales
101
+ return unless attributes.keys.include?('supported_locales')
102
+ return if supported_locales.present?
103
+ return if default_locale.blank?
104
+
105
+ self.supported_locales = default_locale
106
+ end
107
+
108
+ def ensure_supported_currencies
109
+ return unless attributes.keys.include?('supported_currencies')
110
+ return if supported_currencies.present?
111
+ return if default_currency.blank?
112
+
113
+ self.supported_currencies = default_currency
114
+ end
115
+
88
116
  def validate_not_default
89
117
  if default
90
118
  errors.add(:base, :cannot_destroy_default_store)
@@ -94,6 +122,7 @@ module Spree
94
122
 
95
123
  def clear_cache
96
124
  Rails.cache.delete('default_store')
125
+ Rails.cache.delete('stores_available_locales')
97
126
  end
98
127
  end
99
128
  end
@@ -123,15 +123,15 @@ module Spree
123
123
  end
124
124
 
125
125
  def tax_category
126
- if self[:tax_category_id].nil?
127
- product.tax_category
128
- else
129
- Spree::TaxCategory.find(self[:tax_category_id])
130
- end
126
+ @tax_category ||= if self[:tax_category_id].nil?
127
+ product.tax_category
128
+ else
129
+ Spree::TaxCategory.find(self[:tax_category_id])
130
+ end
131
131
  end
132
132
 
133
133
  def options_text
134
- Spree::Variants::OptionsPresenter.new(self).to_sentence
134
+ @options_text ||= Spree::Variants::OptionsPresenter.new(self).to_sentence
135
135
  end
136
136
 
137
137
  # Default to master name
@@ -192,7 +192,7 @@ module Spree
192
192
  end
193
193
 
194
194
  def price_in(currency)
195
- prices.detect { |price| price.currency == currency } || prices.build(currency: currency)
195
+ prices.detect { |price| price.currency == currency&.upcase } || prices.build(currency: currency&.upcase)
196
196
  end
197
197
 
198
198
  def amount_in(currency)
@@ -226,7 +226,7 @@ module Spree
226
226
  end
227
227
 
228
228
  def compare_at_price
229
- price_in(cost_currency).try(:compare_at_amount)
229
+ @compare_at_price ||= price_in(cost_currency).try(:compare_at_amount)
230
230
  end
231
231
 
232
232
  def name_and_sku
@@ -4,7 +4,14 @@ module Spree
4
4
  def initialize(collection, params)
5
5
  @collection = collection
6
6
  @page = params[:page]
7
- @per_page = params[:per_page]
7
+
8
+ per_page_limit = Spree::Api::Config[:api_v2_per_page_limit]
9
+
10
+ @per_page = if params[:per_page].to_i.between?(1, per_page_limit)
11
+ params[:per_page]
12
+ else
13
+ Kaminari.config.default_per_page
14
+ end
8
15
  end
9
16
 
10
17
  def call
@@ -2,6 +2,7 @@ module Spree
2
2
  class VariantPresenter
3
3
  include Rails.application.routes.url_helpers
4
4
  include Spree::BaseHelper
5
+ include Spree::ProductsHelper
5
6
 
6
7
  attr_reader :current_currency, :current_price_options, :current_store
7
8
 
@@ -19,7 +20,7 @@ module Spree
19
20
  display_price: display_price(variant),
20
21
  price: variant.price_in(current_currency),
21
22
  display_compare_at_price: display_compare_at_price(variant),
22
- should_display_compare_at_price: should_display_compare_at_price(variant),
23
+ should_display_compare_at_price: should_display_compare_at_price?(variant),
23
24
  is_product_available_in_currency: @is_product_available_in_currency,
24
25
  backorderable: backorderable?(variant),
25
26
  in_stock: in_stock?(variant),
@@ -75,9 +76,5 @@ module Spree
75
76
  purchasable: variant.purchasable?
76
77
  }
77
78
  end
78
-
79
- def should_display_compare_at_price(variant)
80
- variant.compare_at_price.present? && variant.compare_at_price > variant.price
81
- end
82
79
  end
83
80
  end
@@ -0,0 +1,101 @@
1
+ require 'uri'
2
+
3
+ module Spree
4
+ class BuildLocalizedRedirectUrl
5
+ prepend Spree::ServiceModule::Base
6
+
7
+ LOCALE_REGEX = /^\/[A-Za-z]{2}\/|^\/[A-Za-z]{2}-[A-Za-z]{2}\/|^\/[A-Za-z]{2}$|^\/[A-Za-z]{2}-[A-Za-z]{2}$/.freeze
8
+
9
+ SUPPORTED_PATHS_REGEX = /\/(products|t\/|cart|checkout|addresses|content)/.freeze
10
+
11
+ # rubocop:disable Lint/UnusedMethodArgument
12
+ def call(url:, locale:, default_locale: nil)
13
+ run :initialize_url_object
14
+ run :generate_new_path
15
+ run :append_locale_param
16
+ run :build_url
17
+ end
18
+ # rubocop:enable Lint/UnusedMethodArgument
19
+
20
+ protected
21
+
22
+ def initialize_url_object(url:, locale:, default_locale:)
23
+ success(
24
+ url: URI(url),
25
+ locale: locale,
26
+ default_locale_supplied: default_locale_supplied?(locale, default_locale)
27
+ )
28
+ end
29
+
30
+ def generate_new_path(url:, locale:, default_locale_supplied:)
31
+ unless supported_path?(url.path)
32
+ return success(
33
+ url: url,
34
+ locale: locale,
35
+ path: cleanup_path(url.path),
36
+ default_locale_supplied: default_locale_supplied,
37
+ locale_added_to_path: false
38
+ )
39
+ end
40
+
41
+ new_path = if default_locale_supplied
42
+ maches_locale_regex?(url.path) ? url.path.gsub(LOCALE_REGEX, '/') : url.path
43
+ else
44
+ maches_locale_regex?(url.path) ? url.path.gsub(LOCALE_REGEX, "/#{locale}/") : "/#{locale}#{url.path}"
45
+ end
46
+
47
+ success(
48
+ url: url,
49
+ locale: locale,
50
+ path: cleanup_path(new_path),
51
+ default_locale_supplied: default_locale_supplied,
52
+ locale_added_to_path: true
53
+ )
54
+ end
55
+
56
+ def append_locale_param(url:, locale:, path:, default_locale_supplied:, locale_added_to_path:)
57
+ return success(url: url, path: path, query: url.query) if locale_added_to_path
58
+
59
+ query_params = Rack::Utils.parse_nested_query(url.query)
60
+
61
+ if default_locale_supplied
62
+ query_params.delete('locale')
63
+ else
64
+ query_params.merge!('locale' => locale)
65
+ end
66
+
67
+ query_string = query_params.any? ? query_params.to_query : nil
68
+
69
+ success(url: url, path: path, query: query_string)
70
+ end
71
+
72
+ def build_url(url:, path:, query:)
73
+ localized_url = builder_class(url).build(host: url.host, port: url.port, path: path, query: query).to_s
74
+ success(localized_url)
75
+ end
76
+
77
+ private
78
+
79
+ def supported_path?(path)
80
+ return true if path.blank? || path == '/' || maches_locale_regex?(path)
81
+
82
+ path.match(SUPPORTED_PATHS_REGEX)
83
+ end
84
+
85
+ def maches_locale_regex?(path)
86
+ path.match(LOCALE_REGEX)[0].gsub('/', '') if path.match(LOCALE_REGEX)
87
+ end
88
+
89
+ def default_locale_supplied?(locale, default_locale)
90
+ default_locale.present? && default_locale.to_s == locale.to_s
91
+ end
92
+
93
+ def cleanup_path(path)
94
+ path.chomp('/').gsub('//', '/')
95
+ end
96
+
97
+ def builder_class(url)
98
+ url.scheme == 'http' ? URI::HTTP : URI::HTTPS
99
+ end
100
+ end
101
+ end
@@ -24,7 +24,7 @@ module Spree
24
24
  if country_iso.present?
25
25
  ::Spree::Country.by_iso(country_iso)&.id
26
26
  else
27
- ::Spree::Country.default.id
27
+ Spree::Config[:default_country_id]
28
28
  end
29
29
  end
30
30
 
File without changes
File without changes
@@ -249,11 +249,9 @@
249
249
  color: #51545E;
250
250
  font-size: 15px;
251
251
  line-height: 18px;
252
- width: 62%;
253
252
  }
254
253
 
255
254
  .purchase_image {
256
- width: 18%;
257
255
  padding: 5px;
258
256
  }
259
257
 
@@ -289,10 +287,20 @@
289
287
  color: #333333;
290
288
  }
291
289
 
290
+ .purchase_total--name {
291
+ margin: 0;
292
+ text-align: right;
293
+ color: #333333;
294
+ }
295
+
292
296
  .purchase_total--label {
293
297
  padding: 0 15px 0 0;
294
298
  }
295
299
 
300
+ .purchase_total-col {
301
+ vertical-align: bottom;
302
+ }
303
+
296
304
  body {
297
305
  background-color: #F2F4F6;
298
306
  color: #51545E;
@@ -402,6 +410,9 @@
402
410
  .email-footer {
403
411
  width: 100% !important;
404
412
  }
413
+ .content-cell {
414
+ padding: 40px 15px !important;
415
+ }
405
416
  }
406
417
 
407
418
  @media (prefers-color-scheme: dark) {