scrivito_sdk 0.65.2 → 0.66.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -23,6 +23,26 @@ module Index
23
23
  abstract_base_method
24
24
  end
25
25
 
26
+ def update(key, cached_result, change_index)
27
+ updated = Set.new(cached_result)
28
+
29
+ change_index.each do |id, data|
30
+ value = extract_update_value_from_data(data)
31
+
32
+ if value == key
33
+ updated.add(id)
34
+ else
35
+ updated.delete(id)
36
+ end
37
+ end
38
+
39
+ updated.to_a
40
+ end
41
+
42
+ def extract_update_value_from_data(data)
43
+ abstract_base_method
44
+ end
45
+
26
46
  def group_by(keys, obj_datas)
27
47
  return [obj_datas] if keys.length == 1
28
48
 
@@ -1,63 +1,189 @@
1
1
  module Scrivito
2
2
  module Backend
3
3
 
4
- class ObjDataCache < Struct.new(:workspace_id, :base)
4
+ module ObjDataCache
5
+
6
+ # in the cache, missing objs (no obj exists with the given id) are
7
+ # represented as an empty hash to distinguish them from cache misses.
8
+ # nil => cache miss for given id
9
+ # {} => cache hit for given id, no obj with this id exists
10
+ NONEXISTENT_OBJ = {}
11
+
12
+ def self.convert_from_backend(obj_data)
13
+ if obj_data.blank? || obj_data["_deleted"]
14
+ ObjDataCache::NONEXISTENT_OBJ
15
+ else
16
+ obj_data
17
+ end
18
+ end
5
19
 
6
20
  def self.view_for_revision(revision)
7
- cache = new(revision.workspace.id, revision.base)
8
-
9
- cache.view_from(revision.content_state_node)
21
+ if revision.base
22
+ SimpleCacheView.new(revision.id)
23
+ else
24
+ view_for_workspace(revision.workspace.id, revision.content_state_node)
25
+ end
10
26
  end
11
27
 
12
- def view_from(content_state_node)
13
- CacheView.new(cache_id, content_state_node)
28
+ def self.view_for_workspace(workspace_id, content_state_node)
29
+ IncrementalCacheView.new(workspace_id, content_state_node)
14
30
  end
15
31
 
16
- private
32
+ # builds a "change index", i.e. a hash mapping from `_id` to
33
+ # CmsDataCache-Tags that reference the data of the Obj
34
+ def self.changes_index_from(objs)
35
+ obj_ids = objs.map { |obj| obj["_id"] || obj["_deleted"] }
17
36
 
18
- def cache_id
19
- suffix = base ? "-B" : ""
20
- "#{workspace_id}#{suffix}"
37
+ tags = objs.
38
+ map(&method(:convert_from_backend)).
39
+ map(&CmsDataCache.method(:write_data_to_tag))
40
+
41
+ Hash[obj_ids.zip(tags)]
21
42
  end
22
43
 
23
- class CacheView < Struct.new(:cache_id, :viewed_state)
44
+
45
+ class SimpleCacheView < Struct.new(:cache_id)
46
+
47
+ def read_obj(id)
48
+ if tag = CmsDataCache.read_obj_data_rest(cache_id, "id", id)
49
+ CmsDataCache.read_data_from_tag(tag)
50
+ end
51
+ end
52
+
53
+ def write_obj(id, data)
54
+ write_cache("id", id, CmsDataCache.write_data_to_tag(data))
55
+ end
24
56
 
25
57
  def read_index(index, key)
58
+ index_access
59
+ end
60
+
61
+ def write_index(index, key, data)
62
+ index_access
63
+ end
64
+
65
+ private
66
+
67
+ def index_access
68
+ # SimpleCacheView is currently only used for base revision that do not
69
+ # support index queries.
70
+ raise InternalError
71
+ end
72
+
73
+ def write_cache(index, key, data)
74
+ raise InternalError unless data
75
+
76
+ CmsDataCache.write_obj_data_rest(cache_id, index, key, data)
77
+ end
78
+
79
+ end
80
+
81
+ class IncrementalCacheView < Struct.new(:cache_id, :viewed_state)
82
+
83
+ def read_index(index, key, &update_function)
26
84
  if hit = CmsDataCache.read_obj_data_rest(cache_id, index, key)
