zena 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/History.txt +29 -1
  2. data/Rakefile +0 -1
  3. data/app/controllers/documents_controller.rb +1 -1
  4. data/app/controllers/nodes_controller.rb +34 -8
  5. data/app/controllers/sites_controller.rb +8 -1
  6. data/app/controllers/user_sessions_controller.rb +13 -3
  7. data/app/models/acl.rb +16 -0
  8. data/app/models/document.rb +33 -14
  9. data/app/models/idx_nodes_integer.rb +5 -0
  10. data/app/models/image.rb +16 -4
  11. data/app/models/node.rb +16 -3
  12. data/app/models/relation_proxy.rb +3 -3
  13. data/app/models/site.rb +11 -1
  14. data/app/models/string_hash.rb +1 -1
  15. data/app/models/template.rb +1 -1
  16. data/app/models/user.rb +6 -1
  17. data/app/models/virtual_class.rb +36 -1
  18. data/app/views/acls/_form.rhtml +5 -1
  19. data/app/views/acls/_li.rhtml +1 -1
  20. data/app/views/templates/document_create_tabs/_file.rhtml +1 -0
  21. data/app/views/templates/document_create_tabs/_template.rhtml +1 -1
  22. data/app/views/users/_form.rhtml +1 -0
  23. data/app/views/virtual_classes/_form.erb +8 -7
  24. data/bricks/acls/lib/bricks/acls.rb +43 -15
  25. data/bricks/acls/zena/migrate/20130313110443_add_create_kpath_to_acl.rb +13 -0
  26. data/bricks/acls/zena/migrate/20130429073432_fix_create_kpath_default.rb +8 -0
  27. data/bricks/acls/zena/test/integration/acl_integration_test.rb +53 -1
  28. data/bricks/acls/zena/test/sites/erebus/acls.yml +21 -0
  29. data/bricks/acls/zena/test/unit/acl_test.rb +35 -2
  30. data/bricks/math/lib/bricks/math.rb +1 -1
  31. data/bricks/sphinx/zena/tasks.rb +1 -1
  32. data/bricks/spreadsheet/lib/bricks/spreadsheet.rb +1 -1
  33. data/bricks/worker/zena/worker +25 -0
  34. data/config/environment.rb +1 -1
  35. data/config/environments/production.rb +1 -1
  36. data/config/gems.yml +6 -5
  37. data/lib/bricks/requirements_validation.rb +1 -1
  38. data/lib/log_recorder/lib/log_recorder.rb +1 -1
  39. data/lib/tasks/zena.rake +10 -2
  40. data/lib/zena.rb +4 -3
  41. data/lib/zena/app.rb +1 -0
  42. data/lib/zena/deploy/httpd.rhtml +2 -2
  43. data/lib/zena/deploy/template.rb +15 -5
  44. data/lib/zena/info.rb +1 -1
  45. data/lib/zena/parser/zazen_rules.rb +9 -2
  46. data/lib/zena/remote/connection.rb +2 -2
  47. data/lib/zena/remote/interface.rb +8 -2
  48. data/lib/zena/remote/node.rb +1 -1
  49. data/lib/zena/routes.rb +2 -1
  50. data/lib/zena/use/action.rb +8 -2
  51. data/lib/zena/use/ajax.rb +31 -20
  52. data/lib/zena/use/calendar.rb +2 -0
  53. data/lib/zena/use/conditional.rb +15 -14
  54. data/lib/zena/use/dates.rb +5 -2
  55. data/lib/zena/use/display.rb +3 -2
  56. data/lib/zena/use/forms.rb +36 -9
  57. data/lib/zena/use/i18n.rb +8 -2
  58. data/lib/zena/use/image_builder.rb +7 -0
  59. data/lib/zena/use/query_node.rb +24 -8
  60. data/lib/zena/use/relations.rb +2 -6
  61. data/lib/zena/use/rendering.rb +10 -6
  62. data/lib/zena/use/upload.rb +6 -4
  63. data/lib/zena/use/urls.rb +13 -5
  64. data/lib/zena/use/zafu_safe_definitions.rb +1 -1
  65. data/public/javascripts/grid.js +11 -2
  66. data/public/javascripts/upload-progress.js +5 -3
  67. data/public/javascripts/zena.js +6 -2
  68. data/public/stylesheets/upload-progress.css +1 -0
  69. data/test/fixtures/files/TestNode.zafu +2 -2
  70. data/test/fixtures/files/translations_fr.yml +2 -1
  71. data/test/functional/acls_controller_test.rb +6 -0
  72. data/test/functional/nodes_controller_test.rb +1 -1
  73. data/test/functional/sites_controller_test.rb +19 -0
  74. data/test/integration/navigation_test.rb +7 -0
  75. data/test/integration/query_node/filters.yml +10 -0
  76. data/test/integration/zafu_compiler/action.yml +8 -4
  77. data/test/integration/zafu_compiler/ajax.yml +4 -4
  78. data/test/integration/zafu_compiler/calendar.yml +8 -15
  79. data/test/integration/zafu_compiler/context.yml +1 -1
  80. data/test/integration/zafu_compiler/dates.yml +5 -1
  81. data/test/integration/zafu_compiler/display.yml +1 -2
  82. data/test/integration/zafu_compiler/forms.yml +37 -10
  83. data/test/integration/zafu_compiler/query.yml +5 -5
  84. data/test/integration/zafu_compiler/relations.yml +8 -8
  85. data/test/integration/zafu_compiler/safe_definitions.yml +7 -2
  86. data/test/integration/zafu_compiler/urls.yml +24 -3
  87. data/test/integration/zafu_compiler/zazen.yml +9 -1
  88. data/test/selenium/Destroy/destroy1.rsel +2 -1
  89. data/test/selenium/Destroy/destroy2.rsel +17 -0
  90. data/test/unit/document_test.rb +17 -4
  91. data/test/unit/relation_proxy_test.rb +19 -8
  92. data/test/unit/string_hash_test.rb +1 -1
  93. data/test/unit/template_test.rb +3 -3
  94. data/test/unit/virtual_class_test.rb +77 -0
  95. data/test/unit/zena/use/urls_test.rb +9 -1
  96. data/vendor/plugins/selenium-on-rails/lib/selenium_on_rails_config.rb +1 -1
  97. data/zena.gemspec +60 -53
  98. metadata +145 -125
