decidim-admin 0.8.4 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of decidim-admin might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/README.md +1 -1
- data/app/assets/javascripts/decidim/admin/application.js.es6 +5 -2
- data/app/assets/stylesheets/decidim/admin/_decidim.scss +2 -5
- data/app/assets/stylesheets/decidim/admin/extra/_action-icon.scss +4 -4
- data/app/assets/stylesheets/decidim/admin/extra/_cards.scss +3 -2
- data/app/assets/stylesheets/decidim/admin/extra/_categories.scss +1 -1
- data/app/assets/stylesheets/decidim/admin/extra/_dropdown_inverted.scss +8 -1
- data/app/assets/stylesheets/decidim/admin/extra/_label-required.scss +1 -1
- data/app/assets/stylesheets/decidim/admin/extra/_login.scss +1 -0
- data/app/assets/stylesheets/decidim/admin/extra/_quill.scss +2 -2
- data/app/assets/stylesheets/decidim/admin/extra/_select_multiple.scss +1 -1
- data/app/assets/stylesheets/decidim/admin/extra/_sort.scss +5 -4
- data/app/assets/stylesheets/decidim/admin/extra/_title_bar.scss +17 -7
- data/app/assets/stylesheets/decidim/admin/modules/_action-icon.scss +2 -2
- data/app/assets/stylesheets/decidim/admin/modules/_buttons.scss +6 -2
- data/app/assets/stylesheets/decidim/admin/modules/_callouts.scss +3 -0
- data/app/assets/stylesheets/decidim/admin/modules/_cards.scss +12 -0
- data/app/assets/stylesheets/decidim/admin/modules/_char-counter.scss +2 -0
- data/app/assets/stylesheets/decidim/admin/modules/_forms.scss +5 -1
- data/app/assets/stylesheets/decidim/admin/modules/_icons.scss +1 -0
- data/app/assets/stylesheets/decidim/admin/modules/_layout.scss +9 -1
- data/app/assets/stylesheets/decidim/admin/modules/_main-nav.scss +5 -0
- data/app/assets/stylesheets/decidim/admin/modules/_process-header.scss +1 -0
- data/app/assets/stylesheets/decidim/admin/modules/_secondary-nav.scss +10 -5
- data/app/assets/stylesheets/decidim/admin/modules/_table-list.scss +7 -1
- data/app/assets/stylesheets/decidim/admin/modules/_tabs.scss +7 -0
- data/app/assets/stylesheets/decidim/admin/modules/_title-bar.scss +3 -0
- data/app/assets/stylesheets/decidim/admin/modules/_user-login.scss +2 -0
- data/app/assets/stylesheets/decidim/admin/plugins/_foundation-datepicker.scss +36 -36
- data/app/assets/stylesheets/decidim/admin/utils/_fontface.scss +22 -20
- data/app/assets/stylesheets/decidim/admin/utils/_helpers.scss +6 -6
- data/app/assets/stylesheets/decidim/admin/utils/_keyframes.scss +6 -6
- data/app/assets/stylesheets/decidim/admin/utils/_mixins.scss +2 -3
- data/app/assets/stylesheets/decidim/admin/utils/_settings.scss +406 -129
- data/app/assets/stylesheets/decidim/admin/utils/_toggle-expand.scss +1 -0
- data/app/commands/decidim/admin/create_attachment.rb +13 -3
- data/app/commands/decidim/admin/create_feature.rb +1 -0
- data/app/commands/decidim/admin/create_managed_user.rb +20 -10
- data/app/commands/decidim/admin/impersonate_managed_user.rb +7 -9
- data/app/commands/decidim/admin/officialize_user.rb +47 -0
- data/app/commands/decidim/admin/unofficialize_user.rb +35 -0
- data/app/commands/decidim/admin/update_organization_appearance.rb +32 -5
- data/app/controllers/decidim/admin/categories_controller.rb +1 -1
- data/app/controllers/decidim/admin/features/base_controller.rb +1 -1
- data/app/controllers/decidim/admin/features_controller.rb +7 -0
- data/app/controllers/decidim/admin/managed_users/impersonations_controller.rb +1 -1
- data/app/controllers/decidim/admin/managed_users_controller.rb +23 -9
- data/app/controllers/decidim/admin/newsletters_controller.rb +2 -2
- data/app/controllers/decidim/admin/officializations_controller.rb +64 -0
- data/app/events/decidim/attachment_created_event.rb +25 -0
- data/app/events/decidim/feature_published_event.rb +27 -0
- data/app/events/decidim/participatory_process_step_activated_event.rb +31 -0
- data/app/forms/decidim/admin/managed_user_form.rb +0 -11
- data/app/forms/decidim/admin/officialization_form.rb +30 -0
- data/app/forms/decidim/admin/organization_appearance_form.rb +46 -1
- data/app/helpers/decidim/admin/application_helper.rb +1 -3
- data/app/models/decidim/admin/abilities/admin_ability.rb +1 -0
- data/app/models/decidim/admin/abilities/participatory_process_moderator_ability.rb +1 -1
- data/app/queries/decidim/admin/user_groups_evaluation.rb +9 -8
- data/app/queries/decidim/admin/users_officialization.rb +53 -0
- data/app/views/decidim/admin/managed_users/new.html.erb +5 -5
- data/app/views/decidim/admin/officializations/index.html.erb +82 -0
- data/app/views/decidim/admin/officializations/new.html.erb +18 -0
- data/app/views/decidim/admin/organization_appearance/_form.html.erb +60 -0
- data/app/views/layouts/decidim/admin/_title_bar.html.erb +1 -1
- data/app/views/layouts/decidim/admin/users.html.erb +5 -0
- data/config/locales/ca.yml +44 -2
- data/config/locales/en.yml +44 -1
- data/config/locales/es.yml +47 -5
- data/config/locales/eu.yml +46 -3
- data/config/locales/fi.yml +55 -12
- data/config/locales/fr.yml +45 -2
- data/config/locales/gl.yml +514 -0
- data/config/locales/it.yml +47 -4
- data/config/locales/nl.yml +47 -4
- data/config/locales/pl.yml +44 -1
- data/config/locales/pt-BR.yml +514 -0
- data/config/locales/pt.yml +106 -63
- data/config/locales/ru.yml +36 -6
- data/config/locales/sv.yml +514 -0
- data/config/locales/uk.yml +36 -8
- data/config/routes.rb +2 -0
- data/db/migrate/20171219154507_add_officialization_to_users.rb +10 -0
- data/lib/decidim/admin/engine.rb +1 -2
- data/lib/decidim/admin/test/manage_feature_permissions_examples.rb +0 -1
- data/lib/decidim/admin/test/manage_moderations_examples.rb +64 -0
- data/lib/decidim/admin/test.rb +5 -0
- data/lib/decidim/admin/version.rb +1 -1
- data/vendor/assets/javascripts/moment.min.js +1 -7
- metadata +37 -38
- data/app/assets/javascripts/decidim/admin/scopes.js.es6 +0 -20
- data/app/assets/javascripts/decidim/admin/select2.js.es6 +0 -8
- data/app/assets/stylesheets/decidim/admin/plugins/_select2.scss +0 -27
@@ -27,11 +27,10 @@ module Decidim
|
|
27
27
|
|
28
28
|
if @attachment.valid?
|
29
29
|
@attachment.save!
|
30
|
+
notify_followers
|
30
31
|
broadcast(:ok)
|
31
32
|
else
|
32
|
-
if @attachment.errors.has_key? :file
|
33
|
-
@form.errors.add :file, @attachment.errors[:file]
|
34
|
-
end
|
33
|
+
@form.errors.add :file, @attachment.errors[:file] if @attachment.errors.has_key? :file
|
35
34
|
broadcast(:invalid)
|
36
35
|
end
|
37
36
|
end
|
@@ -48,6 +47,17 @@ module Decidim
|
|
48
47
|
attached_to: @attached_to
|
49
48
|
)
|
50
49
|
end
|
50
|
+
|
51
|
+
def notify_followers
|
52
|
+
return unless @attachment.attached_to.is_a?(Decidim::Followable)
|
53
|
+
|
54
|
+
Decidim::EventsManager.publish(
|
55
|
+
event: "decidim.events.attachments.attachment_created",
|
56
|
+
event_class: Decidim::AttachmentCreatedEvent,
|
57
|
+
resource: @attachment,
|
58
|
+
recipient_ids: @attachment.attached_to.followers.pluck(:id)
|
59
|
+
)
|
60
|
+
end
|
51
61
|
end
|
52
62
|
end
|
53
63
|
end
|
@@ -22,29 +22,39 @@ module Decidim
|
|
22
22
|
return broadcast(:invalid) if form.invalid?
|
23
23
|
|
24
24
|
transaction do
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
managed_user.update!(admin: false, tos_agreement: true) unless managed_user.persisted?
|
26
|
+
|
27
|
+
raise ActiveRecord::Rollback unless authorized_user? && impersonation_ok?
|
28
28
|
|
29
|
-
|
29
|
+
broadcast(:ok)
|
30
|
+
end
|
30
31
|
end
|
31
32
|
|
32
33
|
private
|
33
34
|
|
34
35
|
attr_reader :form, :user
|
35
36
|
|
36
|
-
def
|
37
|
-
@
|
38
|
-
name: form.name,
|
37
|
+
def managed_user
|
38
|
+
@managed_user ||= Decidim::User.find_or_initialize_by(
|
39
39
|
organization: form.current_organization,
|
40
|
-
admin: false,
|
41
40
|
managed: true,
|
42
|
-
|
41
|
+
name: form.name
|
43
42
|
)
|
44
43
|
end
|
45
44
|
|
45
|
+
def impersonation_ok?
|
46
|
+
ImpersonateManagedUser.call(form, managed_user) do
|
47
|
+
on(:ok) do
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
on(:invalid) do
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
46
56
|
def authorized_user?
|
47
|
-
form.authorization.user =
|
57
|
+
form.authorization.user = managed_user
|
48
58
|
Verifications::AuthorizeUser.call(form.authorization) do
|
49
59
|
on(:ok) do
|
50
60
|
return true
|
@@ -8,11 +8,9 @@ module Decidim
|
|
8
8
|
#
|
9
9
|
# form - The form with the authorization info
|
10
10
|
# user - The user to impersonate
|
11
|
-
|
12
|
-
def initialize(form, user, current_user)
|
11
|
+
def initialize(form, user)
|
13
12
|
@form = form
|
14
13
|
@user = user
|
15
|
-
@current_user = current_user
|
16
14
|
end
|
17
15
|
|
18
16
|
# Executes the command. Broadcasts these events:
|
@@ -22,17 +20,17 @@ module Decidim
|
|
22
20
|
#
|
23
21
|
# Returns nothing.
|
24
22
|
def call
|
25
|
-
return broadcast(:invalid)
|
23
|
+
return broadcast(:invalid) unless user.managed? && authorization_valid?
|
26
24
|
|
27
25
|
create_impersonation_log
|
28
|
-
|
26
|
+
enqueue_expire_job
|
29
27
|
|
30
28
|
broadcast(:ok)
|
31
29
|
end
|
32
30
|
|
33
31
|
private
|
34
32
|
|
35
|
-
attr_reader :
|
33
|
+
attr_reader :user, :form
|
36
34
|
|
37
35
|
def authorization_valid?
|
38
36
|
return false unless form.valid?
|
@@ -45,16 +43,16 @@ module Decidim
|
|
45
43
|
|
46
44
|
def create_impersonation_log
|
47
45
|
Decidim::ImpersonationLog.create!(
|
48
|
-
admin: current_user,
|
46
|
+
admin: form.current_user,
|
49
47
|
user: user,
|
50
48
|
started_at: Time.current
|
51
49
|
)
|
52
50
|
end
|
53
51
|
|
54
|
-
def
|
52
|
+
def enqueue_expire_job
|
55
53
|
Decidim::Admin::ExpireImpersonationJob
|
56
54
|
.set(wait: Decidim::ImpersonationLog::SESSION_TIME_IN_MINUTES.minutes)
|
57
|
-
.perform_later(user, current_user)
|
55
|
+
.perform_later(user, form.current_user)
|
58
56
|
end
|
59
57
|
end
|
60
58
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Admin
|
5
|
+
# A command with all the business logic when officializing a user.
|
6
|
+
class OfficializeUser < Rectify::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# form - The officialization form.
|
10
|
+
def initialize(form)
|
11
|
+
@form = form
|
12
|
+
end
|
13
|
+
|
14
|
+
# Executes the command. Broadcasts these events:
|
15
|
+
#
|
16
|
+
# - :ok when the officialization suceeds.
|
17
|
+
# - :invalid when the form is invalid.
|
18
|
+
#
|
19
|
+
# Returns nothing.
|
20
|
+
def call
|
21
|
+
return broadcast(:invalid) unless form.valid?
|
22
|
+
|
23
|
+
officialize_user
|
24
|
+
|
25
|
+
Decidim::EventsManager.publish(
|
26
|
+
event: "decidim.events.users.user_officialized",
|
27
|
+
event_class: Decidim::ProfileUpdatedEvent,
|
28
|
+
resource: form.user,
|
29
|
+
recipient_ids: form.user.followers.pluck(:id)
|
30
|
+
)
|
31
|
+
|
32
|
+
broadcast(:ok, form.user)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :form
|
38
|
+
|
39
|
+
def officialize_user
|
40
|
+
form.user.update!(
|
41
|
+
officialized_at: Time.current,
|
42
|
+
officialized_as: form.officialized_as
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Admin
|
5
|
+
# A command with all the business logic when unofficializing a user.
|
6
|
+
class UnofficializeUser < Rectify::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# user - The user to be unofficialized.
|
10
|
+
def initialize(user)
|
11
|
+
@user = user
|
12
|
+
end
|
13
|
+
|
14
|
+
# Executes the command. Broadcasts these events:
|
15
|
+
#
|
16
|
+
# - :ok when the unofficialization suceeds.
|
17
|
+
# - :invalid when the form is invalid.
|
18
|
+
#
|
19
|
+
# Returns nothing.
|
20
|
+
def call
|
21
|
+
unofficialize_user
|
22
|
+
|
23
|
+
broadcast(:ok)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :user
|
29
|
+
|
30
|
+
def unofficialize_user
|
31
|
+
user.update!(officialized_at: nil, officialized_as: nil)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -44,6 +44,15 @@ module Decidim
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def attributes
|
47
|
+
appearance_attributes
|
48
|
+
.merge(highlighted_content_banner_attributes)
|
49
|
+
.merge(omnipresent_banner_attributes)
|
50
|
+
.tap do |attributes|
|
51
|
+
attributes[:header_snippets] = form.header_snippets if Decidim.enable_html_header_snippets
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def appearance_attributes
|
47
56
|
{
|
48
57
|
cta_button_path: form.cta_button_path,
|
49
58
|
cta_button_text: form.cta_button_text,
|
@@ -61,11 +70,29 @@ module Decidim
|
|
61
70
|
remove_official_img_footer: form.remove_official_img_footer,
|
62
71
|
official_url: form.official_url,
|
63
72
|
show_statistics: form.show_statistics
|
64
|
-
}
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def highlighted_content_banner_attributes
|
77
|
+
{
|
78
|
+
highlighted_content_banner_enabled: form.highlighted_content_banner_enabled,
|
79
|
+
highlighted_content_banner_action_url: form.highlighted_content_banner_action_url,
|
80
|
+
highlighted_content_banner_image: form.highlighted_content_banner_image,
|
81
|
+
remove_highlighted_content_banner_image: form.remove_highlighted_content_banner_image,
|
82
|
+
highlighted_content_banner_title: form.highlighted_content_banner_title,
|
83
|
+
highlighted_content_banner_short_description: form.highlighted_content_banner_short_description,
|
84
|
+
highlighted_content_banner_action_title: form.highlighted_content_banner_action_title,
|
85
|
+
highlighted_content_banner_action_subtitle: form.highlighted_content_banner_action_subtitle
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def omnipresent_banner_attributes
|
90
|
+
{
|
91
|
+
enable_omnipresent_banner: form.enable_omnipresent_banner,
|
92
|
+
omnipresent_banner_url: form.omnipresent_banner_url,
|
93
|
+
omnipresent_banner_short_description: form.omnipresent_banner_short_description,
|
94
|
+
omnipresent_banner_title: form.omnipresent_banner_title
|
95
|
+
}
|
69
96
|
end
|
70
97
|
end
|
71
98
|
end
|
@@ -91,6 +91,13 @@ module Decidim
|
|
91
91
|
|
92
92
|
@feature.publish!
|
93
93
|
|
94
|
+
Decidim::EventsManager.publish(
|
95
|
+
event: "decidim.events.features.feature_published",
|
96
|
+
event_class: Decidim::FeaturePublishedEvent,
|
97
|
+
resource: @feature,
|
98
|
+
recipient_ids: current_participatory_space.followers.pluck(:id)
|
99
|
+
)
|
100
|
+
|
94
101
|
flash[:notice] = I18n.t("features.publish.success", scope: "decidim.admin")
|
95
102
|
redirect_to action: :index
|
96
103
|
end
|
@@ -9,7 +9,9 @@ module Decidim
|
|
9
9
|
class ManagedUsersController < Admin::ApplicationController
|
10
10
|
layout "decidim/admin/users"
|
11
11
|
|
12
|
-
helper_method :
|
12
|
+
helper_method :available_authorization_handlers,
|
13
|
+
:more_than_one_authorization_handler?,
|
14
|
+
:select_authorization_handler_step?
|
13
15
|
|
14
16
|
def index
|
15
17
|
authorize! :index, :managed_users
|
@@ -19,7 +21,12 @@ module Decidim
|
|
19
21
|
def new
|
20
22
|
authorize! :new, :managed_users
|
21
23
|
|
22
|
-
if
|
24
|
+
if available_authorization_handlers.blank?
|
25
|
+
flash[:alert] = I18n.t("managed_users.new.no_authorization_handlers", scope: "decidim.admin")
|
26
|
+
redirect_to action: :index
|
27
|
+
end
|
28
|
+
|
29
|
+
unless select_authorization_handler_step?
|
23
30
|
@form = form(ManagedUserForm).from_params(
|
24
31
|
authorization: {
|
25
32
|
handler_name: handler_name
|
@@ -36,7 +43,7 @@ module Decidim
|
|
36
43
|
CreateManagedUser.call(@form) do
|
37
44
|
on(:ok) do
|
38
45
|
flash[:notice] = I18n.t("managed_users.create.success", scope: "decidim.admin")
|
39
|
-
redirect_to
|
46
|
+
redirect_to decidim.root_path
|
40
47
|
end
|
41
48
|
|
42
49
|
on(:invalid) do
|
@@ -48,21 +55,28 @@ module Decidim
|
|
48
55
|
|
49
56
|
private
|
50
57
|
|
58
|
+
def select_authorization_handler_step?
|
59
|
+
handler_name.blank? && params[:managed_user].blank?
|
60
|
+
end
|
61
|
+
|
51
62
|
def collection
|
52
63
|
@collection ||= current_organization.users.managed
|
53
64
|
end
|
54
65
|
|
55
66
|
def handler_name
|
56
|
-
return
|
57
|
-
|
67
|
+
return if available_authorization_handlers.blank?
|
68
|
+
return params[:handler_name] if more_than_one_authorization_handler?
|
69
|
+
available_authorization_handlers.first.name
|
58
70
|
end
|
59
71
|
|
60
|
-
def
|
61
|
-
|
72
|
+
def available_authorization_handlers
|
73
|
+
Decidim::Verifications::Adapter.from_collection(
|
74
|
+
current_organization.available_authorizations & Decidim.authorization_handlers.map(&:name)
|
75
|
+
)
|
62
76
|
end
|
63
77
|
|
64
|
-
def
|
65
|
-
|
78
|
+
def more_than_one_authorization_handler?
|
79
|
+
available_authorization_handlers.length > 1
|
66
80
|
end
|
67
81
|
end
|
68
82
|
end
|
@@ -3,8 +3,9 @@
|
|
3
3
|
module Decidim
|
4
4
|
module Admin
|
5
5
|
# Controller that allows managing newsletters.
|
6
|
-
#
|
7
6
|
class NewslettersController < Decidim::Admin::ApplicationController
|
7
|
+
include Decidim::NewslettersHelper
|
8
|
+
|
8
9
|
def index
|
9
10
|
authorize! :index, Newsletter
|
10
11
|
@newsletters = collection.order(Newsletter.arel_table[:created_at].desc)
|
@@ -27,7 +28,6 @@ module Decidim
|
|
27
28
|
|
28
29
|
email = NewsletterMailer.newsletter(current_user, @newsletter)
|
29
30
|
Premailer::Rails::Hook.perform(email)
|
30
|
-
|
31
31
|
render html: email.html_part.body.decoded.html_safe
|
32
32
|
end
|
33
33
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Admin
|
5
|
+
# Controller that allows managing user groups at the admin panel.
|
6
|
+
#
|
7
|
+
class OfficializationsController < Decidim::Admin::ApplicationController
|
8
|
+
layout "decidim/admin/users"
|
9
|
+
|
10
|
+
helper_method :user
|
11
|
+
|
12
|
+
def index
|
13
|
+
authorize! :index, :officializations
|
14
|
+
@query = params[:q]
|
15
|
+
@state = params[:state]
|
16
|
+
|
17
|
+
@users = Decidim::Admin::UsersOfficialization.for(@query, @state)
|
18
|
+
.page(params[:page])
|
19
|
+
.per(15)
|
20
|
+
end
|
21
|
+
|
22
|
+
def new
|
23
|
+
authorize! :new, :officializations
|
24
|
+
|
25
|
+
@form = form(OfficializationForm).from_model(user)
|
26
|
+
end
|
27
|
+
|
28
|
+
def create
|
29
|
+
authorize! :create, :officializations
|
30
|
+
|
31
|
+
@form = form(OfficializationForm).from_params(params)
|
32
|
+
|
33
|
+
OfficializeUser.call(@form) do
|
34
|
+
on(:ok) do |user|
|
35
|
+
notice = I18n.t("officializations.create.success", scope: "decidim.admin")
|
36
|
+
|
37
|
+
redirect_to officializations_path(q: user.name), notice: notice
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def destroy
|
43
|
+
authorize! :destroy, :officializations
|
44
|
+
|
45
|
+
UnofficializeUser.call(user) do
|
46
|
+
on(:ok) do
|
47
|
+
notice = I18n.t("officializations.destroy.success", scope: "decidim.admin")
|
48
|
+
|
49
|
+
redirect_to officializations_path(q: user.name), notice: notice
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def user
|
57
|
+
@user ||= Decidim::User.find_by(
|
58
|
+
id: params[:user_id],
|
59
|
+
organization: current_organization
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|