Hi everyone,
I’m working with PHPMaker 2025.11 and trying to skip duplicate contacts during the import process using the Row_Import event.
Here’s what I’m trying to achieve:
-
Each contact has a LinkedIn field.
-
If the LinkedIn URL already exists in the contacts table, the import should skip that single row silently — without stopping the entire import process.
-
For missing required fields (like Firstname, Lastname, EntityId, or ContactownerId), I still want to throw an exception to cancel that row.
I tried the following logic inside Row_Import:
$exists = $conn->fetchOne(
'SELECT 1 FROM contacts WHERE LOWER(Linkedin) = LOWER(?)',
[$row['Linkedin']]
);
if ($exists) {
return false; // Skip the row if LinkedIn already exists
}
I also disabled “Import with transaction” in the Advanced Settings.
However, when I do this:
- Any row that returns
false
is counted as a failure, which is expected. - The other rows are still correctly detected and processed by
Row_Import
— PHPMaker shows them as “green” in the preview. - But because one of the rows returns
false
, I am not allowed to proceed with the actual import at all. - So in the end, none of the records are inserted, even though the others are perfectly valid.
My question:
Why does return false in Row_Import cause the whole import to stop, even when transactions are disabled?
Is there a better way to skip individual duplicates without breaking the entire import?
Full code:
// Row Import event
function Row_Import(array &$row, int $count): bool
{
// Transaction nicht mehr notwendig, weil es dafür eine eigene option in den advanced settings gibt
//Log($count); // Import record count
//var_dump($row); // Import row
//return false; // Return false to skip import
global $conn;
$conn = $GLOBALS["Conn"]; // DB-Verbindung holen
// CSV-Header → DB-Felder mappen (ohne Note, Tags, Dateofbirth)
$row['Lastname'] = trim($row['Last Name'] ?? '');
$row['Firstname'] = trim($row['First Name'] ?? '');
if ($row['Firstname'] === '' || $row['Lastname'] === '') {
throw new \Port\Exception\UnexpectedValueException(
"Missing first name or last name"
);
return false; // Diese Zeile wird nicht importiert
}
$row['Nickname'] = '';
// *********************************************************
// EntityId über Firmenname ermitteln oder neue Firma anlegen
// *********************************************************
$company = trim($row['Company'] ?? '');
$entityId = null;
if ($company !== '') {
$entityId = $conn->fetchOne('SELECT EntityId FROM entities WHERE LOWER(Name) = LOWER(?)', [$company]);
if (!$entityId) {
$conn->executeStatement(
'INSERT INTO entities
(Name, Own, NotesFromEntities, NotesFromContacts, NotesFromProjects, AddedTs)
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)',
[$company, 0, 0, 0, 0]
);
$entityId = $conn->lastInsertId();
}
$row['EntityId'] = $entityId;
} else {
throw new \Port\Exception\UnexpectedValueException(
"Missing company name for {$row['Firstname']} {$row['Lastname']}"
);
return false; // Diese Zeile wird nicht importiert
}
// *********************************************************
// Duplikatcheck über LinkedIn
// *********************************************************
$exists = $conn->fetchOne(
'SELECT 1 FROM contacts WHERE LOWER(Linkedin) = LOWER(?)',
[$row['Linkedin']]
);
if ($exists) {
// Duplikat → überspringen (aber erfolgreich)
//$row = [];
//$row = array_intersect_key($row, array_flip(['Firstname', 'Lastname', 'EntityId']));
//return true;
return false;
}
$row['Entityposition'] = trim($row['Job Title'] ?? '');
//$sql = "SELECT ContactId FROM users WHERE Username = ?";
//$contactId = $conn->executeQuery($sql, [CurrentUserName()])->fetchOne();
//$row['ContactownerId'] = $contactId;
// *********************************************************
// Userermittlung
// *********************************************************
$defaultOwnerId = $conn->fetchOne("
SELECT Value FROM customizing_table
WHERE Name = 'ContactownerId import'
");
$contactId = is_numeric($defaultOwnerId) ? (int)$defaultOwnerId : 0;
if ($contactId <= 0) {
throw new \Port\Exception\UnexpectedValueException(
"Missing or invalid ContactOwnerId in customizing_table"
);
return false;
}
$row['ContactownerId'] = $contactId;
$row['Email'] = trim($row['Email'] ?? '');
$row['Phone'] = trim($row['Phone'] ?? '');
$row['Linkedin'] = trim($row['Linkedin'] ?? '');
if ($row['Linkedin'] === '') {
$row['Linkedin'] = 'NOT AVAILABLE';
}
// Standard-TagId(s) für Import aus customizing_table holen
$defaultTags = $conn->fetchOne("
SELECT Value FROM customizing_table
WHERE Name = 'DefaultImportContactTagId'
AND Active = '1'
");
if ($defaultTags !== '0') {
$row['Tags'] = $defaultTags;
} else {
$row['Tags'] = ''; // leer lassen, wenn kein gültiger Wert gefunden
}
$row['WebsiteLinks'] = '';
$row['SharePointLinks']= '';
// Nur erlaubte Felder behalten – Rest automatisch entfernen
$allowedFields = [
'Lastname',
'Firstname',
'Nickname',
'EntityId',
'Entityposition',
'ContactownerId',
'Email',
'Phone',
'Linkedin',
'Tags',
'WebsiteLinks',
'SharePointLinks'
];
$row = array_intersect_key($row, array_flip($allowedFields));
// Neuer Kontakt → Insert zulassen
return true;
}