scrivito_sdk 0.66.0 → 0.70.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scrivito/blobs_controller.rb +5 -0
  3. data/app/controllers/scrivito/objs_controller.rb +2 -1
  4. data/app/controllers/scrivito/ui_controller.rb +33 -3
  5. data/app/helpers/scrivito_helper.rb +14 -20
  6. data/app/views/scrivito/objs/update.json.jbuilder +1 -1
  7. data/app/views/scrivito/ui/index.html.erb +1 -1
  8. data/app/views/scrivito/webservice/_workspace.json.jbuilder +2 -1
  9. data/config/ca-bundle.crt +1 -1
  10. data/config/precedence_routes.rb +3 -2
  11. data/lib/assets/javascripts/scrivito.js +46 -0
  12. data/lib/assets/javascripts/scrivito_ui.js +931 -501
  13. data/lib/assets/stylesheets/scrivito.css +1 -0
  14. data/lib/assets/stylesheets/scrivito_ui.css +1 -1
  15. data/lib/generators/scrivito/install/templates/app/views/page/index.html.erb +3 -3
  16. data/lib/generators/scrivito/page/page_generator.rb +3 -0
  17. data/lib/generators/scrivito/page/templates/model.erb +3 -0
  18. data/lib/generators/scrivito/widget/templates/model.erb +3 -0
  19. data/lib/generators/scrivito/widget/widget_generator.rb +3 -0
  20. data/lib/scrivito/attribute_content.rb +93 -60
  21. data/lib/scrivito/attribute_definition.rb +3 -3
  22. data/lib/scrivito/attribute_serializer.rb +2 -2
  23. data/lib/scrivito/backend/obj_data_cache.rb +22 -9
  24. data/lib/scrivito/basic_obj.rb +238 -130
  25. data/lib/scrivito/basic_widget.rb +32 -20
  26. data/lib/scrivito/binary.rb +74 -45
  27. data/lib/scrivito/binary_routing.rb +52 -0
  28. data/lib/scrivito/cache_middleware.rb +0 -5
  29. data/lib/scrivito/client_attribute_serializer.rb +134 -0
  30. data/lib/scrivito/cms_backend.rb +51 -33
  31. data/lib/scrivito/cms_data_cache.rb +1 -0
  32. data/lib/scrivito/cms_dispatch_controller.rb +1 -1
  33. data/lib/scrivito/cms_env.rb +1 -11
  34. data/lib/scrivito/cms_field_tag.rb +10 -7
  35. data/lib/scrivito/cms_rest_api/rate_limit.rb +5 -5
  36. data/lib/scrivito/cms_rest_api/request_timer.rb +27 -0
  37. data/lib/scrivito/cms_rest_api/widget_extractor.rb +8 -6
  38. data/lib/scrivito/cms_rest_api.rb +107 -54
  39. data/lib/scrivito/cms_routing.rb +13 -25
  40. data/lib/scrivito/configuration.rb +5 -2
  41. data/lib/scrivito/controller_actions.rb +68 -7
  42. data/lib/scrivito/controller_runtime.rb +8 -0
  43. data/lib/scrivito/date_attribute.rb +8 -0
  44. data/lib/scrivito/errors.rb +6 -3
  45. data/lib/scrivito/generator_helper.rb +13 -0
  46. data/lib/scrivito/image_tag.rb +24 -30
  47. data/lib/scrivito/layout_tags.rb +12 -6
  48. data/lib/scrivito/link.rb +46 -30
  49. data/lib/scrivito/log_subscriber.rb +15 -0
  50. data/lib/scrivito/membership.rb +3 -3
  51. data/lib/scrivito/membership_collection.rb +10 -10
  52. data/lib/scrivito/meta_data_collection.rb +22 -0
  53. data/lib/scrivito/model_library.rb +7 -1
  54. data/lib/scrivito/obj_collection.rb +18 -16
  55. data/lib/scrivito/obj_params_parser.rb +1 -1
  56. data/lib/scrivito/obj_search_enumerator.rb +35 -35
  57. data/lib/scrivito/page_config.rb +55 -0
  58. data/lib/scrivito/request_homepage.rb +23 -0
  59. data/lib/scrivito/routing_helper.rb +8 -8
  60. data/lib/scrivito/sdk_engine.rb +2 -3
  61. data/lib/scrivito/ui_config.rb +85 -0
  62. data/lib/scrivito/user.rb +30 -23
  63. data/lib/scrivito/user_definition.rb +48 -47
  64. data/lib/scrivito/widget_tag.rb +11 -0
  65. data/lib/scrivito/workspace.rb +93 -35
  66. data/lib/scrivito/workspace_selection_middleware.rb +8 -1
  67. data/lib/scrivito_sdk.rb +24 -24
  68. metadata +24 -33
  69. data/lib/assets/javascripts/scrivito_sdk.js +0 -57
  70. data/lib/assets/stylesheets/scrivito_sdk.css +0 -1
  71. data/lib/scrivito/client_config.rb +0 -113
  72. data/lib/scrivito/editing_context_helper.rb +0 -19
  73. data/lib/scrivito/named_link.rb +0 -39
