infopark_cloud_connector 7.0.2 → 7.1.0

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 (61) hide show
  1. checksums.yaml +8 -8
  2. data/app/controllers/rails_connector/objs_controller.rb +29 -37
  3. data/app/helpers/rails_connector/cms_asset_helper.rb +1 -1
  4. data/app/helpers/rails_connector/cms_tag_helper.rb +1 -1
  5. data/app/helpers/rails_connector/default_cms_routing_helper.rb +1 -3
  6. data/app/helpers/rails_connector/display_helper.rb +5 -1
  7. data/app/views/rails_connector/_editing_javascript.html.erb +3 -2
  8. data/config/ca-bundle.crt +2 -2
  9. data/config/routes.rb +3 -0
  10. data/lib/assets/fonts/infopark_icons-webfont.eot +0 -0
  11. data/lib/assets/fonts/infopark_icons-webfont.ttf +0 -0
  12. data/lib/assets/fonts/infopark_icons-webfont.woff +0 -0
  13. data/lib/assets/images/apple-touch-icon.jpg +0 -0
  14. data/lib/assets/images/favicon.ico +0 -0
  15. data/lib/assets/images/ip_logo.svg +50 -0
  16. data/lib/assets/javascripts/infopark_editing.js +903 -164
  17. data/lib/assets/stylesheets/infopark_editing.css +222 -271
  18. data/lib/infopark_cloud_connector.rb +0 -1
  19. data/lib/rails_connector/attribute_content.rb +25 -74
  20. data/lib/rails_connector/backend_error.rb +4 -0
  21. data/lib/rails_connector/basic_obj.rb +173 -20
  22. data/lib/rails_connector/basic_widget.rb +42 -1
  23. data/lib/rails_connector/blob.rb +1 -1
  24. data/lib/rails_connector/cache_middleware.rb +2 -2
  25. data/lib/rails_connector/cms_backend.rb +51 -33
  26. data/lib/rails_connector/cms_rest_api.rb +13 -8
  27. data/lib/rails_connector/cms_rest_api/attribute_serializer.rb +62 -0
  28. data/lib/rails_connector/cms_rest_api/blob_uploader.rb +18 -0
  29. data/lib/rails_connector/communication_error.rb +17 -0
  30. data/lib/rails_connector/configuration.rb +28 -0
  31. data/lib/rails_connector/connection_manager.rb +2 -2
  32. data/lib/rails_connector/content_conversion.rb +16 -62
  33. data/lib/rails_connector/content_service.rb +2 -2
  34. data/lib/rails_connector/engine.rb +2 -1
  35. data/lib/rails_connector/html_string.rb +0 -1
  36. data/lib/rails_connector/link.rb +41 -33
  37. data/lib/rails_connector/link_parser.rb +72 -0
  38. data/lib/rails_connector/migrations/migration_dsl.rb +13 -0
  39. data/lib/rails_connector/{backend_not_available.rb → network_error.rb} +1 -6
  40. data/lib/rails_connector/obj_data.rb +4 -0
  41. data/lib/rails_connector/obj_data_from_hash.rb +6 -0
  42. data/lib/rails_connector/obj_data_from_service.rb +37 -3
  43. data/lib/rails_connector/obj_params_parser.rb +50 -0
  44. data/lib/rails_connector/obj_search_builder.rb +62 -0
  45. data/lib/rails_connector/obj_search_enumerator.rb +60 -5
  46. data/lib/rails_connector/rate_limit_exceeded.rb +5 -0
  47. data/lib/rails_connector/revision.rb +9 -0
  48. data/lib/rails_connector/string_tagging.rb +1 -12
  49. data/lib/rails_connector/text_link.rb +52 -0
  50. data/lib/rails_connector/text_link_conversion.rb +50 -0
  51. data/lib/rails_connector/workspace.rb +125 -3
  52. data/lib/rails_connector/workspace_data_from_service.rb +30 -31
  53. data/lib/rails_connector/workspace_selection_middleware.rb +62 -20
  54. data/lib/tasks/cache.rake +2 -0
  55. data/lib/tasks/rails_connector/cache_garbage_collector_task.rb +98 -0
  56. metadata +24 -17
  57. data/lib/assets/images/ip_logo_app.png +0 -0
  58. data/lib/assets/images/ip_logo_app2x.png +0 -0
  59. data/lib/assets/images/irongrip.png +0 -0
  60. data/lib/rails_connector/link_resolvable.rb +0 -9
  61. data/lib/rails_connector/rack_middlewares.rb +0 -6
