';
}
$tone = contactAvatarTone($contact_id);
return '' . escapeHtml(personInitials($name) ?: '?') . '';
}
/**
* A contact wherever a page mentions one: their avatar, then their name,
* linked to their quick look - or to their page with 'page' => true.
* Other options: 'size' of the avatar ('xs' unless given), 'class' on the
* link, 'archived' to strike the name through.
* Takes values raw or already escaped.
*/
function contactLink($client_id, $contact_id, $name, $photo = '', $options = []) {
$client_id = intval($client_id);
$contact_id = intval($contact_id);
$name = html_entity_decode((string) $name, ENT_QUOTES, 'UTF-8');
$label = escapeHtml($name !== '' ? $name : 'Unnamed contact');
$archived = !empty($options['archived']);
$class = trim('text-truncate ' . ($options['class'] ?? '') . ($archived ? ' text-decoration-line-through text-body-secondary' : ''));
$title = $archived ? ' title="Archived contact"' : '';
$link = !empty($options['page'])
? "$label"
: "$label";
return '' . contactAvatar($client_id, $contact_id, $name, $photo, $options['size'] ?? 'xs') . $link . '';
}
/** The quieter line under a contact in a picker: their title, and Primary or Technical. */
function contactOptionDescription($row) {
$role = !empty($row['contact_primary']) ? 'Primary' : (!empty($row['contact_technical']) ? 'Technical' : '');
return implode(' ยท ', array_filter([trim(html_entity_decode((string) ($row['contact_title'] ?? ''), ENT_QUOTES, 'UTF-8')), $role]));
}
/** For JSON (pickers, search): what contactAvatar() draws - the photo's URL, or their initials and colour. */
function contactAvatarData($client_id, $contact_id, $name, $photo) {
$photo = html_entity_decode((string) $photo, ENT_QUOTES, 'UTF-8');
if ($photo !== '') {
return ['image' => '/uploads/clients/' . intval($client_id) . '/' . rawurlencode($photo)];
}
return ['initials' => personInitials($name) ?: '?', 'tone' => contactAvatarTone($contact_id)];
}
/**
* How they log in to the client portal, or '' with no access: 'Password'
* or 'Microsoft'.
*/
function contactPortalAccess($contact_user_id, $auth_method) {
if (!intval($contact_user_id) || (string) $auth_method === '') {
return '';
}
return $auth_method === 'azure' ? 'Microsoft' : 'Password';
}
/**
* Their flags as pills: Primary (or Important when not primary - a primary
* contact is always important), Billing, Technical, and Portal when they can
* log in to the client portal.
*/
function contactFlagPills($row, $with_portal = true) {
$pills = [];
if (!empty($row['contact_primary'])) {
$pills[] = ['Primary', 'text-bg-success', 'The client\'s main contact'];
} elseif (!empty($row['contact_important'])) {
$pills[] = ['Important', 'bg-warning-subtle text-warning-emphasis', 'Pinned to the top of the list'];
}
if (!empty($row['contact_billing'])) {
$pills[] = ['Billing', 'bg-secondary-subtle text-secondary-emphasis', 'Receives invoices'];
}
if (!empty($row['contact_technical'])) {
$pills[] = ['Technical', 'bg-secondary-subtle text-secondary-emphasis', 'Technical contact'];
}
if ($with_portal) {
$portal = contactPortalAccess($row['contact_user_id'] ?? 0, $row['user_auth_method'] ?? '');
if ($portal) {
$pills[] = ['Portal', 'bg-info-subtle text-info-emphasis', "Client portal login ($portal)"];
}
}
$html = '';
foreach ($pills as [$label, $class, $title]) {
$html .= '' . $label . ' ';
}
return rtrim($html);
}
/** A phone number as a tel: link, with its extension. '' when there is no number. */
function contactPhoneLink($phone, $country_code, $extension = '', $class = '') {
$phone = (string) $phone;
if ($phone === '') {
return '';
}
$display = escapeHtml(formatPhoneNumber($phone, $country_code));
$country_digits = preg_replace('/[^0-9]/', '', (string) $country_code);
$dial = escapeHtml(($country_digits !== '' ? "+$country_digits" : '') . preg_replace('/[^0-9]/', '', $phone));
$extension = escapeHtml($extension);
return '' . $display . ''
. ($extension ? ' x' . $extension . '' : '');
}
/*
* Quick picker (js/quick_pick.js) for a contact's location - the same picker
* the assets use, with the contact's client as the scope.
*/
/** The contact and their location, by id. */
function contactQuickRow($contact_id) {
global $mysqli;
return mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_client_id, contact_location_id,
location_name, location_archived_at
FROM contacts
LEFT JOIN locations ON location_id = contact_location_id
WHERE contact_id = " . intval($contact_id))) ?: null;
}
/** What the location picker shows - the asset location pill, fed the contact's row. */
function contactLocationHtml($row, $style = 'list') {
return assetQuickHtml('asset_location', [
'asset_client_id' => $row['contact_client_id'] ?? 0,
'asset_location_id' => $row['contact_location_id'] ?? 0,
'location_name' => $row['location_name'] ?? '',
'location_archived_at' => $row['location_archived_at'] ?? null,
], $style);
}
/** Move a contact to one of their client's locations (0 for none), as Edit Contact would. Returns [ok, plain-text message]. */
function contactQuickSetLocation($contact_id, $value) {
global $mysqli, $session_name;
$row = contactQuickRow($contact_id);
if (!$row) {
return [false, 'That contact no longer exists.'];
}
$contact_id = intval($row['contact_id']);
$client_id = intval($row['contact_client_id']);
$location_id = assetClientOwnedId('location', $value, $client_id);
if (intval($value) && !$location_id) {
return [false, 'That location is not one of this client\'s.'];
}
if ($location_id === intval($row['contact_location_id'])) {
return [true, ''];
}
$location_name = $location_id ? (string) mysqli_fetch_row(mysqli_query($mysqli, "SELECT location_name FROM locations WHERE location_id = $location_id"))[0] : '';
mysqli_query($mysqli, "UPDATE contacts SET contact_location_id = $location_id WHERE contact_id = $contact_id");
$contact_name = escapeSql($row['contact_name']);
logAudit("Contact", "Edit", $location_id
? "$session_name set the location of contact $contact_name to " . escapeSql($location_name)
: "$session_name removed the location of contact $contact_name", $client_id, $contact_id);
return [true, $location_id ? "Moved to $location_name" : 'Location removed'];
}
/** What quick_pick.js redraws after a change. */
function contactQuickRecord($contact_id) {
$row = contactQuickRow($contact_id);
return [
'current' => (string) intval($row['contact_location_id']),
'html' => ['list' => contactLocationHtml($row, 'list'), 'detail' => contactLocationHtml($row, 'detail')],
];
}