== Intro ==
[http://www.sugarcrm.com SugarCRM] is the world's leading Customer Relationship Management (CRM) software available with complete freedom under the GPL license. Because CRM software captures the varied relationships between a company and it's sources of revenue, most businesses really stand to benefit from the intelligence provided by a CRM system. All companies already have 'ad-hoc' methods to track leads, contacts, customer accounts and the company interactions with these (e.g. spreadsheets, lists and email records); or they even have organized CRM solutions in place. Either way, once you decide to adopt an open and standards-based solution like SugarCRM, your first order of business will be to load it with your existing data. SugarCRM has importing (and exporting) utilities that make it easy to do this, so that will not be the focus of this article. Instead, we'll focus on programmatic interaction with the system. Once the CRM system is deployed, a company will also typically want to create one or more pipelines which act as conduits to capture new account, and contact information. This article will show how easy it can be to establish a web service, using [[wp:SOAP]] to add contacts and accounts to your SugarCRM installation. As an example, let's suppose that a conference organizer wants to offer a sign-up sheet on their existing website. The existing website could be a [http://drupal.org drupal] content management system (CMS) which not only manages the conference, but also adds company and individual contact details to the SugarCRM system via the web service. The details on creating the form for the capture are beyond the scope of this article but the capture routines could easily handle a form input (HTTP POST) just as easily as we read input from a file . We'll focus on the plumbing of the interaction with the SOAP server.
== Goal ==
Import records into SugarCRM without using existing functionalityimport utilities, but rather by writing some a tool in [[wp:PHP tool ]] to do the work. Records should go into Accounts and Contacts, which obviously implies while creating a relationshipbetween the two. == Resources Used ==* http://www.sugarcrm.com/wiki/index.php?title=SOAP_Intro_and_Practical_Examples* http://freephile.com/crm/index.php?module=Home&action=TrainingPortal* http://freephile.com/crm/soap.php* http://www.sugarcrm.com/wiki/index.php?title=SOAP_in_PHP* http://dietrich.ganx4.com/nusoap/* http://www.beanizer.org/site/index.php/en/Articles/Sugar-CRM-integration-with-custom-PHP-applications-I.html
== Download and Install SugarCRM ==
SugarCRM provides easy stack installers which make trying the system as close to a one-click operation as possible. However, in the many scenarios the developer or system administrator will want to install into an existing infrastructure so we'll assume that approach.# I went to Visit the [http://sugarcrm.com SugarCRM homepage], and clicked click the top navbar link to "Sugar Open Source"# I skipped Skip the "Wizard" and went go right to the "Download Page" because I you know what Iyou'm re doing and also have a pre-existing setup of Linux, Apache MySQL and PHP - so I only want the application# I downloaded Download SugarCE-5.0.0e.zip (production release)or the later available production release (getting only the application).# I visited Visit the recommended "Installation" instructions page at http://www.sugarforge.org/content/installation/# Then I installed it Install SugarCRM according to the instructions, however . Note: I immediately ran into trouble because the instructions did say to chmod 766 all files that needed to be writable by the web_user. # This command renders the directories non-executable which manifests in include errors because the web user (www-data) can not see into those directories (to find includes). The following snippet is a fix:
<source lang="bash">
# find directories in the ./crm path and change the mode on them so that all users can execute (see into) the directory
find ./crm/ -type d |xargs chmod a+x
</source>
resolved the include errors
The instructions also fail to mention There is one more 'gotcha' that I ran into with the installation. The application wants to create a .htaccess file (which doesn't exist in the distribution and would not necessarily be writable to the web user. ) As a failsafe, the information .htaccess content is printed to the screen in the installer. However it doesn't properly display (lacking newline characters) so the content is not suitable for copy and paste into the file. I resorted to the source which generates the .htaccess content, and used that. An alternative is to simply touch and chmod 777 a .htaccess file '''prior''' to running the installer; and then chmod'ing it go=r after install.
== Ideas on How to do the import Requirements ==My self-imposed requirement was to capture all of the data provided -- in my sample assuming the owner of the data collected it for a reason and that they wouldn't be too happy with a loss of data in a migration to a new application. I looked briefly at the import functionality exposed by the applicationdid not define new fields, but noticed I did 'draw' a map of my data source and how it corresponds to the lead capture did not reflect data definition of the Accounts and Contacts tables. Because I had parsed the same data profile as the source for column headings, I simply exported that variable and then used it as a comment right in my code workup as I hadwent.
<source lang="php">print '<pre>'; var_export ($arrFields); print '</pre>';// copy and paste that output; then add comments about which fields go into which table// comment the whole block so that it remains as a reference in the toolarray ( 0 => 'Company Name',// a:name 1 => 'Industry', // a:industry 2 => 'First Name', // c:first_name 3 => 'Last Name', // c:last_name 4 => 'Fax', // a:phone_fax c:phone_fax 5 => 'Address', // a:billing_address_street c:primary_address_street 6 = Existing > 'City', // a:billing_address_city c:primary_address_city 7 => 'State', // a:billing_address_state c:primary_address_state 8 => 'Zip Code', // a:billing_address_postalcode c:primary_address_postalcode 9 => 'Country', // a:billing_address_country c:primary_address_country 10 => 'Notes', // a:description 11 => 'Work Phone', // a:phone_office c:phone_work 12 => 'Other Phone',// a:phone_alternate c:phone_other 13 => 'Email', // a:email c:email1 14 => 'website', // a:website My first instinct is always to find info about somebody doing this before (e.g. APIs 15 => 'employees', Documentation // a:employees 16 => 'ticker_symbol// a:ticker_symbol ', forum questions, Wikis) </source> === Using a Form ===I searched the The [http://www.sugarcrm.com/wiki for importing or APIs and quickly found the /index.php?title=SOAP_Intro_and_Practical_Examples SOAP Intro and Practical Examples], so I figured I was onto shows a fast tracksimple form-based processing script for leads.However, the soap example worked with a frontend The form (which wasn't explained much) leading me to investigate more into what was required by the soap service. When I can be found in the form in "examples", it directory in the source. Note: the example did not work as an application entry point (because even though it defined ('sugarEntry', true); there was also an IF that pre-empted that definition) -- because other code was being loaded before requests inside the SugarCRM install directory will automatically bootstrap the formSugarCRM system). I did not immediately see To work around this cause for the example failure, so I either put the example form outside the application directory and successfully used or define sugarEntry as true ''without'' the 'if' conditional. Using the lead capture form to insert a single lead This is illustrated at http://www.sugarcrm.com/wiki/index.php?title=Creating_a_lead_capture_form_for_your_websiteSeeing that If your needs are more complex than the simple example worked, but did not capture all the details provided in the data exercise, I knew I should look you can learn more by looking at the existing functionality of modules dealing with Import (dataMaps), the database abstraction layer (SugarBeans and VarDefs) or the database directly to get a clearer picture of what I needed to label everythinggoing on in the SugarCRM system.
=== Use the Source Luke ===
My second instinct was to look at the source. Answers are always found in the source, with the caveat that it can be confusing and/or time-consuming to find those answers. Looking at the full application import routines and the four-step forms for importing leads, it was obvious that there was a lot of machinery that I didn't necessarily need or want to get involved with to simply import records. For instance, I would not want to (re-)write the form and UI handling when I simply needed to insert records into a database. This made me reconsider using the SOAP example to do the job. In essence, it would only need to read the exercise data and create full records for the 50 provided leads.
<!--From looking Looking at the source code of the full application import routinesand the four-step forms for importing leads, I noticed there is a lot of example machinery that the modules/Import/configyou could use to create a sophisticated web front-end using SugarCRM internals.php file defined various types of data profiles For this exercise, and that the "Salesforce" profile was close I want to the data assume that we'll be operating between two websites, and so I hadwill simply insert records into the database communicating with SugarCRM's SOAP server.
; Tip: You can peer into the SugarCRM environment with a call to PHP's 'get_defined_vars()'. This will give you some information about field maps etc.
<source lang="php">
# temp.php to show a concise view of the variables defined in the config script
)
</source>
-->
== Using SOAP ==
Note: Soap SOAP examples seem to have changed can obviously change slightly over time with the addition of a version numberSugarCRM, so you must be careful about what example you use, and generally rely on the source to be the definitive source ;-)
* soap/SoapPortalUsers.php
* soap/SoapSugarUsers.php
===Accounts fields: ===
See [[Importing_contacts/Accounts See the SOAP profile for Accounts]]
=== Contacts fields: ===
See [[Importing_contacts/Contacts See the SOAP profile for Contacts]]
$arrData[$k] = explode($separator, $v);
}
// insert our Account records
foreach ($arrData as $record) {
// Accounts
$set_entry_params = array(
'session' => $session_id,
)
);
// make the soap call
$result = $soapclient->call('set_entry',$set_entry_params);
} // print "\n <br />Entry results:<br />\n<pre>\n"; var_export ($result); print "\n</pre>\n"; // insert Contactsthe return value will give us the unique identifier for the Account record, which we // then use to create a relationship entry when creating the Contactforeach ( $arrData as accountId = $record) {result['id']; // Contacts
$set_entry_params = array(
'session' => $session_id,
array('name'=>'email', 'value'=>$record[$fieldKeys['Email']]),
array('name'=>'account_name', 'value'=>$record[$fieldKeys['Company Name']]), // relation
array('name'=>'account_id', 'value'=>$accountId), // relation
array('name'=>'assigned_user_id', 'value'=>$user_guid)
)
);
// make the soap call to create the (related) Contact entry
$result = $soapclient->call('set_entry',$set_entry_params);
print "\n <br />Contact Entry results:<br />\n<pre>\n"; var_export ($result); print "\n</pre>\n";
$contactId = $result['id'];
/** // alternately, if you have both ids, you can set the relationship for these two records using set_relationship $set_relationship_params = array( 'session' => $session_id, 'set_relationship_value' => array( 'module1' => 'Contacts', 'module1_id' => $contactId, 'module2' => 'Accounts', 'module2_id' => $accountId ) ); $result = $soapclient->call('set_entryset_relationship',$set_entry_paramsset_relationship_params); print "\n <br />Relationship results:<br />\n<pre>\n"; var_export ($result); print "\n</pre>\n"; */
}
</source>
I didn't just arrive at the code magically. Instead, I just 'drew' a map of the columns of source data that I had, and marked where each data element would potentially fit into the target database table.
<source lang="php">
print '<pre>'; var_export ($arrFields); print '</pre>';
array (
0 => 'Company Name', // a:name
1 => 'Industry', // a:industry
2 => 'First Name', // c:first_name
3 => 'Last Name', // c:last_name
4 => 'Fax', // a:phone_fax c:phone_fax
5 => 'Address', // a:billing_address_street c:primary_address_street
6 => 'City', // a:billing_address_city c:primary_address_city
7 => 'State', // a:billing_address_state c:primary_address_state
8 => 'Zip Code', // a:billing_address_postalcode c:primary_address_postalcode
9 => 'Country', // a:billing_address_country c:primary_address_country
10 => 'Notes', // a:description
11 => 'Work Phone', // a:phone_office c:phone_work
12 => 'Other Phone', // a:phone_alternate c:phone_other
13 => 'Email', // a:email c:email1
14 => 'website', // a:website
15 => 'employees', // a:employees
16 => 'ticker_symbol', // a:ticker_symbol
)
</source>
And there are a few extra elements like GUID that go into the actual scrip
The first attempt did not establish the record relationships the way that I intended by using the '''Company Name''' as the relation. It seemed plausible from the definition, but it didn't happen that way.
== Some Issues discovered Improvements for SugarCRM ==
# "Import Step 2: Upload Export File" is a confusing title on the second step of the "Import Contacts" wizard. How can one use the words 'import', 'upload', 'export' all at once? It could read "Import Step 2: Select Data File to load"
# Typographical bug: I saw that the term "custom_delimeted" in the language files. It doesn't actually cause any errors, but b/c the term is used elsewhere (spelled correctly), this typo could potentially lead to a real bug down the line.
## ./modules/Import/ImportStep2.php
# The source is formatted poorly, or practically not at all in some cases, due to various editors using different space and tab settings, and line endings. PHPBeautifier used in a commit hook would solve this in the repo. Coding standards and configuration files like vim modelines would solve this on the developer desktop.
Being open source, I can pass this information on to the developers (or even get involved myself) and will likely see a positive response to these suggested fixes.
== Further Resources ==
* http://www.sugarcrm.com/wiki/index.php?title=SOAP_Intro_and_Practical_Examples
* http://freephile.com/crm/index.php?module=Home&action=TrainingPortal
* http://freephile.com/crm/soap.php
* http://www.sugarcrm.com/wiki/index.php?title=SOAP_in_PHP
* http://dietrich.ganx4.com/nusoap/
* http://www.beanizer.org/site/index.php/en/Articles/Sugar-CRM-integration-with-custom-PHP-applications-I.html