zena 1.2.3 → 1.2.4

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 (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