scrivito_sdk 0.16.0 → 0.17.0

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