scrivito_sdk 0.16.0 → 0.17.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scrivito/default_cms_controller.rb +3 -3
  3. data/app/controllers/scrivito/objs_controller.rb +18 -7
  4. data/app/controllers/scrivito/webservice_controller.rb +13 -1
  5. data/app/controllers/scrivito/workspaces_controller.rb +5 -1
  6. data/app/helpers/scrivito/cms_asset_helper.rb +8 -1
  7. data/app/helpers/scrivito/default_cms_routing_helper.rb +2 -4
  8. data/app/helpers/scrivito/display_helper.rb +0 -7
  9. data/app/helpers/scrivito/editing_helper.rb +10 -8
  10. data/app/helpers/scrivito/layout_helper.rb +4 -11
  11. data/config/ca-bundle.crt +1773 -1416
  12. data/config/cms_routes.rb +2 -2
  13. data/config/routes.rb +1 -0
  14. data/lib/assets/javascripts/scrivito_editing.js +969 -533
  15. data/lib/assets/stylesheets/scrivito_editing.css +99 -9
  16. data/lib/generators/cms/migration/templates/migration.erb +34 -6
  17. data/lib/generators/cms/widget/templates/migration.erb +3 -6
  18. data/lib/scrivito/attribute.rb +158 -0
  19. data/lib/scrivito/attribute_collection.rb +72 -0
  20. data/lib/scrivito/attribute_content.rb +39 -3
  21. data/lib/scrivito/basic_obj.rb +48 -27
  22. data/lib/scrivito/basic_widget.rb +15 -5
  23. data/lib/scrivito/client_config.rb +46 -19
  24. data/lib/scrivito/cms_field_tag.rb +1 -1
  25. data/lib/scrivito/cms_rest_api/attribute_serializer.rb +6 -1
  26. data/lib/scrivito/cms_rest_api/blob_uploader.rb +1 -1
  27. data/lib/scrivito/configuration.rb +32 -2
  28. data/lib/scrivito/connection_manager.rb +1 -6
  29. data/lib/scrivito/content_conversion.rb +11 -7
  30. data/lib/scrivito/editing_context.rb +12 -0
  31. data/lib/scrivito/gem_info.rb +13 -0
  32. data/lib/scrivito/membership.rb +26 -0
  33. data/lib/scrivito/memberships_collection.rb +78 -0
  34. data/lib/scrivito/migrations/migration.rb +1 -1
  35. data/lib/scrivito/migrations/migration_dsl.rb +37 -0
  36. data/lib/scrivito/obj_class.rb +282 -0
  37. data/lib/scrivito/obj_data.rb +20 -1
  38. data/lib/scrivito/obj_params_parser.rb +2 -0
  39. data/lib/scrivito/obj_search_builder.rb +1 -1
  40. data/lib/scrivito/obj_search_enumerator.rb +11 -6
  41. data/lib/scrivito/objs_collection.rb +130 -0
  42. data/lib/scrivito/restriction_set.rb +54 -0
  43. data/lib/scrivito/user.rb +114 -0
  44. data/lib/scrivito/user_definition.rb +159 -0
  45. data/lib/scrivito/widget_garbage_collection.rb +4 -4
  46. data/lib/scrivito/workspace.rb +13 -78
  47. data/lib/scrivito/workspace_data_from_service.rb +2 -0
  48. metadata +15 -5
