infopark_reactor 1.5.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 (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