spree_core 2.1.3 → 2.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/helpers/spree/admin/images_helper.rb +1 -1
- data/app/helpers/spree/base_helper.rb +5 -2
- data/app/models/spree/address.rb +9 -1
- data/app/models/spree/adjustment.rb +2 -2
- data/app/models/spree/calculator/default_tax.rb +5 -1
- data/app/models/spree/credit_card.rb +2 -0
- data/app/models/spree/gateway.rb +1 -1
- data/app/models/spree/inventory_unit.rb +5 -4
- data/app/models/spree/legacy_user.rb +2 -11
- data/app/models/spree/line_item.rb +2 -3
- data/app/models/spree/log_entry.rb +4 -0
- data/app/models/spree/option_type.rb +6 -0
- data/app/models/spree/option_value.rb +12 -1
- data/app/models/spree/order.rb +34 -16
- data/app/models/spree/order/checkout.rb +4 -0
- data/app/models/spree/order_inventory.rb +1 -1
- data/app/models/spree/payment.rb +10 -2
- data/app/models/spree/payment/processing.rb +5 -4
- data/app/models/spree/payment_method.rb +2 -0
- data/app/models/spree/price.rb +5 -0
- data/app/models/spree/product.rb +6 -5
- data/app/models/spree/product/scopes.rb +12 -6
- data/app/models/spree/product_property.rb +1 -1
- data/app/models/spree/promotion.rb +1 -8
- data/app/models/spree/promotion/rules/user_logged_in.rb +1 -3
- data/app/models/spree/property.rb +8 -0
- data/app/models/spree/shipment.rb +9 -14
- data/app/models/spree/shipping_method.rb +3 -2
- data/app/models/spree/shipping_rate.rb +7 -9
- data/app/models/spree/stock/estimator.rb +21 -14
- data/app/models/spree/stock/package.rb +1 -1
- data/app/models/spree/stock/packer.rb +1 -1
- data/app/models/spree/stock/quantifier.rb +11 -2
- data/app/models/spree/stock_item.rb +2 -2
- data/app/models/spree/stock_location.rb +8 -0
- data/app/models/spree/stock_movement.rb +3 -1
- data/app/models/spree/taxon.rb +2 -2
- data/app/models/spree/variant.rb +19 -4
- data/app/models/spree/zone.rb +1 -1
- data/app/views/spree/shared/_routes.html.erb +1 -1
- data/config/locales/en.yml +15 -1
- data/db/default/spree/countries.rb +7 -7
- data/db/migrate/20130417120034_add_index_to_source_columns_on_adjustments.rb +5 -0
- data/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb +5 -2
- data/db/migrate/20131026154747_add_track_inventory_to_variant.rb +5 -0
- data/db/migrate/20131120234456_add_updated_at_to_variants.rb +5 -0
- data/db/migrate/20131211192741_unique_shipping_method_categories.rb +24 -0
- data/db/migrate/20140120160805_add_index_to_variant_id_and_currency_on_prices.rb +5 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +14 -3
- data/lib/generators/spree/dummy/templates/rails/database.yml +10 -0
- data/lib/spree/core.rb +3 -0
- data/lib/spree/core/controller_helpers/order.rb +4 -1
- data/lib/spree/core/controller_helpers/ssl.rb +5 -7
- data/lib/spree/core/controller_helpers/strong_parameters.rb +6 -0
- data/lib/spree/core/delegate_belongs_to.rb +16 -10
- data/lib/spree/core/engine.rb +11 -2
- data/lib/spree/core/mail_method.rb +27 -0
- data/lib/spree/core/mail_settings.rb +33 -38
- data/lib/spree/core/permalinks.rb +5 -1
- data/lib/spree/core/s3_support.rb +1 -1
- data/lib/spree/core/user_address.rb +30 -0
- data/lib/spree/core/validators/email.rb +9 -3
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/i18n.rb +1 -0
- data/lib/spree/migrations.rb +55 -0
- data/lib/spree/money.rb +171 -1
- data/lib/spree/permitted_attributes.rb +6 -6
- data/lib/spree/testing_support/capybara_ext.rb +6 -5
- data/lib/spree/testing_support/controller_requests.rb +20 -4
- data/lib/spree/testing_support/factories/product_factory.rb +4 -0
- data/lib/spree/testing_support/factories/variant_factory.rb +15 -0
- metadata +158 -164
@@ -0,0 +1,27 @@
|
|
1
|
+
module Spree
|
2
|
+
module Core
|
3
|
+
class MailMethod
|
4
|
+
def initialize(options={})
|
5
|
+
end
|
6
|
+
|
7
|
+
def deliver!(mail)
|
8
|
+
if Config.enable_mail_delivery
|
9
|
+
mailer.deliver!(mail)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def mailer
|
14
|
+
mailer_class.new(mail_server_settings)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def mailer_class
|
19
|
+
Rails.env.test?? Mail::TestMailer : Mail::SMTP
|
20
|
+
end
|
21
|
+
|
22
|
+
def mail_server_settings
|
23
|
+
MailSettings.new.mail_server_settings
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -8,53 +8,48 @@ module Spree
|
|
8
8
|
# This makes it possible to configure the mail settings through an admin
|
9
9
|
# interface instead of requiring changes to the Rails envrionment file
|
10
10
|
def self.init
|
11
|
-
|
11
|
+
override! if override?
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.override?
|
15
15
|
Config.override_actionmailer_config
|
16
16
|
end
|
17
17
|
|
18
|
-
def override!
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
def self.override!
|
19
|
+
ActionMailer::Base.delivery_method = :spree
|
20
|
+
ActionMailer::Base.default_url_options[:host] ||= Config.site_url
|
21
|
+
end
|
22
|
+
|
23
|
+
def mail_server_settings
|
24
|
+
settings = if need_authentication?
|
25
|
+
basic_settings.merge(user_credentials)
|
26
|
+
else
|
27
|
+
basic_settings
|
28
|
+
end
|
29
|
+
|
30
|
+
settings.merge :enable_starttls_auto => secure_connection?
|
26
31
|
end
|
27
32
|
|
28
33
|
private
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
:authentication => Config.mail_auth_type }
|
49
|
-
end
|
50
|
-
|
51
|
-
def need_authentication?
|
52
|
-
Config.mail_auth_type != 'None'
|
53
|
-
end
|
54
|
-
|
55
|
-
def secure_connection?
|
56
|
-
Config.secure_connection_type == 'TLS'
|
57
|
-
end
|
34
|
+
def user_credentials
|
35
|
+
{ :user_name => Config.smtp_username,
|
36
|
+
:password => Config.smtp_password }
|
37
|
+
end
|
38
|
+
|
39
|
+
def basic_settings
|
40
|
+
{ :address => Config.mail_host,
|
41
|
+
:domain => Config.mail_domain,
|
42
|
+
:port => Config.mail_port,
|
43
|
+
:authentication => Config.mail_auth_type }
|
44
|
+
end
|
45
|
+
|
46
|
+
def need_authentication?
|
47
|
+
Config.mail_auth_type != 'None'
|
48
|
+
end
|
49
|
+
|
50
|
+
def secure_connection?
|
51
|
+
Config.secure_connection_type == 'TLS'
|
52
|
+
end
|
58
53
|
end
|
59
54
|
end
|
60
55
|
end
|
@@ -33,6 +33,10 @@ module Spree
|
|
33
33
|
permalink_options[:prefix] || ""
|
34
34
|
end
|
35
35
|
|
36
|
+
def permalink_length
|
37
|
+
permalink_options[:length] || 9
|
38
|
+
end
|
39
|
+
|
36
40
|
def permalink_order
|
37
41
|
order = permalink_options[:order]
|
38
42
|
"#{order} ASC," if order
|
@@ -40,7 +44,7 @@ module Spree
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def generate_permalink
|
43
|
-
"#{self.class.permalink_prefix}#{Array.new(
|
47
|
+
"#{self.class.permalink_prefix}#{Array.new(self.class.permalink_length){rand(9)}.join}"
|
44
48
|
end
|
45
49
|
|
46
50
|
def save_permalink(permalink_value=self.to_param)
|
@@ -15,7 +15,7 @@ module Spree
|
|
15
15
|
self.attachment_definitions[field][:s3_credentials] = s3_creds
|
16
16
|
self.attachment_definitions[field][:s3_headers] = ActiveSupport::JSON.decode(config[:s3_headers])
|
17
17
|
self.attachment_definitions[field][:bucket] = config[:s3_bucket]
|
18
|
-
self.attachment_definitions[field][:s3_protocol] = config[:s3_protocol].
|
18
|
+
self.attachment_definitions[field][:s3_protocol] = config[:s3_protocol].blank? ? '' : config[:s3_protocol].downcase
|
19
19
|
self.attachment_definitions[field][:s3_host_alias] = config[:s3_host_alias] unless config[:s3_host_alias].blank?
|
20
20
|
end
|
21
21
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Spree
|
2
|
+
module Core
|
3
|
+
module UserAddress
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
belongs_to :bill_address, foreign_key: :bill_address_id, class_name: 'Spree::Address'
|
8
|
+
alias_attribute :billing_address, :bill_address
|
9
|
+
|
10
|
+
belongs_to :ship_address, foreign_key: :ship_address_id, class_name: 'Spree::Address'
|
11
|
+
alias_attribute :shipping_address, :ship_address
|
12
|
+
|
13
|
+
def persist_order_address(order)
|
14
|
+
b_address = self.bill_address || self.build_bill_address
|
15
|
+
b_address.attributes = order.bill_address.attributes.except('id', 'updated_at', 'created_at')
|
16
|
+
b_address.save
|
17
|
+
self.update_attributes(bill_address_id: b_address.id)
|
18
|
+
|
19
|
+
# May not be present if delivery step has been removed
|
20
|
+
if order.ship_address
|
21
|
+
s_address = self.ship_address || self.build_ship_address
|
22
|
+
s_address.attributes = order.ship_address.attributes.except('id', 'updated_at', 'created_at')
|
23
|
+
s_address.save
|
24
|
+
self.update_attributes(ship_address_id: s_address.id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -3,10 +3,16 @@
|
|
3
3
|
require 'mail'
|
4
4
|
class EmailValidator < ActiveModel::EachValidator
|
5
5
|
def validate_each(record,attribute,value)
|
6
|
+
unless valid?(value)
|
7
|
+
record.errors.add(attribute, :invalid, {:value => value}.merge!(options))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid?(email)
|
6
12
|
begin
|
7
|
-
m = Mail::Address.new(
|
13
|
+
m = Mail::Address.new(email)
|
8
14
|
# We must check that value contains a domain and that value is an email address
|
9
|
-
r = m.domain && m.address ==
|
15
|
+
r = m.domain && m.address == email
|
10
16
|
t = m.__send__(:tree)
|
11
17
|
# We need to dig into treetop
|
12
18
|
# A valid domain must have dot_atom_text elements size > 1
|
@@ -18,6 +24,6 @@ class EmailValidator < ActiveModel::EachValidator
|
|
18
24
|
rescue Exception => e
|
19
25
|
r = false
|
20
26
|
end
|
21
|
-
|
27
|
+
r
|
22
28
|
end
|
23
29
|
end
|
data/lib/spree/core/version.rb
CHANGED
data/lib/spree/i18n.rb
CHANGED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Spree
|
2
|
+
class Migrations
|
3
|
+
attr_reader :config, :engine_name
|
4
|
+
|
5
|
+
# Takes the engine config block and engine name
|
6
|
+
def initialize(config, engine_name)
|
7
|
+
@config, @engine_name = config, engine_name
|
8
|
+
end
|
9
|
+
|
10
|
+
# Puts warning when any engine migration is not present on the Rails app
|
11
|
+
# db/migrate dir
|
12
|
+
#
|
13
|
+
# First split:
|
14
|
+
#
|
15
|
+
# ["20131128203548", "update_name_fields_on_spree_credit_cards.spree.rb"]
|
16
|
+
#
|
17
|
+
# Second split should give the engine_name of the migration
|
18
|
+
#
|
19
|
+
# ["update_name_fields_on_spree_credit_cards", "spree.rb"]
|
20
|
+
#
|
21
|
+
# Shouldn't run on test mode because migrations inside engine don't have
|
22
|
+
# engine name on the file name
|
23
|
+
def check
|
24
|
+
if File.exists?("config/spree.yml") && File.directory?("db/migrate")
|
25
|
+
engine_in_app = app_migrations.map do |file_name|
|
26
|
+
name, engine = file_name.split(".", 2)
|
27
|
+
next unless engine == "#{engine_name}.rb"
|
28
|
+
name
|
29
|
+
end.compact! || []
|
30
|
+
|
31
|
+
unless (engine_migrations.sort - engine_in_app.sort).empty?
|
32
|
+
puts "[#{engine_name.capitalize} WARNING] Missing migrations." \
|
33
|
+
"Run `bundle exec rake railties:install:migrations` to get them.\n\n"
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def engine_migrations
|
41
|
+
Dir.entries("#{config.root}/db/migrate").map do |file_name|
|
42
|
+
name = file_name.split("_", 2).last.split(".", 2).first
|
43
|
+
name.empty? ? next : name
|
44
|
+
end.compact! || []
|
45
|
+
end
|
46
|
+
|
47
|
+
def app_migrations
|
48
|
+
Dir.entries("db/migrate").map do |file_name|
|
49
|
+
next if [".", ".."].include? file_name
|
50
|
+
name = file_name.split("_", 2).last
|
51
|
+
name.empty? ? next : name
|
52
|
+
end.compact! || []
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/spree/money.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'money'
|
2
4
|
|
3
5
|
module Spree
|
@@ -7,7 +9,7 @@ module Spree
|
|
7
9
|
delegate :cents, :to => :money
|
8
10
|
|
9
11
|
def initialize(amount, options={})
|
10
|
-
@money =
|
12
|
+
@money = self.class.parse([amount, (options[:currency] || Spree::Config[:currency])].join)
|
11
13
|
@options = {}
|
12
14
|
@options[:with_currency] = Spree::Config[:display_currency]
|
13
15
|
@options[:symbol_position] = Spree::Config[:currency_symbol_position].to_sym
|
@@ -20,6 +22,174 @@ module Spree
|
|
20
22
|
@options[:symbol_position] = @options[:symbol_position].to_sym
|
21
23
|
end
|
22
24
|
|
25
|
+
# This method is being deprecated in Money 6.1.0, so now lives here.
|
26
|
+
def self.parse(input, currency = nil)
|
27
|
+
i = input.to_s.strip
|
28
|
+
|
29
|
+
# raise Money::Currency.table.collect{|c| c[1][:symbol]}.inspect
|
30
|
+
|
31
|
+
# Check the first character for a currency symbol, alternatively get it
|
32
|
+
# from the stated currency string
|
33
|
+
c = if ::Money.assume_from_symbol && i =~ /^(\$|€|£)/
|
34
|
+
case i
|
35
|
+
when /^\$/ then "USD"
|
36
|
+
when /^€/ then "EUR"
|
37
|
+
when /^£/ then "GBP"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
i[/[A-Z]{2,3}/]
|
41
|
+
end
|
42
|
+
|
43
|
+
# check that currency passed and embedded currency are the same,
|
44
|
+
# and negotiate the final currency
|
45
|
+
if currency.nil? and c.nil?
|
46
|
+
currency = ::Money.default_currency
|
47
|
+
elsif currency.nil?
|
48
|
+
currency = c
|
49
|
+
elsif c.nil?
|
50
|
+
currency = currency
|
51
|
+
elsif currency != c
|
52
|
+
# TODO: ParseError
|
53
|
+
raise ArgumentError, "Mismatching Currencies"
|
54
|
+
end
|
55
|
+
currency = ::Money::Currency.wrap(currency)
|
56
|
+
|
57
|
+
fractional = extract_cents(i, currency)
|
58
|
+
::Money.new(fractional, currency)
|
59
|
+
end
|
60
|
+
|
61
|
+
# This method is being deprecated in Money 6.1.0, so now lives here.
|
62
|
+
def self.extract_cents(input, currency = Money.default_currency)
|
63
|
+
# remove anything that's not a number, potential thousands_separator, or minus sign
|
64
|
+
num = input.gsub(/[^\d.,'-]/, '')
|
65
|
+
|
66
|
+
# set a boolean flag for if the number is negative or not
|
67
|
+
negative = num =~ /^-|-$/ ? true : false
|
68
|
+
|
69
|
+
# decimal mark character
|
70
|
+
decimal_char = currency.decimal_mark
|
71
|
+
|
72
|
+
# if negative, remove the minus sign from the number
|
73
|
+
# if it's not negative, the hyphen makes the value invalid
|
74
|
+
if negative
|
75
|
+
num = num.sub(/^-|-$/, '')
|
76
|
+
end
|
77
|
+
|
78
|
+
raise ArgumentError, "Invalid currency amount (hyphen)" if num.include?('-')
|
79
|
+
|
80
|
+
#if the number ends with punctuation, just throw it out. If it means decimal,
|
81
|
+
#it won't hurt anything. If it means a literal period or comma, this will
|
82
|
+
#save it from being mis-interpreted as a decimal.
|
83
|
+
num.chop! if num.match(/[\.|,]$/)
|
84
|
+
|
85
|
+
# gather all decimal_marks within the result number
|
86
|
+
used_delimiters = num.scan(/[^\d]/)
|
87
|
+
|
88
|
+
# determine the number of unique decimal_marks within the number
|
89
|
+
#
|
90
|
+
# e.g.
|
91
|
+
# $1,234,567.89 would return 2 (, and .)
|
92
|
+
# $125,00 would return 1
|
93
|
+
# $199 would return 0
|
94
|
+
# $1 234,567.89 would raise an error (decimal_marks are space, comma, and period)
|
95
|
+
case used_delimiters.uniq.length
|
96
|
+
# no decimal_mark or thousands_separator; major (dollars) is the number, and minor (cents) is 0
|
97
|
+
when 0 then major, minor = num, 0
|
98
|
+
|
99
|
+
# two decimal_marks, so we know the last item in this array is the
|
100
|
+
# major/minor thousands_separator and the rest are decimal_marks
|
101
|
+
when 2
|
102
|
+
thousands_separator, decimal_mark = used_delimiters.uniq
|
103
|
+
|
104
|
+
# remove all thousands_separator, split on the decimal_mark
|
105
|
+
major, minor = num.gsub(thousands_separator, '').split(decimal_mark)
|
106
|
+
min = 0 unless min
|
107
|
+
when 1
|
108
|
+
# we can't determine if the comma or period is supposed to be a decimal_mark or a thousands_separator
|
109
|
+
# e.g.
|
110
|
+
# 1,00 - comma is a thousands_separator
|
111
|
+
# 1.000 - period is a thousands_separator
|
112
|
+
# 1,000 - comma is a decimal_mark
|
113
|
+
# 1,000,000 - comma is a decimal_mark
|
114
|
+
# 10000,00 - comma is a thousands_separator
|
115
|
+
# 1000,000 - comma is a thousands_separator
|
116
|
+
|
117
|
+
# assign first decimal_mark for reusability
|
118
|
+
decimal_mark = used_delimiters.first
|
119
|
+
|
120
|
+
# When we have identified the decimal mark character
|
121
|
+
if decimal_char == decimal_mark
|
122
|
+
major, minor = num.split(decimal_char)
|
123
|
+
|
124
|
+
else
|
125
|
+
# decimal_mark is used as a decimal_mark when there are multiple instances, always
|
126
|
+
if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
|
127
|
+
major, minor = num.gsub(decimal_mark, ''), 0
|
128
|
+
else
|
129
|
+
# ex: 1,000 - 1.0000 - 10001.000
|
130
|
+
# split number into possible major (dollars) and minor (cents) values
|
131
|
+
possible_major, possible_minor = num.split(decimal_mark)
|
132
|
+
possible_major ||= "0"
|
133
|
+
possible_minor ||= "00"
|
134
|
+
|
135
|
+
# if the minor (cents) length isn't 3, assign major/minor from the possibles
|
136
|
+
# e.g.
|
137
|
+
# 1,00 => 1.00
|
138
|
+
# 1.0000 => 1.00
|
139
|
+
# 1.2 => 1.20
|
140
|
+
if possible_minor.length != 3 # thousands_separator
|
141
|
+
major, minor = possible_major, possible_minor
|
142
|
+
else
|
143
|
+
# minor length is three
|
144
|
+
# let's try to figure out intent of the thousands_separator
|
145
|
+
|
146
|
+
# the major length is greater than three, which means
|
147
|
+
# the comma or period is used as a thousands_separator
|
148
|
+
# e.g.
|
149
|
+
# 1000,000
|
150
|
+
# 100000,000
|
151
|
+
if possible_major.length > 3
|
152
|
+
major, minor = possible_major, possible_minor
|
153
|
+
else
|
154
|
+
# number is in format ###{sep}### or ##{sep}### or #{sep}###
|
155
|
+
# handle as , is sep, . is thousands_separator
|
156
|
+
if decimal_mark == '.'
|
157
|
+
major, minor = possible_major, possible_minor
|
158
|
+
else
|
159
|
+
major, minor = "#{possible_major}#{possible_minor}", 0
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
else
|
166
|
+
# TODO: ParseError
|
167
|
+
raise ArgumentError, "Invalid currency amount"
|
168
|
+
end
|
169
|
+
|
170
|
+
# build the string based on major/minor since decimal_mark/thousands_separator have been removed
|
171
|
+
# avoiding floating point arithmetic here to ensure accuracy
|
172
|
+
cents = (major.to_i * currency.subunit_to_unit)
|
173
|
+
# Because of an bug in JRuby, we can't just call #floor
|
174
|
+
minor = minor.to_s
|
175
|
+
minor = if minor.size < currency.decimal_places
|
176
|
+
(minor + ("0" * currency.decimal_places))[0,currency.decimal_places].to_i
|
177
|
+
elsif minor.size > currency.decimal_places
|
178
|
+
if minor[currency.decimal_places,1].to_i >= 5
|
179
|
+
minor[0,currency.decimal_places].to_i+1
|
180
|
+
else
|
181
|
+
minor[0,currency.decimal_places].to_i
|
182
|
+
end
|
183
|
+
else
|
184
|
+
minor.to_i
|
185
|
+
end
|
186
|
+
|
187
|
+
cents += minor
|
188
|
+
|
189
|
+
# if negative, multiply by -1; otherwise, return positive cents
|
190
|
+
negative ? cents * -1 : cents
|
191
|
+
end
|
192
|
+
|
23
193
|
def to_s
|
24
194
|
@money.format(@options)
|
25
195
|
end
|