scrivito_sdk 0.65.2 → 0.66.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scrivito/binary_redirect_controller.rb +19 -0
  3. data/app/controllers/scrivito/objs_controller.rb +6 -2
  4. data/app/controllers/scrivito/webservice_controller.rb +1 -1
  5. data/app/controllers/scrivito/workspaces_controller.rb +1 -7
  6. data/app/helpers/scrivito_helper.rb +115 -64
  7. data/app/views/scrivito/objs/is_outdated.json.jbuilder +1 -0
  8. data/app/views/scrivito/ui/index.html.erb +1 -0
  9. data/config/ca-bundle.crt +1 -1
  10. data/config/precedence_routes.rb +6 -4
  11. data/config/routes.rb +1 -1
  12. data/lib/assets/images/scrivito/source_too_large.png +0 -0
  13. data/lib/assets/images/scrivito/source_type_invalid.png +0 -0
  14. data/lib/assets/javascripts/scrivito_ui.js +2975 -357
  15. data/lib/assets/stylesheets/scrivito_sdk.css +1 -3
  16. data/lib/assets/stylesheets/scrivito_ui.css +1 -3
  17. data/lib/generators/scrivito/install/templates/scrivito/migrate/install_scrivito_migration.rb +0 -2
  18. data/lib/generators/scrivito/migration/templates/migration.erb +6 -8
  19. data/lib/scrivito/attribute_content.rb +16 -52
  20. data/lib/scrivito/attribute_deserializer.rb +3 -5
  21. data/lib/scrivito/attribute_serializer.rb +42 -45
  22. data/lib/scrivito/backend/content_state_node.rb +71 -19
  23. data/lib/scrivito/backend/index.rb +20 -0
  24. data/lib/scrivito/backend/obj_data_cache.rb +157 -31
  25. data/lib/scrivito/backend/obj_data_from_rest.rb +1 -0
  26. data/lib/scrivito/backend/obj_load.rb +10 -6
  27. data/lib/scrivito/backend/obj_query.rb +2 -1
  28. data/lib/scrivito/backend/parent_path_index.rb +5 -1
  29. data/lib/scrivito/backend/path_index.rb +5 -1
  30. data/lib/scrivito/backend/permalink_index.rb +18 -10
  31. data/lib/scrivito/basic_obj.rb +62 -42
  32. data/lib/scrivito/basic_widget.rb +5 -5
  33. data/lib/scrivito/binary.rb +154 -13
  34. data/lib/scrivito/binary_param_verifier.rb +31 -0
  35. data/lib/scrivito/binary_rewrite.rb +35 -0
  36. data/lib/scrivito/cache_middleware.rb +5 -0
  37. data/lib/scrivito/child_list_tag.rb +3 -3
  38. data/lib/scrivito/cms_backend.rb +89 -57
  39. data/lib/scrivito/cms_data_cache.rb +21 -4
  40. data/lib/scrivito/cms_dispatch_controller.rb +13 -0
  41. data/lib/scrivito/cms_field_tag.rb +16 -3
  42. data/lib/scrivito/cms_rest_api.rb +2 -2
  43. data/lib/scrivito/cms_routing.rb +41 -18
  44. data/lib/scrivito/configuration.rb +22 -0
  45. data/lib/scrivito/content_state.rb +1 -9
  46. data/lib/scrivito/content_state_caching.rb +0 -20
  47. data/lib/scrivito/controller_actions.rb +3 -2
  48. data/lib/scrivito/controller_runtime.rb +16 -8
  49. data/lib/scrivito/dialog_size_helper.rb +11 -0
  50. data/lib/scrivito/diff.rb +0 -2
  51. data/lib/scrivito/editing_context.rb +24 -13
  52. data/lib/scrivito/editing_context_middleware.rb +6 -1
  53. data/lib/scrivito/errors.rb +18 -0
  54. data/lib/scrivito/image_tag.rb +55 -0
  55. data/lib/scrivito/link.rb +12 -0
  56. data/lib/scrivito/membership_collection.rb +3 -2
  57. data/lib/scrivito/migrations/cms_backend.rb +6 -13
  58. data/lib/scrivito/migrations/migrator.rb +2 -23
  59. data/lib/scrivito/migrations/workspace_lock.rb +23 -11
  60. data/lib/scrivito/named_link.rb +1 -1
  61. data/lib/scrivito/obj_collection.rb +1 -1
  62. data/lib/scrivito/obj_data_from_service.rb +0 -7
  63. data/lib/scrivito/obj_search_enumerator.rb +2 -2
  64. data/lib/scrivito/parent_path.rb +9 -0
  65. data/lib/scrivito/routing_helper.rb +2 -2
  66. data/lib/scrivito/user.rb +2 -2
  67. data/lib/scrivito/user_definition.rb +2 -2
  68. data/lib/scrivito/widget_garbage_collection.rb +2 -2
  69. data/lib/scrivito/widget_tag.rb +6 -3
  70. data/lib/scrivito/workspace.rb +60 -41
  71. data/lib/scrivito/workspace_data.rb +40 -4
  72. data/lib/scrivito/workspace_data_from_service.rb +0 -10
  73. data/lib/scrivito_sdk.rb +0 -16
  74. metadata +12 -12
  75. data/lib/scrivito/attribute.rb +0 -152
  76. data/lib/scrivito/attribute_collection.rb +0 -66
  77. data/lib/scrivito/attribute_definition_migrator.rb +0 -188
  78. data/lib/scrivito/cms_rest_api/legacy_attribute_serializer.rb +0 -105
  79. data/lib/scrivito/image_tag_helper.rb +0 -46
  80. data/lib/scrivito/obj_class.rb +0 -258
  81. data/lib/scrivito/obj_class_collection.rb +0 -71
  82. data/lib/scrivito/obj_class_data.rb +0 -37
  83. data/lib/tasks/migrate_attribute_definitions.rake +0 -6
