spree_core 5.4.0.beta → 5.4.0.beta2

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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/app/finders/spree/products/find.rb +1 -28
  3. data/app/helpers/spree/base_helper.rb +1 -54
  4. data/app/mailers/spree/base_mailer.rb +4 -3
  5. data/app/models/concerns/spree/adjustment_source.rb +0 -8
  6. data/app/models/concerns/spree/admin_user_methods.rb +0 -2
  7. data/app/models/concerns/spree/image_methods.rb +4 -0
  8. data/app/models/concerns/spree/metadata.rb +10 -0
  9. data/app/models/concerns/spree/product_scopes.rb +2 -47
  10. data/app/models/concerns/spree/stores/markets.rb +7 -7
  11. data/app/models/concerns/spree/vat_price_calculation.rb +2 -2
  12. data/app/models/spree/ability.rb +5 -5
  13. data/app/models/spree/address.rb +3 -2
  14. data/app/models/spree/adjustable/promotion_accumulator.rb +1 -1
  15. data/app/models/spree/adjustment.rb +1 -20
  16. data/app/models/spree/api_key.rb +57 -2
  17. data/app/models/spree/country.rb +7 -3
  18. data/app/models/spree/credit_card.rb +0 -27
  19. data/app/models/spree/current.rb +38 -2
  20. data/app/models/spree/exports/products.rb +0 -6
  21. data/app/models/spree/gateway/bogus.rb +9 -9
  22. data/app/models/spree/gateway.rb +0 -3
  23. data/app/models/spree/gift_card_batch.rb +4 -0
  24. data/app/models/spree/image/configuration/active_storage.rb +0 -2
  25. data/app/models/spree/image.rb +7 -31
  26. data/app/models/spree/log_entry.rb +8 -5
  27. data/app/models/spree/order/checkout.rb +0 -18
  28. data/app/models/spree/order.rb +20 -11
  29. data/app/models/spree/payment/gateway_options.rb +2 -2
  30. data/app/models/spree/payment/processing.rb +5 -5
  31. data/app/models/spree/payment.rb +1 -1
  32. data/app/models/spree/payment_connection_error.rb +3 -0
  33. data/app/models/spree/payment_method/check.rb +1 -1
  34. data/app/models/spree/payment_method/store_credit.rb +5 -5
  35. data/app/models/spree/payment_response.rb +70 -0
  36. data/app/models/spree/permission_sets/default_customer.rb +0 -5
  37. data/app/models/spree/permission_sets/product_display.rb +0 -2
  38. data/app/models/spree/permission_sets/product_management.rb +0 -2
  39. data/app/models/spree/product.rb +4 -72
  40. data/app/models/spree/promotion.rb +2 -14
  41. data/app/models/spree/promotion_handler/coupon.rb +3 -3
  42. data/app/models/spree/prototype.rb +0 -3
  43. data/app/models/spree/refund.rb +6 -2
  44. data/app/models/spree/shipment/emails.rb +1 -0
  45. data/app/models/spree/shipping_category.rb +3 -3
  46. data/app/models/spree/store.rb +10 -49
  47. data/app/models/spree/store_credit.rb +4 -0
  48. data/app/models/spree/tax_category.rb +1 -11
  49. data/app/models/spree/taxon.rb +0 -16
  50. data/app/models/spree/variant.rb +2 -40
  51. data/app/models/spree/zone.rb +1 -4
  52. data/app/services/spree/cart/add_item.rb +8 -4
  53. data/app/services/spree/cart/create.rb +13 -2
  54. data/app/services/spree/products/duplicator.rb +0 -12
  55. data/app/services/spree/products/prepare_nested_attributes.rb +0 -12
  56. data/config/locales/en.yml +3 -20
  57. data/db/migrate/20210914000000_spree_four_three.rb +0 -38
  58. data/db/migrate/20210915064329_add_metadata_to_spree_multiple_tables.rb +0 -1
  59. data/db/migrate/20260226000000_add_locale_to_spree_orders.rb +5 -0
  60. data/db/migrate/20260226100000_add_token_digest_to_spree_api_keys.rb +21 -0
  61. data/lib/generators/spree/cursor_rules/templates/spree_rules.mdc +1 -3
  62. data/lib/spree/core/configuration.rb +0 -3
  63. data/lib/spree/core/controller_helpers/strong_parameters.rb +1 -2
  64. data/lib/spree/core/dependencies.rb +2 -10
  65. data/lib/spree/core/engine.rb +0 -13
  66. data/lib/spree/core/pricing/resolver.rb +1 -9
  67. data/lib/spree/core/version.rb +1 -1
  68. data/lib/spree/core.rb +10 -25
  69. data/lib/spree/permitted_attributes.rb +2 -14
  70. data/lib/spree/testing_support/factories/order_factory.rb +1 -0
  71. data/lib/spree/testing_support/factories/payment_method_factory.rb +1 -6
  72. data/lib/spree/testing_support/factories/product_factory.rb +0 -7
  73. data/lib/spree/testing_support/factories/prototype_factory.rb +0 -2
  74. data/lib/tasks/core.rake +0 -265
  75. metadata +8 -101
  76. data/app/finders/spree/posts/find.rb +0 -137
  77. data/app/finders/spree/product_properties/find_available.rb +0 -20
  78. data/app/helpers/spree/mail_helper.rb +0 -27
  79. data/app/jobs/spree/api_key_touch_job.rb +0 -9
  80. data/app/mailers/spree/test_mailer.rb +0 -8
  81. data/app/models/action_text/video_embed.rb +0 -13
  82. data/app/models/concerns/spree/has_one_link.rb +0 -42
  83. data/app/models/concerns/spree/linkable.rb +0 -9
  84. data/app/models/concerns/spree/previewable.rb +0 -17
  85. data/app/models/spree/data_feed/google.rb +0 -15
  86. data/app/models/spree/data_feed.rb +0 -42
  87. data/app/models/spree/gateway/bogus_simple.rb +0 -24
  88. data/app/models/spree/post.rb +0 -108
  89. data/app/models/spree/post_category.rb +0 -46
  90. data/app/models/spree/product_property.rb +0 -51
  91. data/app/models/spree/property.rb +0 -86
  92. data/app/models/spree/property_prototype.rb +0 -9
  93. data/app/models/spree/store_favicon_image.rb +0 -20
  94. data/app/models/spree/store_logo.rb +0 -4
  95. data/app/models/spree/store_mailer_logo.rb +0 -7
  96. data/app/models/spree/taxon_image/configuration/active_storage.rb +0 -26
  97. data/app/models/spree/taxon_image.rb +0 -28
  98. data/app/presenters/spree/filters/properties_presenter.rb +0 -23
  99. data/app/presenters/spree/filters/property_presenter.rb +0 -42
  100. data/app/services/spree/data_feeds/google/optional_attributes.rb +0 -23
  101. data/app/services/spree/data_feeds/google/optional_sub_attributes.rb +0 -21
  102. data/app/services/spree/data_feeds/google/products_list.rb +0 -14
  103. data/app/services/spree/data_feeds/google/required_attributes.rb +0 -68
  104. data/app/services/spree/data_feeds/google/rss.rb +0 -109
  105. data/app/sorters/spree/posts/sort.rb +0 -40
  106. data/app/views/action_text/video_embeds/_thumbnail.html.erb +0 -1
  107. data/app/views/action_text/video_embeds/_video_embed.html.erb +0 -3
  108. data/app/views/layouts/action_text/contents/_content.html.erb +0 -3
  109. data/app/views/spree/test_mailer/test_email.html.erb +0 -40
  110. data/app/views/spree/test_mailer/test_email.text.erb +0 -4
  111. data/config/initializers/oembed.rb +0 -1
  112. data/db/migrate/20221229132350_create_spree_data_feed_settings.rb +0 -14
  113. data/db/migrate/20230109084253_create_product_property_translations.rb +0 -24
  114. data/db/migrate/20230109105943_create_property_translations.rb +0 -24
  115. data/db/migrate/20230415155958_rename_data_feed_settings_table.rb +0 -5
  116. data/db/migrate/20230415160828_rename_data_feed_table_columns.rb +0 -7
  117. data/db/migrate/20230415161226_add_indexes_to_data_feeds_table.rb +0 -5
  118. data/db/migrate/20230512094803_rename_data_feeds_column_provider_to_type.rb +0 -5
  119. data/db/migrate/20240914153106_add_display_on_to_spree_properties.rb +0 -5
  120. data/db/migrate/20240915144935_add_position_to_spree_properties.rb +0 -6
  121. data/db/migrate/20250121160028_create_spree_posts_and_spree_post_categories.rb +0 -33
  122. data/db/migrate/20250127083740_add_kind_to_spree_properties.rb +0 -5
  123. data/db/migrate/20250217171018_create_action_text_video_embeds.rb +0 -11
  124. data/db/migrate/20250305121657_remove_spree_posts_indices.rb +0 -7
  125. data/db/migrate/20250730154601_add_unique_index_on_spree_properties_name.rb +0 -24
  126. data/db/sample_data/posts.rb +0 -7
  127. data/lib/spree/core/controller_helpers/search.rb +0 -17
  128. data/lib/spree/core/product_filters.rb +0 -218
  129. data/lib/spree/core/query_filters/comparable.rb +0 -50
  130. data/lib/spree/core/query_filters/date.rb +0 -8
  131. data/lib/spree/core/query_filters/number.rb +0 -8
  132. data/lib/spree/core/query_filters/text.rb +0 -36
  133. data/lib/spree/core/query_filters.rb +0 -11
  134. data/lib/spree/core/search/base.rb +0 -144
  135. data/lib/spree/testing_support/factories/favicon_image_factory.rb +0 -9
  136. data/lib/spree/testing_support/factories/google_data_feed_factory.rb +0 -7
  137. data/lib/spree/testing_support/factories/post_category_factory.rb +0 -7
  138. data/lib/spree/testing_support/factories/post_factory.rb +0 -22
  139. data/lib/spree/testing_support/factories/product_property_factory.rb +0 -7
  140. data/lib/spree/testing_support/factories/property_factory.rb +0 -28
  141. data/lib/spree/testing_support/factories/taxon_image_factory.rb +0 -9
  142. data/lib/spree/testing_support/flash.rb +0 -25
  143. data/lib/spree/testing_support/flatpickr_capybara.rb +0 -124
  144. data/lib/tasks/exchanges.rake +0 -66
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6991203c2954ac7663c4dfadd8362bee850faf1a1888166cd9c7ed0100167398
4
- data.tar.gz: ec6feab9591d268f2a6f3efd7d5a1557e383e600fb32088c3e09e03abd937ba1
3
+ metadata.gz: af6f55a25483bfb02dab0b15f67356a2732cbeb190074b9ddb75a1692594fb7a
4
+ data.tar.gz: 8f0b8f77983432ef1e1baa52def6ed2674f9e0794bc632dd91757551c4ee71c4
5
5
  SHA512:
