infopark_fiona7 0.71.1.12 → 1.1.0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/fiona7_ui.js +67 -0
  3. data/app/assets/stylesheets/fiona7_ui.css.scss +29 -0
  4. data/app/controllers/fiona7/release_controller.rb +37 -75
  5. data/app/controllers/scrivito/cms_dispatch_controller.rb +2 -12
  6. data/app/controllers/scrivito/objs_controller.rb +41 -13
  7. data/app/controllers/scrivito/webservice_controller.rb +4 -3
  8. data/app/views/fiona7/release/preview.html.erb +73 -0
  9. data/config/routes.rb +3 -1
  10. data/infopark_fiona7.gemspec +1 -1
  11. data/lib/fiona7/assert.rb +4 -0
  12. data/lib/fiona7/attribute_name_mangler.rb +19 -0
  13. data/lib/fiona7/attribute_names_from_cms.rb +17 -0
  14. data/lib/fiona7/attribute_names_from_queries.rb +17 -0
  15. data/lib/fiona7/builder/obj_builder.rb +3 -2
  16. data/lib/fiona7/builder/obj_updater.rb +2 -2
  17. data/lib/fiona7/builder/widget_builder.rb +1 -1
  18. data/lib/fiona7/builder/widget_updater.rb +2 -2
  19. data/lib/fiona7/complex_object.rb +24 -0
  20. data/lib/fiona7/controllers/rest_api/obj_controller.rb +96 -24
  21. data/lib/fiona7/controllers/rest_api/workspace_controller.rb +23 -2
  22. data/lib/fiona7/engine.rb +11 -23
  23. data/lib/fiona7/facet_builder.rb +136 -0
  24. data/lib/fiona7/json/reverse_obj_decorator.rb +2 -0
  25. data/lib/fiona7/mode_switch/cms_routes/scrivito_sdk.rb +68 -10
  26. data/lib/fiona7/mode_switch/cms_routes/scrivito_sdk_slave.rb +66 -14
  27. data/lib/fiona7/mode_switch/views.rb +2 -2
  28. data/lib/fiona7/naive_search_engine.rb +40 -5
  29. data/lib/fiona7/obj_class_name_demangler.rb +11 -0
  30. data/lib/fiona7/obj_class_name_mangler.rb +11 -0
  31. data/lib/fiona7/obj_classes_from_cms.rb +14 -0
  32. data/lib/fiona7/obj_classes_from_queries.rb +11 -0
  33. data/lib/fiona7/recursive_object_finder.rb +149 -0
  34. data/lib/fiona7/routers/rest_api.rb +14 -1
  35. data/lib/fiona7/routing_monkey_patch.rb +5 -2
  36. data/lib/fiona7/scrivito_patches/attribute_serializer.rb +1 -1
  37. data/lib/fiona7/scrivito_patches/basic_obj.rb +4 -9
  38. data/lib/fiona7/scrivito_patches/basic_widget.rb +0 -9
  39. data/lib/fiona7/scrivito_patches/cms_backend.rb +11 -16
  40. data/lib/fiona7/scrivito_patches/cms_rest_api.rb +1 -1
  41. data/lib/fiona7/scrivito_patches/cms_routing.rb +50 -33
  42. data/lib/fiona7/scrivito_patches/link_parser.rb +6 -13
  43. data/lib/fiona7/scrivito_patches/log_subscriber.rb +18 -0
  44. data/lib/fiona7/scrivito_patches/preset_routes.rb +47 -0
  45. data/lib/fiona7/scrivito_patches/routing_extensions.rb +48 -0
  46. data/lib/fiona7/search_engine.rb +4 -0
  47. data/lib/fiona7/type_loader.rb +5 -4
  48. data/lib/fiona7/type_register.rb +13 -27
  49. data/lib/fiona7/type_synchronizer.rb +8 -6
  50. data/lib/fiona7/verity_search_engine.rb +77 -30
  51. data/lib/fiona7/version.rb +1 -1
  52. metadata +18 -13
  53. data/app/models/rails_connector/abstract_obj.rb +0 -24
  54. data/lib/fiona7/controllers/content_service/obj_controller.rb +0 -121
  55. data/lib/fiona7/controllers/content_service/workspace_controller.rb +0 -19
  56. data/lib/fiona7/recursive_link_resolver.rb +0 -93
  57. data/lib/fiona7/routers/content_service.rb +0 -19
  58. data/lib/fiona7/scrivito_patches/client_config.rb +0 -0
  59. data/lib/fiona7/scrivito_patches/controller_actions.rb +0 -6
  60. data/lib/fiona7/scrivito_patches/obj_class.rb +0 -16
  61. data/lib/fiona7/scrivito_patches/obj_data_from_rest.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4022da75c9cc31358e96b0c7acc02a2f600f076
