scrivito_sdk 1.16.0 → 1.17.0.rc1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 120dcd6bd738739f7ec4a76b14efb2c8480a5cfa982994b47e3b7bc2536461ae
4
- data.tar.gz: 94b673926f238fec7899e4179ac018ac2d188a9ccc871b2029ce08c2743f0370
3
+ metadata.gz: 51526b37bad3d471777ad7fffc844651cb89374e653ef1db39f13e01629373ed
4
+ data.tar.gz: 8e0e6d162678e41110d374798dc7f1c0cbb46bf8bdd47d2596cf3b51fdb94b33
5
5
  SHA512:
6
- metadata.gz: 37d17a84fab05dff1451d049b4b8881f342c5aec052a6c1c0d63245c0abc77b7c823398ee5bb9b6b9541b621d040acaf28126de65c22c6cf5c171939a71041b6
7
- data.tar.gz: 8e709d56765165984a41b72eb1845494f96302bb26e28be13d0a92926293d29bf3d383ac0226bc76ef57c0429d92aea238869d42d387229584313c2c42cd9939
6
+ metadata.gz: 3738673cb784d53bdf76e76d82b211a326ed0857a4c72cb4a6f94edd5679238a6b7d594d85039989322e28cdbf15034754fdb1acd26416f9f9d3e886b9bcf909
7
+ data.tar.gz: 574ab479d5a0f01797f85c3be7571ae061dad11d634cd2e2d0d555ebc49fa03cf832dbde5e3cd8a3f6c8d3ec7b97a19308e2ded17366336364bb925cf45f308a
data/README.md CHANGED
@@ -12,7 +12,7 @@ of concepts and APIs, tutorials, and release notes.
12
12
 
13
13
  ## License
14
14
 