27
- stored_at = hit.first
28
85
 
29
- hit.second if viewed_state.content_state_id == stored_at
86
+ cached_at = hit.first
87
+ cached_result = hit.second
88
+
89
+ # if no update needed, just return
90
+ return cached_result if cached_at == viewed_state.content_state_id
91
+
92
+ updated_result = update_result(
93
+ key, cached_result, cached_at, &update_function)
94
+
95
+ return nil unless updated_result
96
+
97
+ if cached_at != csid_to_write_to
98
+ write_cache(index, key, updated_result)
99
+ end
100
+
101
+ updated_result
30
102
  end
31
103
  end
32
104
 
33
105
  def write_index(index, key, data)
34
- CmsDataCache.write_obj_data_rest(
35
- cache_id, index, key, [viewed_state.content_state_id, data])
106
+ write_cache(index, key, data)
36
107
  end
37
108
 
38
109
  def read_obj(id)
39
110
  if hit = CmsDataCache.read_obj_data_rest(cache_id, "id", id)
40
- stored_at, data = hit
41
-
42
- obj_in_changes = viewed_state.obj_from_changes(id, stored_at)
43
-
44
- case obj_in_changes
45
- when ContentStateNode::CACHE_MISS
46
- # could not reconstruct all changes since `stored_at`
47
- # so we don't know if data is still valid.
48
- nil
49
- when ContentStateNode::NOT_FOUND
50
- # there were no relevant changes, so the data is still valid
51
- data
52
- else
53
- # the changes revealed a newer version, return it instead
54
- obj_in_changes
111
+ cached_at, cached_tag = hit
112
+
113
+ tag_in_changes = viewed_state.obj_from_changes(id, cached_at)
114
+
115
+ updated_tag =
116
+ case tag_in_changes
117
+ when ContentStateNode::CACHE_MISS
118
+ # could not reconstruct all changes since `cached_at`
119
+ # so we don't know if data is still valid.
120
+ nil
121
+ when ContentStateNode::NOT_FOUND
122
+ # there were no relevant changes, so the data is still valid
123
+ cached_tag
124
+ else
125
+ # the changes revealed a newer version, return it instead
126
+ tag_in_changes
127
+ end
128
+
129
+ return nil unless updated_tag
130
+
131
+ if cached_at != csid_to_write_to
132
+ write_cache("id", id, updated_tag)
55
133
  end
134
+
135
+ CmsDataCache.read_data_from_tag(updated_tag)
56
136
  end
57
137
  end
58
138
 
59
139
  def write_obj(id, data)
60
- write_index("id", id, data)
140
+ write_obj_tag(id, CmsDataCache.write_data_to_tag(data))
141
+ end
142
+
143
+ def write_obj_tag(id, tag)
144
+ write_cache("id", id, tag)
145
+ end
146
+
147
+ private
148
+
149
+ def write_cache(index, key, data)
150
+ raise InternalError unless data
151
+
152
+ CmsDataCache.write_obj_data_rest(
153
+ cache_id, index, key, [csid_to_write_to, data])
154
+ end
155
+
156
+ # tries to update an outdated cache result using the changes recorded in the
157
+ # ContentStateNode history.
158
+ # returns `nil` (= cache-miss) if update is not possible.
159
+ def update_result(key, cached_result, cached_at, &update_function)
160
+ # if index does not support update, treat as cache-miss
161
+ return nil unless update_function
162
+
163
+ change_index = viewed_state.change_index_for(cached_at)
164
+
165
+ return nil if change_index == ContentStateNode::CACHE_MISS
166
+
167
+ expanded_index = expand_index(change_index)
168
+
169
+ # if the data of changed objs was evicted, treat as cache-miss
170
+ return nil if !expanded_index
171
+
172
+ update_function.call(key, cached_result, expanded_index)
173
+ end
174
+
175
+ # accepts a "change index" (id --> tag), looks up each
176
+ # tag in the cache and return an "expanded index" (id --> data).
177
+ # return `nil` if any tag cannot be found in the cache.
178
+ def expand_index(change_index)
179
+ # using "merge(self)" to map over a hash
180
+ change_index.merge(change_index) do |id, tag|
181
+ CmsDataCache.read_data_from_tag(tag) or return nil
182
+ end
183
+ end
184
+
185
+ def csid_to_write_to
186
+ viewed_state.next_stable_node.content_state_id
61
187
  end