6
- metadata.gz: 2a58de3390d7466d547af3ff46f6d661776e6b4c9790d02da9d7d90c0b96883647dcea51fb27f75110e1083c8e9dc7075b6bbee49499bfd189ce39bb7235362d
7
- data.tar.gz: 37fcc6d23c1e9ebf36314be3b85aea5c612eb7eb75a5fa0c2bd10f4c18ff1877298554f45fa80f8d6b841670e5004c1d953df068bebc2a45b59c4495edc10c2e
6
+ metadata.gz: 46c6fc812dc90ba1ae3061b2e4066df5e9dd8ad530fde2db85095a7b5cb444fec2d51f4736a528c61ee4423e64d986478fdcf5478c40888e99818b849e444d60
7
+ data.tar.gz: aa2913c4e9882dd0e77d03ddcfae4f8277e4657b807080ce2d3549fd63f5c6da18448e2574b7742ad82b227a299dccfce3717a7df504be4442e3c7f0bf21629e
@@ -19,7 +19,6 @@ module Spree
19
19
  @sort_by = params.dig(:sort_by)
20
20
  @deleted = params.dig(:filter, :show_deleted)
21
21
  @discontinued = params.dig(:filter, :show_discontinued)
22
- @properties = params.dig(:filter, :properties)
23
22
  @in_stock = params.dig(:filter, :in_stock)
