Skip to content

Instantly share code, notes, and snippets.

@pgorod
Last active July 22, 2022 08:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pgorod/3ce7868a290df0ef4f8c9de74f12a6cd to your computer and use it in GitHub Desktop.
Save pgorod/3ce7868a290df0ef4f8c9de74f12a6cd to your computer and use it in GitHub Desktop.
Sample Import_script for SuiteCRM, CSV to beans and relationships, and security groups
<?php
//xdebug_break();
if (!defined('sugarEntry') || !sugarEntry) die ('Not a Valid Entry Point!');
$date= new DateTime();
//chdir('/opt/bitnami/apps/suitecrm/htdocs/');
echo 'School Importer started at ';
echo $date->format('r').'<br>';
echo '-------------------------------------------------------------------------------------------------------------------------------------<br>';
require_once("include/utils/sugar_file_utils.php");
SchoolImporterJobs();
die();
function time_elapsed()
{
static $first = null;
static $previous = null;
$now = microtime(true);
if ($first == null) $first = $now;
if ($previous != null)
echo '--- Partial ' . round(($now - $previous), 2) . ', Total ' . round(($now - $first), 2) . ' ---'; // 109s
$ret = round(($now - $previous), 2);
$previous = $now;
return $ret;
}
function myLog ($str2log) {
file_put_contents('./zlog_'.date("j.n.Y").'.txt', date("H:i:s", time())." ".$str2log.PHP_EOL, FILE_APPEND);
}
function calcDelta($a1, $a2)
{
//combine into a nice associative array:
$delta=Array();
foreach ($a1 as $key=>$value) {
if ($a1[$key] != $a2->$key)
$delta[] = array($key => ("Was ". $a1[$key]. ", became " . $a2->$key));
}
//$num = count($data);
if (empty($a1)) $delta[] = array("a1" => ("Was empty"));
if (empty($a2)) $delta[] = array("a2" => ("Was empty"));
return $delta;
}
function fillPerson($record, &$person, $prefix)
{
require('custom/School/SchoolImportclassesTable.php');
// $record is what is being imported from CSV
// $person is the bean about to be filled and going into the SuitCRM DB. It may be new or not, depending on whether it exists in the DB previously.
// $prefix is "Student: ", "Father: ", "Mother: " ou "EE: " and it's part of the field names to import on the CSV, for example: "Father: Address"
// name: only updates if not existant yet, because it's the key we use for search, and because names
// in SuiteCRM are more complex, with parts
if ($person->full_name_c == "") {
$recordname = trim(($record[$prefix . "Name"] != "") ? $record[$prefix . "Name"] : "[To-be-filled]");
$recordname = str_replace(" ", " ", $recordname);
$parts = explode(" ", $recordname);
$person->last_name = array_pop($parts);
$person->first_name = $parts[0];
$person->name = $person->first_name . " " . $person->last_name;
$person->full_name_c = $record[$prefix . "Name"]; // custom field created in Studio
}
$datanasc = DateTime::createFromFormat('!d/m/Y', $record[$prefix . "Birthdate"]);
//$datasnasc->setTime(0, 0);
$person->birthdate = ($datanasc == false) ? "" : $datanasc->format('Y-m-d');
$person->email1 = $record[$prefix . "Email"];
$person->phone_home = $record[$prefix . "Telephone"];
$person->phone_mobile = $record[$prefix . "Mobile"];
$person->title = ""; //Dr.";
if (true || ($person->primary_address_street == "")) {
$Address = $record[$prefix . "Address"];
$codpostal = $record[$prefix . "Codigo Postal"];
$nporta = $record[$prefix . "Numero de Porta"];
$andar = $record[$prefix . "Andar"];
$loc = $record[$prefix . "Localidade"];
if ($nporta != "")
if (!(strpos($Address, $nporta) && strpos($Address, $andar))) {
$nporta = str_replace('nº ','', $nporta);
$nporta = str_replace('n. ','', $nporta);
$nporta = str_replace('nº','', $nporta);
$nporta = str_replace('n.','', $nporta);
$nporta = rtrim($nporta, ' ,-.');
$Address = $Address . ', nº ' . $nporta . (($andar != "") ? (', ' . $andar) : "");
}
$Address = str_replace(' ', ' ', $Address);
$Address = str_replace(',,', ',', $Address);
$Address = str_replace(', ,', ', ', $Address);
$Address = rtrim(ltrim($Address));
// replace Localidade only if at end of string codpostal:
if (substr(strtoupper($codpostal),-strlen($loc))===strtoupper($loc))
$codpostal = rtrim(substr($codpostal, 0, strlen($codpostal)-strlen($loc)));
// replace Localidade only if at end of string Address:
if (substr(strtoupper($Address),-strlen($loc))===strtoupper($loc))
$Address = rtrim(substr($Address, 0, strlen($Address)-strlen($loc)));
if ($codpostal != "") {
$Address = str_replace($codpostal, '', $Address); // algumas Addresss incluem codpostal...
$person->primary_address_postalcode = $codpostal;
}
$person->primary_address_street = $Address;
$person->primary_address_city = ucwords(strtolower($loc)); // converts to title case
$person->primary_address_country = ""; // $record[$prefix . "Fathers"];
}
$sex = $record[$prefix . "Sexo"];
if ($sex == "Male") $person->sex_c = 'M';
if ($sex == "Female") $person->sex_c = 'F';
if ($prefix == "Father: ") $person->sex_c = 'M';
if ($prefix == "Mother: ") $person->sex_c = 'F';
$num_int =$record[$prefix . "Numero Interno"];
if ($num_int != "") {
$person->photo = $num_int . "_photo.jpg";
//$zing_file = 'upload/Fotos/' . $person->photo;
//$filename = 'index.php';
//$file_path = dirname($filename); //. DIRECTORY_SEPARATOR . "{$filename}";
//clearstatcache();
//if (count(glob('upload/Fotos/'. $person->photo))==1) {
if (file_exists('upload/Fotos/' . $person->photo)) {
copy('upload/Fotos/' . $person->photo, 'upload/' . $person->id . '_photo');
}
}
if ($prefix == "Student: ") {
$class = $record["Student: class"];
$college = str_replace("School ", "", $record["Student: Escola"]);
if (is_array($classes[$college][$class])) {
// 0 "Anos", 1: "Ano", 2: "class", 3: "Finalistas", 5: "Curso"),
$person->grade_c = $classes[$college][$class][1];
$person->class_c = $classes[$college][$class][2];
$person->classe_c = $classes[$college][$class][3];
}
else {
mylog("Can't find class " . $class . " at ".$college . " for ".$person->full_name_c );
$person->grade_c = "Alumni";
//$person->class_c = $classes[$college][$class][2];
//$person->classe_c = $classes[$college][$class][3];
}
}
// finish by making a complete analysis of what changed:
return calcDelta($person->fetched_row, $person);
}
function GetOrCreateSimpleContact ($the_name, $college) {
if ($the_name != "") {
$person = BeanFactory::getBean("Contacts");
$person = $person->retrieve_by_string_fields(array('full_name_c' => $the_name));
if (is_null($person)) {
$person = BeanFactory::newBean("Contacts");
$person->full_name_c = $the_name;
$person_name = str_replace(" ", " ", $the_name);
$parts = explode(" ", $person_name);
$person->last_name = array_pop($parts);
$person->first_name = $parts[0];
$person->name = $person->first_name . " " . $person->last_name;
$person_id = $person->save();
// add new person to account belonging to his school:
$account = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name'=> $college));
$person->load_relationship('accounts');
$person->accounts->add($account->id);
// now set Security so accesses are granted to appropriate users:
$securityTeam = new SecurityGroup();
$securityTeam->retrieve_by_string_fields(array('name' => $college ));
if ( $securityTeam->id == null)
$GLOBALS['log']->fatal("Group with the name '" . array('name' => $college ) . "' wasn't found!");
$person->load_relationship('SecurityGroups');
$person->SecurityGroups->add($securityTeam->id);
return $person;
}
return $person;
}
return null;
}
/* ****************************************************************************************************
* ****************************************************************************************************
* ****************************************************************************************************
* ****************************************************************************************************
* ****************************************************************************************************
* ****************************************************************************************************
* ****************************************************************************************************
* ****************************************************************************************************/
//array_push($job_strings, 'SchoolImporterJobs');
function SchoolImporterJobs()
{
try{
time_elapsed();
$GLOBALS['log']->info('Running: SchoolImporterJob');
$config = new Configurator();
$config->loadConfig();
$csvDataDir = 'custom/School/ToImport'; //$config->config['SchoolImporter_DataFilePath'];
$GLOBALS['log']->info("SchoolImporterJob: Scanning CSV Data dir $csvDataDir...");
$directoryContent = scandir($csvDataDir);
$GLOBALS['log']->info("SchoolImporterJob: Scanning CSV Data dir $csvDataDir... [Found " . count($directoryContent) . " files]");
foreach ($directoryContent as $itemFile) {
if (is_dir($csvDataDir . DIRECTORY_SEPARATOR . $itemFile)) continue;
if (strcasecmp(substr($itemFile, -4), ".csv") != 0) continue;
$GLOBALS['log']->info("SchoolImporterJob: Processing $itemFile file...");
myLog("---------------------------------------------------");
myLog("SchoolImporterJob: Processing $itemFile file...");
myLog("----------------------------------------------------");
$oFile = fopen($csvDataDir . DIRECTORY_SEPARATOR . $itemFile, 'r');
if ($oFile !== FALSE) {
// read entire file at once:
// expected separator is ",", expected encoding is UTF-8 without BOM (BOM is 3 weird characters in beginning of file)
while (($data[] = fgetcsv($oFile, 3000, ',')) !== FALSE) { }
fclose($oFile);
//combine into a nice associative array:
$arow=Array();
$csvFields = array_shift($data);
foreach ($data as $i=>$arow) {
//$GLOBALS['log']->info("SchoolImporterJob: array_combine " . $i);
$data[$i] = array_combine($csvFields, $arow);
}
unset($arow); // **********************************************! ! ! !! ! ! ! ! ! !! !
//$myUser = BeanFactory::getBean('Users')->retrieve_by_string_fields(array('first_name' => 'MyName'));
$SchoolA = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name' => 'SchoolA'));
$SchoolB = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name' => 'SchoolB'));
$SchoolC = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name' => 'SchoolC'));
$SchoolD = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name' => 'SchoolD'));
$SchoolAAlumni = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name' => 'A.Alumni'));
$SchoolBAlumni = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name' => 'B.Alumni'));
$SchoolCAlumni = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name' => 'C.Alumni'));
$SchoolDAlumni = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name' => 'D.Alumni'));
$cgi_accounts=Array(
"SchoolA" => $SchoolA,
"SchoolB" => $SchoolB,
"SchoolC" => $SchoolC,
"SchoolD" => $SchoolD,
"A.Alumni" => $SchoolAAlumni,
"B.Alumni" => $SchoolBAlumni,
"C.Alumni" => $SchoolCAlumni,
"D.Alumni" => $SchoolDAlumni,
);
$num = count($data);
echo "<p> $num lines in file $row: <br /></p>\n";
// echo implode(", ", $csvFields) . "<br /><br />\n";
for ($row=0; $row < $num ; $row++) { // normal bounds: from 0 to $num
$limitnames = "";
$limitclasses = "";
$limitcollege = "";
if (($limitnames!="") and (substr($data[$row]["Student: Name"], - strlen($limitnames)) != $limitnames)) {
$GLOBALS['log']->info("SchoolImporterJob: Skipping " . $data[$row]["Student: Name"]);
myLog("Skipping " . $data[$row]["Student: Name"]);
continue;
}
$college = str_replace("School ", "", $data[$row]["Student: Escola"]);
if (($limitcollege!="") and ($college != $limitcollege)) {
$GLOBALS['log']->info("SchoolImporterJob: Skipping " . $college);
myLog("Skipping by limitcollege " . $college. " for " . $data[$row]["Student: Name"]);
continue;
}
if (($limitclasses!="") and (substr($data[$row]["Student: class"], - strlen($limitclasses)) != $limitclasses)) {
$GLOBALS['log']->info("SchoolImporterJob: Skipping " . $data[$row]["Student: class"]);
myLog("Skipping by limitclasses " . $data[$row]["Student: class"] . " for " . $data[$row]["Student: Name"]);
continue;
}
if ($data[$row]["Student: Numero Interno"] == "") {
$GLOBALS['log']->info("SchoolImporterJob: Skipping empty Numero Interno for " . $data[$row]["Student: Name"]);
myLog("Skipping empty Numero Interno for " . $data[$row]["Student: Name"]);
continue;
}
$GLOBALS['log']->info("SchoolImporterJob: Importing " . $data[$row]["Student: Name"]);
myLog("Importing #". $row. " " . $data[$row]["Student: Name"]);
// echo "<table>\n";
// foreach ($csvFields as $aField) {
// echo "<tr><td>" . $aField . "</td><td>" . $data[$row][$aField] . "</td><td>" . $data[$row+1][$aField] . "</td><td>" . $data[$row+2][$aField] . "</td></tr>\n";
// }
// echo "</table>\n";
// echo implode(", ", $data[$row]) . "<br /><br />\n";
//in input file, each line (record) includes fields for Student, mother, Father and ee
$Student = BeanFactory::getBean("Contacts");
$Student=$Student->retrieve_by_string_fields(array('full_name_c' => $data[$row]["Student: Name"]));
if (is_null($Student))
$Student = BeanFactory::newBean("Contacts");
$delta = fillPerson($data[$row], $Student, "Student: ");
// tutores, with Account college, linked as relationships:
// dt = Director de class, filled in as direct reports
$tutor = GetOrCreateSimpleContact($data[$row]["Student: tutor"], $college);
$dt = GetOrCreateSimpleContact($data[$row]["Student: Diretor de class"], $college);
$extra_change = false;
if ($Student->reports_to_id != $dt->id) {
$Student->reports_to_id = $dt->id;
$extra_change = true;
}
if (count($delta) || $extra_change) {
$Student_id = $Student->save();
}
$Father = BeanFactory::getBean("Contacts");
$Father=$Father->retrieve_by_string_fields(array('full_name_c' => $data[$row]["Father: Name"]));
if (is_null($Father))
$Father = BeanFactory::newBean("Contacts");
$delta = fillPerson($data[$row], $Father, "Father: ");
if (count($delta))
$Father_id = $Father->save();
$mother = BeanFactory::getBean("Contacts");
$mother=$mother->retrieve_by_string_fields(array('full_name_c' => $data[$row]["Mother: Name"]));
if (is_null($mother))
$mother = BeanFactory::newBean("Contacts");
$delta = fillPerson($data[$row], $mother, "Mother: "); // Mother é Mother...
if (count($delta))
$mother_id = $mother->save();
// Records have been saved: from this point on, only work on relationships:
// primaryEducator:
$Father_ee = 0;
$mother_ee = 0;
if ($Father->full_name_c == $data[$row]["EE: Name"])
$Father_ee =1;
if ($mother->full_name_c == $data[$row]["EE: Name"])
$mother_ee = 1;
$Student->load_relationship('contacts_contacts_1');
$newrelFather = $Student->contacts_contacts_1->add($Father, array('reltype' => 'Father', 'primaryEducator' => $Father_ee));
$newrelmother = $Student->contacts_contacts_1->add($mother, array('reltype' => 'Mother', 'primaryEducator' => $mother_ee));
$newreltutor = $Student->contacts_contacts_1->add($tutor, array('reltype' => 'tutor', 'primaryEducator' => 'false'));
// student gets into account for his/her school / class:
$grade = substr($college, 0, 1) . "." . $Student->grade_c;
$account = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name' => $grade));
if (is_null($account)) {
$account = BeanFactory::newBean("Accounts");
$account->parent_id = $cgi_accounts[$college]->id;
$account->name = $grade;
$account->save();
$securityTeam = new SecurityGroup();
$securityTeam->retrieve_by_string_fields(array('name' => $college ));
$account->load_relationship('SecurityGroups');
$account->SecurityGroups->add($securityTeam->id);
}
$Student->load_relationship('accounts');
$Student->accounts->add($account->id);
// remove Student from generic accounts like "P.Alumni", "M.Alumni":
if ($Student->grade_c != "Alumni") {
$somereturn = $Student->accounts->delete($Student->id, $cgi_accounts[substr($college, 0, 1) . ".Alumni"]);
myLog('Deleting Alumni account '. (substr($college, 0, 1) . ".Alumni").' returned '.print_r($somereturn));
}
// remove Student from generic accounts like "SchoolA", "SchoolB":
$somereturn = $Student->accounts->delete($Student->id, $cgi_accounts[$college]);
// parents might have kids in two schools, so they all go into generic "Fathers" account:
$account = BeanFactory::getBean('Accounts')->retrieve_by_string_fields(array('name'=> 'Fathers'));
$Father->load_relationship('accounts');
$Father->accounts->add($account->id);
$mother->load_relationship('accounts');
$mother->accounts->add($account->id);
// now set Security so accesses are granted to appropriate users:
$securityTeam = new SecurityGroup();
$securityTeam->retrieve_by_string_fields(array('name' => $college ));
if ( $securityTeam->id == null)
$GLOBALS['log']->fatal("Group with the name '" . array('name' => $college ) . "' wasn't found!");
$Student->load_relationship('SecurityGroups');
$Student->SecurityGroups->add($securityTeam->id);
$Father->load_relationship('SecurityGroups');
$Father->SecurityGroups->add($securityTeam->id);
$mother->load_relationship('SecurityGroups');
$mother->SecurityGroups->add($securityTeam->id);
if (!is_null($tutor)) {
$tutor->load_relationship('SecurityGroups');
$tutor->SecurityGroups->add($securityTeam->id);
}
//$GLOBALS['log']->info("SchoolImporterJob: Importing done in " . round(time_elapsed(),2) . " seconds.");
myLog("Importing done in " . round(time_elapsed(),2) . " seconds.");
echo "<br /><br />\n";
unset($data[$row]);
}
// echo print_r($GLOBALS["beanList"]);
}
}
$GLOBALS['log']->info('End: SchoolImporterJob');
myLog('End: SchoolImporterJob');
time_elapsed();
return true;
} catch (Exception $e) {
$GLOBALS['log']->fatal("SchoolImporterJob: Exception " . $e->getMessage());
myLog("SchoolImporterJob: Exception " . $e->getMessage());
echo '\n\nCaught exception: ', $e->getMessage(), "\n";
return false;
}
}
/*
$myfield_defs = $Student->getFieldDefinitions(); // just to help while developing
foreach($myfield_defs as $def) echo $def["name"] . "<br />\n";
Fields from SugarBean "Contact":
------------------------------
id
name
date_entered
date_modified
modified_user_id
modified_by_name
created_by
created_by_name
description
deleted
created_by_link
modified_user_link
assigned_user_id
assigned_user_name
assigned_user_link
SecurityGroups
salutation
first_name
last_name
full_name
title
photo
department
do_not_call
phone_home
email
phone_mobile
phone_work
phone_other
phone_fax
email1
email2
invalid_email
email_opt_out
primary_address_street
primary_address_street_2
primary_address_street_3
primary_address_city
primary_address_state
primary_address_postalcode
primary_address_country
alt_address_street
alt_address_street_2
alt_address_street_3
alt_address_city
alt_address_state
alt_address_postalcode
alt_address_country
assistant
assistant_phone
email_addresses_primary
email_addresses
email_addresses_non_primary
email_and_name1
lead_source
account_name
account_id
opportunity_role_fields
opportunity_role_id
opportunity_role
reports_to_id
report_to_name
birthdate
accounts
reports_to_link
opportunities
bugs
calls
cases
direct_reports
emails
documents
leads
meetings
notes
project
project_resource
tasks
tasks_parent
notes_parent
user_sync
camFathergn_id
camFathergn_name
camFathergns
camFathergn_contacts
c_accept_status_fields
m_accept_status_fields
accept_status_id
accept_status_name
prospect_lists
sync_contact
fp_events_contacts
aos_quotes
aos_invoices
aos_contracts
e_invite_status_fields
event_status_name
event_invite_id
e_accept_status_fields
event_accept_status
event_status_id
project_contacts_1
aop_case_updates
joomla_account_id
portal_account_disabled
joomla_account_access
portal_user_type
full_name_c
grade_c
class_c
classe_c
enceduc_c // not used, we have field in Relationship instead
sex_c
contacts_contacts_1
e_reltype
reltype
rel_id
jjwg_maps_address_c
jjwg_maps_geocode_status_c
jjwg_maps_lat_c
jjwg_maps_lng_c
SQL Commands:
Orphaned contacts_cstm:
SELECT * FROM `contacts_cstm` as t1 LEFT JOIN `contacts` as t2 ON t1.`id_c` = t2.`id` WHERE t2.`id` IS NULL
delete t1 FROM `contacts_cstm` as t1 LEFT JOIN `contacts` as t2 ON t1.`id_c` = t2.`id` WHERE t2.`id` IS NULL
Orphaned contacts_contacts_1_c fro ida side:
SELECT * FROM `contacts_contacts_1_c` as t1 LEFT JOIN `contacts` as t2 ON t1.`contacts_contacts_1contacts_ida` = t2.`id` WHERE t2.`id` IS NULL
Orphaned contacts_contacts_1_c fro idb side:
SELECT * FROM `contacts_contacts_1_c` as t1 LEFT JOIN `contacts` as t2 ON t1.`contacts_contacts_1contacts_idb` = t2.`id` WHERE t2.`id` IS NULL
*/
@pgorod
Copy link
Author