@@ -1,8 +1,36 @@
1
+ == 1.2.4 2013-06-13
2
+
3
+ * Major changes
4
+ * Added class filtering to Acl in 'create' action. <== TODO: Document
5
+ * Uploading html files does not transform them into zafu (use .zafu ext for this)
6
+ * Added file upload support from zena remote API.
7
+ * Added 'content_type' regexp to force virtual class on Document creation. <== TODO: Document
8
+ * Extended "create group" in vclass to be "allow group" (edit).
9
+ * Added support for [versions_list] to display list of versions and status.
10
+
11
+ * Minor changes
12
+ * Support for raw html in zazen with '<notextile>' or '<html>' tag. Must be allowed with notextile='true' on [zazen] tag.
13
+ * Fixed preview of content with ACL (considering the POST on /zafu as a 'read').
14
+ * Added support for "class not like Image" or "class <> Image" to sqliss.
15
+ * Added url to clear cache with /sites/clear_cache (admin only). <== TODO: Document
16
+ * encode_params now supports arrays.
17
+ * Add 'id' to date input.
18
+ * Fixed 404 error.
19
+ * Toggle takes dynamic parameters for "js" and "arity".
20
+ * Fixed multiple toggles side-by-side.
21
+ * Fixed nested blocks and class scoping.
22
+ * Added support for "onUpdate" in [input] with date type.
23
+ * Hash 'keys' returns sorted elements in zafu.
24
+ * Improved computation of width and height in [img] when using 'forced' iformat.
25
+ * Image width and height properties auto-fix themselves (need to read file on each display if not fixed).
26
+ * Added support for 'mode' in encode_params.
27
+ * Fixed [link_name]_status, _comment and other relation proxy methods.
28
+
1
29
  == 1.2.3 2013-03-11
2
30
 
3
31
  * Major changes
4
32
  * Better support for Passenger (deploy receipt, asset host)
5
- * Support for 'sortable' <== TODO: Document
33
+ * Support for 'sortable'
6
34
  * html_escape all properties by default
7
35
  * Better support for Passenger (default deployment method now)
8
36
  * Simplified caching (using cachestamp in filename)
data/Rakefile CHANGED
@@ -5,7 +5,6 @@ require(File.join(File.dirname(__FILE__), 'config', 'boot'))
5
5
 
6
6
  require 'rake'
7
7
  require 'rake/testtask'
