scrivito_sdk 0.60.0 → 0.65.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/app/controllers/scrivito/objs_controller.rb +10 -2
  4. data/app/controllers/scrivito/workspaces_controller.rb +2 -1
  5. data/app/helpers/scrivito_helper.rb +32 -1
  6. data/app/views/scrivito/page_details.html.erb +1 -0
  7. data/app/views/scrivito/{workspaces → webservice}/_workspace.json.jbuilder +0 -0
  8. data/app/views/scrivito/webservice/error.json.jbuilder +1 -1
  9. data/app/views/scrivito/{workspaces/index.json.jbuilder → webservice/workspaces.json.jbuilder} +0 -0
  10. data/config/ca-bundle.crt +1 -1
  11. data/config/precedence_routes.rb +1 -0
  12. data/config/routes.rb +4 -5
  13. data/lib/assets/javascripts/scrivito_ui.js +1875 -1065
  14. data/lib/assets/stylesheets/scrivito_sdk.css +1 -1
  15. data/lib/assets/stylesheets/scrivito_ui.css +1 -1
  16. data/lib/generators/scrivito/install/templates/app/views/page/details.html.erb +3 -1
  17. data/lib/generators/scrivito/page/templates/details.html.erb +3 -1
  18. data/lib/scrivito/attribute_content.rb +159 -17
  19. data/lib/scrivito/attribute_serializer.rb +7 -3
  20. data/lib/scrivito/backend/content_state_node.rb +68 -0
  21. data/lib/scrivito/backend/index.rb +45 -0
  22. data/lib/scrivito/backend/obj_data_cache.rb +68 -0
  23. data/lib/scrivito/backend/obj_data_from_rest.rb +64 -0
  24. data/lib/scrivito/backend/obj_load.rb +45 -0
  25. data/lib/scrivito/backend/obj_query.rb +63 -0
  26. data/lib/scrivito/backend/parent_path_index.rb +21 -0
  27. data/lib/scrivito/backend/path_index.rb +27 -0
  28. data/lib/scrivito/backend/permalink_index.rb +17 -0
  29. data/lib/scrivito/basic_obj.rb +67 -39
  30. data/lib/scrivito/basic_widget.rb +19 -3
  31. data/lib/scrivito/cache_middleware.rb +9 -3
  32. data/lib/scrivito/client_error.rb +3 -3
  33. data/lib/scrivito/cms_backend.rb +64 -18
  34. data/lib/scrivito/cms_data_cache.rb +33 -30
  35. data/lib/scrivito/cms_dispatch_controller.rb +18 -0
  36. data/lib/scrivito/cms_field_tag.rb +3 -2
  37. data/lib/scrivito/cms_rest_api.rb +18 -12
  38. data/lib/scrivito/cms_rest_api/rate_limit.rb +40 -0
  39. data/lib/scrivito/cms_routing.rb +9 -8
  40. data/lib/scrivito/configuration.rb +8 -1
  41. data/lib/scrivito/controller_actions.rb +6 -1
  42. data/lib/scrivito/errors.rb +5 -0
  43. data/lib/scrivito/migrations/cms_backend.rb +2 -0
  44. data/lib/scrivito/named_link.rb +9 -45
  45. data/lib/scrivito/obj_collection.rb +14 -15
  46. data/lib/scrivito/obj_create_params_parser.rb +3 -5
  47. data/lib/scrivito/obj_params_parser.rb +2 -2
  48. data/lib/scrivito/obj_update_params_parser.rb +5 -3
  49. data/lib/scrivito/revision.rb +62 -2
  50. data/lib/scrivito/type_computer.rb +6 -2
  51. data/lib/scrivito/widget_tag.rb +4 -4
  52. data/lib/scrivito/workspace.rb +19 -3
  53. data/lib/scrivito/workspace_data.rb +23 -0
  54. data/lib/scrivito/workspace_data_from_service.rb +11 -28
  55. metadata +31 -7
  56. data/lib/scrivito/workspace_data_from_rest_api.rb +0 -6