62
188
 
63
189
  end
@@ -3,6 +3,7 @@ module Backend
3
3
 
4
4
  class ObjDataFromRest < ObjData
5
5
  def initialize(data)
6
+ raise InternalError unless Hash === data
6
7
  @data = data
7
8
  end
8
9
 
@@ -20,12 +20,14 @@ class << self
20
20
  if missing_ids.blank?
21
21
  results_from_cache
22
22
  else
23
- results_from_backend = revision.obj_mget_request(missing_ids)
23
+ raw_results = revision.obj_mget_request(missing_ids)
24
24
 
25
- results_from_backend.each do |result|
26
- if result
27
- cache.write_obj(result["_id"], result)
28
- end
25
+ results_from_backend = raw_results.map do |result|
26
+ ObjDataCache.convert_from_backend(result)
27
+ end
28
+
29
+ results_from_backend.each_with_index do |result, index|
30
+ cache.write_obj(missing_ids[index], result)
29
31
  end
30
32
 
31
33
  results_from_cache.map do |result|
@@ -34,7 +36,9 @@ class << self
34
36
  end
35
37
 
36
38
  overall_results.map do |raw_data|
37
- Backend::ObjDataFromRest.new(raw_data) if raw_data
39
+ if raw_data != ObjDataCache::NONEXISTENT_OBJ
40
+ Backend::ObjDataFromRest.new(raw_data)
41
+ end
38
42
  end
39
43
  end
40
44
 
@@ -8,8 +8,9 @@ class << self
8
8
  cache = Backend::ObjDataCache.view_for_revision(revision)
9
9
 
10
10
  missing_keys = []
11
+
11
12
  ids_from_cache = keys.map do |key|
12
- result = cache.read_index(index.id, key)
13
+ result = cache.read_index(index.id, key, &index.method(:update))
13
14
 
14
15
  missing_keys << key unless result
15
16
 
@@ -11,7 +11,11 @@ class << self
11
11
  end
12
12
 
13
13
  def query(keys)
14
- [{:field => "_parent_path", :operator => :equals, :value => keys}]
14
+ [{ field: "_parent_path", operator: :equals, value: keys }]
15
+ end
16
+
17
+ def extract_update_value_from_data(data)
18
+ ParentPath.of(data["_path"])
15
19
  end
16
20
 
17
21
  end
@@ -11,7 +11,11 @@ class << self
11
11
  end
12
12
 
13
13
  def query(keys)
14
- [{:field => "_path", :operator => :equals, :value => keys}]
14
+ [{ field: '_path', operator: :equals, value: keys }]
15
+ end
16
+
17
+ def extract_update_value_from_data(data)
18
+ data["_path"]
15
19
  end
16
20
 
17
21
  def group_by_multiple(paths, obj_datas)
@@ -1,17 +1,25 @@
1
1
  module Scrivito
2
2
  module Backend
3
- class PermalinkIndex
4
- class << self
5
- include Backend::Index
6
3
 
7
- def id
8
- "permalink"
9
- end
4
+ module PermalinkIndex
5
+ class << self
10
6
 
11
- def query(keys)
12
- [{ field: '_permalink', operator: :equals, value: keys }]
13
- end
14
- end
7
+ include Backend::Index
8
+
9
+ def id
10
+ "permalink"
11
+ end
12
+
13
+ def query(keys)
14
+ [{ field: '_permalink', operator: :equals, value: keys }]
15
+ end
16
+
17
+ def extract_update_value_from_data(data)
18
+ data["_permalink"]
15
19
  end
