infopark_fiona7 1.1.0.0.10 → 1.2.0.0.0

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/fiona7_ui.js +96 -50
  3. data/app/assets/javascripts/scrivito_patches/models/blob.js +21 -0
  4. data/app/assets/javascripts/scrivito_patches/models/obj.js +1 -1
  5. data/app/controllers/fiona7/release_controller.rb +6 -2
  6. data/app/controllers/fiona7/sessions_controller.rb +3 -0
  7. data/app/controllers/fiona7/workflow_controller.rb +46 -0
  8. data/app/helpers/fiona7_login_helper.rb +16 -0
  9. data/app/helpers/fiona7_override_helper.rb +1 -1
  10. data/config/delayed_routes.rb +1 -0
  11. data/config/precedence_routes.rb +92 -0
  12. data/infopark_fiona7.gemspec +2 -2
  13. data/lib/fiona7/blob_id_generator.rb +13 -0
  14. data/lib/fiona7/builder/indirect_blob_builder.rb +71 -0
  15. data/lib/fiona7/builder/lazy_blob_copier.rb +207 -0
  16. data/lib/fiona7/builder/obj_builder.rb +29 -50
  17. data/lib/fiona7/builder/obj_class_builder.rb +20 -0
  18. data/lib/fiona7/controllers/rest_api/blob_controller.rb +19 -0
  19. data/lib/fiona7/controllers/rest_api/obj_controller.rb +8 -4
  20. data/lib/fiona7/controllers/rest_api/workspace_controller.rb +2 -1
  21. data/lib/fiona7/engine.rb +56 -2
  22. data/lib/fiona7/json/obj_decorator.rb +4 -1
  23. data/lib/fiona7/link_converter/fiona_to_scrivito.rb +3 -1
  24. data/lib/fiona7/link_converter/scrivito_to_fiona.rb +12 -1
  25. data/lib/fiona7/mode_switch/cms_routes/scrivito_sdk.rb +1 -71
  26. data/lib/fiona7/mode_switch/cms_routes/scrivito_sdk_slave.rb +1 -71
  27. data/lib/fiona7/recursive_object_finder.rb +32 -8
  28. data/lib/fiona7/routers/rest_api.rb +6 -7
  29. data/lib/fiona7/scrivito_patches/attribute_content.rb +1 -1
  30. data/lib/fiona7/scrivito_patches/attribute_serializer.rb +22 -1
  31. data/lib/fiona7/scrivito_patches/basic_obj.rb +27 -5
  32. data/lib/fiona7/scrivito_patches/cms_backend.rb +3 -22
  33. data/lib/fiona7/scrivito_patches/cms_rest_api.rb +2 -1
  34. data/lib/fiona7/scrivito_patches/cms_routing.rb +2 -2
  35. data/lib/fiona7/scrivito_patches/model_library.rb +3 -3
  36. data/lib/fiona7/scrivito_patches/page_config.rb +21 -1
  37. data/lib/fiona7/scrivito_patches/preset_routes.rb +15 -2
  38. data/lib/fiona7/scrivito_patches/routing_extensions.rb +33 -20
  39. data/lib/fiona7/scrivito_patches/workspace.rb +3 -1
  40. data/lib/fiona7/super_object_finder.rb +63 -0
  41. data/lib/fiona7/type_register.rb +35 -9
  42. data/lib/fiona7/verity_search_engine.rb +1 -1
  43. data/lib/fiona7/version.rb +1 -1
  44. metadata +13 -7
  45. data/config/routes.rb +0 -13
@@ -17,11 +17,11 @@ Gem::Specification.new do |s|
17
17
  s.files = Dir["{app,config,db,lib}/**/*", "Rakefile", "README.md", "infopark_fiona7.gemspec"]
18
18
 
19
19
  s.add_dependency "rails", "~> 4.2.2"
20
- s.add_dependency "scrivito", "= 1.1.0"
20
+ s.add_dependency "scrivito", "= 1.2.0"
21
21
  s.add_dependency "scrivito_sdk"
22
22
  s.add_dependency "scrivito_editors"
23
23
  s.add_dependency "infopark_fiona_connector", "= 7.0.1.beta2"
24
- s.add_dependency "infopark_reactor", ">= 1.18.0"
24
+ s.add_dependency "infopark_reactor", ">= 1.22.3"
25
25
  s.add_dependency "mini_magick"
26
26
  #s.add_development_dependency "ruby-prof"
27
27
  end