@@ -0,0 +1,64 @@
1
+ module Scrivito
2
+ module Backend
3
+
4
+ class ObjDataFromRest < ObjData
5
+ def initialize(data)
6
+ @data = data
7
+ end
8
+
9
+ def raw_value_and_type_of(attribute_name)
10
+ return [value_of_widget_pool, nil] if attribute_name == '_widget_pool'
11
+
12
+ if attribute_data = @data[attribute_name]
13
+ if attribute_name.starts_with?('_')
14
+ [attribute_data, nil]
15
+ else
16
+ type = attribute_data[0]
17
+ [legacy_compatible(attribute_data[1], type), type]
18
+ end
19
+ else
20
+ [nil, nil]
21
+ end
22
+ end
23
+
24
+ def attribute_names_for_comparison
25
+ @data.keys
26
+ end
27
+
28
+ def to_h
29
+ @data
30
+ end
31
+
32
+ private
33
+
34
+ def legacy_compatible(value, type)
35
+ if type == "linklist"
36
+ value.map(&method(:legacy_link))
37
+ elsif type == "link"
38
+ legacy_link(value)
39
+ else
40
+ value
41
+ end
42
+ end
43
+
44
+ def legacy_link(link)
45
+ return link unless link["obj_id"]
46
+
47
+ transformed = link.dup
48
+ transformed["destination"] = transformed.delete("obj_id")
49
+
50
+ transformed
51
+ end
52
+
53
+ def value_of_widget_pool
54
+ if widget_pool = @data['_widget_pool']
55
+ # using Hash#merge to "map" to a new Hash
56
+ widget_pool.merge(widget_pool) do |id, raw_widget_data|
57
+ self.class.new(raw_widget_data)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,45 @@
1
+ module Scrivito
2
+ module Backend
3
+
4
+ module ObjLoad
5
+ class << self
6
+
7
+ def load(revision, ids)
8
+ cache = Backend::ObjDataCache.view_for_revision(revision)
9
+
10
+ missing_ids = []
11
+ results_from_cache = ids.map do |id|
12
+ result = cache.read_obj(id)
13
+
14
+ missing_ids << id unless result
15
+
16
+ result
17
+ end
18
+
19
+ overall_results =
20
+ if missing_ids.blank?
21
+ results_from_cache
22
+ else
23
+ results_from_backend = revision.obj_mget_request(missing_ids)
24
+
25
+ results_from_backend.each do |result|
26
+ if result
27
+ cache.write_obj(result["_id"], result)
28
+ end
29
+ end
30
+
31
+ results_from_cache.map do |result|
32
+ result || results_from_backend.shift
33
+ end
34
+ end
35
+
36
+ overall_results.map do |raw_data|
37
+ Backend::ObjDataFromRest.new(raw_data) if raw_data
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,63 @@
1
+ module Scrivito
2
+ module Backend
3
+
4
+ module ObjQuery
5
+ class << self
6
+
7
+ def query(revision, index, keys)
8
+ cache = Backend::ObjDataCache.view_for_revision(revision)
9
+
10
+ missing_keys = []
11
+ ids_from_cache = keys.map do |key|
12
+ result = cache.read_index(index.id, key)
13
+
14
+ missing_keys << key unless result
15
+
16
+ result
17
+ end
18
+
19
+ if missing_keys.blank?
20
+ return load_nested_ids(revision, ids_from_cache)
21
+ end
22
+
23
+ backend_ids = revision.obj_search_request(index.query(missing_keys))
24
+
25
+ all_obj_datas = load_nested_ids(revision, ids_from_cache + [backend_ids])
26
+
27
+ cache_obj_datas = all_obj_datas[0..-2]
28
+ backend_obj_datas = all_obj_datas.last
29
+
30
+ grouped_backend_results = index.group_by(missing_keys, backend_obj_datas)
31
+
32
+ grouped_backend_results.each_with_index do |result, i|
33
+ ids = result.map { |obj_data| obj_data.value_of("_id") }
34
+ cache.write_index(index.id, missing_keys[i], ids)
35
+ end
36
+
37
+ cache_obj_datas.map do |obj_datas|
38
+ obj_datas || grouped_backend_results.shift
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # nested_ids is a list containing elements that are either lists of ids or nil
45
+ # returns a corresponding list
46
+ # containing elements that are either lists of obj_datas or nil
47
+ def load_nested_ids(revision, nested_ids)
48
+ all_ids = nested_ids.inject([]) do |list, ids|
49
+ ids ? list + ids : list
50
+ end
51
+
52
+ all_obj_datas = Backend::ObjLoad.load(revision, all_ids)
53
+
54
+ nested_ids.map do |ids|
55
+ all_obj_datas.shift(ids.length) if ids
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ module Scrivito
2
+ module Backend
3
+
4
+ module ParentPathIndex
5
+ class << self
6
+
7
+ include Backend::Index
8
+
9
+ def id
10
+ "ppath"
11
+ end
12
+
13
+ def query(keys)
14
+ [{:field => "_parent_path", :operator => :equals, :value => keys}]
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Scrivito
2
+ module Backend
3
+
4
+ module PathIndex
5
+ class << self
6
+
7
+ include Backend::Index
8
+
9
+ def id
10
+ "path"
11
+ end
12
+
13
+ def query(keys)
14
+ [{:field => "_path", :operator => :equals, :value => keys}]
15
+ end
16
+
17
+ def group_by_multiple(paths, obj_datas)
18
+ paths.map do |path|
19
+ obj_datas.select { |obj_data| obj_data.value_of("_path") == path }
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ module Scrivito
2
+ module Backend
3
+ class PermalinkIndex
4
+ class << self
5
+ include Backend::Index
6
+
7
+ def id
8
+ "permalink"
9
+ end
10
+
11
+ def query(keys)
12
+ [{ field: '_permalink', operator: :equals, value: keys }]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -55,11 +55,20 @@ module Scrivito
55
55
  end