@@ -60,6 +60,7 @@ module Scrivito
60
60
  def initialize
61
61
  @query_counter = 0
62
62
  @caching = true
63
+ @die_content_service = true
63
64
  end
64
65
 
65
66
  def begin_caching
@@ -88,7 +89,25 @@ module Scrivito
88
89
  @query_counter = 0
89
90
  end
90
91
 
91
- def find_workspace_data_by_id(id)
92
+ def find_workspace_data_from_cache(id)
93
+ if die_content_service
94
+ cached_workspace_state = CmsDataCache.read_workspace_state(id)
95
+ cached_data_tag = cached_workspace_state.try(:second)
96
+ cached_content_state_id = cached_workspace_state.try(:first)
97
+
98
+ if cached_data_tag && cached_content_state_id
99
+ if raw_workspace_data = fetch_cached_data_by_tag(cached_data_tag)
100
+ build_workspace_data(raw_workspace_data, cached_content_state_id)
101
+ end
102
+ end
103
+ else
104
+ WorkspaceDataFromService.find_from_cache(id)
105
+ end
106
+ end
107
+
108
+ def find_workspace_data_by_id(id, timeout=nil)
109
+ options = timeout ? {timeout: timeout} : {}
110
+
92
111
  if die_content_service
93
112
  begin
94
113
  cached_workspace_state = CmsDataCache.read_workspace_state(id)
@@ -96,12 +115,12 @@ module Scrivito
96
115
  cached_csid = cached_workspace_state.try(:first)
97
116
  cached_workspace_data_tag = cached_workspace_state.try(:second)
98
117
 
99
- changes = CmsRestApi.get("/workspaces/#{id}/changes", from: cached_csid)
118
+ changes = CmsRestApi.get("/workspaces/#{id}/changes", {from: cached_csid}, options)
100
119
 
101
120
  update_obj_cache(id, cached_csid, changes)
102
121
 
103
- workspace_data, workspace_data_tag = update_workspace_cache(
104
- id, cached_workspace_data_tag, changes["workspace"])
122
+ raw_workspace_data, workspace_data_tag = update_workspace_cache(
123
+ id, cached_workspace_data_tag, changes["workspace"], options)
105
124
 
106
125
  current_csid = changes["current"]
107
126
  current_workspace_state = [current_csid, workspace_data_tag]
@@ -110,8 +129,7 @@ module Scrivito
110
129
  CmsDataCache.write_workspace_state(id, current_workspace_state)
111
130
  end
112
131
 
113
- return WorkspaceData.new(workspace_data.merge(
114
- "content_state_id" => current_csid))
132
+ return build_workspace_data(raw_workspace_data, current_csid)
115
133
 
116
134
  rescue Scrivito::ClientError => client_error
117
135
  if client_error.http_code == 404
@@ -122,22 +140,13 @@ module Scrivito
122
140
  end
123
141
  end
124
142
 
125
- workspace_data_from_cache = WorkspaceDataFromService.find_from_cache(id)
143
+ workspace_data_from_cache = find_workspace_data_from_cache(id)
126
144
  from_content_state_id = workspace_data_from_cache.try(:content_state_id)
127
145
 