15
- Copyright (c) 2009 - 2019 Infopark AG (http://www.infopark.com)
15
+ Copyright (c) 2009 - 2020 Infopark Group GmbH (http://www.infopark.com)
16
16
 
17
17
  This software can be used and modified under the LGPL-3.0. Please refer to
18
18
  http://www.gnu.org/licenses/lgpl-3.0.html for the license text.
@@ -29,6 +29,8 @@ class AttributeSerializer < Struct.new(:obj_class, :obj_id)
29
29
  _permalink
30
30
  ].include?(attribute_name)
31
31
  [attribute_name, attribute_value.to_s]
32
+ elsif attribute_name == '_restriction'
33
+ [attribute_name, attribute_value]
32
34
  else
33
35
  raise_unknown_attribute(attribute_name)
34
36
  end
@@ -1,4 +1,3 @@
1
- require 'json'
2
1
  require 'ostruct'
3
2
  require 'active_model/naming'
4
3
 
@@ -25,15 +24,18 @@ module Scrivito
25
24
  PublicSystemAttributeDefinition = Class.new(AttributeDefinition)
26
25
 
27
26
  SYSTEM_ATTRIBUTES = AttributeDefinitionCollection.new(
28
- '_id' => PublicSystemAttributeDefinition.new(:_id, :string),
29
- '_last_changed' => PublicSystemAttributeDefinition.new(:_last_changed, :date),
30
- '_obj_class' => PublicSystemAttributeDefinition.new(:_obj_class, :string),
31
- '_path' => PublicSystemAttributeDefinition.new(:_path, :string),
32
- '_permalink' => PublicSystemAttributeDefinition.new(:_permalink, :string),
33
-
34
- '_conflicts' => AttributeDefinition.new(:_conflicts, nil),
35
- '_modification' => AttributeDefinition.new(:_modification, nil),
36
- '_widget_pool' => AttributeDefinition.new(:_widget_pool, nil),
27
+ '_id' => PublicSystemAttributeDefinition.new(:_id, :string),
28
+ '_created_at' => PublicSystemAttributeDefinition.new(:_created_at, :date),
29
+ '_last_changed' => PublicSystemAttributeDefinition.new(:_last_changed, :date),
30
+ '_first_published_at' => PublicSystemAttributeDefinition.new(:_first_published_at, :date),
31
+ '_published_at' => PublicSystemAttributeDefinition.new(:_published_at, :date),
32
+ '_obj_class' => PublicSystemAttributeDefinition.new(:_obj_class, :string),
33
+ '_path' => PublicSystemAttributeDefinition.new(:_path, :string),
34
+ '_permalink' => PublicSystemAttributeDefinition.new(:_permalink, :string),
35
+
36
+ '_conflicts' => AttributeDefinition.new(:_conflicts, nil),
37
+ '_modification' => AttributeDefinition.new(:_modification, nil),
38
+ '_widget_pool' => AttributeDefinition.new(:_widget_pool, nil),
37
39
  )
38
40
 
39
41
  UNIQ_ATTRIBUTES = %w[
@@ -44,7 +46,10 @@ module Scrivito
44
46
 
45
47
  GENERATED_ATTRIBUTES = %w[
46
48
  _conflicts
49
+ _created_at
47
50
  _last_changed
51
+ _first_published_at
52
+ _published_at
48
53
  _modification
49
54
  _obj_type
50
55
  _text_links
@@ -138,14 +143,14 @@ module Scrivito
138
143
  else
139
144
  attributes = build_attributes_with_defaults(attributes, context)
140
145
  attributes = prepare_attributes_for_instantiation(attributes)
141
- api_attributes, widget_properties = prepare_attributes_for_rest_api(attributes)
146
+ extraction, api_attributes = prepare_attributes_for_rest_api(attributes)
142
147
 
143
148
  workspace = Workspace.current
144
149
  obj_data = workspace.create_obj(obj: api_attributes)
145
150
  obj = BasicObj.instantiate(obj_data)
146
151
  obj.revision = workspace.revision
147
152
 
148
- CmsRestApi::WidgetExtractor.notify_persisted_widgets(obj, widget_properties)
153
+ extraction.notify_persisted(obj)
149
154
  obj
150
155
  end
151
156
  end
@@ -301,7 +306,7 @@ module Scrivito
301
306
  # @api public
302
307
  def self.find_by_path(path)
303
308
  Workspace.current.objs.find_one_by(:path, path,
304
- obj_class_name: type_computer.obj_class_name_for_type(self))
309
+ type_computer.obj_class_name_for_type(self))
305
310
  end
306
311
 
307
312
  # Find an {Scrivito::BasicObj Obj} with the given name.
@@ -329,7 +334,7 @@ module Scrivito
329
334
  # @api public
330
335
  def self.find_by_permalink(permalink)
331
336
  Workspace.current.objs.find_one_by(:permalink, permalink,
332
- obj_class_name: type_computer.obj_class_name_for_type(self))
337
+ type_computer.obj_class_name_for_type(self))
333
338
  end
334
339
 
335
340
  # Returns the {Scrivito::BasicObj Obj} with the given permalink, or raises
@@ -414,9 +419,9 @@ module Scrivito
414
419
  # }
415
420
  # )
416
421
  def update(attributes)
417
- api_attributes, widget_properties = prepare_attributes_for_rest_api(attributes)
422
+ extraction, api_attributes = prepare_attributes_for_rest_api(attributes)
418
423
  update_data(workspace.update_obj(id, obj: api_attributes))
419
- CmsRestApi::WidgetExtractor.notify_persisted_widgets(self, widget_properties)
424
+ extraction.notify_persisted(self)
420
425
  self
421
426
  end
422
427
 
@@ -633,7 +638,7 @@ module Scrivito
633
638
  return [] if binary?
634
639
  toclist = children
635
640
  toclist = toclist.reject { |toc| toc.binary? } unless args.include?(:all)
636
- toclist
641
+ toclist.sort_by(&:id)
637
642
  end
638
643
 
639
644
  # @api public
@@ -659,13 +664,23 @@ module Scrivito
659
664
  SYSTEM_KEYS = Set.new(%w[
660
665
  body
661
666
  _id
667
+ _created_at
662
668
  _last_changed
669
+ _first_published_at
670
+ _published_at
663
671
  _path
664
672
  _permalink
665
673
  _obj_class
666
674
  title
667
675
  ])
668
676
 
677
+ DATE_KEYS = Set.new(%w[
678
+ _created_at
679
+ _last_changed
680
+ _first_published_at
681
+ _published_at
682
+ ])
683
+
669
684
  # Returns the value of a system or custom attribute specified by its name.
670
685
  # Passing an invalid key will not raise an error but return +nil+.
671
686
  # @api public
@@ -704,11 +719,30 @@ module Scrivito
704
719
  }
705
720
  end
706
721
 
722
+ # @return [nil, ActiveSupport::TimeWithZone]
723
+ # @api public
724
+ def created_at
725
+ read_attribute('_created_at')
726
+ end
727
+
728
+ # @return [ActiveSupport::TimeWithZone]
707
729
  # @api public
708
730
  def last_changed
709
731
  read_attribute('_last_changed')
710
732
  end
711
733
 