24
23
  @backorderable = params.dig(:filter, :backorderable)
25
24
  @purchasable = params.dig(:filter, :purchasable)
@@ -46,7 +45,6 @@ module Spree
46
45
  products = by_slug(products)
47
46
  products = by_options(products)
48
47
  products = by_option_value_ids(products)
49
- products = by_properties(products)
50
48
  products = by_tags(products)
51
49
  products = include_deleted(products)
52
50
  products = show_only_stock(products)
@@ -63,7 +61,7 @@ module Spree
63
61
  private
64
62
 
65
63
  attr_reader :ids, :skus, :price, :currency, :taxons, :concat_taxons, :name, :options, :option_value_ids, :scope,
66
- :sort_by, :deleted, :discontinued, :properties, :store, :in_stock, :backorderable, :purchasable, :tags,
64
+ :sort_by, :deleted, :discontinued, :store, :in_stock, :backorderable, :purchasable, :tags,
67
65
  :query, :vendor_ids, :out_of_stock, :slug, :taxonomies
68
66
 
69
67
  def query?
@@ -114,10 +112,6 @@ module Spree
114
112
  sort_by.present?
115
113
  end
116
114
 
117
- def properties?
118
- properties.present? && properties.values.reject(&:empty?).present?
119
- end
120
-
121
115
  def vendor_ids?
122
116
  vendor_ids.present?
123
117
  end
@@ -224,27 +218,6 @@ module Spree
224
218
  products.where(id: product_ids)
225
219
  end
226
220
 
227
- def by_properties(products)
228
- return products unless properties?
229
-
230
- product_ids = []
231
- index = 0
232
-
233
- properties.to_unsafe_hash.each do |property_filter_param, product_properties_values|
234
- next if property_filter_param.blank? || product_properties_values.empty?
235
-
236
- values = product_properties_values.split(',').reject(&:empty?).uniq.map(&:parameterize)
237
-
238
- next if values.empty?
239
-
240
- ids = scope.unscope(:order, :includes).with_property_values(property_filter_param, values).ids
241
- product_ids = index == 0 ? ids : product_ids & ids
242
- index += 1
243
- end
244
-
245
- products.where(id: product_ids)
246
- end
247
-
248
221
  def by_tags(products)
249
222
  return products if tags.empty?
250
223
 
@@ -68,30 +68,10 @@ module Spree
68
68
  end
69
69
  end
70
70
 