128
146
  request_params = {:workspace_id => id}
129
147
  request_params[:content_state_id] = from_content_state_id if from_content_state_id
130
148
 
131
- raw_data = if id == 'published' && workspace_data_from_cache
132
- begin
133
- ContentService.query('workspaces/query', request_params, timeout: 1)
134
- rescue CommunicationError => e
135
- warn_backend_not_available(id, from_content_state_id, e.message)
136
- return workspace_data_from_cache
137
- end
138
- else
139
- ContentService.query('workspaces/query', request_params)
140
- end
149
+ raw_data = ContentService.query('workspaces/query', request_params, options)
141
150
 
142
151
  if raw_workspace_data = raw_data['workspace']
143
152
  workspace_data = WorkspaceDataFromService.new(raw_workspace_data)
@@ -200,6 +209,18 @@ module Scrivito
200
209
  end
201
210
  end
202
211
 
212
+ def find_binary_meta_data(blob_id)
213
+ blob_id = normalize_blob_id(blob_id)
214
+ cache_key = "binary_meta_data/#{blob_id}"
215
+ if meta_data = CmsDataCache.cache.read(cache_key)
216
+ meta_data
217
+ else
218
+ meta_data = CmsRestApi.get("blobs/#{blob_id}/meta_data")['meta_data']
219
+ CmsDataCache.cache.write(cache_key, meta_data)
220
+ meta_data
221
+ end
222
+ end
223
+
203
224
  def search_objs(workspace, params)
204
225
  cache_index = 'search'
205
226
  cache_key = params.to_param
@@ -213,7 +234,7 @@ module Scrivito
213
234
 
214
235
  result = request_search_result_from_backend(workspace, params)
215
236
 
216
- cache.write_index(cache_index, cache_key, result)
237
+ cache.write_index_not_updatable(cache_index, cache_key, result)
217
238
 
218
239
  return result
219
240
  end
@@ -231,17 +252,15 @@ module Scrivito
231
252
 
232
253
  private
233
254
 
234
- def update_workspace_cache(id, cached_data_tag, changed_workspace)
255
+ def update_workspace_cache(id, cached_data_tag, changed_workspace, options)
235
256
  if changed_workspace
236
257
  workspace_data = changed_workspace
237
258
  else
238
- cached_workspace_data = CmsDataCache.read_data_from_tag(cached_data_tag)
239
-
240
- if cached_workspace_data
259
+ if cached_workspace_data = fetch_cached_data_by_tag(cached_data_tag)
241
260
  workspace_data = cached_workspace_data
242
261
  workspace_data_tag = cached_data_tag
243
262
  else
244
- workspace_data = CmsRestApi.get("/workspaces/#{id}")
263
+ workspace_data = CmsRestApi.get("/workspaces/#{id}", nil, options)
245
264
  end
246
265
  end
247
266
 
@@ -250,6 +269,14 @@ module Scrivito
250
269
  [workspace_data, workspace_data_tag]
251
270
  end
252
271
 
272
+ def fetch_cached_data_by_tag(data_tag)
273
+ data_tag && CmsDataCache.read_data_from_tag(data_tag)
274
+ end
275
+
276
+ def build_workspace_data(raw_data, content_state_id)
277
+ WorkspaceData.new(raw_data.merge('content_state_id' => content_state_id))
278
+ end
279
+
253
280
  def update_obj_cache(workspace_id, cached_csid, changes)
254
281
  objs = changes["objs"]
255
282
  if objs.present? && objs != "*"
@@ -302,7 +329,7 @@ module Scrivito
302
329
  end
303
330
 
304
331
  def normalize_blob_id(id)
305
- Addressable::URI.normalize_component(id, Addressable::URI::CharacterClasses::UNRESERVED)
332
+ CmsRestApi.normalize_path_component(id)
306
333
  end
307
334
 
308
335
  def fetch_blob_metadata_from_cache(id)
@@ -422,15 +449,6 @@ module Scrivito
422
449
  raise ArgumentError, "invalid index name '#{index}'" unless VALID_INDEX_NAMES.include?(index)
423
450
  end
424
451
 
