spree_core 5.4.0.beta → 5.4.0.beta3

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 (161) hide show
  1. checksums.yaml +4 -4
  2. data/app/finders/spree/products/find.rb +2 -42
  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 +21 -49
  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 +60 -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/market.rb +1 -2
  28. data/app/models/spree/market_country.rb +17 -0
  29. data/app/models/spree/newsletter_subscriber.rb +0 -3
  30. data/app/models/spree/order/checkout.rb +0 -18
  31. data/app/models/spree/order.rb +22 -16
  32. data/app/models/spree/payment/gateway_options.rb +2 -2
  33. data/app/models/spree/payment/processing.rb +5 -5
  34. data/app/models/spree/payment.rb +1 -1
  35. data/app/models/spree/payment_connection_error.rb +3 -0
  36. data/app/models/spree/payment_method/check.rb +1 -1
  37. data/app/models/spree/payment_method/store_credit.rb +5 -5
  38. data/app/models/spree/payment_response.rb +70 -0
  39. data/app/models/spree/permission_sets/default_customer.rb +0 -5
  40. data/app/models/spree/permission_sets/product_display.rb +0 -2
  41. data/app/models/spree/permission_sets/product_management.rb +0 -2
  42. data/app/models/spree/product.rb +5 -85
  43. data/app/models/spree/promotion.rb +2 -14
  44. data/app/models/spree/promotion_handler/coupon.rb +3 -3
  45. data/app/models/spree/prototype.rb +0 -3
  46. data/app/models/spree/refund.rb +6 -2
  47. data/app/models/spree/reimbursement.rb +0 -2
  48. data/app/models/spree/shipment.rb +0 -1
  49. data/app/models/spree/shipment_handler.rb +0 -2
  50. data/app/models/spree/shipping_category.rb +3 -3
  51. data/app/models/spree/store.rb +10 -49
  52. data/app/models/spree/store_credit.rb +4 -0
  53. data/app/models/spree/tax_category.rb +1 -11
  54. data/app/models/spree/taxon.rb +0 -16
  55. data/app/models/spree/variant.rb +2 -40
  56. data/app/models/spree/zone.rb +1 -4
  57. data/app/services/spree/cart/add_item.rb +8 -4
  58. data/app/services/spree/cart/create.rb +13 -2
  59. data/app/services/spree/newsletter/subscribe.rb +2 -2
  60. data/app/services/spree/products/duplicator.rb +0 -12
  61. data/app/services/spree/products/prepare_nested_attributes.rb +0 -12
  62. data/app/subscribers/spree/invitation_email_subscriber.rb +1 -1
  63. data/config/locales/en.yml +4 -20
  64. data/db/migrate/20210914000000_spree_four_three.rb +0 -38
  65. data/db/migrate/20210915064329_add_metadata_to_spree_multiple_tables.rb +0 -1
  66. data/db/migrate/20260226000000_add_locale_to_spree_orders.rb +5 -0
  67. data/db/migrate/20260226100000_add_token_digest_to_spree_api_keys.rb +21 -0
  68. data/lib/spree/core/configuration.rb +0 -3
  69. data/lib/spree/core/controller_helpers/strong_parameters.rb +1 -2
  70. data/lib/spree/core/dependencies.rb +2 -10
  71. data/lib/spree/core/engine.rb +0 -13
  72. data/lib/spree/core/pricing/resolver.rb +1 -9
  73. data/lib/spree/core/version.rb +1 -1
  74. data/lib/spree/core.rb +10 -26
  75. data/lib/spree/permitted_attributes.rb +2 -14
  76. data/lib/spree/testing_support/factories/order_factory.rb +1 -0
  77. data/lib/spree/testing_support/factories/payment_method_factory.rb +1 -6
  78. data/lib/spree/testing_support/factories/product_factory.rb +0 -7
  79. data/lib/spree/testing_support/factories/prototype_factory.rb +0 -2
  80. data/lib/tasks/cli.rake +50 -0
  81. data/lib/tasks/core.rake +0 -265
  82. metadata +9 -111
  83. data/app/finders/spree/posts/find.rb +0 -137
  84. data/app/finders/spree/product_properties/find_available.rb +0 -20
  85. data/app/helpers/spree/mail_helper.rb +0 -27
  86. data/app/jobs/spree/api_key_touch_job.rb +0 -9
  87. data/app/mailers/spree/test_mailer.rb +0 -8
  88. data/app/models/action_text/video_embed.rb +0 -13
  89. data/app/models/concerns/spree/filter_param.rb +0 -21
  90. data/app/models/concerns/spree/has_one_link.rb +0 -42
  91. data/app/models/concerns/spree/linkable.rb +0 -9
  92. data/app/models/concerns/spree/previewable.rb +0 -17
  93. data/app/models/spree/data_feed/google.rb +0 -15
  94. data/app/models/spree/data_feed.rb +0 -42
  95. data/app/models/spree/gateway/bogus_simple.rb +0 -24
  96. data/app/models/spree/newsletter_subscriber/emails.rb +0 -12
  97. data/app/models/spree/order/emails.rb +0 -24
  98. data/app/models/spree/post.rb +0 -108
  99. data/app/models/spree/post_category.rb +0 -46
  100. data/app/models/spree/product_property.rb +0 -51
  101. data/app/models/spree/property.rb +0 -86
  102. data/app/models/spree/property_prototype.rb +0 -9
  103. data/app/models/spree/reimbursement/emails.rb +0 -11
  104. data/app/models/spree/shipment/emails.rb +0 -11
  105. data/app/models/spree/store_favicon_image.rb +0 -20
  106. data/app/models/spree/store_logo.rb +0 -4
  107. data/app/models/spree/store_mailer_logo.rb +0 -7
  108. data/app/models/spree/taxon_image/configuration/active_storage.rb +0 -26
  109. data/app/models/spree/taxon_image.rb +0 -28
  110. data/app/presenters/spree/filters/properties_presenter.rb +0 -23
  111. data/app/presenters/spree/filters/property_presenter.rb +0 -42
  112. data/app/services/spree/data_feeds/google/optional_attributes.rb +0 -23
  113. data/app/services/spree/data_feeds/google/optional_sub_attributes.rb +0 -21
  114. data/app/services/spree/data_feeds/google/products_list.rb +0 -14
  115. data/app/services/spree/data_feeds/google/required_attributes.rb +0 -68
  116. data/app/services/spree/data_feeds/google/rss.rb +0 -109
  117. data/app/sorters/spree/posts/sort.rb +0 -40
  118. data/app/views/action_text/video_embeds/_thumbnail.html.erb +0 -1
  119. data/app/views/action_text/video_embeds/_video_embed.html.erb +0 -3
  120. data/app/views/layouts/action_text/contents/_content.html.erb +0 -3
  121. data/app/views/spree/test_mailer/test_email.html.erb +0 -40
  122. data/app/views/spree/test_mailer/test_email.text.erb +0 -4
  123. data/config/initializers/oembed.rb +0 -1
  124. data/db/migrate/20221229132350_create_spree_data_feed_settings.rb +0 -14
  125. data/db/migrate/20230109084253_create_product_property_translations.rb +0 -24
  126. data/db/migrate/20230109105943_create_property_translations.rb +0 -24
  127. data/db/migrate/20230415155958_rename_data_feed_settings_table.rb +0 -5
  128. data/db/migrate/20230415160828_rename_data_feed_table_columns.rb +0 -7
  129. data/db/migrate/20230415161226_add_indexes_to_data_feeds_table.rb +0 -5
  130. data/db/migrate/20230512094803_rename_data_feeds_column_provider_to_type.rb +0 -5
  131. data/db/migrate/20240914153106_add_display_on_to_spree_properties.rb +0 -5
  132. data/db/migrate/20240915144935_add_position_to_spree_properties.rb +0 -6
  133. data/db/migrate/20250121160028_create_spree_posts_and_spree_post_categories.rb +0 -33
  134. data/db/migrate/20250127083740_add_kind_to_spree_properties.rb +0 -5
  135. data/db/migrate/20250217171018_create_action_text_video_embeds.rb +0 -11
  136. data/db/migrate/20250305121657_remove_spree_posts_indices.rb +0 -7
  137. data/db/migrate/20250730154601_add_unique_index_on_spree_properties_name.rb +0 -24
  138. data/db/sample_data/posts.rb +0 -7
  139. data/lib/generators/spree/cursor_rules/cursor_rules_generator.rb +0 -19
  140. data/lib/generators/spree/cursor_rules/templates/spree_rules.mdc +0 -387
  141. data/lib/spree/core/controller_helpers/search.rb +0 -17
  142. data/lib/spree/core/importer/order.rb +0 -244
  143. data/lib/spree/core/importer/product.rb +0 -67
  144. data/lib/spree/core/importer.rb +0 -9
  145. data/lib/spree/core/product_filters.rb +0 -218
  146. data/lib/spree/core/query_filters/comparable.rb +0 -50
  147. data/lib/spree/core/query_filters/date.rb +0 -8
  148. data/lib/spree/core/query_filters/number.rb +0 -8
  149. data/lib/spree/core/query_filters/text.rb +0 -36
  150. data/lib/spree/core/query_filters.rb +0 -11
  151. data/lib/spree/core/search/base.rb +0 -144
  152. data/lib/spree/testing_support/factories/favicon_image_factory.rb +0 -9
  153. data/lib/spree/testing_support/factories/google_data_feed_factory.rb +0 -7
  154. data/lib/spree/testing_support/factories/post_category_factory.rb +0 -7
  155. data/lib/spree/testing_support/factories/post_factory.rb +0 -22
  156. data/lib/spree/testing_support/factories/product_property_factory.rb +0 -7
  157. data/lib/spree/testing_support/factories/property_factory.rb +0 -28
  158. data/lib/spree/testing_support/factories/taxon_image_factory.rb +0 -9
  159. data/lib/spree/testing_support/flash.rb +0 -25
  160. data/lib/spree/testing_support/flatpickr_capybara.rb +0 -124
  161. 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: ea9fcfc9e4ac5937296c789f151d5d431b60a2fadb131e5a7bca034cae47b2ab