71
- def spree_favicon_path
72
- Spree::Deprecation.warn('BaseHelper#spree_favicon_path is deprecated and will be removed in Spree 5.5. Please use Active Storage URL helpers instead.')
73
-
74
- if current_store.favicon.present?
75
- main_app.cdn_image_url(current_store.favicon)
76
- else
77
- url_for('favicon.ico')
78
- end
79
- end
80
-
81
71
  def object
82
72
  instance_variable_get('@' + controller_name.singularize)
83
73
  end
84
74
 
85
- def method_missing(method_name, *args, &block)
86
- if image_style = image_style_from_method_name(method_name)
87
- Spree::Deprecation.warn("#{BaseHelper.name}##{method_name} is deprecated and will be removed in Spree 5.5. Please use spree_image_tag instead - https://spreecommerce.org/docs/developer/core-concepts/images-assets#preprocessed-named-variants")
88
- define_image_method(image_style)
89
- send(method_name, *args)
90
- else
91
- super
92
- end
93
- end
94
-
95
75
  def pretty_time(time)
96
76
  return '' if time.blank?
97
77
 
@@ -119,7 +99,7 @@ module Spree
119
99
  end
120
100
 
121
101
  # returns the URL of an object on the storefront
122
- # @param resource [Spree::Product, Spree::Post, Spree::Taxon, Spree::Page] the resource to get the URL for
102
+ # @param resource [Spree::Product, Spree::Taxon, Spree::Page] the resource to get the URL for
123
103
  # @param options [Hash] the options for the URL
124
104
  # @option options [String] :locale the locale of the resource, defaults to I18n.locale
125
105
  # @option options [String] :store the store of the resource, defaults to current_store
@@ -155,9 +135,6 @@ module Spree
155
135
  params = "?#{params}" if params.present?
156
136
 
157
137
  "#{base_url + localize}/products/#{resource.slug}#{params}"
158
- elsif defined?(Spree::Post) && resource.is_a?(Spree::Post)
159
- preview_id = options[:preview_id].present? ? "?preview_id=#{options[:preview_id]}" : ''
160
- "#{base_url + localize}/posts/#{resource.slug}#{preview_id}"
161
138
  elsif resource.is_a?(Spree::Taxon)
162
139
  "#{base_url + localize}/t/#{resource.permalink}"
163
140
  elsif defined?(Spree::Page) && (resource.is_a?(Spree::Page) || resource.is_a?(Spree::Policy))
@@ -221,36 +198,6 @@ module Spree
221
198
 
222
199
  private
223
200
 
224
- def create_product_image_tag(image, product, options, style)
225
- options[:alt] = image.alt.blank? ? product.name : image.alt
226
- image_tag main_app.cdn_image_url(image.url(style)), options
227
- end
228
-
229
- def define_image_method(style)
230
- self.class.send :define_method, "#{style}_image" do |product, *options|
231
- options = options.first || {}
232
- options[:alt] ||= product.name
233
- image_path = default_image_for_product_or_variant(product)
234
- img = if image_path.present?
235
- create_product_image_tag image_path, product, options, style
236
- else
237
- width = style.to_s.split('x').first.to_i
238
- height = style.to_s.split('x').last.to_i
239
- content_tag(:div, width: width, height: height, style: "background-color: #f0f0f0;")
240
- end
241
-
242
- content_tag(:div, img, class: "admin-product-image-container #{style}-img")
243
- end
244
- end
245
-
246
- # Returns style of image or nil
247
- def image_style_from_method_name(method_name)
248
- style = method_name.to_s.sub(/_image$/, '')
249
- if method_name.to_s.match(/_image$/) && Spree::Image.styles.keys.map(&:to_s).include?(style)
250
- style
251
- end
252
- end
253
-
254
201
  I18N_PLURAL_MANY_COUNT = 2.1
255
202
  def plural_resource_name(resource_class)
256
203
  resource_class.model_name.human(count: I18N_PLURAL_MANY_COUNT)
@@ -1,7 +1,5 @@
1
1
  module Spree
2
2
  class BaseMailer < ActionMailer::Base
3
- helper Spree::MailHelper
4
-
5
3
  def current_store
6
4
  @current_store ||= @order&.store.presence || Spree::Store.current || Spree::Store.default
7
5
  end
@@ -47,8 +45,11 @@ module Spree
47
45
  ActionMailer::Base.default_url_options[:host] = host_url
48
46
  end
49
47
 
48
+ # Sets the I18n locale for the email.
49
+ # Prefers the order's locale (the language the customer used),
50
+ # falls back to the store's default locale.
50
51
  def set_email_locale
51
- locale = @order&.store&.default_locale || current_store&.default_locale
52
+ locale = @order&.locale.presence || @order&.store&.default_locale || current_store&.default_locale
52
53
  I18n.locale = locale if locale.present?
53
54
  end
54
55
  end
@@ -5,7 +5,6 @@ module Spree
5
5
  included do
6
6
  has_many :adjustments, as: :source
7
7
  before_destroy :deals_with_adjustments_for_deleted_source