@@ -1,5 +1,4 @@
1
1
  require 'rails_connector/core_extensions'
2
- require 'rails_connector/rack_middlewares'
3
2
  require "rails_connector/errors"
4
3
 
5
4
  # @api public
@@ -24,24 +24,11 @@ module AttributeContent
24
24
  end
25
25
  end
26
26
 
27
- def widgets(attribute_name)
28
- if widget_data = read_widget_attribute(attribute_name)
29
- (widget_data['list'] || []).map do |list_item|
30
- widget_from_pool(list_item['widget'])
31
- end
32
- else
33
- []
34
- end
35
- end
36
-
37
- def read_attribute(attribute_name, skip_widgets = true)
38
- if skip_widgets && (type_of_attribute(attribute_name) == 'widget')
39
- raise "Field #{attribute_name} not (yet) available, since it's a widget"
40
- end
27
+ def read_attribute(attribute_name)
41
28
  @attribute_cache.fetch(attribute_name) do
42
29
  (raw_value, attribute_type) = data_from_cms.value_and_type_of(attribute_name)
43
30
  @attribute_cache[attribute_name] =
44
- prepare_attribute_value(raw_value, attribute_type)
31
+ prepare_attribute_value(raw_value, attribute_type, attribute_name)
45
32
  end
46
33
  end
47
34
 
@@ -62,7 +49,7 @@ module AttributeContent
62
49
  has_attribute?(key) ? read_attribute(key) : nil
63
50
  end
64
51
 
65
- # Hook method to control which widget classes should be avaiable for this page.
52
+ # Hook method to control which widget classes should be available for this page.
66
53
  # Override it to allow only certain classes or none.
67
54
  # Must return either +NilClass+, or +Array+.
68
55
  #
@@ -79,40 +66,17 @@ module AttributeContent
79
66
  def valid_widget_classes_for(field_name)
80
67
  end
81
68
 
82
- def text_links
83
- read_attribute('_text_links')
84
- end
85
-
86
69
  private
87
70
 
88
- def read_widget_attribute(attribute_name)
89
- assert_widget_attribute(attribute_name)
90
-
91
- widget_data = read_attribute(attribute_name.to_s, false)
92
-
93
- if widget_data && widget_data['layout'] # Old style widget fields have a 'layout' key.
94
- Deprecation.warn("Key 'layout' found in attribute '#{attribute_name}' of type 'widget'." \
95
- ' Please migrate the content so that is uses embedded widgets.')
96
- end
97
-
98
- widget_data
99
- end
100
-
101
- def assert_widget_attribute(attribute_name)
102
- unless type_of_attribute(attribute_name) == 'widget'
103
- raise "Not a widget field: #{attribute_name}."
104
- end
105
- end
106
-
107
71
  def update_data(data)
108
72
  self.data_from_cms = data
109
73
  @attribute_cache = {}
110
74
  end
111
75
 
112
- def prepare_attribute_value(attribute_value, attribute_type)
76
+ def prepare_attribute_value(attribute_value, attribute_type, attribute_name)
113
77
  case attribute_type
114
78
  when "html"
115
- StringTagging.tag_as_html(convert_links(attribute_value), self)
79
+ StringTagging.tag_as_html(attribute_value)
116
80
  when "date"
117
81
  DateAttribute.parse(attribute_value) if attribute_value
118
82
  when "linklist"
@@ -121,44 +85,13 @@ module AttributeContent
121
85
  BasicObj.find([attribute_value]).first
122
86
  when "referencelist"
123
87
  BasicObj.find(attribute_value).compact
88
+ when "widget"
89
+ build_widgets(attribute_value, attribute_name)
124
90
  else
125
91
  attribute_value
126
92
  end
127
93
  end
128
94
 
