spree_core 4.2.0.rc3 → 4.2.2

Sign up to get free protection for your applications and to get access to all the features.
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) {