8
- after_commit :clear_adjustment_source_cache
9
8
  end
10
9
 
11
10
  protected
@@ -55,12 +54,5 @@ module Spree
55
54
  # This would mean that the order's total is not altered at all.
56
55
  adjustments.for_complete_order.update_all(source_id: nil, updated_at: Time.current)
57
56
  end
58
-
59
- def clear_adjustment_source_cache
60
- # Use base_class.name to match the polymorphic_name stored in source_type
61
- # For STI models like PromotionAction subclasses, this ensures cache key consistency
62
- cache_class_name = self.class.polymorphic_name
63
- Rails.cache.delete("spree/adjustment_source/#{cache_class_name}/#{id}")
64
- end
65
57
  end
66
58
  end
@@ -22,7 +22,6 @@ module Spree
22
22
  has_many :created_gift_card_batches, class_name: 'Spree::GiftCardBatch', foreign_key: :created_by_id
23
23
  has_many :refunded_refunds, class_name: 'Spree::Refund', foreign_key: :refunder_id
24
24
  has_many :performed_reimbursements, class_name: 'Spree::Reimbursement', foreign_key: :performed_by_id
25
- has_many :authored_posts, class_name: 'Spree::Post', foreign_key: :author_id
26
25
  has_many :created_store_credits, class_name: 'Spree::StoreCredit', foreign_key: :created_by_id
27
26
  has_many :reports, class_name: 'Spree::Report', foreign_key: :user_id
28
27
  has_many :exports, class_name: 'Spree::Export', foreign_key: :user_id
@@ -73,7 +72,6 @@ module Spree
73
72
  created_gift_card_batches.update_all(created_by_id: nil, updated_at: Time.current)
74
73
  refunded_refunds.update_all(refunder_id: nil, updated_at: Time.current)
75
74
  performed_reimbursements.update_all(performed_by_id: nil, updated_at: Time.current)
76
- authored_posts.update_all(author_id: nil, updated_at: Time.current)
77
75
  created_store_credits.update_all(created_by_id: nil, updated_at: Time.current)
78
76
 
79
77
  # resources to destroy
@@ -3,6 +3,8 @@ module Spree
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  def generate_url(size:, gravity: 'centre', quality: 80, background: [0, 0, 0])
6
+ Spree::Deprecation.warn("ImageMethods#generate_url is deprecated and will be removed in Spree 6.0. Please use active storage variants with cdn_image_url")
7
+
6
8
  return if size.blank?
7
9
 
8
10
  size = size.gsub(/\s+/, '')
@@ -18,6 +20,8 @@ module Spree
18
20
  end
19
21
 
20
22
  def original_url
23
+ Spree::Deprecation.warn("ImageMethods#original_url is deprecated and will be removed in Spree 6.0. Please use active storage variants with cdn_image_url")
24
+
21
25
  cdn_image_url(attachment)
22
26
  end
23
27
 
@@ -12,6 +12,16 @@ module Spree
12
12
  serialize :private_metadata, coder: HashSerializer
13
13
  end
14
14
 
15
+ # `metadata` is the primary API-facing accessor.
16
+ # It maps to `private_metadata` under the hood (Stripe-style: write-only, never returned in Store API).
17
+ def metadata
18
+ private_metadata
19
+ end
20
+
21
+ def metadata=(value)
22
+ self.private_metadata = value
23
+ end
24
+
15
25
  # https://nandovieira.com/using-postgresql-and-jsonb-with-ruby-on-rails
16
26
  class HashSerializer
17
27
  def self.dump(hash)
@@ -31,21 +31,6 @@ module Spree
31
31
  end
32
32
  end
33
33
 
34
- def self.property_conditions(property)
35
- properties_table = Property.table_name
36
-
37
- case property
38
- when Property then { "#{properties_table}.id" => property.id }
39
- when Integer then { "#{properties_table}.id" => property }
40
- else
41
- if Property.column_for_attribute('id').type == :uuid
42
- ["#{properties_table}.name = ? OR #{properties_table}.id = ?", property, property]
43
- else
44
- { "#{properties_table}.name" => property }
45
- end
46
- end
47
- end
48
-
49
34
  add_simple_scopes simple_scopes
50
35
 
51
36
  add_search_scope :ascend_by_master_price do
@@ -149,33 +134,6 @@ module Spree
149
134
  order(Arel.sql("#{min_position_sql} ASC"))
150
135
  end
151
136
 
152
- # a scope that finds all products having property specified by name, object or id
153
- add_search_scope :with_property do |property|
154
- joins(:properties).where(property_conditions(property))
155
- end
156
-
157
- # a simple test for product with a certain property-value pairing
158
- # note that it can test for properties with NULL values, but not for absent values
159
- add_search_scope :with_property_value do |property, value|
160
- if Spree.use_translations?
161
- joins(:properties).
162
- join_translation_table(Property).
163
- join_translation_table(ProductProperty).
164
- where(ProductProperty.translation_table_alias => { value: value }).
165
- where(property_conditions(property))
166
- else
167
- joins(:properties).
168
- where(ProductProperty.table_name => { value: value }).
169
- where(property_conditions(property))
170
- end
171
- end
172
-
173
- add_search_scope :with_property_values do |property_filter_param, property_values|
174
- joins(product_properties: :property).
175
- where(Property.table_name => { filter_param: property_filter_param }).
176
- where(ProductProperty.table_name => { filter_param: property_values.map(&:parameterize) })
177
- end
178
-
179
137
  add_search_scope :with_option do |option|