129
- def convert_links(html)
130
- if html
131
- html.gsub(%r{<?\binternallink:(\d+)\b>?(['"]?)}) do
132
- link, postfix = text_link_map[$1], $2
133
- link ? convert_link(link, postfix) : missing_link_url(postfix)
134
- end
135
- end
136
- end
137
-
138
- def convert_link(link, postfix)
139
- link.internal? ? convert_internal_link(link, postfix) : convert_external_link(link, postfix)
140
- end
141
-
142
- def convert_internal_link(link, postfix)
143
- link.obj ? decorate_link_url(link.internal_url, link, postfix) : missing_link_url(postfix)
144
- end
145
-
146
- def convert_external_link(link, postfix)
147
- decorate_link_url(link.external_url, link, postfix)
148
- end
149
-
150
- def decorate_link_url(url, link, postfix)
151
- "#{url}#{link.query_and_fragment}#{postfix} #{link.html_attribute_snippet}"
152
- end
153
-
154
- def missing_link_url(postfix)
155
- "#{CmsRoutingHelper::LINK_TO_UNREACHABLE}#{postfix}"
156
- end
157
-
158
- def text_link_map
159
- @_text_link_map ||= text_links.each_with_object({}) { |link, map| map[link.id] = link }
160
- end
161
-
162
95
  def build_links(link_definitions)
163
96
  if link_definitions.present?
164
97
  link_definitions = link_definitions.map(&:with_indifferent_access)
@@ -175,6 +108,24 @@ module AttributeContent
175
108
  end
176
109
  end
177
110
 
111
+ def build_widgets(widget_data, attribute_name)
112
+ if widget_data
113
+ if widget_data['layout'] # Old style widget fields have a 'layout' key.
114
+ Deprecation.warn("Key 'layout' found in attribute of type 'widget' in obj '#{id}'." \
115
+ ' Please migrate the content so that is uses embedded widgets.')
116
+ end
117
+
118
+ (widget_data['list'] || []).map do |list_item|
119
+ widget_from_pool(list_item['widget']).tap do |widget|
120
+ widget.container = self
121
+ widget.container_field_name = attribute_name
122
+ end
123
+ end
124
+ else
125
+ []
126
+ end
127
+ end
128
+
178
129
  module ClassMethods
179
130
  # Instantiate an {BasicObj Obj} instance from obj_data.
180
131
  # If a subclass of Obj with the same name as the property +_obj_class+ exists,
@@ -0,0 +1,4 @@
1
+ module RailsConnector
2
+ class BackendError < RailsConnector::CommunicationError
3
+ end
4
+ end
@@ -20,6 +20,51 @@ module RailsConnector
20
20
  @_type_computer = nil
21
21
  end
22
22
 
23
+ # Create a new {BasicObj Obj} in the cms
24
+ #
25
+ # This allows you to set the different attributes types of an obj by
26
+ # providing a hash with the attributes names as key and the values you want
27
+ # to set as values
28
+ #
29
+ # @example Reference lists have to be provided as an Array of {BasicObj Objs}
30
+ # Obj.create(:reference_list => [other_obj])
31
+ #
32
+ # @example Passing an {BasicObj Obj} allows you to set a reference
33
+ # Obj.create(:reference => other_obj)
34
+ #
35
+ # @example you can upload files by passing a ruby File object
36
+ # Obj.create(:blob => File.new("image.png"))
37
+ #
38
+ # @example Link list can be set as an Array of {Link Links}
39
+ # Obj.create(:link_list => [
40
+ # # external link
41
+ # Link.new(:url => "http://www.example.com", :title => "Example"),
42
+ # # internal link
43
+ # Link.new(:obj => other_obj, :title => "Other Obj")
44
+ # ])
45
+ #
46
+ # @example Dates attributes accept Time, Date and their subclasses (DateTime for example)
47
+ # Obj.create(:date => Time.new)
48
+ # Obj.create(:date => Date.now)
49
+ #
50
+ # @example String, text, html and enum can be set by passing a {String} value
51
+ # Obj.create(:title => "My Title")
52
+ #
53
+ # @example Arrays of {String Strings} allow you to set multi enum fields
54
+ # Obj.create(:tags => ["ruby", "rails"])
55
+ #
56
+ # @api public
57
+ # @param [Hash] attributes
58
+ # @return [Obj] the newly created {BasicObj Obj}
59
+ def self.create(attributes)
60
+ converted_attributes = CmsRestApi::AttributeSerializer.convert(attributes)
61
+
62
+ json = CmsRestApi.post("workspaces/#{Workspace.current.id}/objs",
63
+ obj: converted_attributes)
64
+
65
+ find(json['id'])
66
+ end
67
+
23
68
  # Create a new {BasicObj Obj} instance with the given values and attributes.
24
69
  # Normally this method should not be used.
25
70
  # Instead Objs should be loaded from the cms database.
@@ -38,19 +83,27 @@ module RailsConnector
38
83
 
39
84
  ### FINDERS ####################
40
85
 
41
- # Find an {BasicObj Obj} by its id.
86
+ # Find a {BasicObj Obj} by its id.
42
87
  # If the paremeter is an Array containing ids, return a list of corresponding Objs.
43
88
  # @param [String, Integer, Array<String, Integer>]id_or_list
44
89
  # @return [Obj, Array<Obj>]
45
90
  # @api public
46
91
  def self.find(id_or_list)
47
- case id_or_list
48
- when Array
49
- find_objs_by(:id, id_or_list).map(&:first)
50
- else
51
- obj = find_objs_by(:id, [id_or_list.to_s]).first.first
52
- obj or raise ResourceNotFound, "Could not find Obj with id #{id_or_list}"
53
- end
92
+ find_filtering_deleted(id_or_list, false)
93
+ end
94
+
95
+ def self.find_by_id(id)
96
+ find_objs_by(:id, [id]).first.first
97
+ end
98
+
99
+ # Find a {BasicObj Obj} by its id.
100
+ # If the paremeter is an Array containing ids, return a list of corresponding Objs.
101
+ # The results include deleted objects as well.
102
+ # @param [String, Integer, Array<String, Integer>]id_or_list
103
+ # @return [Obj, Array<Obj>]
104
+ # @api public
105
+ def self.find_including_deleted(id_or_list)
106
+ find_filtering_deleted(id_or_list, true)
54
107
  end
55
108
 
56
109
  # Returns a {ObjSearchEnumerator} with the given initial subquery consisting of the four arguments.
@@ -139,10 +192,19 @@ module RailsConnector
139
192
  raise ResourceNotFound, "Could not find Obj with permalink '#{permalink}'"
140
193
  end
141
194
 
142
- # accepts the name of an "obj_by" - view and a list of keys.
195
+ # accepts the name of an "obj_by" - view, a list of keys
196
+ # and an "include_deleted" flag
143
197
  # returns a list of lists of Objs: a list of Objs for each given keys.
144
- def self.find_objs_by(view, keys)
145
- CmsBackend.find_obj_data_by(Workspace.current.data, view, keys).map do |list|
198
+ def self.find_objs_by(view, keys, include_deleted = false)
199
+ if include_deleted
200
+ finder_method_name = :find_obj_data_including_deleted_by
201
+ else
202
+ finder_method_name = :find_obj_data_by
203
+ end
204
+
205
+ result = CmsBackend.instance.public_send(finder_method_name, Workspace.current.revision, view, keys)
206
+
207
+ result.map do |list|
146
208
  list.map { |obj_data| BasicObj.instantiate(obj_data) }
147
209
  end
148
210
  end
@@ -167,6 +229,37 @@ module RailsConnector
167
229
  def self.valid_page_classes_beneath(parent_path)
168
230
  end
169
231
 
232
+ # Update the {BasicObj Obj} with the attributes provided.
233
+ #
234
+ # For an overview of which values you can set via this method see the
235
+ # documentation of {BasicObj.create Obj.create}.
236
+ #
237
+ # @api public
238
+ # @param [Hash] attributes
239
+ def update(attributes)
240
+ converted_attributes = CmsRestApi::AttributeSerializer.convert(attributes)
241
+
242
+ CmsRestApi.put(
243
+ "workspaces/#{Workspace.current.id}/objs/#{id}",
244
+ obj: converted_attributes)
245
+ Workspace.reload
246
+
247
+ reload
248
+ self
249
+ end
250
+
251
+ # Destroys the {BasicObj Obj} in the current {Workspace}
252
+ # @api public
253
+ def destroy
254
+ if children.any?
255
+ raise ClientError.new(I18n.t('rails_connector.errors.models.basic_obj.has_children'), 412)
256
+ end
257
+
258
+ CmsRestApi.delete("workspaces/#{Workspace.current.id}/objs/#{id}")
259
+
260
+ Workspace.reload
261
+ end
262
+
170
263
  def to_param
171
264
  id
172
265
  end
@@ -281,6 +374,13 @@ module RailsConnector
281
374
  (title || '').parameterize
282
375
  end
283
376
 
377
+ # This method determines the description that is shown in the changes list.
378
+ # It can be overriden by a custom value.
379
+ # @api public
380
+ def description_for_editor
381
+ slug.presence || path
382
+ end
383
+
284
384
  # Returns the title of the content or the name.
285
385
  # @return [String]
286
386
  # @api public
@@ -498,7 +598,7 @@ module RailsConnector
498
598
  # even if the obj_class in the database has changed.
499
599
  # @api public
500
600
  def reload
501
- obj_data = CmsBackend.find_obj_data_by(Workspace.current.data, :id, [id.to_s]).first.first
601
+ obj_data = CmsBackend.instance.find_obj_data_by(Workspace.current.revision, :id, [id.to_s]).first.first
502
602
  update_data(obj_data)
503
603
  end
504
604
 
@@ -523,6 +623,10 @@ module RailsConnector
523
623
  read_attribute('_valid_until')
524
624
  end
525
625
 
626
+ def modification
627
+ read_attribute('_modification')
628
+ end
629
+
526
630
  # For a binary Obj, the content_type is equal to the content_type of its body (i.e. its data).
527
631
  # For non-binary Objs, a the default content_type is "text/html".
528
632
  # Override this method in subclasses to define a different content_type.
@@ -555,7 +659,7 @@ module RailsConnector
555
659
  if binary?
556
660
  nil
557
661
  else
558
- StringTagging.tag_as_html(read_attribute('body'), self)
662
+ StringTagging.tag_as_html(read_attribute('body'))
559
663
  end
560
664
  end
561
665
 
@@ -601,13 +705,6 @@ module RailsConnector
601
705
  "<#{self.class} id=\"#{id}\" path=\"#{path}\">"
602
706
  end
603
707
 
604
- def destroy
605
- if children.any?
606
- raise ClientError.new(I18n.t('rails_connector.errors.models.basic_obj.has_children'), 412)
607
- end
608
- CmsRestApi.delete("workspaces/#{Workspace.current.id}/objs/#{id}")
609
- end
610
-
611
708
  def edit_view_path
612
709
  "#{obj_class.underscore}/edit"
613
710
  end
@@ -622,8 +719,54 @@ module RailsConnector
622
719
  find_blob.try(:id)
623
720
  end
624
721
 
722
+ # Reverts changes of this object.
723
+ # After calling this method it's as if this object has been never modified in the current working copy.
724
+ # This method does not work with +new+ or +deleted+ objects.
725
+ # This method also does also not work for the +published+ workspace or the +rtc+ working copy.
726
+ def revert
727
+ Workspace.current.raise_if_reverting_objs_impossible
728
+
729
+ if binary?
730
+ raise "revert not supported for binary objs"
731
+ else
732
+ case modification
733
+ when nil
734
+ # don't do anything
735
+ when 'edited'
736
+ previous_content = CmsRestApi.get(
737
+ "revisions/#{Workspace.current.base_revision_id}/objs/#{id}")
738
+ previous_content.except!('id')
739
+ CmsRestApi.put("workspaces/#{Workspace.current.id}/objs/#{id}", {obj: previous_content})
740
+
741
+ reload
742
+ else
743
+ raise "cannot revert changes, since obj is #{modification}."
744
+ end
745
+ end
746
+ end
747
+
748
+ def container_and_field_name_for_widget(widget_id)
749
+ if field_name = field_name_in_data_for_widget(data_from_cms, widget_id)
750
+ return [self, field_name]
751
+ else
752
+ read_widget_pool.each do |parent_widget_id, widget_data|
753
+ if field_name = field_name_in_data_for_widget(widget_data, widget_id)
754
+ return [widget_from_pool(parent_widget_id), field_name]
755
+ end
756
+ end
757
+ end
758
+ [nil, nil]
759
+ end
760
+
625
761
  private
626
762
 
763
+ def field_name_in_data_for_widget(data, widget_id)
764
+ data.all_custom_attributes.find do |attribute_name|
765
+ (value, type) = data.value_and_type_of(attribute_name)
766
+ type == "widget" && value && (value['list'] || []).include?({"widget" => widget_id})
767
+ end
768
+ end
769
+
627
770
  def widget_data_from_pool(widget_id)
628
771
  read_widget_pool[widget_id]
629
772
  end
@@ -680,6 +823,16 @@ module RailsConnector
680
823
  class << self
681
824
  private
682
825
 
826
+ def find_filtering_deleted(id_or_list, include_deleted)
827
+ case id_or_list
828
+ when Array
829
+ find_objs_by(:id, id_or_list, include_deleted).map(&:first).compact
830
+ else
831
+ obj = find_objs_by(:id, [id_or_list.to_s], include_deleted).first.first
832
+ obj or raise ResourceNotFound, "Could not find Obj with id #{id_or_list}"
833
+ end
834
+ end
835
+
683
836
  def search_for_all
684
837
  ObjSearchEnumerator.new(nil).batch_size(1000)
685
838
  end
@@ -1,5 +1,7 @@
1
1
  module RailsConnector
2
2
 
3
+ # The CMS widget class
4
+ # @api public
3
5
  class BasicWidget
4
6
  include AttributeContent
5
7
 
@@ -11,7 +13,7 @@ class BasicWidget
11
13
  @_type_computer = nil
12
14
  end
13
15
 
14
- attr_accessor :id, :obj
16
+ attr_accessor :id, :obj, :container, :container_field_name
15
17
 
16
18
  alias_method :initialize, :update_data
17
19
 
@@ -29,9 +31,48 @@ class BasicWidget
29
31
  self == other
30
32
  end
31
33
 
34
+ # Reverts content changes of this widget. Widget attributes of this widget are not effected.
35
+ # After calling this method it's as if this widget has been never modified in the current working copy.
36
+ # This method does not work with +new+ or +deleted+ widgets.
37
+ # This method also does also not work for the +published+ workspace or the +rtc+ working copy.
38
+ def revert
39
+ Workspace.current.raise_if_reverting_objs_impossible
40
+
41
+ previous_obj_content =
42
+ CmsRestApi.get("revisions/#{Workspace.current.base_revision_id}/objs/#{obj.id}")
43
+
44
+ if previous_widget_content = previous_obj_content["_widget_pool"]["#{id}"]
45
+ previous_widget_content.delete_if do |attribute_name, _|
46
+ type_of_attribute(attribute_name) == "widget"
47
+ end
48
+ CmsRestApi.put("workspaces/#{Workspace.current.id}/objs/#{obj.id}",
49
+ { obj: {_widget_pool: {id => previous_widget_content}} })
50
+ else
51
+ raise "cannot revert changes, since widget is new."
52
+ end
53
+ end
54
+
32
55
  def hash
33
56
  (id + obj.id).hash
34
57
  end
58
+
59
+ # returns the entity ({BasicObj} or {BasicWidget}) that references this widget
60
+ # @api public
61
+ def container
62
+ @container || cache_container_and_field_name_for_widget.first
63
+ end
64
+
65
+ # returns the name of the widget field that references this widget
66
+ # @api public
67
+ def container_field_name
68
+ @container_field_name || cache_container_and_field_name_for_widget.second
69
+ end
70
+
71
+ private
72
+
73
+ def cache_container_and_field_name_for_widget
74
+ @cache_container_and_field_name_for_widget ||= obj.container_and_field_name_for_widget(id)
75
+ end
35
76
  end
36
77
 
37
78
  end