infopark_reactor 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +165 -0
  4. data/README.md +142 -0
  5. data/Rakefile +25 -0
  6. data/app/controllers/reactor_controller.rb +53 -0
  7. data/config/routes.rb +6 -0
  8. data/infopark_reactor.gemspec +30 -0
  9. data/lib/engine.rb +35 -0
  10. data/lib/infopark_reactor.rb +38 -0
  11. data/lib/reactor/already_released.rb +3 -0
  12. data/lib/reactor/attributes.rb +365 -0
  13. data/lib/reactor/attributes/date_serializer.rb +35 -0
  14. data/lib/reactor/attributes/html_serializer.rb +31 -0
  15. data/lib/reactor/attributes/link_list_extender.rb +50 -0
  16. data/lib/reactor/attributes/link_list_serializer.rb +27 -0
  17. data/lib/reactor/cache/permission.rb +36 -0
  18. data/lib/reactor/cache/user.rb +37 -0
  19. data/lib/reactor/legacy.rb +60 -0
  20. data/lib/reactor/link/external.rb +19 -0
  21. data/lib/reactor/link/internal.rb +19 -0
  22. data/lib/reactor/link/temporary_link.rb +43 -0
  23. data/lib/reactor/no_working_version.rb +1 -0
  24. data/lib/reactor/not_permitted.rb +1 -0
  25. data/lib/reactor/permission.rb +262 -0
  26. data/lib/reactor/persistence.rb +473 -0
  27. data/lib/reactor/rc_independent.rb +13 -0
  28. data/lib/reactor/session.rb +48 -0
  29. data/lib/reactor/session/observers.rb +32 -0
  30. data/lib/reactor/session/user.rb +22 -0
  31. data/lib/reactor/streaming_upload.rb +22 -0
  32. data/lib/reactor/sudo.rb +11 -0
  33. data/lib/reactor/support/link_matcher.rb +44 -0
  34. data/lib/reactor/tools/workflow_generator.rb +195 -0
  35. data/lib/reactor/validations.rb +55 -0
  36. data/lib/reactor/version.rb +3 -0
  37. data/lib/reactor/workflow.rb +19 -0
  38. data/lib/reactor/workflow/empty.rb +27 -0
  39. data/lib/reactor/workflow/standard.rb +34 -0
  40. metadata +204 -0