4
+ data.tar.gz: e89e3fd21bf14b0bb0bda0de42c0ecc49d173add3d9ce58c29b3c781eafb55fa
5
5
  SHA512:
6
- metadata.gz: 2a58de3390d7466d547af3ff46f6d661776e6b4c9790d02da9d7d90c0b96883647dcea51fb27f75110e1083c8e9dc7075b6bbee49499bfd189ce39bb7235362d
7
- data.tar.gz: 37fcc6d23c1e9ebf36314be3b85aea5c612eb7eb75a5fa0c2bd10f4c18ff1877298554f45fa80f8d6b841670e5004c1d953df068bebc2a45b59c4495edc10c2e
6
+ metadata.gz: b9bd748be9f9b4949d9b71887990fe3ac2377a6f4e7ed3d3060b0be073d6c199bd92a633bb2c62e7b8ee13dc0d7dae07e1bedd3d4d9dc5edebf3e4fc3839cae7
7
+ data.tar.gz: 8f8639ca080722372b01f91df71e54e0dc3f8f39d82cdbd1b3ad72103d995dbaf9bc50ef0f2a6032b0d400c2e94387edd2e06f64a52f3932391ed49ee5650014
@@ -19,18 +19,11 @@ 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)
26
- @out_of_stock = params.dig(:filter, :out_of_stock).to_b
27
25
  @tags = params.dig(:filter, :tags).to_s.split(',').compact_blank
