cortex-reaver 0.0.1

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.
Files changed (128) hide show
  1. data/LICENSE +25 -0
  2. data/README +48 -0
  3. data/bin/console +11 -0
  4. data/bin/cortex_reaver +110 -0
  5. data/bin/import.rb +78 -0
  6. data/lib/cortex_reaver.rb +114 -0
  7. data/lib/cortex_reaver/config.rb +21 -0
  8. data/lib/cortex_reaver/controller/comment.rb +113 -0
  9. data/lib/cortex_reaver/controller/config.rb +14 -0
  10. data/lib/cortex_reaver/controller/journal.rb +40 -0
  11. data/lib/cortex_reaver/controller/main.rb +56 -0
  12. data/lib/cortex_reaver/controller/page.rb +34 -0
  13. data/lib/cortex_reaver/controller/photograph.rb +45 -0
  14. data/lib/cortex_reaver/controller/project.rb +40 -0
  15. data/lib/cortex_reaver/controller/tag.rb +67 -0
  16. data/lib/cortex_reaver/controller/user.rb +71 -0
  17. data/lib/cortex_reaver/helper/activity.rb +9 -0
  18. data/lib/cortex_reaver/helper/attachments.rb +139 -0
  19. data/lib/cortex_reaver/helper/auth.rb +45 -0
  20. data/lib/cortex_reaver/helper/canonical.rb +55 -0
  21. data/lib/cortex_reaver/helper/crud.rb +317 -0
  22. data/lib/cortex_reaver/helper/date.rb +29 -0
  23. data/lib/cortex_reaver/helper/error.rb +14 -0
  24. data/lib/cortex_reaver/helper/feeds.rb +88 -0
  25. data/lib/cortex_reaver/helper/form.rb +114 -0
  26. data/lib/cortex_reaver/helper/navigation.rb +142 -0
  27. data/lib/cortex_reaver/helper/photographs.rb +25 -0
  28. data/lib/cortex_reaver/helper/tags.rb +47 -0
  29. data/lib/cortex_reaver/helper/workflow.rb +27 -0
  30. data/lib/cortex_reaver/migrations/001_users.rb +24 -0
  31. data/lib/cortex_reaver/migrations/002_pages.rb +29 -0
  32. data/lib/cortex_reaver/migrations/003_journals.rb +26 -0
  33. data/lib/cortex_reaver/migrations/004_photographs.rb +24 -0
  34. data/lib/cortex_reaver/migrations/005_projects.rb +27 -0
  35. data/lib/cortex_reaver/migrations/006_tags.rb +64 -0
  36. data/lib/cortex_reaver/migrations/007_comments.rb +40 -0
  37. data/lib/cortex_reaver/migrations/008_config.rb +23 -0
  38. data/lib/cortex_reaver/model/comment.rb +109 -0
  39. data/lib/cortex_reaver/model/journal.rb +53 -0
  40. data/lib/cortex_reaver/model/page.rb +87 -0
  41. data/lib/cortex_reaver/model/photograph.rb +133 -0
  42. data/lib/cortex_reaver/model/project.rb +49 -0
  43. data/lib/cortex_reaver/model/tag.rb +72 -0
  44. data/lib/cortex_reaver/model/user.rb +147 -0
  45. data/lib/cortex_reaver/public/css/admin.css +45 -0
  46. data/lib/cortex_reaver/public/css/custom.css +0 -0
  47. data/lib/cortex_reaver/public/css/form.css +51 -0
  48. data/lib/cortex_reaver/public/css/main.css +325 -0
  49. data/lib/cortex_reaver/public/css/photo.css +113 -0
  50. data/lib/cortex_reaver/public/css/ramaze_error.css +90 -0
  51. data/lib/cortex_reaver/public/css/text.css +25 -0
  52. data/lib/cortex_reaver/public/dispatch.fcgi +11 -0
  53. data/lib/cortex_reaver/public/images/CortexReaver.gif +0 -0
  54. data/lib/cortex_reaver/public/images/atom-xml-icon.png +0 -0
  55. data/lib/cortex_reaver/public/images/body.png +0 -0
  56. data/lib/cortex_reaver/public/images/border_bottom.png +0 -0
  57. data/lib/cortex_reaver/public/images/border_bottom_left.png +0 -0
  58. data/lib/cortex_reaver/public/images/border_bottom_right.png +0 -0
  59. data/lib/cortex_reaver/public/images/border_left.png +0 -0
  60. data/lib/cortex_reaver/public/images/border_right.png +0 -0
  61. data/lib/cortex_reaver/public/images/border_top.png +0 -0
  62. data/lib/cortex_reaver/public/images/border_top_left.png +0 -0
  63. data/lib/cortex_reaver/public/images/border_top_right.png +0 -0
  64. data/lib/cortex_reaver/public/images/comment.gif +0 -0
  65. data/lib/cortex_reaver/public/images/dark_trans.png +0 -0
  66. data/lib/cortex_reaver/public/images/delete.gif +0 -0
  67. data/lib/cortex_reaver/public/images/edit.gif +0 -0
  68. data/lib/cortex_reaver/public/images/header.png +0 -0
  69. data/lib/cortex_reaver/public/images/header.xcf +0 -0
  70. data/lib/cortex_reaver/public/images/header_background.png +0 -0
  71. data/lib/cortex_reaver/public/images/parent.gif +0 -0
  72. data/lib/cortex_reaver/public/images/rss-xml-icon.png +0 -0
  73. data/lib/cortex_reaver/public/images/sections.png +0 -0
  74. data/lib/cortex_reaver/public/images/sections_highlight.png +0 -0
  75. data/lib/cortex_reaver/public/js/admin.js +36 -0
  76. data/lib/cortex_reaver/public/js/cookie.js +27 -0
  77. data/lib/cortex_reaver/public/js/jquery.js +32 -0
  78. data/lib/cortex_reaver/public/js/photo.js +33 -0
  79. data/lib/cortex_reaver/snippets/array.rb +7 -0
  80. data/lib/cortex_reaver/snippets/ramaze/dispatcher/file.rb +37 -0
  81. data/lib/cortex_reaver/support/attachments.rb +235 -0
  82. data/lib/cortex_reaver/support/cached_rendering.rb +79 -0
  83. data/lib/cortex_reaver/support/canonical.rb +107 -0
  84. data/lib/cortex_reaver/support/comments.rb +69 -0
  85. data/lib/cortex_reaver/support/pagination.rb +38 -0
  86. data/lib/cortex_reaver/support/renderer.rb +196 -0
  87. data/lib/cortex_reaver/support/sequenceable.rb +248 -0
  88. data/lib/cortex_reaver/support/tags.rb +108 -0
  89. data/lib/cortex_reaver/support/timestamps.rb +33 -0
  90. data/lib/cortex_reaver/version.rb +8 -0
  91. data/lib/cortex_reaver/view/adminbox.rhtml +56 -0
  92. data/lib/cortex_reaver/view/blank_layout.rhtml +46 -0
  93. data/lib/cortex_reaver/view/comments/comment.rhtml +34 -0
  94. data/lib/cortex_reaver/view/comments/form.rhtml +25 -0
  95. data/lib/cortex_reaver/view/comments/list.rhtml +5 -0
  96. data/lib/cortex_reaver/view/comments/post_form.rhtml +36 -0
  97. data/lib/cortex_reaver/view/config/form.rhtml +10 -0
  98. data/lib/cortex_reaver/view/error.rhtml +72 -0
  99. data/lib/cortex_reaver/view/journals/form.rhtml +12 -0
  100. data/lib/cortex_reaver/view/journals/journal.rhtml +39 -0
  101. data/lib/cortex_reaver/view/journals/list.rhtml +33 -0
  102. data/lib/cortex_reaver/view/journals/short.rhtml +3 -0
  103. data/lib/cortex_reaver/view/journals/show.rhtml +5 -0
  104. data/lib/cortex_reaver/view/pages/form.rhtml +12 -0
  105. data/lib/cortex_reaver/view/pages/list.rhtml +26 -0
  106. data/lib/cortex_reaver/view/pages/show.rhtml +12 -0
  107. data/lib/cortex_reaver/view/photographs/atom_fragment.rhtml +82 -0
  108. data/lib/cortex_reaver/view/photographs/form.rhtml +19 -0
  109. data/lib/cortex_reaver/view/photographs/grid.rhtml +36 -0
  110. data/lib/cortex_reaver/view/photographs/list.rhtml +9 -0
  111. data/lib/cortex_reaver/view/photographs/short.rhtml +3 -0
  112. data/lib/cortex_reaver/view/photographs/show.rhtml +114 -0
  113. data/lib/cortex_reaver/view/photographs/sidebar.rhtml +7 -0
  114. data/lib/cortex_reaver/view/projects/form.rhtml +13 -0
  115. data/lib/cortex_reaver/view/projects/list.rhtml +27 -0
  116. data/lib/cortex_reaver/view/projects/show.rhtml +38 -0
  117. data/lib/cortex_reaver/view/tags/form.rhtml +9 -0
  118. data/lib/cortex_reaver/view/tags/list.rhtml +28 -0
  119. data/lib/cortex_reaver/view/tags/show.rhtml +51 -0
  120. data/lib/cortex_reaver/view/text_layout.rhtml +78 -0
  121. data/lib/cortex_reaver/view/users/form.rhtml +16 -0
  122. data/lib/cortex_reaver/view/users/list.rhtml +38 -0
  123. data/lib/cortex_reaver/view/users/login.rhtml +13 -0
  124. data/lib/cortex_reaver/view/users/register.rhtml +13 -0
  125. data/lib/cortex_reaver/view/users/show.rhtml +37 -0
  126. data/lib/cortex_reaver/view/users/user.rhtml +35 -0
  127. data/lib/proto/cortex_reaver.yaml +28 -0
  128. metadata +285 -0