425
- def warn_backend_not_available(workspace_id, content_state_id, error_message)
426
- message = <<-EOS
427
- Couldn't connect to content service for workspace with id=#{workspace_id} and content_state_id=#{content_state_id}.
428
- #{error_message}
429
- Serving from cache.
430
- EOS
431
- Rails.logger.warn(message)
432
- end
433
-
434
452
  end
435
453
 
436
454
  end
@@ -75,6 +75,7 @@ module CmsDataCache
75
75
  end
76
76
 
77
77
  def read_data_from_tag(tag)
78
+ raise ArgumentError, 'tag can not be nil' if tag.nil?
78
79
  read_tag_data(tag)
79
80
  end
80
81
 
@@ -18,7 +18,7 @@ module Scrivito
18
18
  end
19
19
 
20
20
  if obj_not_found? && editing_context.workspace_changed?
21
- redirect_to scrivito_path(Obj.homepage)
21
+ redirect_to :scrivito_root
22
22
  return self.response
23
23
  end
24
24
 
@@ -33,17 +33,7 @@ module Scrivito
33
33
  elsif params[:permalink].present?
34
34
  self.class.find_permalink_by_param(params[:permalink])
35
35
  else
36
- if callback = Scrivito::Configuration.choose_homepage_callback
37
- callback_result = callback.call(env)
38
- if callback_result.is_a?(Obj)
39
- callback_result
40
- else
41
- raise "choose_homepage callback did not return an Obj. "\
42
- "Instead saw #{callback_result.class}."
43
- end
44
- else
45
- Obj.homepage
46
- end
36
+ Scrivito::RequestHomepage.call(env)
47
37
  end
48
38
 
49
39
  found_obj
@@ -1,6 +1,6 @@
1
1
  module Scrivito
2
2
 
3
- # this class is the server-side equivalent of the JavaScript class `cms_field_element`
3
+ # This class is the server-side equivalent of the JavaScript +cms_field_element+ class.
4
4
  class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :editing_options)