56
56
 
57
57
  #
58
- # Create a new {Scrivito::BasicObj Obj} in the cms
58
+ # Create a new {Scrivito::BasicObj Obj} in the CMS.
59
59
  #
60
- # This allows you to set the different attributes types of an obj by
61
- # providing a hash with the attributes names as key and the values you want
62
- # to set as values
60
+ # @api public
61
+ #
62
+ # This allows you to set the different attributes types of an obj by providing a hash with the
63
+ # attributes names as key and the values you want to set as values. It also considers the
64
+ # defaults set via {Scrivito::AttributeContent::ClassMethods#default_for Obj.default_for}.
65
+ #
66
+ # @param [Hash] attributes for the new obj
67
+ # @param [Hash] context in which the object creating should happen
68
+ # @option context [Scrivito::User] :scrivito_user current visitor
69
+ # @return [Obj] the newly created {Scrivito::BasicObj Obj}
70
+ #
71
+ # @see Scrivito::AttributeContent::ClassMethods#default_for
63
72
  #
64
73
  # @example Reference lists have to be provided as an Array of {Scrivito::BasicObj Objs}
65
74
  # Obj.create(:reference_list => [other_obj])
@@ -107,14 +116,11 @@ module Scrivito
107
116
  # # Clear a widget field
108
117
  # obj.update(:widgets => [])
109
118
  #
110
- # @api public
111
- # @param [Hash] attributes
112
- # @return [Obj] the newly created {Scrivito::BasicObj Obj}
113
- #
114
- def self.create(attributes = {})
119
+ def self.create(attributes = {}, context = {})
115
120
  if obj_class = extract_obj_class_from_attributes(attributes)
116
- obj_class.create(attributes)
121
+ obj_class.create(attributes, context)
117
122
  else
123
+ attributes = build_attributes_with_defaults(attributes, context)
118
124
  attributes = prepare_attributes_for_instantiation(attributes)
119
125
  api_attributes, widget_properties = prepare_attributes_for_rest_api(attributes)
120
126
  json = Workspace.current.api_request(:post, '/objs', obj: api_attributes)
@@ -604,42 +610,59 @@ module Scrivito
604
610
  read_attribute('_last_changed')
605
611
  end
606
612
 
607
- def new?(revision=Workspace.current.base_revision)
608
- return false unless revision
613
+ def new?(revision=workspace.base_revision)
614
+ quick_modification(revision) == "new"
615
+ end
609
616
 
610
- if read_attribute('_modification') != 'deleted'
611
- cms_data_for_revision(revision).nil?
612
- else
613
- false
614
- end
617
+ def deleted?(revision=workspace.base_revision)
618
+ quick_modification(revision) == "deleted"
615
619
  end
616
620
 
617
- def deleted?(revision=Workspace.current.base_revision)
618
- return false unless revision
621
+ def modification(revision=workspace.base_revision)
622
+ quick_modification = quick_modification(revision)
619
623
 
620
- if read_attribute('_modification') == 'deleted'
621
- cms_data_for_revision(revision).present?
624
+ if ObjData === quick_modification
625
+ if data_from_cms == quick_modification
626
+ Modification::UNMODIFIED
627
+ else
628
+ Modification::EDITED
629
+ end
630
+ else
631
+ quick_modification
622
632
  end
623
633
  end
624
634
 
625
- def modification(revision=workspace.base_revision)
635
+ # similar to modification, but faster if you are only interested in
636
+ # "new" and "deleted".
637
+ # this method sometimes does not return a string, but an instance of
638
+ # ObjData instead. this indicates that the modification is either
639
+ # UNMODIFIED or EDITED. Which one it is can be determined by comparing
640
+ # the returned ObjData.
641
+ def quick_modification(revision)
626
642
  return Modification::UNMODIFIED unless revision