@@ -0,0 +1,13 @@
1
+ module Fiona7
2
+ class BlobIdGenerator
3
+ def initialize(obj_id, time=nil)
4
+ @obj_id = obj_id.to_i
5
+ @time = time || Time.now
6
+ @stamp = @time.to_i.to_s(16)
7
+ end
8
+
9
+ def call
10
+ ("%022d" % @obj_id) + "bb" + @stamp
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,71 @@
1
+ module Fiona7
2
+ module Builder
3
+ class IndirectBlobBuilder
4
+ def initialize(obj, filename, file=nil)
5
+ @obj = obj
6
+ @filename = filename
7
+ @file = file
8
+ end
9
+
10
+ def call
11
+ # only publication type can contain children (code = "5")
12
+ if @obj.obj_type_code != "5" || @obj.id == 2001
13
+ parent_path = "/_uploads/#{@obj.id}"
14
+ else
15
+ parent_path = "#{@obj.path}/_uploads"
16
+ end
17
+
18
+ parent = ensure_parent_exists(parent_path)
19
+ ext = ::File.extname(@filename).to_s[1..-1]
20
+ name = ::File.basename(@filename, '.' + ext.to_s)
21
+ obj_class = if ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff'].include?(ext)
22
+ 'X_Image'
23
+ else
24
+ 'X_Generic'
25
+ end
26
+
27
+ if @file
28
+ upload = WriteObj.upload(@file, ext, {name: name, parent: parent, obj_class: obj_class}).tap(&:release!)
29
+ else
30
+ upload = WriteObj.create({name: name, parent: parent, obj_class: obj_class}).tap(&:release!)
31
+ end
32
+ end
33
+
34
+ protected
35
+ # TODO: extract this code
36
+ def ensure_parent_exists(path)
37
+ remaining = path.split("/")
38
+ current = []
39
+ paths = []
40
+ original = path
41
+
42
+ while !remaining.empty?
43
+ current.push(remaining.shift)
44
+
45
+ path = current.join('/').presence || '/'
46
+ paths.push(path)
47
+ end
48
+
49
+ # TODO: add an optimization for exiting parents
50
+
51
+ paths.each do |path|
52
+ if !WriteObj.exists?(path: path)
53
+ name, parent_path = name_and_parent_path_from_path(path)
54
+ WriteObj.create!(name: name, parent_obj_id: WriteObj.find_by_path(parent_path).id, obj_class: 'X_Container')
55
+ end
56
+ end
57
+
58
+ WriteObj.find_by_path(original) || (raise "Tried to make sure that the parent under '#{original}' exist, but it does not :(")
59
+ end
60
+
61
+ def name_and_parent_path_from_path(path)
62
+ components = path.split('/')
63
+ name = components.pop.presence
64
+ parent_path= components.join('/').presence || '/'
65
+
66
+ return name, parent_path
67
+ end
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,207 @@
1
+ require 'fiona7/super_object_finder'
2
+ require 'fiona7/blob_id_generator'
3
+ require 'fiona7/builder/indirect_blob_builder'
4
+
5
+ module Fiona7
6
+ module Builder
7
+ # This class implements a lazy blob copy/rename operation.
8
+ #
9
+ # Blob copy is trivial: an existing blob is taken
10
+ # and copied to the destination object.
11
+ #
12
+ # Blob rename occurs, when an apparent copy is executed,
13
+ # that is the blob that is copied is already associated
14
+ # with this object (and only with this object)
15
+ #
16
+ # The lazy part of this class is the fact that you
17
+ # need to call .save! on the passed destination obj
18
+ # to actually persist the changes in the obj
19
+ #
20
+ # NOTE: there is an exception to the above:
21
+ # renaming a blob in standalone mode executes immediately
22
+ class LazyBlobCopier
23
+ def initialize(attrs={})
24
+ @destination_obj = attrs[:destination_obj]
25
+ @attribute = attrs[:attr_name] || 'blob'
26
+ @source_blob_id = attrs[:source_blob_id]
27
+ @filename = attrs[:filename]
28
+ @content_type = attrs[:content_type]
29
+
30
+ @basename, @ext = *split_filename(@filename)
31
+ end
32
+
33
+ def call
34
+ validate_input!
35
+
36
+ @blob_obj = Fiona7::WriteObj.find(@source_blob_id.to_i)
37
+ @source_obj = find_unique_source_obj
38
+
39
+ if @destination_obj == @source_obj
40
+ rename
41
+ else
42
+ copy
43
+ end
44
+
45
+ return {id: @destination_blob_id}
46
+ end
47
+
48
+ protected
49
+
50
+ def rename
51
+ return unless @basename.present? && @ext.present?
52
+
53
+ if Fiona7.mode == :legacy
54
+ direct_rename
55
+ else
56
+ indirect_rename
57
+ end
58
+ end
59
+
60
+ def copy
61
+ if Fiona7.mode == :legacy
62
+ direct_copy
63
+ else
64
+ indirect_copy
65
+ end
66
+ end
67
+
68
+ def direct_rename
69
+ @destination_obj.set(:name, @basename)
70
+ # CM stores the file extension as :contentType and not the mimetype
71
+ @destination_obj.set(:content_type, @ext)
72
+
73
+ @destination_blob_id = Fiona7::BlobIdGenerator.new(@destination_obj.id).call
74
+ end
75
+
76
+ def indirect_rename
77
+ # This is kind of silly, but as far as I can tell there is no way
78
+ # around it: whenever you rename a blob, you will also rename
79
+ # it in the published workspace.
80
+ # Basically the same design flaw as in the "classical" CM
81
+ #
82
+ # NOTE: this applies the change directly, whether a "save"
83
+ # happens afterwards on not, so it breaks the "lazy" part
84
+ # of the contract
85
+ @blob_obj.set(:name, @basename)
86
+ @blob_obj.set(:content_type, @ext)
87
+ @blob_obj.save!
88
+ # Just for good measure, since contentType is a content attribute
89
+ @blob_obj.release
90
+ # And now for the "lazy" part, we have to update the linklist with
91
+ # new "fake" blob id to fool SDK into thinking we actually copied
92
+ # the blob
93
+ @destination_blob_id = Fiona7::BlobIdGenerator.new(@blob_obj.id, @blob_obj.last_changed).call
94
+ @destination_obj.set(@attribute, {
95
+ title: @destination_blob_id,
96
+ destination_object: @blob_obj
97
+ })
98
+ end
99
+
100
+ def direct_copy
101
+ @destination_blob_id = Fiona7::BlobIdGenerator.new(@destination_obj.id).call
102
+
103
+ content = @blob_obj.edited? ? 'edited' : 'released'
104
+
105
+ begin
106
+ blob_ticket_id = @blob_obj.send(:crul_obj).blob_ticket_id(content)
107
+ target = @destination_obj.send(:crul_obj)
108
+
109
+ target.set(:blob, {blob_ticket_id => {encoding: 'stream'}})
110
+ rescue Reactor::Cm::BlobTooSmallError
111
+ # Reading blob_ticket_id failed,
112
+ # an alternative strategy is required:
113
+ # reading into memory
114
+ @destination_obj.set(:blob, @blob_obj.body)
115
+ end
116
+
117
+ if @basename.present? && @ext.present?
118
+ direct_rename
119
+ end
120
+
121
+ # NOTE: calling @destination_obj.save! will trigger target.save!
122
+ end
123
+
124
+ def indirect_copy
125
+ destination_blob = construct_destination_blob
126
+
127
+ # NOTE: this should under normal circumstances
128
+ # never return 'edited'. But this is on the safe side
129
+ content = @blob_obj.edited? ? 'edited' : 'released'
130
+
131
+ begin
132
+ blob_ticket_id = @blob_obj.send(:crul_obj).blob_ticket_id(content)
133
+ target = destination_blob.send(:crul_obj)
134
+
135
+ target.set(:blob, {blob_ticket_id => {encoding: 'stream'}})
136
+ rescue Reactor::Cm::BlobTooSmallError
137
+ # Reading blob_ticket_id failed,
138
+ # an alternative strategy is required:
139
+ # reading into memory
140
+ destination_blob.set(:blob, @blob_obj.body)
141
+ end
142
+
143
+ # NOTE: this will also call target.save!
144
+ destination_blob.save!
145
+ destination_blob.release
146
+
147
+ @destination_blob_id = Fiona7::BlobIdGenerator.new(destination_blob.id, destination_blob.last_changed).call
148
+
149
+ @destination_obj.set(@attribute, {
150
+ title: @destination_blob_id, destination_object: destination_blob
151
+ })
152
+ end
153
+
154
+ def split_filename(filename)
155
+ return unless filename
156
+
157
+ ext = ::File.extname(filename)[1..-1]
158
+ base = ::File.basename(filename, ::File.extname(filename))
159
+
160
+ return base, ext
161
+ end
162
+
163
+ def validate_input!
164
+ if Fiona7.mode == :legacy && @attribute != 'blob'
165
+ raise Scrivito::ClientError.new("Blob attributes other than :blob are unsupported in :legacy mode")
166
+ end
167
+ if !extension_and_content_type_match?
168
+ Rails.logger.error("BLOB COPY ERROR: content_type passed to the blob copy operation ignored, because it does not match the file extension")
169
+ end
170
+ if !@destination_obj.kind_of?(Fiona7::WriteObj)
171
+ raise ArgumentError, "Wrong input for LazyBlobCopier"
172
+ end
173
+ end
174
+
175
+ def extension_and_content_type_match?
176
+ return true if @content_type.blank?
177
+ return false if @ext.blank?
178
+
179
+ return MIME::Types.type_for(@ext).any? {|m| m.content_type == @content_type }
180
+ #rescue
181
+ return false
182
+ end
183
+
184
+ def find_unique_source_obj
185
+ if Fiona7.mode == :legacy
186
+ return @blob_obj
187
+ end
188
+
189
+ linking = Fiona7::SuperObjectFinder.new.find(@blob_obj)
190
+
191
+ if linking.count == 1
192
+ return linking.first
193
+ else
194
+ return nil
195
+ end
196
+ rescue ActiveRecord::RecordNotFound
197
+ return nil
198
+ end
199
+
200
+ def construct_destination_blob
201
+ filename = @filename || (@source_obj.name + "." + @source_obj.file_extension)
202
+ Fiona7::Builder::IndirectBlobBuilder.new(@destination_obj, filename).call
203
+
204
+ end
205
+ end
206
+ end
207
+ end
@@ -13,6 +13,9 @@ require 'fiona7/obj_class_name_demangler'
13
13
  require 'fiona7/builder/batch_widget_writer'