8
- require 'rake/rdoctask'
9
8
 
10
9
  require 'tasks/rails'
11
10
 
@@ -13,7 +13,7 @@ class DocumentsController < ApplicationController
13
13
  def new
14
14
  # Use the Template class so that we can use the same object in forms which need the Template properties.
15
15
  @node = @parent.new_child(:class => Template)
16
-
16
+
17
17
  respond_to do |format|
18
18
  format.html
19
19
  end
@@ -23,7 +23,7 @@ class NodesController < ApplicationController
23
23
  if Bricks.raw_config['passenger']
24
24
  before_filter :escape_path, :only => [:index, :show]
25
25
  end
26
- before_filter :find_node, :except => [:index, :create, :not_found, :catch_all, :search]
26
+ before_filter :find_node, :except => [:index, :create, :update, :zafu, :not_found, :catch_all, :search]
27
27
  before_filter :check_can_drive, :only => [:edit]
28
28
  before_filter :check_path, :only => [:index, :show]
29
29
 
@@ -113,10 +113,23 @@ class NodesController < ApplicationController
113
113
  end
114
114
  end
115
115
 
116
- # RJS method. show.js not working... ?
117
- # FIXME: remove.
116
+ # RJS method. Enables using POST in JS for large text preview. Seen as 'read' in ACL.
118
117
  def zafu
119
- return self.update if params[:method] == 'put'
118
+ # We allow preview by using POST requests (long text in js) but they should appear as 'GET' in
119
+ # find_node.
120
+ request.method = 'GET' if request.method == 'POST'
121
+
122
+ @node = visitor.find_node(nil, params[:id], nil, request)
123
+
124
+ if params[:link_id]
125
+ @link = Link.find_through(@node, params[:link_id])
126
+ end
127
+
128
+ # security risk with ACL (change an object before display with extended rights). Must check no ACL before
129
+ # preview. Only enable with proper security if this is really needed.
130
+ # if params['node']
131
+ # @node.attributes = secure(Node) {Node.transform_attributes(params['node'], @node, true)}
132
+ # end
120
133
  respond_to do |format|
121
134
  format.js { render :action => 'show' }
122
135
  end
@@ -225,12 +238,20 @@ class NodesController < ApplicationController
225
238
 
226
239
  begin
227
240
  # Make sure we can load parent (also enables ACL to work for us here).
228
- parent = visitor.find_node(nil, attrs.delete('parent_zip'), nil, request)
241
+ zip = attrs.delete('parent_zip')
242
+ parent = visitor.find_node(nil, zip, nil, request, true)
243
+
229
244
  @node = parent.new_child(attrs, false)
