muck-users 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +27 -0
  3. data/Rakefile +96 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/admin/muck/roles_controller.rb +57 -0
  6. data/app/controllers/admin/muck/users_controller.rb +122 -0
  7. data/app/controllers/muck/activations_controller.rb +31 -0
  8. data/app/controllers/muck/password_resets_controller.rb +81 -0
  9. data/app/controllers/muck/user_sessions_controller.rb +40 -0
  10. data/app/controllers/muck/username_request_controller.rb +43 -0
  11. data/app/controllers/muck/users_controller.rb +211 -0
  12. data/app/models/muck_user_mailer.rb +53 -0
  13. data/app/models/permission.rb +17 -0
  14. data/app/models/role.rb +25 -0
  15. data/app/views/admin/roles/_role.html.erb +9 -0
  16. data/app/views/admin/roles/edit.html.erb +17 -0
  17. data/app/views/admin/roles/index.html.erb +8 -0
  18. data/app/views/admin/roles/new.html.erb +16 -0
  19. data/app/views/admin/roles/show.html.erb +7 -0
  20. data/app/views/admin/users/_activate.html.erb +5 -0
  21. data/app/views/admin/users/_ajax_search_box.html.erb +6 -0
  22. data/app/views/admin/users/_row.html.erb +6 -0
  23. data/app/views/admin/users/_search_box.html.erb +6 -0
  24. data/app/views/admin/users/_table.html.erb +21 -0
  25. data/app/views/admin/users/_user_navigation.html.erb +11 -0
  26. data/app/views/admin/users/do_search.html.erb +5 -0
  27. data/app/views/admin/users/inactive.html.erb +8 -0
  28. data/app/views/admin/users/inactive_emails.html.erb +6 -0
  29. data/app/views/admin/users/index.html.erb +12 -0
  30. data/app/views/admin/users/search.html.erb +5 -0
  31. data/app/views/muck_user_mailer/activation_confirmation.html.erb +7 -0
  32. data/app/views/muck_user_mailer/activation_instructions.html.erb +7 -0
  33. data/app/views/muck_user_mailer/password_not_active_instructions.html.erb +10 -0
  34. data/app/views/muck_user_mailer/password_reset_instructions.html.erb +10 -0
  35. data/app/views/muck_user_mailer/username_request.html.erb +3 -0
  36. data/app/views/muck_user_mailer/welcome_notification.html.erb +5 -0
  37. data/app/views/password_resets/edit.html.erb +9 -0
  38. data/app/views/password_resets/new.html.erb +11 -0
  39. data/app/views/user_sessions/new.html.erb +17 -0
  40. data/app/views/username_request/new.html.erb +11 -0
  41. data/app/views/users/_user.html.erb +15 -0
  42. data/app/views/users/activation_confirmation.html.erb +1 -0
  43. data/app/views/users/activation_instructions.html.erb +1 -0
  44. data/app/views/users/edit.html.erb +45 -0
  45. data/app/views/users/new.html.erb +51 -0
  46. data/app/views/users/show.html.erb +4 -0
  47. data/app/views/users/welcome.html.erb +4 -0
  48. data/config/muck_users_routes.rb +56 -0
  49. data/db/migrate/20090320174818_create_muck_permissions_and_roles.rb +16 -0
  50. data/db/migrate/20090512013727_add_photo_to_user.rb +13 -0
  51. data/install.rb +1 -0
  52. data/lib/action_controller/authentic_application.rb +213 -0
  53. data/lib/active_record/acts/muck_user.rb +192 -0
  54. data/lib/muck-users/exceptions.rb +5 -0
  55. data/lib/muck-users/initialize_routes.rb +8 -0
  56. data/lib/muck-users/tasks.rb +46 -0
  57. data/lib/muck-users.rb +7 -0
  58. data/locales/ar.yml +124 -0
  59. data/locales/bg.yml +124 -0
  60. data/locales/ca.yml +124 -0
  61. data/locales/cs.yml +124 -0
  62. data/locales/da.yml +124 -0
  63. data/locales/de.yml +124 -0
  64. data/locales/el.yml +124 -0
  65. data/locales/en.yml +127 -0
  66. data/locales/es.yml +124 -0
  67. data/locales/fr.yml +124 -0
  68. data/locales/it.yml +124 -0
  69. data/locales/iw.yml +124 -0
  70. data/locales/ja.yml +124 -0
  71. data/locales/ko.yml +124 -0
  72. data/locales/lt.yml +124 -0
  73. data/locales/lv.yml +124 -0
  74. data/locales/nl.yml +124 -0
  75. data/locales/no.yml +125 -0
  76. data/locales/pl.yml +124 -0
  77. data/locales/pt.yml +124 -0
  78. data/locales/ro.yml +124 -0
  79. data/locales/ru.yml +124 -0
  80. data/locales/sk.yml +124 -0
  81. data/locales/sl.yml +124 -0
  82. data/locales/sr.yml +124 -0
  83. data/locales/sv.yml +124 -0
  84. data/locales/tl.yml +124 -0
  85. data/locales/uk.yml +124 -0
  86. data/locales/vi.yml +124 -0
  87. data/locales/zh-CN.yml +124 -0
  88. data/locales/zh-TW.yml +124 -0
  89. data/locales/zh.yml +124 -0
  90. data/muck-users.gemspec +170 -0
  91. data/pkg/muck-users-0.1.0.gem +0 -0
  92. data/public/images/profile_default.jpg +0 -0
  93. data/rails/init.rb +18 -0
  94. data/tasks/muck_users_engine.rake +27 -0
  95. data/tasks/rails.rake +2 -0
  96. data/test/factories.rb +56 -0
  97. data/test/functional/activations_controller_test.rb +73 -0
  98. data/test/functional/admin/roles_controller_test.rb +10 -0
  99. data/test/functional/admin/users_controller_test.rb +55 -0
  100. data/test/functional/password_resets_controller_test.rb +60 -0
  101. data/test/functional/user_sessions_controller_test.rb +62 -0
  102. data/test/functional/users_controller_test.rb +255 -0
  103. data/test/shoulda_macros/controller.rb +43 -0
  104. data/test/shoulda_macros/forms.rb +28 -0
  105. data/test/shoulda_macros/models.rb +34 -0
  106. data/test/shoulda_macros/pagination.rb +48 -0
  107. data/test/shoulda_macros/plugins.rb +30 -0
  108. data/test/test_helper.rb +36 -0
  109. data/test/unit/muck_user_mailer_test.rb +64 -0
  110. data/test/unit/permission_test.rb +19 -0
  111. data/test/unit/role_test.rb +17 -0
  112. data/uninstall.rb +1 -0
  113. metadata +198 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Justin Ball
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,27 @@
1
+ RestfulAuthenticationEngine
2
+ ====================
3
+
4
+ The muck users engine is part of the muck framework and relies upon the muck_engine.
5
+
6
+ This engine implements authlogic. Some of the code contained was taken from here:
7
+ http://railsforum.com/viewtopic.php?id=14216 and here
8
+ http://github.com/activefx/restful_authentication_tutorial/tree/master
9
+
10
+ Inspiration also came from:
11
+ http://github.com/tsechingho/authlogic_bundle/tree/master
12
+
13
+ Example
14
+ =======
15
+ After installing the engine just create a user model thus:
16
+
17
+ class User < ActiveRecord::Base
18
+ acts_as_authentic
19
+ acts_as_muck_user
20
+ end
21
+
22
+ Then you will be able to go to:
23
+ http//:localhost:3000/login
24
+ http//:localhost:3000/signup
25
+
26
+
27
+ Copyright (c) 2009 Justin Ball, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,96 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "muck-users"
9
+ gem.summary = "Easy to use user engine for Rails"
10
+ gem.email = "justinball@gmail.com"
11
+ gem.homepage = "http://github.com/jbasdf/muck-users"
12
+ gem.description = "Easily add user signup, login and other features to your application"
13
+ gem.authors = ["Justin Ball"]
14
+ gem.rubyforge_project = "muck-users"
15
+ gem.add_dependency "authlogic"
16
+ gem.add_dependency "muck-engine"
17
+ # gem.files.include %w( lib/muck-users
18
+ # tasks/*
19
+ # db/migrate/*.rb
20
+ # app/**/**/**/*
21
+ # config/*
22
+ # locales/*
23
+ # rails/*
24
+ # test/*
25
+ # lib/**/* )
26
+ end
27
+ rescue LoadError
28
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
29
+ end
30
+
31
+ # rubyforge tasks
32
+ begin
33
+ require 'rake/contrib/sshpublisher'
34
+ namespace :rubyforge do
35
+
36
+ desc "Release gem and RDoc documentation to RubyForge"
37
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
38
+
39
+ namespace :release do
40
+ desc "Publish RDoc to RubyForge."
41
+ task :docs => [:rdoc] do
42
+ config = YAML.load(
43
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
44
+ )
45
+
46
+ host = "#{config['username']}@rubyforge.org"
47
+ remote_dir = "/var/www/gforge-projects/muck-users/"
48
+ local_dir = 'rdoc'
49
+
50
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
51
+ end
52
+ end
53
+ end
54
+ rescue LoadError
55
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
56
+ end
57
+
58
+ desc 'Test the muck-users gem.'
59
+ Rake::TestTask.new(:test) do |t|
60
+ t.libs << 'lib'
61
+ t.libs << 'test'
62
+ t.pattern = 'test/**/*_test.rb'
63
+ t.verbose = true
64
+ end
65
+
66
+ begin
67
+ require 'rcov/rcovtask'
68
+ Rcov::RcovTask.new do |test|
69
+ test.libs << 'test'
70
+ test.pattern = 'test/**/*_test.rb'
71
+ test.verbose = true
72
+ end
73
+ rescue LoadError
74
+ task :rcov do
75
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
76
+ end
77
+ end
78
+
79
+
80
+ task :default => :test
81
+
82
+ require 'rake/rdoctask'
83
+ Rake::RDocTask.new do |rdoc|
84
+ if File.exist?('VERSION.yml')
85
+ config = YAML.load(File.read('VERSION.yml'))
86
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
87
+ else
88
+ version = ""
89
+ end
90
+
91
+ rdoc.rdoc_dir = 'rdoc'
92
+ rdoc.title = "muck-users #{version}"
93
+ rdoc.rdoc_files.include('README*')
94
+ rdoc.rdoc_files.include('lib/**/*.rb')
95
+ end
96
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,57 @@
1
+ class Admin::Muck::RolesController < Admin::Muck::BaseController
2
+ unloadable
3
+
4
+ def index
5
+ @user = User.find(params[:user_id])
6
+ @all_roles = Role.find(:all)
7
+ end
8
+
9
+ def show
10
+ @role = Role.new(params[:role])
11
+ end
12
+
13
+ def new
14
+ @role = Role.new(params[:role])
15
+ end
16
+
17
+ # POST /websites
18
+ # POST /websites.xml
19
+ def create
20
+ @role = Role.new(params[:role])
21
+
22
+ respond_to do |format|
23
+ if @role.save
24
+ flash[:notice] = I18n.t('muck.roles.role_created')
25
+ format.html { redirect_to(admin_roles_path(@role)) }
26
+ format.xml { render :xml => @role, :status => :created, :location => @role }
27
+ else
28
+ format.html { render :action => "new" }
29
+ format.xml { render :xml => @role.errors, :status => :unprocessable_entity }
30
+ end
31
+ end
32
+ end
33
+
34
+ def edit
35
+ @role = Role.new(params[:role])
36
+ end
37
+
38
+ def update
39
+ @user = User.find(params[:user_id])
40
+ @role = Role.find(params[:id])
41
+ unless @user.has_role?(@role.rolename)
42
+ @user.roles << @role
43
+ end
44
+ redirect_to :action => 'index'
45
+ end
46
+
47
+ def destroy
48
+ @user = User.find(params[:user_id])
49
+ @role = Role.find(params[:id])
50
+ if @user.has_role?(@role.rolename)
51
+ @user.roles.delete(@role)
52
+ end
53
+ redirect_to :action => 'index'
54
+ end
55
+
56
+ end
57
+
@@ -0,0 +1,122 @@
1
+ class Admin::Muck::UsersController < Admin::Muck::BaseController
2
+ unloadable
3
+
4
+ before_filter :get_user, :only => [:update, :destroy]
5
+
6
+ def index
7
+ @user_count = User.count
8
+ @user_inactive_count = User.inactive_count
9
+ @users = User.by_newest.paginate(:page => @page, :per_page => @per_page)
10
+ respond_to do |format|
11
+ format.html { render :template => 'admin/users/index' }
12
+ end
13
+ end
14
+
15
+ def inactive
16
+ @user_inactive_count = User.inactive_count
17
+ @users = User.inactive.paginate(:page => @page, :per_page => @per_page)
18
+ respond_to do |format|
19
+ format.html { render :template => 'admin/users/inactive' }
20
+ end
21
+ end
22
+
23
+ def inactive_emails
24
+ @user_inactive_count = User.inactive_count
25
+ @users = User.inactive
26
+ respond_to do |format|
27
+ format.html { render :template => 'admin/users/inactive_emails' }
28
+ end
29
+ end
30
+
31
+ def activate_all
32
+ User.activate_all
33
+ respond_to do |format|
34
+ format.html do
35
+ redirect_to inactive_admin_users_path
36
+ end
37
+ end
38
+ end
39
+
40
+ def search_results
41
+ @users = User.do_search( params[:query] ).paginate(:page => @page, :per_page => @per_page )
42
+ end
43
+
44
+ def search
45
+ search_results
46
+ respond_to do |format|
47
+ format.html do
48
+ render :template => 'admin/users/index'
49
+ end
50
+ end
51
+ end
52
+
53
+ def ajax_search
54
+ search_results
55
+ respond_to do |format|
56
+ format.html do
57
+ render :partial => 'admin/users/table', :layout => false
58
+ end
59
+ end
60
+ end
61
+
62
+ def update
63
+ if is_me?(@user)
64
+ message = I18n.t("muck.users.cannot_deactivate_yourself")
65
+ else
66
+ if @user.force_activate!
67
+ message = I18n.t('muck.users.user_marked_active')
68
+ else
69
+ message = I18n.t('muck.users.user_marked_inactive')
70
+ end
71
+ end
72
+ activate_text = '<div class="flasherror">' + message + '</div>'
73
+ activate_text << render_to_string(:partial => 'admin/users/activate', :locals => {:user => @user})
74
+ respond_to do |format|
75
+ format.js do
76
+ render :update do |page|
77
+ page.replace_html @user.dom_id('link'), activate_text
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def enable
84
+ @user = User.find(params[:id])
85
+ if @user.update_attribute(:enabled, true)
86
+ self.current_user = @user
87
+ flash[:notice] = t("muck.users.user_enabled")
88
+ else
89
+ flash[:error] = t("muck.users.user_enable_problem")
90
+ end
91
+ redirect_to :action => 'index'
92
+ end
93
+
94
+ def disable
95
+ @user = admin? ? User.find(params[:id]) : User.find(current_user)
96
+ if @user.update_attribute(:enabled, false)
97
+ flash[:notice] = t("users.user_disabled")
98
+ else
99
+ flash[:error] = t("users.user_disable_problem")
100
+ end
101
+ redirect_to :action => 'index'
102
+ end
103
+
104
+ def destroy
105
+ @user.destroy
106
+ respond_to do |format|
107
+ format.html do
108
+ flash[:notice] = I18n.t('muck.users.user_successfully_deleted', :login => @user.login)
109
+ redirect_to admin_users_path
110
+ end
111
+ format.xml { head :ok }
112
+ format.js { render(:update){|page| page.visual_effect :fade, "#{@user.dom_id('row')}".to_sym} }
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def get_user
119
+ @user = User.find_by_login(params[:id]) || User.find(params[:id])
120
+ end
121
+
122
+ end
@@ -0,0 +1,31 @@
1
+ # new file app/controllers/activations_controller.rb
2
+ class Muck::ActivationsController < ApplicationController
3
+ unloadable
4
+
5
+ ssl_required :new
6
+ before_filter :not_logged_in_required, :only => [:new]
7
+
8
+ def new
9
+ @user = User.find_using_perishable_token(params[:id])
10
+ if @user.blank?
11
+ flash[:notice] = t('muck.users.activation_not_found')
12
+ redirect_to new_user_path and return
13
+ end
14
+
15
+ if @user.active?
16
+ flash[:notice] = t('muck.users.already_activated')
17
+ redirect_to login_path and return
18
+ end
19
+
20
+ if @user.activate!
21
+ UserSession.create(@user)
22
+ flash[:notice] = t('muck.users.account_activated')
23
+ @user.deliver_activation_confirmation!
24
+ redirect_to welcome_user_path(@user)
25
+ else
26
+ render :action => :new
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,81 @@
1
+ class Muck::PasswordResetsController < ApplicationController
2
+ unloadable
3
+
4
+ ssl_required :edit, :update
5
+ ssl_allowed :new, :create
6
+ before_filter :not_logged_in_required
7
+ before_filter :load_user_using_perishable_token, :only => [:edit, :update]
8
+
9
+ # Enter email address to recover password
10
+ def new
11
+ @title = t('muck.users.recover_password')
12
+ respond_to do |format|
13
+ format.html { render :template => 'password_resets/new' }
14
+ end
15
+ end
16
+
17
+ # Forgot password action
18
+ def create
19
+ @title = t('muck.users.recover_password')
20
+ if @user = User.find_by_email(params[:email])
21
+ @user.deliver_password_reset_instructions!
22
+ flash[:notice] = t('muck.users.password_reset_link_sent')
23
+ respond_to do |format|
24
+ format.html { redirect_to login_path }
25
+ end
26
+ else
27
+ flash[:notice] = t('muck.users.could_not_find_user_with_email')
28
+ respond_to do |format|
29
+ format.html { render :template => 'password_resets/new' }
30
+ end
31
+ end
32
+ end
33
+
34
+ # Action triggered by clicking on the /reset_password/:id link recieved via email
35
+ # Makes sure the id code is included
36
+ # Checks that the id code matches a user in the database
37
+ # Then if everything checks out, shows the password reset fields
38
+ def edit
39
+ @title = t('muck.users.reset_password')
40
+ respond_to do |format|
41
+ format.html { render :template => 'password_resets/edit' }
42
+ end
43
+ end
44
+
45
+ # Reset password action /reset_password/:id
46
+ # Checks once again that an id is included and makes sure that the password field isn't blank
47
+ def update
48
+ if @user.reset_password!(params[:user])
49
+ flash[:success] = t('muck.users.password_updated')
50
+ respond_to do |format|
51
+ format.html { redirect_to account_url }
52
+ end
53
+ else
54
+ respond_to do |format|
55
+ format.html { render :template => 'password_resets/edit' }
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def load_user_using_perishable_token
63
+ @user = User.find_using_perishable_token(params[:id])
64
+ unless @user
65
+ flash[:notice] = t('muck.users.sorry_invalid_reset_code')
66
+ respond_to do |format|
67
+ format.html { redirect_to root_url }
68
+ end
69
+ end
70
+ end
71
+
72
+ def permission_denied
73
+ respond_to do |format|
74
+ format.html do
75
+ flash[:notice] = t('muck.users.already_logged_in')
76
+ redirect_to account_url
77
+ end
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,40 @@
1
+ class Muck::UserSessionsController < ApplicationController
2
+ unloadable
3
+
4
+ ssl_required :new, :create
5
+ before_filter :login_required, :only => :destroy
6
+ before_filter :not_logged_in_required, :only => [:new, :create]
7
+
8
+ def new
9
+ @title = t('muck.users.sign_in_title')
10
+ @user_session = UserSession.new
11
+ respond_to do |format|
12
+ format.html { render :template => 'user_sessions/new' }
13
+ end
14
+ end
15
+
16
+ def create
17
+ @title = t('muck.users.sign_in_title')
18
+ @user_session = UserSession.new(params[:user_session])
19
+ if @user_session.save
20
+ flash[:notice] = t('muck.users.login_success')
21
+ respond_to do |format|
22
+ format.html { redirect_back_or_default user_path(@user_session.user) }
23
+ end
24
+ else
25
+ flash[:notice] = t('muck.users.login_fail')
26
+ respond_to do |format|
27
+ format.html { render :template => 'user_sessions/new' }
28
+ end
29
+ end
30
+ end
31
+
32
+ def destroy
33
+ @title = t('muck.users.sign_out_title')
34
+ current_user_session.destroy
35
+ flash[:notice] = t('muck.users.login_out_success')
36
+ respond_to do |format|
37
+ format.html { redirect_back_or_default login_path }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,43 @@
1
+ class Muck::UsernameRequestController < ApplicationController
2
+ unloadable
3
+
4
+ ssl_allowed :new, :create
5
+ before_filter :not_logged_in_required
6
+
7
+ # Enter email address to recover username
8
+ def new
9
+ @title = t('muck.users.username_request')
10
+ respond_to do |format|
11
+ format.html { render :template => 'username_request/new' }
12
+ end
13
+ end
14
+
15
+ # Forgot username action
16
+ def create
17
+ @title = t('muck.users.username_request')
18
+ if @user = User.find_by_email(params[:request_username][:email])
19
+ @user.deliver_username_request!
20
+ flash[:notice] = t('muck.users.username_sent')
21
+ respond_to do |format|
22
+ format.html { redirect_to login_path }
23
+ end
24
+ else
25
+ flash[:notice] = t('muck.users.could_not_find_user_with_email')
26
+ respond_to do |format|
27
+ format.html { render :template => 'username_request/new' }
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def permission_denied
35
+ respond_to do |format|
36
+ format.html do
37
+ flash[:notice] = t('muck.users.already_logged_in')
38
+ redirect_to account_url
39
+ end
40
+ end
41
+ end
42
+
43
+ end