14
14
  require 'fiona7/link_converter/scrivito_to_fiona'
15
15
  require 'fiona7/widget_resolver'
16
+ require 'fiona7/builder/lazy_blob_copier'
17
+ require 'fiona7/builder/indirect_blob_builder'
18
+ require 'fiona7/blob_id_generator'
16
19
 
17
20
 
18
21
  module Fiona7
@@ -25,7 +28,6 @@ module Fiona7
25
28
  @values.delete(:_modification)
26
29
  # revert command sends this info. which is silly.
27
30
  @values.delete(:_last_changed)
28
- #puts "CREATING/UPDATING: #{@values.inspect}"
29
31
  end
30
32
 
31
33
  def build
@@ -218,6 +220,8 @@ module Fiona7
218
220
  #if value.nil? && value != @obj[attribute_name]
219
221
  @obj.set(attribute_name.to_s, value)
220
222
  #end
223
+ when :number
224
+ @obj.set(attribute_name.to_s, value.to_s)
221
225
  when :stringlist
222
226
  if Fiona7.mode == :legacy && attribute_name.to_s == "channels"
223
227
  @obj.set(:channels, value || [])
@@ -247,9 +251,25 @@ module Fiona7
247
251
  elsif value.nil?
248
252
  @obj.set(attribute_name.to_s, [])
