spree_core 5.0.3 → 5.1.0.beta
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/assets/images/logo.png +0 -0
- data/app/finders/spree/products/find.rb +28 -1
- data/app/finders/spree/taxons/find.rb +1 -1
- data/app/helpers/spree/integrations_helper.rb +15 -0
- data/app/javascript/spree/core/controllers/disable_submit_button_controller.js +19 -0
- data/app/mailers/spree/invitation_mailer.rb +24 -0
- data/app/models/concerns/spree/integrations_concern.rb +11 -0
- data/app/models/concerns/spree/product_scopes.rb +6 -6
- data/app/models/concerns/spree/translatable_resource.rb +4 -0
- data/app/models/concerns/spree/translatable_resource_scopes.rb +17 -3
- data/app/models/concerns/spree/unique_name.rb +2 -0
- data/app/models/concerns/spree/user_management.rb +33 -0
- data/app/models/concerns/spree/user_methods.rb +19 -0
- data/app/models/concerns/spree/user_roles.rb +43 -17
- data/app/models/spree/ability.rb +8 -6
- data/app/models/spree/asset.rb +1 -6
- data/app/models/spree/base.rb +1 -0
- data/app/models/spree/base_analytics_event_handler.rb +7 -2
- data/app/models/spree/classification.rb +13 -0
- data/app/models/spree/custom_domain.rb +2 -1
- data/app/models/spree/export.rb +1 -1
- data/app/models/spree/integration.rb +63 -0
- data/app/models/spree/invitation.rb +153 -0
- data/app/models/spree/invitations/store.rb +6 -0
- data/app/models/spree/order.rb +17 -1
- data/app/models/spree/order_merger.rb +7 -5
- data/app/models/spree/page_blocks/products/buy_buttons.rb +8 -0
- data/app/models/spree/page_blocks/products/quantity_selector.rb +4 -0
- data/app/models/spree/page_blocks/products/variant_picker.rb +4 -0
- data/app/models/spree/page_sections/featured_product.rb +4 -0
- data/app/models/spree/page_sections/image_banner.rb +12 -0
- data/app/models/spree/page_sections/image_with_text.rb +12 -0
- data/app/models/spree/page_sections/newsletter.rb +1 -1
- data/app/models/spree/page_sections/rich_text.rb +11 -0
- data/app/models/spree/page_sections/video.rb +8 -0
- data/app/models/spree/product.rb +10 -2
- data/app/models/spree/product_property.rb +1 -1
- data/app/models/spree/property.rb +3 -1
- data/app/models/spree/reports/sales_total.rb +5 -1
- data/app/models/spree/role.rb +16 -0
- data/app/models/spree/role_user.rb +32 -1
- data/app/models/spree/shipment.rb +1 -1
- data/app/models/spree/shipment_handler.rb +1 -0
- data/app/models/spree/shipping_method.rb +1 -1
- data/app/models/spree/store.rb +11 -4
- data/app/models/spree/store_credit_category.rb +4 -0
- data/app/models/spree/taxon.rb +4 -3
- data/app/models/spree/variant.rb +9 -1
- data/app/services/spree/country_to_timezone.rb +273 -0
- data/app/services/spree/seeds/admin_user.rb +4 -2
- data/app/services/spree/seeds/all.rb +3 -1
- data/app/services/spree/seeds/digital_delivery.rb +20 -0
- data/app/services/spree/seeds/returns_environment.rb +27 -0
- data/app/services/spree/seeds/tax_categories.rb +12 -0
- data/app/services/spree/stores/settings_defaults_by_country.rb +38 -0
- data/app/services/spree/tags/bulk_add.rb +13 -7
- data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +12 -0
- data/app/views/spree/invitation_mailer/invitation_email.html.erb +21 -0
- data/config/locales/en.yml +48 -9
- data/db/migrate/20250407085228_create_spree_integrations.rb +12 -0
- data/db/migrate/20250410061306_create_spree_invitations.rb +20 -0
- data/db/migrate/20250418174652_add_resource_to_spree_role_users.rb +8 -0
- data/db/migrate/20250508060800_add_selected_locale_to_spree_admin_users.rb +8 -0
- data/db/migrate/20250509143831_add_session_id_to_spree_assets.rb +5 -0
- data/lib/generators/spree/authentication/devise/devise_generator.rb +5 -2
- data/lib/generators/spree/authentication/devise/templates/authentication_helpers.rb.tt +3 -3
- data/lib/generators/spree/install/install_generator.rb +5 -0
- data/lib/spree/core/controller_helpers/auth.rb +16 -14
- data/lib/spree/core/controller_helpers/currency.rb +11 -0
- data/lib/spree/core/controller_helpers/order.rb +2 -1
- data/lib/spree/core/controller_helpers/strong_parameters.rb +3 -2
- data/lib/spree/core/engine.rb +13 -2
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +1 -0
- data/lib/spree/permitted_attributes.rb +118 -13
- data/lib/spree/testing_support/capybara_config.rb +1 -1
- data/lib/spree/testing_support/factories/integration_factory.rb +7 -0
- data/lib/spree/testing_support/factories/invitation_factory.rb +6 -0
- data/lib/spree/testing_support/factories/promotion_action_factory.rb +4 -0
- data/lib/spree/testing_support/factories/stock_item_factory.rb +5 -1
- data/lib/spree/testing_support/factories/user_factory.rb +14 -1
- data/lib/spree/translation_migrations.rb +27 -15
- data/lib/tasks/core.rake +8 -0
- metadata +41 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c1195535152943b227b48f47f34f8a53164727cff509e72ef64a720b4dfbf36
|
4
|
+
data.tar.gz: e88f4d0212bf72d2ef986faae3bd97a1793cffa2fa215a7d078f47044a1ad8f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 702032f4dbd79afda63c303e4f2a957e982f6cac18f95dded6f241a920f6600b3ea5e08253780185b7e87ec4d676dd4fa7d6fa92c99e43e4cb79420afeef94ad
|
7
|
+
data.tar.gz: 72a2841b3265909bcc97404cc8c504216dc65033b251010ba96acc2e666ece5c30cba4902eed857ced89598c9b411659f424a376c61230d5d71771deda6660cb
|
Binary file
|
@@ -11,6 +11,7 @@ module Spree
|
|
11
11
|
@currency = params.dig(:filter, :currency) || params[:currency] || Spree::Store.default.default_currency
|
12
12
|
@taxons = taxon_ids(params.dig(:filter, :taxons))
|
13
13
|
@concat_taxons = taxon_ids(params.dig(:filter, :concat_taxons))
|
14
|
+
@taxonomies = params.dig(:filter, :taxonomy_ids).to_h
|
14
15
|
@name = params.dig(:filter, :name)
|
15
16
|
@slug = params.dig(:filter, :slug)
|
16
17
|
@options = params.dig(:filter, :options).try(:to_unsafe_hash)
|
@@ -52,6 +53,7 @@ module Spree
|
|
52
53
|
products = show_only_backorderable(products)
|
53
54
|
products = show_only_purchasable(products)
|
54
55
|
products = show_only_out_of_stock(products)
|
56
|
+
products = by_taxonomies(products)
|
55
57
|
products = ordered(products)
|
56
58
|
products = by_vendor_ids(products)
|
57
59
|
|
@@ -62,7 +64,7 @@ module Spree
|
|
62
64
|
|
63
65
|
attr_reader :ids, :skus, :price, :currency, :taxons, :concat_taxons, :name, :options, :option_value_ids, :scope,
|
64
66
|
:sort_by, :deleted, :discontinued, :properties, :store, :in_stock, :backorderable, :purchasable, :tags,
|
65
|
-
:query, :vendor_ids, :out_of_stock, :slug
|
67
|
+
:query, :vendor_ids, :out_of_stock, :slug, :taxonomies
|
66
68
|
|
67
69
|
def query?
|
68
70
|
query.present?
|
@@ -169,6 +171,21 @@ module Spree
|
|
169
171
|
products.where(id: product_ids)
|
170
172
|
end
|
171
173
|
|
174
|
+
def by_taxonomies(products)
|
175
|
+
return products if taxonomies.none?
|
176
|
+
|
177
|
+
taxon_groups = taxonomies.values.map { |taxonomy| taxon_ids(taxonomy[:taxon_ids].join(',')) }.compact_blank
|
178
|
+
|
179
|
+
return products if taxon_groups.empty?
|
180
|
+
|
181
|
+
taxonomies_products = products.joins(:classifications).where(Classification.table_name => { taxon_id: taxon_groups.flatten.uniq })
|
182
|
+
|
183
|
+
# No need to filter if there is only one taxonomy
|
184
|
+
return taxonomies_products if taxonomies.size == 1
|
185
|
+
|
186
|
+
products.where(id: products_matching_all_taxonomies_ids(taxonomies_products.ids, taxon_groups))
|
187
|
+
end
|
188
|
+
|
172
189
|
def by_name(products)
|
173
190
|
return products unless name?
|
174
191
|
|
@@ -336,6 +353,16 @@ module Spree
|
|
336
353
|
def order_by_best_selling(scope)
|
337
354
|
scope.by_best_selling(:desc)
|
338
355
|
end
|
356
|
+
|
357
|
+
def products_matching_all_taxonomies_ids(products_ids, taxon_groups)
|
358
|
+
classifications = Spree::Classification.grouped_taxon_ids_for_products(products_ids, taxon_groups.flatten)
|
359
|
+
classifications_hash = classifications.to_h.transform_values { |taxon_ids| taxon_ids.split(',') }
|
360
|
+
|
361
|
+
# Find products ids that match all taxonomies to tighten filter results
|
362
|
+
classifications_hash.filter_map do |product_id, product_taxon_ids|
|
363
|
+
product_id if taxon_groups.all? { |group| group.intersect?(product_taxon_ids) }
|
364
|
+
end
|
365
|
+
end
|
339
366
|
end
|
340
367
|
end
|
341
368
|
end
|
@@ -68,7 +68,7 @@ module Spree
|
|
68
68
|
if Spree.use_translations?
|
69
69
|
taxons.joins(:parent).
|
70
70
|
join_translation_table(Taxon, 'parents_spree_taxons').
|
71
|
-
where(
|
71
|
+
where(Taxon.translation_table_alias => { permalink: parent_permalink })
|
72
72
|
else
|
73
73
|
taxons.joins(:parent).where(parent: { permalink: parent_permalink })
|
74
74
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Spree
|
2
|
+
module IntegrationsHelper
|
3
|
+
def store_integrations
|
4
|
+
@store_integrations ||= current_store.integrations.active.to_a
|
5
|
+
end
|
6
|
+
|
7
|
+
def store_integration(name)
|
8
|
+
store_integrations.find { |integration| integration.type.to_s.demodulize.underscore == name }
|
9
|
+
end
|
10
|
+
|
11
|
+
def grouped_available_store_integrations
|
12
|
+
Rails.application.config.spree.integrations.group_by(&:integration_group).sort_by { |group, _| group }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["button"]
|
5
|
+
|
6
|
+
connect() {
|
7
|
+
this.element.addEventListener('submit', this.toggleDisabledButton)
|
8
|
+
}
|
9
|
+
|
10
|
+
disconnect() {
|
11
|
+
this.element.removeEventListener('submit', this.toggleDisabledButton)
|
12
|
+
}
|
13
|
+
|
14
|
+
toggleDisabledButton = () => {
|
15
|
+
if (this.hasButtonTarget) {
|
16
|
+
this.buttonTarget.disabled = !this.buttonTarget.disabled
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Spree
|
2
|
+
class InvitationMailer < BaseMailer
|
3
|
+
# invitation email, sending email to the invited to let them know they have been invited to join a store/account/vendor
|
4
|
+
def invitation_email(invitation)
|
5
|
+
@invitation = invitation
|
6
|
+
mail(to: invitation.email,
|
7
|
+
from: from_address,
|
8
|
+
reply_to: reply_to_address,
|
9
|
+
subject: Spree.t('invitation_mailer.invitation_email.subject',
|
10
|
+
resource_name: invitation.resource&.name))
|
11
|
+
end
|
12
|
+
|
13
|
+
# sending email to the inviter to let them know the invitee has accepted the invitation
|
14
|
+
def invitation_accepted(invitation)
|
15
|
+
@invitation = invitation
|
16
|
+
mail(to: invitation.inviter.email,
|
17
|
+
from: from_address,
|
18
|
+
reply_to: reply_to_address,
|
19
|
+
subject: Spree.t('invitation_mailer.invitation_accepted.subject',
|
20
|
+
invitee_name: invitation.invitee&.name,
|
21
|
+
resource_name: invitation.resource&.name))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Spree
|
2
|
+
module IntegrationsConcern
|
3
|
+
def store_integrations
|
4
|
+
@store_integrations ||= Spree::Store.current.integrations.active.to_a
|
5
|
+
end
|
6
|
+
|
7
|
+
def store_integration(name)
|
8
|
+
store_integrations.find { |integration| integration.type.to_s.demodulize.underscore == name }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -49,11 +49,11 @@ module Spree
|
|
49
49
|
add_simple_scopes simple_scopes
|
50
50
|
|
51
51
|
add_search_scope :ascend_by_master_price do
|
52
|
-
order(
|
52
|
+
order(price_table_name => { amount: :asc })
|
53
53
|
end
|
54
54
|
|
55
55
|
add_search_scope :descend_by_master_price do
|
56
|
-
order(
|
56
|
+
order(price_table_name => { amount: :desc })
|
57
57
|
end
|
58
58
|
|
59
59
|
add_search_scope :price_between do |low, high|
|
@@ -61,11 +61,11 @@ module Spree
|
|
61
61
|
end
|
62
62
|
|
63
63
|
add_search_scope :master_price_lte do |price|
|
64
|
-
where(
|
64
|
+
where(Price.table_name => { amount: ..price })
|
65
65
|
end
|
66
66
|
|
67
67
|
add_search_scope :master_price_gte do |price|
|
68
|
-
where(
|
68
|
+
where(Price.table_name => { amount: price.. })
|
69
69
|
end
|
70
70
|
|
71
71
|
add_search_scope :in_stock do
|
@@ -137,11 +137,11 @@ module Spree
|
|
137
137
|
joins(:properties).
|
138
138
|
join_translation_table(Property).
|
139
139
|
join_translation_table(ProductProperty).
|
140
|
-
where(
|
140
|
+
where(ProductProperty.translation_table_alias => { value: value }).
|
141
141
|
where(property_conditions(property))
|
142
142
|
else
|
143
143
|
joins(:properties).
|
144
|
-
where(
|
144
|
+
where(ProductProperty.table_name => { value: value }).
|
145
145
|
where(property_conditions(property))
|
146
146
|
end
|
147
147
|
end
|
@@ -15,9 +15,23 @@ module Spree
|
|
15
15
|
end
|
16
16
|
translatable_class_foreign_key = "#{translatable_class.table_name.singularize}_id"
|
17
17
|
|
18
|
-
joins(
|
19
|
-
|
20
|
-
|
18
|
+
joins(
|
19
|
+
Arel::Nodes::OuterJoin.new(
|
20
|
+
Arel::Table.new(translatable_class::Translation.table_name).alias(translatable_class.translation_table_alias),
|
21
|
+
Arel::Nodes::On.new(
|
22
|
+
Arel::Nodes::And.new([
|
23
|
+
Arel::Nodes::Equality.new(
|
24
|
+
Arel::Table.new(translatable_class.translation_table_alias)[translatable_class_foreign_key],
|
25
|
+
Arel::Table.new(join_on_table_name)[:id]
|
26
|
+
),
|
27
|
+
Arel::Nodes::Equality.new(
|
28
|
+
Arel::Table.new(translatable_class.translation_table_alias)[:locale],
|
29
|
+
Arel::Nodes::Quoted.new(Mobility.locale.to_s)
|
30
|
+
)
|
31
|
+
])
|
32
|
+
)
|
33
|
+
)
|
34
|
+
)
|
21
35
|
end
|
22
36
|
end
|
23
37
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Spree
|
2
|
+
module UserManagement
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_many :role_users, class_name: 'Spree::RoleUser', as: :resource, dependent: :destroy
|
7
|
+
has_many :users, through: :role_users, source: :user, source_type: Spree.admin_user_class.to_s
|
8
|
+
has_many :invitations, class_name: 'Spree::Invitation', as: :resource, dependent: :destroy
|
9
|
+
end
|
10
|
+
|
11
|
+
# Adds a user to the resource with the default user role
|
12
|
+
# If no role is provided, the default user role will be used
|
13
|
+
# If a role is provided, it will be used instead of the default user role
|
14
|
+
# @param user [Spree.admin_user_class] The user to add to the resource
|
15
|
+
# @param role [Spree::Role] The role to add the user to
|
16
|
+
def add_user(user, role = nil)
|
17
|
+
role = role || default_user_role
|
18
|
+
role_users.find_or_create_by!(user: user, role: role)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Revokes a user's access to the resource
|
22
|
+
# @param user [Spree.admin_user_class] The user to remove from the resource
|
23
|
+
# @return [void]
|
24
|
+
def remove_user(user)
|
25
|
+
role_users.where(user: user).destroy_all
|
26
|
+
end
|
27
|
+
|
28
|
+
# this can be overridden in the base model to use a different user role, eg. 'vendor'
|
29
|
+
def default_user_role
|
30
|
+
Spree::Role.default_admin_role
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -84,6 +84,11 @@ module Spree
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
+
# Returns the last incomplete spree order for the current store
|
88
|
+
# @param [Spree::Store] store
|
89
|
+
# @param [Hash] options
|
90
|
+
# @option options [Array<Symbol>] :includes
|
91
|
+
# @return [Spree::Order]
|
87
92
|
def last_incomplete_spree_order(store, options = {})
|
88
93
|
orders.where(store: store).incomplete.not_canceled.
|
89
94
|
includes(options[:includes]).
|
@@ -91,12 +96,19 @@ module Spree
|
|
91
96
|
first
|
92
97
|
end
|
93
98
|
|
99
|
+
# Returns the total available store credit for the current store per currency
|
100
|
+
# @param [Spree::Store] store
|
101
|
+
# @param [String] currency
|
102
|
+
# @return [Float]
|
94
103
|
def total_available_store_credit(currency = nil, store = nil)
|
95
104
|
store ||= Store.default
|
96
105
|
currency ||= store.default_currency
|
97
106
|
store_credits.for_store(store).where(currency: currency).reload.to_a.sum(&:amount_remaining)
|
98
107
|
end
|
99
108
|
|
109
|
+
# Returns the available store credits for the current store per currency
|
110
|
+
# @param [Spree::Store] store
|
111
|
+
# @return [Array<Spree::Money>]
|
100
112
|
def available_store_credits(store)
|
101
113
|
store ||= Store.default
|
102
114
|
|
@@ -105,12 +117,18 @@ module Spree
|
|
105
117
|
end
|
106
118
|
end
|
107
119
|
|
120
|
+
# Returns the default wishlist for the current store
|
121
|
+
# if no default wishlist exists, it creates one
|
122
|
+
# @param [Spree::Store] current_store
|
123
|
+
# @return [Spree::Wishlist]
|
108
124
|
def default_wishlist_for_store(current_store)
|
109
125
|
wishlists.find_by(is_default: true, store_id: current_store.id) || ActiveRecord::Base.connected_to(role: :writing) do
|
110
126
|
wishlists.create!(store: current_store, is_default: true, name: Spree.t(:default_wishlist_name))
|
111
127
|
end
|
112
128
|
end
|
113
129
|
|
130
|
+
# Returns true if the user can be deleted
|
131
|
+
# @return [Boolean]
|
114
132
|
def can_be_deleted?
|
115
133
|
orders.complete.none?
|
116
134
|
end
|
@@ -140,6 +158,7 @@ module Spree
|
|
140
158
|
use_billing.in?([true, 'true', '1'])
|
141
159
|
end
|
142
160
|
|
161
|
+
# Scrambles the email and names of the user
|
143
162
|
def scramble_email_and_names
|
144
163
|
self.email = "#{SecureRandom.uuid}@example.net"
|
145
164
|
self.first_name = 'Deleted'
|
@@ -5,38 +5,64 @@ module Spree
|
|
5
5
|
included do
|
6
6
|
has_many :role_users, class_name: 'Spree::RoleUser', foreign_key: :user_id, as: :user, dependent: :destroy
|
7
7
|
has_many :spree_roles, through: :role_users, class_name: 'Spree::Role', source: :role
|
8
|
+
has_many :stores, through: :role_users, source: :resource, source_type: 'Spree::Store'
|
9
|
+
has_many :invitations, class_name: 'Spree::Invitation', foreign_key: :invitee_id, dependent: :destroy
|
8
10
|
|
9
|
-
scope :spree_admin, -> { joins(:spree_roles).where(Spree::Role.table_name => { name:
|
11
|
+
scope :spree_admin, -> { joins(:spree_roles).where(Spree::Role.table_name => { name: Spree::Role::ADMIN_ROLE }) }
|
10
12
|
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def
|
17
|
-
|
13
|
+
# Adds a role to a resource
|
14
|
+
#
|
15
|
+
# @param role_name [String] The name of the role to add, eg. 'admin'
|
16
|
+
# @param resource [Spree::Base] The resource to add the role to
|
17
|
+
# @return [Spree::RoleUser] The role user created
|
18
|
+
def add_role(role_name, resource = nil)
|
19
|
+
resource ||= Spree::Store.current
|
20
|
+
role = Spree::Role.find_by(name: role_name)
|
21
|
+
return if role.nil?
|
18
22
|
|
19
|
-
|
23
|
+
role_users.find_or_create_by!(role: role, resource: resource)
|
20
24
|
end
|
21
25
|
|
22
|
-
|
23
|
-
|
26
|
+
# Removes a role from a resource
|
27
|
+
#
|
28
|
+
# @param role_name [String] The name of the role to remove, eg. 'admin'
|
29
|
+
# @param resource [Spree::Base] The resource to remove the role from
|
30
|
+
def remove_role(role_name, resource = nil)
|
31
|
+
resource ||= Spree::Store.current
|
32
|
+
role = Spree::Role.find_by(name: role_name)
|
33
|
+
return if role.nil?
|
24
34
|
|
25
|
-
|
35
|
+
role_users.where(role: role, resource: resource).destroy_all
|
26
36
|
end
|
27
37
|
|
28
|
-
|
29
|
-
|
38
|
+
# has_spree_role? simply needs to return true or false whether a user has a role or not.
|
39
|
+
#
|
40
|
+
# @param role_name [String] The name of the role to check for
|
41
|
+
# @param resource [Spree::Base] The resource to get roles for
|
42
|
+
# @return [Boolean] Whether the user has the role for the resource
|
43
|
+
def has_spree_role?(role_name, resource = nil)
|
44
|
+
resource ||= Spree::Store.current
|
30
45
|
|
31
|
-
|
46
|
+
role_users.where(resource: resource).joins(:role).where(Spree::Role.table_name => { name: role_name }).exists?
|
32
47
|
end
|
33
48
|
|
34
49
|
def self.spree_admin_created?
|
35
50
|
spree_admin.exists?
|
36
51
|
end
|
37
52
|
|
38
|
-
|
39
|
-
|
53
|
+
# Returns true if the user has the admin role for a given resource
|
54
|
+
#
|
55
|
+
# @param resource [Spree::Base] The resource to check the admin role for
|
56
|
+
# @return [Boolean] Whether the user has the admin role for the resource
|
57
|
+
def spree_admin?(resource = nil)
|
58
|
+
resource ||= Spree::Store.current
|
59
|
+
has_spree_role?(Spree::Role::ADMIN_ROLE, resource)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the user who invited the current user
|
63
|
+
# @return [Spree.admin_user_class]
|
64
|
+
def invited_by
|
65
|
+
invitations.first&.inviter
|
40
66
|
end
|
41
67
|
end
|
42
68
|
end
|
data/app/models/spree/ability.rb
CHANGED
@@ -23,15 +23,16 @@ module Spree
|
|
23
23
|
abilities.delete(ability)
|
24
24
|
end
|
25
25
|
|
26
|
-
def initialize(user)
|
26
|
+
def initialize(user, options = {})
|
27
27
|
alias_cancan_delete_action
|
28
28
|
|
29
29
|
user ||= Spree.user_class.new
|
30
|
+
store ||= options[:store] || Spree::Current.store
|
30
31
|
|
31
|
-
if user.persisted? && user.try(:spree_admin
|
32
|
-
apply_admin_permissions(user)
|
32
|
+
if user.persisted? && user.is_a?(Spree.admin_user_class) && user.try(:spree_admin?, store)
|
33
|
+
apply_admin_permissions(user, options)
|
33
34
|
else
|
34
|
-
apply_user_permissions(user)
|
35
|
+
apply_user_permissions(user, options)
|
35
36
|
end
|
36
37
|
|
37
38
|
# Include any abilities registered by extensions, etc.
|
@@ -56,7 +57,7 @@ module Spree
|
|
56
57
|
alias_action :create, :update, :destroy, to: :modify
|
57
58
|
end
|
58
59
|
|
59
|
-
def apply_admin_permissions(
|
60
|
+
def apply_admin_permissions(_user, _options)
|
60
61
|
can :manage, :all
|
61
62
|
cannot :cancel, Spree::Order
|
62
63
|
can :cancel, Spree::Order, &:allow_cancel?
|
@@ -64,7 +65,7 @@ module Spree
|
|
64
65
|
cannot [:edit, :update], Spree::ReimbursementType, mutable: false
|
65
66
|
end
|
66
67
|
|
67
|
-
def apply_user_permissions(user)
|
68
|
+
def apply_user_permissions(user, _options)
|
68
69
|
can :read, ::Spree::Country
|
69
70
|
can :read, ::Spree::OptionType
|
70
71
|
can :read, ::Spree::OptionValue
|
@@ -95,6 +96,7 @@ module Spree
|
|
95
96
|
can [:create, :update, :destroy], ::Spree::WishedItem do |wished_item|
|
96
97
|
wished_item.wishlist.user == user
|
97
98
|
end
|
99
|
+
can :accept, Spree::Invitation, invitee_id: [user.id, nil], invitee_type: user.class.name, status: 'pending'
|
98
100
|
end
|
99
101
|
|
100
102
|
def protect_admin_role
|
data/app/models/spree/asset.rb
CHANGED
@@ -17,12 +17,7 @@ module Spree
|
|
17
17
|
|
18
18
|
store_accessor :private_metadata, :session_uploaded_assets_uuid
|
19
19
|
scope :with_session_uploaded_assets_uuid, lambda { |uuid|
|
20
|
-
|
21
|
-
when 'PostgreSQL'
|
22
|
-
where("#{table_name}.private_metadata @> ?", { session_uploaded_assets_uuid: uuid }.to_json)
|
23
|
-
when 'Mysql2', 'SQLite'
|
24
|
-
where("JSON_EXTRACT(private_metadata, '$.session_uploaded_assets_uuid') = '#{uuid}'")
|
25
|
-
end
|
20
|
+
where(session_id: uuid)
|
26
21
|
}
|
27
22
|
|
28
23
|
def product
|
data/app/models/spree/base.rb
CHANGED
@@ -2,6 +2,7 @@ class Spree::Base < ApplicationRecord
|
|
2
2
|
include Spree::Preferences::Preferable
|
3
3
|
include Spree::RansackableAttributes
|
4
4
|
include Spree::TranslatableResourceScopes
|
5
|
+
include Spree::IntegrationsConcern
|
5
6
|
|
6
7
|
after_initialize do
|
7
8
|
if has_attribute?(:preferences) && !preferences.nil?
|
@@ -5,13 +5,16 @@ module Spree
|
|
5
5
|
# @param opts [Hash] The options
|
6
6
|
# @option opts [User] :user
|
7
7
|
# @option opts [String] :session_id
|
8
|
+
# @option opts [Spree::Store] :store
|
8
9
|
def initialize(opts = {})
|
9
10
|
@user = opts[:user]
|
10
11
|
@session = opts[:session]
|
11
12
|
@request = opts[:request]
|
13
|
+
@store = opts[:store]
|
14
|
+
@visitor_id = opts[:visitor_id]
|
12
15
|
end
|
13
16
|
|
14
|
-
attr_reader :user, :session, :request
|
17
|
+
attr_reader :user, :session, :request, :store, :visitor_id
|
15
18
|
|
16
19
|
# Returns the client
|
17
20
|
# @return [Object] The client object
|
@@ -42,7 +45,9 @@ module Spree
|
|
42
45
|
def identity_hash
|
43
46
|
{
|
44
47
|
user_id: user&.id,
|
45
|
-
|
48
|
+
# session.id is a custom class (not a string), which has overridden the `to_json` method, we have to convert it to a string first so it does not send garbage to the analytics service
|
49
|
+
session_id: session&.id&.to_s,
|
50
|
+
visitor_id: visitor_id
|
46
51
|
}
|
47
52
|
end
|
48
53
|
end
|
@@ -23,5 +23,18 @@ module Spree
|
|
23
23
|
group("#{Spree::Classification.table_name}.id").
|
24
24
|
reorder(completed_orders_count: order_direction, completed_orders_total: order_direction)
|
25
25
|
}
|
26
|
+
|
27
|
+
scope :grouped_taxon_ids_for_products, lambda { |product_ids, taxon_groups|
|
28
|
+
where(product_id: product_ids, taxon_id: taxon_groups).
|
29
|
+
group(:product_id).
|
30
|
+
then do |query|
|
31
|
+
case ActiveRecord::Base.connection.adapter_name
|
32
|
+
when 'PostgreSQL'
|
33
|
+
query.pluck(:product_id, Arel.sql("STRING_AGG(taxon_id::text, ',')"))
|
34
|
+
when 'Mysql2', 'SQLite'
|
35
|
+
query.pluck(:product_id, Arel.sql('GROUP_CONCAT(taxon_id)'))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
}
|
26
39
|
end
|
27
40
|
end
|
@@ -14,7 +14,7 @@ module Spree
|
|
14
14
|
# Validations
|
15
15
|
#
|
16
16
|
validates :url, presence: true, uniqueness: true, format: {
|
17
|
-
with: %r{[a-
|
17
|
+
with: %r{\A(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z}i
|
18
18
|
}, length: { in: 1..63 }
|
19
19
|
validate :url_is_valid
|
20
20
|
|
@@ -26,6 +26,7 @@ module Spree
|
|
26
26
|
after_validation :ensure_default, on: :create
|
27
27
|
|
28
28
|
def url_is_valid
|
29
|
+
return if url.blank?
|
29
30
|
parts = url.split('.')
|
30
31
|
|
31
32
|
errors.add(:url, 'use domain or subdomain') if (parts[0] != 'www' && parts.size > 3) || (parts[0] == 'www' && parts.size > 4) || parts.size < 2
|
data/app/models/spree/export.rb
CHANGED
@@ -124,7 +124,7 @@ module Spree
|
|
124
124
|
end
|
125
125
|
|
126
126
|
def current_ability
|
127
|
-
@current_ability ||= Spree::Dependencies.ability_class.constantize.new(user)
|
127
|
+
@current_ability ||= Spree::Dependencies.ability_class.constantize.new(user, { store: store })
|
128
128
|
end
|
129
129
|
|
130
130
|
# eg. Spree::Exports::Products => products-store-my-store-code-20241030133348.csv
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Spree
|
2
|
+
class Integration < Spree.base_class
|
3
|
+
include Spree::SingleStoreResource
|
4
|
+
|
5
|
+
#
|
6
|
+
# Associations
|
7
|
+
#
|
8
|
+
belongs_to :store, class_name: 'Spree::Store', touch: true
|
9
|
+
|
10
|
+
#
|
11
|
+
# Validations
|
12
|
+
#
|
13
|
+
validates :type, presence: true
|
14
|
+
validates :store, presence: true, uniqueness: { scope: :type }
|
15
|
+
|
16
|
+
#
|
17
|
+
# Scopes
|
18
|
+
#
|
19
|
+
scope :active, -> { where(active: true) }
|
20
|
+
|
21
|
+
# This attribute is used to temporarily store connection-related error messages
|
22
|
+
# that can be displayed to users when testing or validating integration connections.
|
23
|
+
# It is not persisted to the database and is reset on each new connection attempt.
|
24
|
+
# @param message [String, nil] The error message to be stored
|
25
|
+
# @return [String, nil] The current error message
|
26
|
+
attr_accessor :connection_error_message
|
27
|
+
|
28
|
+
# Associates the integration to a group.
|
29
|
+
# The name here will be used as Spree.t key to display the group name.
|
30
|
+
# Leave blank to leave the integration ungrouped.
|
31
|
+
def self.integration_group
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.icon_path
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.integration_name
|
40
|
+
name.demodulize.titleize.strip
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.integration_key
|
44
|
+
name.demodulize.underscore
|
45
|
+
end
|
46
|
+
|
47
|
+
def name
|
48
|
+
self.class.integration_name
|
49
|
+
end
|
50
|
+
|
51
|
+
def key
|
52
|
+
self.class.integration_key
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks if the integration can establish a connection.
|
56
|
+
# This is a base implementation that always returns true.
|
57
|
+
# Subclasses should override this method to implement their own connection validation logic.
|
58
|
+
# @return [Boolean] true if the integration can connect, false otherwise
|
59
|
+
def can_connect?
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|