180
138
  if option.is_a?(OptionType)
181
139
  joins(:option_types).where(spree_option_types: { id: option.id })
@@ -226,13 +184,10 @@ module Spree
226
184
  where(Spree::OptionValue.table_name => { id: actual_ids })
227
185
  end
228
186
 
229
- # Finds all products which have either:
230
- # 1) have an option value with the name matching the one given
231
- # 2) have a product property with a value matching the one given
187
+ # Finds all products which have an option value with the name matching the one given
232
188
  add_search_scope :with do |value|
233
189
  includes(variants: :option_values).
234
- includes(:product_properties).
235
- where("#{OptionValue.table_name}.name = ? OR #{ProductProperty.table_name}.value = ?", value, value)
190
+ where("#{OptionValue.table_name}.name = ?", value)
236
191
  end
237
192
 
238
193
  # Finds all products that have a name containing the given words.
@@ -66,15 +66,15 @@ module Spree
66
66
  # @return [ActiveRecord::Relation<Spree::Country>]
67
67
  def countries_from_markets
68
68
  Spree::Country
69
- .where(id: Spree::Country.joins(market_countries: :market).where(Spree::Market.table_name => { store_id: id, deleted_at: nil }).select(:id))
70
- .order(:name)
69
+ .where(id: Spree::Country.joins(market_countries: :market).where(Spree::Market.table_name => { store_id: id, deleted_at: nil }).select(:id))
70
+ .order(:name)
71
71
  end
72
72
 
73
73
  # Returns the countries available for checkout, derived from markets
74
74
  # @return [Array<Spree::Country>]
75
75
  def countries_available_for_checkout
76
- @countries_available_for_checkout ||= Rails.cache.fetch(countries_available_for_checkout_cache_key) do
77
- if markets.any?
76
+ @countries_available_for_checkout = begin
77
+ if has_markets?
78
78
  markets.flat_map(&:countries).uniq.sort_by(&:name)
79
79
  else
80
80
  Spree::Country.all.to_a
@@ -85,7 +85,7 @@ module Spree
85
85
  # Returns supported currencies derived from markets, falling back to store attributes
86
86
  # @return [Array<Money::Currency>]
87
87
  def supported_currencies_list
88
- @supported_currencies_list ||= if markets.any?
88
+ @supported_currencies_list ||= if has_markets?
89
89
  markets.pluck(:currency).uniq.map do |code|
90
90
  ::Money::Currency.find(code)
91
91
  end.compact.sort_by { |c| c.iso_code == default_currency ? 0 : 1 }
@@ -97,7 +97,7 @@ module Spree
97
97
  # Returns supported locales derived from markets, falling back to store attributes
98
98
  # @return [Array<String>]
99
99
  def supported_locales_list
100
- @supported_locales_list ||= if markets.any?
100
+ @supported_locales_list ||= if has_markets?
101
101
  (markets.flat_map(&:supported_locales_list) << default_locale).compact.uniq.sort
102
102
  else
103
103
  legacy_supported_locales_list
@@ -107,7 +107,7 @@ module Spree
107
107
  private
108
108
 
109
109
  def has_markets?
110
- persisted? && (markets.loaded? ? markets.any? : markets.exists?)
110
+ @has_markets ||= persisted? && (markets.loaded? ? markets.any? : markets.exists?)
111
111
  end
112
112
 
113
113
  def legacy_supported_currencies_list
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  module VatPriceCalculation
3
3
  def gross_amount(amount, price_options)
4
- return amount unless outside_default_vat_zone?(price_options)
4
+ return amount if amount.nil? || !outside_default_vat_zone?(price_options)
5
5
 
6
6
  round_to_two_places(add_foreign_vat_for(amount, price_options))
7
7
  end
@@ -38,7 +38,7 @@ module Spree
38
38
  end
39
39
 
40
40
  def default_zone
41
- @_default_zone ||= Spree::Zone.default_tax
41
+ @default_zone ||= Spree::Zone.default_tax
42
42
  end
43
43
 
44
44
  def round_to_two_places(amount)
@@ -59,6 +59,8 @@ module Spree
59
59
  # Include any abilities registered by extensions, etc.
60
60
  # this is legacy behaviour and should be removed in Spree 5.0
61
61
  Ability.abilities.merge(abilities_to_register).each do |clazz|
62
+ Spree::Deprecation.warn("Ability merging is deprecated and will be removed in Spree 5.5. Please use Permission Sets")
63
+
62
64
  merge clazz.new(@user)