249
253
  elsif value.kind_of?(Hash)
250
- #TODO: legacy mode!
251
- target = {title: value["id"], destination_object: InternalReleasedObj.find(value["id"].to_i)}
252
- @obj.set(attribute_name.to_s, target)
254
+ source_blob_id = value["id"] || value["id_to_copy"]
255
+ filename = value["filename"]
256
+ content_type = value["content_type"]
257
+
258
+ # There is a little bit of magic behind this.
259
+ # This will pass some data directly to the crul_obj
260
+ # bypassing @obj.set(), but calling @obj.save
261
+ # afterwards still persists the data correctly
262
+ #
263
+ # This works the same way as Reactor::Tools::Uploader
264
+ Fiona7::Builder::LazyBlobCopier.new({
265
+ destination_obj: @obj,
266
+ attr_name: attribute_name.to_s,
267
+ source_blob_id: source_blob_id,
268
+ filename: filename,
269
+ content_type: content_type
270
+ }).call
271
+
272
+ # NOTE: no @obj.set() required here
253
273
  else
254
274
  raise Scrivito::ClientError.new("Invalid input for binary field", 422)
255
275
  end
@@ -291,7 +311,7 @@ module Fiona7
291
311
  end
292
312
 
293
313
  def rewrite_full_text
294
- full_text = ::YAML.load(@obj.attr_values["X_full_text"])
314
+ full_text = ::YAML.load(@obj.attr_values["X_full_text"]) rescue {}
295
315
  full_text = {} unless full_text.kind_of?(Hash)
296
316
  full_text["_widget_pool"] ||= {}
297
317
  full_text["_widget_pool"].deep_merge!(@widget_pool)