5
5
  FIELD_TYPES_WITH_ORIGINAL_CONTENT = %w[
6
6
  binary
@@ -135,12 +135,15 @@ class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :editing_option
135
135
  end
136
136
 
137
137
  def original_content(field_type, field_value, raw_value)
138
- case field_type
139
- when 'binary' then field_value && {}
140
- when 'date' then raw_value.try(:utc).try(:iso8601)
141
- when 'reference' then field_value.try(:id)
142
- when 'referencelist' then field_value.map(&:id)
143
- else field_value
138
+ if %w[
139
+ binary
140
+ date
141
+ reference
142
+ referencelist
143
+ ].include?(field_type)
144
+ ClientAttributeSerializer.serialize_custom_attr_value(field_type, raw_value)
145
+ else
146
+ ClientAttributeSerializer.serialize_custom_attr_value(field_type, field_value)
144
147
  end
145
148
  end
146
149
 
@@ -2,21 +2,21 @@ module Scrivito
2
2
  class CmsRestApi
3
3
  module RateLimit
4
4
  class << self
5
- def retry_on_rate_limit(try_until, &block)
6
- internal_retry(block, try_until, 0)
5
+ def retry_on_rate_limit(request_timer, &block)
6
+ internal_retry(block, request_timer, 0)
7
7
  end
8
8
 
9
9
  private
10
10
 
11
- def internal_retry(request_proc, try_until, retry_count)
11
+ def internal_retry(request_proc, request_timer, retry_count)
12
12
  response = request_proc.call
13
13
 
14
14
  if failed_because_of_rate_limit?(response)
15
15
  time_to_sleep = calculate_time_to_sleep(response['Retry-After'].to_f, retry_count)
16
16
 
17
- if (Time.now + time_to_sleep.seconds) <= try_until
17
+ if request_timer.cover?(Time.now + time_to_sleep.seconds)
18
18
  sleep time_to_sleep
19
- internal_retry(request_proc, try_until, retry_count + 1)
19
+ internal_retry(request_proc, request_timer, retry_count + 1)
20
20
  else
21
21
  raise Scrivito::RateLimitExceeded.new('rate limit exceeded', 429)
22
22
  end
@@ -0,0 +1,27 @@
1
+ module Scrivito
2
+ class CmsRestApi
3
+ class RequestTimer
4
+ MIN_REQUEST_TIME = 0.005
5
+
6
+ def initialize(max_duration)
7
+ @finish_before = Time.now + max_duration
8
+ end
9
+
10
+ def finished?
11
+ remaining_time <= MIN_REQUEST_TIME
12
+ end
13
+
14
+ def remaining_time
15
+ [finish_before - Time.now, 0].max
16
+ end
17
+
18
+ def cover?(point_in_time)
19
+ point_in_time <= finish_before
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :finish_before
25
+ end
26
+ end
27
+ end
@@ -2,9 +2,11 @@ module Scrivito
2
2
  class CmsRestApi
3
3
  class WidgetExtractor
4
4
 
5
- # Extract widgets from a given attribute tree and return them as a flat
6
- # hash of widget_like => updated_attributes
7
- def self.call(updated_attributes, obj=nil)
5
+ # Extract widgets from a given attribute tree
6
+ # first return param: a flat hash of widget_like => updated widget attributes
7
+ # second return param: the updated attributes
8
+ def self.call(attributes, obj=nil)
9
+ updated_attributes = attributes.dup
8
10
  extracted_widgets = {}
9
11
 
10
12
  (updated_attributes.delete(:_widget_pool) || {}).each do |widget, widget_properties|
@@ -12,7 +14,7 @@ module Scrivito
12
14
  raise ScrivitoError, 'new top-level widgets are not allowed in the _widget_pool'
13
15
  end
14
16
  extracted_widgets[widget] = widget_properties
15
- extracted_widgets.merge!(call(widget_properties, obj))
17
+ extracted_widgets.merge!(call(widget_properties, obj).first)
16
18
  end
17
19
 
18
20
  updated_attributes.each do |attribute_name, value|
@@ -21,13 +23,13 @@ module Scrivito
21
23
  unless widget.persisted?
22
24
  attach_widget_to_obj(widget, obj)
23
25
  extracted_widgets[widget] = widget.attributes_to_be_saved
24
- extracted_widgets.merge!(call(widget.attributes_to_be_saved, obj))
26
+ extracted_widgets.merge!(call(widget.attributes_to_be_saved, obj).first)
25
27
  end
26
28
  end
27
29
  end
28
30
  end
29
31
 
30
- extracted_widgets
32
+ return extracted_widgets, updated_attributes
31
33
  end
32
34
 
33
35
  def self.attach_widget_to_obj(widget, obj)
@@ -32,27 +32,28 @@ module Scrivito
32
32
  :delete => Net::HTTP::Delete,
33
33
  }.freeze
34
34
 
35
- MAX_RATE_LIMIT_RETRY_DURATION = 20.seconds.freeze
35
+ DEFAULT_TIMEOUT = 25.seconds.freeze
36
+ MAX_REQUEST_TIME = 10.seconds.freeze
36
37
 
37
- def self.get(resource_path, payload = nil, options = nil)
38
+ def self.get(resource_path, payload = nil, options = {})
38
39
  request_cms_api(:get, resource_path, payload, options)
39
40
  end
40
41
 
41
- def self.put(resource_path, payload, options = nil)
42
+ def self.put(resource_path, payload, options = {})
42
43
  request_cms_api(:put, resource_path, payload, options)
43
44
  end
44
45
 
45
- def self.post(resource_path, payload, options = nil)
46
+ def self.post(resource_path, payload, options = {})
46
47
  request_cms_api(:post, resource_path, payload, options)
47
48
  end
48
49
 
49
- def self.delete(resource_path, payload = nil, options = nil)
50
+ def self.delete(resource_path, payload = nil, options = {})
50
51
  request_cms_api(:delete, resource_path, payload, options)
51
52
  end
52
53
 
53
54
  def self.task_unaware_request(method, resource_path, payload = nil)
54
55
  raise "Unexpected method #{method}" unless [:delete, :get, :post, :put].include?(method)
55
- response_for_request_cms_api(method, resource_path, payload)
56
+ response_for_request_cms_api(method, resource_path, payload, build_timer)
56
57
  end
57
58
 
58
59
  def self.count_requests(path)
