scrivito_sdk 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/README +6 -0
  4. data/app/controllers/cms_controller.rb +7 -0
  5. data/app/controllers/scrivito/blobs_controller.rb +10 -0
  6. data/app/controllers/scrivito/default_cms_controller.rb +61 -0
  7. data/app/controllers/scrivito/objs_controller.rb +200 -0
  8. data/app/controllers/scrivito/tasks_controller.rb +11 -0
  9. data/app/controllers/scrivito/webservice_controller.rb +36 -0
  10. data/app/controllers/scrivito/workspaces_controller.rb +41 -0
  11. data/app/helpers/cms_helper.rb +7 -0
  12. data/app/helpers/cms_routing_helper.rb +7 -0
  13. data/app/helpers/scrivito/cms_asset_helper.rb +103 -0
  14. data/app/helpers/scrivito/cms_tag_helper.rb +231 -0
  15. data/app/helpers/scrivito/default_cms_helper.rb +21 -0
  16. data/app/helpers/scrivito/default_cms_routing_helper.rb +130 -0
  17. data/app/helpers/scrivito/display_helper.rb +71 -0
  18. data/app/helpers/scrivito/editing_helper.rb +26 -0
  19. data/app/helpers/scrivito/layout_helper.rb +28 -0
  20. data/app/models/named_link.rb +2 -0
  21. data/app/views/cms/_index.html.erb +7 -0
  22. data/app/views/cms/index.html.erb +1 -0
  23. data/app/views/scrivito/_editing_javascript.html.erb +7 -0
  24. data/app/views/scrivito/default_cms/show_widget.html.erb +1 -0
  25. data/app/views/scrivito/objs/copy_widget.html.erb +1 -0
  26. data/app/views/scrivito/objs/create_widget.html.erb +1 -0
  27. data/app/views/scrivito/widget_thumbnail.html.erb +9 -0
  28. data/config/ca-bundle.crt +3509 -0
  29. data/config/cms_routes.rb +17 -0
  30. data/config/locales/de.scrivito.errors.yml +7 -0
  31. data/config/locales/de.scrivito.lib.yml +6 -0
  32. data/config/locales/de.scrivito.models.yml +6 -0
  33. data/config/locales/en.scrivito.errors.yml +7 -0
  34. data/config/locales/en.scrivito.lib.yml +6 -0
  35. data/config/locales/en.scrivito.models.yml +6 -0
  36. data/config/routes.rb +37 -0
  37. data/lib/assets/images/180x120.gif +0 -0
  38. data/lib/assets/images/scrivito/image_placeholder.png +0 -0
  39. data/lib/assets/javascripts/scrivito_editing.js +14642 -0
  40. data/lib/assets/stylesheets/scrivito.css +180 -0
  41. data/lib/assets/stylesheets/scrivito_editing.css +2213 -0
  42. data/lib/generators/cms/migration/USAGE +9 -0
  43. data/lib/generators/cms/migration/migration_generator.rb +21 -0
  44. data/lib/generators/cms/migration/templates/migration.erb +10 -0
  45. data/lib/obj.rb +3 -0
  46. data/lib/scrivito/access_denied.rb +6 -0
  47. data/lib/scrivito/attribute_content.rb +194 -0
  48. data/lib/scrivito/backend_error.rb +4 -0
  49. data/lib/scrivito/basic_obj.rb +840 -0
  50. data/lib/scrivito/basic_widget.rb +238 -0
  51. data/lib/scrivito/blob.rb +48 -0
  52. data/lib/scrivito/cache.rb +41 -0
  53. data/lib/scrivito/cache_garbage_collector.rb +83 -0
  54. data/lib/scrivito/cache_middleware.rb +17 -0
  55. data/lib/scrivito/client_config.rb +62 -0
  56. data/lib/scrivito/client_error.rb +12 -0
  57. data/lib/scrivito/cms_accessible.rb +30 -0
  58. data/lib/scrivito/cms_backend.rb +238 -0
  59. data/lib/scrivito/cms_cache_storage.rb +51 -0
  60. data/lib/scrivito/cms_dispatch_controller.rb +46 -0
  61. data/lib/scrivito/cms_env.rb +63 -0
  62. data/lib/scrivito/cms_field_tag.rb +112 -0
  63. data/lib/scrivito/cms_rest_api.rb +151 -0
  64. data/lib/scrivito/cms_rest_api/attribute_serializer.rb +98 -0
  65. data/lib/scrivito/cms_rest_api/blob_uploader.rb +18 -0
  66. data/lib/scrivito/cms_rest_api/widget_extractor.rb +42 -0
  67. data/lib/scrivito/cms_test_request.rb +23 -0
  68. data/lib/scrivito/communication_error.rb +17 -0
  69. data/lib/scrivito/comparison.rb +67 -0
  70. data/lib/scrivito/configuration.rb +221 -0
  71. data/lib/scrivito/connection_manager.rb +100 -0
  72. data/lib/scrivito/content_conversion.rb +43 -0
  73. data/lib/scrivito/content_service.rb +118 -0
  74. data/lib/scrivito/content_state.rb +109 -0
  75. data/lib/scrivito/content_state_caching.rb +47 -0
  76. data/lib/scrivito/content_state_visitor.rb +19 -0
  77. data/lib/scrivito/controller_runtime.rb +35 -0
  78. data/lib/scrivito/date_attribute.rb +16 -0
  79. data/lib/scrivito/deprecation.rb +21 -0
  80. data/lib/scrivito/diff.rb +110 -0
  81. data/lib/scrivito/editing_context.rb +106 -0
  82. data/lib/scrivito/editing_context_middleware.rb +60 -0
  83. data/lib/scrivito/engine.rb +65 -0
  84. data/lib/scrivito/errors.rb +11 -0
  85. data/lib/scrivito/html_string.rb +18 -0
  86. data/lib/scrivito/link.rb +187 -0
  87. data/lib/scrivito/link_parser.rb +81 -0
  88. data/lib/scrivito/log_subscriber.rb +29 -0
  89. data/lib/scrivito/migration.rb +2 -0
  90. data/lib/scrivito/migrations.rb +12 -0
  91. data/lib/scrivito/migrations/cms_backend.rb +94 -0
  92. data/lib/scrivito/migrations/installer.rb +45 -0
  93. data/lib/scrivito/migrations/migration.rb +93 -0
  94. data/lib/scrivito/migrations/migration_dsl.rb +143 -0
  95. data/lib/scrivito/migrations/migration_store.rb +23 -0
  96. data/lib/scrivito/migrations/migrator.rb +135 -0
  97. data/lib/scrivito/migrations/workspace_lock.rb +39 -0
  98. data/lib/scrivito/model_identity.rb +13 -0
  99. data/lib/scrivito/modification.rb +8 -0
  100. data/lib/scrivito/named_link.rb +75 -0
  101. data/lib/scrivito/network_error.rb +11 -0
  102. data/lib/scrivito/obj_data.rb +140 -0
  103. data/lib/scrivito/obj_data_from_hash.rb +31 -0
  104. data/lib/scrivito/obj_data_from_service.rb +84 -0
  105. data/lib/scrivito/obj_params_parser.rb +61 -0
  106. data/lib/scrivito/obj_search_builder.rb +62 -0
  107. data/lib/scrivito/obj_search_enumerator.rb +374 -0
  108. data/lib/scrivito/rate_limit_exceeded.rb +5 -0
  109. data/lib/scrivito/revision.rb +9 -0
  110. data/lib/scrivito/string_tagging.rb +18 -0
  111. data/lib/scrivito/text_link.rb +52 -0
  112. data/lib/scrivito/text_link_conversion.rb +52 -0
  113. data/lib/scrivito/type_computer.rb +34 -0
  114. data/lib/scrivito/widget_field_params.rb +61 -0
  115. data/lib/scrivito/widget_garbage_collection.rb +97 -0
  116. data/lib/scrivito/workspace.rb +222 -0
  117. data/lib/scrivito/workspace_data_from_service.rb +80 -0
  118. data/lib/scrivito/workspace_selection_middleware.rb +23 -0
  119. data/lib/scrivito_sdk.rb +19 -0
  120. data/lib/tasks/cache.rake +12 -0
  121. data/lib/tasks/migration.rake +35 -0
  122. data/lib/widget.rb +3 -0
  123. metadata +291 -0