63
65
  end
64
66
  end
@@ -133,6 +135,7 @@ module Spree
133
135
  end
134
136
 
135
137
  def apply_admin_permissions(_user, _options)
138
+ Spree::Deprecation.warn("Ability#apply_admin_permissions is deprecated and will be removed in Spree 5.5. Please use Permission Sets")
136
139
  can :manage, :all
137
140
  cannot :cancel, Spree::Order
138
141
  can :cancel, Spree::Order, &:allow_cancel?
@@ -143,6 +146,8 @@ module Spree
143
146
  end
144
147
 
145
148
  def apply_user_permissions(user, _options)
149
+ Spree::Deprecation.warn("Ability#apply_user_permissions is deprecated and will be removed in Spree 5.5. Please use Permission Sets")
150
+
146
151
  can :read, ::Spree::Country
147
152
  can :read, ::Spree::OptionType
148
153
  can :read, ::Spree::OptionValue
@@ -157,8 +162,6 @@ module Spree
157
162
  can :manage, ::Spree::Address, user_id: user.id if user.persisted?
158
163
  can [:read, :destroy], ::Spree::CreditCard, user_id: user.id
159
164
  can :read, ::Spree::Product
160
- can :read, ::Spree::ProductProperty
161
- can :read, ::Spree::Property
162
165
  can :create, ::Spree.user_class
163
166
  can [:show, :update, :destroy], ::Spree.user_class, id: user.id
164
167
  can :read, ::Spree::State
@@ -179,9 +182,6 @@ module Spree
179
182
  digital_link.token == token
180
183
  end
181
184
  can :read, ::Spree::Policy
182
- can :read, ::Spree::Page if defined?(Spree::Page)
183
- can :read, ::Spree::Post if defined?(Spree::Post)
184
- can :read, ::Spree::PostCategory if defined?(Spree::PostCategory)
185
185
  end
186
186
 
187
187
  def protect_admin_role
@@ -175,8 +175,8 @@ module Spree
175
175
  attributes.except('id', 'created_at', 'updated_at', 'country_id').all? { |_, v| v.nil? }
176
176
  end
177
177
 
178
- # Generates an ActiveMerchant compatible address hash
179
- def active_merchant_hash
178
+ # Generates an address hash for payment gateway options
179
+ def gateway_hash
180
180
  {
181
181
  name: full_name,
182
182
  address1: address1,
@@ -188,6 +188,7 @@ module Spree
188
188
  phone: phone
189
189
  }
190
190
  end
191
+ alias_method :active_merchant_hash, :gateway_hash
191
192
 
192
193
  def require_phone?
193
194
  # We want to collect phone number for quick checkout but not to validate it
@@ -25,7 +25,7 @@ module Spree
25
25
  def add_adjustment(adjustment, opts = {})
26
26
  return unless adjustment.promotion?
27
27
 
28
- source = opts[:source] || adjustment.cached_source
28
+ source = opts[:source] || adjustment.source
29
29
  promotion = opts[:promotion] || source.promotion
30
30
 
31
31
  add(adjustments, adjustment, adjustment.id)
@@ -98,30 +98,11 @@ module Spree
98
98
  !included?
99
99
  end
100
100
 
101
- # Returns the source using Rails.cache to avoid repeated database lookups.
102
- # Sources are cached by their type and ID combination.
103
- # Cache is automatically invalidated when the source is saved (see AdjustmentSource concern).
104
- #
105
- # @return [Object, nil] The source object (TaxRate, PromotionAction, etc.)
106
- def cached_source
107
- return nil if source_type.blank? || source_id.blank?
108
-
109
- Rails.cache.fetch(source_cache_key) { source }
110
- rescue TypeError
111
- # Handle objects that can't be serialized (e.g., mock objects in tests)
112
- source
113
- end
114
-
115
- # Cache key for the source object
116
- def source_cache_key
117
- "spree/adjustment_source/#{source_type}/#{source_id}"
118
- end
119
-
120
101
  # Passing a target here would always be recommended as it would avoid
121
102
  # hitting the database again and would ensure you're compute values over
122
103
  # the specific object amount passed here.
123
104
  def update!(target = adjustable)
124
- src = cached_source
105
+ src = source
125
106
  return amount if closed? || src.blank?
126
107
 
127
108
  new_amount = src.compute_amount(target)
@@ -6,13 +6,21 @@ module Spree
6
6
  PREFIXES = { 'publishable' => 'pk_', 'secret' => 'sk_' }.freeze
7
7
  TOKEN_LENGTH = 24
8
8
 
9
+ # @!attribute [r] plaintext_token
10
+ # The raw token value, only available in memory immediately after creation
11
+ # of a secret key. Not persisted to the database.
12
+ # @return [String, nil]
13
+ attr_reader :plaintext_token
14
+
9
15
  belongs_to :store, class_name: 'Spree::Store'
10
16
  belongs_to :created_by, polymorphic: true, optional: true
11
17
  belongs_to :revoked_by, polymorphic: true, optional: true
