sugarcrm_emp 0.10.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.
- data/.document +5 -0
- data/.gitignore +29 -0
- data/Gemfile +14 -0
- data/LICENSE +20 -0
- data/README.rdoc +275 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/WATCHLIST.rdoc +7 -0
- data/bin/sugarcrm +26 -0
- data/lib/rails/generators/sugarcrm/config/config_generator.rb +22 -0
- data/lib/rails/generators/sugarcrm/config/templates/initializer.rb +4 -0
- data/lib/rails/generators/sugarcrm/config/templates/sugarcrm.yml +19 -0
- data/lib/sugarcrm/associations/association.rb +170 -0
- data/lib/sugarcrm/associations/association_cache.rb +36 -0
- data/lib/sugarcrm/associations/association_collection.rb +141 -0
- data/lib/sugarcrm/associations/association_methods.rb +91 -0
- data/lib/sugarcrm/associations/associations.rb +61 -0
- data/lib/sugarcrm/associations.rb +5 -0
- data/lib/sugarcrm/attributes/attribute_methods.rb +203 -0
- data/lib/sugarcrm/attributes/attribute_serializers.rb +55 -0
- data/lib/sugarcrm/attributes/attribute_typecast.rb +44 -0
- data/lib/sugarcrm/attributes/attribute_validations.rb +62 -0
- data/lib/sugarcrm/attributes.rb +4 -0
- data/lib/sugarcrm/base.rb +355 -0
- data/lib/sugarcrm/config/sugarcrm.yaml +10 -0
- data/lib/sugarcrm/connection/api/get_available_modules.rb +22 -0
- data/lib/sugarcrm/connection/api/get_document_revision.rb +14 -0
- data/lib/sugarcrm/connection/api/get_entries.rb +23 -0
- data/lib/sugarcrm/connection/api/get_entries_count.rb +20 -0
- data/lib/sugarcrm/connection/api/get_entry.rb +23 -0
- data/lib/sugarcrm/connection/api/get_entry_list.rb +31 -0
- data/lib/sugarcrm/connection/api/get_module_fields.rb +15 -0
- data/lib/sugarcrm/connection/api/get_note_attachment.rb +14 -0
- data/lib/sugarcrm/connection/api/get_relationships.rb +30 -0
- data/lib/sugarcrm/connection/api/get_report_entries.rb +17 -0
- data/lib/sugarcrm/connection/api/get_server_info.rb +7 -0
- data/lib/sugarcrm/connection/api/get_user_id.rb +13 -0
- data/lib/sugarcrm/connection/api/get_user_team_id.rb +14 -0
- data/lib/sugarcrm/connection/api/login.rb +18 -0
- data/lib/sugarcrm/connection/api/logout.rb +15 -0
- data/lib/sugarcrm/connection/api/seamless_login.rb +13 -0
- data/lib/sugarcrm/connection/api/search_by_module.rb +25 -0
- data/lib/sugarcrm/connection/api/set_campaign_merge.rb +15 -0
- data/lib/sugarcrm/connection/api/set_document_revision.rb +35 -0
- data/lib/sugarcrm/connection/api/set_entries.rb +15 -0
- data/lib/sugarcrm/connection/api/set_entry.rb +15 -0
- data/lib/sugarcrm/connection/api/set_note_attachment.rb +25 -0
- data/lib/sugarcrm/connection/api/set_relationship.rb +27 -0
- data/lib/sugarcrm/connection/api/set_relationships.rb +22 -0
- data/lib/sugarcrm/connection/connection.rb +201 -0
- data/lib/sugarcrm/connection/helper.rb +50 -0
- data/lib/sugarcrm/connection/request.rb +61 -0
- data/lib/sugarcrm/connection/response.rb +91 -0
- data/lib/sugarcrm/connection.rb +5 -0
- data/lib/sugarcrm/connection_pool.rb +163 -0
- data/lib/sugarcrm/exceptions.rb +23 -0
- data/lib/sugarcrm/extensions/README.txt +23 -0
- data/lib/sugarcrm/finders/dynamic_finder_match.rb +41 -0
- data/lib/sugarcrm/finders/finder_methods.rb +243 -0
- data/lib/sugarcrm/finders.rb +2 -0
- data/lib/sugarcrm/module.rb +174 -0
- data/lib/sugarcrm/module_methods.rb +91 -0
- data/lib/sugarcrm/session.rb +218 -0
- data/lib/sugarcrm.rb +22 -0
- data/sugarcrm.gemspec +178 -0
- data/test/config_test.yaml +15 -0
- data/test/connection/test_get_available_modules.rb +9 -0
- data/test/connection/test_get_entries.rb +15 -0
- data/test/connection/test_get_entry.rb +22 -0
- data/test/connection/test_get_entry_list.rb +23 -0
- data/test/connection/test_get_module_fields.rb +11 -0
- data/test/connection/test_get_relationships.rb +12 -0
- data/test/connection/test_get_server_info.rb +9 -0
- data/test/connection/test_get_user_id.rb +9 -0
- data/test/connection/test_get_user_team_id.rb +9 -0
- data/test/connection/test_login.rb +9 -0
- data/test/connection/test_logout.rb +9 -0
- data/test/connection/test_set_document_revision.rb +28 -0
- data/test/connection/test_set_entry.rb +15 -0
- data/test/connection/test_set_note_attachment.rb +16 -0
- data/test/connection/test_set_relationship.rb +18 -0
- data/test/extensions_test/patch.rb +9 -0
- data/test/helper.rb +17 -0
- data/test/test_association_collection.rb +11 -0
- data/test/test_associations.rb +156 -0
- data/test/test_connection.rb +13 -0
- data/test/test_connection_pool.rb +40 -0
- data/test/test_finders.rb +201 -0
- data/test/test_module.rb +51 -0
- data/test/test_request.rb +35 -0
- data/test/test_response.rb +26 -0
- data/test/test_session.rb +136 -0
- data/test/test_sugarcrm.rb +213 -0
- metadata +266 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
# Returns the ID, module name and fields for specified modules.
|
|
3
|
+
# Supported modules are Accounts, Bugs, Calls, Cases, Contacts,
|
|
4
|
+
# Leads, Opportunities, Projects, Project Tasks, and Quotes.
|
|
5
|
+
def search_by_module(search_string, modules, opts={})
|
|
6
|
+
login! unless logged_in?
|
|
7
|
+
|
|
8
|
+
options = {
|
|
9
|
+
:offset => nil,
|
|
10
|
+
:limit => nil,
|
|
11
|
+
}.merge! opts
|
|
12
|
+
|
|
13
|
+
json = <<-EOF
|
|
14
|
+
{
|
|
15
|
+
"session": "#{@sugar_session_id}",
|
|
16
|
+
"search_string": "#{search_string}",
|
|
17
|
+
"modules": "#{modules}",
|
|
18
|
+
"offset": #{options[:offset]},
|
|
19
|
+
"max_results": #{options[:limit]}
|
|
20
|
+
}
|
|
21
|
+
EOF
|
|
22
|
+
json.gsub!(/^\s{6}/,'')
|
|
23
|
+
send!(:search_by_module, json)
|
|
24
|
+
end
|
|
25
|
+
end; end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
# Performs a mail merge for the specified campaign.
|
|
3
|
+
def set_campaign_merge(targets, campaign_id)
|
|
4
|
+
login! unless logged_in?
|
|
5
|
+
json = <<-EOF
|
|
6
|
+
{
|
|
7
|
+
"session": "#{@sugar_session_id}",
|
|
8
|
+
"targets": #{targets.to_json},
|
|
9
|
+
"campaign-id": "#{campaign_id}"
|
|
10
|
+
}
|
|
11
|
+
EOF
|
|
12
|
+
json.gsub!(/^\s{6}/,'')
|
|
13
|
+
send!(:set_campaign_merge, json)
|
|
14
|
+
end
|
|
15
|
+
end; end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
# Sets a new revision for a document.
|
|
3
|
+
def set_document_revision(document_id, revision_number, opts={})
|
|
4
|
+
options = {
|
|
5
|
+
:file => '',
|
|
6
|
+
:file_name => '',
|
|
7
|
+
:document_name => nil
|
|
8
|
+
}.merge! opts
|
|
9
|
+
|
|
10
|
+
# Raise an exception of we try to pass :file, but not :file_name
|
|
11
|
+
if (!options[:file].empty? && options[:file_name].empty?)
|
|
12
|
+
raise ArgumentException, ":file_name must be specified if :file is specified"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# If no document_name is given, use the file_name
|
|
16
|
+
options[:document_name] ||= options[:file_name]
|
|
17
|
+
|
|
18
|
+
login! unless logged_in?
|
|
19
|
+
|
|
20
|
+
json = <<-EOF
|
|
21
|
+
{
|
|
22
|
+
"session": "#{@sugar_session_id}",
|
|
23
|
+
"document_revision": {
|
|
24
|
+
"id": "#{document_id}",
|
|
25
|
+
"document_name": "#{options[:document_name]}",
|
|
26
|
+
"revision": "#{revision_number}",
|
|
27
|
+
"filename": "#{options[:file_name]}",
|
|
28
|
+
"file": "#{b64_encode(options[:file])}"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
EOF
|
|
32
|
+
json.gsub!(/^\s{6}/,'')
|
|
33
|
+
send!(:set_document_revision, json)
|
|
34
|
+
end
|
|
35
|
+
end; end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
# Creates or updates a list of SugarBeans.
|
|
3
|
+
def set_entries(module_name, name_value_lists)
|
|
4
|
+
login! unless logged_in?
|
|
5
|
+
json = <<-EOF
|
|
6
|
+
{
|
|
7
|
+
"session": "#{@sugar_session_id}",
|
|
8
|
+
"module_name": "#{module_name}",
|
|
9
|
+
"name_value_list": #{name_value_lists.to_json}
|
|
10
|
+
}
|
|
11
|
+
EOF
|
|
12
|
+
json.gsub!(/^\s{6}/,'')
|
|
13
|
+
send!(:set_entries, json)
|
|
14
|
+
end
|
|
15
|
+
end; end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
# Creates or updates a single SugarBean.
|
|
3
|
+
def set_entry(module_name, name_value_list)
|
|
4
|
+
login! unless logged_in?
|
|
5
|
+
json = <<-EOF
|
|
6
|
+
{
|
|
7
|
+
"session": "#{@sugar_session_id}",
|
|
8
|
+
"module_name": "#{module_name}",
|
|
9
|
+
"name_value_list": #{name_value_list.to_json}
|
|
10
|
+
}
|
|
11
|
+
EOF
|
|
12
|
+
json.gsub!(/^\s{6}/,'')
|
|
13
|
+
send!(:set_entry, json)
|
|
14
|
+
end
|
|
15
|
+
end; end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
# Creates or updates an attachment on a note
|
|
3
|
+
def set_note_attachment(id, filename, file, opts={})
|
|
4
|
+
options = {
|
|
5
|
+
:module_id => '',
|
|
6
|
+
:module_name => ''
|
|
7
|
+
}.merge! opts
|
|
8
|
+
|
|
9
|
+
login! unless logged_in?
|
|
10
|
+
json = <<-EOF
|
|
11
|
+
{
|
|
12
|
+
"session": "#{@sugar_session_id}",
|
|
13
|
+
"note": {
|
|
14
|
+
"id": "#{id}",
|
|
15
|
+
"filename": "#{filename}",
|
|
16
|
+
"file": "#{b64_encode(file)}",
|
|
17
|
+
"related_module_id": "#{options[:module_id]}",
|
|
18
|
+
"related_module_name": "#{options[:module_name]}"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
EOF
|
|
22
|
+
json.gsub!(/^\s{6}/,'')
|
|
23
|
+
send!(:set_note_attachment, json)
|
|
24
|
+
end
|
|
25
|
+
end; end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
# Sets a single relationship between two SugarBeans.
|
|
3
|
+
def set_relationship(module_name, module_id, link_field_name, related_ids, opts={})
|
|
4
|
+
login! unless logged_in?
|
|
5
|
+
options = {
|
|
6
|
+
:name_value_list => [],
|
|
7
|
+
:delete => 0,
|
|
8
|
+
}.merge! opts
|
|
9
|
+
raise ArgumentError, "related_ids must be an Array" unless related_ids.class == Array
|
|
10
|
+
json = <<-EOF
|
|
11
|
+
{
|
|
12
|
+
"session": "#{@sugar_session_id}",
|
|
13
|
+
"module_name": "#{module_name}",
|
|
14
|
+
"module_id": "#{module_id}",
|
|
15
|
+
"link_field_name": "#{link_field_name}",
|
|
16
|
+
"related_ids": #{related_ids.to_json},
|
|
17
|
+
"name_value_list": #{options[:name_value_list].to_json},
|
|
18
|
+
"delete": #{options[:delete]}
|
|
19
|
+
}
|
|
20
|
+
EOF
|
|
21
|
+
json.gsub!(/^\s{6}/,'')
|
|
22
|
+
# TODO: Add a handler for the response. By default it returns a hash like:
|
|
23
|
+
# {failed => 0, deleted => 0, created => 1}. We should add this to the
|
|
24
|
+
# Response.handle method and return true/false depending on the outcome.
|
|
25
|
+
send!(:set_relationship, json)
|
|
26
|
+
end
|
|
27
|
+
end; end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
# Sets multiple relationships between two SugarBeans.
|
|
3
|
+
def set_relationships(module_names, module_ids, link_field_names, related_ids)
|
|
4
|
+
login! unless logged_in?
|
|
5
|
+
|
|
6
|
+
[module_names, module_ids, link_field_names, related_ids].each do |arg|
|
|
7
|
+
raise ArgumentError, "argument must be an Array" unless arg.class == Array
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
json = <<-EOF
|
|
11
|
+
{
|
|
12
|
+
"session": "#{@sugar_session_id}",
|
|
13
|
+
"module_names": "#{module_names.to_json}",
|
|
14
|
+
"module_ids": #{module_ids.to_json},
|
|
15
|
+
"link_field_names": #{link_field_names.to_json},
|
|
16
|
+
"related_ids": #{related_ids.to_json}
|
|
17
|
+
}
|
|
18
|
+
EOF
|
|
19
|
+
json.gsub!(/^\s{6}/,'')
|
|
20
|
+
send!(:set_relationships, json)
|
|
21
|
+
end
|
|
22
|
+
end; end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
|
|
3
|
+
URL = "/service/v2/rest.php"
|
|
4
|
+
# Set this to filter out debug output on a certain method (i.e. get_modules, or get_fields)
|
|
5
|
+
DONT_SHOW_DEBUG_FOR = []
|
|
6
|
+
RESPONSE_IS_NOT_JSON = [:get_user_id, :get_user_team_id]
|
|
7
|
+
|
|
8
|
+
attr :url, true
|
|
9
|
+
attr :user, false
|
|
10
|
+
attr :pass, false
|
|
11
|
+
attr :session, true
|
|
12
|
+
attr :sugar_session_id, true
|
|
13
|
+
attr :connection, true
|
|
14
|
+
attr :options, true
|
|
15
|
+
attr :request, true
|
|
16
|
+
attr :response, true
|
|
17
|
+
attr :errors, true
|
|
18
|
+
|
|
19
|
+
# This is the singleton connection class.
|
|
20
|
+
def initialize(url, user, pass, options={})
|
|
21
|
+
@options = {
|
|
22
|
+
:debug => false,
|
|
23
|
+
:register_modules => true,
|
|
24
|
+
:load_environment => true
|
|
25
|
+
}.merge(options)
|
|
26
|
+
@errors = []
|
|
27
|
+
@url = URI.parse(url)
|
|
28
|
+
@user = user
|
|
29
|
+
@pass = pass
|
|
30
|
+
@request = ""
|
|
31
|
+
@response = ""
|
|
32
|
+
resolve_url
|
|
33
|
+
login!
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check to see if we are logged in
|
|
38
|
+
def logged_in?
|
|
39
|
+
connect! unless connected?
|
|
40
|
+
@sugar_session_id ? true : false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Login
|
|
44
|
+
def login!
|
|
45
|
+
@sugar_session_id = login["id"]
|
|
46
|
+
raise SugarCRM::LoginError, "Invalid Login" unless logged_in?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def logout
|
|
50
|
+
logout
|
|
51
|
+
@sugar_session_id = nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check to see if we are connected
|
|
55
|
+
def connected?
|
|
56
|
+
return false unless @connection
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Connect
|
|
61
|
+
def connect!
|
|
62
|
+
@connection = HTTPClient.new
|
|
63
|
+
end
|
|
64
|
+
alias :reconnect! :connect!
|
|
65
|
+
|
|
66
|
+
# Send a request to the Sugar Instance
|
|
67
|
+
def send!(method, json, max_retry=3)
|
|
68
|
+
if max_retry == 0
|
|
69
|
+
raise SugarCRM::RetryLimitExceeded, "SugarCRM::Connection Errors: \n#{@errors.reverse.join "\n\s\s"}"
|
|
70
|
+
end
|
|
71
|
+
@request = SugarCRM::Request.new(@url, method, json, @options[:debug])
|
|
72
|
+
# Send Ze Reques
|
|
73
|
+
begin
|
|
74
|
+
if @request.length > 3900
|
|
75
|
+
@response = @connection.post(@url, @request)
|
|
76
|
+
else
|
|
77
|
+
@response = @connection.get(@url, @request)
|
|
78
|
+
end
|
|
79
|
+
return handle_response
|
|
80
|
+
# Timeouts are usually a server side issue
|
|
81
|
+
rescue Timeout::Error => error
|
|
82
|
+
@errors << error
|
|
83
|
+
send!(method, json, max_retry.pred)
|
|
84
|
+
# Lower level errors requiring a reconnect
|
|
85
|
+
rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, EOFError => error
|
|
86
|
+
@errors << error
|
|
87
|
+
reconnect!
|
|
88
|
+
send!(method, json, max_retry.pred)
|
|
89
|
+
# Handle invalid sessions
|
|
90
|
+
rescue SugarCRM::InvalidSession => error
|
|
91
|
+
@errors << error
|
|
92
|
+
old_session = @sugar_session_id.dup
|
|
93
|
+
login!
|
|
94
|
+
# Update the session id in the request that we want to retry.
|
|
95
|
+
json.gsub!(old_session, @sugar_session_id)
|
|
96
|
+
send!(method, json, max_retry.pred)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
alias :retry! :send!
|
|
100
|
+
|
|
101
|
+
def debug=(debug)
|
|
102
|
+
options[:debug] = debug
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def debug?
|
|
106
|
+
options[:debug]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def handle_response
|
|
112
|
+
case @response.status
|
|
113
|
+
when 200
|
|
114
|
+
return process_response
|
|
115
|
+
when 404
|
|
116
|
+
raise SugarCRM::InvalidSugarCRMUrl, "#{@url} is invalid"
|
|
117
|
+
when 500
|
|
118
|
+
raise SugarCRM::InvalidRequest, "#{@request} is invalid"
|
|
119
|
+
else
|
|
120
|
+
if @options[:debug]
|
|
121
|
+
puts "#{@request.method}: Raw Response:"
|
|
122
|
+
puts @response.body
|
|
123
|
+
puts "\n"
|
|
124
|
+
end
|
|
125
|
+
raise SugarCRM::UnhandledResponse, "Can't handle response #{@response}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def process_response
|
|
130
|
+
empty_body?
|
|
131
|
+
if response_contains_json?
|
|
132
|
+
return parse_response
|
|
133
|
+
else
|
|
134
|
+
return @response.body
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def resolve_url
|
|
139
|
+
# Appends the rest.php path onto the end of the URL if it's not included
|
|
140
|
+
if @url.path !~ /rest.php$/
|
|
141
|
+
@url.path += URL
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Complain if our body is empty.
|
|
146
|
+
def empty_body?
|
|
147
|
+
raise SugarCRM::EmptyResponse unless @response.body
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Some methods are dumb and don't return a JSON Response
|
|
151
|
+
def response_contains_json?
|
|
152
|
+
if RESPONSE_IS_NOT_JSON.include? @request.method
|
|
153
|
+
return false
|
|
154
|
+
end
|
|
155
|
+
true
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def parse_response
|
|
159
|
+
begin
|
|
160
|
+
# Push it through the old meat grinder.
|
|
161
|
+
json = JSON.parse(@response.body)
|
|
162
|
+
rescue StandardError => e
|
|
163
|
+
# Complain if we can't parse
|
|
164
|
+
raise UnhandledResponse, @response.body
|
|
165
|
+
end
|
|
166
|
+
# Do ze debugs!
|
|
167
|
+
nice_debugging_for json
|
|
168
|
+
# Check for an invalid session
|
|
169
|
+
invalid_session? json
|
|
170
|
+
# Check for an empty result set
|
|
171
|
+
if zero_results? json
|
|
172
|
+
return nil
|
|
173
|
+
end
|
|
174
|
+
json
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Check if we got an invalid session error back
|
|
178
|
+
# something like:
|
|
179
|
+
# {"name"=>"Invalid Session ID",
|
|
180
|
+
# "number"=>11,
|
|
181
|
+
# "description"=>"The session ID is invalid"}
|
|
182
|
+
def invalid_session?(json)
|
|
183
|
+
return false unless json["name"]
|
|
184
|
+
return false if @request.method == :logout
|
|
185
|
+
raise SugarCRM::InvalidSession if json["name"] == "Invalid Session ID"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def zero_results?(json)
|
|
189
|
+
json["result_count"] == 0
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Filter debugging on REALLY BIG responses
|
|
193
|
+
def nice_debugging_for(json)
|
|
194
|
+
if @options[:debug] && !(DONT_SHOW_DEBUG_FOR.include? @request.method)
|
|
195
|
+
puts "#{@request.method}: JSON Response:"
|
|
196
|
+
pp json
|
|
197
|
+
puts "\n"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
end; end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module SugarCRM; class Connection
|
|
2
|
+
# Attempts to return a list of fields for the target of the association.
|
|
3
|
+
# i.e. if we are associating Contact -> Account, using the "contacts" link
|
|
4
|
+
# field name - this will lookup the contacts association and try to determine
|
|
5
|
+
# the target object type (Contact). It will then pull the fields for that object
|
|
6
|
+
# and shove them in the related_fields portion of the get_relationship request.
|
|
7
|
+
def resolve_related_fields(module_name, link_field)
|
|
8
|
+
a = Association.new(class_for(module_name), link_field)
|
|
9
|
+
if a.target
|
|
10
|
+
fields = a.target.new.attributes.keys
|
|
11
|
+
else
|
|
12
|
+
fields = ["id"]
|
|
13
|
+
end
|
|
14
|
+
fields.to_json
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def resolve_fields(module_name, fields)
|
|
18
|
+
# FIXME: This is to work around a bug in SugarCRM 6.0
|
|
19
|
+
# where no fields are returned if no fields are specified
|
|
20
|
+
if fields.length == 0
|
|
21
|
+
mod = Module.find(module_name.classify, @session)
|
|
22
|
+
if mod
|
|
23
|
+
fields = mod.fields.keys
|
|
24
|
+
else
|
|
25
|
+
fields = ["id"]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
return fields.to_json
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns an instance of class for the provided module name
|
|
32
|
+
def class_for(module_name)
|
|
33
|
+
begin
|
|
34
|
+
class_const = @session.namespace_const.const_get(module_name.classify)
|
|
35
|
+
klass = class_const.new
|
|
36
|
+
rescue NameError
|
|
37
|
+
raise InvalidModule, "Module: #{module_name} is not registered"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# We need to strip newlines from Base64 encoding for JSON validation purposes.
|
|
42
|
+
def b64_encode(file)
|
|
43
|
+
Base64.encode64(file).gsub(/\n/, '')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def b64_decode(file)
|
|
47
|
+
Base64.decode64(file)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end; end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module SugarCRM; class Request
|
|
2
|
+
attr :request, true
|
|
3
|
+
attr :url, true
|
|
4
|
+
attr :method, true
|
|
5
|
+
attr :json, true
|
|
6
|
+
attr :http_method
|
|
7
|
+
|
|
8
|
+
def initialize(url, method, json, debug=false)
|
|
9
|
+
@url = url
|
|
10
|
+
@method = method
|
|
11
|
+
@json = escape(json)
|
|
12
|
+
@request = 'method=' << @method.to_s
|
|
13
|
+
@request << '&input_type=JSON'
|
|
14
|
+
@request << '&response_type=JSON'
|
|
15
|
+
@request << '&rest_data=' << @json
|
|
16
|
+
if debug
|
|
17
|
+
puts "#{method}: Request:"
|
|
18
|
+
puts json
|
|
19
|
+
puts "\n"
|
|
20
|
+
end
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def escape(json)
|
|
25
|
+
# BUG: SugarCRM doesn't properly handle '"' inside of JSON for some reason. Let's unescape any html elements.
|
|
26
|
+
j = convert_reserved_characters(json)
|
|
27
|
+
# Now we escape the resulting string.
|
|
28
|
+
j = CGI.escape(j)
|
|
29
|
+
j
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# TODO: Fix this so that it JSON.parse will consume it.
|
|
33
|
+
def unescape
|
|
34
|
+
j = CGI.unescape(@json)
|
|
35
|
+
j.gsub!(/\n/, '')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def bytesize
|
|
39
|
+
self.to_s.bytesize
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def length
|
|
43
|
+
self.to_s.length
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s
|
|
47
|
+
@request
|
|
48
|
+
end
|
|
49
|
+
alias :to_str :to_s
|
|
50
|
+
|
|
51
|
+
# A tiny helper for converting reserved characters for html encoding
|
|
52
|
+
def convert_reserved_characters(string)
|
|
53
|
+
string.gsub!(/"/, '\"')
|
|
54
|
+
string.gsub!(/'/, '\'')
|
|
55
|
+
string.gsub!(/&/, '\&')
|
|
56
|
+
string.gsub!(/</, '\<')
|
|
57
|
+
string.gsub!(/</, '\>')
|
|
58
|
+
string
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end; end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module SugarCRM; class Response
|
|
2
|
+
class << self
|
|
3
|
+
# This class handles the response from the server.
|
|
4
|
+
# It tries to convert the response into an object such as User
|
|
5
|
+
# or an object collection. If it fails, it just returns the response hash
|
|
6
|
+
def handle(json, session)
|
|
7
|
+
r = new(json, session)
|
|
8
|
+
begin
|
|
9
|
+
return r.to_obj
|
|
10
|
+
rescue UninitializedModule => e
|
|
11
|
+
raise e
|
|
12
|
+
rescue InvalidAttribute => e
|
|
13
|
+
raise e
|
|
14
|
+
rescue InvalidAttributeType => e
|
|
15
|
+
raise e
|
|
16
|
+
rescue => e
|
|
17
|
+
if session.connection.debug?
|
|
18
|
+
puts "Failed to process JSON:"
|
|
19
|
+
pp json
|
|
20
|
+
end
|
|
21
|
+
raise e
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr :response, false
|
|
27
|
+
|
|
28
|
+
def initialize(json, session, opts={})
|
|
29
|
+
@options = { :always_return_array => false }.merge! opts
|
|
30
|
+
@response = json
|
|
31
|
+
@response = json.with_indifferent_access if json.is_a? Hash
|
|
32
|
+
@session = session
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Tries to instantiate and return an object with the values
|
|
36
|
+
# populated from the response
|
|
37
|
+
def to_obj
|
|
38
|
+
# If this is not a "entry_list" response, just return
|
|
39
|
+
return @response unless @response && @response["entry_list"]
|
|
40
|
+
|
|
41
|
+
objects = []
|
|
42
|
+
@response["entry_list"].each do |object|
|
|
43
|
+
attributes = []
|
|
44
|
+
_module = resolve_module(object)
|
|
45
|
+
attributes = flatten_name_value_list(object)
|
|
46
|
+
namespace = @session.namespace_const
|
|
47
|
+
if namespace.const_get(_module)
|
|
48
|
+
if attributes.length == 0
|
|
49
|
+
raise AttributeParsingError, "response contains objects without attributes!"
|
|
50
|
+
end
|
|
51
|
+
objects << namespace.const_get(_module).new(attributes)
|
|
52
|
+
else
|
|
53
|
+
raise InvalidModule, "#{_module} does not exist, or is not accessible"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
# If we only have one result, just return the object
|
|
57
|
+
if objects.length == 1 && !@options[:always_return_array]
|
|
58
|
+
return objects[0]
|
|
59
|
+
else
|
|
60
|
+
return objects
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_json
|
|
65
|
+
@response.to_json
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def resolve_module(list)
|
|
69
|
+
list["module_name"].classify
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def flatten_name_value_list(list)
|
|
73
|
+
if list["name_value_list"]
|
|
74
|
+
return flatten(list["name_value_list"])
|
|
75
|
+
else
|
|
76
|
+
return false
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Takes a hash like { "first_name" => {"name" => "first_name", "value" => "John"}}
|
|
81
|
+
# And flattens it into {"first_name" => "John"}
|
|
82
|
+
def flatten(list)
|
|
83
|
+
raise ArgumentError, list[0]['value'] if list[0] && list[0]['name'] == 'warning'
|
|
84
|
+
raise ArgumentError, 'method parameter must respond to #each_pair' unless list.respond_to? :each_pair
|
|
85
|
+
flat_list = {}
|
|
86
|
+
list.each_pair do |k,v|
|
|
87
|
+
flat_list[k.to_sym] = v["value"]
|
|
88
|
+
end
|
|
89
|
+
flat_list
|
|
90
|
+
end
|
|
91
|
+
end; end
|