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,118 @@
1
+ require 'timeout'
2
+ require 'net/http'
3
+
4
+ module Scrivito
5
+ class ContentService
6
+ class RateLimitExceeded < StandardError
7
+ def initialize(retry_after)
8
+ @retry_after = retry_after
9
+ end
10
+
11
+ attr_reader :retry_after
12
+ end
13
+
14
+ class MaxSleepTimeReached < StandardError
15
+ end
16
+
17
+ class Delay
18
+ def initialize
19
+ @sleep_count = 0
20
+ @total_sleep_time = 0
21
+ end
22
+
23
+ def next_sleep_time(min_sleep = 0)
24
+ sleep_time = [2 ** @sleep_count * 0.5, min_sleep.to_f].max
25
+ @total_sleep_time += sleep_time
26
+ raise MaxSleepTimeReached if @total_sleep_time > 40
27
+ @sleep_count += 1
28
+ sleep_time
29
+ end
30
+ end
31
+
32
+ class << self
33
+ @next_request_not_before = nil
34
+
35
+ def query(path, payload, options={})
36
+ timeout = options.fetch(:timeout, ConnectionManager::DEFAULT_TIMEOUT)
37
+ retry_until_timeout_on_rate_limit_exceeded do
38
+ request = build_request(path, payload)
39
+ if @next_request_not_before && @next_request_not_before > (now = Time.now)
40
+ sleep @next_request_not_before - now
41
+ end
42
+
43
+ response = nil
44
+ retry_once_on_network_error do
45
+ response = connection_manager.request(request, timeout)
46
+ end
47
+ handle_response(response)
48
+ end
49
+ end
50
+
51
+ # for tests only
52
+ def forget_retry_after
53
+ @next_request_not_before = nil
54
+ end
55
+
56
+ def connection_manager
57
+ ConnectionManager.instance
58
+ end
59
+
60
+ private
61
+
62
+ def retry_once_on_network_error
63
+ retried = false
64
+ begin
65
+ yield
66
+ rescue NetworkError => e
67
+ raise e if retried
68
+ retried = true
69
+ retry
70
+ end
71
+ end
72
+
73
+ def retry_until_timeout_on_rate_limit_exceeded
74
+ delay = Delay.new
75
+ begin
76
+ yield
77
+ rescue RateLimitExceeded => e
78
+ begin
79
+ sleep delay.next_sleep_time(e.retry_after)
80
+ rescue MaxSleepTimeReached
81
+ raise ::Scrivito::RateLimitExceeded.new('rate limit exceeded', 429)
82
+ end
83
+ retry
84
+ end
85
+ end
86
+
87
+ def build_request(path, payload)
88
+ url = "#{Configuration.endpoint_uri.path}/#{path}".squeeze('/')
89
+ request = Net::HTTP::Post.new(url, request_headers)
90
+ request.basic_auth('api_token', Configuration.api_key)
91
+ request.body = MultiJson.dump(payload)
92
+ request
93
+ end
94
+
95
+ def handle_response(response)
96
+ if response.code.first == '2'
97
+ retry_after = response["Retry-After"]
98
+ @next_request_not_before = retry_after ? Time.now + retry_after.to_f : nil
99
+ MultiJson.load(response.body)
100
+ elsif response.code == "429"
101
+ raise RateLimitExceeded.new(response["Retry-After"])
102
+ elsif response.code == '403'
103
+ raise AccessDenied.new(response.body)
104
+ else
105
+ raise NetworkError.new("Server responded with status code #{response.code}", response.code)
106
+ end
107
+ end
108
+
109
+ def request_headers
110
+ {
111
+ 'Accept' => 'application/json',
112
+ 'Content-Type' => 'application/json',
113
+ }
114
+ end
115
+ end
116
+
117
+ end
118
+ end
@@ -0,0 +1,109 @@
1
+ module Scrivito
2
+
3
+ # This class represents a single instance of a content state.
4
+ # Basically a content state has three important characteristics: cache with obj datas,
5
+ # changes representing changed objs and ways they has been changed and an ancestor content state.
6
+ class ContentState < Struct.new(:content_state_id, :changes, :changes_index, :from_content_state_id)
7
+ class << self
8
+ private :new
9
+
10
+ # Creates a new content state with given changes and ancestor (optional).
11
+ def create(attributes)
12
+ new(attributes).tap do |content_state|
13
+ content_state.index_changes!
14
+ CmsCacheStorage.write_content_state(content_state.content_state_id, content_state.to_hash)
15
+ end
16
+ end
17
+
18
+ # Finds a previously saved content state.
19
+ # Returns nil if not found.
20
+ def find(content_state_id)
21
+ if content_state_data = CmsCacheStorage.read_content_state(content_state_id)
22
+ new(content_state_data)
23
+ end
24
+ end
25
+
26
+ # Fetches an existing workspace.
27
+ # If not found creates a new one and returns it.
28
+ def find_or_create(content_state_id)
29
+ find(content_state_id) || create(content_state_id: content_state_id)
30
+ end
31
+ end
32
+
33
+ def initialize(attributes)
34
+ super(*attributes.symbolize_keys.values_at(:content_state_id, :changes, :changes_index,
35
+ :from_content_state_id))
36
+ end
37
+
38
+ # Stores arbitrary data in cache.
39
+ # Cache key is build from given index and key.
40
+ def save_obj_data(index, key, data)
41
+ CmsCacheStorage.write_obj_data(content_state_id, index, key, data)
42
+ end
43
+
44
+ # Fetches previously stored arbitrary data from cache.
45
+ # Returns nil if nothing found.
46
+ # Cache key is build from given index and key.
47
+ def find_obj_data(index, key)
48
+ CmsCacheStorage.read_obj_data(content_state_id, index, key)
49
+ end
50
+
51
+ # Fetches and caches the ancestor.
52
+ # Returns nil if there is no ancestor.
53
+ def from_content_state
54
+ @from_content_state ||= self.class.find(from_content_state_id)
55
+ end
56
+
57
+ # Determines whether given data is still up-to-date for given index and key.
58
+ def has_changes_for?(index, key, data)
59
+ case index
60
+ when 'id'
61
+ id_index.include?(key)
62
+ when 'path'
63
+ path_index.include?(key) || data.first && id_index.include?(data.first['_id'].first)
64
+ when 'ppath'
65
+ ppath_index.include?(key) || data.find { |d| id_index.include?(d['_id'].first) }
66
+ end
67
+ end
68
+
69
+ # Computes for a given changes feed a set of access-efficient indexes.
70
+ def index_changes!
71
+ id_index, path_index, ppath_index = Set.new, Set.new, Set.new
72
+ if changes.present?
73
+ changes.each do |hash|
74
+ id_index.add(hash['id'])
75
+ if path = hash['modified_path']
76
+ path_index.add(path)
77
+ ppath_index.add(path.gsub(/\/[^\/]+$/, '').presence || '/') if path != '/'
78
+ end
79
+ end
80
+ end
81
+ self.changes = nil
82
+ self.changes_index = {'id' => id_index, 'path' => path_index, 'ppath' => ppath_index}
83
+ end
84
+
85
+ # Returns a hash representation of a content state for serialization purpose.
86
+ def to_hash
87
+ {
88
+ content_state_id: content_state_id,
89
+ changes_index: changes_index,
90
+ from_content_state_id: from_content_state_id
91
+ }
92
+ end
93
+
94
+ private
95
+
96
+ def id_index
97
+ @id_index ||= changes_index['id']
98
+ end
99
+
100
+ def path_index
101
+ @path_index ||= changes_index['path']
102
+ end
103
+
104
+ def ppath_index
105
+ @ppath_index ||= changes_index['ppath']
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,47 @@
1
+ module Scrivito
2
+
3
+ # This module provides advances auto-invalidating caching mechanism for storing obj data.
4
+ #
5
+ # To keep it up-to-date its caches and changes should be updated periodically
6
+ # It's changes should be updated every time a new workspace data has been fetched.
7
+ # It's caches should be updated every time a new obj data has been fetched.
8
+ module ContentStateCaching
9
+ class << self
10
+ # How deep should a content state chain be inspected. Default depth is 20.
11
+ attr_accessor :cache_lookup_depth
12
+
13
+ # At which lookup depth to copy a hit found in an ancestor content state
14
+ # to the current content state's cache. Default depth is 5.
15
+ attr_accessor :cache_replication_depth
16
+
17
+ # Updates caches with data from given workspace.
18
+ # Should be called every time a new OBJ data has been fetched.
19
+ def store_obj_data(content_state, index, key, data)
20
+ content_state.save_obj_data(index, key, data) if content_state
21
+ end
22
+
23
+ # Fetches up-to-date obj data for given workspace, index and key.
24
+ # Returns nil if no up-to-date data found.
25
+ def find_obj_data(current_content_state , index, key)
26
+ if index == 'permalink'
27
+ current_content_state.find_obj_data(index, key)
28
+ else
29
+ visitor = ContentStateVisitor.new(current_content_state)
30
+ cache_lookup_depth.times do |depth|
31
+ return unless content_state = visitor.visit_next
32
+ if hit = content_state.find_obj_data(index, key)
33
+ visitor.visited_except_current.each { |cs| return if cs.has_changes_for?(index, key, hit) }
34
+ current_content_state.save_obj_data(index, key, hit) if depth >= cache_replication_depth
35
+ return hit
36
+ end
37
+ end
38
+ nil
39
+ end
40
+ end
41
+ end
42
+
43
+ self.cache_replication_depth = 5
44
+ self.cache_lookup_depth = 20
45
+ end
46
+
47
+ end
@@ -0,0 +1,19 @@
1
+ module Scrivito
2
+ class ContentStateVisitor
3
+ def initialize(start_content_state)
4
+ @next_content_state, @visited = start_content_state, []
5
+ end
6
+
7
+ def visit_next
8
+ if content_state = @next_content_state
9
+ @visited << content_state
10
+ @next_content_state = content_state.from_content_state
11
+ content_state
12
+ end
13
+ end
14
+
15
+ def visited_except_current
16
+ @visited[0..-2]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ require 'active_support/core_ext/module/attr_internal'
2
+
3
+ module Scrivito
4
+
5
+ module ControllerRuntime
6
+ extend ActiveSupport::Concern
7
+
8
+ protected
9
+
10
+ attr_internal :rc_runtime
11
+
12
+ def cleanup_view_runtime
13
+ rc_rt_before_render = Scrivito::LogSubscriber.reset_runtime
14
+ runtime = super
15
+ rc_rt_after_render = Scrivito::LogSubscriber.reset_runtime
16
+ self.rc_runtime = rc_rt_before_render + rc_rt_after_render
17
+ runtime - rc_rt_after_render
18
+ end
19
+
20
+ def append_info_to_payload(payload)
21
+ super
22
+ payload[:rc_runtime] = rc_runtime
23
+ end
24
+
25
+ module ClassMethods
26
+ def log_process_action(payload)
27
+ messages, rc_runtime = super, payload[:rc_runtime]
28
+ messages << ("Scrivito: %.1fms" % rc_runtime.to_f)
29
+ messages
30
+ end
31
+ end
32
+ end
33
+
34
+ end # module Scrivito
35
+
@@ -0,0 +1,16 @@
1
+ module Scrivito
2
+
3
+ # Adds support for string columns which contain ISO dates
4
+ module DateAttribute
5
+ def self.parse(iso_date_time)
6
+ return nil unless iso_date_time
7
+
8
+ if iso_date_time.to_s =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/
9
+ Time.utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i).in_time_zone
10
+ else
11
+ raise "The value is not a valid ISO date time: #{iso_date_time.inspect}"
12
+ end
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,21 @@
1
+ module Scrivito
2
+ class Deprecation
3
+ class << self
4
+ def warn(message, source = caller(1))
5
+ ActiveSupport::Deprecation.warn(message, source)
6
+ end
7
+
8
+ def warn_method(depricated_method_name, new_method_name = nil)
9
+ warn(warn_method_message(depricated_method_name, new_method_name), caller(1))
10
+ end
11
+
12
+ private
13
+
14
+ def warn_method_message(depricated_method_name, new_method_name = nil)
15
+ message = "The method #{depricated_method_name} will be removed from the Scrivito SDK. "
16
+ message << "Please use #{new_method_name} instead" if new_method_name
17
+ message
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,110 @@
1
+ require "diff-lcs"
2
+ require "hyp_diff"
3
+
4
+ module Scrivito
5
+
6
+ class Diff; class << self
7
+
8
+ def for(mode, field_type, old, new)
9
+ case field_type
10
+ when "widget"
11
+ ListComparison.for(mode, old || [], new || [])
12
+ when "html"
13
+ for_html(mode, old, new)
14
+ when "text"
15
+ for_string(mode, old, new)
16
+ when "string"
17
+ for_string(mode, old, new)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def for_string(mode, old, new)
24
+ for_html(mode, ERB::Util.h(old), ERB::Util.h(new))
25
+ end
26
+
27
+ def for_html(mode, old, new)
28
+ return nil if !old
29
+ return nil if !new
30
+
31
+ hyp_diff_options =
32
+ case mode
33
+ when "added"
34
+ {render_deletion: proc {}, markup_from: "after"}
35
+ when "deleted"
36
+ {render_insertion: proc {}, markup_from: "before"}
37
+ when "diff"
38
+ {}
39
+ else
40
+ return nil
41
+ end
42
+
43
+ diff = HypDiff.compare(old, new, hyp_diff_options)
44
+ [StringTagging.tag_as_html(diff)]
45
+ end
46
+
47
+ class ListComparison
48
+ def self.for(mode, old, new)
49
+ new(mode).compare(old, new).result
50
+ end
51
+
52
+ def initialize(mode)
53
+ @mode = mode
54
+ @list = []
55
+ @modifications = []
56
+ end
57
+
58
+ def compare(old, new)
59
+ ::Diff::LCS.sdiff(old, new).map do |change|
60
+ case change.action
61
+ when "-" then
62
+ note_deletion(change)
63
+ when "+" then
64
+ note_insertion(change)
65
+ when "=" then
66
+ note_equal(change)
67
+ when "!" then
68
+ note_deletion(change)
69
+ note_insertion(change)
70
+ else
71
+ raise "unexpected change.action #{change.action}"
72
+ end
73
+ end
74
+
75
+ self
76
+ end
77
+
78
+ def result
79
+ [@list, @modifications]
80
+ end
81
+
82
+ private
83
+
84
+ def note_equal(change)
85
+ @modifications << nil
86
+ if @mode == "deleted"
87
+ @list << change.old_element
88
+ else
89
+ @list << change.new_element
90
+ end
91
+ end
92
+
93
+ def note_deletion(change)
94
+ unless @mode == "added"
95
+ @modifications << "deleted"
96
+ @list << change.old_element
97
+ end
98
+ end
99
+
100
+ def note_insertion(change)
101
+ unless @mode == "deleted"
102
+ @modifications << "new"
103
+ @list << change.new_element
104
+ end
105
+ end
106
+ end
107
+
108
+ end; end
109
+
110
+ end