20
+
21
+ end
22
+ end
23
+
16
24
  end
17
25
  end
@@ -97,7 +97,7 @@ module Scrivito
97
97
  # Obj.create(:date => Time.new)
98
98
  # Obj.create(:date => Date.now)
99
99
  #
100
- # @example String, text, html and enum can be set by passing a {String} value
100
+ # @example String, html and enum can be set by passing a {String} value
101
101
  # Obj.create(:title => "My Title")
102
102
  #
103
103
  # @example Arrays of {String Strings} allow you to set multi enum fields
@@ -185,13 +185,12 @@ module Scrivito
185
185
  # {ObjSearchEnumerator}s can be chained using one of the chainable methods
186
186
  # (e.g. {ObjSearchEnumerator#and} and {ObjSearchEnumerator#and_not}).
187
187
  #
188
- # @example Look for the first 10 Objs whose ObjClass is "Pressrelease" and whose title contains "quarterly":
188
+ # @example Look for the first 10 Objs whose obj class is "Pressrelease" and whose title contains "quarterly":
189
189
  # Obj.where(:_obj_class, :equals, 'Pressrelease').and(:title, :contains, 'quarterly').take(10)
190
190
  # @param [Symbol, String, Array<Symbol, String>] field See {ObjSearchEnumerator#and} for details
191
191
  # @param [Symbol, String] operator See {ObjSearchEnumerator#and} for details
192
192
  # @param [String, Array<String>] value See {ObjSearchEnumerator#and} for details
193
193
  # @param [Hash] boost See {ObjSearchEnumerator#and} for details
194
- # @raise [ScrivitoError] if called on a subclass of +Obj+ with no corresponding {ObjClass}
195
194
  # @raise [ScrivitoError] if called directly on +BasicObj+. Use +Obj.where+ instead.
196
195
  # @return [ObjSearchEnumerator]
197
196
  # @api public
@@ -208,7 +207,6 @@ module Scrivito
208
207
  # Returns a {ObjSearchEnumerator} of all {Scrivito::BasicObj Obj}s.
209
208
  # If invoked on a subclass of Obj, the result will be restricted to instances of that subclass.
210
209
  # @return [ObjSearchEnumerator]
211
- # @raise [ScrivitoError] if called on a subclass of +Obj+ with no corresponding {ObjClass}
212
210
  # @raise [ScrivitoError] if called directly on +BasicObj+. Use +Obj.all+ instead.
213
211
  # @api public
214
212
  def self.all
@@ -221,7 +219,7 @@ module Scrivito
221
219
  end
222
220
 
223
221
  # Returns a {ObjSearchEnumerator} of all Objs with the given +obj_class+.
224
- # @param [String] obj_class Name of the ObjClass.
222
+ # @param [String] obj_class name of the obj class.
225
223
  # @return [ObjSearchEnumerator]
226
224
  # @api public
227
225
  def self.find_all_by_obj_class(obj_class)
@@ -280,30 +278,23 @@ module Scrivito
280
278
  #
281
279
  # Be aware that the given argument is a parent path.
282
280
  # E.g. when creating a page with path +/products/shoes+ then the argument will be +/products+.
281
+ # The given parent path can also be +NilClass+.
283
282
  #
284
283
  # If +NilClass+ is returned, then all possible classes will be available.
285
284
  # By default +NilClass+ is returned.
286
285
  #
287
- # If +Array+ is returned, then it should include desired class names.
288
- # Each class name must be either a +String+ or a +Symbol+.
289
- # Only this class names will be available. Order of the class names will be preserved.
286
+ # If +Array+ is returned, then it should include the desired classes.
287
+ # Only this classes will be available. Order of the classes will be preserved.
290
288
  #