734
+ # @return [nil, ActiveSupport::TimeWithZone]
735
+ # @api public
736
+ def first_published_at
737
+ read_attribute('_first_published_at')
738
+ end
739
+
740
+ # @return [nil, ActiveSupport::TimeWithZone]
741
+ # @api public
742
+ def published_at
743
+ read_attribute('_published_at')
744
+ end
745
+
712
746
  def new?(revision=workspace.base_revision)
713
747
  quick_modification(revision) == "new"
714
748
  end
@@ -991,15 +1025,16 @@ module Scrivito
991
1025
  end
992
1026
 
993
1027
  def self.prepare_attributes_for_rest_api(attributes, obj = nil)
994
- widget_pool_attributes, obj_attributes = CmsRestApi::WidgetExtractor.call(attributes, obj)
1028
+ extraction = CmsRestApi::WidgetExtractor.call(attributes, obj)
1029
+ obj_attributes = extraction.attributes
995
1030
  obj_id = obj ? obj.id : obj_attributes.fetch(:_id)
996
1031
  workspace = obj ? obj.revision.workspace : Workspace.current
997
1032
 
998
1033
  api_attributes = serialize_attributes(
999
- obj_attributes, widget_pool_attributes, workspace, obj_id
1034
+ obj_attributes, extraction.widget_pool_attributes, workspace, obj_id
1000
1035
  )
1001
1036
 
1002
- [api_attributes, widget_pool_attributes]
1037
+ [extraction, api_attributes]
1003
1038
  end
1004
1039
 
1005
1040
  def self.find_attribute_definitions(obj_class, basic_class = self)
@@ -1025,7 +1060,7 @@ module Scrivito
1025
1060
 
1026
1061
  private_class_method def self.serialize_widget_pool_attributes(serializer, widget_pool_attributes)
1027
1062
  {}.tap do |serialized_attributes|
1028
- widget_pool_attributes.each_pair do |widget, widget_attributes|
1063
+ widget_pool_attributes.each_value do |(widget, widget_attributes)|
1029
1064
  obj_class = widget_attributes['_obj_class']
1030
1065
  widget_serializer = AttributeSerializer.new(obj_class, serializer.obj_id)
1031
1066
 
@@ -1066,8 +1101,7 @@ module Scrivito
1066
1101
 
1067
1102
  def instantiate_widget(widget_id, widget_data)
1068
1103
  BasicWidget.instantiate(widget_data).tap do |widget|
1069
- widget.id = widget_id
1070
- widget.obj = self
1104
+ widget.attach_to_obj(self, widget_id)
1071
1105
  end
1072
1106
  end
1073
1107
 
@@ -1151,7 +1185,7 @@ module Scrivito
1151
1185
 
1152
1186
  def value_of_system_attribute(attribute_name)
1153
1187
  attribute_value = data_from_cms.value_of(attribute_name)
1154
- if attribute_name == '_last_changed'
1188
+ if DATE_KEYS.include?(attribute_name)
1155
1189
  DateConversion.deserialize_from_backend(attribute_value)
1156
1190
  else
1157
1191
  attribute_value
@@ -95,8 +95,6 @@ class BasicWidget
95
95
 
96
96
  attr_accessor :container, :container_attribute_name
97
97
 
98
- attr_writer :obj, :id
99
-
100
98
  attr_reader :attributes_to_be_saved
101
99
 
102
100
  delegate :widget_from_pool, to: :obj
@@ -221,12 +219,29 @@ class BasicWidget
221
219
  @attributes_to_be_saved.nil?
222
220
  end
223
221
 
222
+ def attach_to_obj(an_obj, widget_id = compute_widget_id(an_obj))
223
+ @obj = an_obj
224
+ @id = widget_id
225
+
226
+ attributes_to_be_saved
227
+ end
228
+
224
229
  def ==(other)
225
- other.respond_to?(:obj) && obj == other.obj && other.respond_to?(:id) && id == other.id
230
+ eql?(other)
226
231
  end
227
232
 
228
233
  def eql?(other)
229
- self == other
234
+ other.respond_to?(:equality_identifier, true) &&
235
+ equality_identifier.eql?(other.equality_identifier)
236
+ end
237
+
238
+ def hash
239
+ unless persisted?
240
+ # An unbound widget will likely be attached soon, which mutates its #hash value. Evil.
241
+ raise ScrivitoError, 'a new widget is not allowed to be used as a hash key'
242
+ end
243
+
244
+ equality_identifier.hash
230
245
  end
231
246
 
232
247
  #
