scrivito_sdk 0.60.0 → 0.65.0.rc1

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