pgorod commented Jun 12, 2018

So, this has a lot of stuff people will never need, but it comes from a real-life usage and I don't have time to reduce this and clean-up code. I just translated parts and obfuscated others.

Parts that will be of use to many people are:

  • loop to read several CSV from directory
  • loads CSV into memory (all of it - it can be pretty big but will eventually hit limits, so watch out)
  • imports records one by one, with some filters, and using beans, not direct database access
  • builds relationships around Contacts
  • sets permissions (relating records to security groups)

@Ramblin
Copy link

Ramblin commented Apr 29, 2022

@pgorod

It seems every place I turn, you are there! :-)

How do I call this script or connect it to an existing SuiteCRM UI to integrate it with the Contacts and Accounts Import function?

I am going to go through this script to see if I can figure out how to make it work for my situation.

I can share my document on how I got this far with my upgrade safe edits, but to net it out, in my situation, I needed a many-to-many relationship between Contacts<->Products and another between Accounts<->Products. I needed an additional field in the relationship table (number) to record how many of each "Product" each Contact/Account bought. And I needed to have the subpanel Edit button enable the user to edit the number field. I have all that - document available if you want.

But I now need to figure out how to:

  • Import data for Contacts - including the Product and the number field for the product
  • Import data for Accounts - including the Product and the number field for the product
  • Search and Report on the Contacts/Accounts history with Products, including the number
  • Integrate the results of the search in creating Target Lists and sending Campaign emails

(not much left ... right ? :-)

So hopefully your script will help with parts 1 and 2

I have

@pgorod
Copy link
Author

pgorod commented Apr 30, 2022

Hi!

This script doesn't have to be integrated into the UI, the easiest thing is to just run it from the command-line or by typing a full URL into the browser. At least for me, this was just an initial import, not something done regularly.

Ask me in the Forums you have any questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment