scrivito_sdk 0.66.0 → 0.70.0.rc1

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