@@ -21,13 +21,13 @@ class BasicWidget
21
21
  # to ensure that only a special type of widget can be added to a specific
22
22
  # container. An example for this is a +TabGroupWidget+ that should
23
23
  # contain widgets of the +TabWidget+ type only, and, vice versa, a +TabWidget+
24
- # should only be contained in a +TabGroupWidget+. A value of +nil+ means that
24
+ # should only be contained in a +TabGroupWidget+. A value of +NilClass+ means that
25
25
  # no additional restrictions are applied.
26
26
  #
27
27
  # This method only further restricts the list of valid classes defined
28
28
  # by means of {AttributeContent#valid_widget_classes_for}.
29
29
  #
30
- # @return [Array<String, Symbol, Class>, nil]
30
+ # @return [NilClass, Array<Class>]
31
31
  #
32
32
  # @example
33
33
  # class TabGroupWidget < Widget
@@ -46,8 +46,9 @@ class BasicWidget
46
46
  end
47
47
 
48
48
  def self.valid_inside_container?(container_class)
49
- valid_container_classes.nil? ||
50
- valid_container_classes.map(&:to_s).include?(container_class.name)
49
+ computed_classes = assert_classes(valid_container_classes, '.valid_container_classes')
50
+
51
+ computed_classes.nil? || computed_classes.include?(container_class)
51
52
  end
52
53
 
53
54
  attr_accessor :container, :container_attribute_name
@@ -202,7 +203,6 @@ class BasicWidget
202
203
  # Please use {Scrivito::BasicObj#revert Obj#revert} to restore them.
203
204
  #
204
205
  # @raise [ScrivitoError] If the current workspace is +published+.
205
- # @raise [ScrivitoError] If the current workspace is the +rtc+ workspace.
206
206
  # @raise [ScrivitoError] If the +Widget+ is +new+.
207
207
  # @raise [ScrivitoError] If the +Widget+ is +deleted+.
208
208
  #
@@ -4,11 +4,13 @@ module Scrivito
4
4
  # The Binary class represents the data stored in a binary attribute of an Obj
5
5
  # or Widget
6
6
  class Binary
7
- attr_reader :id
7
+ attr_reader :id, :transformation_definition
8
8
 
9
- def initialize(id, public_content)
9
+ def initialize(id, is_public, transformation_definition = nil, original = nil)
10
10
  @id = id
11
- @public_content = public_content
11
+ @is_public = !!is_public
12
+ @transformation_definition = transformation_definition
13
+ @original = original
12
14
  end
13
15
 
14
16
  # @api public
@@ -17,21 +19,35 @@ class Binary
17
19
  # been published yet.
18
20
  # @return [Boolean]