28
26
  @vendor_ids = params.dig(:filter, :vendor_ids)&.split(',')&.compact_blank || []
29
-
30
- if @purchasable.present? && @out_of_stock.present?
31
- @purchasable = false
32
- @out_of_stock = false
33
- end
34
27
  end
35
28
 
36
29
  def execute
@@ -46,13 +39,11 @@ module Spree
46
39
  products = by_slug(products)
47
40
  products = by_options(products)
48
41
  products = by_option_value_ids(products)
49
- products = by_properties(products)
50
42
  products = by_tags(products)
51
43
  products = include_deleted(products)
52
44
  products = show_only_stock(products)
53
45
  products = show_only_backorderable(products)
54
46
  products = show_only_purchasable(products)
55
- products = show_only_out_of_stock(products)
56
47
  products = by_taxonomies(products)
57
48
  products = ordered(products)
58
49
  products = by_vendor_ids(products)
@@ -63,8 +54,8 @@ module Spree
63
54
  private
64
55
 
65
56
  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,
67
- :query, :vendor_ids, :out_of_stock, :slug, :taxonomies
57
+ :sort_by, :deleted, :discontinued, :store, :in_stock, :backorderable, :purchasable, :tags,
58
+ :query, :vendor_ids, :slug, :taxonomies
68
59
 