@@ -302,55 +322,14 @@ module Fiona7
302
322
  end
303
323
 
304
324
  def upload_file(file)
305
- # only publication type can contain children (code = "5")
306
- # also handle / this way
307
- if @obj.obj_type_code != "5" || @obj.id == 2001
308
- parent_path = "/_uploads/#{@obj.id}"
309
- else
310
- parent_path = "#{@obj.path}/_uploads"
311
- end
312
-
313
- parent = ensure_parent_exists(parent_path)
314
- ext = ::File.extname(file.path).to_s[1..-1]
315
- name = ::File.basename(file.path, '.' + ext.to_s)
316
- obj_class = if ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff'].include?(ext)
317
- 'X_Image'
318
- else
319
- 'X_Generic'
320
- end
321
-
322
- raise Scrivito::ScrivitoError.new("File extension of uploaded file cannot be identified", 422) if ext.nil?
323
-
324
- upload = WriteObj.upload(file, ext, {name: name, parent: parent, obj_class: obj_class}).tap(&:release!)
325
- encoded_id = '%032d' % upload.id
325
+ upload = Fiona7::Builder::IndirectBlobBuilder.new(@obj, ::File.basename(file.path), file).call
326
+ encoded_id = Fiona7::BlobIdGenerator.new(upload.id, upload.last_changed).call
326
327
  {title: encoded_id, destination_object: upload}
327
328
  end
328
329
 
329
330
  def upload_uploaded_file(file)
330
- # FIXME: DRY
331
- # only publication type can contain children (code = "5")
332
- # also handle / this way
333
- if @obj.obj_type_code != "5" || @obj.id == 2001
334
- parent_path = "/_uploads/#{@obj.id}"
335
- else
336
- parent_path = "#{@obj.path}/_uploads"
337
- end
338
-
339
- parent = ensure_parent_exists(parent_path)
340
- # here the first difference
341
- ext = ::File.extname(file.original_filename).to_s[1..-1]
342
- name = ::File.basename(file.original_filename, '.' + ext.to_s)
343
- obj_class = if ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff'].include?(ext)
344
- 'X_Image'
345
- else
346
- 'X_Generic'
347
- end
348
-
349
- raise Scrivito::ScrivitoError.new("File extension of uploaded file cannot be identified", 422) if ext.nil?
350
-
351
- # here the second difference
352
- upload = WriteObj.upload(file.open, ext, {name: name, parent: parent, obj_class: obj_class}).tap(&:release!)
353
- encoded_id = '%032d' % upload.id
331
+ upload = Fiona7::Builder::IndirectBlobBuilder.new(@obj, file.original_filename, file.open).call
332
+ encoded_id = Fiona7::BlobIdGenerator.new(upload.id, upload.last_changed).call
354
333
  {title: encoded_id, destination_object: upload}
355
334
  end
356
335
 
@@ -48,6 +48,26 @@ module Fiona7
48
48
  else
49
49
  Assert.constraint(false, "Type #{attribute[:type]} requested for #{attribute[:real_name]}, but is #{cms_attribute.attribute_type}")
50
50
  end
51
+ elsif cms_attribute.attribute_type == "multienum" && attribute[:type] == :stringlist
52
+ # This is a bit of hack. This allows a multienum to exist
53
+ # But be treated in the application like a stringlist
54
+ #
55
+ # The intention here is to allow dynamically restricting or allowing
56
+ # values in the application, while having a fixed superset of allowed
57
+ # values in the CMS
58
+ #
59
+ # Nothing to do.
60
+ Rails.logger.warn("TYPE SYNCHRONIZATION: Type #{attribute[:type]} requested for #{attribute[:real_name]}, but is #{cms_attribute.attribute_type}, this may cause errors.")
61
+ elsif cms_attribute.attribute_type == "enum" && attribute[:type] == :string
62
+ # This is a bit of hack. This allows an enum to exist
63
+ # But be treated in the application like a string
64
+ #
65
+ # The intention here is to allow dynamically restricting or allowing
66
+ # values in the application, while having a fixed superset of allowed
67
+ # values in the CMS
68
+ #
69
+ # Nothing to do.
70
+ Rails.logger.warn("TYPE SYNCHRONIZATION: Type #{attribute[:type]} requested for #{attribute[:real_name]}, but is #{cms_attribute.attribute_type}, this may cause errors.")
51
71
  elsif attribute[:type] == :enum || attribute[:type] == :multienum
52
72
  if cms_attribute.attribute_type == "multienum" || cms_attribute.attribute_type == "enum"
53
73
  if attribute[:values]