19
21
  def private?
20
- !public_content?
22
+ !@is_public
23
+ end
24
+
25
+ def public?
26
+ @is_public
21
27
  end
22
28
 
23
29
  # @api public
24
- # The URL where this binary data is accessible and can be downloaded using an
25
- # HTTP GET request. Note that urls for private content will have an
26
- # expiration time in order to protect them.
27
- # Therefore the url returned here should be accessed immediately after it has been returned (i.e. within a couple of minutes).
28
- # When accessed after they have expired, an error will occur.
29
- # The urls should not be used for long-term-storage (i.e. they are no longer accessible hours or days after they have been generated).
30
- # @return [String] the URL under which this content is available
30
+ # The URL for accessing the binary data and downloading it using an HTTP GET request.
31
+ #
32
+ # @note URLs for private content have an expiration time in order to protect them.
33
+ # Therefore, the URL should be accessed immediately after it has been returned
34
+ # (i.e. within a couple of minutes). Accessing it after expiration causes an error.
35
+ #
36
+ # The URLs should not be used for long-term storage since they are no longer
37
+ # accessible hours or days after they have been generated.
38
+ # @return [String] the URL at which this content is available.
31
39
  def url
32
40
  find_url('get')
33
41
  end
34
42
 
43
+ def url_from_cache
44
+ blob_data = CmsBackend.instance
45
+ .find_blob_data_from_cache(id, access_type, 'get', transformation_definition)
46
+ if blob_data
47
+ blob_data['url']
48
+ end
49
+ end
50
+
35
51
  # @api public
36
52
  # the filename of this binary data, for example "my_image.jpg"
37
53
  # @return [String] the filename of binary
@@ -53,6 +69,121 @@ class Binary
53
69
  headers[:content_length].to_i
54
70
  end
55
71
 