@@ -0,0 +1,45 @@
1
+ module Ramaze
2
+ module Helper
3
+ # Provides authentication services
4
+ module Auth
5
+ # Is the current user an admin?
6
+ def admin?
7
+ u = session[:user] and u.admin?
8
+ end
9
+
10
+ # Tries to log in a user by login and password. If successful, sets
11
+ # session[:user] to the user and returns that user. Otherwise returns
12
+ # false.
13
+ def do_login(login, password)
14
+ if user = CortexReaver::User.authenticate(login, password)
15
+ # Successful login
16
+ session[:user] = user
17
+ else
18
+ false
19
+ end
20
+ end
21
+
22
+ # Log out the current user, and returns the user object.
23
+ def do_logout
24
+ session.delete :user
25
+ end
26
+
27
+ def require_admin
28
+ if session[:user] and session[:user].admin?
29
+ true
30
+ elsif session[:user]
31
+ flash[:error] = "You must have administrative privileges to do this."
32
+ redirect :/
33
+ else
34
+ flash[:notice] = "Please log in first."
35
+ session[:target_uri] = request.request_uri
36
+ redirect R('/users', :login)
37
+ end
38
+ end
39
+
40
+ def error_403
41
+ respond 'Forbidden', 403
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,55 @@
1
+ module Ramaze
2
+ module Helper
3
+ module Canonical
4
+ Helper::LOOKUP << self
5
+
6
+ # Returns the canonical name for this model, given a candidate new name.
7
+ def canonicalize
8
+ respond self.class.const_get('MODEL').canonicalize(
9
+ request[:new],
10
+ request[:id]
11
+ )
12
+ end
13
+
14
+ private
15
+
16
+ # Adds an AJAX-ified name field for a model.
17
+ def live_name_field(model, opts={:name => 'name', :watch => 'title'})
18
+ name = opts[:name]
19
+ watch = opts[:watch]
20
+ title = opts[:title] || name.to_s.titleize
21
+
22
+ s = "<p><label for=\"#{name}\">#{title}</label>"
23
+ s << "<input name=\"#{name}\" id=\"#{name}\" type=\"text\" value=\"#{attr_h(model.send(opts[:name]))}\" /></p>"
24
+
25
+ s << <<EOF
26
+ <script type="text/javascript">
27
+ /* <![CDATA[ */
28
+
29
+ // Updates the "name" field as the watched field changes.
30
+ $('##{watch}').change(function () {
31
+ $.get('#{Rs('canonicalize')}', {
32
+ new: $('##{watch}').val(),
33
+ id: '#{model.id}'
34
+ }, function(response) {
35
+ $('##{name}').val(response);
36
+ })
37
+ });
38
+
39
+ // Canonicalizes the name when we're done, just to make sure.
40
+ $('##{name}').blur(function() {
41
+ $.get('#{Rs('canonicalize')}', {
42
+ new: $('##{name}').val(),
43
+ id: '#{model.id}'
44
+ }, function(response) {
45
+ $('##{name}').val(response);
46
+ })
47
+ });
48
+
49
+ /* ]]> */
50
+ </script>
51
+ EOF
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,317 @@
1
+ module Ramaze
2
+ module Helper
3
+ # Provides list, create, read, update, and delete functionality for Cortex
4
+ # Reaver. Centralizes the lookup and database logic for re-use across
5
+ # several models.
6
+ #
7
+ # Requires pagination, auth, workflow, and feeds
8
+ module Crud
9
+ require 'builder'
10
+
11
+ Helper::LOOKUP << self
12
+
13
+ def self.included(base)
14
+ base.instance_eval do
15
+ # Provides on_save, on_create, on_update callbacks for controllers,
16
+ # which defines how the controller CRUD sets attributes on the
17
+ # primary model.
18
+
19
+ # This block is called when a new model is being created. The model
20
+ # and request are passed to the block. The resulting model is stored
21
+ # in the database. Example:
22
+ #
23
+ # JournalController.on_save do |journal, request|
24
+ # journal.title = request[:title]
25
+ # journal.body = request[:body].downcase
26
+ # end
27
+
28
+ def self.on_create(&block)
29
+ unless block_given? and block.arity == 2
30
+ raise ArgumentError.new("needs a block with two arguments")
31
+ end
32
+ @on_create = block
33
+ end
34
+
35
+ def self.on_update(&block)
36
+ unless block_given? and block.arity == 2
37
+ raise ArgumentError.new("needs a block with two arguments")
38
+ end
39
+ @on_update = block
40
+ end
41
+
42
+ def self.on_save(&block)
43
+ unless block_given? and block.arity == 2
44
+ raise ArgumentError.new("needs a block with two arguments")
45
+ end
46
+ @on_save = block
47
+ end
48
+
49
+ def self.on_second_save(&block)
50
+ unless block_given? and block.arity == 2
51
+ raise ArgumentError.new("needs a block with two arguments")
52
+ end
53
+ @on_second_save = block
54
+ end
55
+
56
+ def self.on_create_block
57
+ @on_create
58
+ end
59
+
60
+ def self.on_save_block
61
+ @on_save
62
+ end
63
+
64
+ def self.on_second_save_block
65
+ @on_second_save
66
+ end
67
+
68
+ def self.on_update_block
69
+ @on_update
70
+ end
71
+ end
72
+
73
+ base.class_eval do
74
+ # Check that MODEL is defined, so we know what we're working with.
75
+ unless const_defined?('MODEL')
76
+ raise RuntimeError.new("can't use CRUD helper unless MODEL is set.")
77
+ end
78
+
79
+ # Set the singular and plural instance vars for MODEL.
80
+ unless const_defined?('SINGULAR_MODEL_VAR')
81
+ const_set('SINGULAR_MODEL_VAR', '@' + const_get('MODEL').to_s.demodulize.underscore)
82
+ end
83
+ unless const_defined?('PLURAL_MODEL_VAR')
84
+ const_set('PLURAL_MODEL_VAR', const_get('SINGULAR_MODEL_VAR').pluralize)
85
+ end
86
+
87
+ # Prevent conflicts with our 0-ary action methods. Not needed if
88
+ # we're using controller/show/name instead of controller/name
89
+ #
90
+ #begin
91
+ # const_get('MODEL').reserved_canonical_names += ['index', 'new']
92
+ #rescue
93
+ #end
94
+
95
+ # This action can't be in the normal module body, or it breaks things.
96
+ def new
97
+ require_admin
98
+
99
+ @title = "New #{h model_class.to_s.demodulize.titleize}"
100
+ @form_action = :new
101
+
102
+ if request.post?
103
+ begin
104
+ # Create model
105
+ CortexReaver.db.transaction do
106
+ @model = model_class.new
107
+
108
+ # Initial callbacks
109
+ if block = self.class.on_save_block
110
+ block.call(@model, request)
111
+ end
112
+ if block = self.class.on_create_block
113
+ block.call(@model, request)
114
+ end
115
+
116
+ # Save for the first time
117
+ raise unless @model.save
118
+
119
+ # Second save callback, if applicable
120
+ if block = self.class.on_second_save_block
121
+ block.call(@model, request)
122
+ raise unless @model.save
123
+ end
124
+
125
+ flash[:notice] = "Created #{model_class.to_s.demodulize.downcase} #{h @model.to_s}."
126
+ redirect @model.url
127
+ end
128
+ rescue => e
129
+ # An error occurred
130
+ Ramaze::Log.error e.inspect + e.backtrace.join("\n")
131
+ flash[:error] = "Couldn't create #{model_class.to_s.demodulize.downcase} #{h request[:title]}: #{h e}."
132
+ set_singular_model_var @model
133
+ end
134
+ else
135
+ set_singular_model_var model_class.new
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ # Returns the model class for this controller, e.g., 'Journal'
144
+ def model_class
145
+ self.class.const_get('MODEL')
146
+ end
147
+
148
+ # Set the singular instance var for this controller, e.g. '@journal'.
149
+ def set_singular_model_var(value)
150
+ instance_variable_set(self.class.const_get('SINGULAR_MODEL_VAR'), value)
151
+ end
152
+
153
+ # Set the plural instance var for this controller, e.g. '@journals'
154
+ def set_plural_model_var(value)
155
+ instance_variable_set(self.class.const_get('PLURAL_MODEL_VAR'), value)
156
+ end
157
+
158
+ public
159
+
160
+ # Normal actions start here.
161
+
162
+ def index(id = nil)
163
+ if id
164
+ # Redirect to show
165
+ #
166
+ # This way, you can (assuming your name doesn't conflict with an existing
167
+ # action) tell people to visit /journals/my-cool-event, and it will go to
168
+ # /journals/show/my-cool-event.
169
+ raw_redirect Rs(:show, id), :status => 301
170
+ else
171
+ # Display index
172
+ page :last
173
+ end
174
+ end
175
+
176
+ def delete(id)
177
+ require_admin
178
+
179
+ if @model = model_class.get(id)
180
+ if @model.destroy
181
+ flash[:notice] = "#{model_class.to_s.demodulize.downcase} #{h @model.to_s} deleted."
182
+ redirect Rs()
183
+ else
184
+ flash[:notice] = "Couldn't delete #{model_class.to_s.demodulize.downcase} #{h @model.to_s}."
185
+ redirect @model.url
186
+ end
187
+ else
188
+ flash[:error] = "No such #{model_class.to_s.demodulize.downcase} (#{h id}) exists."
189
+ redirect Rs()
190
+ end
191
+ end
192
+
193
+ def edit(id = nil)
194
+ require_admin
195
+
196
+ if @model = model_class.get(id)
197
+ @title = "Edit #{model_class.to_s.demodulize.downcase} #{h @model.to_s}"
198
+ if @model.class.respond_to? :canonical_name_attr
199
+ @form_action = "edit/#{@model.send(@model.class.canonical_name_attr)}"
200
+ else
201
+ @form_action = "edit/#{@model.id}"
202
+ end
203
+
204
+ set_singular_model_var @model
205
+
206
+ if request.post?
207
+ begin
208
+ # Update model
209
+ CortexReaver.db.transaction do
210
+ # Initial callbacks
211
+ if block = self.class.on_save_block
212
+ block.call(@model, request)
213
+ end
214
+ if block = self.class.on_update_block
215
+ block.call(@model, request)
216
+ end
217
+
218
+ # Save
219
+ raise unless @model.save
220
+
221
+ # Second callbacks and save if applicable
222
+ if block = self.class.on_second_save_block
223
+ block.call(@model, request)
224
+ raise unless @model.save
225
+ end
226
+
227
+ flash[:notice] = "Updated #{model_class.to_s.demodulize.downcase} #{h @model.to_s}."
228
+ redirect @model.url
229
+ end
230
+ rescue => e
231
+ # An error occurred
232
+ Ramaze::Log.error e.inspect + e.backtrace.join("\n")
233
+ flash[:error] = "Couldn't update #{model_class.to_s.demodulize.downcase} #{h @model.to_s}: #{h e}."
234
+ end
235
+ end
236
+ else
237
+ flash[:error] = "No such #{model_class.to_s.demodulize.downcase} (#{id}) exists."
238
+ redirect Rs()
239
+ end
240
+ end
241
+
242
+ def page(page)
243
+
244
+ page = case page
245
+ when Symbol
246
+ page
247
+ when 'first'
248
+ :first
249
+ when 'last'
250
+ :last
251
+ when nil
252
+ error_404
253
+ else
254
+ page.to_i
255
+ end
256
+
257
+ @title = "#{model_class.to_s.demodulize.pluralize.titleize}"
258
+ if self.class.private_method_defined? :feed and model_class.respond_to? :atom_url
259
+ feed @title, model_class.atom_url
260
+ end
261
+ @models = model_class.window(page)
262
+ @page = page
263
+
264
+ if model_class.count.zero?
265
+ # There are NO models
266
+ elsif @models.empty?
267
+ # Empty page
268
+ error_404
269
+ end
270
+
271
+ set_plural_model_var @models
272
+
273
+ workflow "New #{model_class.to_s.demodulize}", Rs(:new)
274
+
275
+ render_template :list
276
+ end
277
+
278
+ def show(id)
279
+ if id and @model = model_class.get(id)
280
+ # Found that model
281
+ # Redirect IDs to names
282
+ raw_redirect(@model.url, :status => 301) if id =~ /^\d+$/
283
+
284
+ @title = h @model.to_s
285
+ set_singular_model_var @model
286
+
287
+ # Retrieve pending comment from session, if applicable.
288
+ if comment = session[:pending_comment] and comment.parent == @model
289
+ @new_comment = session.delete :pending_comment
290
+ else
291
+ # Create a comment to be posted
292
+ @new_comment = CortexReaver::Comment.new
293
+ @new_comment.send("#{model_class.to_s.demodulize.underscore.downcase}=", @model)
294
+ end
295
+
296
+ if session[:user]
297
+ @new_comment.user = session[:user]
298
+ end
299
+
300
+ # ID component of edit/delete links
301
+ if @model.class.respond_to? :canonical_name_attr
302
+ id = @model.send(@model.class.canonical_name_attr)
303
+ else
304
+ id = @model.id
305
+ end
306
+
307
+ workflow "Edit this #{model_class.to_s.demodulize}", Rs(:edit, id)
308
+ workflow "Delete this #{model_class.to_s.demodulize}", Rs(:delete, id)
309
+ render_template :show
310
+ elsif id
311
+ # Didn't find that model
312
+ error_404
313
+ end
314
+ end
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,29 @@
1
+ module Ramaze
2
+ module Helper
3
+ # Provides assistance methods for displaying dates of objects.
4
+ module Date
5
+ # Puts out a line describing the creation (and modification) date of a
6
+ # model. If times are within identical_tolerance seconds (for example,
7
+ # because you forgot something on the form, hit back, and updated the
8
+ # record), the update time isn't displayed.
9
+ def date_line(model, identical_tolerance = 3600)
10
+ c = model.created_on
11
+ u = model.updated_on
12
+ date = '<span class="date">' + c.strftime('%A, %e %B %Y, %H:%M') + "</span>"
13
+
14
+ if (u - c) > identical_tolerance
15
+ # A significant modification time.
16
+ if u.year != c.year
17
+ date << " (updated <span class=\"date\">#{u.strftime('%A, %e %B %Y, %H:%M')}</span>)"
18
+ elsif u.day != c.day
19
+ date << " (updated <span class=\"date\">#{u.strftime('%A, %e %B, %H:%M')})</span>"
20
+ else
21
+ date << " (updated <span class=\"date\">#{u.strftime('%H:%M')}</span>)"
22
+ end
23
+ end
24
+
25
+ date
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ module Ramaze
2
+ module Helper
3
+ # Provides some error pages
4
+ module Error
5
+ def error_404
6
+ respond 'Page not found', 404
7
+ end
8
+
9
+ def error_403
10
+ respond 'Forbidden', 403
11
+ end
12
+ end
13
+ end
14
+ end