230
- @node.save
245
+ if visitor.exec_acl && !(@node.kpath =~ %r{^#{visitor.exec_acl.create_kpath}})
246
+ # Document creation can change initial klass depending on mime type. Make sure it is still allowed.
247
+ @node.errors.add('klass', 'Not allowed')
248
+ else
249
+ @node.save
250
+ end
231
251
  rescue ActiveRecord::RecordNotFound
232
252
  # Let normal processing insert errors
233
- @node = secure!(Node) { Node.create_node(attrs) }
253
+ @node = Node.new
254
+ @node.errors.add('base', 'Not allowed')
234
255
  end
235
256
  @node.errors.add('file', file_error) if file_error
236
257
 
@@ -388,6 +409,12 @@ class NodesController < ApplicationController
388
409
  end
389
410
 
390
411
  def update
412
+ @node = visitor.find_node(nil, params[:id], nil, request, true)
413
+
414
+ if params[:link_id]
415
+ @link = Link.find_through(@node, params[:link_id])
416
+ end
417
+
391
418
  params['node'] ||= {}
392
419
  file, file_error = get_attachment
393
420
  params['node']['file'] = file if file
@@ -612,7 +639,6 @@ class NodesController < ApplicationController
612
639
  else
613
640
  set_format(stamp_and_format)
614
641
  end
615
-
616
642
  # We use the visitor to find the node in order to ease implementation
617
643
  # of custom access rules (Acl).
618
644
  @node = visitor.find_node(path, zip, name, request)
@@ -1,6 +1,7 @@
1
+ # TODO: Cleanup sites_controller now that we only support visitors for a single site !!
1
2
  class SitesController < ApplicationController
2
3
  before_filter :remove_methods, :only => [:new, :create, :destroy]
3
- before_filter :find_site, :except => [:index, :create, :new]
4
+ before_filter :find_site, :except => [:index, :create, :new, :clear_cache]
4
5
  before_filter :visitor_node
5
6
  before_filter :check_is_admin
6
7
  layout :admin_layout
@@ -51,6 +52,12 @@ class SitesController < ApplicationController
51
52
  end
52
53
  end
53
54
  end
55
+
56
+ def clear_cache
57
+ @site = secure!(Site) { Site.first }
58
+ @site.clear_cache
59
+ redirect_to '/'
60
+ end
54
61
 
55
62
  def action
56
63
  if Site::ACTIONS.include?(params[:do])
@@ -3,7 +3,7 @@
3
3
  Create, destroy sessions by letting users login and logout. When the user does not login, he/she is considered to be the anonymous user.
4
4
  =end
5
5
  class UserSessionsController < ApplicationController
6
- skip_before_filter :set_after_login, :force_authentication?, :redirect_to_https
6
+ skip_before_filter :force_authentication?, :redirect_to_https
7
7
  before_filter :session_redirect_to_https
8
8
 
9
9
  # /login
@@ -28,15 +28,25 @@ class UserSessionsController < ApplicationController
28
28
  end
29
29
  end
30
30
 
31
+ # Logout
31
32
  def destroy
32
33
  port = request.port == 80 ? '' : ":#{request.port}"
33
34
  if @user_session = UserSession.find
34
35
  @user_session.destroy
35
36
  reset_session
37
+ if current_site.ssl_on_auth
38
+ # SSH only when authenticated
39
+ host = current_site.host
40
+ http = 'http'
41
+ else
42
+ # Keep current host and port settings
43
+ host = host_with_port
44
+ http = host =~ /:/ ? 'https' : 'http'
45
+ end
36
46
  #flash.now[:notice] = _("Successfully logged out.")
37
- redirect_to "http://#{current_site.host}#{params[:redirect] || home_path(:prefix => prefix)}"
47
+ redirect_to "#{http}://#{host}#{params[:redirect] || home_path(:prefix => prefix)}"
38
48
  else
39
- redirect_to "http://#{current_site.host}#{home_path(:prefix => prefix)}"
49
+ redirect_to "http://#{host}#{home_path(:prefix => prefix)}"
40
50
  end
41
51
  end
42
52
 
@@ -49,6 +49,18 @@ class Acl < ActiveRecord::Base
49
49
  def visitor
50
50
  super
51
51
  end
52
+
53
+ def create_vclass_name
54
+ if create_kpath
55
+ if klass = VirtualClass.find_by_kpath(create_kpath)
56
+ klass.name
57
+ else
58
+ create_kpath
59
+ end
60
+ else
61
+ 'Node'
62
+ end
63
+ end
52
64
 
53
65
  protected
54
66
  def set_defaults
@@ -66,6 +78,10 @@ class Acl < ActiveRecord::Base
66
78
 
67
79
  def validate_acl
68
80
  make_query(visitor.prototype)
81
+ self[:create_kpath] = 'N' if self[:create_kpath].blank?
82
+ unless VirtualClass.find_by_kpath(create_kpath)
83
+ errors.add(:create_kpath, 'invalid (could not find class)')
84
+ end
69
85
  end
70
86
 
71
87
  def make_query(node, params = {}, request = nil)
@@ -63,29 +63,34 @@ class Document < Node
63
63
  def new(attrs = {}, vclass = nil)
64
64
  attrs = attrs.stringify_keys
65
65
  file = attrs['file'] || ((attrs['version_attributes'] || {})['content_attributes'] || {})['file']
66
- if attrs['content_type']
67
- content_type = attrs['content_type']
68
- elsif file && file.respond_to?(:content_type)
69
- content_type = file.content_type
70
- elsif ct = attrs['content_type']
66
+
67
+ if ct = attrs['content_type']
71
68
  content_type = ct
69
+ elsif file && file.respond_to?(:content_type) && file.content_type != 'application/octet-stream'
70
+ content_type = file.content_type
72
71
  elsif attrs['title'] =~ /^.*\.(\w+)$/ && types = Zena::EXT_TO_TYPE[$1.downcase]
73
72
  content_type = types[0]
73
+ elsif file
74
+ content_type = 'application/octet-stream'
75
+ elsif attrs['target_klass'] || self <= Template
76
+ content_type = 'text/zafu'
77
+ else
78
+ content_type = 'text/plain'
74
79
  end
75
80
 
76
- real_class = document_class_from_content_type(content_type)
81
+ klass = document_class_from_content_type(content_type)
82
+ real_class = klass.real_class
77
83
 
78
- unless vclass && vclass.kpath =~ /\A#{real_class.kpath}/
79
- # vclass is not compatible (force kpath)
80
- vclass = VirtualClass[real_class.to_s]
84
+ if vclass && vclass.kpath =~ /\A#{real_class.kpath}/ && vclass.content_type_re =~ content_type
85
+ klass = vclass
81
86
  end
82
87
 
83
88
  attrs['content_type'] = content_type
84
89
 
85
90
  if real_class != self
86
- secure(real_class) { real_class.o_new(attrs, vclass) }
91
+ secure(real_class) { real_class.o_new(attrs, klass) }
87
92
  else
88
- super(attrs, vclass)
93
+ super(attrs, klass)
89
94
  end
90
95
  end
91
96
 
@@ -99,7 +104,7 @@ class Document < Node
99
104
 
100
105
  # Return document class and content_type from content_type
101
106
  def document_class_from_content_type(content_type)
102
- if content_type
107
+ base = if content_type
103
108
  if Image.accept_content_type?(content_type)
104
109
  Image
105
110
  elsif Template.accept_content_type?(content_type)
@@ -115,6 +120,18 @@ class Document < Node
115
120
  else
116
121
  self
117
122
  end
123
+
124
+ # Try to find a virtual sub-class accepting the content type
125
+ vclass = nil
126
+ VirtualClass[base.to_s].sub_classes.each do |v|
127
+ next if v.real_class?
128
+
129
+ if content_type =~ v.content_type_re
130
+ vclass = v
131
+ break
132
+ end
133
+ end
134
+ vclass || VirtualClass[base.to_s]
118
135
  end
119
136
 
120
137
  # Return true if the content_type can change independantly from the file
@@ -156,7 +173,7 @@ class Document < Node
156
173
  def filepath(format=nil)
157
174
  version.attachment.filepath(format)
158
175
  end
159
-
176
+
160
177
  protected
161
178
  def set_defaults
162
179
  set_defaults_from_file
@@ -200,8 +217,10 @@ class Document < Node
200
217
  end
201
218
 
202
219
  klass = Document.document_class_from_content_type(content_type)
220
+
221
+ real_class = klass.real_class
203
222
 
204
- if klass != self.class
223
+ if real_class != self.class
205
224
  if @new_file
206
225
  errors.add('file', 'incompatible with this class')
207
226
  else
@@ -0,0 +1,5 @@
1
+
2
+ # This is a dummy class that is only loaded during testing (to load fixtures and
3
+ # to count/find index entries).
4
+ class IdxNodesInteger < ActiveRecord::Base
5
+ end
@@ -82,8 +82,10 @@ class Image < Document
82
82
 
83
83
  # Return the width in pixels for an image at the given format.
84
84
  def width(format=nil)
85
- if format.nil? || format.size == :keep
85
+ if format.nil? || format[:size] == :keep
86
86
  prop['width']
87
+ elsif format[:size] == :force
88
+ format[:width]
87
89
  else
88
90
  if img = image_with_format(format)
89
91
  img.width
@@ -95,8 +97,10 @@ class Image < Document
95
97
 
96
98
  # Return the height in pixels for an image at the given format.
97
99
  def height(format=nil)
98
- if format.nil? || format.size == :keep
100
+ if format.nil? || format[:size] == :keep
99
101
  prop['height']
102
+ elsif format[:size] == :force
103
+ format[:height]
100
104
  else
101
105
  if img = image_with_format(format)
102
106
  img.height
@@ -147,7 +151,7 @@ class Image < Document
147
151
 
148
152
  # Return a file with the data for the given format. It is the receiver's responsability to close the file.
149
153
  def file(format=nil)
150
- if format.nil? || format.size == :keep
154
+ if format.nil? || format[:size] == :keep
151
155
  super()
152
156
  else
153
157
  if File.exist?(self.filepath(format)) || make_image(format)
@@ -199,6 +203,14 @@ class Image < Document
199
203
  fname = "#{filename}.#{Zena::TYPE_TO_EXT[ctype][0]}"
200
204
  uploaded_file(file, filename, ctype)
201
205
  end
206
+
207
+ # This is called if the image's width and/or height is nil and image builder could
208
+ # compute the size.
209
+ def fix_sizes(w, h)
210
+ prop['width'] = w
211
+ prop['height'] = h
212
+ Zena::Db.set_attribute(version, 'properties', encode_properties(@properties))
213
+ end
202
214
 
203
215
  private
204
216
 
@@ -234,7 +246,7 @@ class Image < Document
234
246
  format ||= Iformat['full']
235
247
  @formats ||= {}
236
248
  @formats[format[:name]] ||= Zena::Use::ImageBuilder.new(:path => filepath,
237
- :width => prop['width'], :height => prop['height']).transform!(format)
249
+ :width => prop['width'], :height => prop['height'], :node => self).transform!(format)
238
250
  else
239
251
  raise StandardError, "No image to work on"
240
252
  end
@@ -619,7 +619,9 @@ class Node < ActiveRecord::Base
619
619
  def new_node(new_attributes, transform = true)
620
620
  attributes = transform ? transform_attributes(new_attributes) : new_attributes
621
621
 
622
- klass_name = attributes.delete('class') || attributes.delete('klass') || 'Page'
622
+ klass_name = attributes.delete('class') || attributes.delete('klass')
623
+ klass_name ||= attributes['file'] ? 'Document' : 'Page'
624
+
623
625
  if klass_name.kind_of?(VirtualClass) || klass_name.kind_of?(Class)
624
626
  klass = klass_name
625
627
  else
@@ -633,6 +635,10 @@ class Node < ActiveRecord::Base
633
635
  return node
634
636
  end
635
637
  end
638
+
639
+ if attributes['file'] && !(klass.kpath =~ %r{^ND})
640
+ klass = VirtualClass['Document']
641
+ end
636
642
 
637
643
  if klass.kind_of?(VirtualClass)
638
644
  node = secure(klass.real_class) { klass.new_instance(attributes) }
@@ -766,6 +772,7 @@ class Node < ActiveRecord::Base
766
772
  if ['html','xhtml'].include?(attrs['ext']) && attrs['title'] == 'index'
767
773
  attrs['ext'] = 'zafu'
768
774
  attrs['title'] = 'Node'
775
+ attrs['content_type'] = 'text/zafu'
769
776
  insert_zafu_headings = true
770
777
  elsif attrs['ext'] == 'yml' && attrs['title'] == '_roles'
771
778
  # import roles
@@ -908,7 +915,7 @@ class Node < ActiveRecord::Base
908
915
  end
909
916
 
910
917
  if !res['parent_id'] && p = attributes['parent_id']
911
- res['parent_zip'] = p
918
+ res['parent_zip'] = p unless p.blank?
912
919
  end
913
920
 
914
921
  attributes.each do |key, value|
@@ -942,7 +949,7 @@ class Node < ActiveRecord::Base
942
949
  elsif key =~ /^(\w+)_id$/
943
950
  res["#{$1}_zip"] = value
944
951
  elsif key =~ /^(\w+)_ids$/
945
- res["#{$1}_zips"] = value.kind_of?(Array) ? value : value.split(',')
952
+ res["#{$1}_zips"] = value.kind_of?(Array) ? value : value.split(',').map(&:strip)
946
953
  elsif key == 'v_status' || key == 'file'
947
954
  res[key] = value unless value.blank?
948
955
  elsif value.kind_of?(Hash)
@@ -1684,6 +1691,10 @@ class Node < ActiveRecord::Base
1684
1691
  errors.add('klass', 'invalid') if !self.class.allowed_change_to_classes.include?(@new_klass)
1685
1692
  end
1686
1693
  end
1694
+
1695
+ if vclass.create_group_id
1696
+ errors.add('klass', 'unauthorized') if !visitor.group_ids.include?(vclass.create_group_id)
1697
+ end
1687
1698
  end
1688
1699
 
1689
1700
  # Called before destroy. An node must be empty to be destroyed
@@ -1811,8 +1822,10 @@ class Node < ActiveRecord::Base
1811
1822
  def change_klass
1812
1823
  if @new_klass
1813
1824
  if !can_drive? || !self[:parent_id]
1825
+ # not allowed
1814
1826
  return
1815
1827
  elsif !self.class.allowed_change_to_classes.include?(@new_klass)
1828
+ # invalid class (unknown or visitor does have access)
1816
1829
  return
1817
1830
  end
1818
1831
  end