72
+ #
73
+ # Returns a transformed {Scrivito::Binary}.
74
+ #
75
+ # @api beta
76
+ #
77
+ # Calling this method will not change the binary, but instead return a copy of it,
78
+ # transformed using the +definition+.
79
+ #
80
+ # If the original binary has already been transformed, then the returned binary will be a
81
+ # combination of the transformations. Thus the transformations can be chained (see examples).
82
+ #
83
+ # The transformed data is calculated "lazily", so calling {Scrivito::Binary#transform} will not
84
+ # trigger any calculation. The calculation will be triggered only if data is accessed, for example
85
+ # via {Scrivito::Binary#url}.
86
+ #
87
+ # @param [Hash] definition transformation definition
88
+ #
89
+ # @option definition [Fixnum,String] :width The width in pixels of the output image. Must be a
90
+ # positive integer.
91
+ #
92
+ # If only this dimension is specified, the other dimension will be calculated automatically to
93
+ # preserve the aspect ratio of the input image.
94
+ #
95
+ # If the +fit+ parameter is set to +:clip+, then the actual output one of width and height may
96
+ # be equal to or less than the dimensions you specify to prevent distortion.
97
+ #
98
+ # If neither +width+ nor +height+ is given, the width and height of the input image
99
+ # will be used.
100
+ #
101
+ # The maximum output image size width + height = 4096 pixels. The given width and height may be
102
+ # adjusted to accomodate this limit. The output image will never be larger than the source
103
+ # image, i.e. the given width and height may be adjusted to keep the output image from exceeding
104
+ # the dimensions of the input image.
105
+ #
106
+ # When the given width and height are adjusted, the aspect ratio is preserved.
107
+ #
108
+ # @option definition [Fixnum,String] :height The height in pixels of the output image. Must be a
109
+ # positive integer.
110
+ #
111
+ # If only this dimension is specified, the other dimension will be calculated automatically to
112
+ # preserve the aspect ratio of the input image.
113
+ #
114
+ # If the +fit+ parameter is set to +:clip+, then the actual output one of width and height may
115
+ # be equal to or less than the dimensions you specify to prevent distortion.
116
+ #
117
+ # If neither +width+ nor +height+ is given, the width and height of the input image
118
+ # will be used.
119
+ #
120
+ # The maximum output image size width + height = 4096 pixels. The given width and height may be
121
+ # adjusted to accomodate this limit. The output image will never be larger than the source
122
+ # image, i.e. the given width and height may be adjusted to keep the output image from exceeding
123
+ # the dimensions of the input image.
124
+ #
125
+ # When the given width and height are adjusted, the aspect ratio is preserved.
126
+ #
127
+ # @option definition [Symbol,String] :fit Controls how the tranformed image is fitted to the
128
+ # given width and heigth. Valid values are +:clip+ and +:crop+. The default value is +:clip+.
129
+ #
130
+ # If set to +:clip+ it resizes the image to fit within the width and height boundaries without
131
+ # cropping or distorting the image. The resulting image is assured to match one of the
132
+ # constraining dimensions, while the other dimension is altered to maintain the same aspect
133
+ # ratio of the input image.
134
+ #
135
+ # If set to +:crop+ it resizes the image to fill the given width and height and preserves the
136
+ # aspect ration by cropping any excess image data. The resulting image will match both the given
137
+ # width and height without distorting the image. The crop is done centered, i.e. the center of
138
+ # the image is preserved.
139
+ #
140
+ # @option definition [Fixnum,String] :quality Controls the output quality of lossy file formats.
141
+ # Applies when the format is +"jpg"+. Valid values are in the range from +0+ to +100+.
142
+ # The default value is +75+.
143
+ #
144
+ # @return [Scrivito::Binary] transformed binary
145
+ #
146
+ # @example Crop image to fit into 50 x 50 pixel square
147
+ # @obj.blob.transform(width: 50, height: 50, fit: :crop)
148
+ #
149
+ # @example Convert image to a low quality JPEG
150
+ # @obj.blob.transform(quality: 25)
151
+ #
152
+ # @example Combine the both transformations
153
+ # @obj.blob.transform(width: 50, height: 50, fit: :crop).transform(quality: 25)
154
+ #
155
+ def transform(definition)
156
+ self.class.new(id, public?, (transformation_definition || {}).merge(definition), original)
157
+ end
158
+
159
+ #
160
+ # Returns whether a binary is transformed.
161
+ # @api beta
162
+ #
163
+ def transformed?
164
+ !!transformation_definition
165
+ end
166
+
167
+ #
168
+ # Returns the transformation original of a binary.
169
+ #
170
+ # @api beta
171
+ #
172
+ # If a binary is a result of a transformation, then its original binary is returned.
173
+ # Otherwise +self+.
174
+ #
175
+ # @return [Scrivito::Binary] original binary
176
+ #
177
+ # @example
178
+ # @obj.blob.id # => "abc123"
179
+ # @obj.blob.original.id # => "abc123"
180
+ # @obj.blob.transform(width: 50).original.id # => "abc123"
181
+ # @obj.blob.transform(width: 50).transform(height: 50).original.id # => "abc123"
182
+ #
183
+ def original
184
+ @original || self
185
+ end
186
+
56
187
  private
57
188
 
58
189
  def public_content?
@@ -64,11 +195,21 @@ class Binary
64
195
  end
65
196
 
66
197
  def find_url(verb)
67
- CmsBackend.instance.find_blob_data(@id, access_type, verb)['url']
198
+ CmsBackend.instance.find_blob_data(id, access_type, verb, transformation_definition)['url']
199
+ rescue ClientError => e
200
+ case e.backend_code
201
+ when 'binary.unprocessable.image.transform.source_too_large',
202
+ 'binary.unprocessable.image.transform.source_type_invalid'
203
+ raise TransformationSourceError.new(e.message, e.backend_code)
204
+ when 'binary.unprocessable.image.transform.invalid_config'
205
+ raise TransformationDefinitionError.new(e.message, e.backend_code)
206
+ else
207
+ raise e
208
+ end
68
209
  end
69
210
 
70
211
  def access_type
71
- public_content? ? 'public_access' : 'private_access'
212
+ public? ? 'public_access' : 'private_access'
72
213
  end
73
214
  end
74
215
 
