infopark_reactor 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|