@@ -0,0 +1,72 @@
1
+ module Scrivito
2
+ # Represents a collection of {Scrivito::Attribute} instances. It provides convienient ways to
3
+ # find and add attributes and is returned by {Scrivito::ObjClass#attributes}. It behaves almost
4
+ # exactly as an Array, so methods like `#each`, `#select` etc. are available. It is not necessary
5
+ # to manually create an {Scrivito::AttributeCollection}, because a simple Array with attributes
6
+ # can be used instead.
7
+ #
8
+ # @api public
9
+ class AttributeCollection
10
+ include Enumerable
11
+ extend Forwardable
12
+
13
+ # Initializes an attribute collection for an obj class.
14
+ #
15
+ # @param [Scrivito::ObjClass] obj_class
16
+ # @param [Array<Scrivito::Attribute>] attributes
17
+ # @return [Scrivito::AttributeCollection]
18
+ def initialize(obj_class, attributes)
19
+ @obj_class = obj_class
20
+ @attributes = attributes
21
+ end
22
+
23
+ # Yields successive attributes of the collection. Implements the {Enumerable} interface.
24
+ # @api public
25
+ def_delegators :@attributes, :each
26
+
27
+ # Finds an attribute in this collection by its name.
28
+ #
29
+ # @api public
30
+ #
31
+ # See {Scrivito::ObjClass#attributes} for example of how to find an attribute by name.
32
+ #
33
+ # @param [String] name
34
+ # @return [Scrivito::Attribute]
35
+ # @raise [Scrivito::ResourceNotFound] Raised when no attribute with the given +name+ can be found.
36
+ def [](name)
37
+ attribute = @attributes.detect { |attribute| attribute.name == name.to_s }
38
+
39
+ unless attribute
40
+ raise ResourceNotFound, "Could not find #{Attribute} with name '#{name}' for" \
41
+ " obj class '#{@obj_class.name}'."
42
+ end
43
+
44
+ attribute
45
+ end
46
+
47
+ # Adds an attribute to this collection and updates the obj class.
48
+ #
49
+ # @api public
50
+ #
51
+ # See {Scrivito::ObjClass#attributes} for example of how to add an attribute.
52
+ #
53
+ # @param [Scrivito::Attribute, Hash] attribute The attribute to add. Can be either an attribute
54
+ # instance or an attribute property hash.
55
+ # @return [Scrivito::AttributeCollection]
56
+ def add(attribute)
57
+ unless attribute.respond_to?(:to_cms_rest_api)
58
+ attribute = Attribute.new(attribute)
59
+ end
60
+
61
+ @attributes << attribute
62
+
63
+ @obj_class.update(attributes: @attributes)
64
+
65
+ self
66
+ end
67
+ alias_method :<<, :add
68
+
69
+ # Deletes all attributes from this collection that are equal to the given attribute.
70
+ def_delegators :@attributes, :delete
71
+ end
72
+ end
@@ -4,7 +4,7 @@ module AttributeContent
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  def respond_to?(method_id, include_private=false)
7
- if has_attribute?(method_id)
7
+ if has_custom_attribute?(method_id)
8
8
  true
9
9
  else
10
10
  super
@@ -12,7 +12,7 @@ module AttributeContent
12
12
  end
13
13
 
14
14
  def method_missing(method_name, *args)
15
- if has_attribute?(method_name)
15
+ if has_custom_attribute?(method_name)
16
16
  read_attribute(method_name.to_s)
17
17
  else
18
18
  super
@@ -39,9 +39,10 @@ module AttributeContent
39
39
  end
40
40
  end
41
41
 
42
- def has_attribute?(name)
42
+ def has_custom_attribute?(name)
43
43
  data_from_cms.has_custom_attribute?(name.to_s)
44
44
  end
45
+ alias_method :has_attribute?, :has_custom_attribute?
45
46
 
46
47
  # @return [String]
47
48
  def type_of_attribute(field_name)
@@ -121,6 +122,8 @@ module AttributeContent
121
122
  DateAttribute.parse(attribute_value) if attribute_value
122
123
  when "linklist"
123
124
  build_links(attribute_value)
125
+ when "link"
126
+ build_link(attribute_value)
124
127
  when "reference"
125
128
  BasicObj.find([attribute_value]).first
126
129
  when "referencelist"
@@ -148,6 +151,39 @@ module AttributeContent
148
151
  end
149
152
  end
150
153
 