@@ -0,0 +1,31 @@
1
+ module Scrivito
2
+
3
+ module BinaryParamVerifier
4
+ InvalidSignature = Class.new(StandardError)
5
+
6
+ class << self
7
+ def verify(params)
8
+ params = message_verifier.verify(params)
9
+ expires = DateAttribute.parse(params['expires'])
10
+ raise InvalidSignature if expires && expires < Time.zone.now
11
+ Binary.new(params['binary_id'], expires.nil?, params['transformation_definition'])
12
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
13
+ raise InvalidSignature
14
+ end
15
+
16
+ def generate(binary)
17
+ params = {binary_id: binary.id, transformation_definition: binary.transformation_definition}
18
+ params[:expires] = DateAttribute.serialize(Time.zone.now + 1.hour) if binary.private?
19
+ message_verifier.generate(params)
20
+ end
21
+
22
+ private
23
+
24
+ def message_verifier
25
+ ActiveSupport::MessageVerifier.new(
26
+ Rails.application.secrets.secret_key_base, serializer: JSON)
27
+ end
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,35 @@
1
+ module Scrivito
2
+ #
3
+ # This module lets you change the URL of a binary CMS object for the purpose of
4
+ # fetching the binary data from a different location (e.g. via a proxy or from a mirror).
5
+ #
6
+ #
7
+ # @api public
8
+ #
9
+ module BinaryRewrite
10
+ def self.call(request, url)
11
+ change_url_callback = request.env["SCRIVITO_BINARY_URL_CALLBACK"]
12
+ url = change_url_callback.call(url) if change_url_callback
13
+ url
14
+ end
15
+ #
16
+ # You can, for example, have the URLs of images, etc., point to your desired
17
+ # domain instead of to the CDN Scrivito uses.
18
+ #
19
+ # @api public
20
+ #
21
+ # @param request [ActionDispatch::Request]
22
+ # @param block [Proc] block change default CDN url of current binary obj
23
+ #
24
+ # @example
25
+ # Scrivito::BinaryRewrite.enable_for(request) do |binary_url|
26
+ # binary_url.sub(
27
+ # "https://scrivito-public-cdn.s3-eu-west-1.amazonaws.com/",
28
+ # "https://my.reverse.proxy.com/")
29
+ # end
30
+ #
31
+ def self.enable_for(request, &block)
32
+ request.env["SCRIVITO_BINARY_URL_CALLBACK"] = block
33
+ end
34
+ end
35
+ end
@@ -7,6 +7,7 @@ class CacheMiddleware
7
7
 
8
8
  def call(env)
9
9
  clear_caches
10
+ clear_runtime
10
11
 
11
12
  @app.call(env)
12
13
  end
@@ -14,6 +15,10 @@ class CacheMiddleware
14
15
 
15
16
  private
16
17
 
18
+ def clear_runtime
19
+ Scrivito::LogSubscriber.reset_runtime
20
+ end
21
+
17
22
  def clear_caches
18
23
  Workspace.cache.clear
19
24
  CmsBackend.instance.clear_cache
@@ -65,9 +65,9 @@ class ChildListTag < Struct.new(:tag_name, :obj, :field_name, :view)
65
65
  # @return [String] The rendered html tag
66
66
  #
67
67
  # @example Render a <div> tag containing the text "random content" and assigns the tag a css class called +very_important+.
68
- # <%= list.tag :div, class: "very_important" do %>
69
- # random content
70
- # <% end %>
68
+ # list.tag(:div, class: "very_important") do
69
+ # "random content"
70
+ # end
71
71
  #
72
72
  def tag(tag_name, html_options = {}, &block)
73
73
  raise '"list.tag" can only be called once per iteration!' if @rendered
@@ -90,30 +90,36 @@ module Scrivito
90
90
 
91
91
  def find_workspace_data_by_id(id)
92
92
  if die_content_service
93
- from_csid = CmsDataCache.read_workspace_csid(id)
93
+ begin
94
+ cached_workspace_state = CmsDataCache.read_workspace_state(id)
94
95
 
95
- changes = CmsRestApi.get("/workspaces/#{id}/changes", from: from_csid)
96
+ cached_csid = cached_workspace_state.try(:first)
97
+ cached_workspace_data_tag = cached_workspace_state.try(:second)
96
98
 
97
- objs = changes["objs"]
98
- if objs.present?
99
- if objs != "*"
100
- last_state = Backend::ContentStateNode.find(from_csid)
99
+ changes = CmsRestApi.get("/workspaces/#{id}/changes", from: cached_csid)
101
100
 
102
- successor_state = changes["to"]
103
- current_state = last_state.create_successor(successor_state, objs)
104
- end
101
+ update_obj_cache(id, cached_csid, changes)
105
102
 