69
60
  def query?
70
61
  query.present?
@@ -114,10 +105,6 @@ module Spree
114
105
  sort_by.present?
115
106
  end
116
107
 
117
- def properties?
118
- properties.present? && properties.values.reject(&:empty?).present?
119
- end
120
-
121
108
  def vendor_ids?
122
109
  vendor_ids.present?
123
110
  end
@@ -224,27 +211,6 @@ module Spree
224
211
  products.where(id: product_ids)
225
212
  end
226
213
 
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
214
  def by_tags(products)
249
215
  return products if tags.empty?
250
216
 
@@ -321,12 +287,6 @@ module Spree
321
287
  products.in_stock_or_backorderable
322
288
  end
323
289
 
324
- def show_only_out_of_stock(products)
325
- return products unless out_of_stock.present?
326
-
327
- products.out_of_stock
328
- end
329
-
330
290
  def map_prices(prices)
331
291
  prices.map do |price|
332
292
  price == 'Infinity' ? BigDecimal::INFINITY : price.to_f
@@ -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
@@ -96,9 +81,26 @@ module Spree
96
81
  where(Price.table_name => { amount: price.. })
97
82
  end
98
83
 
99
- add_search_scope :in_stock do
100
- joins(:variants_including_master).merge(Spree::Variant.in_stock)
84
+ # Can't use add_search_scope for this as it needs a default argument
85
+ # Ransack calls with '1' to activate, '0' or nil to skip
86
+ # In Ruby code: in_stock(true) for in-stock, in_stock(false) for out-of-stock
87
+ def self.in_stock(in_stock = true)
88
+ if in_stock == '0' || !in_stock
89
+ all
90
+ else
91
+ joins(:variants_including_master).merge(Spree::Variant.in_stock_or_backorderable)
92
+ end
93
+ end
94
+ search_scopes << :in_stock
95
+
96
+ def self.out_of_stock(out_of_stock = true)
97
+ if out_of_stock == '0' || !out_of_stock
98
+ all
99
+ else
100
+ where.not(id: joins(:variants_including_master).merge(Spree::Variant.in_stock_or_backorderable))
101
+ end
101
102
  end
103
+ search_scopes << :out_of_stock
102
104
 
103
105
  add_search_scope :backorderable do
104
106
  joins(:variants_including_master).merge(Spree::Variant.backorderable)
@@ -149,33 +151,6 @@ module Spree
149
151
  order(Arel.sql("#{min_position_sql} ASC"))
150
152
  end
151
153
 
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
154
  add_search_scope :with_option do |option|
180
155
  if option.is_a?(OptionType)
181
156
  joins(:option_types).where(spree_option_types: { id: option.id })
@@ -226,13 +201,10 @@ module Spree
226
201
  where(Spree::OptionValue.table_name => { id: actual_ids })
227
202
  end
228
203
 
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
204
+ # Finds all products which have an option value with the name matching the one given
232
205
  add_search_scope :with do |value|
233
206
  includes(variants: :option_values).
234
- includes(:product_properties).
235
- where("#{OptionValue.table_name}.name = ? OR #{ProductProperty.table_name}.value = ?", value, value)
207
+ where("#{OptionValue.table_name}.name = ?", value)
236
208
  end
237
209
 
238
210
  # 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,24 @@ module Spree
6
6
  PREFIXES = { 'publishable' => 'pk_', 'secret' => 'sk_' }.freeze
7
7
  TOKEN_LENGTH = 24
8
8
 
