infopark_cloud_connector 6.9.1.3.22208381 → 6.9.2.1.125136549
Sign up to get free protection for your applications and to get access to all the features.
- data/app/controllers/rails_connector/blobs_controller.rb +1 -1
- data/app/controllers/rails_connector/objs_controller.rb +29 -9
- data/app/controllers/rails_connector/tasks_controller.rb +1 -1
- data/app/controllers/rails_connector/widget_renderer.rb +66 -0
- data/app/controllers/rails_connector/workspaces_controller.rb +9 -5
- data/app/helpers/rails_connector/cms_tag_helper.rb +40 -12
- data/app/views/rails_connector/objs/create_widget.html.erb +1 -1
- data/app/views/rails_connector/objs/show.html.erb +1 -0
- data/config/routes.rb +1 -1
- data/lib/assets/javascripts/infopark_editing.js +5598 -3254
- data/lib/assets/stylesheets/infopark_editing.css +366 -311
- data/lib/generators/cms/migration/templates/migration.rb +7 -9
- data/lib/rails_connector/backend_not_available.rb +1 -1
- data/lib/rails_connector/basic_obj.rb +10 -21
- data/lib/rails_connector/cache.rb +1 -1
- data/lib/rails_connector/cms_cache_storage.rb +10 -1
- data/lib/rails_connector/cms_rest_api.rb +80 -40
- data/lib/rails_connector/content_service.rb +1 -1
- data/lib/rails_connector/date_attribute.rb +1 -1
- data/lib/rails_connector/link.rb +0 -4
- data/lib/rails_connector/migrations/migration_dsl.rb +116 -12
- data/lib/rails_connector/migrations/migrator.rb +0 -1
- data/lib/rails_connector/workspace.rb +1 -1
- metadata +5 -7
- data/lib/rails_connector/cms_api_search_request.rb +0 -50
- data/lib/rails_connector/default_search_request.rb +0 -6
- data/lib/rails_connector/elasticsearch_request.rb +0 -78
- data/lib/rails_connector/widget_renderer.rb +0 -41
@@ -1,17 +1,15 @@
|
|
1
1
|
class <%= class_name %> < ::RailsConnector::Migration
|
2
2
|
def up
|
3
|
-
# get_attribute('test')
|
4
|
-
# create_attribute(:name => 'test', :type => :string)
|
5
|
-
# update_attribute('test', :title => 'Test Title')
|
6
|
-
# delete_attribute('test')
|
7
|
-
|
8
3
|
# get_obj_class('Test')
|
9
|
-
# create_obj_class(:
|
10
|
-
# update_obj_class('Test', :
|
4
|
+
# create_obj_class(name: 'Test', type: :publication, attributes: [])
|
5
|
+
# update_obj_class('Test', title: 'Test Title')
|
6
|
+
# add_attribute_to('Test', { name: 'test', type: 'string' })
|
7
|
+
# update_attribute_for('Test', 'test', { title: 'New Title' })
|
8
|
+
# delete_attribute_from('Test', 'test')
|
11
9
|
|
12
10
|
# get_obj('a1b2c3')
|
13
|
-
# create_obj(:
|
14
|
-
# update_obj('a1b2c3', :
|
11
|
+
# create_obj(_path: '/test', _obj_class: 'Test')
|
12
|
+
# update_obj('a1b2c3', title: 'Test Title')
|
15
13
|
# delete_obj('a1b2c3')
|
16
14
|
end
|
17
15
|
end
|
@@ -8,7 +8,6 @@ module RailsConnector
|
|
8
8
|
extend ActiveModel::Naming
|
9
9
|
|
10
10
|
include DateAttribute
|
11
|
-
include SEO
|
12
11
|
include ModelIdentity
|
13
12
|
|
14
13
|
# Create a new Obj instance with the given values and attributes.
|
@@ -343,12 +342,11 @@ module RailsConnector
|
|
343
342
|
object_type == :document
|
344
343
|
end
|
345
344
|
|
346
|
-
# Returns true if this object is active
|
345
|
+
# Returns true if this object is active.
|
347
346
|
# @api public
|
348
|
-
def active?
|
347
|
+
def active?
|
349
348
|
return false unless valid_from
|
350
|
-
|
351
|
-
valid_from <= time_then && (!valid_until || time_then <= valid_until)
|
349
|
+
valid_from <= Time.now && (!valid_until || Time.now <= valid_until)
|
352
350
|
end
|
353
351
|
|
354
352
|
# compatibility with legacy apps.
|
@@ -365,8 +363,8 @@ module RailsConnector
|
|
365
363
|
end
|
366
364
|
|
367
365
|
# Returns true if the export of the object is not suppressed and the content is active?
|
368
|
-
def exportable?
|
369
|
-
!suppressed? && active?
|
366
|
+
def exportable?
|
367
|
+
!suppressed? && active?
|
370
368
|
end
|
371
369
|
|
372
370
|
# Returns the file name to which the Content.file_extension has been appended.
|
@@ -404,8 +402,7 @@ module RailsConnector
|
|
404
402
|
# @api public
|
405
403
|
def toclist(*args)
|
406
404
|
return [] unless publication?
|
407
|
-
|
408
|
-
toclist = children.select{ |toc| toc.exportable?(time) }
|
405
|
+
toclist = children.select{ |toc| toc.exportable? }
|
409
406
|
toclist = toclist.reject { |toc| toc.binary? } unless args.include?(:all)
|
410
407
|
toclist
|
411
408
|
end
|
@@ -638,10 +635,6 @@ module RailsConnector
|
|
638
635
|
end
|
639
636
|
end
|
640
637
|
|
641
|
-
def to_liquid
|
642
|
-
LiquidSupport::ObjDrop.new(self)
|
643
|
-
end
|
644
|
-
|
645
638
|
def respond_to?(method_id, include_private=false)
|
646
639
|
if has_attribute?(method_id)
|
647
640
|
true
|
@@ -650,14 +643,6 @@ module RailsConnector
|
|
650
643
|
end
|
651
644
|
end
|
652
645
|
|
653
|
-
def self.preview_time=(t)
|
654
|
-
Thread.current[:preview_time] = t
|
655
|
-
end
|
656
|
-
|
657
|
-
def self.preview_time
|
658
|
-
Thread.current[:preview_time] || Time.now
|
659
|
-
end
|
660
|
-
|
661
646
|
def inspect
|
662
647
|
"<#{self.class} id=\"#{id}\" path=\"#{path}\">"
|
663
648
|
end
|
@@ -697,6 +682,10 @@ module RailsConnector
|
|
697
682
|
CmsRestApi.delete("revisions/#{Workspace.current.revision_id}/objs/#{id}")
|
698
683
|
end
|
699
684
|
|
685
|
+
def widget_container
|
686
|
+
@widget_container ||= self.class.find(parent_path.split('/').last)
|
687
|
+
end
|
688
|
+
|
700
689
|
private
|
701
690
|
|
702
691
|
attr_accessor :data_from_cms
|
@@ -11,10 +11,19 @@ module CmsCacheStorage
|
|
11
11
|
@cache ||= begin
|
12
12
|
prefix = VERSION.dup
|
13
13
|
prefix.concat('-utf8') if String.new.encoding_aware?
|
14
|
-
Cache.new(fallback_backend:
|
14
|
+
Cache.new(fallback_backend: backend_cache, cache_prefix: prefix)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
def backend_cache=(new_cache)
|
19
|
+
@backend_cache = new_cache
|
20
|
+
@cache = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def backend_cache
|
24
|
+
@backend_cache || Rails.cache
|
25
|
+
end
|
26
|
+
|
18
27
|
def read_workspace_data(workspace_id)
|
19
28
|
cache.read("workspace/#{workspace_id}")
|
20
29
|
end
|
@@ -19,23 +19,38 @@ module RailsConnector
|
|
19
19
|
#
|
20
20
|
# @example Delete an Obj:
|
21
21
|
# RailsConnector::CmsRestApi.delete('revisions/001384beff9e5845/objs/f4123622ff07b70b')
|
22
|
+
#
|
23
|
+
# @example Specify a poll interval (in seconds; default: 2) to use in case the response
|
24
|
+
# is a task reference response and the final response is polled for:
|
25
|
+
# RailsConnector::CmsRestApi.put('workspace/rtc/publish', nil, :interval => 10)
|
26
|
+
#
|
27
|
+
# @example Return immediately with the first response (without polling in case it is a
|
28
|
+
# task reference response):
|
29
|
+
# RailsConnector::CmsRestApi.task_unaware_request(:put, 'workspace/rtc/publish', nil)
|
30
|
+
#
|
22
31
|
class CmsRestApi
|
32
|
+
|
23
33
|
cattr_accessor :credentials
|
24
34
|
|
25
|
-
def self.get(resource_path, payload = nil)
|
26
|
-
request_cms_api(:get, resource_path, payload)
|
35
|
+
def self.get(resource_path, payload = nil, options = nil)
|
36
|
+
request_cms_api(:get, resource_path, payload, options)
|
27
37
|
end
|
28
38
|
|
29
|
-
def self.put(resource_path, payload)
|
30
|
-
request_cms_api(:put, resource_path, payload)
|
39
|
+
def self.put(resource_path, payload, options = nil)
|
40
|
+
request_cms_api(:put, resource_path, payload, options)
|
31
41
|
end
|
32
42
|
|
33
|
-
def self.post(resource_path, payload)
|
34
|
-
request_cms_api(:post, resource_path, payload)
|
43
|
+
def self.post(resource_path, payload, options = nil)
|
44
|
+
request_cms_api(:post, resource_path, payload, options)
|
35
45
|
end
|
36
46
|
|
37
|
-
def self.delete(resource_path, payload = nil)
|
38
|
-
request_cms_api(:delete, resource_path, payload)
|
47
|
+
def self.delete(resource_path, payload = nil, options = nil)
|
48
|
+
request_cms_api(:delete, resource_path, payload, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.task_unaware_request(method, resource_path, payload = nil)
|
52
|
+
raise "Unexpected method #{method}" unless [:delete, :get, :post, :put].include?(method)
|
53
|
+
response_for_request_cms_api(method, resource_path, payload)
|
39
54
|
end
|
40
55
|
|
41
56
|
# @param [Hash] value
|
@@ -44,48 +59,73 @@ module RailsConnector
|
|
44
59
|
@@credentials = value.symbolize_keys
|
45
60
|
end
|
46
61
|
|
47
|
-
|
62
|
+
class << self
|
48
63
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
64
|
+
private
|
65
|
+
|
66
|
+
def request_cms_api(action, resource_path, payload, options)
|
67
|
+
decoded = response_for_request_cms_api(action, resource_path, payload)
|
68
|
+
return decoded unless Hash === decoded
|
69
|
+
return decoded unless decoded.keys == ["task"]
|
70
|
+
task_data = decoded["task"]
|
71
|
+
return decoded unless Hash === task_data
|
72
|
+
task_path = "tasks/#{task_data["id"]}"
|
73
|
+
final_response(task_path, options)
|
74
|
+
end
|
54
75
|
|
55
|
-
|
76
|
+
def response_for_request_cms_api(action, resource_path, payload)
|
77
|
+
request_params = basic_request_params
|
78
|
+
request_params[:method] = action
|
79
|
+
request_params[:url] = url(resource_path)
|
80
|
+
request_params[:payload] = MultiJson.encode(payload) if payload.present?
|
56
81
|
MultiJson.decode(RestClient::Request.execute(request_params))
|
57
82
|
rescue RestClient::ExceptionWithResponse => e
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
83
|
+
http_code = e.http_code
|
84
|
+
if http_code == 403
|
85
|
+
raise AccessDenied.new(e.http_body)
|
86
|
+
elsif 400 <= http_code && http_code < 500
|
87
|
+
begin
|
88
|
+
specific_output = MultiJson.decode(e.http_body)['error']
|
89
|
+
rescue MultiJson::DecodeError
|
90
|
+
# fall through
|
91
|
+
else
|
92
|
+
raise ClientError.new(specific_output, http_code)
|
64
93
|
end
|
65
|
-
rescue MultiJson::DecodeError
|
66
94
|
end
|
95
|
+
raise BackendNotAvailable.new(e.http_body, http_code)
|
96
|
+
end
|
67
97
|
|
68
|
-
|
98
|
+
def final_response(task_path, options)
|
99
|
+
options ||= {}
|
100
|
+
wait = options[:interval].presence.try(:to_f) || 2
|
101
|
+
task_data = response = nil
|
102
|
+
loop do
|
103
|
+
sleep wait
|
104
|
+
task_data = response_for_request_cms_api(:get, task_path, nil)
|
105
|
+
break unless task_data["status"] == "open"
|
106
|
+
end
|
107
|
+
return task_data["result"] if task_data["status"] == "success"
|
108
|
+
message = task_data["message"] || "Missing error message in task response #{task_data}"
|
109
|
+
raise ClientError.new(message, 400)
|
69
110
|
end
|
70
|
-
end
|
71
111
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
{
|
80
|
-
:user => credentials[:login],
|
81
|
-
:password => credentials[:api_key],
|
82
|
-
:headers => headers,
|
83
|
-
}
|
84
|
-
end
|
112
|
+
def basic_request_params
|
113
|
+
headers = {
|
114
|
+
:content_type => :json,
|
115
|
+
:accept => :json,
|
116
|
+
}
|
117
|
+
headers[:host] = credentials[:http_host] if credentials[:http_host].present?
|
85
118
|
|
86
|
-
|
87
|
-
|
119
|
+
{
|
120
|
+
:user => credentials[:login],
|
121
|
+
:password => credentials[:api_key],
|
122
|
+
:headers => headers,
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
def url(resource_path)
|
127
|
+
"#{credentials[:url]}/#{resource_path}"
|
128
|
+
end
|
88
129
|
end
|
89
130
|
end
|
90
|
-
|
91
131
|
end
|
@@ -107,7 +107,7 @@ class ContentService
|
|
107
107
|
elsif response.code == "429"
|
108
108
|
raise RateLimitExceeded.new(response["Retry-After"])
|
109
109
|
else
|
110
|
-
raise "Server responded with status code #{response.code}"
|
110
|
+
raise BackendNotAvailable.new("Server responded with status code #{response.code}", response.code)
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
data/lib/rails_connector/link.rb
CHANGED
@@ -7,7 +7,7 @@ module RailsConnector
|
|
7
7
|
#
|
8
8
|
# @example Create "test" Attribute
|
9
9
|
#
|
10
|
-
# create_attribute(:
|
10
|
+
# create_attribute(name: 'test', type: 'string')
|
11
11
|
#
|
12
12
|
# @param attributes [Hash] The attributes and their values of the new
|
13
13
|
# CMS attribute.
|
@@ -20,14 +20,94 @@ module RailsConnector
|
|
20
20
|
warn_deprecated __callee__, 'create_obj_class', 'update_obj_class'
|
21
21
|
endpoint = "revisions/#{Workspace.current.revision_id}/attributes"
|
22
22
|
|
23
|
-
CmsRestApi.post(endpoint, :
|
23
|
+
CmsRestApi.post(endpoint, attribute: attributes)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Adds an attribute to an object class.
|
27
|
+
#
|
28
|
+
# @example Add "test" attribute to object class "Foo.
|
29
|
+
#
|
30
|
+
# add_attribute_to('Foo', { name: 'test', type: 'string' })
|
31
|
+
#
|
32
|
+
# @param obj_class_name [String] The class name of the object class.
|
33
|
+
# @param params [Hash] The definition of the new attribute.
|
34
|
+
#
|
35
|
+
# @return nothing
|
36
|
+
def add_attribute_to(obj_class_name, params)
|
37
|
+
attributes = get_obj_class(obj_class_name)['attributes']
|
38
|
+
attributes << params
|
39
|
+
|
40
|
+
update_obj_class(obj_class_name, attributes: attributes)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Deletes an attribute from an object class.
|
44
|
+
#
|
45
|
+
# @example Delete "test" attribute from object class "Foo.
|
46
|
+
#
|
47
|
+
# delete_attribute_from('Foo', 'test')
|
48
|
+
#
|
49
|
+
# @param obj_class_name [String] The class name of the object class.
|
50
|
+
# @param attribute_name [String] The name of the attribute that should be
|
51
|
+
# deleted.
|
52
|
+
#
|
53
|
+
# @return nothing
|
54
|
+
def delete_attribute_from(obj_class_name, attribute_name)
|
55
|
+
attributes = get_obj_class(obj_class_name)['attributes']
|
56
|
+
attributes = attributes.delete_if do |attribute|
|
57
|
+
# handle global and local attributes
|
58
|
+
name = attribute.is_a?(String) ? attribute : attribute['name']
|
59
|
+
|
60
|
+
name == attribute_name.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
update_obj_class(obj_class_name, attributes: attributes)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Updates an attribute for an object class. Turns local into global
|
67
|
+
# attribute if found.
|
68
|
+
#
|
69
|
+
# @example Update "test" attribute for object class "Foo.
|
70
|
+
#
|
71
|
+
# update_attribute_for('Foo', 'test', { title: 'New Title' })
|
72
|
+
#
|
73
|
+
# @param obj_class_name [String] The class name of the object class.
|
74
|
+
# @param attribute_name [String] The name of the attribute that should be
|
75
|
+
# updated.
|
76
|
+
# @param params [Hash] Hash of updated attributes.
|
77
|
+
#
|
78
|
+
# @return nothing
|
79
|
+
def update_attribute_for(obj_class_name, attribute_name, params)
|
80
|
+
attribute_name = attribute_name.to_s
|
81
|
+
attributes = get_obj_class(obj_class_name)['attributes']
|
82
|
+
found = false
|
83
|
+
|
84
|
+
attributes.each_with_index do |attribute, index|
|
85
|
+
name = attribute.is_a?(String) ? attribute : attribute['name']
|
86
|
+
|
87
|
+
if name == attribute_name
|
88
|
+
if attribute.is_a?(String)
|
89
|
+
attribute = get_attribute(attribute)
|
90
|
+
end
|
91
|
+
|
92
|
+
attributes[index] = attribute.merge(params.stringify_keys)
|
93
|
+
update_obj_class(obj_class_name, attributes: attributes)
|
94
|
+
|
95
|
+
found = true
|
96
|
+
|
97
|
+
break
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
unless found
|
102
|
+
raise ArgumentError.new("Object class '#{obj_class_name}' does not have attribute '#{attribute_name}'.")
|
103
|
+
end
|
24
104
|
end
|
25
105
|
|
26
106
|
# Creates a CMS object.
|
27
107
|
#
|
28
108
|
# @example Create "/test" Object
|
29
109
|
#
|
30
|
-
# create_obj(:
|
110
|
+
# create_obj(_path: '/test', _obj_class: 'Test')
|
31
111
|
#
|
32
112
|
# @param attributes [Hash] The attributes and their values of the new
|
33
113
|
# CMS object.
|
@@ -37,14 +117,14 @@ module RailsConnector
|
|
37
117
|
def create_obj(attributes = {})
|
38
118
|
endpoint = "revisions/#{Workspace.current.revision_id}/objs"
|
39
119
|
|
40
|
-
CmsRestApi.post(endpoint, :
|
120
|
+
CmsRestApi.post(endpoint, obj: attributes)
|
41
121
|
end
|
42
122
|
|
43
123
|
# Creates a CMS object class.
|
44
124
|
#
|
45
125
|
# @example Create "Test" Object Class
|
46
126
|
#
|
47
|
-
# create_obj_class(:
|
127
|
+
# create_obj_class(name: 'Test', type: 'publication')
|
48
128
|
#
|
49
129
|
# @param attributes [Hash] The attributes and their values of the new
|
50
130
|
# CMS object class.
|
@@ -54,7 +134,7 @@ module RailsConnector
|
|
54
134
|
def create_obj_class(attributes = {})
|
55
135
|
endpoint = "revisions/#{Workspace.current.revision_id}/obj_classes"
|
56
136
|
|
57
|
-
CmsRestApi.post(endpoint, :
|
137
|
+
CmsRestApi.post(endpoint, obj_class: attributes)
|
58
138
|
end
|
59
139
|
|
60
140
|
# Deletes a CMS attribute.
|
@@ -145,7 +225,7 @@ module RailsConnector
|
|
145
225
|
#
|
146
226
|
# @example Update the title of the "test" Attribute
|
147
227
|
#
|
148
|
-
# update_attribute(:
|
228
|
+
# update_attribute(title: 'Test Title')
|
149
229
|
#
|
150
230
|
# @param id [String] The ID of the attribute.
|
151
231
|
# @param attributes [Hash] The updated attributes and their values.
|
@@ -157,14 +237,14 @@ module RailsConnector
|
|
157
237
|
warn_deprecated __callee__, 'update_obj_class'
|
158
238
|
endpoint = "revisions/#{Workspace.current.revision_id}/attributes/#{id}"
|
159
239
|
|
160
|
-
CmsRestApi.put(endpoint, :
|
240
|
+
CmsRestApi.put(endpoint, attribute: attributes)
|
161
241
|
end
|
162
242
|
|
163
243
|
# Updates a CMS object.
|
164
244
|
#
|
165
245
|
# @example Update the title of the "a1b2c3" Object
|
166
246
|
#
|
167
|
-
# update_attribute('a1b2c3', :
|
247
|
+
# update_attribute('a1b2c3', title: 'Test Title')
|
168
248
|
#
|
169
249
|
# @param id [String] The ID of the CMS object.
|
170
250
|
# @param attributes [Hash] The updated attributes and their values.
|
@@ -174,14 +254,14 @@ module RailsConnector
|
|
174
254
|
def update_obj(id, attributes = {})
|
175
255
|
endpoint = "revisions/#{Workspace.current.revision_id}/objs/#{id}"
|
176
256
|
|
177
|
-
CmsRestApi.put(endpoint, :
|
257
|
+
CmsRestApi.put(endpoint, obj: attributes)
|
178
258
|
end
|
179
259
|
|
180
260
|
# Updates a CMS object class.
|
181
261
|
#
|
182
262
|
# @example Update the title of the "Test" Object Class
|
183
263
|
#
|
184
|
-
# update_obj_class('Test', :
|
264
|
+
# update_obj_class('Test', title: 'Test Title')
|
185
265
|
#
|
186
266
|
# @param id [String] The ID of the CMS object class.
|
187
267
|
# @param attributes [Hash] The updated attributes and their values.
|
@@ -191,7 +271,31 @@ module RailsConnector
|
|
191
271
|
def update_obj_class(id, attributes = {})
|
192
272
|
endpoint = "revisions/#{Workspace.current.revision_id}/obj_classes/#{id}"
|
193
273
|
|
194
|
-
CmsRestApi.put(endpoint, :
|
274
|
+
CmsRestApi.put(endpoint, obj_class: attributes)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Uploads a file to the content store.
|
278
|
+
#
|
279
|
+
# @example Upload "image.png" and store it in an "Image" CMS object.
|
280
|
+
#
|
281
|
+
# filename = 'image.png'
|
282
|
+
# file = File.new(filename)
|
283
|
+
# blob = upload_file(file)
|
284
|
+
# create_obj(_path: "/resources/images/#{filename}", _obj_class: 'Image', blob: blob)
|
285
|
+
#
|
286
|
+
# @param file [IO] The file IO object that gets uploaded.
|
287
|
+
#
|
288
|
+
# @return The blob of the uploaded file that can be stored on a CMS object.
|
289
|
+
# @api public
|
290
|
+
def upload_file(file)
|
291
|
+
upload_permission = RailsConnector::CmsRestApi.get('blobs/upload_permission')
|
292
|
+
|
293
|
+
fields = upload_permission['fields'].map { |name, value| [name, value] }
|
294
|
+
fields << [:file, file]
|
295
|
+
|
296
|
+
RestClient.post(upload_permission['url'], fields)
|
297
|
+
|
298
|
+
upload_permission['blob']
|
195
299
|
end
|
196
300
|
|
197
301
|
private
|