spree_core 5.4.0.beta4 → 5.4.0.beta5
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.
- checksums.yaml +4 -4
- data/app/jobs/spree/images/save_from_url_job.rb +47 -23
- data/app/models/concerns/spree/product_scopes.rb +65 -40
- data/app/models/concerns/spree/user_methods.rb +1 -0
- data/app/models/spree/category.rb +6 -0
- data/app/models/spree/payment/gateway_options.rb +5 -0
- data/app/models/spree/product.rb +5 -34
- data/app/models/spree/store.rb +41 -80
- data/app/models/spree/taxon.rb +39 -34
- data/app/models/spree/webhook_endpoint.rb +17 -0
- data/app/services/spree/cart/create.rb +18 -2
- data/app/services/spree/cart/upsert_items.rb +80 -0
- data/app/services/spree/gift_cards/apply.rb +5 -4
- data/app/services/spree/orders/update.rb +121 -0
- data/config/locales/en.yml +5 -0
- data/lib/spree/core/configuration.rb +1 -0
- data/lib/spree/core/controller_helpers/strong_parameters.rb +1 -1
- data/lib/spree/core/dependencies.rb +2 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/permitted_attributes.rb +3 -5
- data/lib/spree/testing_support/factories/store_factory.rb +0 -9
- data/lib/tasks/core.rake +0 -28
- metadata +21 -5
- data/app/models/concerns/spree/stores/socials.rb +0 -72
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a2a4eae0b4e102ba4160e435fe81d656ced27b014ed013670226bb8a522b5996
|
|
4
|
+
data.tar.gz: c8c6737253472a4456f29b06f1ae028b2c4144732fba6dae7d8dd8c5e2b92d8f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec9479d52b6bcbd036354511897d0ee360da7784aef051f20affe23991b1eaebe8525b7a0173fbc7cdc1310a977909e4b78a33b0cd677869d579d1666bbada7a
|
|
7
|
+
data.tar.gz: 44b40ddca311ed24ae26cd99b5455a7259a0aa986cb8a9a76598101e4c42ea48042b8f1256365b2734c7e43218337ec7963c78b0bcffc5ab70f62e0f794d4b9e
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
require 'open-uri'
|
|
2
2
|
require 'openssl'
|
|
3
|
+
require 'ssrf_filter'
|
|
4
|
+
require 'tempfile'
|
|
3
5
|
|
|
4
6
|
module Spree
|
|
5
7
|
module Images
|
|
6
8
|
class SaveFromUrlJob < ::Spree::BaseJob
|
|
7
9
|
queue_as Spree.queues.images
|
|
8
|
-
retry_on ActiveRecord::RecordInvalid,
|
|
10
|
+
retry_on ActiveRecord::RecordInvalid, wait: :polynomially_longer, attempts: Spree::Config.images_save_from_url_job_attempts.to_i
|
|
9
11
|
discard_on URI::InvalidURIError
|
|
12
|
+
discard_on SsrfFilter::Error
|
|
10
13
|
|
|
11
14
|
def perform(viewable_id, viewable_type, external_url, external_id = nil, position = nil)
|
|
12
15
|
viewable = viewable_type.safe_constantize.find(viewable_id)
|
|
@@ -29,34 +32,55 @@ module Spree
|
|
|
29
32
|
# still trigger save! if position has changed
|
|
30
33
|
image.save! and return if image_already_saved?(image, external_url)
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
unless %w[http https].include?(uri.scheme)
|
|
34
|
-
raise URI::InvalidURIError, "Invalid URL scheme: #{uri.scheme}. Only http and https are allowed."
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
file = uri.open(
|
|
38
|
-
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
39
|
-
'Accept' => 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
|
|
40
|
-
'Accept-Language' => 'en-US,en;q=0.9',
|
|
41
|
-
'Accept-Encoding' => 'gzip, deflate, br',
|
|
42
|
-
'Cache-Control' => 'no-cache',
|
|
43
|
-
'Pragma' => 'no-cache',
|
|
44
|
-
read_timeout: 60,
|
|
45
|
-
ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
|
|
46
|
-
redirect: true
|
|
47
|
-
)
|
|
48
|
-
filename = File.basename(uri.path)
|
|
49
|
-
|
|
50
|
-
image.attachment.attach(io: file, filename: filename)
|
|
51
|
-
image.external_url = external_url
|
|
52
|
-
image.external_id = external_id if external_id.present? && image.respond_to?(:external_id)
|
|
53
|
-
image.save!
|
|
35
|
+
download_and_attach_image(external_url, image, external_id)
|
|
54
36
|
rescue ActiveStorage::IntegrityError => e
|
|
55
37
|
raise e unless Rails.env.test?
|
|
56
38
|
end
|
|
57
39
|
|
|
58
40
|
private
|
|
59
41
|
|
|
42
|
+
def download_and_attach_image(external_url, image, external_id)
|
|
43
|
+
max_size = Spree::Config.max_image_download_size
|
|
44
|
+
|
|
45
|
+
response = SsrfFilter.get(
|
|
46
|
+
external_url,
|
|
47
|
+
headers: {
|
|
48
|
+
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
49
|
+
'Accept' => 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
|
|
50
|
+
'Accept-Language' => 'en-US,en;q=0.9',
|
|
51
|
+
'Accept-Encoding' => 'gzip, deflate, br',
|
|
52
|
+
'Cache-Control' => 'no-cache',
|
|
53
|
+
'Pragma' => 'no-cache'
|
|
54
|
+
},
|
|
55
|
+
http_options: {
|
|
56
|
+
read_timeout: 60,
|
|
57
|
+
open_timeout: 30
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
body = response.body
|
|
62
|
+
if body.bytesize > max_size
|
|
63
|
+
raise StandardError, "Image file size exceeds the maximum allowed size of #{max_size} bytes"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
uri = URI.parse(external_url)
|
|
67
|
+
filename = File.basename(uri.path)
|
|
68
|
+
tempfile = Tempfile.new(['spree_image', File.extname(uri.path)], binmode: true)
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
tempfile.write(body)
|
|
72
|
+
tempfile.rewind
|
|
73
|
+
|
|
74
|
+
image.attachment.attach(io: tempfile, filename: filename)
|
|
75
|
+
image.external_url = external_url
|
|
76
|
+
image.external_id = external_id if external_id.present? && image.respond_to?(:external_id)
|
|
77
|
+
image.save!
|
|
78
|
+
ensure
|
|
79
|
+
tempfile.close
|
|
80
|
+
tempfile.unlink
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
60
84
|
def image_already_saved?(image, external_url)
|
|
61
85
|
image.persisted? && image.attachment.attached? && image.external_url.present? && external_url == image.external_url
|
|
62
86
|
end
|
|
@@ -41,32 +41,38 @@ module Spree
|
|
|
41
41
|
order(price_table_name => { amount: :desc })
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
# Price sorting scopes that use
|
|
45
|
-
# These ensure products with only variant prices (no master price) are included in results
|
|
44
|
+
# Price sorting scopes that use a derived table JOIN to get prices across all variants.
|
|
45
|
+
# These ensure products with only variant prices (no master price) are included in results.
|
|
46
|
+
#
|
|
47
|
+
# Uses Arel::Nodes::As for select expressions so that:
|
|
48
|
+
# - PG allows ORDER BY with DISTINCT (expressions must appear in SELECT list)
|
|
49
|
+
# - Mobility's select_for_count can safely call .right on all select_values
|
|
46
50
|
add_search_scope :ascend_by_price do
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.select('MIN(amount)')
|
|
51
|
+
price_agg_sql = Price.non_zero.joins(:variant)
|
|
52
|
+
.select("#{Variant.table_name}.product_id AS product_id, MIN(#{Price.table_name}.amount) AS agg_price")
|
|
53
|
+
.group("#{Variant.table_name}.product_id")
|
|
54
|
+
.to_sql
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
price_expr = Arel.sql('COALESCE(price_agg.agg_price, 999999999)')
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
joins("LEFT JOIN (#{price_agg_sql}) AS price_agg ON price_agg.product_id = #{Product.table_name}.id").
|
|
59
|
+
select("#{Product.table_name}.*").
|
|
60
|
+
select(Arel::Nodes::As.new(price_expr, Arel.sql('min_price'))).
|
|
61
|
+
order(price_expr.asc)
|
|
57
62
|
end
|
|
58
63
|
|
|
59
64
|
add_search_scope :descend_by_price do
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
.select('MAX(amount)')
|
|
65
|
+
price_agg_sql = Price.non_zero.joins(:variant)
|
|
66
|
+
.select("#{Variant.table_name}.product_id AS product_id, MAX(#{Price.table_name}.amount) AS agg_price")
|
|
67
|
+
.group("#{Variant.table_name}.product_id")
|
|
68
|
+
.to_sql
|
|
65
69
|
|
|
66
|
-
|
|
70
|
+
price_expr = Arel.sql('COALESCE(price_agg.agg_price, 0)')
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
joins("LEFT JOIN (#{price_agg_sql}) AS price_agg ON price_agg.product_id = #{Product.table_name}.id").
|
|
73
|
+
select("#{Product.table_name}.*").
|
|
74
|
+
select(Arel::Nodes::As.new(price_expr, Arel.sql('max_price'))).
|
|
75
|
+
order(price_expr.desc)
|
|
70
76
|
end
|
|
71
77
|
|
|
72
78
|
add_search_scope :price_between do |low, high|
|
|
@@ -81,6 +87,25 @@ module Spree
|
|
|
81
87
|
where(Price.table_name => { amount: price.. })
|
|
82
88
|
end
|
|
83
89
|
|
|
90
|
+
# Joins spree_variants and spree_stock_items directly (without association
|
|
91
|
+
# aliases) so that the table names stay as-is. This avoids alias conflicts
|
|
92
|
+
# when combined with other scopes (e.g., price sorting) that also join
|
|
93
|
+
# spree_variants through associations which generate aliases.
|
|
94
|
+
def self.join_variants_and_stock_items
|
|
95
|
+
joins("INNER JOIN #{Variant.table_name} ON #{Variant.table_name}.deleted_at IS NULL AND #{Variant.table_name}.product_id = #{Product.table_name}.id").
|
|
96
|
+
joins("LEFT OUTER JOIN #{StockItem.table_name} ON #{StockItem.table_name}.deleted_at IS NULL AND #{StockItem.table_name}.variant_id = #{Variant.table_name}.id")
|
|
97
|
+
end
|
|
98
|
+
private_class_method :join_variants_and_stock_items
|
|
99
|
+
|
|
100
|
+
# Mirrors Spree::Variant.in_stock_or_backorderable logic using raw table
|
|
101
|
+
# names (to pair with join_variants_and_stock_items).
|
|
102
|
+
scope :in_stock_or_backorderable_condition, -> {
|
|
103
|
+
where(
|
|
104
|
+
"#{Variant.table_name}.track_inventory = ? OR #{StockItem.table_name}.count_on_hand > ? OR #{StockItem.table_name}.backorderable = ?",
|
|
105
|
+
false, 0, true
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
84
109
|
# Can't use add_search_scope for this as it needs a default argument
|
|
85
110
|
# Ransack calls with '1' to activate, '0' or nil to skip
|
|
86
111
|
# In Ruby code: in_stock(true) for in-stock, in_stock(false) for out-of-stock
|
|
@@ -88,7 +113,7 @@ module Spree
|
|
|
88
113
|
if in_stock == '0' || !in_stock
|
|
89
114
|
all
|
|
90
115
|
else
|
|
91
|
-
|
|
116
|
+
join_variants_and_stock_items.in_stock_or_backorderable_condition
|
|
92
117
|
end
|
|
93
118
|
end
|
|
94
119
|
|
|
@@ -105,17 +130,17 @@ module Spree
|
|
|
105
130
|
if out_of_stock == '0' || !out_of_stock
|
|
106
131
|
all
|
|
107
132
|
else
|
|
108
|
-
where.not(id:
|
|
133
|
+
where.not(id: join_variants_and_stock_items.in_stock_or_backorderable_condition)
|
|
109
134
|
end
|
|
110
135
|
end
|
|
111
136
|
search_scopes << :out_of_stock
|
|
112
137
|
|
|
113
138
|
add_search_scope :backorderable do
|
|
114
|
-
|
|
139
|
+
join_variants_and_stock_items.where(StockItem.table_name => { backorderable: true })
|
|
115
140
|
end
|
|
116
141
|
|
|
117
142
|
add_search_scope :in_stock_or_backorderable do
|
|
118
|
-
|
|
143
|
+
join_variants_and_stock_items.in_stock_or_backorderable_condition
|
|
119
144
|
end
|
|
120
145
|
|
|
121
146
|
# This scope selects products in taxon AND all its descendants
|
|
@@ -140,6 +165,11 @@ module Spree
|
|
|
140
165
|
where("#{Classification.table_name}.taxon_id" => taxon.cached_self_and_descendants_ids).distinct
|
|
141
166
|
end
|
|
142
167
|
|
|
168
|
+
# Alias for in_taxon — public API name
|
|
169
|
+
add_search_scope :in_category do |category|
|
|
170
|
+
in_taxon(category)
|
|
171
|
+
end
|
|
172
|
+
|
|
143
173
|
# This scope selects products in all taxons AND all its descendants
|
|
144
174
|
# If you need products only within one taxon use
|
|
145
175
|
#
|
|
@@ -204,9 +234,7 @@ module Spree
|
|
|
204
234
|
|
|
205
235
|
return none if actual_ids.empty?
|
|
206
236
|
|
|
207
|
-
|
|
208
|
-
joins(variants: :option_values).
|
|
209
|
-
where(Spree::OptionValue.table_name => { id: actual_ids })
|
|
237
|
+
joins(variants: :option_values).where(Spree::OptionValue.table_name => { id: actual_ids })
|
|
210
238
|
end
|
|
211
239
|
|
|
212
240
|
# Finds all products which have an option value with the name matching the one given
|
|
@@ -325,25 +353,22 @@ module Spree
|
|
|
325
353
|
end
|
|
326
354
|
|
|
327
355
|
# Orders products by best selling based on units_sold_count and revenue
|
|
328
|
-
#
|
|
329
|
-
#
|
|
330
|
-
# These metrics are updated asynchronously when orders are completed
|
|
331
|
-
# via the ProductMetricsSubscriber.
|
|
356
|
+
# from spree_products_stores (already joined via store.products).
|
|
332
357
|
#
|
|
333
|
-
#
|
|
334
|
-
#
|
|
358
|
+
# Uses Arel::Nodes::As so that ORDER BY expressions appear in SELECT
|
|
359
|
+
# and work with DISTINCT (same pattern as the price sorting scopes).
|
|
335
360
|
add_search_scope :by_best_selling do |order_direction = :desc|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
conditions = sp_table[:product_id].eq(products_table[:id]).and(sp_table[:store_id].eq(store_id))
|
|
341
|
-
|
|
342
|
-
units_sold = Arel::Nodes::NamedFunction.new('COALESCE', [sp_table.project(sp_table[:units_sold_count]).where(conditions), 0])
|
|
343
|
-
revenue = Arel::Nodes::NamedFunction.new('COALESCE', [sp_table.project(sp_table[:revenue]).where(conditions), 0])
|
|
361
|
+
sp_table = StoreProduct.table_name
|
|
362
|
+
units_expr = Arel.sql("COALESCE(#{sp_table}.units_sold_count, 0)")
|
|
363
|
+
revenue_expr = Arel.sql("COALESCE(#{sp_table}.revenue, 0)")
|
|
344
364
|
|
|
345
365
|
order_dir = order_direction == :desc ? :desc : :asc
|
|
346
|
-
|
|
366
|
+
|
|
367
|
+
select("#{Product.table_name}.*").
|
|
368
|
+
select(Arel::Nodes::As.new(units_expr, Arel.sql('best_selling_units'))).
|
|
369
|
+
select(Arel::Nodes::As.new(revenue_expr, Arel.sql('best_selling_revenue'))).
|
|
370
|
+
order(units_expr.send(order_dir)).
|
|
371
|
+
order(revenue_expr.send(order_dir))
|
|
347
372
|
end
|
|
348
373
|
|
|
349
374
|
# .search_by_name
|
|
@@ -34,6 +34,10 @@ module Spree
|
|
|
34
34
|
payment.number
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
def idempotency_key
|
|
38
|
+
"spree-#{payment.number}"
|
|
39
|
+
end
|
|
40
|
+
|
|
37
41
|
def shipping
|
|
38
42
|
order.ship_total * exchange_multiplier
|
|
39
43
|
end
|
|
@@ -66,6 +70,7 @@ module Spree
|
|
|
66
70
|
:ip,
|
|
67
71
|
:order_id,
|
|
68
72
|
:payment_id,
|
|
73
|
+
:idempotency_key,
|
|
69
74
|
:shipping,
|
|
70
75
|
:tax,
|
|
71
76
|
:subtotal,
|
data/app/models/spree/product.rb
CHANGED
|
@@ -40,9 +40,9 @@ module Spree
|
|
|
40
40
|
|
|
41
41
|
publishes_lifecycle_events
|
|
42
42
|
|
|
43
|
-
MEMOIZED_METHODS = %w[total_on_hand taxonomy_ids taxon_and_ancestors
|
|
43
|
+
MEMOIZED_METHODS = %w[total_on_hand taxonomy_ids taxon_and_ancestors
|
|
44
44
|
default_variant_id tax_category default_variant variant_for_images
|
|
45
|
-
|
|
45
|
+
brand_taxon main_taxon
|
|
46
46
|
purchasable? in_stock? backorderable? digital?]
|
|
47
47
|
|
|
48
48
|
STATUSES = %w[draft active archived].freeze
|
|
@@ -74,6 +74,7 @@ module Spree
|
|
|
74
74
|
has_many :option_types, through: :product_option_types
|
|
75
75
|
has_many :classifications, -> { order(created_at: :asc) }, dependent: :delete_all, inverse_of: :product
|
|
76
76
|
has_many :taxons, through: :classifications, before_remove: :remove_taxon
|
|
77
|
+
has_many :categories, through: :classifications, class_name: 'Spree::Category', source: :taxon
|
|
77
78
|
has_many :taxonomies, through: :taxons
|
|
78
79
|
|
|
79
80
|
has_many :product_promotion_rules, class_name: 'Spree::ProductPromotionRule'
|
|
@@ -217,7 +218,7 @@ module Spree
|
|
|
217
218
|
alias options product_option_types
|
|
218
219
|
|
|
219
220
|
self.whitelisted_ransackable_attributes = %w[description name slug discontinue_on status available_on created_at updated_at]
|
|
220
|
-
self.whitelisted_ransackable_associations = %w[taxons stores variants_including_master master variants tags labels
|
|
221
|
+
self.whitelisted_ransackable_associations = %w[taxons categories stores variants_including_master master variants tags labels
|
|
221
222
|
shipping_category classifications option_types]
|
|
222
223
|
self.whitelisted_ransackable_scopes = %w[not_discontinued search_by_name in_taxon price_between
|
|
223
224
|
price_lte price_gte
|
|
@@ -556,40 +557,10 @@ module Spree
|
|
|
556
557
|
brand&.name
|
|
557
558
|
end
|
|
558
559
|
|
|
559
|
-
# Returns the category for the product
|
|
560
|
-
# If a category association is defined (e.g., belongs_to :category), it will be used
|
|
561
|
-
# Otherwise, falls back to category_taxon for compatibility
|
|
562
|
-
# @return [Spree::Category, Spree::Taxon]
|
|
563
|
-
def category
|
|
564
|
-
if self.class.reflect_on_association(:category)
|
|
565
|
-
super
|
|
566
|
-
else
|
|
567
|
-
Spree::Deprecation.warn('Spree::Product#category is deprecated and will be removed in Spree 5.5. Please use Spree::Product#category_taxon instead.')
|
|
568
|
-
category_taxon
|
|
569
|
-
end
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
# Returns the category taxon for the product
|
|
573
|
-
# @return [Spree::Taxon]
|
|
574
|
-
def category_taxon
|
|
575
|
-
@category_taxon ||= if classification_count.zero?
|
|
576
|
-
nil
|
|
577
|
-
elsif Spree.use_translations?
|
|
578
|
-
taxons.joins(:taxonomy).
|
|
579
|
-
join_translation_table(Taxonomy).
|
|
580
|
-
order(depth: :desc).
|
|
581
|
-
find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_categories_name) })
|
|
582
|
-
elsif taxons.loaded?
|
|
583
|
-
taxons.find { |taxon| taxon.taxonomy.name == Spree.t(:taxonomy_categories_name) }
|
|
584
|
-
else
|
|
585
|
-
taxons.joins(:taxonomy).order(depth: :desc).find_by(Taxonomy.table_name => { name: Spree.t(:taxonomy_categories_name) })
|
|
586
|
-
end
|
|
587
|
-
end
|
|
588
|
-
|
|
589
560
|
def main_taxon
|
|
590
561
|
return if classification_count.zero?
|
|
591
562
|
|
|
592
|
-
@main_taxon ||=
|
|
563
|
+
@main_taxon ||= taxons.first
|
|
593
564
|
end
|
|
594
565
|
|
|
595
566
|
def taxons_for_store(store)
|
data/app/models/spree/store.rb
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'uri'
|
|
2
4
|
|
|
3
5
|
module Spree
|
|
4
6
|
class Store < Spree.base_class
|
|
5
|
-
has_prefix_id :store
|
|
7
|
+
has_prefix_id :store # Spree-specific: store
|
|
6
8
|
|
|
7
9
|
include FriendlyId
|
|
8
10
|
include Spree::TranslatableResource
|
|
9
11
|
include Spree::Metafields
|
|
10
12
|
include Spree::Metadata
|
|
11
13
|
include Spree::Stores::Setup
|
|
12
|
-
include Spree::Stores::Socials
|
|
13
14
|
include Spree::Stores::Markets
|
|
14
15
|
include Spree::Security::Stores if defined?(Spree::Security::Stores)
|
|
15
16
|
include Spree::UserManagement
|
|
@@ -23,8 +24,7 @@ module Spree
|
|
|
23
24
|
#
|
|
24
25
|
# Translations
|
|
25
26
|
#
|
|
26
|
-
TRANSLATABLE_FIELDS = %i[name meta_description meta_keywords seo_title
|
|
27
|
-
twitter instagram customer_support_email
|
|
27
|
+
TRANSLATABLE_FIELDS = %i[name meta_description meta_keywords seo_title customer_support_email
|
|
28
28
|
address contact_phone].freeze
|
|
29
29
|
translates(*TRANSLATABLE_FIELDS, column_fallback: !Spree.always_use_translations?)
|
|
30
30
|
self::Translation.class_eval do
|
|
@@ -43,9 +43,6 @@ module Spree
|
|
|
43
43
|
preference :unit_system, :string, default: 'imperial'
|
|
44
44
|
# email preferences
|
|
45
45
|
preference :send_consumer_transactional_emails, :boolean, default: true
|
|
46
|
-
# SEO preferences
|
|
47
|
-
preference :index_in_search_engines, :boolean, default: false
|
|
48
|
-
preference :password_protected, :boolean, default: false
|
|
49
46
|
# Checkout preferences
|
|
50
47
|
preference :guest_checkout, :boolean, default: true
|
|
51
48
|
preference :special_instructions_enabled, :boolean, default: false
|
|
@@ -86,6 +83,7 @@ module Spree
|
|
|
86
83
|
|
|
87
84
|
has_many :taxonomies, class_name: 'Spree::Taxonomy'
|
|
88
85
|
has_many :taxons, through: :taxonomies, class_name: 'Spree::Taxon'
|
|
86
|
+
has_many :categories, through: :taxonomies, class_name: 'Spree::Category', source: :taxons
|
|
89
87
|
|
|
90
88
|
has_many :store_promotions, class_name: 'Spree::StorePromotion'
|
|
91
89
|
has_many :promotions, through: :store_promotions, class_name: 'Spree::Promotion'
|
|
@@ -111,21 +109,6 @@ module Spree
|
|
|
111
109
|
|
|
112
110
|
has_many :api_keys, class_name: 'Spree::ApiKey', dependent: :destroy
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
#
|
|
116
|
-
# ActionText
|
|
117
|
-
#
|
|
118
|
-
has_rich_text :checkout_message
|
|
119
|
-
has_rich_text :customer_terms_of_service
|
|
120
|
-
has_rich_text :customer_privacy_policy
|
|
121
|
-
has_rich_text :customer_returns_policy
|
|
122
|
-
has_rich_text :customer_shipping_policy
|
|
123
|
-
|
|
124
|
-
#
|
|
125
|
-
# Virtual attributes
|
|
126
|
-
#
|
|
127
|
-
store_accessor :private_metadata, :storefront_password
|
|
128
|
-
|
|
129
112
|
#
|
|
130
113
|
# Validations
|
|
131
114
|
#
|
|
@@ -137,19 +120,17 @@ module Spree
|
|
|
137
120
|
validates :mail_from_address, email: { allow_blank: false }
|
|
138
121
|
# FIXME: we should remove this condition in v5
|
|
139
122
|
if !ENV['SPREE_DISABLE_DB_CONNECTION'] &&
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
123
|
+
connected? &&
|
|
124
|
+
table_exists? &&
|
|
125
|
+
connection.column_exists?(:spree_stores, :new_order_notifications_email)
|
|
143
126
|
validates :new_order_notifications_email, email: { allow_blank: true }
|
|
144
127
|
end
|
|
145
|
-
validates :
|
|
128
|
+
validates :mailer_logo, content_type: Rails.application.config.active_storage.web_image_content_types
|
|
146
129
|
|
|
147
130
|
#
|
|
148
131
|
# Attachments
|
|
149
132
|
#
|
|
150
133
|
has_one_attached :logo, service: Spree.public_storage_service_name
|
|
151
|
-
has_one_attached :favicon_image, service: Spree.public_storage_service_name
|
|
152
|
-
has_one_attached :social_image, service: Spree.public_storage_service_name
|
|
153
134
|
has_one_attached :mailer_logo, service: Spree.public_storage_service_name
|
|
154
135
|
|
|
155
136
|
#
|
|
@@ -157,8 +138,6 @@ module Spree
|
|
|
157
138
|
before_validation :set_default_code, on: :create
|
|
158
139
|
before_save :ensure_default_exists_and_is_unique
|
|
159
140
|
after_create :ensure_default_market
|
|
160
|
-
after_create :ensure_default_taxonomies_are_created
|
|
161
|
-
after_create :ensure_default_automatic_taxons
|
|
162
141
|
after_create :create_default_policies
|
|
163
142
|
|
|
164
143
|
#
|
|
@@ -233,16 +212,6 @@ module Spree
|
|
|
233
212
|
@default_country_for_market = country
|
|
234
213
|
end
|
|
235
214
|
|
|
236
|
-
def seo_meta_description
|
|
237
|
-
if meta_description.present?
|
|
238
|
-
meta_description
|
|
239
|
-
elsif seo_title.present?
|
|
240
|
-
seo_title
|
|
241
|
-
else
|
|
242
|
-
name
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
215
|
def unique_name
|
|
247
216
|
@unique_name ||= "#{name} (#{code})"
|
|
248
217
|
end
|
|
@@ -312,18 +281,18 @@ module Spree
|
|
|
312
281
|
# @return [ActiveRecord::Relation<Spree::Country>]
|
|
313
282
|
def countries_with_shipping_coverage
|
|
314
283
|
zone_ids = Spree::Zone
|
|
315
|
-
|
|
316
|
-
|
|
284
|
+
.joins(:shipping_methods)
|
|
285
|
+
.select(:id)
|
|
317
286
|
|
|
318
287
|
country_zone_country_ids = Spree::ZoneMember
|
|
319
|
-
|
|
320
|
-
|
|
288
|
+
.where(zone_id: zone_ids, zoneable_type: 'Spree::Country')
|
|
289
|
+
.select(:zoneable_id)
|
|
321
290
|
|
|
322
291
|
state_zone_country_ids = Spree::State
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
292
|
+
.where(id: Spree::ZoneMember
|
|
293
|
+
.where(zone_id: zone_ids, zoneable_type: 'Spree::State')
|
|
294
|
+
.select(:zoneable_id))
|
|
295
|
+
.select(:country_id)
|
|
327
296
|
|
|
328
297
|
Spree::Country
|
|
329
298
|
.where(id: country_zone_country_ids)
|
|
@@ -337,7 +306,8 @@ module Spree
|
|
|
337
306
|
@default_stock_location ||= begin
|
|
338
307
|
stock_location_scope = Spree::StockLocation.where(default: true)
|
|
339
308
|
stock_location_scope.first || ActiveRecord::Base.connected_to(role: :writing) do
|
|
340
|
-
stock_location_scope.create(default: true, name: Spree.t(:default_stock_location_name),
|
|
309
|
+
stock_location_scope.create(default: true, name: Spree.t(:default_stock_location_name),
|
|
310
|
+
country: default_country)
|
|
341
311
|
end
|
|
342
312
|
end
|
|
343
313
|
end
|
|
@@ -348,12 +318,6 @@ module Spree
|
|
|
348
318
|
users
|
|
349
319
|
end
|
|
350
320
|
|
|
351
|
-
def favicon
|
|
352
|
-
return unless favicon_image.attached? && favicon_image.variable?
|
|
353
|
-
|
|
354
|
-
favicon_image.variant(resize_to_limit: [32, 32])
|
|
355
|
-
end
|
|
356
|
-
|
|
357
321
|
def metric_unit_system?
|
|
358
322
|
preferred_unit_system == 'metric'
|
|
359
323
|
end
|
|
@@ -366,14 +330,6 @@ module Spree
|
|
|
366
330
|
@digital_shipping_category ||= ShippingCategory.find_or_create_by(name: 'Digital')
|
|
367
331
|
end
|
|
368
332
|
|
|
369
|
-
%w[customer_terms_of_service customer_privacy_policy customer_returns_policy customer_shipping_policy].each do |policy_method|
|
|
370
|
-
define_method policy_method do
|
|
371
|
-
Spree::Deprecation.warn("Store##{policy_method} is deprecated and will be removed in Spree 5.5. Please use Store#policies instead.")
|
|
372
|
-
|
|
373
|
-
ActionText::RichText.find_by(name: policy_method, record: self)
|
|
374
|
-
end
|
|
375
|
-
end
|
|
376
|
-
|
|
377
333
|
private
|
|
378
334
|
|
|
379
335
|
def ensure_default_market
|
|
@@ -395,7 +351,25 @@ module Spree
|
|
|
395
351
|
end
|
|
396
352
|
end
|
|
397
353
|
|
|
354
|
+
def create_default_policies
|
|
355
|
+
Spree::Events.disable do
|
|
356
|
+
[
|
|
357
|
+
translate_with_store_locale_fallback('spree.terms_of_service'),
|
|
358
|
+
translate_with_store_locale_fallback('spree.privacy_policy'),
|
|
359
|
+
translate_with_store_locale_fallback('spree.returns_policy'),
|
|
360
|
+
translate_with_store_locale_fallback('spree.shipping_policy')
|
|
361
|
+
].each do |policy_name|
|
|
362
|
+
# Manual exists?/create to work around Mobility bug with find_or_create_by
|
|
363
|
+
next if policies.with_matching_name(policy_name).exists?
|
|
364
|
+
|
|
365
|
+
policies.create(name: policy_name)
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
398
370
|
def ensure_default_taxonomies_are_created
|
|
371
|
+
Spree::Deprecation.warn('Store#ensure_default_taxonomies_are_created is deprecated and will be removed in Spree 5.5. Please remove it from your codebase')
|
|
372
|
+
|
|
399
373
|
Spree::Events.disable do
|
|
400
374
|
[
|
|
401
375
|
translate_with_store_locale_fallback('spree.taxonomy_categories_name'),
|
|
@@ -411,13 +385,16 @@ module Spree
|
|
|
411
385
|
end
|
|
412
386
|
|
|
413
387
|
def ensure_default_automatic_taxons
|
|
388
|
+
Spree::Deprecation.warn('Store#ensure_default_automatic_taxons is deprecated and will be removed in Spree 5.5. Please remove it from your codebase')
|
|
389
|
+
|
|
414
390
|
Spree::Events.disable do
|
|
415
391
|
# Use Mobility-safe lookup for taxonomy
|
|
416
392
|
collections_taxonomy = taxonomies.with_matching_name(translate_with_store_locale_fallback('spree.taxonomy_collections_name')).first
|
|
417
393
|
return unless collections_taxonomy.present?
|
|
418
394
|
|
|
419
395
|
automatic_taxons_config = [
|
|
420
|
-
{ name: translate_with_store_locale_fallback('spree.automatic_taxon_names.on_sale'),
|
|
396
|
+
{ name: translate_with_store_locale_fallback('spree.automatic_taxon_names.on_sale'),
|
|
397
|
+
rule_type: 'Spree::TaxonRules::Sale', rule_value: 'true' },
|
|
421
398
|
{ name: translate_with_store_locale_fallback('spree.automatic_taxon_names.new_arrivals'), rule_type: 'Spree::TaxonRules::AvailableOn', rule_value: 30 }
|
|
422
399
|
]
|
|
423
400
|
|
|
@@ -439,22 +416,6 @@ module Spree
|
|
|
439
416
|
end
|
|
440
417
|
end
|
|
441
418
|
|
|
442
|
-
def create_default_policies
|
|
443
|
-
Spree::Events.disable do
|
|
444
|
-
[
|
|
445
|
-
translate_with_store_locale_fallback('spree.terms_of_service'),
|
|
446
|
-
translate_with_store_locale_fallback('spree.privacy_policy'),
|
|
447
|
-
translate_with_store_locale_fallback('spree.returns_policy'),
|
|
448
|
-
translate_with_store_locale_fallback('spree.shipping_policy')
|
|
449
|
-
].each do |policy_name|
|
|
450
|
-
# Manual exists?/create to work around Mobility bug with find_or_create_by
|
|
451
|
-
next if policies.with_matching_name(policy_name).exists?
|
|
452
|
-
|
|
453
|
-
policies.create(name: policy_name)
|
|
454
|
-
end
|
|
455
|
-
end
|
|
456
|
-
end
|
|
457
|
-
|
|
458
419
|
# Translates a key using the store's default locale with fallback to :en
|
|
459
420
|
def translate_with_store_locale_fallback(key)
|
|
460
421
|
locale = default_locale.presence&.to_sym || :en
|