@@ -63,21 +64,19 @@ module Scrivito
63
64
  @number_of_requests
64
65
  end
65
66
 
66
- def self.upload_file(file)
67
+ def self.upload_file(file, obj_id)
67
68
  upload_permission = get('blobs/upload_permission')
68
- uri = URI.parse(upload_permission['url'])
69
- File.open(file) do |open_file|
70
- content_type = MIME::Types.type_for(file.path).first.content_type
71
- upload_io = UploadIO.new(open_file, content_type, File.basename(file))
72
- params = upload_permission['fields'].merge('file' => upload_io)
73
- request = Net::HTTP::Post::Multipart.new(uri.path, params)
74
- response = ConnectionManager.request(uri, request)
75
- if response.code.starts_with?('2')
76
- upload_permission['blob'].merge('filename' => File.basename(file.path))
77
- else
78
- raise ScrivitoError, "File upload failed with code #{response.code}"
79
- end
80
- end
69
+ upload = perform_file_upload(file, upload_permission)
70
+ activate_upload(upload: upload, obj_id: obj_id)
71
+ end
72
+
73
+ def self.activate_upload(params)
74
+ put('blobs/activate_upload', params)
75
+ end
76
+
77
+ def self.normalize_path_component(component)
78
+ Addressable::URI.normalize_component(component,
79
+ Addressable::URI::CharacterClasses::UNRESERVED)
81
80
  end
82
81
 
83
82
  class << self
@@ -85,35 +84,47 @@ module Scrivito
85
84
  private
86
85
 
87
86
  def request_cms_api(action, resource_path, payload, options)
88
- @number_of_requests += 1 if resource_path == @count_requests
89
- decoded = response_for_request_cms_api(action, resource_path, payload)
90
- return decoded unless Hash === decoded
91
- return decoded unless decoded.keys == ["task"]
92
- task_data = decoded["task"]
93
- return decoded unless Hash === task_data
94
- task_path = "tasks/#{task_data["id"]}"
95
- final_response(task_path, options)
87
+ log_api_request(action, resource_path, payload) do
88
+ @number_of_requests += 1 if resource_path == @count_requests
89
+
90
+ timer = build_timer(options)
91
+ response = response_for_request_cms_api(action, resource_path, payload, timer)
92
+
93
+ if task_response?(response)
94
+ task_path = "tasks/#{response['task']['id']}"
95
+ poll_interval = options[:interval].presence.try(:to_f) || 2
96
+ wait_for_tasks_final_response(task_path, poll_interval)
97
+ else
98
+ response
99
+ end
100
+ end
101
+ end
102
+
103
+ def log_api_request(action, resource_path, payload, &block)
104
+ ActiveSupport::Notifications.instrumenter.instrument(
105
+ "backend_request.scrivito",
106
+ {
107
+ :path => resource_path,
108
+ :verb => action,
109
+ :params => action == :get ? payload : nil
110
+ },
111
+ &block
112
+ )
96
113
  end
97
114
 
98
- def response_for_request_cms_api(method, resource_path, payload = nil)
115
+ def task_response?(response)
116
+ response.is_a?(Hash) && response.keys == ['task'] && response['task'].is_a?(Hash)
117
+ end
118
+
119
+ def response_for_request_cms_api(method, resource_path, payload, timer)
99
120
  request = method_to_net_http_class(method).new(path(resource_path))
100
121
  set_headers(request)
101
122
  request.body = MultiJson.encode(payload) if payload.present?
102
123
 
103
- response = nil
104
- retried = false
105
- wait_until = Time.now + MAX_RATE_LIMIT_RETRY_DURATION
106
- begin
107
- response = CmsRestApi::RateLimit.retry_on_rate_limit(wait_until) do
108
- # lower timeout back to DEFAULT_TIMEOUT once the backend has been fixed
109
- connection_manager.request(request, 25)
110
- end
111
- rescue NetworkError => e
112
- if method == :post || retried
113
- raise e
114
- else
115
- retried = true
116
- retry
124
+ response = retry_once_on_network_error(method, timer) do
125
+ CmsRestApi::RateLimit.retry_on_rate_limit(timer) do
126
+ request_timeout = [timer.remaining_time, MAX_REQUEST_TIME].min
127
+ connection_manager.request(request, request_timeout)
117
128
  end