291
- # @param [String] parent_path Path of the parent obj
292
- # @return [NilClass, Array<Symbol, String>]
289
+ # @param [String, NilClass] parent_path Path of the parent obj
290
+ # @return [NilClass, Array<Class>]
293
291
  # @api public
294
292
  def self.valid_page_classes_beneath(parent_path)
295
293
  end
296
294
 
297
295
  def self.valid_page_ruby_classes_beneath(parent_path)
298
- computed_classes = valid_page_classes_beneath(parent_path)
299
-
300
- return Scrivito.models.pages.to_a unless computed_classes
301
-
302
- convert_to_obj_classes(computed_classes, Obj.type_computer) do |class_name|
303
- Rails.logger.warn(
304
- "Invalid page class #{class_name} returned by #valid_page_classes_beneath."
305
- )
306
- end
296
+ assert_classes(valid_page_classes_beneath(parent_path), '.valid_page_classes_beneath') ||
297
+ Scrivito.models.pages.to_a
307
298
  end
308
299
 
309
300
  # Update the {Scrivito::BasicObj Obj} with the attributes provided.
@@ -432,7 +423,7 @@ module Scrivito
432
423
  'Try "bundle exec rake scrivito:migrate scrivito:migrate:publish".'
433
424
  end
434
425
 
435
- # Returns the homepage obj. This can be overwritten in your application's +Obj+.
426
+ # Returns the homepage obj. This can be overridden in your application's +Obj+.
436
427
  # Use {#homepage?} to check if an obj is the homepage.
437
428
  # @return [Obj]
438
429
  # @api public
@@ -453,7 +444,7 @@ module Scrivito
453
444
  # This method determines the controller that should be invoked when the +Obj+ is requested.
454
445
  # By default a controller matching the Obj's obj_class will be used.
455
446
  # If the controller does not exist, the CmsController will be used as a fallback.
456
- # Overwrite this method to force a different controller to be used.
447
+ # Override this method to force a different controller to be used.
457
448
  # @return [String]
458
449
  # @api public
459
450
  def controller_name
@@ -462,7 +453,7 @@ module Scrivito
462
453
 
463
454
  # This method determines the action that should be invoked when the +Obj+ is requested.
464
455
  # The default action is 'index'.
465
- # Overwrite this method to force a different action to be used.
456
+ # Override this method to force a different action to be used.
466
457
  # @return [String]
467
458
  # @api public
468
459
  def controller_action_name
@@ -482,7 +473,7 @@ module Scrivito
482
473
  # The default is {http://apidock.com/rails/ActiveSupport/Inflector/parameterize parameterize}
483
474
  # on +obj.title+.
484
475
  #
485
- # You can customize this part by overwriting {#slug}.
476
+ # You can customize this part by overriding {#slug}.
486
477
  # @return [String]
487
478
  # @api public
488
479
  def slug
@@ -491,7 +482,7 @@ module Scrivito
491
482
 
492
483
  #
493
484
  # This method determines the description that is shown in the UI
494
- # and defaults to {Scrivito::BasicObj#display_title}. It can be overriden by a custom value.
485
+ # and defaults to {Scrivito::BasicObj#display_title}. It can be overridden by a custom value.
495
486
  #
496
487
  # @api public
497
488
  #
@@ -512,6 +503,18 @@ module Scrivito
512
503
  (binary_title || title).presence || self.class.description_for_editor
513
504
  end
514
505
 
506
+ # The alt description of an +Obj+ used for {ScrivitoHelper#scrivito_image_tag}.
507
+ #
508
+ # By default this method returns the +title+ of this +Obj+.
509
+ #
510
+ # You can customize this part by overriding {#alt_description}.
511
+ #
512
+ # @return [String]
513
+ # @api public
514
+ def alt_description
515
+ title
516
+ end
517
+
515
518
  # @api public
516
519
  def title
517
520
  read_attribute('title')
@@ -531,6 +534,23 @@ module Scrivito
531
534
  blob_attribute_definition.present? && blob_attribute_definition.type == 'binary'
532
535
  end
533
536
 