@@ -262,7 +277,7 @@ class BasicWidget
262
277
  { obj: {_widget_pool: {id => previous_widget_content}} })
263
278
  reload
264
279
  else
265
- raise ScrivitoError, "cannot revert changes, since widget is #{modification}."
280
+ raise ScrivitoError, "cannot revert changes, since widget is #{modification}"
266
281
  end
267
282
  end
268
283
 
@@ -297,14 +312,6 @@ class BasicWidget
297
312
  end
298
313
  end
299
314
 
300
- def hash
301
- if @obj && @id
302
- (id + obj.id).hash
303
- else
304
- super
305
- end
306
- end
307
-
308
315
  # Returns the entity ({Scrivito::BasicObj} or {Scrivito::BasicWidget})
309
316
  # that references this widget.
310
317
  # @api public
@@ -360,7 +367,8 @@ class BasicWidget
360
367
  end
361
368
  end
362
369
 
363
- def forget_unsaved_attributes
370
+ def notify_persisted(obj)
371
+ @obj = obj
364
372
  @attributes_to_be_saved = nil
365
373
  reload_data
366
374
  end
@@ -398,6 +406,12 @@ class BasicWidget
398
406
  referenced + referenced.map { |widget| widget.contained_widgets }.flatten
399
407
  end
400
408
 
409
+ protected
410
+
411
+ def equality_identifier
412
+ [id, obj.id]
413
+ end
414
+
401
415
  private
402
416
 
403
417
  def workspace
@@ -433,6 +447,14 @@ class BasicWidget
433
447
  def value_of_system_attribute(attribute_name)
434
448
  data_from_cms.value_of(attribute_name)
435
449
  end
450
+
451
+ def compute_widget_id(obj)
452
+ @attributes_to_be_saved.delete('_id') || generate_widget_id(obj)
453
+ end
454
+
455
+ def generate_widget_id(obj)
456
+ obj.present? ? obj.generate_widget_pool_id : BasicObj.generate_widget_pool_id
457
+ end
436
458
  end
437
459
 
438
460
  end
@@ -206,7 +206,7 @@ module Scrivito
206
206
  private_class_method :blob_data_cache_key
207
207
 
208
208
  def self.search_params_cache_key(params)
209
- MultiJson.encode(normalize_search_param(params))
209
+ JSON.generate(normalize_search_param(params))
210
210
  end
211
211
  private_class_method :search_params_cache_key
212
212
 
@@ -67,7 +67,7 @@ class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :editing_option
67
67
  if FIELD_TYPES_WITH_ORIGINAL_CONTENT.include?(field_type)
68
68
  original_value = view.scrivito_value(current_value)
69
69
  original_content = original_content(field_type, original_value, current_value)
70
- encoded_content = Base64.strict_encode64(MultiJson.encode(original_content))
70
+ encoded_content = Base64.strict_encode64(JSON.generate(original_content))
71
71
  options['private-field-original-content'] = encoded_content
72
72
  end
73
73
 
@@ -161,7 +161,7 @@ class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :editing_option
161
161
  end
162
162
 
163
163
  def valid_values
164
- MultiJson.encode(obj_or_widget.attribute_definitions[field_name].try(:values) || [])
164
+ JSON.generate(obj_or_widget.attribute_definitions[field_name].try(:values) || [])
165
165
  end
166
166
 
167
167
  def assert_valid_attribute
@@ -123,7 +123,7 @@ module Scrivito
123
123
  timer)
124
124
  request = method_to_net_http_class(method).new(path(resource_path))
125
125
  set_headers(request)
126
- request.body = MultiJson.encode(payload) if payload.present?
126
+ request.body = JSON.generate(payload) if payload.present?
127
127
 
128
128
  response = retry_once_on_network_error(method, timer) do
129
129
  CmsRestApi::RateLimit.retry_on_rate_limit(timer) do
@@ -138,12 +138,12 @@ module Scrivito
138
138
  private_class_method def self.handle_response(resource_path, response)
139
139
  http_code = response.code.to_i
140
140
  if response.code.start_with?('2')
141
- MultiJson.load(response.body)
141
+ JSON.parse(response.body)
142
142
  elsif response.code == '403'
143
143
  raise AccessDenied.new(response.body)
144
144
  else
145
145
  begin
146
- error_body = MultiJson.decode(response.body)
146
+ error_body = JSON.parse(response.body)
147
147
  specific_output = error_body['error']
148
148
 
149
149
  if response.code.start_with?('4')
@@ -155,7 +155,7 @@ module Scrivito
155
155
  else # 3xx and >500 are treated as NetworkErrors