118
129
  end
119
130
 
@@ -146,19 +157,57 @@ module Scrivito
146
157
  end
147
158
  end
148
159
 
149
- def final_response(task_path, options)
150
- options ||= {}
151
- wait = options[:interval].presence.try(:to_f) || 2
152
- task_data = response = nil
160
+ def wait_for_tasks_final_response(task_path, poll_interval)
161
+ task_data = fetch_final_response(task_path, poll_interval)
162
+
163
+ if task_data['status'] == 'success'
164
+ task_data['result']
165
+ else
166
+ message = task_data["message"] ||
167
+ "Missing error message in task response #{task_data}"
168
+
169
+ backend_code = task_data['code']
170
+ raise ClientError.new(message, 400, backend_code)
171
+ end
172
+ end
173
+
174
+ def perform_file_upload(file, upload_permission)
175
+ uri = URI.parse(upload_permission['url'])
176
+ File.open(file) do |open_file|
177
+ content_type = MIME::Types.type_for(file.path).first.content_type
178
+ upload_io = UploadIO.new(open_file, content_type, File.basename(file))
179
+ params = upload_permission['fields'].merge('file' => upload_io)
180
+ request = Net::HTTP::Post::Multipart.new(uri.path, params)
181
+ response = ConnectionManager.request(uri, request)
182
+ if response.code.starts_with?('2')
183
+ upload_permission['blob'].merge('filename' => File.basename(file.path))
184
+ else
185
+ raise ScrivitoError, "File upload failed with code #{response.code}"
186
+ end
187
+ end
188
+ end
189
+
190
+ def fetch_final_response(task_path, poll_interval)
153
191
  loop do
154
- sleep wait
155
- task_data = response_for_request_cms_api(:get, task_path, nil)
156
- break unless task_data["status"] == "open"
192
+ sleep poll_interval
193
+ task_data = response_for_request_cms_api(:get, task_path, nil, build_timer)
194
+
195
+ return task_data if task_data['status'] != 'open'
196
+ end
197
+ end
198
+
199
+ def retry_once_on_network_error(method, timer)
200
+ return yield if method == :post
201
+
202
+ begin
203
+ yield
204
+ rescue NetworkError => e
205
+ if timer.finished?
206
+ raise e
207
+ else
208
+ yield
209
+ end
157
210
  end
158
- return task_data["result"] if task_data["status"] == "success"
159
- message = task_data["message"] || "Missing error message in task response #{task_data}"
160
- backend_code = task_data['code']
161
- raise ClientError.new(message, 400, backend_code)
162
211
  end
163
212
 
164
213
  def set_headers(request)
@@ -175,6 +224,10 @@ module Scrivito
175
224
  ConnectionManager.instance
176
225
  end
177
226
 
227
+ def build_timer(options={})
228
+ CmsRestApi::RequestTimer.new(options.fetch(:timeout, DEFAULT_TIMEOUT))
229
+ end
230
+
178
231
  def method_to_net_http_class(method)
179
232
  METHOD_TO_NET_HTTP_CLASS.fetch(method)
180
233
  end