106
- # TODO what if workspace data is missing?
107
- # have the backend team include a new key `current`
108
- current_csid = changes["workspace"]["content_state_id"]
109
- CmsDataCache.write_workspace_csid(id, current_csid)
110
- end
103
+ workspace_data, workspace_data_tag = update_workspace_cache(
104
+ id, cached_workspace_data_tag, changes["workspace"])
111
105
 
112
- # TODO what if workspace data is missing?
113
- # implement workspace data caching
114
- workspace_data = changes["workspace"]
106
+ current_csid = changes["current"]
107
+ current_workspace_state = [current_csid, workspace_data_tag]
108
+
109
+ if current_workspace_state != cached_workspace_state
110
+ CmsDataCache.write_workspace_state(id, current_workspace_state)
111
+ end
115
112
 
116
- return WorkspaceData.new(workspace_data)
113
+ return WorkspaceData.new(workspace_data.merge(
114
+ "content_state_id" => current_csid))
115
+
116
+ rescue Scrivito::ClientError => client_error
117
+ if client_error.http_code == 404
118
+ return nil
119
+ else
120
+ raise
121
+ end
122
+ end
117
123
  end
118
124
 
119
125
  workspace_data_from_cache = WorkspaceDataFromService.find_from_cache(id)
@@ -168,17 +174,22 @@ module Scrivito
168
174
  end
169
175
  end
170
176
 
171
- def find_blob_data(id, access, verb)
172
- id = Addressable::URI.normalize_component(id, Addressable::URI::CharacterClasses::UNRESERVED)
173
- if blob_data = fetch_blob_data_from_cache(id, access, verb)
177
+ def find_blob_data(id, access, verb, transformation_definition)
178
+ if blob_data = find_blob_data_from_cache(id, access, verb, transformation_definition)
174
179
  blob_data
175
180
  else
176
- blob_datas = request_blob_datas_from_backend(id)
177
- store_blob_datas_in_cache(id, blob_datas)
181
+ id = normalize_blob_id(id)
182
+ blob_datas = request_blob_datas_from_backend(id, transformation_definition)
183
+ store_blob_datas_in_cache(id, transformation_definition, blob_datas)
178
184
  blob_datas[access][verb]
179
185
  end
180
186
  end
181
187
 
188
+ def find_blob_data_from_cache(id, access, verb, transformation_definition)
189
+ cache_key = blob_data_cache_key(normalize_blob_id(id), access, verb, transformation_definition)
190
+ CmsDataCache.cache.read(cache_key)
191
+ end
192
+
182
193
  def find_blob_metadata(id, url)
183
194
  if blob_metadata = fetch_blob_metadata_from_cache(id)
184
195
  blob_metadata
@@ -190,14 +201,24 @@ module Scrivito
190
201
  end
191
202
 
192
203
  def search_objs(workspace, params)
204
+ cache_index = 'search'
205
+ cache_key = params.to_param
206
+
193
207
  if die_content_service
194
- # TODO caching
195
- return request_search_result_from_backend(workspace, params)
208
+ cache = Backend::ObjDataCache.view_for_revision(workspace.revision)
209
+
210
+ if hit = cache.read_index(cache_index, cache_key)
211
+ return hit
212
+ end
213
+
214
+ result = request_search_result_from_backend(workspace, params)
215
+
216
+ cache.write_index(cache_index, cache_key, result)
217
+
218
+ return result
196
219
  end
197
220
 
198
221
  content_state = workspace.revision.content_state
199
- cache_index = 'search'
200
- cache_key = params.to_param
201
222
 
202
223
  if result = fetch_search_result_from_cache(content_state, cache_index, cache_key)
203
224
  result
@@ -208,34 +229,39 @@ module Scrivito
208
229
  end
209
230
  end
210
231
 
211
- def find_obj_class_data_by_name(revision, name)
212
- find_all_obj_class_data(revision).find { |obj_class_data| obj_class_data.name == name }
213
- end
232
+ private
214
233
 
215
- def find_all_obj_class_data(revision)
216
- content_state = revision.content_state
217
- if obj_classes_data = fetch_obj_classes_data_from_cache(content_state)
218
- obj_classes_data
234
+ def update_workspace_cache(id, cached_data_tag, changed_workspace)
235
+ if changed_workspace
236
+ workspace_data = changed_workspace
219
237
  else