537
+ #
538
+ # When delivering binary Objs, this method decides whether the image transformations should be
539
+ # applied by default.
540
+ #
541
+ # @api beta
542
+ #
543
+ # By default this method returns +false+.
544
+ # Override in subclasses to fit your needs.
545
+ #
546
+ # @note Only relevant for binary Objs
547
+ # @see Scrivito::Configuration.default_image_transformation=
548
+ # @see Scrivito::Binary#transform
549
+ #
550
+ def apply_image_transformation?
551
+ false
552
+ end
553
+
534
554
  # Returns true if this object is the root object.
535
555
  # @api public
536
556
  def root?
@@ -802,7 +822,6 @@ module Scrivito
802
822
  # Please use {Scrivito::BasicObj.restore Obj.restore} to restore them.
803
823
  #
804
824
  # @raise [ScrivitoError] If the current workspace is +published+.
805
- # @raise [ScrivitoError] If the current workspace is the +rtc+ workspace.
806
825
  # @raise [ScrivitoError] If the +Obj+ is +new+.
807
826
  # @raise [ScrivitoError] If the +Obj+ is +deleted+.
808
827
  #
@@ -894,15 +913,25 @@ module Scrivito
894
913
  end
895
914
 
896
915
  def parent_path
897
- unless root? || path.nil?
898
- path.gsub(/\/[^\/]+$/, '').presence || '/'
899
- end
916
+ ParentPath.of(path) unless root?
900
917
  end
901
918
 
902
919
  def as_client_json
903
920
  data_from_cms.to_h.except(*GENERATED_ATTRIBUTES)
904
921
  end
905
922
 
923
+ def outdated?
924
+ return false if workspace.published?
925
+
926
+ base_revision = workspace.base_revision
927
+ published_revision = Workspace.published.revision
928
+
929
+ return false if base_revision == published_revision
930
+ return true if has_conflict?
931
+
932
+ cms_data_for_revision(base_revision) != cms_data_for_revision(published_revision)
933
+ end
934
+
906
935
  private
907
936
 
908
937
  def cms_data_for_revision(revision)
@@ -1003,8 +1032,6 @@ module Scrivito
1003
1032
  # @api public
1004
1033
  #
1005
1034
  # @raise [ScrivitoError] If the current workspace is +published+.
1006
- # @raise [ScrivitoError] If the current workspace is the +rtc+ workspace.
1007
- #
1008
1035
  def restore(obj_id)
1009
1036
  Workspace.current.assert_revertable
1010
1037
  base_revision_path = "revisions/#{Workspace.current.base_revision_id}/objs/#{obj_id}"
@@ -1028,18 +1055,11 @@ module Scrivito
1028
1055
  end
1029
1056
 
1030
1057
  def serialize_attributes(obj_attributes, widget_pool_attributes, workspace)
1031
- if workspace.uses_obj_classes
1032
- serialized_attributes = CmsRestApi::LegacyAttributeSerializer.convert(obj_attributes)
1033
- serialized_attributes['_widget_pool'] = CmsRestApi::LegacyAttributeSerializer
1034
- .generate_widget_pool_changes(widget_pool_attributes)
1035
- serialized_attributes
1036
- else
1037
- serializer = AttributeSerializer.new(obj_attributes['_obj_class'] || name)
1038
- serialized_attributes = serialize_obj_attributes(serializer, obj_attributes)
1039
- serialized_attributes['_widget_pool'] =
1040
- serialize_widget_pool_attributes(serializer, widget_pool_attributes)
1041
- serialized_attributes
1042
- end
1058
+ serializer = AttributeSerializer.new(obj_attributes['_obj_class'] || name)
1059
+ serialized_attributes = serialize_obj_attributes(serializer, obj_attributes)
1060
+ serialized_attributes['_widget_pool'] =
1061
+ serialize_widget_pool_attributes(serializer, widget_pool_attributes)
1062
+ serialized_attributes
1043
1063
  end
1044
1064
 
1045
1065
  def serialize_obj_attributes(serializer, obj_attributes)