@@ -18,7 +18,7 @@ class CmsRouting < Struct.new(:request, :main_app, :scrivito_engine, :image_opti
18
18
 
19
19
  def convert_links(html)
20
20
  if html
21
- html.gsub(%r{\bobjid:([a-f0-9]{16})\b([^"']*)}) do
21
+ html.to_str.gsub(%r{\bobjid:([a-f0-9]{16})\b([^"']*)}) do
22
22
  if obj = Obj.find_by_id($1)
23
23
  options = {}
24
24
 
@@ -62,7 +62,7 @@ class CmsRouting < Struct.new(:request, :main_app, :scrivito_engine, :image_opti
62
62
  return LINK_TO_EMPTY_LINKLIST
63
63
  end
64
64
  elsif target.is_a?(Binary)
65
- binary_url(target, path_or_url)
65
+ binary_url(target)
66
66
  else
67
67
  raise "scrivito_path or scrivito_url was called with an instance of #{target.class}. "+
68
68
  "It must only be called with an Obj or a Link or a non-empty LinkList."
@@ -81,14 +81,10 @@ class CmsRouting < Struct.new(:request, :main_app, :scrivito_engine, :image_opti
81
81
  if permalink
82
82
  main_app
83
83
  .public_send("scrivito_permalink_#{path_or_url}", options.merge(:permalink => permalink))
84
- elsif obj.homepage?
84
+ elsif homepage?(obj)
85
85
  main_app.public_send("scrivito_root_#{path_or_url}", options)
86
86
  elsif obj.binary?
87
- if binary = obj.binary
88
- binary_url(binary, path_or_url)
89
- else
90
- LINK_TO_EMPTY_BLOB
91
- end
87
+ binary_obj_url(obj) || LINK_TO_EMPTY_BLOB
92
88
  else
93
89
  slug = obj.slug.present? ? obj.slug.sub(/^\//, '') : nil
94
90
  id_path_or_url_for_objs(obj, path_or_url, options.merge(slug: slug))
@@ -105,6 +101,10 @@ class CmsRouting < Struct.new(:request, :main_app, :scrivito_engine, :image_opti
105
101
  main_app.public_send(method_name, options.merge(id: obj.id))
106
102
  end
107
103
 
104
+ def homepage?(obj)
105
+ RequestHomepage.call(request.env) == obj
106
+ end
107
+
108
108
  def editor_authenticated?
109
109
  editing_context.authenticated_editor?
110
110
  end
@@ -141,27 +141,15 @@ class CmsRouting < Struct.new(:request, :main_app, :scrivito_engine, :image_opti
141
141
  Rack::Utils.parse_query(uri.query)
142
142
  end
143
143
 
144
- def binary_url(binary, path_or_url)
145
- binary = transform_binary(binary)
146
- if url_from_cache = binary.url_from_cache
147
- BinaryRewrite.call(request, url_from_cache)
148
- else
149
- encrypted_params = BinaryParamVerifier.generate(binary)
150
- scrivito_engine.public_send("binary_#{path_or_url}", encrypted_params: encrypted_params)
151
- end
144
+ def binary_url(binary)
145
+ BinaryRouting.new(request, scrivito_engine).binary_url(binary)
152
146
  end
153
147
 
154
- def transform_binary(binary)
155
- if transformation_definition = image_options[:transform]
156
- binary.transform(transformation_definition)
157
- else
158
- binary
148
+ def binary_obj_url(obj)
149
+ if binary = obj.binary
150
+ BinaryRouting.new(request, scrivito_engine).binary_obj_url(obj, binary, image_options || {})
159
151
  end
160
152
  end
161
-
162
- def image_options
163
- super || {}
164
- end
165
153
  end
166
154
 
167
155
  end
@@ -208,6 +208,9 @@ module Scrivito
208
208
  self.check_batch_size = 100
209
209
  self.legacy_routing = false
210
210
  self.default_image_transformation = {}
211
+ self.choose_homepage_callback = -> (env) { Obj.root }
212
+ self.find_user_proc = nil
213
+ reset_editing_auth_callback!
211
214
  end
212
215
 
213
216
  #
@@ -238,7 +241,7 @@ module Scrivito
238
241
  #
239
242
  # Set the default {Scrivito::Binary#transform image transformation}.
240
243
  #
241
- # @api beta
244
+ # @api public
242
245
  #
243
246
  # When delivering binary Objs, the default image transformation will be applied if
244
247
  # {Scrivito::BasicObj#apply_image_transformation? Obj#apply_image_transformation?} returns
@@ -259,7 +262,7 @@ module Scrivito
259
262
  # Configure a callback to be invoked when the Scrivito SDK delivers the homepage.
260
263
  # The given callback will receive the rack env
261
264
  # and must return an {BasicObj Obj} to be used as the homepage.
262
- # If no callback is configured, {BasicObj.homepage Obj.homepage} will be used as the default.
265
+ # If no callback is configured, {BasicObj.root Obj.root} will be used as the default.
263
266
  # @api public
264
267
  def choose_homepage(&block)
265
268
  self.choose_homepage_callback = block