220
- request_obj_classes_data_from_backend(revision).tap do |obj_classes_data|
221
- store_obj_classes_data_in_cache(content_state, obj_classes_data)
238
+ cached_workspace_data = CmsDataCache.read_data_from_tag(cached_data_tag)
239
+
240
+ if cached_workspace_data
241
+ workspace_data = cached_workspace_data
242
+ workspace_data_tag = cached_data_tag
243
+ else
244
+ workspace_data = CmsRestApi.get("/workspaces/#{id}")
222
245
  end
223
246
  end
224
- end
225
247
 
226
- private
248
+ workspace_data_tag ||= CmsDataCache.write_data_to_tag(workspace_data)
227
249
 
228
- def fetch_obj_classes_data_from_cache(content_state)
229
- ContentStateCaching.find_obj_classes_data(content_state) if caching?
250
+ [workspace_data, workspace_data_tag]
230
251
  end
231
252
 
232
- def request_obj_classes_data_from_backend(revision)
233
- response = CmsRestApi.get("revisions/#{revision.id}/obj_classes", include_inactive: true)
234
- response['results'].map { |raw_data| ObjClassData.new(raw_data) }
235
- end
253
+ def update_obj_cache(workspace_id, cached_csid, changes)
254
+ objs = changes["objs"]
255
+ if objs.present? && objs != "*"
256
+ last_state = Backend::ContentStateNode.find(cached_csid)
257
+ changes_index = Backend::ObjDataCache.changes_index_from(objs)
258
+ successor = last_state.create_successor(changes["to"], changes_index)
236
259
 
237
- def store_obj_classes_data_in_cache(content_state, obj_classes_data)
238
- ContentStateCaching.store_obj_classes_data(content_state, obj_classes_data) if caching?
260
+ cache = Backend::ObjDataCache.view_for_workspace(workspace_id, successor)
261
+ changes_index.each do |id, tag|
262
+ cache.write_obj_tag(id, tag)
263
+ end
264
+ end
239
265
  end
240
266
 
241
267
  def fetch_search_result_from_cache(content_state, cache_index, cache_key)
@@ -250,27 +276,33 @@ module Scrivito
250
276
  content_state.save_obj_data(cache_index, cache_key, result) if caching?
251
277
  end
252
278
 
253
- def fetch_blob_data_from_cache(id, access, verb)
254
- CmsDataCache.cache.read(blob_data_cache_key(id, access, verb))
255
- end
256
-
257
- def request_blob_datas_from_backend(id)
279
+ def request_blob_datas_from_backend(id, transformation_definition)
258
280
  @query_counter += 1
259
- CmsRestApi.get("blobs/#{id}")
281
+ if transformation_definition
282
+ CmsRestApi.get("blobs/#{id}/transform", transformation: transformation_definition)
283
+ else
284
+ CmsRestApi.get("blobs/#{id}")
285
+ end
260
286
  end
261
287
 
262
- def store_blob_datas_in_cache(id, blob_datas)
288
+ def store_blob_datas_in_cache(id, transformation_definition, blob_datas)
263
289
  %w[public_access private_access].each do |access|
264
290
  %w[get head].each do |verb|
265
291
  blob_data = blob_datas[access][verb]
266
- CmsDataCache.cache.write(blob_data_cache_key(id, access, verb),
267
- blob_data, blob_data['maxage'])
292
+ cache_key = blob_data_cache_key(id, access, verb, transformation_definition)
293
+ CmsDataCache.cache.write(cache_key, blob_data, blob_data['maxage'])
268
294
  end
269
295
  end
270
296
  end
271
297
 
272
- def blob_data_cache_key(id, access, verb)
273
- "blob_data/#{id}/#{access}/#{verb}"
298
+ def blob_data_cache_key(id, access, verb, transformation_definition)
299
+ cache_key = "blob_data/#{id}/#{access}/#{verb}"
300
+ cache_key << "/#{transformation_definition.to_query}" if transformation_definition
301
+ cache_key
302
+ end
303
+
304
+ def normalize_blob_id(id)
305
+ Addressable::URI.normalize_component(id, Addressable::URI::CharacterClasses::UNRESERVED)
274
306
  end
275
307
 
276
308
  def fetch_blob_metadata_from_cache(id)