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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +165 -0
- data/README.md +142 -0
- data/Rakefile +25 -0
- data/app/controllers/reactor_controller.rb +53 -0
- data/config/routes.rb +6 -0
- data/infopark_reactor.gemspec +30 -0
- data/lib/engine.rb +35 -0
- data/lib/infopark_reactor.rb +38 -0
- data/lib/reactor/already_released.rb +3 -0
- data/lib/reactor/attributes.rb +365 -0
- data/lib/reactor/attributes/date_serializer.rb +35 -0
- data/lib/reactor/attributes/html_serializer.rb +31 -0
- data/lib/reactor/attributes/link_list_extender.rb +50 -0
- data/lib/reactor/attributes/link_list_serializer.rb +27 -0
- data/lib/reactor/cache/permission.rb +36 -0
- data/lib/reactor/cache/user.rb +37 -0
- data/lib/reactor/legacy.rb +60 -0
- data/lib/reactor/link/external.rb +19 -0
- data/lib/reactor/link/internal.rb +19 -0
- data/lib/reactor/link/temporary_link.rb +43 -0
- data/lib/reactor/no_working_version.rb +1 -0
- data/lib/reactor/not_permitted.rb +1 -0
- data/lib/reactor/permission.rb +262 -0
- data/lib/reactor/persistence.rb +473 -0
- data/lib/reactor/rc_independent.rb +13 -0
- data/lib/reactor/session.rb +48 -0
- data/lib/reactor/session/observers.rb +32 -0
- data/lib/reactor/session/user.rb +22 -0
- data/lib/reactor/streaming_upload.rb +22 -0
- data/lib/reactor/sudo.rb +11 -0
- data/lib/reactor/support/link_matcher.rb +44 -0
- data/lib/reactor/tools/workflow_generator.rb +195 -0
- data/lib/reactor/validations.rb +55 -0
- data/lib/reactor/version.rb +3 -0
- data/lib/reactor/workflow.rb +19 -0
- data/lib/reactor/workflow/empty.rb +27 -0
- data/lib/reactor/workflow/standard.rb +34 -0
- 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
|