4
- data.tar.gz: 5f460a2ca0c1f02f0ff54949c11f04a1ea3ba6a6
3
+ metadata.gz: 300034555ee49868b2dad5e66fe59ad671403225
4
+ data.tar.gz: c2277f0fe2150975bb5473963e0f0953dcd579a4
5
5
  SHA512:
6
- metadata.gz: 449f8ecefe2906fb1bc8581dad50753611914588e6108a9f38447d1e502012b5cab2f5939454bc440423b31873de3fb2819d4e72d15f922d85240ac9734d3ad8
7
- data.tar.gz: a703033b66e872469499468fb1b3ccbb29169cdb4ad149e13fcc0b6458dace9815a9cd1087ba749eb2a81c4f07780f1c77cf2b4233753daf07a8aab0c7087b10
6
+ metadata.gz: f613ce7c780241d0100b4674d751f50a8698979b512b8dfdd1b638cbfa59c41f4e24865784edd0db417768c0b23c62e4c3b70f17b9b31a1c8d1b7e7a564601fa
7
+ data.tar.gz: bfab39e9bfe82767fb45411f584478d6ad1a43667a1c7d7c1addaa76203ee27197bae9a7cd273103909c4da122dee60ce08c3037046e2b3e6cda0cb5f5524a3b
@@ -38,6 +38,63 @@
38
38
  });
39
39
  };
40
40
 
