decidim-verifications 0.15.2 → 0.16.0
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/README.md +37 -7
- data/app/commands/decidim/verifications/id_documents/admin/confirm_user_offline_authorization.rb +74 -0
- data/app/commands/decidim/verifications/id_documents/admin/update_config.rb +39 -0
- data/app/controllers/decidim/verifications/id_documents/admin/config_controller.rb +40 -0
- data/app/controllers/decidim/verifications/id_documents/admin/offline_confirmations_controller.rb +40 -0
- data/app/controllers/decidim/verifications/id_documents/admin/pending_authorizations_controller.rb +13 -2
- data/app/controllers/decidim/verifications/id_documents/authorizations_controller.rb +30 -2
- data/app/controllers/decidim/verifications/sms/authorizations_controller.rb +76 -0
- data/app/forms/decidim/verifications/id_documents/admin/config_form.rb +42 -0
- data/app/forms/decidim/verifications/id_documents/admin/offline_confirmation_form.rb +16 -0
- data/app/forms/decidim/verifications/id_documents/information_form.rb +12 -1
- data/app/forms/decidim/verifications/id_documents/upload_form.rb +2 -1
- data/app/forms/decidim/verifications/sms/confirmation_form.rb +19 -0
- data/app/forms/decidim/verifications/sms/mobile_phone_form.rb +59 -0
- data/app/views/decidim/verifications/id_documents/admin/config/edit.html.erb +26 -0
- data/app/views/decidim/verifications/id_documents/admin/confirmations/new.html.erb +1 -0
- data/app/views/decidim/verifications/id_documents/admin/offline_confirmations/new.html.erb +24 -0
- data/app/views/decidim/verifications/id_documents/admin/pending_authorizations/index.html.erb +3 -1
- data/app/views/decidim/verifications/id_documents/authorizations/_form.html.erb +17 -0
- data/app/views/decidim/verifications/id_documents/authorizations/choose.html.erb +21 -0
- data/app/views/decidim/verifications/id_documents/authorizations/edit.html.erb +14 -23
- data/app/views/decidim/verifications/id_documents/authorizations/new.html.erb +6 -13
- data/app/views/decidim/verifications/sms/authorizations/edit.html.erb +27 -0
- data/app/views/decidim/verifications/sms/authorizations/new.html.erb +27 -0
- data/config/locales/ca.yml +51 -1
- data/config/locales/de.yml +50 -0
- data/config/locales/en.yml +51 -1
- data/config/locales/es-PY.yml +50 -0
- data/config/locales/es.yml +51 -1
- data/config/locales/eu.yml +50 -0
- data/config/locales/fi-pl.yml +51 -1
- data/config/locales/fi.yml +51 -1
- data/config/locales/fr.yml +50 -0
- data/config/locales/gl.yml +50 -0
- data/config/locales/hu.yml +50 -0
- data/config/locales/id-ID.yml +51 -1
- data/config/locales/it.yml +50 -0
- data/config/locales/nl.yml +50 -0
- data/config/locales/pl.yml +50 -0
- data/config/locales/pt-BR.yml +50 -0
- data/config/locales/pt.yml +50 -0
- data/config/locales/sv.yml +50 -0
- data/config/locales/tr-TR.yml +51 -1
- data/lib/decidim/verifications.rb +1 -0
- data/lib/decidim/verifications/id_documents/admin_engine.rb +3 -0
- data/lib/decidim/verifications/id_documents/engine.rb +6 -2
- data/lib/decidim/verifications/sms.rb +4 -0
- data/lib/decidim/verifications/sms/engine.rb +29 -0
- data/lib/decidim/verifications/sms/example_gateway.rb +21 -0
- data/lib/decidim/verifications/version.rb +1 -1
- metadata +27 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f72535ac2342a5548bc109d27133bdbbc04ea5d31bf75e6bea9388129cfd962
|
4
|
+
data.tar.gz: 65a300dbe94c86dc4a185946617608e78cf912879df40aca37b72e4eb08504a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8aea64e88a8f1ec983ee81e4ccdaab8c6788077ef171aac9ba432710eddbbf2d595e9a8200c0b5cff2735fc1b3659d4d4384c77d8c73cb6a369c33f4d0be550
|
7
|
+
data.tar.gz: 27e690f78d70610141648ec4acc0551401aa904476caeb842de371a57586cdd812ea2c5ab3dc23c27328a6f220deb5a4210b4b515305ce860c890304cfd57af0
|
data/README.md
CHANGED
@@ -76,9 +76,9 @@ Decidim implements two type of authorization methods:
|
|
76
76
|
```ruby
|
77
77
|
# config/initializers/decidim.rb
|
78
78
|
|
79
|
-
Decidim::Verifications.register_workflow(:
|
80
|
-
workflow.engine = Decidim::Verifications::
|
81
|
-
workflow.admin_engine = Decidim::Verifications::
|
79
|
+
Decidim::Verifications.register_workflow(:my_verification) do |workflow|
|
80
|
+
workflow.engine = Decidim::Verifications::MyVerification::Engine
|
81
|
+
workflow.admin_engine = Decidim::Verifications::MyVerification::AdminEngine
|
82
82
|
end
|
83
83
|
```
|
84
84
|
|
@@ -97,6 +97,24 @@ Decidim implements two type of authorization methods:
|
|
97
97
|
* `edit_authorization_path`: This is the entry point to resume an existing
|
98
98
|
authorization process.
|
99
99
|
|
100
|
+
### SMS verification
|
101
|
+
|
102
|
+
Decidim comes with a verification workflow designed to verify users by sending
|
103
|
+
an SMS to their mobile phone.
|
104
|
+
|
105
|
+
Much like a Census verification you just need to implement a class that sends an
|
106
|
+
SMS code using your preferred provider.
|
107
|
+
|
108
|
+
In order to setup Decidim with SMS verification you need to:
|
109
|
+
|
110
|
+
1. Create a class that accepts two parameters when initializing it (mobile phone and code) and a method named `deliver_code` that will send an SMS and return a truthy or falsey value if the delivery was OK or not.
|
111
|
+
1. Set the `sms_gateway_service` configuration variable to the name of the class that you just created (use a String, not the actual class) at `config/initializers/decidim.rb`.
|
112
|
+
|
113
|
+
Keep in mind that Decidim won't store a free text version of the mobile phone, only a hashed
|
114
|
+
version so we can avoid duplicates and guarantee the users' privacy.
|
115
|
+
|
116
|
+
You can find an example [here][example SMS gateway].
|
117
|
+
|
100
118
|
## Authorization options
|
101
119
|
|
102
120
|
Sometimes you want to scope authorizations only to users that meet certain
|
@@ -163,10 +181,10 @@ its workflow manifest:
|
|
163
181
|
```ruby
|
164
182
|
# config/initializers/decidim.rb
|
165
183
|
|
166
|
-
Decidim::Verifications.register_workflow(:
|
167
|
-
workflow.engine = Decidim::Verifications::
|
168
|
-
workflow.admin_engine = Decidim::Verifications::
|
169
|
-
workflow.action_authorizer = "Decidim::Verifications::
|
184
|
+
Decidim::Verifications.register_workflow(:my_verification) do |workflow|
|
185
|
+
workflow.engine = Decidim::Verifications::MyVerification::Engine
|
186
|
+
workflow.admin_engine = Decidim::Verifications::MyVerification::AdminEngine
|
187
|
+
workflow.action_authorizer = "Decidim::Verifications::MyVerification::ActionAuthorizer"
|
170
188
|
end
|
171
189
|
```
|
172
190
|
|
@@ -174,6 +192,17 @@ Check the [example authorization handler](https://github.com/decidim/decidim/blo
|
|
174
192
|
and the [DefaultActionAuthorizer class](https://github.com/decidim/decidim/blob/master/decidim-verifications/lib/decidim/verifications/default_action_authorizer.rb)
|
175
193
|
for additional technical details.
|
176
194
|
|
195
|
+
## How Handlers work
|
196
|
+
|
197
|
+
For a workflow to be visible in the user's profile, the organization must have
|
198
|
+
it in it's `available_authorizations` and the given handler must exist.
|
199
|
+
The name of the handler must match the authorization name plus the "Hander"
|
200
|
+
suffix. It also has to be in the `Decidim::Verifications` namespace.
|
201
|
+
|
202
|
+
The handler is both the Form object that the user must fill in order to be
|
203
|
+
verified, but also the validator of the filled information in order to grant the
|
204
|
+
authorization.
|
205
|
+
|
177
206
|
## Installation
|
178
207
|
|
179
208
|
Add this line to your application's Gemfile:
|
@@ -197,6 +226,7 @@ See [Decidim](https://github.com/decidim/decidim).
|
|
197
226
|
See [Decidim](https://github.com/decidim/decidim).
|
198
227
|
|
199
228
|
[authorization handler base class]: https://github.com/decidim/decidim/blob/master/decidim-core/app/services/decidim/authorization_handler.rb
|
229
|
+
[example SMS gateway]: https://github.com/decidim/decidim/blob/master/decidim-verifications/lib/decidim/verifications/sms/example_gateway.rb
|
200
230
|
|
201
231
|
[Decidim Barcelona]: https://github.com/AjuntamentdeBarcelona/decidim-barcelona/blob/master/app/services/census_authorization_handler.rb
|
202
232
|
[Decidim Terrassa]: https://github.com/AjuntamentDeTerrassa/decidim-terrassa/blob/master/app/services/census_authorization_handler.rb
|
data/app/commands/decidim/verifications/id_documents/admin/confirm_user_offline_authorization.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Verifications
|
5
|
+
module IdDocuments
|
6
|
+
module Admin
|
7
|
+
# A command to confirm a previous partial offline authorization.
|
8
|
+
class ConfirmUserOfflineAuthorization < Rectify::Command
|
9
|
+
# Public: Initializes the command.
|
10
|
+
#
|
11
|
+
# form - A form object with the verification data to confirm it.
|
12
|
+
def initialize(form)
|
13
|
+
@form = form
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes the command. Broadcasts these events:
|
17
|
+
#
|
18
|
+
# - :ok when everything is valid.
|
19
|
+
# - :invalid if the handler wasn't valid and we couldn't proceed.
|
20
|
+
#
|
21
|
+
# Returns nothing.
|
22
|
+
def call
|
23
|
+
return broadcast(:invalid) unless form.valid?
|
24
|
+
return broadcast(:invalid) unless authorization
|
25
|
+
|
26
|
+
if confirmation_successful?
|
27
|
+
grant_authorization
|
28
|
+
broadcast(:ok)
|
29
|
+
else
|
30
|
+
broadcast(:invalid)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def confirmation_successful?
|
37
|
+
form.verification_metadata.all? do |key, value|
|
38
|
+
authorization.verification_metadata[key] == value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :form
|
45
|
+
|
46
|
+
def grant_authorization
|
47
|
+
Decidim.traceability.perform_action!(
|
48
|
+
:grant_id_documents_offline_verification,
|
49
|
+
authorization_user,
|
50
|
+
form.current_user
|
51
|
+
) do
|
52
|
+
authorization.grant!
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def authorization
|
57
|
+
@authorization ||= Authorizations
|
58
|
+
.new(organization: form.current_organization, name: "id_documents", granted: false)
|
59
|
+
.query
|
60
|
+
.where("verification_metadata->'rejected' IS NULL")
|
61
|
+
.where("verification_metadata->>'verification_type' = 'offline'")
|
62
|
+
.find_by(user: authorization_user)
|
63
|
+
end
|
64
|
+
|
65
|
+
def authorization_user
|
66
|
+
@authorization_user ||= Decidim::User
|
67
|
+
.where(organization: form.current_organization)
|
68
|
+
.find_by(email: form.email)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Verifications
|
5
|
+
module IdDocuments
|
6
|
+
module Admin
|
7
|
+
class UpdateConfig < Rectify::Command
|
8
|
+
def initialize(form)
|
9
|
+
@form = form
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
return broadcast(:invalid) if form.invalid?
|
14
|
+
|
15
|
+
update_config
|
16
|
+
|
17
|
+
broadcast(:ok)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :form
|
23
|
+
|
24
|
+
def update_config
|
25
|
+
Decidim.traceability.perform_action!(
|
26
|
+
:update_id_documents_config,
|
27
|
+
form.current_organization,
|
28
|
+
form.current_user
|
29
|
+
) do
|
30
|
+
form.current_organization.id_documents_methods = form.selected_methods
|
31
|
+
form.current_organization.id_documents_explanation_text = form.offline_explanation
|
32
|
+
form.current_organization.save!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Verifications
|
5
|
+
module IdDocuments
|
6
|
+
module Admin
|
7
|
+
#
|
8
|
+
# Handles the configuration for the ID documents verification
|
9
|
+
#
|
10
|
+
class ConfigController < Decidim::Admin::ApplicationController
|
11
|
+
layout "decidim/admin/users"
|
12
|
+
|
13
|
+
def edit
|
14
|
+
enforce_permission_to :update, :organization, organization: current_organization
|
15
|
+
|
16
|
+
@form = form(ConfigForm).from_model(current_organization)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update
|
20
|
+
enforce_permission_to :update, :organization, organization: current_organization
|
21
|
+
|
22
|
+
@form = form(ConfigForm).from_params(params)
|
23
|
+
|
24
|
+
UpdateConfig.call(@form) do
|
25
|
+
on(:ok) do
|
26
|
+
flash[:notice] = t("config.update.success", scope: "decidim.verifications.id_documents.admin")
|
27
|
+
redirect_to pending_authorizations_path
|
28
|
+
end
|
29
|
+
|
30
|
+
on(:invalid) do
|
31
|
+
flash.now[:alert] = t("config.update.error", scope: "decidim.verifications.id_documents.admin")
|
32
|
+
render action: :edit
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/app/controllers/decidim/verifications/id_documents/admin/offline_confirmations_controller.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Verifications
|
5
|
+
module IdDocuments
|
6
|
+
module Admin
|
7
|
+
#
|
8
|
+
# Handles confirmations for offline verification by identity document.
|
9
|
+
#
|
10
|
+
class OfflineConfirmationsController < Decidim::Admin::ApplicationController
|
11
|
+
layout "decidim/admin/users"
|
12
|
+
|
13
|
+
def new
|
14
|
+
enforce_permission_to :update, :authorization
|
15
|
+
|
16
|
+
@form = form(OfflineConfirmationForm).instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def create
|
20
|
+
enforce_permission_to :update, :authorization
|
21
|
+
|
22
|
+
@form = form(OfflineConfirmationForm).from_params(params)
|
23
|
+
|
24
|
+
ConfirmUserOfflineAuthorization.call(@form) do
|
25
|
+
on(:ok) do
|
26
|
+
flash[:notice] = t("offline_confirmations.create.success", scope: "decidim.verifications.id_documents.admin")
|
27
|
+
redirect_to pending_authorizations_path
|
28
|
+
end
|
29
|
+
|
30
|
+
on(:invalid) do
|
31
|
+
flash.now[:alert] = t("offline_confirmations.create.error", scope: "decidim.verifications.id_documents.admin")
|
32
|
+
render action: :new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/app/controllers/decidim/verifications/id_documents/admin/pending_authorizations_controller.rb
CHANGED
@@ -7,19 +7,30 @@ module Decidim
|
|
7
7
|
class PendingAuthorizationsController < Decidim::Admin::ApplicationController
|
8
8
|
layout "decidim/admin/users"
|
9
9
|
|
10
|
+
helper_method :has_offline_method?
|
11
|
+
|
10
12
|
def index
|
11
13
|
enforce_permission_to :index, :authorization
|
12
14
|
|
13
|
-
@
|
15
|
+
@pending_online_authorizations = pending_online_authorizations
|
14
16
|
end
|
15
17
|
|
16
18
|
private
|
17
19
|
|
18
|
-
def
|
20
|
+
def pending_online_authorizations
|
19
21
|
Authorizations
|
20
22
|
.new(organization: current_organization, name: "id_documents", granted: false)
|
21
23
|
.query
|
22
24
|
.where("verification_metadata->'rejected' IS NULL")
|
25
|
+
.where("verification_metadata->>'verification_type' = 'online'")
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_offline_method?
|
29
|
+
available_methods.include?("offline")
|
30
|
+
end
|
31
|
+
|
32
|
+
def available_methods
|
33
|
+
@available_methods ||= current_organization.id_documents_methods
|
23
34
|
end
|
24
35
|
end
|
25
36
|
end
|
@@ -7,14 +7,20 @@ module Decidim
|
|
7
7
|
# Handles verification by identity document upload
|
8
8
|
#
|
9
9
|
class AuthorizationsController < ApplicationController
|
10
|
-
helper_method :authorization
|
10
|
+
helper_method :authorization, :verification_type, :using_offline?, :using_online?, :available_methods
|
11
11
|
|
12
12
|
before_action :load_authorization
|
13
13
|
|
14
|
+
def choose
|
15
|
+
return redirect_to action: :new, using: verification_type if available_methods.count == 1
|
16
|
+
render :choose
|
17
|
+
end
|
18
|
+
|
14
19
|
def new
|
20
|
+
raise ActionController::RoutingError, "Method not available" unless available_methods.include?(verification_type)
|
15
21
|
enforce_permission_to :create, :authorization, authorization: @authorization
|
16
22
|
|
17
|
-
@form = UploadForm.
|
23
|
+
@form = UploadForm.from_params(id_document_upload: { verification_type: verification_type })
|
18
24
|
end
|
19
25
|
|
20
26
|
def create
|
@@ -47,6 +53,7 @@ module Decidim
|
|
47
53
|
@form = UploadForm.from_params(
|
48
54
|
params.merge(
|
49
55
|
user: current_user,
|
56
|
+
verification_type: verification_type,
|
50
57
|
verification_attachment: params[:id_document_upload][:verification_attachment] || @authorization.verification_attachment
|
51
58
|
)
|
52
59
|
)
|
@@ -78,6 +85,27 @@ module Decidim
|
|
78
85
|
name: "id_documents"
|
79
86
|
)
|
80
87
|
end
|
88
|
+
|
89
|
+
def verification_type
|
90
|
+
params[:using] || authorization_verification_type || available_methods.first
|
91
|
+
end
|
92
|
+
|
93
|
+
def authorization_verification_type
|
94
|
+
return unless @authorization
|
95
|
+
@authorization.verification_metadata["verification_type"]
|
96
|
+
end
|
97
|
+
|
98
|
+
def using_online?
|
99
|
+
verification_type == "online"
|
100
|
+
end
|
101
|
+
|
102
|
+
def using_offline?
|
103
|
+
verification_type == "offline"
|
104
|
+
end
|
105
|
+
|
106
|
+
def available_methods
|
107
|
+
@available_methods ||= current_organization.id_documents_methods
|
108
|
+
end
|
81
109
|
end
|
82
110
|
end
|
83
111
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Verifications
|
5
|
+
module Sms
|
6
|
+
class AuthorizationsController < ApplicationController
|
7
|
+
helper_method :authorization
|
8
|
+
|
9
|
+
before_action :load_authorization
|
10
|
+
|
11
|
+
def new
|
12
|
+
enforce_permission_to :create, :authorization, authorization: @authorization
|
13
|
+
|
14
|
+
@form = MobilePhoneForm.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def create
|
18
|
+
enforce_permission_to :create, :authorization, authorization: @authorization
|
19
|
+
|
20
|
+
@form = MobilePhoneForm.from_params(params.merge(user: current_user))
|
21
|
+
|
22
|
+
PerformAuthorizationStep.call(@authorization, @form) do
|
23
|
+
on(:ok) do
|
24
|
+
flash[:notice] = t("authorizations.create.success", scope: "decidim.verifications.sms")
|
25
|
+
authorization_method = Decidim::Verifications::Adapter.from_element(authorization.name)
|
26
|
+
redirect_to authorization_method.resume_authorization_path
|
27
|
+
end
|
28
|
+
on(:invalid) do
|
29
|
+
flash.now[:alert] = t("authorizations.create.error", scope: "decidim.verifications.sms")
|
30
|
+
render :new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def edit
|
36
|
+
enforce_permission_to :update, :authorization, authorization: @authorization
|
37
|
+
|
38
|
+
@form = ConfirmationForm.from_params(params)
|
39
|
+
end
|
40
|
+
|
41
|
+
def update
|
42
|
+
enforce_permission_to :update, :authorization, authorization: @authorization
|
43
|
+
|
44
|
+
@form = ConfirmationForm.from_params(params)
|
45
|
+
|
46
|
+
ConfirmUserAuthorization.call(@authorization, @form) do
|
47
|
+
on(:ok) do
|
48
|
+
flash[:notice] = t("authorizations.update.success", scope: "decidim.verifications.sms")
|
49
|
+
redirect_to decidim_verifications.authorizations_path
|
50
|
+
end
|
51
|
+
|
52
|
+
on(:invalid) do
|
53
|
+
flash.now[:alert] = t("authorizations.update.error", scope: "decidim.verifications.sms")
|
54
|
+
render :edit
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
62
|
+
def authorization
|
63
|
+
@authorization_presenter ||= AuthorizationPresenter.new(@authorization)
|
64
|
+
end
|
65
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
66
|
+
|
67
|
+
def load_authorization
|
68
|
+
@authorization = Decidim::Authorization.find_or_initialize_by(
|
69
|
+
user: current_user,
|
70
|
+
name: "sms"
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|