154
+ def build_link(attribute_value)
155
+ return unless attribute_value
156
+
157
+ if attribute_value['destination']
158
+ build_internal_link(attribute_value)
159
+ else
160
+ build_external_link(attribute_value)
161
+ end
162
+ end
163
+
164
+ def build_internal_link(attribute_value)
165
+ properties = {
166
+ obj: Obj.find(attribute_value['destination']),
167
+ title: attribute_value['title'],
168
+ query: attribute_value['query'],
169
+ fragment: attribute_value['fragment'],
170
+ target: attribute_value['target'],
171
+ }
172
+
173
+ Link.new(properties)
174
+ rescue ResourceNotFound
175
+ end
176
+
177
+ def build_external_link(attribute_value)
178
+ properties = {
179
+ url: attribute_value['url'],
180
+ title: attribute_value['title'],
181
+ target: attribute_value['target'],
182
+ }
183
+
184
+ Link.new(properties)
185
+ end
186
+
151
187
  def build_widgets(widget_data, attribute_name)
152
188
  widget_data.map do |widget_id|
153
189
  widget = widget_from_pool(widget_id)
@@ -42,6 +42,12 @@ module Scrivito
42
42
  # Link.new(:obj => other_obj, :title => "Other Obj")
43
43
  # ])
44
44
  #
45
+ # @example Passing a {Link Link} allows you to set a link.
46
+ # Obj.create(
47
+ # external_link: Link.new(url: 'http://www.example.com', title: 'Example')
48
+ # internal_link: Link.new(obj: other_obj, title: 'Other Obj')
49
+ # )
50
+ #
45
51
  # @example Dates attributes accept Time, Date and their subclasses (DateTime for example)
46
52
  # Obj.create(:date => Time.new)
47
53
  # Obj.create(:date => Date.now)
@@ -74,6 +80,7 @@ module Scrivito
74
80
  json = CmsRestApi.post(cms_rest_api_path, obj: api_attributes)
75
81
  obj = find(json['id'] || json['_id'])
76
82
  CmsRestApi::WidgetExtractor.notify_persisted_widgets(obj, widget_properties)
83
+ Workspace.current.reload
77
84
  obj
78
85
  end
79
86
 
@@ -106,11 +113,11 @@ module Scrivito
106
113
  # @return [Obj, Array<Obj>]
107
114
  # @api public
108
115
  def self.find(id_or_list)
109
- Workspace.current.find_obj(id_or_list)
116
+ Workspace.current.objs.find(id_or_list)
110
117
  end
111
118
 
112
119
  def self.find_by_id(id)
113
- Workspace.current.find_obj_by_id(id)
120
+ Workspace.current.objs.find_by_id(id)
114
121
  end
115
122
 
116
123
  # Find a {BasicObj Obj} by its id.
@@ -120,7 +127,7 @@ module Scrivito
120
127
  # @return [Obj, Array<Obj>]
121
128
  # @api public
122
129
  def self.find_including_deleted(id_or_list)
123
- Workspace.current.find_obj_including_deleted(id_or_list)
130
+ Workspace.current.objs.find_including_deleted(id_or_list)
124
131
  end
125
132
 
126
133
  # Returns a {ObjSearchEnumerator} with the given initial subquery consisting of the four arguments.
@@ -138,7 +145,7 @@ module Scrivito
138
145
  # @return [ObjSearchEnumerator]
139
146
  # @api public
140
147
  def self.where(field, operator, value, boost = nil)
141
- ObjSearchEnumerator.new.and(field, operator, value, boost)
148
+ Workspace.current.objs.where(field, operator, value, boost)
142
149
  end
143
150
 
144
151
  # Returns a {ObjSearchEnumerator} of all {BasicObj Obj}s.
@@ -147,7 +154,7 @@ module Scrivito
147
154
  # @api public
148
155
  def self.all
149
156
  if superclass == Scrivito::BasicObj
150
- search_for_all
157
+ Workspace.current.objs.all
151
158
  else
152
159
  find_all_by_obj_class(name)
153
160
  end
@@ -158,7 +165,7 @@ module Scrivito
158
165
  # @return [ObjSearchEnumerator]
159
166
  # @api public
160
167
  def self.find_all_by_obj_class(obj_class)
161
- search_for_all.and(:_obj_class, :equals, obj_class)
168
+ Workspace.current.objs.find_all_by_obj_class(obj_class)
162
169
  end
163
170
 
164
171
  # Find the {BasicObj Obj} with the given path.