9
+ # Returns the raw token value. For publishable keys this is the persisted
10
+ # +token+ column. For secret keys it is only available in memory immediately
11
+ # after creation (not persisted).
12
+ #
13
+ # @return [String, nil]
14
+ def plaintext_token
15
+ publishable? ? token : @plaintext_token
16
+ end
17
+
9
18
  belongs_to :store, class_name: 'Spree::Store'
10
19
  belongs_to :created_by, polymorphic: true, optional: true
11
20
  belongs_to :revoked_by, polymorphic: true, optional: true
12
21
 
13
22
  validates :name, presence: true
14
23
  validates :key_type, presence: true, inclusion: { in: KEY_TYPES }
15
- validates :token, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }
24
+ validates :token, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }, if: :publishable?
25
+ validates :token_digest, presence: true, uniqueness: true, if: :secret?
26
+ validates :token_prefix, presence: true, if: :secret?
16
27
  validates :store, presence: true
17
28
 
18
29
  before_validation :generate_token, on: :create
@@ -22,26 +33,73 @@ module Spree
22
33
  scope :publishable, -> { where(key_type: 'publishable') }
23
34
  scope :secret, -> { where(key_type: 'secret') }
24
35
 
36
+ # Finds an active secret API key by computing the HMAC-SHA256 digest
37
+ # of the provided plaintext token and looking up by +token_digest+.
38
+ #
39
+ # @param plaintext [String] the raw secret key (e.g. "sk_abc123...")
40
+ # @return [Spree::ApiKey, nil] the matching active secret key, or nil
41
+ def self.find_by_secret_token(plaintext)
42
+ return nil if plaintext.blank?
43
+
44
+ digest = compute_token_digest(plaintext)
45
+ active.secret.find_by(token_digest: digest)
46
+ end
47
+
48
+ # Computes the HMAC-SHA256 hex digest for a given plaintext token.
49
+ #
50
+ # @param plaintext [String] the raw token value
51
+ # @return [String] the hex-encoded HMAC-SHA256 digest
52
+ def self.compute_token_digest(plaintext)
53
+ OpenSSL::HMAC.hexdigest('SHA256', hmac_secret, plaintext)
54
+ end
55
+
56
+ # Returns the HMAC secret used for token hashing.
57
+ #
58
+ # @return [String] the application's secret key base
59
+ def self.hmac_secret
60
+ Rails.application.secret_key_base
61
+ end
62
+
63
+ # @return [Boolean] whether this is a publishable (Store API) key
25
64
  def publishable?
26
65
  key_type == 'publishable'
27
66
  end
28
67
 
68
+ # @return [Boolean] whether this is a secret (Admin API) key
29
69
  def secret?
30
70
  key_type == 'secret'
31
71
  end
32
72
 
73
+ # @return [Boolean] whether this key has not been revoked
33
74
  def active?
34
75
  revoked_at.nil?
35
76
  end
36
77
 
78
+ # Revokes this API key by setting +revoked_at+ to the current time.
79
+ #
80
+ # @param user [Object, nil] the user who performed the revocation
81
+ # @return [Boolean] true if the update succeeded
37
82
  def revoke!(user = nil)
38
83
  update!(revoked_at: Time.current, revoked_by: user)
39
84
  end
40
85
 
41
86
  private
42
87
 
88
+ # Generates the token on creation. For publishable keys, stores the raw token
89
+ # in the +token+ column. For secret keys, computes an HMAC-SHA256 digest stored
90
+ # in +token_digest+, saves the first 12 characters as +token_prefix+ for display,
91
+ # and exposes the raw value via {#plaintext_token} (available only in memory).
43
92
  def generate_token
44
- self.token ||= "#{PREFIXES[key_type]}#{SecureRandom.base58(TOKEN_LENGTH)}"
93
+ raw_token = "#{PREFIXES[key_type]}#{SecureRandom.base58(TOKEN_LENGTH)}"
94
+
95
+ if secret?
96
+ @plaintext_token = raw_token
97
+ self.token_prefix = raw_token[0, 12]
98
+ self.token_digest = self.class.compute_token_digest(raw_token)
99
+ self.token = nil
100
+ else
101
+ self.token ||= raw_token
102
+ end
45
103
  end
46
104
  end
47
105
  end