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
@@ -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)