@@ -167,7 +174,7 @@ module Scrivito
167
174
  # @return [Obj]
168
175
  # @api public
169
176
  def self.find_by_path(path)
170
- Workspace.current.find_obj_by_path(path)
177
+ Workspace.current.objs.find_by_path(path)
171
178
  end
172
179
 
173
180
  # Find an {BasicObj Obj} with the given name.
@@ -193,7 +200,7 @@ module Scrivito
193
200
  # @return [Obj]
194
201
  # @api public
195
202
  def self.find_by_permalink(permalink)
196
- Workspace.current.find_obj_by_permalink(permalink)
203
+ Workspace.current.objs.find_by_permalink(permalink)
197
204
  end
198
205
 
199
206
  # Returns the {BasicObj Obj} with the given permalink, or raise ResourceNotFound if no matching Obj exists.
@@ -257,7 +264,7 @@ module Scrivito
257
264
  def update(attributes)
258
265
  api_attributes, widget_properties = prepare_attributes_for_rest_api(attributes)
259
266
  CmsRestApi.put(cms_rest_api_path, obj: api_attributes)
260
- Workspace.reload
267
+ workspace.reload
261
268
  reload
262
269
  CmsRestApi::WidgetExtractor.notify_persisted_widgets(self, widget_properties)
263
270
  self
@@ -272,7 +279,7 @@ module Scrivito
272
279
 
273
280
  CmsRestApi.delete(cms_rest_api_path)
274
281
 
275
- Workspace.reload
282
+ workspace.reload
276
283
  end
277
284
 
278
285
  def to_param
@@ -298,7 +305,7 @@ module Scrivito
298
305
  list << list.last + component
299
306
  end
300
307
  ancestor_paths[0] = "/"
301
- Workspace.current.find_objs_by_paths(ancestor_paths)
308
+ Workspace.current.objs.find_by_paths(ancestor_paths)
302
309
  end
303
310
 
304
311
  # return a list of all child {BasicObj Obj}s.
@@ -307,7 +314,7 @@ module Scrivito
307
314
  def children
308
315
  return [] unless path
309
316
 
310
- Workspace.current.find_objs_by_parent_path(path)
317
+ workspace.objs.find_by_parent_path(path)
311
318
  end
312
319
 
313
320
  ### ATTRIBUTES #################
@@ -464,6 +471,16 @@ module Scrivito
464
471
  end
465
472
  end
466
473
 
474
+ def has_attribute?(key)
475
+ key = key.to_s
476
+
477
+ if INTERNAL_KEYS.include?(key)
478
+ true
479
+ else
480
+ super
481
+ end
482
+ end
483
+
467
484
  # Reloads the attributes of this object from the database.
468
485
  # Notice that the ruby class of this Obj instance will NOT change,
469
486
  # even if the obj_class in the database has changed.
@@ -472,7 +489,7 @@ module Scrivito
472
489
  id = self.id.to_s
473
490
 
474
491
  reload_data = Proc.new do
475
- CmsBackend.instance.find_obj_data_by(Workspace.current.revision, :id, [id]).first.first
492
+ CmsBackend.instance.find_obj_data_by(workspace.revision, :id, [id]).first.first
476
493
  end
477
494
 
478
495
  update_data(reload_data)
@@ -484,9 +501,11 @@ module Scrivito
484
501
  read_attribute('_obj_class')
485
502
  end
486
503
 
504
+ # Returns the {Scrivito::ObjClass} of this object.
505
+ # @return [Scrivito::ObjClass]
506
+ # @api public
487
507
  def obj_class
488
- raise ScrivitoError, "BasicObj#obj_class is no longer available"+
489
- ", please use BasicObj#obj_class_name instead."
508
+ ObjClass.find(obj_class_name)
490
509
  end
491
510
 
492
511
  # @api public
@@ -512,7 +531,7 @@ module Scrivito
512
531
  end
513
532
  end
514
533
 
515
- def modification(revision=Workspace.current.base_revision)
534
+ def modification(revision=workspace.base_revision)
516
535
  return Modification::UNMODIFIED unless revision
517
536
 