627
- return read_attribute('_modification') if revision == workspace.base_revision
628
-
629
- if deleted?(revision)
630
- Modification::DELETED
631
- elsif new?(revision)
632
- Modification::NEW
633
- else # Edited
634
- obj_data_from_revision = cms_data_for_revision(revision)
635
- if obj_data_from_revision.present?
636
- if data_from_cms == obj_data_from_revision
637
- Modification::UNMODIFIED
638
- else
639
- Modification::EDITED
640
- end
643
+
644
+ modification_attr = read_attribute('_modification')
645
+
646
+ return modification_attr if revision == workspace.base_revision
647
+
648
+ data_for_comparison = cms_data_for_revision(revision)
649
+
650
+ if data_for_comparison.present?
651
+ if modification_attr == 'deleted'
652
+ # Obj exists in comparison revision, but not in current
653
+ Modification::DELETED
641
654
  else
655
+ # Obj exists in both revisions, leave the actual comparions
656
+ # up to the caller
657
+ data_for_comparison
658
+ end
659
+ else
660
+ if modification_attr == "deleted"
661
+ # Obj does not exist in either revision
642
662
  Modification::UNMODIFIED
663
+ else
664
+ # Obj exists in current, but not in comparision revision
665
+ Modification::NEW
643
666
  end
644
667
  end
645
668
  end
@@ -883,8 +906,13 @@ module Scrivito
883
906
  private
884
907
 
885
908
  def cms_data_for_revision(revision)
886
- if revision
887
- CmsBackend.instance.find_obj_data_by(revision, "id", [id]).first.first
909
+ return nil unless revision
910
+
911
+ result = CmsBackend.instance.find_obj_data_by(revision, "id", [id])
912
+ obj_data = result.first.first
913
+
914
+ if obj_data && obj_data.value_of("_modification") != "deleted"
915
+ obj_data
888
916
  end
889
917
  end
890
918
 
@@ -1006,7 +1034,7 @@ module Scrivito
1006
1034
  .generate_widget_pool_changes(widget_pool_attributes)
1007
1035
  serialized_attributes
1008
1036
  else
1009
- serializer = AttributeSerializer.new
1037
+ serializer = AttributeSerializer.new(obj_attributes['_obj_class'] || name)
1010
1038
  serialized_attributes = serialize_obj_attributes(serializer, obj_attributes)
1011
1039
  serialized_attributes['_widget_pool'] =
1012
1040
  serialize_widget_pool_attributes(serializer, widget_pool_attributes)
@@ -83,11 +83,27 @@ class BasicWidget
83
83
  @attribute_cache = {}
84
84
  end
85
85
 
86
- def self.new(attributes = {})
86
+ #
87
+ # Creates a new {Scrivito::BasicWidget Widget}.
88
+ #
89
+ # @api public
90
+ #
91
+ # It also considers the defaults set via
92
+ # {Scrivito::AttributeContent::ClassMethods#default_for Widget.default_for}.
93
+ #
94
+ # @param [Hash] attributes for the new widget
95
+ # @param [Hash] context in which the object creating should happen
96
+ # @option context [Scrivito::User] :scrivito_user current visitor
97
+ # @return [Widget] the newly created {Scrivito::BasicWidget Widget}
98
+ #
99
+ # @see Scrivito::BasicWidget.new
100
+ # @see Scrivito::AttributeContent::ClassMethods#default_for
101
+ #
102
+ def self.new(attributes = {}, context = {})
87
103
  if obj_class = extract_obj_class_from_attributes(attributes)
88
- obj_class.new(attributes)
104
+ obj_class.new(attributes, context)
89
105
  else
90
- super
106
+ super(build_attributes_with_defaults(attributes, context))
91
107
  end
92
108
  end
93
109
 
@@ -6,11 +6,17 @@ class CacheMiddleware
6
6
  end
7
7
 
8
8
  def call(env)
9
- CmsBackend.instance.begin_caching
9
+ clear_caches
10
+
10
11
  @app.call(env)
11
- ensure
12
- CmsBackend.instance.end_caching
12
+ end
13
+
14
+
15
+ private
16
+
17
+ def clear_caches
13
18
  Workspace.cache.clear
19
+ CmsBackend.instance.clear_cache
14
20
  end
15
21
  end
16
22