spree_core 5.2.1 → 5.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/finders/spree/line_items/find_by_variant.rb +1 -1
- data/app/helpers/spree/images_helper.rb +34 -18
- data/app/helpers/spree/mail_helper.rb +1 -1
- data/app/jobs/spree/imports/create_rows_job.rb +1 -1
- data/app/jobs/spree/variants/remove_line_item_job.rb +1 -1
- data/app/models/spree/asset.rb +26 -2
- data/app/models/spree/export.rb +1 -1
- data/app/models/spree/image/configuration/active_storage.rb +6 -15
- data/app/models/spree/image.rb +2 -1
- data/app/models/spree/import.rb +1 -1
- data/app/models/spree/order/gift_card.rb +3 -3
- data/app/models/spree/order/store_credit.rb +2 -2
- data/app/models/spree/order.rb +4 -4
- data/app/models/spree/order_contents.rb +9 -9
- data/app/models/spree/order_merger.rb +3 -3
- data/app/models/spree/page_sections/featured_taxon.rb +1 -2
- data/app/models/spree/promotion/actions/create_line_items.rb +6 -6
- data/app/models/spree/promotion_handler/coupon.rb +1 -1
- data/app/models/spree/shipping_method.rb +1 -1
- data/app/models/spree/store.rb +1 -1
- data/app/models/spree/themes/default.rb +7 -4
- data/app/presenters/spree/csv/product_variant_presenter.rb +1 -4
- data/app/services/spree/cart/add_item.rb +2 -2
- data/app/services/spree/cart/remove_item.rb +4 -4
- data/app/services/spree/cart/remove_line_item.rb +3 -3
- data/app/services/spree/cart/remove_out_of_stock_items.rb +1 -1
- data/app/services/spree/cart/set_quantity.rb +1 -1
- data/app/services/spree/checkout/advance.rb +1 -1
- data/app/services/spree/checkout/complete.rb +1 -1
- data/app/services/spree/data_feeds/google/rss.rb +4 -4
- data/app/services/spree/line_items/helper.rb +1 -1
- data/app/services/spree/shipments/helper.rb +2 -2
- data/lib/spree/core/configuration.rb +30 -0
- data/lib/spree/core/controller_helpers/auth.rb +1 -1
- data/lib/spree/core/controller_helpers/store.rb +1 -1
- data/lib/spree/core/dependencies.rb +2 -2
- data/lib/spree/core/dependencies_helper.rb +100 -5
- data/lib/spree/core/search/base.rb +1 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +34 -0
- data/lib/spree/testing_support/authorization_helpers.rb +2 -2
- data/lib/tasks/dependencies.rake +76 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40e658898150457b9405d6d68e58d78c1b31b23518e2ffdb6829d3bfb0d68a90
|
|
4
|
+
data.tar.gz: e36b7e5d2e39426e2ea9cb4078625f72c36ed58e626959b277174f7f033bdd1f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f01a4d3e5245986e4a851dc51fb18103405cf2fab2669a43d504c922a5e7284739a23f6cd17da01276a6ab9a71b4c9589667a4bd7e3373d2c83a9508ea9b87e4
|
|
7
|
+
data.tar.gz: 1be2f451d4c370d7c72eee740dfce1536308d61ad7f16c114a7db2eb2462401150ca874b56cda455645abccdc65e9bc97e5810ef82dac16a263ed64ea584c7f7
|
|
@@ -5,7 +5,7 @@ module Spree
|
|
|
5
5
|
line_item = order.line_items.loaded? ? order.line_items.detect { |li| li.variant_id == variant.id } : order.line_items.find_by(variant_id: variant.id)
|
|
6
6
|
|
|
7
7
|
if line_item
|
|
8
|
-
Spree
|
|
8
|
+
Spree.cart_compare_line_items_service.call(order: order, line_item: line_item, options: options).value
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
line_item
|
|
@@ -7,10 +7,17 @@ module Spree
|
|
|
7
7
|
# @param options [Hash] options for the image tag
|
|
8
8
|
# @option options [Integer] :width the width of the image
|
|
9
9
|
# @option options [Integer] :height the height of the image
|
|
10
|
+
# @option options [Symbol] :variant use a preprocessed named variant (e.g., :mini, :small, :medium, :large, :xlarge)
|
|
10
11
|
def spree_image_tag(image, options = {})
|
|
12
|
+
url_options = if options[:variant].present?
|
|
13
|
+
{ variant: options[:variant] }
|
|
14
|
+
else
|
|
15
|
+
{ width: options[:width], height: options[:height], format: options[:format] }
|
|
16
|
+
end
|
|
17
|
+
|
|
11
18
|
image_tag(
|
|
12
|
-
spree_image_url(image,
|
|
13
|
-
options
|
|
19
|
+
spree_image_url(image, url_options),
|
|
20
|
+
options.except(:variant, :format)
|
|
14
21
|
)
|
|
15
22
|
end
|
|
16
23
|
|
|
@@ -19,9 +26,17 @@ module Spree
|
|
|
19
26
|
return unless image.variable?
|
|
20
27
|
return if image.respond_to?(:attached?) && !image.attached?
|
|
21
28
|
url_helpers = respond_to?(:main_app) ? main_app : Rails.application.routes.url_helpers
|
|
29
|
+
|
|
30
|
+
# Use preprocessed named variant if specified (e.g., :mini, :small, :medium, :large, :xlarge)
|
|
31
|
+
if options[:variant].present?
|
|
32
|
+
return url_helpers.cdn_image_url(image.variant(options[:variant]))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Dynamic variant generation for width/height options
|
|
22
36
|
width = options[:width]
|
|
23
37
|
height = options[:height]
|
|
24
38
|
|
|
39
|
+
# Double dimensions for retina displays
|
|
25
40
|
width *= 2 if width.present?
|
|
26
41
|
height *= 2 if height.present?
|
|
27
42
|
|
|
@@ -74,27 +89,28 @@ module Spree
|
|
|
74
89
|
# @param options [Hash] options for the image variant
|
|
75
90
|
# @option options [Integer] :width the width of the image
|
|
76
91
|
# @option options [Integer] :height the height of the image
|
|
92
|
+
#
|
|
93
|
+
# @note The key order matters for variation digest matching with preprocessed variants.
|
|
94
|
+
# Active Storage reorders keys alphabetically, so use: format, resize_to_fill/limit, saver
|
|
95
|
+
# @note Use string values (not symbols) for format because variation keys are JSON-encoded
|
|
96
|
+
# in URLs and JSON converts symbols to strings, causing digest mismatches.
|
|
77
97
|
def spree_image_variant_options(options = {})
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
format_opt = options[:format]&.to_s
|
|
99
|
+
saver_options = format_opt == "png" ? png_saver_options : Spree::Asset::WEBP_SAVER_OPTIONS
|
|
100
|
+
format = format_opt || "webp"
|
|
101
|
+
|
|
102
|
+
# Build hash in alphabetical order to match Active Storage's key ordering
|
|
103
|
+
result = {}
|
|
104
|
+
result[:format] = format
|
|
105
|
+
result[:resize_to_fill] = options[:resize_to_fill] if options[:resize_to_fill]
|
|
106
|
+
result[:resize_to_limit] = options[:resize_to_limit] if options[:resize_to_limit]
|
|
107
|
+
result[:saver] = saver_options
|
|
108
|
+
result
|
|
82
109
|
end
|
|
83
110
|
|
|
84
111
|
private
|
|
85
112
|
|
|
86
|
-
def
|
|
87
|
-
{
|
|
88
|
-
strip: true,
|
|
89
|
-
quality: 75,
|
|
90
|
-
lossless: false,
|
|
91
|
-
alpha_q: 85,
|
|
92
|
-
reduction_effort: 6,
|
|
93
|
-
smart_subsample: true
|
|
94
|
-
}
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def png_variant_options
|
|
113
|
+
def png_saver_options
|
|
98
114
|
{
|
|
99
115
|
strip: true,
|
|
100
116
|
compression_level: 8,
|
|
@@ -5,7 +5,7 @@ module Spree
|
|
|
5
5
|
|
|
6
6
|
def variant_image_url(variant)
|
|
7
7
|
image = variant.default_image
|
|
8
|
-
image.present? && image.attached? ? spree_image_url(image,
|
|
8
|
+
image.present? && image.attached? ? spree_image_url(image, variant: :mini) : image_url('noimage/small.png')
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def name_for(order)
|
|
@@ -31,7 +31,7 @@ module Spree
|
|
|
31
31
|
|
|
32
32
|
upsert_options = {}
|
|
33
33
|
if %w[PostgreSQL SQLite].include?(ActiveRecord::Base.connection.adapter_name)
|
|
34
|
-
upsert_options[:unique_by] =
|
|
34
|
+
upsert_options[:unique_by] = :index_spree_import_rows_on_import_id_and_row_number
|
|
35
35
|
end
|
|
36
36
|
# NOTE: Ensure a DB-level unique index on [:import_id, :row_number] for all adapters.
|
|
37
37
|
|
|
@@ -4,7 +4,7 @@ module Spree
|
|
|
4
4
|
queue_as Spree.queues.variants
|
|
5
5
|
|
|
6
6
|
def perform(line_item:)
|
|
7
|
-
Spree
|
|
7
|
+
Spree.cart_remove_line_item_service.call(order: line_item.order, line_item: line_item)
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
data/app/models/spree/asset.rb
CHANGED
|
@@ -12,9 +12,33 @@ module Spree
|
|
|
12
12
|
belongs_to :viewable, polymorphic: true, touch: true
|
|
13
13
|
acts_as_list scope: [:viewable_id, :viewable_type]
|
|
14
14
|
|
|
15
|
-
delegate :key, :attached?, :variant, :variable?, :blob, :filename, to: :attachment
|
|
15
|
+
delegate :key, :attached?, :variant, :variable?, :blob, :filename, :variation, to: :attachment
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
WEBP_SAVER_OPTIONS = {
|
|
18
|
+
strip: true,
|
|
19
|
+
quality: 75,
|
|
20
|
+
lossless: false,
|
|
21
|
+
alpha_q: 85,
|
|
22
|
+
reduction_effort: 6,
|
|
23
|
+
smart_subsample: true
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
has_one_attached :attachment, service: Spree.public_storage_service_name do |attachable|
|
|
27
|
+
# Note: Key order matters for variation digest matching.
|
|
28
|
+
# Active Storage reorders keys alphabetically when calling variant(:name),
|
|
29
|
+
# so we must define them in alphabetical order: format, resize_to_fill, saver
|
|
30
|
+
#
|
|
31
|
+
# IMPORTANT: Use string values (not symbols) for format because the variation key
|
|
32
|
+
# is JSON-encoded in URLs. JSON converts symbols to strings, so "webp" != :webp
|
|
33
|
+
# after round-tripping, which causes digest mismatches.
|
|
34
|
+
Spree::Config.product_image_variant_sizes.each do |name, (width, height)|
|
|
35
|
+
attachable.variant name,
|
|
36
|
+
format: "webp",
|
|
37
|
+
resize_to_fill: [width, height],
|
|
38
|
+
saver: WEBP_SAVER_OPTIONS,
|
|
39
|
+
preprocessed: true
|
|
40
|
+
end
|
|
41
|
+
end
|
|
18
42
|
|
|
19
43
|
default_scope { includes(attachment_attachment: :blob) }
|
|
20
44
|
|
data/app/models/spree/export.rb
CHANGED
|
@@ -157,7 +157,7 @@ module Spree
|
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
def current_ability
|
|
160
|
-
@current_ability ||= Spree
|
|
160
|
+
@current_ability ||= Spree.ability_class.new(user, { store: store })
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
# eg. Spree::Exports::Products => products-store-my-store-code-20241030133348.csv
|
|
@@ -7,25 +7,16 @@ module Spree
|
|
|
7
7
|
included do
|
|
8
8
|
validates :attachment, attached: true, content_type: Rails.application.config.active_storage.web_image_content_types
|
|
9
9
|
|
|
10
|
+
# Returns image styles derived from Spree::Config.product_image_variant_sizes
|
|
11
|
+
# Format: { variant_name: 'WxH>' } for API compatibility
|
|
10
12
|
def self.styles
|
|
11
|
-
@styles ||=
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
product: '240x240>',
|
|
15
|
-
pdp_thumbnail: '160x200>',
|
|
16
|
-
plp_and_carousel: '448x600>',
|
|
17
|
-
plp_and_carousel_xs: '254x340>',
|
|
18
|
-
plp_and_carousel_sm: '350x468>',
|
|
19
|
-
plp_and_carousel_md: '222x297>',
|
|
20
|
-
plp_and_carousel_lg: '278x371>',
|
|
21
|
-
large: '600x600>',
|
|
22
|
-
plp: '278x371>',
|
|
23
|
-
zoomed: '650x870>'
|
|
24
|
-
}
|
|
13
|
+
@styles ||= Spree::Config.product_image_variant_sizes.transform_values do |dimensions|
|
|
14
|
+
"#{dimensions[0]}x#{dimensions[1]}>"
|
|
15
|
+
end
|
|
25
16
|
end
|
|
26
17
|
|
|
27
18
|
def default_style
|
|
28
|
-
:
|
|
19
|
+
:small
|
|
29
20
|
end
|
|
30
21
|
end
|
|
31
22
|
end
|
data/app/models/spree/image.rb
CHANGED
data/app/models/spree/import.rb
CHANGED
|
@@ -217,7 +217,7 @@ module Spree
|
|
|
217
217
|
# Returns the current ability for the import
|
|
218
218
|
# @return [Spree::Ability]
|
|
219
219
|
def current_ability
|
|
220
|
-
@current_ability ||= Spree
|
|
220
|
+
@current_ability ||= Spree.ability_class.new(user, { store: store })
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
class << self
|
|
@@ -25,13 +25,13 @@ module Spree
|
|
|
25
25
|
# @param gift_card [Spree::GiftCard] the gift card to apply
|
|
26
26
|
# @return [Spree::Order] the order with the gift card applied
|
|
27
27
|
def apply_gift_card(gift_card)
|
|
28
|
-
Spree
|
|
28
|
+
Spree.gift_card_apply_service.call(gift_card: gift_card, order: self)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# Removes a gift card from the order
|
|
32
32
|
# @return [Spree::Order] the order with the gift card removed
|
|
33
33
|
def remove_gift_card
|
|
34
|
-
Spree
|
|
34
|
+
Spree.gift_card_remove_service.call(order: self)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def recalculate_gift_card
|
|
@@ -44,7 +44,7 @@ module Spree
|
|
|
44
44
|
def redeem_gift_card
|
|
45
45
|
return unless gift_card.present?
|
|
46
46
|
|
|
47
|
-
Spree
|
|
47
|
+
Spree.gift_card_redeem_service.call(gift_card: gift_card)
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -2,11 +2,11 @@ module Spree
|
|
|
2
2
|
class Order < Spree.base_class
|
|
3
3
|
module StoreCredit
|
|
4
4
|
def add_store_credit_payments(amount = nil)
|
|
5
|
-
Spree
|
|
5
|
+
Spree.checkout_add_store_credit_service.call(order: self, amount: amount)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def remove_store_credit_payments
|
|
9
|
-
Spree
|
|
9
|
+
Spree.checkout_remove_store_credit_service.call(order: self)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def covered_by_store_credit?
|
data/app/models/spree/order.rb
CHANGED
|
@@ -349,7 +349,7 @@ module Spree
|
|
|
349
349
|
end
|
|
350
350
|
|
|
351
351
|
def updater
|
|
352
|
-
@updater ||= Spree
|
|
352
|
+
@updater ||= Spree.order_updater.new(self)
|
|
353
353
|
end
|
|
354
354
|
|
|
355
355
|
def update_with_updater!
|
|
@@ -407,7 +407,7 @@ module Spree
|
|
|
407
407
|
def find_line_item_by_variant(variant, options = {})
|
|
408
408
|
line_items.detect do |line_item|
|
|
409
409
|
line_item.variant_id == variant.id &&
|
|
410
|
-
Spree
|
|
410
|
+
Spree.cart_compare_line_items_service.new.call(order: self, line_item: line_item, options: options).value
|
|
411
411
|
end
|
|
412
412
|
end
|
|
413
413
|
|
|
@@ -578,7 +578,7 @@ module Spree
|
|
|
578
578
|
def empty!
|
|
579
579
|
raise Spree.t(:cannot_empty_completed_order) if completed?
|
|
580
580
|
|
|
581
|
-
result = Spree
|
|
581
|
+
result = Spree.cart_empty_service.call(order: self)
|
|
582
582
|
result.value
|
|
583
583
|
end
|
|
584
584
|
|
|
@@ -957,7 +957,7 @@ module Spree
|
|
|
957
957
|
if gift_card.present?
|
|
958
958
|
recalculate_gift_card
|
|
959
959
|
elsif using_store_credit?
|
|
960
|
-
Spree
|
|
960
|
+
Spree.checkout_add_store_credit_service.call(order: self)
|
|
961
961
|
end
|
|
962
962
|
end
|
|
963
963
|
end
|
|
@@ -7,17 +7,17 @@ module Spree
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def add(variant, quantity = 1, options = {})
|
|
10
|
-
Spree
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
Spree.cart_add_item_service.call(order: order,
|
|
11
|
+
variant: variant,
|
|
12
|
+
quantity: quantity,
|
|
13
|
+
options: options).value
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def remove(variant, quantity = 1, options = {})
|
|
17
|
-
Spree
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
Spree.cart_remove_item_service.call(order: order,
|
|
18
|
+
variant: variant,
|
|
19
|
+
quantity: quantity,
|
|
20
|
+
options: options).value
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def remove_line_item(line_item, options = {})
|
|
@@ -25,7 +25,7 @@ module Spree
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def update_cart(params)
|
|
28
|
-
Spree
|
|
28
|
+
Spree.cart_update_service.call(order: order, params: params).value
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -35,9 +35,9 @@ module Spree
|
|
|
35
35
|
def find_matching_line_item(other_order_line_item)
|
|
36
36
|
order.line_items.detect do |my_li|
|
|
37
37
|
my_li.variant == other_order_line_item.variant &&
|
|
38
|
-
Spree
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
Spree.cart_compare_line_items_service.new.call(order: order,
|
|
39
|
+
line_item: my_li,
|
|
40
|
+
options: other_order_line_item.serializable_hash).value
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -61,8 +61,7 @@ module Spree
|
|
|
61
61
|
sort_by: 'default'
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
products_finder
|
|
65
|
-
products_finder.new(scope: store.products, params: finder_params).execute.limit(preferred_max_products_to_show)
|
|
64
|
+
Spree.products_finder.new(scope: store.products, params: finder_params).execute.limit(preferred_max_products_to_show)
|
|
66
65
|
end
|
|
67
66
|
end
|
|
68
67
|
|
|
@@ -47,9 +47,9 @@ module Spree
|
|
|
47
47
|
current_quantity = order.quantity_of(item.variant)
|
|
48
48
|
next unless current_quantity < item.quantity && item_available?(item)
|
|
49
49
|
|
|
50
|
-
line_item = Spree
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
line_item = Spree.cart_add_item_service.call(order: order,
|
|
51
|
+
variant: item.variant,
|
|
52
|
+
quantity: item.quantity - current_quantity).value
|
|
53
53
|
action_taken = true if line_item.try(:valid?)
|
|
54
54
|
end
|
|
55
55
|
action_taken
|
|
@@ -68,9 +68,9 @@ module Spree
|
|
|
68
68
|
line_item = order.find_line_item_by_variant(item.variant)
|
|
69
69
|
next unless line_item.present?
|
|
70
70
|
|
|
71
|
-
Spree
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
Spree.cart_remove_item_service.call(order: order,
|
|
72
|
+
variant: item.variant,
|
|
73
|
+
quantity: (item.quantity || 1))
|
|
74
74
|
action_taken = true
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -133,7 +133,7 @@ module Spree
|
|
|
133
133
|
line_item = order.find_line_item_by_variant(item.variant)
|
|
134
134
|
next if line_item.blank?
|
|
135
135
|
|
|
136
|
-
Spree
|
|
136
|
+
Spree.cart_remove_item_service.call(order: order, variant: item.variant, quantity: item.quantity)
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
|
|
@@ -77,7 +77,7 @@ module Spree
|
|
|
77
77
|
|
|
78
78
|
# your shipping method subclasses can override this method to provide a custom tracking number service
|
|
79
79
|
def tracking_number_service(tracking)
|
|
80
|
-
@tracking_number_service ||= Spree
|
|
80
|
+
@tracking_number_service ||= Spree.tracking_number_service.new(tracking)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def self.calculators
|
data/app/models/spree/store.rb
CHANGED
|
@@ -85,10 +85,13 @@ module Spree
|
|
|
85
85
|
preference :border_shadow_blur, :integer, default: 5
|
|
86
86
|
|
|
87
87
|
# PRODUCT IMAGES
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
preference :
|
|
88
|
+
# These defaults match preprocessed variant sizes for optimal performance:
|
|
89
|
+
# Desktop (360x360) → large variant (720x720 at 2x)
|
|
90
|
+
# Mobile (200x200) → medium variant (400x400 at 2x)
|
|
91
|
+
preference :product_listing_image_height, :integer, default: 360
|
|
92
|
+
preference :product_listing_image_width, :integer, default: 360
|
|
93
|
+
preference :product_listing_image_height_mobile, :integer, default: 200
|
|
94
|
+
preference :product_listing_image_width_mobile, :integer, default: 200
|
|
92
95
|
end
|
|
93
96
|
end
|
|
94
97
|
end
|
|
@@ -6,7 +6,7 @@ module Spree
|
|
|
6
6
|
def call(order:, variant:, quantity: nil, public_metadata: {}, private_metadata: {}, options: {})
|
|
7
7
|
ApplicationRecord.transaction do
|
|
8
8
|
run :add_to_line_item
|
|
9
|
-
run Spree
|
|
9
|
+
run Spree.cart_recalculate_service
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ module Spree
|
|
|
18
18
|
|
|
19
19
|
return failure(variant, "#{variant.name} is not available in #{order.currency}") if variant.amount_in(order.currency).nil?
|
|
20
20
|
|
|
21
|
-
line_item = Spree
|
|
21
|
+
line_item = Spree.line_item_by_variant_finder.new.execute(order: order, variant: variant, options: options)
|
|
22
22
|
|
|
23
23
|
line_item_created = line_item.nil?
|
|
24
24
|
if line_item.nil?
|
|
@@ -9,9 +9,9 @@ module Spree
|
|
|
9
9
|
|
|
10
10
|
ActiveRecord::Base.transaction do
|
|
11
11
|
line_item = remove_from_line_item(order: order, variant: variant, quantity: quantity, options: options)
|
|
12
|
-
Spree
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
Spree.cart_recalculate_service.call(line_item: line_item,
|
|
13
|
+
order: order,
|
|
14
|
+
options: options)
|
|
15
15
|
success(line_item)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -19,7 +19,7 @@ module Spree
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
21
|
def remove_from_line_item(order:, variant:, quantity:, options:)
|
|
22
|
-
line_item = Spree
|
|
22
|
+
line_item = Spree.line_item_by_variant_finder.new.execute(order: order, variant: variant, options: options)
|
|
23
23
|
|
|
24
24
|
raise ActiveRecord::RecordNotFound if line_item.nil?
|
|
25
25
|
|
|
@@ -7,9 +7,9 @@ module Spree
|
|
|
7
7
|
options ||= {}
|
|
8
8
|
ActiveRecord::Base.transaction do
|
|
9
9
|
order.line_items.destroy(line_item)
|
|
10
|
-
Spree
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Spree.cart_recalculate_service.new.call(order: order,
|
|
11
|
+
line_item: line_item,
|
|
12
|
+
options: options)
|
|
13
13
|
end
|
|
14
14
|
success(line_item)
|
|
15
15
|
end
|
|
@@ -15,7 +15,7 @@ module Spree
|
|
|
15
15
|
transitions_count = 0
|
|
16
16
|
|
|
17
17
|
until cannot_make_transition?(order, state)
|
|
18
|
-
next_result = Spree
|
|
18
|
+
next_result = Spree.checkout_next_service.call(order: order)
|
|
19
19
|
return failure(order, order.errors) if next_result.failure? && (transitions_count.zero? || state.present?)
|
|
20
20
|
|
|
21
21
|
transitions_count +=1
|
|
@@ -4,7 +4,7 @@ module Spree
|
|
|
4
4
|
prepend Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
6
|
def call(order:)
|
|
7
|
-
Spree
|
|
7
|
+
Spree.checkout_next_service.call(order: order) until cannot_make_transition?(order)
|
|
8
8
|
|
|
9
9
|
if order.reload.complete?
|
|
10
10
|
success(order)
|
|
@@ -87,19 +87,19 @@ module Spree
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def optional_attributes
|
|
90
|
-
Spree
|
|
90
|
+
Spree.data_feeds_google_optional_attributes_service.new
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
def required_attributes
|
|
94
|
-
Spree
|
|
94
|
+
Spree.data_feeds_google_required_attributes_service.new
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def optional_sub_attributes
|
|
98
|
-
Spree
|
|
98
|
+
Spree.data_feeds_google_optional_sub_attributes_service.new
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def products_list
|
|
102
|
-
Spree
|
|
102
|
+
Spree.data_feeds_google_products_list.new
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
end
|
|
@@ -12,11 +12,11 @@ module Spree
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def add_item_service
|
|
15
|
-
Spree
|
|
15
|
+
Spree.cart_add_item_service
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def remove_item_service
|
|
19
|
-
Spree
|
|
19
|
+
Spree.cart_remove_item_service
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -46,6 +46,36 @@ module Spree
|
|
|
46
46
|
preference :expedited_exchanges_days_window, :integer, default: 14 # the amount of days the customer has to return their item after the expedited exchange is shipped in order to avoid being charged
|
|
47
47
|
preference :geocode_addresses, :boolean, default: true
|
|
48
48
|
preference :images_save_from_url_job_attempts, :integer, default: 5
|
|
49
|
+
|
|
50
|
+
# Preprocessed product image variant sizes at 2x retina resolution.
|
|
51
|
+
# These variants are generated on upload to reduce runtime processing.
|
|
52
|
+
# When using spree_image_tag, pass variant option instead of width and height.
|
|
53
|
+
#
|
|
54
|
+
# Default sizes:
|
|
55
|
+
# mini (128x128) - admin thumbnails, checkout line items
|
|
56
|
+
# small (256x256) - cart/order items, gallery thumbnails
|
|
57
|
+
# medium (400x400) - mobile listing, admin media
|
|
58
|
+
# large (720x720) - product listing, mobile gallery
|
|
59
|
+
# xlarge (2000x2000) - gallery main, lightbox
|
|
60
|
+
#
|
|
61
|
+
# To customize, override in your initializer:
|
|
62
|
+
# Spree::Config.product_image_variant_sizes = {
|
|
63
|
+
# mini: [128, 128],
|
|
64
|
+
# small: [256, 256],
|
|
65
|
+
# # ... your custom sizes
|
|
66
|
+
# }
|
|
67
|
+
attr_writer :product_image_variant_sizes
|
|
68
|
+
|
|
69
|
+
def product_image_variant_sizes
|
|
70
|
+
@product_image_variant_sizes ||= {
|
|
71
|
+
mini: [128, 128],
|
|
72
|
+
small: [256, 256],
|
|
73
|
+
medium: [400, 400],
|
|
74
|
+
large: [720, 720],
|
|
75
|
+
xlarge: [2000, 2000],
|
|
76
|
+
og_image: [1200, 630]
|
|
77
|
+
}
|
|
78
|
+
end
|
|
49
79
|
preference :layout, :string, deprecated: 'Please use Spree::Frontend::Config[:layout] instead'
|
|
50
80
|
preference :logo, :string, deprecated: true
|
|
51
81
|
preference :mailer_logo, :string, deprecated: true
|
|
@@ -17,7 +17,7 @@ module Spree
|
|
|
17
17
|
|
|
18
18
|
# Needs to be overridden so that we use Spree's Ability rather than anyone else's.
|
|
19
19
|
def current_ability
|
|
20
|
-
@current_ability ||= Spree
|
|
20
|
+
@current_ability ||= Spree.ability_class.new(try_spree_current_user, { store: current_store })
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def redirect_back_or_default(default)
|
|
@@ -97,8 +97,8 @@ module Spree
|
|
|
97
97
|
# finders
|
|
98
98
|
address_finder: 'Spree::Addresses::Find',
|
|
99
99
|
country_finder: 'Spree::Countries::Find',
|
|
100
|
-
cms_page_finder:
|
|
101
|
-
menu_finder:
|
|
100
|
+
cms_page_finder: nil, # LEGACY
|
|
101
|
+
menu_finder: nil, # LEGACY
|
|
102
102
|
current_order_finder: 'Spree::Orders::FindCurrent',
|
|
103
103
|
current_store_finder: 'Spree::Stores::FindCurrent',
|
|
104
104
|
completed_order_finder: 'Spree::Orders::FindComplete',
|
|
@@ -1,21 +1,103 @@
|
|
|
1
1
|
module Spree
|
|
2
|
+
class DependencyError < StandardError; end
|
|
3
|
+
|
|
2
4
|
module DependenciesHelper
|
|
5
|
+
# Patterns to skip when finding the actual caller (internal routing code)
|
|
6
|
+
INTERNAL_CALLER_PATTERNS = [
|
|
7
|
+
%r{lib/spree/core\.rb.*method_missing},
|
|
8
|
+
%r{lib/spree/api\.rb.*method_missing}
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
3
11
|
def self.included(base)
|
|
4
12
|
injection_points = base::INJECTION_POINTS_WITH_DEFAULTS.keys.freeze
|
|
5
13
|
base.const_set(:INJECTION_POINTS, injection_points)
|
|
6
|
-
|
|
14
|
+
|
|
15
|
+
injection_points.each do |point|
|
|
16
|
+
# Original getter - returns raw value (string, class, or proc result)
|
|
17
|
+
# BACKWARDS COMPATIBLE: Spree::Dependencies.foo.constantize still works
|
|
18
|
+
base.attr_reader(point)
|
|
19
|
+
|
|
20
|
+
# Setter with override tracking
|
|
21
|
+
# BACKWARDS COMPATIBLE: Spree::Dependencies.foo = "MyClass" still works
|
|
22
|
+
base.define_method("#{point}=") do |value|
|
|
23
|
+
@overrides ||= {}
|
|
24
|
+
@overrides[point] = {
|
|
25
|
+
value: value,
|
|
26
|
+
source: find_caller_source,
|
|
27
|
+
set_at: Time.current
|
|
28
|
+
}
|
|
29
|
+
# Clear memoized class when value changes
|
|
30
|
+
remove_instance_variable("@#{point}_resolved") if instance_variable_defined?("@#{point}_resolved")
|
|
31
|
+
instance_variable_set("@#{point}", value)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns resolved class with memoization
|
|
35
|
+
# Usage: Spree::Dependencies.foo_class or Spree.foo
|
|
36
|
+
base.define_method("#{point}_class") do
|
|
37
|
+
return instance_variable_get("@#{point}_resolved") if instance_variable_defined?("@#{point}_resolved")
|
|
38
|
+
|
|
39
|
+
value = send(point)
|
|
40
|
+
resolved = case value
|
|
41
|
+
when String then value.constantize
|
|
42
|
+
when Proc then value.call.then { |v| v.is_a?(String) ? v.constantize : v }
|
|
43
|
+
else value
|
|
44
|
+
end
|
|
45
|
+
instance_variable_set("@#{point}_resolved", resolved)
|
|
46
|
+
resolved
|
|
47
|
+
end
|
|
48
|
+
end
|
|
7
49
|
end
|
|
8
50
|
|
|
9
51
|
def initialize
|
|
10
52
|
set_default_values
|
|
11
53
|
end
|
|
12
54
|
|
|
55
|
+
# Returns hash of all overridden dependencies with metadata
|
|
56
|
+
def overrides
|
|
57
|
+
@overrides || {}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Check if a specific dependency has been overridden
|
|
61
|
+
def overridden?(name)
|
|
62
|
+
overrides.key?(name.to_sym)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get override info for a specific dependency
|
|
66
|
+
def override_info(name)
|
|
67
|
+
overrides[name.to_sym]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns array of all dependencies with current values and metadata
|
|
13
71
|
def current_values
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
72
|
+
self.class::INJECTION_POINTS.map do |point|
|
|
73
|
+
default = self.class::INJECTION_POINTS_WITH_DEFAULTS[point]
|
|
74
|
+
default_val = default.respond_to?(:call) ? default.call : default
|
|
75
|
+
current = send(point)
|
|
76
|
+
|
|
77
|
+
{
|
|
78
|
+
name: point,
|
|
79
|
+
current: current,
|
|
80
|
+
default: default_val,
|
|
81
|
+
# Use overridden? to check if setter was called, not value comparison
|
|
82
|
+
# Value comparison fails for proc-based defaults that reference other dependencies
|
|
83
|
+
overridden: overridden?(point),
|
|
84
|
+
override_info: overrides[point]
|
|
85
|
+
}
|
|
17
86
|
end
|
|
18
|
-
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Validate all dependencies can be resolved to classes
|
|
90
|
+
# Raises Spree::DependencyError if any dependency is invalid
|
|
91
|
+
def validate!
|
|
92
|
+
errors = []
|
|
93
|
+
self.class::INJECTION_POINTS.each do |point|
|
|
94
|
+
send("#{point}_class")
|
|
95
|
+
rescue NameError => e
|
|
96
|
+
errors << { name: point, value: send(point), error: e.message }
|
|
97
|
+
end
|
|
98
|
+
raise Spree::DependencyError, "Invalid dependencies: #{errors.map { |e| e[:name] }.join(', ')}" if errors.any?
|
|
99
|
+
|
|
100
|
+
true
|
|
19
101
|
end
|
|
20
102
|
|
|
21
103
|
private
|
|
@@ -26,5 +108,18 @@ module Spree
|
|
|
26
108
|
instance_variable_set("@#{injection_point}", value)
|
|
27
109
|
end
|
|
28
110
|
end
|
|
111
|
+
|
|
112
|
+
# Find the actual caller source, skipping internal DSL routing code
|
|
113
|
+
# This ensures Spree.foo = X reports the user's code location, not method_missing
|
|
114
|
+
def find_caller_source
|
|
115
|
+
caller_locations(2, 10).each do |location|
|
|
116
|
+
location_str = location.to_s
|
|
117
|
+
next if INTERNAL_CALLER_PATTERNS.any? { |pattern| location_str.match?(pattern) }
|
|
118
|
+
|
|
119
|
+
return location_str
|
|
120
|
+
end
|
|
121
|
+
# Fallback to immediate caller if no external caller found
|
|
122
|
+
caller_locations(2, 1).first.to_s
|
|
123
|
+
end
|
|
29
124
|
end
|
|
30
125
|
end
|
|
@@ -32,7 +32,7 @@ module Spree
|
|
|
32
32
|
def extended_base_scope
|
|
33
33
|
base_scope = current_store.products.spree_base_scopes
|
|
34
34
|
base_scope = get_products_conditions_for(base_scope, keywords)
|
|
35
|
-
base_scope = Spree
|
|
35
|
+
base_scope = Spree.products_finder.new(**product_finder_params(base_scope)).execute
|
|
36
36
|
base_scope = add_search_scopes(base_scope)
|
|
37
37
|
add_eagerload_scopes(base_scope)
|
|
38
38
|
end
|
data/lib/spree/core/version.rb
CHANGED
data/lib/spree/core.rb
CHANGED
|
@@ -374,6 +374,40 @@ module Spree
|
|
|
374
374
|
@permissions ||= PermissionConfiguration.new
|
|
375
375
|
end
|
|
376
376
|
|
|
377
|
+
class << self
|
|
378
|
+
# Dynamic methods for core dependencies
|
|
379
|
+
#
|
|
380
|
+
# @example Getting a dependency (returns resolved class)
|
|
381
|
+
# Spree.cart_add_item_service.call(order: order, variant: variant)
|
|
382
|
+
#
|
|
383
|
+
# @example Setting a dependency
|
|
384
|
+
# Spree.cart_add_item_service = MyApp::CartAddItem
|
|
385
|
+
def method_missing(method_name, *args, &block)
|
|
386
|
+
base_name = method_name.to_s.chomp('=').to_sym
|
|
387
|
+
|
|
388
|
+
return super unless core_dependency?(base_name)
|
|
389
|
+
|
|
390
|
+
if method_name.to_s.end_with?('=')
|
|
391
|
+
Spree::Dependencies.send(method_name, args.first)
|
|
392
|
+
else
|
|
393
|
+
# Returns resolved class (not string)
|
|
394
|
+
Spree::Dependencies.send("#{method_name}_class")
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
399
|
+
base_name = method_name.to_s.chomp('=').to_sym
|
|
400
|
+
core_dependency?(base_name) || super
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
private
|
|
404
|
+
|
|
405
|
+
def core_dependency?(name)
|
|
406
|
+
defined?(Spree::Dependencies) &&
|
|
407
|
+
Spree::Dependencies.class::INJECTION_POINTS.include?(name)
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
377
411
|
module Core
|
|
378
412
|
autoload :ProductFilters, 'spree/core/product_filters'
|
|
379
413
|
autoload :TokenGenerator, 'spree/core/token_generator'
|
|
@@ -30,7 +30,7 @@ module Spree
|
|
|
30
30
|
|
|
31
31
|
def stub_authorization!
|
|
32
32
|
ability = build_ability
|
|
33
|
-
ability_class = Spree
|
|
33
|
+
ability_class = Spree.ability_class
|
|
34
34
|
|
|
35
35
|
after(:all) do
|
|
36
36
|
ability_class.remove_ability(ability)
|
|
@@ -51,7 +51,7 @@ module Spree
|
|
|
51
51
|
|
|
52
52
|
def custom_authorization!(&block)
|
|
53
53
|
ability = build_ability(&block)
|
|
54
|
-
ability_class = Spree
|
|
54
|
+
ability_class = Spree.ability_class
|
|
55
55
|
|
|
56
56
|
after(:all) do
|
|
57
57
|
ability_class.remove_ability(ability)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
namespace :spree do
|
|
2
|
+
namespace :dependencies do
|
|
3
|
+
desc 'List all Spree dependencies with their current values'
|
|
4
|
+
task list: :environment do
|
|
5
|
+
print_dependencies('CORE', Spree::Dependencies)
|
|
6
|
+
print_dependencies('API', Spree::Api::Dependencies) if defined?(Spree::Api::Dependencies)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
desc 'Show only overridden dependencies'
|
|
10
|
+
task overrides: :environment do
|
|
11
|
+
core_overrides = Spree::Dependencies.current_values.select { |d| d[:overridden] }
|
|
12
|
+
api_overrides = if defined?(Spree::Api::Dependencies)
|
|
13
|
+
Spree::Api::Dependencies.current_values.select { |d| d[:overridden] }
|
|
14
|
+
else
|
|
15
|
+
[]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if core_overrides.empty? && api_overrides.empty?
|
|
19
|
+
puts 'No dependencies have been overridden.'
|
|
20
|
+
else
|
|
21
|
+
print_overrides('Core', core_overrides) if core_overrides.any?
|
|
22
|
+
print_overrides('API', api_overrides) if api_overrides.any?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
desc 'Validate all dependencies resolve to valid classes'
|
|
27
|
+
task validate: :environment do
|
|
28
|
+
errors = validate_dependencies('Core', Spree::Dependencies)
|
|
29
|
+
errors += validate_dependencies('API', Spree::Api::Dependencies) if defined?(Spree::Api::Dependencies)
|
|
30
|
+
|
|
31
|
+
puts "\n"
|
|
32
|
+
if errors.any?
|
|
33
|
+
puts "\n\e[31m#{errors.size} invalid dependencies:\e[0m"
|
|
34
|
+
errors.each { |err| puts " [#{err[:source]}] #{err[:name]}: #{err[:error]}" }
|
|
35
|
+
exit 1
|
|
36
|
+
else
|
|
37
|
+
puts "\e[32mAll dependencies valid\e[0m"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def print_dependencies(name, deps)
|
|
42
|
+
puts "\n[#{name}]"
|
|
43
|
+
|
|
44
|
+
# Calculate max width for alignment
|
|
45
|
+
max_name_width = deps.class::INJECTION_POINTS.map(&:length).max
|
|
46
|
+
|
|
47
|
+
deps.current_values.each do |dep|
|
|
48
|
+
status = dep[:overridden] ? ' [OVERRIDDEN]' : ''
|
|
49
|
+
puts "#{dep[:name].to_s.ljust(max_name_width)} #{dep[:current]}#{status}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def print_overrides(name, overrides)
|
|
54
|
+
puts "\n[#{name} OVERRIDES]"
|
|
55
|
+
|
|
56
|
+
max_name_width = overrides.map { |d| d[:name].length }.max
|
|
57
|
+
|
|
58
|
+
overrides.each do |dep|
|
|
59
|
+
source = dep[:override_info] ? " (#{dep[:override_info][:source]})" : ''
|
|
60
|
+
puts "#{dep[:name].to_s.ljust(max_name_width)} #{dep[:default]} -> #{dep[:current]}#{source}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def validate_dependencies(name, deps)
|
|
65
|
+
errors = []
|
|
66
|
+
deps.class::INJECTION_POINTS.each do |point|
|
|
67
|
+
deps.send("#{point}_class")
|
|
68
|
+
print "\e[32m.\e[0m"
|
|
69
|
+
rescue NameError => e
|
|
70
|
+
errors << { source: name, name: point, error: e.message }
|
|
71
|
+
print "\e[31mF\e[0m"
|
|
72
|
+
end
|
|
73
|
+
errors
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.2.
|
|
4
|
+
version: 5.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sean Schofield
|
|
@@ -1665,6 +1665,7 @@ files:
|
|
|
1665
1665
|
- lib/spree/translation_migrations.rb
|
|
1666
1666
|
- lib/spree_core.rb
|
|
1667
1667
|
- lib/tasks/core.rake
|
|
1668
|
+
- lib/tasks/dependencies.rake
|
|
1668
1669
|
- lib/tasks/exchanges.rake
|
|
1669
1670
|
- vendor/javascript/@rails--request.js.js
|
|
1670
1671
|
- vendor/javascript/@stimulus-components--auto-submit.js
|
|
@@ -1677,9 +1678,9 @@ licenses:
|
|
|
1677
1678
|
- BSD-3-Clause
|
|
1678
1679
|
metadata:
|
|
1679
1680
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
1680
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v5.2.
|
|
1681
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v5.2.2
|
|
1681
1682
|
documentation_uri: https://docs.spreecommerce.org/
|
|
1682
|
-
source_code_uri: https://github.com/spree/spree/tree/v5.2.
|
|
1683
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.2.2
|
|
1683
1684
|
rdoc_options: []
|
|
1684
1685
|
require_paths:
|
|
1685
1686
|
- lib
|