518
537
  obj_data_from_revision = cms_data_for_revision(revision)
@@ -646,7 +665,7 @@ module Scrivito
646
665
  # This method does not work with +new+ or +deleted+ objects.
647
666
  # This method also does also not work for the +published+ workspace or the +rtc+ working copy.
648
667
  def revert
649
- Workspace.current.assert_revertable
668
+ workspace.assert_revertable
650
669
 
651
670
  if binary?
652
671
  raise "revert not supported for binary objs"
@@ -656,7 +675,7 @@ module Scrivito
656
675
  # don't do anything
657
676
  when Modification::EDITED
658
677
  previous_content = CmsRestApi.get(
659
- "revisions/#{Workspace.current.base_revision_id}/objs/#{id}")
678
+ "revisions/#{workspace.base_revision_id}/objs/#{id}")
660
679
  updated_content = previous_content.except('id', '_id')
661
680
 
662
681
  added_widget_ids = read_widget_pool.keys - previous_content['_widget_pool'].keys
@@ -756,7 +775,15 @@ module Scrivito
756
775
  end
757
776
 
758
777
  def cms_rest_api_path(obj_id = id)
759
- "#{self.class.cms_rest_api_path}/#{obj_id}"
778
+ "#{self.class.cms_rest_api_path(workspace)}/#{obj_id}"
779
+ end
780
+
781
+ def workspace
782
+ if revision.workspace
783
+ revision.workspace
784
+ else
785
+ raise ScrivitoError, "No workspace set for this obj"
786
+ end
760
787
  end
761
788
 
762
789
  def child_path?
@@ -776,8 +803,8 @@ module Scrivito
776
803
  CmsRestApi.post(cms_rest_api_path, obj: obj_attributes)
777
804
  end
778
805
 
779
- def cms_rest_api_path
780
- "workspaces/#{Workspace.current.id}/objs"
806
+ def cms_rest_api_path(workspace = Workspace.current)
807
+ "workspaces/#{workspace.id}/objs"
781
808
  end
782
809
 
783
810
  def prepare_attributes_for_rest_api(attributes, obj)
@@ -794,12 +821,6 @@ module Scrivito
794
821
 
795
822
  [api_attributes, widget_properties]
796
823
  end
797
-
798
- private
799
-
800
- def search_for_all
801
- ObjSearchEnumerator.new.batch_size(1000)
802
- end
803
824
  end
804
825
  end
805
826
 
@@ -103,9 +103,11 @@ class BasicWidget
103
103
  data_from_cms.value_of('_obj_class')
104
104
  end
105
105
 
106
+ # Returns the {Scrivito::ObjClass} of this widget.
107
+ # @return [Scrivito::ObjClass]
108
+ # @api public
106
109
  def obj_class
107
- raise ScrivitoError, "BasicWidget#obj_class is no longer available"+
108
- ", please use BasicWidget#obj_class_name instead."
110
+ ObjClass.find(obj_class_name)
109
111
  end
110
112
 
111
113
  def ==(other)
@@ -121,19 +123,19 @@ class BasicWidget
121
123
  # This method does not work with +new+ or +deleted+ widgets.
122
124
  # This method also does also not work for the +published+ workspace or the +rtc+ working copy.
123
125
  def revert
124
- Workspace.current.assert_revertable
126
+ workspace.assert_revertable
125
127
 
126
128
  case modification
127
129
  when Modification::UNMODIFIED
128
130
  # do nothing
129
131
  when Modification::EDITED
130
132
  previous_obj_content =
131
- CmsRestApi.get("revisions/#{Workspace.current.base_revision_id}/objs/#{obj.id}")
133
+ CmsRestApi.get("revisions/#{workspace.base_revision_id}/objs/#{obj.id}")
132
134
  previous_widget_content = previous_obj_content["_widget_pool"]["#{id}"]
133
135
  previous_widget_content.delete_if do |attribute_name, _|
134
136
  type_of_attribute(attribute_name) == "widget"
135
137
  end
