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,31 @@
1
+ module Scrivito
2
+
3
+ class ObjDataFromHash < ObjData
4
+ def initialize(hash, type_hash = {})
5
+ @hash = hash.stringify_keys
6
+ @type_hash = type_hash.stringify_keys
7
+ end
8
+
9
+ def raw_value_and_type_of(attribute_name)
10
+ if has_custom_attribute?(attribute_name)
11
+ [@hash[attribute_name], @type_hash[attribute_name]]
12
+ end
13
+ end
14
+
15
+ def has_custom_attribute?(name)
16
+ @hash.has_key?(name) || name == '_id'
17
+ end
18
+
19
+ def all_attributes
20
+ @all_attributes ||= begin
21
+ @hash.keys |
22
+ INTERNAL_KEYS.to_a |
23
+ SPECIAL_KEYS.to_a |
24
+ %w[_id]
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
@@ -0,0 +1,84 @@
1
+ module Scrivito
2
+
3
+ class ObjDataFromService < ObjData
4
+ def initialize(data)
5
+ @data = data
6
+ end
7
+
8
+ def raw_value_and_type_of(attribute_name)
9
+ if attribute_name == '_widget_pool'
10
+ [value_of_widget_pool, nil]
11
+ else
12
+ type_and_value = @data[attribute_name]
13
+
14
+ if type_and_value.present?
15
+ type = if type_and_value.length == 1
16
+ type_of_internal(attribute_name)
17
+ else
18
+ type_and_value.last
19
+ end
20
+
21
+ [convert_value(type_and_value.first, type), type]
22
+ end
23
+ end
24
+ end
25
+
26
+ def has_custom_attribute?(attribute_name)
27
+ is_custom_attribute?(attribute_name) && !!@data[attribute_name]
28
+ end
29
+
30
+ def all_attributes
31
+ @all_attributes ||= (@data.keys | INTERNAL_KEYS.to_a | SPECIAL_KEYS.to_a)
32
+ end
33
+
34
+ private
35
+
36
+ def convert_value(value, type)
37
+ case type
38
+ when "html"
39
+ text_links_conversion.convert(value)
40
+ when "widget"
41
+ if !value
42
+ []
43
+ else
44
+ (value['list'] || []).map do |list_item|
45
+ list_item['widget']
46
+ end
47
+ end
48
+ else
49
+ value
50
+ end
51
+ end
52
+
53
+ def text_links_conversion
54
+ @text_links_conversion = TextLinkConversion.new(text_links_map)
55
+ end
56
+
57
+ def text_links_map
58
+ text_links = @data["_text_links"]
59
+ return nil unless text_links
60
+
61
+ text_links.first.each_with_object({}) do |link, map|
62
+ map[link["link_id"]] = TextLink.new(link)
63
+ end
64
+ end
65
+
66
+ def value_of_widget_pool
67
+ raw_value_of_widget_pool.dup.tap do |hash|
68
+ hash.each_pair do |widget_id, raw_widget_data|
69
+ hash[widget_id] = self.class.new(raw_widget_data)
70
+ end
71
+ end
72
+ end
73
+
74
+ def raw_value_of_widget_pool
75
+ @data['_widget_pool'].first
76
+ end
77
+
78
+ def is_custom_attribute?(attribute_name)
79
+ !attribute_name.starts_with?('_') && !SPECIAL_KEYS.include?(attribute_name)
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,61 @@
1
+ module Scrivito
2
+ class ObjParamsParser
3
+ def initialize(host, port)
4
+ @host = host
5
+ @port = port
6
+ end
7
+
8
+ def parse(obj, orig_params)
9
+ raise "Required parameter 'obj' is missing." unless orig_params.present?
10
+ raise "Parameter 'obj' is not a hash." unless orig_params.is_a?(Hash)
11
+
12
+ params = orig_params.dup
13
+
14
+ if obj
15
+ widget_field_params = WidgetFieldParams.new
16
+
17
+ convert_params(params, obj, widget_field_params)
18
+
19
+ if widget_pool_params = params['_widget_pool']
20
+ convert_widget_pool_params(widget_pool_params, obj, widget_field_params)
21
+ end
22
+
23
+ additional_widget_pool_params = widget_field_params.pool_params
24
+ if additional_widget_pool_params.any?
25
+ params['_widget_pool'] ||= {}
26
+ params['_widget_pool'].merge!(additional_widget_pool_params)
27
+ end
28
+ end
29
+
30
+ params
31
+ end
32
+
33
+ private
34
+
35
+ def convert_widget_pool_params(widget_pool_params, obj, widget_field_params_parser)
36
+ widget_pool_params.each_pair do |widget_id, widget_params|
37
+ widget = obj.widget_from_pool(widget_id)
38
+ if widget_params.present?
39
+ convert_params(widget_params, widget, widget_field_params_parser)
40
+ end
41
+ end
42
+ end
43
+
44
+ def convert_params(params, obj, widget_field_params)
45
+ params.each do |key, value|
46
+ type = obj.type_of_attribute(key.to_s)
47
+
48
+ params[key] = case type
49
+ when 'html'
50
+ ContentConversion.convert_html_links(value, @host, @port)
51
+ when 'linklist'
52
+ ContentConversion.convert_linklist_urls(value, @host, @port)
53
+ when 'widget'
54
+ widget_field_params.convert(value)
55
+ else
56
+ value
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,62 @@
1
+ module Scrivito
2
+
3
+ class ObjSearchBuilder < Struct.new(:query)
4
+
5
+ def build
6
+ reset_enumerator
7
+
8
+ set_predicates
9
+ set_offset
10
+ set_order
11
+ set_batch_size
12
+ set_format
13
+ set_include_deleted
14
+
15
+ enumerator
16
+ end
17
+
18
+ private
19
+
20
+ def reset_enumerator
21
+ @enumerator = nil
22
+ end
23
+
24
+ def enumerator
25
+ @enumerator ||= ObjSearchEnumerator.new(nil)
26
+ end
27
+
28
+ def set_predicates
29
+ query[:predicates].each do |p|
30
+ if p[:negate]
31
+ enumerator.and_not(p[:field], p[:operator], p[:value])
32
+ else
33
+ enumerator.and(p[:field], p[:operator], p[:value], p[:boost])
34
+ end
35
+ end
36
+ end
37
+
38
+ def set_offset
39
+ enumerator.offset(query[:offset].to_i) if query[:offset]
40
+ end
41
+
42
+ def set_order
43
+ enumerator.order(query[:order]) if query[:order]
44
+ enumerator.reverse_order if query[:reverse_order]
45
+ end
46
+
47
+ def set_batch_size
48
+ enumerator.batch_size(query[:batch_size]) if query[:batch_size]
49
+ end
50
+
51
+ def set_format
52
+ enumerator.format(query[:format]) if query[:format]
53
+ end
54
+
55
+ def set_include_deleted
56
+ enumerator.include_deleted if query[:include_deleted]
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
@@ -0,0 +1,374 @@
1
+ # encoding: UTF-8
2
+ module Scrivito
3
+ # Provides an enumerator for iterating over obj search results and retrieving obj instances.
4
+ # This is done using the {http://ruby-doc.org/core-1.8.7/Enumerable.html <code>Enumerable</code> mixin},
5
+ # which provides methods such as <code>map</code>, <code>select</code> or <code>take</code>.
6
+ #
7
+ # This enumerator is lazy. If for example you are looking for {BasicObj Obj}s with the ObjClass "Publication",
8
+ # and there are 93 objs in total, than <code>enum.take(10)</code> will fetch the first 10 objs only,
9
+ # ignoring the other 83 objs.
10
+ # This also means, that iterating multiple times over this enumerator causes the search results and objs to be fetched again.
11
+ # If you want to get all objs at once, use <code>enum.to_a</code>.
12
+ #
13
+ # To start searching use one of the {BasicObj Obj} methods that returns an {ObjSearchEnumerator}. The preferred way is to start with {BasicObj.where Obj.where}.
14
+ #
15
+ # == Currently available fields and their values
16
+ #
17
+ # [+:*+] Searches all fields.
18
+ # This is only possible with the operators +contais+ or +starts_with+.
19
+ # [+:id+] Id of an {BasicObj Obj}. This is a +string+ field.
20
+ # [+:_path+] Path of an {BasicObj Obj}. This is a +string+ field.
21
+ # [+:_name+] Name of an {BasicObj Obj}. This is a +string+ field.
22
+ # [+:title+] Title of an {BasicObj Obj}. This is a +string+ field.
23
+ # [+:body+] Body of an {BasicObj Obj}. This is an +html+ field. Thus, only the +contains+ and
24
+ # +contains_prefix+ operators can be applied to this field.
25
+ # [+:_obj_class+] ObjClass of an {BasicObj Obj}. This is a +string+ field.
26
+ # [+:_permalink+] Permalink of an {BasicObj Obj}. This is a +string+ field.
27
+ # [+:_last_changed+] Date of last change of an {BasicObj Obj}.
28
+ # [every <em><code>:custom_attribute</code></em>] Custom attribute of an {BasicObj Obj}. Note that
29
+ # depending on the attribute type (e.g. an
30
+ # +html+ field), some operators can not be applied.
31
+ #
32
+ # All values are stored as strings.
33
+ #
34
+ # Date values are stored in the format YYYYMMDDHHMMSS in UTC. For example, 2000-01-01 00:00:00 UTC is stored as "<code>20000101000000</code>".
35
+ # This is relevant for string comparisons using the +is_less_than+ and +is_greater_than+ operators.
36
+ #
37
+ # == Currently available operators
38
+ #
39
+ # For +:contains+ and +:contains_prefix+, the examples are based on the following field value:
40
+ # "Behind every cloud is another cloud."
41
+ #
42
+ # [+:contains+] Searches for one or more whole words. Each word needs to be present.
43
+ #
44
+ # Example subquery values:
45
+ #
46
+ # ✔ "behind cloud" (case insensitive)
47
+ #
48
+ # ✘ "behi clo" (not whole words)
49
+ #
50
+ # ✘ "behind everything" (second word does not match)
51
+ # [+:contains_prefix+] Searches for one prefix. A whole word is also a prefix.
52
+ #
53
+ # Example subquery values:
54
+ #
55
+ # ✔ "Clou" (case insensitive)
56
+ #
57
+ # ✔ "Every" (case insensitive)
58
+ #
59
+ # For +:equals+ and +:starts_with+, the examples are based on the following field value:
60
+ # "Some content."
61
+ #
62
+ # [+:equals+] The +field+ value needs to be identical to the +value+ of this subquery.
63
+ #
64
+ # Only applicable to +string+, +enum+, +multienum+ and +date+ fields.
65
+ #
66
+ # Example subquery values:
67
+ #
68
+ # ✔ "Some content." (exact value)
69
+ #
70
+ # ✘ "Some" (not exact value)
71
+ #
72
+ # [+:starts_with+] The +field+ value needs to start exactly with the +value+ of this subquery.
73
+ #
74
+ # Only applicable to +string+, +enum+, +multienum+ and +date+ fields.
75
+ #
76
+ # Example subquery values:
77
+ #
78
+ # ✔ "Som" (prefix of the value)
79
+ #
80
+ # ✘ "som" (incorrect case of prefix)
81
+ #
82
+ # ✘ "content" (not prefix of the whole value)
83
+ #
84
+ # For +:is_less_than+ and +:is_greater_than+, the examples are based on the following field value (date string):
85
+ # "20000101000000"
86
+ #
87
+ # [+:is_less_than+] Matches if the field string value is less than the subquery string value.
88
+ #
89
+ # Only applicable to +string+, +enum+, +multienum+ and +date+ fields.
90
+ #
91
+ # Example subquery values:
92
+ #
93
+ # ✔ "19991231235959" (is less than "20000101000000")
94
+ #
95
+ # ✘ "20000101000000" (equal, not less than)
96
+ #
97
+ # [+:is_greater_than+] Matches if the field string value is greater than the subquery string value.
98
+ #
99
+ # Only applicable to +string+, +enum+, +multienum+ and +date+ fields.
100
+ #
101
+ # Example subquery values:
102
+ #
103
+ # ✔ "20000101000001" (is greater than "20000101000000")
104
+ #
105
+ # ✘ "20000101000000" (equal, not greater than)
106
+ #
107
+ # @api public
108
+ class ObjSearchEnumerator
109
+ class UnregisteredObjFormat < StandardError; end
110
+
111
+ include Enumerable
112
+
113
+ attr_reader :query
114
+ def initialize(query)
115
+ @query = query
116
+ @options = {}
117
+ end
118
+
119
+ # @group Chainable methods
120
+
121
+ # Adds this additional AND subquery to this {ObjSearchEnumerator}.
122
+ #
123
+ # Compares the +field(s)+ with the +value(s)+ using the +operator+ of this subquery.
124
+ # All objs to which this criterion applies remain in the result set.
125
+ #
126
+ # @param [Symbol, String, Array<Symbol, String>] field Name(s) of the field(s) to be searched.
127
+ # For arrays, the subquery matches, if one or more of these fields meet this criterion.
128
+ # @param [Symbol, String] operator See "Currently available operators" at the top.
129
+ # @param [String, Array<String>] value The value(s) with which the field value(s) are compared using the +operator+ of this subquery.
130
+ # For arrays, the subquery matches, if the condition is met for one or more of the array elements.
131
+ # @param [Hash] boost A hash where the keys are field names and their values are boosting factors.
132
+ # Boosting factors must be in the range from 1 to 10.
133
+ # Boosting can only be applied to subqueries in which the +contains+ or +contains_prefix+ operator is used.
134
+ # @return [ObjSearchEnumerator]
135
+ # @api public
136
+ def and(field, operator, value, boost = nil)
137
+ real_operator = operator_mapping(operator)
138
+ subquery = {:field => field, :operator => real_operator, :value => convert_value(value)}
139
+ if boost.present?
140
+ valid_boost_operators = [:contains, :contains_prefix]
141
+ if valid_boost_operators.include?(operator.to_sym)
142
+ subquery[:boost] = boost
143
+ else
144
+ raise "Boost is not allowed with operator '#{operator}'. " +
145
+ "Valid operators are: #{valid_boost_operators.join(', ')}"
146
+ end
147
+ end
148
+ @size = nil
149
+ @query = (query || []) + [subquery]
150
+
151
+ self
152
+ end
153
+
154
+ # Adds this additional negated AND subquery to this {ObjSearchEnumerator}.
155
+ #
156
+ # Compares the +field(s)+ with the +value(s)+ using the negated +operator+ of this subquery.
157
+ # All objs to which this criterion applies are removed from the result set.
158
+ #
159
+ # @param [Symbol, String, Array<Symbol, String>] field Name(s) of the field(s) to be searched.
160
+ # For arrays, the subquery matches, if one or more of these fields meet this criterion.
161
+ # @param [Symbol, String] operator Only applicable to subqueries in which the +equals+,
162
+ # +starts_with+, +is_greater_than+ and +is_less_than+ operator is used
163
+ # (See "Currently available operators" at the top).
164
+ # @param [String, Array<String>] value The value(s) with which the field value(s) are compared using the +operator+ of this subquery.
165
+ # For arrays, the subquery matches, if the condition is met for one or more of the array elements.
166
+ # @return [ObjSearchEnumerator]
167
+ # @api public
168
+ def and_not(field, operator, value)
169
+ real_operator = operator_mapping(operator)
170
+ valid_negated_operators = [:equals, :starts_with, :is_greater_than, :is_less_than]
171
+ unless valid_negated_operators.include?(operator.to_sym)
172
+ raise "Negating operator '#{operator}' is not valid."
173
+ end
174
+ subquery = {:field => field, :operator => real_operator, :value => convert_value(value),
175
+ :negate => true}
176
+ @size = nil
177
+ @query = (query || []) + [subquery]
178
+
179
+ self
180
+ end
181
+
182
+ # Orders the results by +field_name+.
183
+ # @param [Symbol, String] field_name This parameter determines by which field the hits are sorted (e.g. +:_path+).
184
+ # @return [ObjSearchEnumerator]
185
+ # @api public
186
+ def order(field_name)
187
+ options[:sort_by] = field_name
188
+
189
+ self
190
+ end
191
+
192
+ # Reverses the order of the results. Requires {#order} to be specified before.
193
+ # @return [ObjSearchEnumerator]
194
+ # @api public
195
+ def reverse_order
196
+ options[:sort_by].present? or raise "A search order has to be specified"\
197
+ " before reverse_order can be applied."
198
+ @reverse_order = !@reverse_order
199
+
200
+ self
201
+ end
202
+
203
+ # Number of search results to be returned by each of the internal search requests.
204
+ # The default is +10+. The server may limit a large batch size to a reasonable value.
205
+ # @param [Integer] size A value in the range from +1+ to +100+.
206
+ # @return [ObjSearchEnumerator]
207
+ # @api public
208
+ def batch_size(size)
209
+ options[:size] = size
210
+
211
+ self
212
+ end
213
+
214
+
215
+ # Omits the first +amount+ number of {BasicObj Obj}s from the results. The default is +0+.
216
+ # @param [Integer] amount
217
+ # @return [ObjSearchEnumerator]
218
+ # @api public
219
+ def offset(amount)
220
+ options[:offset] ||= 0
221
+ options[:offset] += amount
222
+
223
+ self
224
+ end
225
+
226
+ def include_deleted
227
+ @include_deleted = true
228
+
229
+ self
230
+ end
231
+
232
+ # @!endgroup
233
+
234
+ # Iterates over the search result, yielding {BasicObj Obj}.
235
+ # @yield [Obj]
236
+ # @return [void]
237
+ # @api public
238
+ def each
239
+ offset = options[:offset] || 0
240
+ current_batch, total = fetch_next_batch(offset)
241
+ loop do
242
+ if current_batch.size == 0
243
+ if offset < total
244
+ current_batch, total = fetch_next_batch(offset)
245
+ else
246
+ raise StopIteration
247
+ end
248
+ end
249
+
250
+ offset += 1
251
+ hit = current_batch.shift
252
+ yield hit
253
+ end
254
+ end
255
+
256
+ # The total number of hits.
257
+ # @return [Integer]
258
+ # @api public
259
+ def size
260
+ return @size if @size
261
+
262
+ size_query = {
263
+ query: query,
264
+ size: 0
265
+ }
266
+ if @include_deleted
267
+ size_query[:options] = {
268
+ include_deleted: true
269
+ }
270
+ end
271
+
272
+ @size ||= CmsRestApi.get(resource_path, size_query)['total'].to_i
273
+ end
274
+
275
+ # load a single batch of search results from the backend.
276
+ # returns an array of Objs.
277
+ # will usually return `batch_size` results if available,
278
+ # but may occasionally return fewer than `batch_size` results (due to rate limit, for example).
279
+ # @api public
280
+ def load_batch
281
+ next_batch = fetch_next_batch(options[:offset] || 0)
282
+
283
+ formatter = @formatter || -> obj { obj.id }
284
+ next_batch.first.map { |obj| formatter.call(obj) }
285
+ end
286
+
287
+ def format(name)
288
+ if @formatter = Configuration.obj_formats[name]
289
+ self
290
+ else
291
+ raise UnregisteredObjFormat, "The format with name '#{name}' is not registered."
292
+ end
293
+ end
294
+
295
+ # @api public
296
+ alias_method :length, :size
297
+
298
+ # @api public
299
+ alias_method :count, :size
300
+
301
+ private
302
+
303
+ attr_reader :options
304
+
305
+ def convert_value(value)
306
+ if value.kind_of?(Array)
307
+ value.map{ |v| convert_single_value(v) }
308
+ else
309
+ convert_single_value(value)
310
+ end
311
+ end
312
+
313
+ def convert_single_value(value)
314
+ if value.is_a?(Time) || value.is_a?(Date)
315
+ CmsRestApi::AttributeSerializer.convert_time(value)
316
+ else
317
+ value
318
+ end
319
+ end
320
+
321
+ def operator_mapping(operator)
322
+ case operator.to_sym
323
+ when :contains
324
+ :search
325
+ when :contains_prefix
326
+ :prefix_search
327
+ when :equals
328
+ :equal
329
+ when :starts_with
330
+ :prefix
331
+ when :is_greater_than
332
+ :greater_than
333
+ when :is_less_than
334
+ :less_than
335
+ else
336
+ raise "Operator '#{operator}'' is not valid!"
337
+ end
338
+ end
339
+
340
+ def fetch_next_batch(offset)
341
+ request_result = CmsRestApi.get(resource_path, search_dsl(offset))
342
+
343
+ obj_ids = request_result['results'].map { |result| result['id'] }
344
+ objs = Obj.find_including_deleted(obj_ids)
345
+
346
+ @size = request_result['total'].to_i
347
+
348
+ [objs, @size]
349
+ end
350
+
351
+ def resource_path
352
+ "workspaces/#{Workspace.current.id}/objs/search"
353
+ end
354
+
355
+ def search_dsl(offset)
356
+ patches = {
357
+ offset: offset,
358
+ query: query,
359
+ }
360
+ if @reverse_order
361
+ patches[:sort_order] = options[:sort_by].present? ? :desc : :asc
362
+ end
363
+
364
+ if @include_deleted
365
+ patches[:options] = {
366
+ include_deleted: true
367
+ }
368
+ end
369
+
370
+ options.merge(patches)
371
+ end
372
+
373
+ end
374
+ end