12
18
 
13
19
  validates :name, presence: true
14
20
  validates :key_type, presence: true, inclusion: { in: KEY_TYPES }
15
- validates :token, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }
21
+ validates :token, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }, if: :publishable?
22
+ validates :token_digest, presence: true, uniqueness: true, if: :secret?
23
+ validates :token_prefix, presence: true, if: :secret?
16
24
  validates :store, presence: true
17
25
 
18
26
  before_validation :generate_token, on: :create
@@ -22,26 +30,73 @@ module Spree
22
30
  scope :publishable, -> { where(key_type: 'publishable') }
23
31
  scope :secret, -> { where(key_type: 'secret') }
24
32
 
33
+ # Finds an active secret API key by computing the HMAC-SHA256 digest
34
+ # of the provided plaintext token and looking up by +token_digest+.
35
+ #
36
+ # @param plaintext [String] the raw secret key (e.g. "sk_abc123...")
37
+ # @return [Spree::ApiKey, nil] the matching active secret key, or nil
38
+ def self.find_by_secret_token(plaintext)
39
+ return nil if plaintext.blank?
40
+
41
+ digest = compute_token_digest(plaintext)
42
+ active.secret.find_by(token_digest: digest)
43
+ end
44
+
45
+ # Computes the HMAC-SHA256 hex digest for a given plaintext token.
46
+ #
47
+ # @param plaintext [String] the raw token value
48
+ # @return [String] the hex-encoded HMAC-SHA256 digest
49
+ def self.compute_token_digest(plaintext)
50
+ OpenSSL::HMAC.hexdigest('SHA256', hmac_secret, plaintext)
51
+ end
52
+
53
+ # Returns the HMAC secret used for token hashing.
54
+ #
55
+ # @return [String] the application's secret key base
56
+ def self.hmac_secret
57
+ Rails.application.secret_key_base
58
+ end
59
+
60
+ # @return [Boolean] whether this is a publishable (Store API) key
25
61
  def publishable?
26
62
  key_type == 'publishable'
27
63
  end
28
64
 
65
+ # @return [Boolean] whether this is a secret (Admin API) key
29
66
  def secret?
30
67
  key_type == 'secret'
31
68
  end
32
69
 
70
+ # @return [Boolean] whether this key has not been revoked
33
71
  def active?
34
72
  revoked_at.nil?
35
73
  end
36
74
 
75
+ # Revokes this API key by setting +revoked_at+ to the current time.
76
+ #
77
+ # @param user [Object, nil] the user who performed the revocation
78
+ # @return [Boolean] true if the update succeeded
37
79
  def revoke!(user = nil)
38
80
  update!(revoked_at: Time.current, revoked_by: user)
39
81
  end
40
82
 
41
83
  private
42
84
 
85
+ # Generates the token on creation. For publishable keys, stores the raw token
86
+ # in the +token+ column. For secret keys, computes an HMAC-SHA256 digest stored
87
+ # in +token_digest+, saves the first 12 characters as +token_prefix+ for display,
88
+ # and exposes the raw value via {#plaintext_token} (available only in memory).
43
89
  def generate_token
44
- self.token ||= "#{PREFIXES[key_type]}#{SecureRandom.base58(TOKEN_LENGTH)}"
90
+ raw_token = "#{PREFIXES[key_type]}#{SecureRandom.base58(TOKEN_LENGTH)}"
91
+
92
+ if secret?
93
+ @plaintext_token = raw_token
94
+ self.token_prefix = raw_token[0, 12]
95
+ self.token_digest = self.class.compute_token_digest(raw_token)
96
+ self.token = nil
97
+ else
98
+ self.token ||= raw_token
99
+ end
45
100
  end
46
101
  end
47
102
  end
@@ -43,7 +43,7 @@ module Spree
43
43
  #
44
44
  # @return [String, nil] currency code (e.g., 'USD', 'EUR') or nil if no market found
45
45
  def market_currency
46
- Spree::Current.store&.market_for_country(self)&.currency
46
+ current_market&.currency
47
47
  end
48
48
 
49
49
  # Returns the default locale for this country from its market in the current store.
@@ -51,14 +51,18 @@ module Spree
51
51
  #
52
52
  # @return [String, nil] locale code (e.g., 'en', 'de') or nil if no market found
53
53
  def market_locale
54
- Spree::Current.store&.market_for_country(self)&.default_locale
54
+ current_market&.default_locale
55
55
  end
56
56
 
57
57
  # Returns the supported locales for this country from its market in the current store.
58
58
  #
59
59
  # @return [Array<String>] locale codes (e.g., ['en', 'fr']) or empty array if no market found
60
60
  def market_supported_locales
61
- Spree::Current.store&.market_for_country(self)&.supported_locales_list || []
61
+ current_market&.supported_locales_list || []
62
+ end
63
+
64
+ def current_market
65
+ @current_market ||= Spree::Current.store&.market_for_country(self)
62
66
  end
63
67
 
64
68
  def <=>(other)