136
- CmsRestApi.put("workspaces/#{Workspace.current.id}/objs/#{obj.id}",
138
+ CmsRestApi.put("workspaces/#{workspace.id}/objs/#{obj.id}",
137
139
  { obj: {_widget_pool: {id => previous_widget_content}} })
138
140
  else
139
141
  raise ScrivitoError, "cannot revert changes, since widget is #{modification}."
@@ -232,6 +234,14 @@ class BasicWidget
232
234
 
233
235
  private
234
236
 
237
+ def workspace
238
+ if revision.workspace
239
+ revision.workspace
240
+ else
241
+ raise ScrivitoError, "No workspace set for the obj of this widget"
242
+ end
243
+ end
244
+
235
245
  def data_from_cms
236
246
  if persisted?
237
247
  super
@@ -1,24 +1,28 @@
1
1
  module Scrivito
2
2
 
3
- class ClientConfig < Struct.new(:obj, :editing_context, :lookup_context)
3
+ class ClientConfig < Struct.new(:obj, :editing_context, :lookup_context, :resource, :return_to)
4
+ def initialize(obj, editing_context, lookup_context, options = {})
5
+ super(obj, editing_context, lookup_context, *options.values_at(:resource, :return_to))
6
+ end
7
+
4
8
  def to_json
5
9
  config = {}
6
10
  config[:editing_context] = editing_context_config
11
+ config[:user_permissions] = user_permissions_config
7
12
  config[:i18n] = i18n_config
8
13
  config[:obj] = obj_config
9
- config[:open_resource_dialog] = @resource_dialog_config
14
+ config[:resource_dialog] = resource_dialog_config
10
15
  config.to_json
11
16
  end
12
17
 
13
- def open_resource_dialog(obj_id, redirect_to)
14
- @resource_dialog_config = {
15
- obj_id: obj_id,
16
- redirect_to: redirect_to,
18
+ private
19
+
20
+ def user_permissions_config
21
+ {
22
+ publish_workspace: can_publish_workspace
17
23
  }
18
24
  end
19
25
 
20
- private
21
-
22
26
  def editing_context_config
23
27
  {
24
28
  display_mode: editing_context.display_mode,
@@ -35,20 +39,32 @@ class ClientConfig < Struct.new(:obj, :editing_context, :lookup_context)
35
39
  }
36
40
  end
37
41
 
42
+ def can_publish_workspace
43
+ if editing_context.editor
44
+ editing_context.editor.able_to?(:publish_workspace)
45
+ else
46
+ false
47
+ end
48
+ end
49
+
38
50
  def i18n_config
39
51
  {locale: I18n.locale}
40
52
  end
41
53
 
42
54
  def obj_config
43
55
  if obj
44
- {current_page: {
45
- id: obj.id,
46
- obj_class_name: obj.obj_class_name,
47
- has_children: obj.children.any?,
48
- has_conflict: obj.has_conflict?,
49
- has_details_view: obj_has_details_view?,
50
- modification: obj_modification,
51
- }}
56
+ {
57
+ current_page: {
58
+ id: obj.id,
59
+ obj_class_name: obj.obj_class_name,
60
+ has_children: obj.children.any?,
61
+ has_conflict: obj.has_conflict?,
62
+ has_details_view: obj_has_details_view?,
63
+ modification: modification(obj),
64
+ }
65
+ }
66
+ else
67
+ {}
52
68
  end
53
69
  end
54
70
 
@@ -58,11 +74,22 @@ class ClientConfig < Struct.new(:obj, :editing_context, :lookup_context)
58
74
  false
59
75
  end
60
76
 
61
- def obj_modification
77
+ def resource_dialog_config
78
+ if resource
79
+ {
80
+ obj: Configuration.obj_formats.fetch('_default').call(resource),
81
+ return_to: return_to,
82
+ }
83
+ else
84
+ {}
85
+ end
86
+ end
87
+
88
+ def modification(obj_or_resource)
62
89
  if editing_context.comparison.active?
63
- editing_context.comparison.modification(obj)
90
+ editing_context.comparison.modification(obj_or_resource)
64
91
  else
65
- obj.modification
92
+ obj_or_resource.modification
66
93
  end
67
94
  end
68
95
  end