156
156
  raise NetworkError.new(response.body, http_code)
157
157
  end
158
- rescue MultiJson::DecodeError
158
+ rescue JSON::ParserError, TypeError
159
159
  raise NetworkError.new(response.body, http_code)
160
160
  end
161
161
  end
@@ -170,6 +170,9 @@ module Scrivito
170
170
  message = task_data["message"] ||
171
171
  "Missing error message in task response #{task_data}"
172
172
 
173
+ if task_data['status'] == 'exception'
174
+ raise BackendError.new(message, 500)
175
+ end
173
176
  backend_code = task_data['code']
174
177
  raise ClientError.new(message, backend_code: backend_code)
175
178
  end
@@ -1,53 +1,68 @@
1
1
  module Scrivito
2
2
  class CmsRestApi
3
3
  class WidgetExtractor
4
-
5
4
  # Extract widgets from a given attribute tree
6
5
  # first return param: a flat hash of widget_like => updated widget attributes
7
6
  # second return param: the updated attributes
8
7
  def self.call(attributes, obj=nil)
8
+ new(obj).extract_widgets(attributes)
9
+ end
10
+
11
+ def initialize(obj)
12
+ @obj = obj
13
+ end
14
+
15
+ def extract_widgets(attributes)
16
+ @extracted_widgets = {}
9
17
  updated_attributes = attributes.dup
10
- extracted_widgets = {}
18
+ widget_pool = updated_attributes.delete(:_widget_pool)
19
+ extract_widgets_from_pool(widget_pool) if widget_pool
20
+ extract_widgets_from_attributes(updated_attributes)
11
21
 
12
- (updated_attributes.delete(:_widget_pool) || {}).each do |widget, widget_properties|
13
- unless widget.persisted?
14
- raise ScrivitoError, 'new top-level widgets are not allowed in the _widget_pool'
15
- end
16
- extracted_widgets[widget] = widget_properties
17
- extracted_widgets.merge!(call(widget_properties, obj).first)
22
+ Extraction.new(extracted_widgets, updated_attributes)
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :obj
28
+ attr_reader :extracted_widgets
29
+
30
+ def extract_widgets_from_pool(widget_pool)
31
+ widget_pool.each do |widget, widget_properties|
32
+ extracted_widgets[widget.id] = [widget, widget_properties]
33
+ extract_widgets_from_attributes(widget_properties)
18
34
  end
35
+ end
19
36
 
20
- updated_attributes.each do |attribute_name, value|
37
+ def extract_widgets_from_attributes(attributes)
38
+ attributes.each do |attribute_name, value|
21
39
  if value.is_a?(Array) && value.all? { |w| w.is_a?(BasicWidget) }
22
40
  value.each do |widget|
23
41
  unless widget.persisted?
24
- attach_widget_to_obj(widget, obj)
25
- extracted_widgets[widget] = widget.attributes_to_be_saved
26
- extracted_widgets.merge!(call(widget.attributes_to_be_saved, obj).first)
42
+ widget_attributes = widget.attach_to_obj(obj)
43
+ extracted_widgets[widget.id] = [widget, widget_attributes]
44
+ extract_widgets_from_attributes(widget_attributes)
27
45
  end
28
46
  end
29
47
  end
30
48
  end
31
-
32
- return extracted_widgets, updated_attributes
33
49
  end
34
50
 
35
- def self.attach_widget_to_obj(widget, obj)
36
- widget.id = widget.attributes_to_be_saved.delete('_id') || generate_widget_pool_id(obj)
37
- widget.obj = obj
38
- end
51
+ class Extraction
52
+ attr_reader :widget_pool_attributes, :attributes
39
53
 
40
- def self.notify_persisted_widgets(obj, widget_hash)
41
- widget_hash.each do |widget, attributes|
42
- if attributes.present?
43
- widget.obj = obj
44
- widget.forget_unsaved_attributes
45
- end
54
+ def initialize(widget_pool_attributes, attributes)
55
+ @widget_pool_attributes = widget_pool_attributes
56
+ @attributes = attributes
46
57
  end
47
- end
48
58
 
49
- def self.generate_widget_pool_id(obj)
50
- obj.presence ? obj.generate_widget_pool_id : BasicObj.generate_widget_pool_id
59
+ def notify_persisted(obj)
60
+ widget_pool_attributes.each_value do |(widget, attributes)|
61
+ if attributes.present?
62
+ widget.notify_persisted(obj)
63
+ end
64
+ end
65
+ end
51
66
  end
52
67
  end
53
68
  end