41
+ var open_release_dialog = function(obj_id) {
42
+ var base_url = window.location.protocol + '//' + window.location.host + '/__scrivito/';
43
+ var path = 'objs/' + obj_id + '/release/preview';
44
+
45
+ var deferred = $.Deferred();
46
+
47
+ $.ajax(base_url + path, {
48
+ type: 'GET',
49
+ dataType: 'html',
50
+ cache: false
51
+ }).then(
52
+ function(result, text_status, xhr) {
53
+ var view = $(result);
54
+
55
+ $('#scrivito_editing').append(view);
56
+
57
+ var accept = function() {
58
+ scrivito.dialog.close_with_transition(view);
59
+
60
+ var checkedObjs = view.find(':checked').map(function() {
61
+ return $(this).val();
62
+ }).get();
63
+
64
+ var comment = view.find('#release-comment').val();
65
+
66
+ deferred.resolve(checkedObjs, comment);
67
+ return false;
68
+ };
69
+
70
+ var cancel = function() {
71
+ scrivito.dialog.close_with_transition(view);
72
+ deferred.reject();
73
+ return false;
74
+ };
75
+
76
+ var expand = function() {
77
+ $(this).next().slideToggle();
78
+ return false;
79
+ };
80
+
81
+ view.find('.scrivito_accept').on('click', accept);
82
+ view.find('.scrivito_cancel').on('click', cancel);
83
+
84
+ view.find('.expanding').on('click', expand);
85
+
86
+ scrivito.dialog.open_and_center_with_transition(view).then(function() {
87
+ // view loaded
88
+ });
89
+
90
+ scrivito.with_dialog_behaviour(view, deferred, {enter: accept, escape: cancel});
91
+ },
92
+ function(xhr, text_status, error) {
93
+ });
94
+
95
+ return deferred;
96
+ };
97
+
41
98
  $.i18n().load({
42
99
  'commands.release_obj.title': 'Seite freigeben',
43
100
  'commands.release_obj.not_modified_obj': 'Diese Seite wurde nicht geändert. Daher gibt es nichts zu freigeben.',
@@ -176,6 +233,16 @@
176
233
  },
177
234
 
178
235
  execute: function() {
236
+ open_release_dialog(obj.id()).done(function(objIds, comment) {
237
+ scrivito.with_saving_overlay(scrivito.ajax('PUT', 'objs/release', {data: {objs: objIds, comment: comment}}).then(function() {
238
+ scrivito.reload();
239
+ }).fail(function(error) {
240
+ scrivito.alert_dialog(scrivito.t('commands.'+action+'_obj.failed'));
241
+ }));
242
+ });
243
+
244
+ return;
245
+
179
246
  /* TODO: implement permission check */
180
247
  open_confirmation_dialog(action, dialog_icons[action]).done(function(comment) {
181
248
  scrivito.with_saving_overlay(scrivito.ajax('PUT', 'objs/' + obj.id() + '/release?comment=' + (comment || "")).then(function() {
@@ -13,6 +13,35 @@ body > div.scrivito_topbar > div > div.scrivito_button_bar.scrivito_app.scrivito
13
13
  display: none;
14
14
  }
15
15
 
16
+ .scrivito_modal_body {
17
+ .main_object {
18
+ margin: 10px 0;
19
+ }
20
+
21
+ .expanding {
22
+ cursor: pointer;
23
+ margin-top: 10px;
24
+
25
+ i.scrivito_icon {
26
+ font-size: 9px;
27
+ color: white;
28
+ }
29
+ }
30
+
31
+ .expandable.collapsed {
32
+ display: none;
33
+ }
34
+
35
+ .expandable {
36
+ margin: 3px 0;
37
+
38
+ input#release-comment {
39
+ margin: 3px 0 3px 12px;
40
+ }
41
+ }
42
+
43
+ }
44
+
16
45
  /*
17
46
  .scrivito_input_new_ws_name, #scrivito_delete_workspace, #scrivito_rename_workspace, #scrivito_create_workspace, #scrivito_rebase_workspace, #scrivito_workspace_settings {
18
47
  display: none !important;
@@ -1,100 +1,62 @@
1
1
  module Fiona7
2
2
  class ReleaseController < ActionController::Base
3
3
  def release
4
- @releasable = Set.new
5
- @errors = []
4
+ obj_ids = params[:objs]
5
+ comment = params[:comment]
6
+ errors = []
6
7
 
7
- @obj = WriteObj.find(params[:id])
8
+ if !obj_ids.empty?
9
+ main_obj = obj_ids.shift
8
10
 
9
- recursively_find_objects(@releasable, @obj, Set.new)
10
-
11
- @releasable.each do |obj|
12
- if obj.really_edited?
13
- carefully_release(@errors, obj)
11
+ carefully_release(errors, main_obj, comment)
12
+ obj_ids.each do |other_obj|
13
+ carefully_release(errors, other_obj)
14
14
  end
15
15
  end
16
16
 
17
- # TODO: display errors
18
17
  render json: {}
19
18
  end
20
19
 
21
- protected
22
-
23
- def carefully_release(errors, obj)
24
- obj.take
25
- obj.release!
26
- rescue => e
27
- Rails.logger.error("Error occured when releasing #{@obj.id}: unable to release Object with ID #{obj.id} (#{obj.path} because: #{e.inspect}")
28
-
29
- # TODO: translate
30
- error = "Unable to release Object with ID #{obj.id} because: #{e.message}"
31
- errors << error
32
- end
33
-
34
- def recursively_find_objects(set, obj, already_encountered)
35
- # substract already_encountered to prevent loops
36
- widgets_of_this_object = widgets(obj) - already_encountered
37
- referenced_objects_of_this_object = referenced_objects(obj) - already_encountered
20
+ def preview
21
+ self.in_rtc_workspace do
22
+ @obj = WriteObj.find(params[:id])
38
23
 
39
- already_encountered.merge(widgets_of_this_object)
40
- already_encountered.merge(referenced_objects_of_this_object)
24
+ @recursive = Fiona7::RecursiveObjectFinder.new(@obj)
41
25
 
42
- widgets_of_this_object.each do |widget|
43
- recursively_find_objects(set, widget, already_encountered)
44
- end
26
+ @images = @recursive.images.select {|o| o.really_edited? }
27
+ @downloads = @recursive.downloads.select {|o| o.really_edited? }
28
+ @referenced = @recursive.referenced.select {|o| o.really_edited? }
29
+ @linked = @recursive.linked.select {|o| o.really_edited? }
45
30
 
46
- referenced_objects_of_this_object.each do |referenced|
47
- recursively_find_objects(set, referenced, already_encountered)
31
+ render "preview", layout: false
48
32
  end
49
-
50
- set.merge(widgets_of_this_object)
51
- set.merge(referenced_objects_of_this_object)
52
- set << obj
53
-
54
- set
55
33
  end
56
34
 
57
- def widgets(obj)
58
- links = @obj[:X_widget_pool] || []
59
- WriteObj.where(obj_id: links.map {|l| l.destination_object_id }).to_a.to_set
35
+ protected
36
+ def in_rtc_workspace(&block)
37
+ Scrivito::Workspace.find('rtc').as_current(&block)
60
38
  end
61
39
 
62
- def referenced_objects(obj)
63
- referenced = Set.new
64
-
65
- type_definition = Fiona7::TypeRegister.instance.read_mangled(obj.obj_class)
66
- type_definition.attrs.each do |attribute|
67
- if attribute.type == :reference || attribute.type == :referencelist && attribute.name != "child_order"
68
- (obj[attribute.real_name] || []).each do |link|
69
- if link.internal?
70
- destination_object = link.destination_object
71
- if destination_object && (destination_object.image? || destination_object.generic?)
72
- referenced << link.destination_object
73
- end
74
- end
75
- end
76
- elsif attribute.type == :link || attribute.type == :linklist
77
- (obj[attribute.real_name] || []).each do |link|
78
- if link.internal?
79
- destination_object = link.destination_object
80
- if destination_object && (destination_object.image? || destination_object.generic?)
81
- referenced << link.destination_object
82
- end
83
- end
84
- end
85
- end
86
- end
40
+ def carefully_release(errors, obj, comment=nil)
41
+ obj.take
42
+ obj.release!(comment)
87
43
 
88
- obj.text_links.each do |link|
89
- if link.internal?
90
- destination_object = link.destination_object
91
- if destination_object && (destination_object.image? || destination_object.generic?)
92
- referenced << link.destination_object
93
- end
94
- end
44
+ complex_object = Fiona7::ComplexObject.new(obj)
45
+ complex_object.widgets.each do |widget_obj|
46
+ widget_obj.take
47
+ widget_obj.release!(comment)
95
48
  end
49
+ #complex_object.binaries.each do |widget_obj|
50
+ # widget_obj.take
51
+ # widget_obj.release!(comment)
52
+ #end
53
+ rescue => e
54
+ Rails.logger.error("Error occured when releasing #{@obj.id}: unable to release Object with ID #{obj.id} (#{obj.path} because: #{e.inspect}")
96
55
 
97
- referenced
56
+ # TODO: translate
57
+ error = "Unable to release Object with ID #{obj.id} because: #{e.message}"
58
+ errors << error
98
59
  end
60
+
99
61
  end
100
62
  end
@@ -8,17 +8,8 @@ module Scrivito
8
8
  def process(action)
9
9
  CmsEnv.new(env).load
10
10
 
11
- if !obj_not_found? && action == 'legacy'
12
- if Scrivito::Configuration.legacy_routing
13
- action = 'index'
14
- else
15
- redirect_to scrivito_path(loaded_obj), status: :moved_permanently
16
- return self.response
17
- end
18
- end
19
-
20
11
  if obj_not_found? && editing_context.workspace_changed?
21
- redirect_to :scrivito_root
12
+ redirect_to "/"
22
13
  return self.response
23
14
  end
24
15
 
@@ -53,7 +44,7 @@ module Scrivito
53
44
  return default_controller if obj_not_found?
54
45
  controller = "#{loaded_obj.controller_name}Controller".constantize
55
46
 
56
- if controller.respond_to?(:use_for_obj_dispatch?) && controller.use_for_obj_dispatch? && controller.include?(Scrivito::ControllerActions)
47
+ if controller.respond_to?(:use_for_obj_dispatch?) && controller.use_for_obj_dispatch?
57
48
  controller
58
49
  else
59
50
  default_controller
@@ -70,7 +61,6 @@ module Scrivito
70
61
  loaded_obj.is_a?(StandardError)
71
62
  end
72
63
 
73
- # support DefaultScrivitoCmsController!
74
64
  def default_controller
75
65
  return Fiona7::DefaultScrivitoCmsController if Fiona7.mode == :legacy
76
66
  CmsController
@@ -35,8 +35,12 @@ module Scrivito
35
35
  params_parser = ObjCreateParamsParser.new(request.host, request.port)
36
36
  @obj = Obj.create(params_parser.parse(params[:obj]), scrivito_user: scrivito_user)
37
37
  render :obj
38
- rescue ObjClassNotFound
39
- head :not_found
38
+ rescue ObjClassNotFound => e
39
+ raise ClientError.new(
40
+ "You've tried to create a CMS object based on the \"#{e.message}\" class, which is "\
41
+ "either missing or not suitable for the object.",
42
+ 400
43
+ )
40
44
  end
41
45
 
42
46
  def details
@@ -48,14 +52,12 @@ module Scrivito
48
52
  params_parser = ObjUpdateParamsParser.new(request.host, request.port,
49
53
  current_obj: current_obj, scrivito_user: scrivito_user)
50
54
  current_obj.update(params_parser.parse(params[:obj]))
51
- rescue ObjClassNotFound
52
- head :not_found
53
55
  end
54
56
 
55
57
  def destroy
56
58
  in_selected_workspace do
57
59
  parent = current_obj.parent
58
- @redirect_to = parent ? scrivito_path(parent) : scrivito_root_path
60
+ @redirect_to = parent ? scrivito_path(parent) : "/"
59
61
  current_obj.destroy
60
62
  end
61
63
  end
@@ -85,17 +87,22 @@ module Scrivito
85
87
  render_empty_json
86
88
  end
87
89
 
88
- # optimized away
89
90
  def conflicting_workspaces
90
91
  @workspaces = []
91
92
  render :workspaces
92
93
  end
93
94
 
94
- # optimized away
95
95
  def is_outdated
96
96
  @is_outdated = false
97
97
  end
98
98
 
99
+ def binary_no_cache
100
+ in_selected_workspace do
101
+ binary = current_obj[params[:attribute_name]]
102
+ @no_cache_url = binary.no_cache_url
103
+ end
104
+ end
105
+
99
106
  def mark_resolved
100
107
  in_selected_workspace { current_obj.mark_resolved }
101
108
  render_empty_json
@@ -106,8 +113,20 @@ module Scrivito
106
113
  render :obj
107
114
  end
108
115
 
109
- # optimized away
110
116
  def transfer_modifications
117
+ in_selected_workspace do
118
+ begin
119
+ obj = Obj.find_including_deleted(params[:id])
120
+ workspace = Workspace.find(params[:workspace_id])
121
+ authorize_workspace_access(:write, workspace) do
122
+ obj.transfer_modifications_to(workspace)
123
+ end
124
+ rescue TransferModificationsModifiedError
125
+ @error_reason = :modified
126
+ rescue TransferModificationsConflictError
127
+ @error_reason = :conflict
128
+ end
129
+ end
111
130
  end
112
131
 
113
132
  def duplicate
@@ -135,12 +154,21 @@ module Scrivito
135
154
  @enumerator = ObjSearchBuilder.new(@query).build
136
155
 
137
156
  if params[:query_action] == 'size'
138
- render :search_only_size
139
- elsif @formatter = fetch_formatter(@query[:format])
140
- render :search
141
- else
142
- render :format_missing_error, status: :not_found
157
+ return render action: 'search/size'
158
+ end
159
+
160
+ unless @formatter = fetch_formatter(@query[:format])
161
+ return render action: 'search/missing_formatter', status: :not_found
143
162
  end
163
+
164
+ if facet_params = params[:facet]
165
+ attribute, options = MultiJson.decode(facet_params).values_at('attribute', 'options')
166
+ @facets = @enumerator.facet(attribute, options || {})
167
+ return render action: 'search/facet'
168
+ end
169
+
170
+ @batch = @enumerator.fetch_batch(params[:continuation])
171
+ render :search
144
172
  end
145
173
  end
146
174
 
@@ -1,9 +1,10 @@
1
1
  module Scrivito
2
2
 
3
3
  class WebserviceController < ActionController::Base
4
- rescue_from ClientError do |exception|
5
- @exception = exception
6
- render 'scrivito/webservice/error', formats: :json, status: exception.http_code
4
+ rescue_from ApplicationError, ClientError, ResourceNotFound do |error|
5
+ @error = error
6
+ @timestamp = Time.zone.now
7
+ render 'scrivito/webservice/error', formats: :json, status: error.http_code
7
8
  end
8
9
 
9
10
  before_filter :merge_correctly_parsed_json_params
@@ -0,0 +1,73 @@
1
+ <div class="scrivito_prompt_dialog scrivito_center_dialog scrivito_modal_prompt scrivito_green scrivito_show" style="margin-left: -250px; margin-top: -165px; left: 50%;">
2
+ <div class="scrivito_modal_header">
3
+ <i class="scrivito_icon"></i>
4
+ <h3 class="scrivito_title">Änderungen an dieser Seite veröffentlichen?</h3>
5
+
6
+ <p class="scrivito_description">Eine Arbeitsversion zu veröffentlichen ist endgültig. Dieser Vorgang kann nicht rückgängig gemacht werden.</p>
7
+
8
+ </div>
9
+ <div class="scrivito_modal_body">
10
+ <div class="main_object">
11
+ <label class=""><input type="checkbox" value="<%= @obj.id %>" name="checked" checked="checked" disabled="disabled"> <%= @obj.title.presence || @obj.path %> (<%= @obj.obj_class %>)</label>
12
+ </div>
13
+
14
+ <% if !@images.empty? %>
15
+ <div class="expanding">
16
+ <span><i class="scrivito_icon"></i> Bilder</span>
17
+ </div>
18
+ <div class="expandable collapsed">
19
+ <% @images.each do |obj| %>
20
+ <label class=""><input type="checkbox" value="<%= obj.id %>" name="obj<%= obj.id %>" checked="checked"> <%= obj.title.presence || obj.path %> (<%= obj.obj_class %>)</label>
21
+ <% end %>
22
+ </div>
23
+ <% end %>
24
+
25
+ <% if !@downloads.empty? %>
26
+ <div class="expanding">
27
+ <span><i class="scrivito_icon"></i> Downloads</span>
28
+ </div>
29
+ <div class="expandable collapsed">
30
+ <% @downloads.each do |obj| %>
31
+ <label class=""><input type="checkbox" value="<%= obj.id %>" name="obj<%= obj.id %>" checked="checked"> <%= obj.title.presence || obj.path %> (<%= obj.obj_class %>)</label>
32
+ <% end %>
33
+ </div>
34
+ <% end %>
35
+
36
+ <% if !@referenced.empty? %>
37
+ <div class="expanding">
38
+ <span><i class="scrivito_icon"></i> Referenzierte Seiten</span>
39
+ </div>
40
+ <div class="expandable collapsed">
41
+ <% @referenced.each do |obj| %>
42
+ <label class=""><input type="checkbox" value="<%= obj.id %>" name="obj<%= obj.id %>"> <%= obj.title.presence || obj.path %> (<%= obj.obj_class %>)</label>
43
+ <% end %>
44
+ </div>
45
+ <% end %>
46
+
47
+ <% if !@linked.empty? %>
48
+ <div class="expanding">
49
+ <span><i class="scrivito_icon"></i> Verlinkte Seiten</span>
50
+ </div>
51
+ <div class="expandable collapsed">
52
+ <% @linked.each do |obj| %>
53
+ <label class=""><input type="checkbox" value="<%= obj.id %>" name="obj<%= obj.id %>"> <%= obj.title.presence || obj.path %> (<%= obj.obj_class %>)</label>
54
+ <% end %>
55
+ </div>
56
+ <% end %>
57
+
58
+ <div class="expanding">
59
+ <span><i class="scrivito_icon"></i> Kommentar</span>
60
+ </div>
61
+ <div class="expandable collapsed">
62
+ <input type="text" id="release-comment" placeholder="Kommentar (optional)">
63
+ </div>
64
+ </div>
65
+ <div class="scrivito_modal_footer">
66
+ <a href="#" class="scrivito_button scrivito_ scrivito_cancel">
67
+ Abbrechen
68
+ </a>
69
+ <a href="#" class="scrivito_button scrivito_green scrivito_accept">
70
+ Freigeben
71
+ </a>
72
+ </div>
73
+ </div>