@@ -0,0 +1,112 @@
1
+ module Scrivito
2
+
3
+ # this class is the server-side equivalent of the JavaScript class `cms_field_element`
4
+ class CmsFieldTag < Struct.new(
5
+ :view_context, :editing_context, :obj_or_widget, :field_name
6
+ )
7
+
8
+ def options
9
+ formatted_options = {}
10
+ raw_options.each do |key, value|
11
+ formatted_options["data-scrivito-#{key}"] = value
12
+ end
13
+
14
+ formatted_options
15
+ end
16
+
17
+ def field_type
18
+ @field_type ||= obj_or_widget.type_of_attribute(field_name)
19
+ end
20
+
21
+ def default_content
22
+ calculate_content_and_modification
23
+ @content_and_modification.first
24
+ end
25
+
26
+ def modification_info
27
+ calculate_content_and_modification
28
+ @content_and_modification.second
29
+ end
30
+
31
+ private
32
+
33
+ def calculate_content_and_modification
34
+ @content_and_modification ||=
35
+ comparison.diff_for(obj_or_widget, field_name) || [current_value, nil]
36
+ end
37
+
38
+ def raw_options
39
+ return {} unless inplace_editing_allowed?
40
+
41
+ options = {
42
+ 'private-field-workspace-id' => current_workspace_id,
43
+ 'field-name' => field_name,
44
+ 'field-obj-class' => obj_or_widget.obj_class_name,
45
+ 'field-type' => field_type,
46
+ }
47
+
48
+ modification = comparison.modification_for_attribute(obj_or_widget, field_name)
49
+ if modification == Modification::EDITED && field_type != 'widget'
50
+ options['field-modification'] = modification
51
+ end
52
+
53
+ if obj_or_widget.kind_of?(BasicWidget)
54
+ options['private-field-id'] = obj_or_widget.obj.id
55
+ options['private-field-widget-id'] = obj_or_widget.id
56
+ else
57
+ options['private-field-id'] = obj_or_widget.id
58
+ end
59
+
60
+ if FIELD_TYPES_WITH_ORIGINAL_CONTENT.include?(field_type)
61
+ original_value = view_context.display_original_value(current_value)
62
+ original_content = original_content(field_type, original_value)
63
+ options['private-field-original-content'] = MultiJson.encode(original_content)
64
+ end
65
+
66
+ if field_type == 'widget'
67
+ valid_widget_classes = obj_or_widget.valid_widget_classes_for(field_name.to_s)
68
+ if valid_widget_classes
69
+ options['private-field-widget-allowed-classes'] = valid_widget_classes.to_json
70
+ end
71
+ end
72
+
73
+ options
74
+ end
75
+
76
+ def comparison
77
+ editing_context.comparison
78
+ end
79
+
80
+ def current_value
81
+ obj_or_widget[field_name]
82
+ end
83
+
84
+ def current_workspace_id
85
+ Workspace.current.id
86
+ end
87
+
88
+ FIELD_TYPES_WITH_ORIGINAL_CONTENT = %w[
89
+ enum
90
+ html
91
+ multienum
92
+ reference
93
+ referencelist
94
+ string
95
+ text
96
+ ]
97
+
98
+ def inplace_editing_allowed?
99
+ editing_context.authenticated_editor?
100
+ end
101
+
102
+ def original_content(field_type, field_value)
103
+ case field_type
104
+ when 'reference' then field_value.try(:id)
105
+ when 'referencelist' then field_value.map(&:id)
106
+ else field_value
107
+ end
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,151 @@
1
+ require 'restclient'
2
+ require 'active_support/all'
3
+
4
+ module Scrivito
5
+
6
+ # Provides a simple wrapper for the CMS Rest API.
7
+ #
8
+ # @example Request the published workspace:
9
+ # Scrivito::CmsRestApi.get('workspaces/published')
10
+ #
11
+ # @example Create a new Obj:
12
+ # Scrivito::CmsRestApi.post('workspaces/001384beff9e5845/objs',
13
+ # {'obj' => {'_path' => '/new_obj', '_obj_class' => 'Publication'}})
14
+ #
15
+ # @example Update an Obj:
16
+ # Scrivito::CmsRestApi.put('workspaces/001384beff9e5845/objs/9e432077f0412a63',
17
+ # {'obj' => {'title' => 'new title'}})
18
+ #
19
+ # @example Delete an Obj:
20
+ # Scrivito::CmsRestApi.delete('workspaces/001384beff9e5845/objs/f4123622ff07b70b')
21
+ #
22
+ # @example Specify a poll interval (in seconds; default: 2) to use in case the response
23
+ # is a task reference response and the final response is polled for:
24
+ # Scrivito::CmsRestApi.put('workspace/rtc/publish', nil, :interval => 10)
25
+ #
26
+ # @example Return immediately with the first response (without polling in case it is a
27
+ # task reference response):
28
+ # Scrivito::CmsRestApi.task_unaware_request(:put, 'workspace/rtc/publish', nil)
29
+ #
30
+ class CmsRestApi
31
+ METHOD_TO_NET_HTTP_CLASS = {
32
+ :get => Net::HTTP::Get,
33
+ :put => Net::HTTP::Put,
34
+ :post => Net::HTTP::Post,
35
+ :delete => Net::HTTP::Delete,
36
+ }.freeze
37
+
38
+ def self.get(resource_path, payload = nil, options = nil)
39
+ request_cms_api(:get, resource_path, payload, options)
40
+ end
41
+
42
+ def self.put(resource_path, payload, options = nil)
43
+ request_cms_api(:put, resource_path, payload, options)
44
+ end
45
+
46
+ def self.post(resource_path, payload, options = nil)
47
+ request_cms_api(:post, resource_path, payload, options)
48
+ end
49
+
50
+ def self.delete(resource_path, payload = nil, options = nil)
51
+ request_cms_api(:delete, resource_path, payload, options)
52
+ end
53
+
54
+ def self.task_unaware_request(method, resource_path, payload = nil)
55
+ raise "Unexpected method #{method}" unless [:delete, :get, :post, :put].include?(method)
56
+ response_for_request_cms_api(method, resource_path, payload)
57
+ end
58
+
59
+ class << self
60
+
61
+ private
62
+
63
+ def request_cms_api(action, resource_path, payload, options)
64
+ decoded = response_for_request_cms_api(action, resource_path, payload)
65
+ return decoded unless Hash === decoded
66
+ return decoded unless decoded.keys == ["task"]
67
+ task_data = decoded["task"]
68
+ return decoded unless Hash === task_data
69
+ task_path = "tasks/#{task_data["id"]}"
70
+ final_response(task_path, options)
71
+ end
72
+
73
+ def response_for_request_cms_api(method, resource_path, payload = nil)
74
+ request = method_to_net_http_class(method).new(path(resource_path))
75
+ set_headers(request)
76
+ request.body = MultiJson.encode(payload) if payload.present?
77
+
78
+ response = nil
79
+ retried = false
80
+ begin
81
+ # lower timeout back to DEFAULT_TIMEOUT once the backend has been fixed
82
+ response = connection_manager.request(request, 25)
83
+ rescue NetworkError => e
84
+ if method == :post || retried
85
+ raise e
86
+ else
87
+ retried = true
88
+ retry
89
+ end
90
+ end
91
+
92
+ handle_response(response)
93
+ end
94
+
95
+ def handle_response(response)
96
+ code = response.code.to_i
97
+ if response.code.start_with?('2')
98
+ MultiJson.load(response.body)
99
+ elsif response.code == '403'
100
+ raise AccessDenied.new(response.body)
101
+ else
102
+ begin
103
+ specific_output = MultiJson.decode(response.body)['error']
104
+
105
+ if response.code.start_with?('4')
106
+ raise ClientError.new(specific_output, code)
107
+ elsif response.code == '500' && specific_output
108
+ raise BackendError.new(specific_output, code)
109
+ else # 3xx and >500 are treated as NetworkErrors
110
+ raise NetworkError.new(response.body, code)
111
+ end
112
+ rescue MultiJson::DecodeError
113
+ raise NetworkError.new(response.body, code)
114
+ end
115
+ end
116
+ end
117
+
118
+ def final_response(task_path, options)
119
+ options ||= {}
120
+ wait = options[:interval].presence.try(:to_f) || 2
121
+ task_data = response = nil
122
+ loop do
123
+ sleep wait
124
+ task_data = response_for_request_cms_api(:get, task_path, nil)
125
+ break unless task_data["status"] == "open"
126
+ end
127
+ return task_data["result"] if task_data["status"] == "success"
128
+ message = task_data["message"] || "Missing error message in task response #{task_data}"
129
+ raise ClientError.new(message, 400)
130
+ end
131
+
132
+ def set_headers(request)
133
+ request.basic_auth('api_token', Configuration.api_key)
134
+ request['Content-type'] = 'application/json'
135
+ request['Accept'] = 'application/json'
136
+ end
137
+
138
+ def path(path)
139
+ "/#{Configuration.endpoint_uri.path}/#{path}".squeeze('/')
140
+ end
141
+
142
+ def connection_manager
143
+ ConnectionManager.instance
144
+ end
145
+
146
+ def method_to_net_http_class(method)
147
+ METHOD_TO_NET_HTTP_CLASS.fetch(method)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,98 @@
1
+ module Scrivito
2
+ class CmsRestApi
3
+ module AttributeSerializer
4
+ class << self
5
+
6
+ def generate_widget_pool_changes(widget_hash)
7
+ widget_pool_changes = {}
8
+
9
+ widget_hash.each do |widget, attributes|
10
+ if attributes.present?
11
+ widget_pool_changes[widget.id] =
12
+ self.convert(attributes)
13
+ else # Empty hash or nil
14
+ widget_pool_changes[widget.id] = attributes
15
+ end
16
+ end
17
+
18
+ widget_pool_changes
19
+ end
20
+
21
+ def convert(attributes)
22
+ Hash[
23
+ attributes.map do |attribute_name, value|
24
+ attribute_name = attribute_name.to_s
25
+
26
+ converted_value = if value.nil? || value == []
27
+ nil
28
+ elsif link_array?(value)
29
+ convert_links(value)
30
+ elsif value.is_a?(BasicObj)
31
+ value.id
32
+ elsif widget_array?(value)
33
+ convert_widgets(value)
34
+ elsif obj_array?(value)
35
+ value.map(&:id)
36
+ elsif value.is_a?(Time) || value.is_a?(Date)
37
+ convert_time(value)
38
+ elsif value.is_a?(Array)
39
+ value.map(&:to_s)
40
+ elsif value.is_a?(File)
41
+ Scrivito::CmsRestApi::BlobUploader.upload_file(value)
42
+ else
43
+ value.to_s
44
+ end
45
+
46
+ [attribute_name, converted_value]
47
+ end
48
+ ]
49
+ end
50
+
51
+ def convert_time(point_in_time)
52
+ time_object = if point_in_time.instance_of?(Date)
53
+ point_in_time.to_time
54
+ else
55
+ point_in_time.to_time.utc
56
+ end
57
+
58
+ time_object.strftime("%Y%m%d%H%M%S")
59
+ end
60
+
61
+ private
62
+
63
+ def widget_array?(value)
64
+ value.is_a?(Array) && value.all? do |element|
65
+ element.is_a?(BasicWidget)
66
+ end
67
+ end
68
+
69
+ def convert_widgets(value)
70
+ converted_widgets = value.map do |widget|
71
+ { 'widget' => widget.id }
72
+ end
73
+
74
+ { 'list' => converted_widgets }
75
+ end
76
+
77
+ def link_array?(value)
78
+ value.is_a?(Array) && value.all? do |element|
79
+ element.is_a?(Link)
80
+ end
81
+ end
82
+
83
+ def obj_array?(value)
84
+ value.is_a?(Array) && value.all? do |element|
85
+ element.is_a?(BasicObj)
86
+ end
87
+ end
88
+
89
+ def convert_links(links)
90
+ links.map do |link|
91
+ link.to_cms_api_linklist_params
92
+ end
93
+ end
94
+
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,18 @@
1
+ module Scrivito
2
+ class CmsRestApi
3
+ module BlobUploader
4
+ class << self
5
+ def upload_file(file)
6
+ upload_permission = CmsRestApi.get('blobs/upload_permission')
7
+
8
+ fields = upload_permission['fields'].to_a + [[:file, file]]
9
+
10
+ # Net/HTTP does not support multipart forms
11
+ RestClient.post(upload_permission['url'], fields)
12
+
13
+ upload_permission['blob']
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ module Scrivito
2
+ class CmsRestApi
3
+ class WidgetExtractor
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)
8
+ extracted_widgets = {}
9
+
10
+ updated_attributes.each do |attribute_name, value|
11
+ if value.is_a?(Array) && value.all? {|w| w.is_a?(BasicWidget)}
12
+ value.each do |widget|
13
+ unless widget.persisted?
14
+ widget.id = if obj.present?
15
+ obj.generate_widget_pool_id
16
+ else
17
+ BasicObj.generate_widget_pool_id
18
+ end
19
+
20
+ widget.obj = obj
21
+
22
+ extracted_widgets[widget] = widget.attributes_to_be_saved
23
+ extracted_widgets.merge!(self.call(widget.attributes_to_be_saved, obj))
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ extracted_widgets
30
+ end
31
+
32
+ def self.notify_persisted_widgets(obj, widget_hash)
33
+ widget_hash.each do |widget, attributes|
34
+ if attributes.present?
35
+ widget.obj = obj
36
+ widget.forget_unsaved_attribtues
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ module Scrivito
2
+ module CmsTestRequest
3
+ def for_cms_object(test_obj=nil)
4
+ # prepare the env as if the given obj was loaded by CmsEnv
5
+ env[Scrivito::CmsEnv::OBJ_ENV_KEY] = test_obj if test_obj
6
+ class << self
7
+ # This special behaviour is necessary for testing controllers that descend from DefaultCmsController.
8
+ # These controllers do not have explicit routes by default.
9
+ # Instead, they are reached via the CmsDispatchController.
10
+ # The TestRequest must therefore use CmsDispatchController as a fallback route.
11
+ def assign_parameters(routes, controller_path, action, parameters = {})
12
+ super(routes, controller_path, action, parameters)
13
+ rescue ActionController::RoutingError, AbstractController::ActionNotFound => e
14
+ begin
15
+ super(routes, "scrivito/cms_dispatch", action, parameters)
16
+ rescue ActionController::RoutingError, AbstractController::ActionNotFound
17
+ raise e
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end