@@ -0,0 +1,473 @@
1
+ # encoding: utf-8
2
+ module Reactor
3
+ module Persistence
4
+ # Provides API for writing into the Content Manager.
5
+ # It aims to be just like ActiveRecord::Persistence, so that
6
+ # the difference for the developer is minimal
7
+ # If the method is marked as exception raising, then it should
8
+ # be expected also to raise Reactor::Cm::XmlRequestError
9
+ # when generic write/connection error occurs.
10
+ #
11
+ # It should support all generic model callbacks, plus
12
+ # complete set of callbacks for release action (before/after/around).
13
+ #
14
+ # @raise [Reactor::Cm::XmlRequestError] generic error occoured
15
+ module Base
16
+ def self.included(base)
17
+ base.extend(ClassMethods)
18
+ base.send(:define_model_callbacks, :release)
19
+ base.class_eval do
20
+ before_create :sanitize_name
21
+ before_create :trim_crul_attributes
22
+ end
23
+ end
24
+
25
+ # Releases the object. Returns true on success,
26
+ # false when one of the following occurs:
27
+ # 1. user lacks the permissions
28
+ # 2. the object has already been released
29
+ # 3. object is invalid
30
+ # 4. other error occoured
31
+ def release
32
+ return release!
33
+ rescue Reactor::Cm::XmlRequestError, ActiveRecord::RecordInvalid, Reactor::NotPermitted, Reactor::AlreadyReleased
34
+ return false
35
+ end
36
+
37
+ # Releases the object. Returns true on succes, can raise exceptions
38
+ # @raise [Reactor::AlreadyReleased]
39
+ # @raise [ActiveRecord::RecordInvalid] validations failed
40
+ # @raise [Reactor::NotPermitted] current user lacks required permissions
41
+ def release!
42
+ run_callbacks(:release) do
43
+ raise(Reactor::AlreadyReleased) unless self.really_edited?
44
+ crul_obj.release!
45
+ reload
46
+ end
47
+ return true
48
+ end
49
+
50
+ # Makes the current user the editor of the object. Returns true when
51
+ # user is already the editor or take succeded,
52
+ # false when one of the following occurs:
53
+ # 1. user lacks the permissions
54
+ # 2. the object has not beed edited
55
+ # 3. other error occured
56
+ def take
57
+ take!
58
+ return true
59
+ rescue Reactor::Cm::XmlRequestError, Reactor::NotPermitted, Reactor::NoWorkingVersion
60
+ return false
61
+ end
62
+
63
+ # Makes the current user the editor of the object. Returns true when
64
+ # user is already the editor or take succeded. Raises exceptions
65
+ # @raise [Reactor::NoWorkingVersion] there is no working version of the object
66
+ # @raise [Reactor::NotPermitted] current user lacks required permissions
67
+ def take!
68
+ raise(Reactor::NoWorkingVersion) unless self.really_edited?
69
+ # TODO: refactor the if condition
70
+ crul_obj.take! if (crul_obj.editor != Reactor::Configuration::xml_access[:username])
71
+ # neccessary to recalculate #editor
72
+ reload
73
+ return true
74
+ end
75
+
76
+ # Creates a working version of the object. Returns true on success or when
77
+ # the object already has a working version. Returns false when:
78
+ # 1. user lacks the permissions
79
+ # 2. other error occured
80
+ def edit
81
+ edit!
82
+ return true
83
+ rescue Reactor::Cm::XmlRequestError, Reactor::NotPermitted
84
+ return false
85
+ end
86
+
87
+ # Creates a working version of the object. Returns true on success or when
88
+ # the object already has a working version. Raises exceptions
89
+ # @raise [Reactor::NotPermitted] current user lacks required permissions
90
+ def edit!
91
+ crul_obj.edit! unless self.really_edited?
92
+ reload
93
+ return true
94
+ end
95
+
96
+ # Returns true, if the object has any links pointing to it.
97
+ # @raise [Reactor::Cm::XmlRequestError] generic error occoured
98
+ def has_super_links?
99
+ crul_obj.get('hasSuperLinks') == '1'
100
+ end
101
+
102
+ # Return an array of RailsConnector::Obj that contain a link
103
+ # to this file.
104
+ # @raise [Reactor::Cm::XmlRequestError] generic error occoured
105
+ def super_objects
106
+ RailsConnector::Obj.where(:obj_id => crul_obj.get('superObjects')).to_a
107
+ end
108
+
109
+ # Returns true if this object hasn't been saved yet -- that is, a record
110
+ # for the object doesn't exist in the data store yet; otherwise, returns false.
111
+ def new_record?
112
+ #!destroyed? && (self.id.nil? || !self.class.exists?(self.id))
113
+ !destroyed? && (self.id.nil? || self.path.blank?)
114
+ end
115
+
116
+ # Stolen from Rails 3.
117
+ # Returns if the record is persisted, i.e. stored in database (it's not a new
118
+ # record and it was not destroyed.)
119
+ # @note Code should not be changed without large modifications to the module.
120
+ def persisted?
121
+ !(new_record? || destroyed?)
122
+ end
123
+
124
+ # Returns true if this object has been destroyed, otherwise returns false.
125
+ def destroyed?
126
+ @destroyed == true
127
+ end
128
+
129
+ # @private
130
+ def readonly? #:nodoc:
131
+ # No, RailsConnector. I will not be shut-up!
132
+ false
133
+ end
134
+
135
+ # Deletes the object from the CM. No callbacks are executed. Though exceptions
136
+ # can be raised. Freezes the object.
137
+ def delete
138
+ crul_obj_delete if persisted?
139
+ @destroyed = true
140
+ freeze
141
+ end
142
+
143
+ # Removes the object from the CM. Runs all the callbacks. Can raise exception.
144
+ # Freezes the object.
145
+ def destroy
146
+ run_callbacks(:destroy) do
147
+ self.delete
148
+ end
149
+ end
150
+
151
+ # Reloads object attributes. Invalidates caches. Does not call
152
+ # any other reload methods (neither from RailsConnector nor from ActiveRecord)
153
+ # but tries to mimmic their behaviour.
154
+ def reload(options = nil)
155
+ #super # Throws RecordNotFound when changing obj_class
156
+ # AR reload
157
+ clear_aggregation_cache
158
+ clear_association_cache
159
+ fresh_object = Obj.find(self.id, options)
160
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
161
+ @attributes_cache = {}
162
+ # RC reload
163
+ @attr_values = nil
164
+ @attr_defs = nil
165
+ @attr_dict = nil
166
+ @obj_class_definition = nil
167
+ @object_with_meta_data = nil
168
+ # meta reload
169
+ @editor = nil
170
+ @object_with_meta_data = nil
171
+ self
172
+ end
173
+
174
+ # Resolves references in any of the html fields. Returns true on success,
175
+ # or false when:
176
+ # 1. user lacks the permissions
177
+ # 2. generic error occoured
178
+ def resolve_refs
179
+ resolve_refs!
180
+ return true
181
+ rescue Reactor::Cm::XmlRequestError, Reactor::NotPermitted
182
+ return false
183
+ end
184
+
185
+ # Resolves references in any of the html fields. Returns true on success,
186
+ # raises exceptions.
187
+ # @raise [Reactor::NotPermitted] current user lacks required permissions
188
+ def resolve_refs!
189
+ crul_obj.resolve_refs!
190
+ return true
191
+ end
192
+
193
+ if Reactor.rails3_0?
194
+ # It should excactly match ActiveRecord::Base.new in it's behavior
195
+ # @see ActiveRecord::Base.new
196
+ def initialize(attributes = nil, &block)
197
+ if !self.class.send(:attribute_methods_overriden?)
198
+ ignored_attributes = ignore_attributes(attributes)
199
+ # supress block hijacking!
200
+ super(attributes) {}
201
+ load_ignored_attributes(ignored_attributes)
202
+ yield self if block_given?
203
+ else
204
+ super(attributes)
205
+ end
206
+ end
207
+ elsif Reactor.rails3_1? || Reactor.rails3_2?
208
+ # It should excactly match ActiveRecord::Base.new in it's behavior
209
+ # @see ActiveRecord::Base.new
210
+ def initialize(attributes = nil, options={}, &block)
211
+ if !self.class.send(:attribute_methods_overriden?)
212
+ ignored_attributes = ignore_attributes(attributes)
213
+ # supress block hijacking!
214
+ super(attributes, options) {}
215
+ load_ignored_attributes(ignored_attributes)
216
+ yield self if block_given?
217
+ else
218
+ super(attributes, options)
219
+ end
220
+ end
221
+ else
222
+ raise RuntimeError, "Unsupported Rails version!"
223
+ end
224
+
225
+
226
+ # @see [ActiveRecord::Persistence#update_attributes]
227
+ def update_attributes(attributes, options={})
228
+ attributes.each do |attr, value|
229
+ self.send(:"#{attr}=", value)
230
+ end
231
+ self.save
232
+ end
233
+
234
+ # @see [ActiveRecord::Persistence#update_attributes!]
235
+ def update_attributes!(attributes, options={})
236
+ attributes.each do |attr, value|
237
+ self.send(:"#{attr}=", value)
238
+ end
239
+ self.save!
240
+ end
241
+
242
+ # Equivalent to Obj#edited?
243
+ def really_edited?
244
+ self.edited?
245
+ end
246
+
247
+ # Returns an array of errors
248
+ def reasons_for_incomplete_state
249
+ crul_obj.get('reasonsForIncompleteState') || []
250
+ end
251
+
252
+ protected
253
+ def requires_resolve_refs?(field)
254
+ force_resolve_refs?|| field == :blob || attribute_type(field.to_s) == :html
255
+ end
256
+
257
+ def force_resolve_refs
258
+ @force_resolve_refs = true
259
+ end
260
+
261
+ def prevent_resolve_refs
262
+ @prevent_resolve_refs = true
263
+ end
264
+
265
+ def prevent_resolve_refs?
266
+ @prevent_resolve_refs == true
267
+ end
268
+
269
+ def force_resolve_refs?
270
+ @force_resolve_refs == true
271
+ end
272
+
273
+ def sanitize_name
274
+ self.name = self.class.send(:sanitize_name, self.name)
275
+ end
276
+
277
+ def crul_attributes_set?
278
+ !crul_attributes.empty? || uploaded?
279
+ end
280
+
281
+ def crul_links_changed?
282
+ !changed_linklists.empty?
283
+ end
284
+
285
+ def changed_linklists
286
+ (self.class.send(:instance_variable_get, '@_o_allowed_attrs') || []).select do |attr|
287
+ self.send(:attribute_type, attr) == :linklist && self.send(:[],attr.to_sym).try(:changed?)
288
+ end
289
+ end
290
+
291
+ # Returns all values that will be set for crul interface
292
+ def crul_attributes
293
+ @__crul_attributes || {}
294
+ end
295
+
296
+ # Takes cached values and sets the values for crul interface.
297
+ # Does not store them, only forwards them to underlying class.
298
+ def forward_crul_attributes
299
+ crul_attributes.each do |field, (value, options)|
300
+ options ||= {}
301
+ crul_obj.set(field, value, options) unless self.send(:attribute_type, field) == :linklist
302
+ end
303
+ end
304
+
305
+ def prepare_crul_links
306
+ changed_linklists.each do |link|
307
+ crul_set(link, self.send(:[], link.to_sym), {})
308
+ end
309
+ end
310
+
311
+ def crul_obj
312
+ @crul_obj ||= Reactor::Cm::Obj.get(obj_id)
313
+ end
314
+
315
+ def crul_obj_delete
316
+ crul_obj.delete!
317
+ end
318
+
319
+ def crul_obj_save
320
+ prepare_crul_links
321
+ if persisted?
322
+ take
323
+ edit
324
+ end
325
+
326
+ forward_crul_attributes
327
+
328
+ crul_obj.save!
329
+ self.id = crul_obj.obj_id
330
+
331
+ crul_store_links
332
+
333
+ # TODO: REFACTOR!!!
334
+ possible_fields = ((self.obj_class_def.custom_attributes.keys) + [:blob, :body])
335
+ crul_obj.resolve_refs! if possible_fields.map {|field| requires_resolve_refs?(field) }.include?(true) && !prevent_resolve_refs?
336
+ #crul_obj.resolve_refs! if @force_resolve_refs == true || possible_fields.include?(:blob) || possible_fields.detect do |field|
337
+ # attr = cms_attributes[field.to_s]
338
+ # attr && attr.attribute_type == 'html'
339
+ #end
340
+
341
+ @__crul_attributes = nil
342
+
343
+ true
344
+ end
345
+
346
+ private
347
+
348
+ # TODO: test it & make it public
349
+ # Copy an Obj to another tree, returnes ID of the new Object
350
+ # @param [String, Obj, Integer] new_parent path, id, or instance of target where to copy
351
+ # @param [true, false] recursive set to true to also copy the underlying subtree
352
+ # @param [String] new_name gives the object new name
353
+ def copy(new_parent, recursive = false, new_name = nil)
354
+ self.id = crul_obj.copy(Obj.path_from_anything(new_parent), recursive, new_name)
355
+ #self.reload
356
+ resolve_refs #?
357
+ self.id
358
+ end
359
+
360
+ def trim_crul_attributes
361
+ crul_attributes.delete_if {|attr, options| [:name, :objClass].include?(attr) }
362
+ end
363
+
364
+ def crul_obj_create(name, parent, klass)
365
+ @crul_obj = Reactor::Cm::Obj.create(name, parent, klass)
366
+ end
367
+
368
+ def crul_store_links
369
+ crul_attributes.each do |field, (value, options)|
370
+ if self.send(:attribute_type, field) == :linklist then
371
+ crul_store_links_for_attribute(field, value)
372
+ end
373
+ end
374
+ # self.class.send(:instance_variable_get, '@_o_allowed_attrs').each do |attr|
375
+ # if self.send(:attribute_type, attr) == :linklist #&& self.send(attr).try(:changed?) then
376
+ # crul_store_links_for_attribute(attr, self.send(attr))
377
+ # end
378
+ # end
379
+ end
380
+
381
+ def crul_store_links_for_attribute(attr, links)
382
+ # FIXME: l.link_id ??
383
+ crul_obj.set_links(attr, links.map {|l| {:link_id => l.id, :title => l.title, :destination_url => (l.internal? ? l.destination_object.path : l.url)} })
384
+ end
385
+
386
+ def create
387
+ run_callbacks(:create) do
388
+ c_name = self.name
389
+ c_parent= self.class.path_from_anything(self.parent_obj_id)
390
+ c_objcl = self.obj_class
391
+ crul_obj_create(c_name, c_parent, c_objcl)
392
+ crul_obj_save if crul_attributes_set? || crul_links_changed?
393
+ self.id = @crul_obj.obj_id
394
+ self.reload # ?
395
+ self.id
396
+ end
397
+ end
398
+
399
+ def update
400
+ run_callbacks(:update) do
401
+ crul_obj_save if crul_attributes_set? || crul_links_changed?
402
+ self.reload
403
+ self.id
404
+ end
405
+ end
406
+
407
+ def ignore_attributes(attributes)
408
+ return {} if attributes.nil?
409
+
410
+ obj_class = attributes.delete(:obj_class)
411
+ parent = attributes.delete(:parent)
412
+ {:obj_class => obj_class, :parent => parent}
413
+ end
414
+
415
+
416
+
417
+ def load_ignored_attributes(attributes)
418
+ return if attributes.nil?
419
+
420
+ set_parent(attributes[:parent]) if attributes[:parent].present?
421
+ set_obj_class(attributes[:obj_class]) if attributes[:obj_class].present?
422
+ end
423
+
424
+ def set_parent(parent_something)
425
+ self.parent_obj_id = self.class.obj_id_from_anything(parent_something)
426
+ end
427
+
428
+ def set_obj_class(obj_class)
429
+ self.obj_class = obj_class
430
+ end
431
+ end
432
+ module ClassMethods
433
+ def sanitize_name(old_name)
434
+ character_map = {'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss', 'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue'}
435
+ new_name = old_name.gsub(/[^-$a-zA-Z0-9]/) {|char| character_map[char] || '_'}.
436
+ gsub(/__+/,'_').
437
+ gsub(/^_+/,'').
438
+ gsub(/_+$/,'')
439
+ new_name
440
+ end
441
+
442
+ # Convenience method: it is equivalent to following call chain:
443
+ #
444
+ # i = create(attributes)
445
+ # i.upload(data_or_io, extension)
446
+ # i.save!
447
+ # i
448
+ #
449
+ # Use it like this:
450
+ #
451
+ # image = Image.upload(File.open('image.jpg'), 'ext', :name => 'image', :parent => '/')
452
+ #
453
+ def upload(data_or_io, extension, attributes={})
454
+ # Try to guess the object name from filename, if it's missing
455
+ if (data_or_io.respond_to?(:path) && !attributes.key?(:name))
456
+ attributes[:name] = sanitize_name(File.basename(data_or_io.path, File.extname(data_or_io.path)))
457
+ end
458
+
459
+ instance = self.create!(attributes)# do |instance|
460
+ #debugger
461
+ instance.upload(data_or_io, extension)
462
+ instance.save!
463
+ #end
464
+ instance
465
+ end
466
+
467
+ protected
468
+ def attribute_methods_overriden?
469
+ self.name != 'RailsConnector::Obj'
470
+ end
471
+ end
472
+ end
473
+ end