mirror of
https://github.com/itflow-org/itflow
synced 2026-11-11 15:07:24 +00:00
322 lines
16 KiB
PHP
322 lines
16 KiB
PHP
<?php
|
|
|
|
/*
|
|
* ITFlow - vendor templates (Admin > Vendor Templates): the vendors an MSP's
|
|
* clients use again and again - the ISPs, the hardware makers, the
|
|
* distributors, the line-of-business software - set up once with their
|
|
* support line, hours, SLA, support email and site, ready to add to any
|
|
* client. A vendor made from one gets its fields and remembers which template
|
|
* it came from (vendors.vendor_template_id), so each template's use is
|
|
* counted: how many clients have a vendor from it, and when one was last
|
|
* added. License templates take their vendor from one
|
|
* (software_templates.software_template_vendor_template_id).
|
|
*
|
|
* From database 3.3.4 on a template says who made it and last changed it.
|
|
*
|
|
* Shared by admin/vendor_templates.php, admin/vendor_template.php,
|
|
* admin/modals/vendor_template/* and admin/post/vendor_template.php. A
|
|
* vendor is made from one on the agent side by add_vendor,
|
|
* add_vendor_from_template and add_vendor_template_to_clients
|
|
* (agent/post/vendor.php, with vendorTemplateRow() and
|
|
* vendorTemplateFormFields() in functions/vendor.php).
|
|
*/
|
|
|
|
/*
|
|
* What a template carries, as it shows on its page and in the window - key
|
|
* => label and icon, in order. 'phone' is the support line with its country
|
|
* code and extension.
|
|
*/
|
|
function vendorTemplateFieldLabels() {
|
|
return [
|
|
'name' => ['label' => 'Name', 'icon' => 'fa-building'],
|
|
'description' => ['label' => 'What they are', 'icon' => 'fa-align-left'],
|
|
'phone' => ['label' => 'Support line', 'icon' => 'fa-phone'],
|
|
'hours' => ['label' => 'Support hours', 'icon' => 'fa-clock'],
|
|
'sla' => ['label' => 'SLA', 'icon' => 'fa-handshake'],
|
|
'email' => ['label' => 'Support email', 'icon' => 'fa-envelope'],
|
|
'website' => ['label' => 'Support site', 'icon' => 'fa-globe'],
|
|
'account_number' => ['label' => 'Account number', 'icon' => 'fa-fingerprint'],
|
|
'code' => ['label' => 'PIN', 'icon' => 'fa-key'],
|
|
'contact_name' => ['label' => 'Account manager', 'icon' => 'fa-user-tie'],
|
|
'notes' => ['label' => 'Notes', 'icon' => 'fa-sticky-note'],
|
|
];
|
|
}
|
|
|
|
/*
|
|
* Each field's columns - on the template, and on a vendor made from it. The
|
|
* support line is three: country code, number and extension.
|
|
*/
|
|
function vendorTemplateColumns() {
|
|
return [
|
|
'name' => [['vendor_template_name', 'vendor_name']],
|
|
'description' => [['vendor_template_description', 'vendor_description']],
|
|
'phone' => [['vendor_template_phone_country_code', 'vendor_phone_country_code'], ['vendor_template_phone', 'vendor_phone'], ['vendor_template_extension', 'vendor_extension']],
|
|
'hours' => [['vendor_template_hours', 'vendor_hours']],
|
|
'sla' => [['vendor_template_sla', 'vendor_sla']],
|
|
'email' => [['vendor_template_email', 'vendor_email']],
|
|
'website' => [['vendor_template_website', 'vendor_website']],
|
|
'account_number' => [['vendor_template_account_number', 'vendor_account_number']],
|
|
'code' => [['vendor_template_code', 'vendor_code']],
|
|
'contact_name' => [['vendor_template_contact_name', 'vendor_contact_name']],
|
|
'notes' => [['vendor_template_notes', 'vendor_notes']],
|
|
];
|
|
}
|
|
|
|
/** One template, archived or not, with who made it and last changed it - raw, or null. */
|
|
function vendorTemplateInfo($vendor_template_id) {
|
|
global $mysqli;
|
|
$vendor_template_id = intval($vendor_template_id);
|
|
if ($vendor_template_id <= 0) {
|
|
return null;
|
|
}
|
|
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT vendor_templates.*, creator.user_name AS created_by_name, updater.user_name AS updated_by_name
|
|
FROM vendor_templates
|
|
LEFT JOIN users creator ON creator.user_id = vendor_template_created_by AND vendor_template_created_by > 0
|
|
LEFT JOIN users updater ON updater.user_id = vendor_template_updated_by AND vendor_template_updated_by > 0
|
|
WHERE vendor_template_id = $vendor_template_id"));
|
|
return $row ?: null;
|
|
}
|
|
|
|
/*
|
|
* How the templates are used - template id => clients (current clients with a
|
|
* current vendor from it), vendors (those vendors), ours (our own vendors from
|
|
* it) and last (when the latest was added). Ones not used aren't in it.
|
|
*/
|
|
function vendorTemplateUsage(array $vendor_template_ids) {
|
|
global $mysqli;
|
|
$ids = array_values(array_filter(array_map('intval', $vendor_template_ids)));
|
|
$usage = [];
|
|
if (!$ids) {
|
|
return $usage;
|
|
}
|
|
$sql = mysqli_query($mysqli, "SELECT vendor_template_id, COUNT(DISTINCT NULLIF(vendor_client_id, 0)) AS clients, SUM(vendor_client_id > 0) AS vendors,
|
|
SUM(vendor_client_id = 0) AS ours, MAX(vendor_created_at) AS last
|
|
FROM vendors LEFT JOIN clients ON client_id = vendor_client_id AND vendor_client_id > 0
|
|
WHERE vendor_template_id IN (" . implode(',', $ids) . ") AND vendor_archived_at IS NULL AND (vendor_client_id = 0 OR client_archived_at IS NULL AND client_id IS NOT NULL)
|
|
GROUP BY vendor_template_id");
|
|
while ($row = mysqli_fetch_assoc($sql)) {
|
|
$usage[intval($row['vendor_template_id'])] = ['clients' => intval($row['clients']), 'vendors' => intval($row['vendors']), 'ours' => intval($row['ours']), 'last' => $row['last']];
|
|
}
|
|
return $usage;
|
|
}
|
|
|
|
/*
|
|
* The window's fields, checked - or null with $error saying why. Raw values:
|
|
* name, description, phone_country_code, phone and extension (digits),
|
|
* hours, sla, email, website (without its scheme), account_number, code,
|
|
* contact_name and notes - as vendorFormSet() reads a vendor's.
|
|
*/
|
|
function vendorTemplateFields(array $post, &$error = '') {
|
|
$error = '';
|
|
$line = function ($key, $length = 200) use ($post) {
|
|
$value = is_scalar($post[$key] ?? null) ? trim(preg_replace('/\s+/u', ' ', (string) $post[$key])) : '';
|
|
return mb_substr($value, 0, $length);
|
|
};
|
|
$digits = function ($key, $length = 200) use ($post) {
|
|
return substr(preg_replace('/[^0-9]/', '', is_scalar($post[$key] ?? null) ? (string) $post[$key] : ''), 0, $length);
|
|
};
|
|
$name = is_scalar($post['name'] ?? null) ? trim(preg_replace('/\s+/u', ' ', (string) $post['name'])) : '';
|
|
if ($name === '') {
|
|
$error = 'A vendor template needs a name';
|
|
return null;
|
|
}
|
|
if (mb_strlen($name) > 200) {
|
|
$error = 'Keep the name to 200 characters';
|
|
return null;
|
|
}
|
|
$email = $line('email');
|
|
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
$error = "The support email isn't an email address";
|
|
return null;
|
|
}
|
|
$phone = $digits('phone');
|
|
return [
|
|
'name' => $name,
|
|
'description' => $line('description'),
|
|
'phone_country_code' => $phone !== '' ? $digits('phone_country_code', 10) : '',
|
|
'phone' => $phone,
|
|
'extension' => $digits('extension'),
|
|
'hours' => $line('hours'),
|
|
'sla' => $line('sla'),
|
|
'email' => $email,
|
|
'website' => mb_substr(rtrim(preg_replace('#^https?://#i', '', $line('website')), '/'), 0, 200),
|
|
'account_number' => $line('account_number'),
|
|
'code' => $line('code'),
|
|
'contact_name' => $line('contact_name'),
|
|
'notes' => is_scalar($post['notes'] ?? null) ? trim((string) $post['notes']) : '',
|
|
];
|
|
}
|
|
|
|
/** A template's fields as vendorTemplateFields() gives them - from its row, for comparing and copying. */
|
|
function vendorTemplateRowFields(array $row) {
|
|
return [
|
|
'name' => (string) $row['vendor_template_name'],
|
|
'description' => (string) $row['vendor_template_description'],
|
|
'phone_country_code' => (string) $row['vendor_template_phone_country_code'],
|
|
'phone' => (string) $row['vendor_template_phone'],
|
|
'extension' => (string) $row['vendor_template_extension'],
|
|
'hours' => (string) $row['vendor_template_hours'],
|
|
'sla' => (string) $row['vendor_template_sla'],
|
|
'email' => (string) $row['vendor_template_email'],
|
|
'website' => (string) $row['vendor_template_website'],
|
|
'account_number' => (string) $row['vendor_template_account_number'],
|
|
'code' => (string) $row['vendor_template_code'],
|
|
'contact_name' => (string) $row['vendor_template_contact_name'],
|
|
'notes' => (string) $row['vendor_template_notes'],
|
|
];
|
|
}
|
|
|
|
/* Save a template's fields (vendorTemplateFields()) - a new one with $vendor_template_id 0. Its id. */
|
|
function vendorTemplateSave($vendor_template_id, array $f) {
|
|
global $mysqli, $session_user_id;
|
|
$vendor_template_id = intval($vendor_template_id);
|
|
$user_id = intval($session_user_id ?? 0);
|
|
$set = [];
|
|
foreach (vendorTemplateColumns() as $key => $pairs) {
|
|
foreach ($pairs as [$template_column]) {
|
|
$field = substr($template_column, strlen('vendor_template_'));
|
|
$set[] = "$template_column = '" . escapeSql((string) ($f[$field] ?? '')) . "'";
|
|
}
|
|
}
|
|
$set = implode(', ', $set);
|
|
if ($vendor_template_id) {
|
|
mysqli_query($mysqli, "UPDATE vendor_templates SET $set, vendor_template_updated_by = $user_id WHERE vendor_template_id = $vendor_template_id");
|
|
return $vendor_template_id;
|
|
}
|
|
mysqli_query($mysqli, "INSERT INTO vendor_templates SET $set, vendor_template_created_by = $user_id");
|
|
return intval(mysqli_insert_id($mysqli));
|
|
}
|
|
|
|
/** What changed between two sets of fields - the labels, in order: ['Support line', 'Support hours']. */
|
|
function vendorTemplateChanged(array $before, array $after) {
|
|
$changed = [];
|
|
foreach (vendorTemplateColumns() as $key => $pairs) {
|
|
foreach ($pairs as [$template_column]) {
|
|
$field = substr($template_column, strlen('vendor_template_'));
|
|
if ((string) ($before[$field] ?? '') !== (string) ($after[$field] ?? '')) {
|
|
$changed[$key] = vendorTemplateFieldLabels()[$key]['label'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return $changed;
|
|
}
|
|
|
|
/* A new template starting from this one - "Copy of". Its id, or 0. */
|
|
function vendorTemplateCopy($vendor_template_id) {
|
|
$row = vendorTemplateInfo($vendor_template_id);
|
|
if (!$row) {
|
|
return 0;
|
|
}
|
|
$fields = vendorTemplateRowFields($row);
|
|
$fields['name'] = mb_substr('Copy of ' . $fields['name'], 0, 200);
|
|
return vendorTemplateSave(0, $fields);
|
|
}
|
|
|
|
/*
|
|
* Delete a template. The vendors made from it stay, no longer linked to it,
|
|
* and license templates that took their vendor from it have none.
|
|
*/
|
|
function vendorTemplateDelete($vendor_template_id) {
|
|
global $mysqli;
|
|
$vendor_template_id = intval($vendor_template_id);
|
|
mysqli_query($mysqli, "UPDATE vendors SET vendor_template_id = 0, vendor_updated_at = vendor_updated_at WHERE vendor_template_id = $vendor_template_id");
|
|
mysqli_query($mysqli, "UPDATE software_templates SET software_template_vendor_template_id = 0, software_template_updated_at = software_template_updated_at WHERE software_template_vendor_template_id = $vendor_template_id");
|
|
mysqli_query($mysqli, "DELETE FROM vendor_templates WHERE vendor_template_id = $vendor_template_id");
|
|
}
|
|
|
|
/*
|
|
* Put the template's fields picked ($keys, from vendorTemplateColumns()) on
|
|
* every vendor made from it - for Change them on the client vendors too.
|
|
* The vendors changed: vendor id => ['name', 'client_id'].
|
|
*/
|
|
function vendorTemplatePush($vendor_template_id, array $keys) {
|
|
global $mysqli;
|
|
$vendor_template_id = intval($vendor_template_id);
|
|
$row = vendorTemplateInfo($vendor_template_id);
|
|
$columns = vendorTemplateColumns();
|
|
$keys = array_values(array_intersect(array_keys($columns), $keys));
|
|
if (!$row || !$keys) {
|
|
return [];
|
|
}
|
|
$set = [];
|
|
foreach ($keys as $key) {
|
|
foreach ($columns[$key] as [$template_column, $vendor_column]) {
|
|
$set[] = "$vendor_column = '" . escapeSql((string) $row[$template_column]) . "'";
|
|
}
|
|
}
|
|
$vendors = [];
|
|
$sql = mysqli_query($mysqli, "SELECT vendor_id, vendor_name, vendor_client_id FROM vendors WHERE vendor_template_id = $vendor_template_id");
|
|
while ($vendor = mysqli_fetch_assoc($sql)) {
|
|
$vendors[intval($vendor['vendor_id'])] = ['name' => $vendor['vendor_name'], 'client_id' => intval($vendor['vendor_client_id'])];
|
|
}
|
|
if ($vendors) {
|
|
mysqli_query($mysqli, "UPDATE vendors SET " . implode(', ', $set) . " WHERE vendor_template_id = $vendor_template_id");
|
|
}
|
|
return $vendors;
|
|
}
|
|
|
|
/*
|
|
* The vendors made from a template, for its page - current ones first, by
|
|
* client: vendor id => vendor_id, vendor_name, client_id (0 for ours),
|
|
* client_name, client_abbreviation, account_number, code, created_at,
|
|
* archived (the vendor), client_archived.
|
|
*/
|
|
function vendorTemplateVendors($vendor_template_id) {
|
|
global $mysqli;
|
|
$vendor_template_id = intval($vendor_template_id);
|
|
$vendors = [];
|
|
$sql = mysqli_query($mysqli, "SELECT vendor_id, vendor_name, vendor_client_id, vendor_account_number, vendor_code, vendor_created_at, vendor_archived_at,
|
|
client_name, client_abbreviation, client_archived_at
|
|
FROM vendors LEFT JOIN clients ON client_id = vendor_client_id AND vendor_client_id > 0
|
|
WHERE vendor_template_id = $vendor_template_id
|
|
ORDER BY vendor_archived_at IS NOT NULL, client_archived_at IS NOT NULL, vendor_client_id = 0, client_name ASC, vendor_name ASC");
|
|
while ($row = mysqli_fetch_assoc($sql)) {
|
|
$vendors[intval($row['vendor_id'])] = [
|
|
'vendor_id' => intval($row['vendor_id']),
|
|
'vendor_name' => (string) $row['vendor_name'],
|
|
'client_id' => intval($row['vendor_client_id']),
|
|
'client_name' => (string) $row['client_name'],
|
|
'client_abbreviation' => (string) $row['client_abbreviation'],
|
|
'account_number' => trim((string) $row['vendor_account_number']),
|
|
'code' => trim((string) $row['vendor_code']),
|
|
'created_at' => $row['vendor_created_at'],
|
|
'archived' => !empty($row['vendor_archived_at']),
|
|
'client_archived' => !empty($row['client_archived_at']),
|
|
];
|
|
}
|
|
return $vendors;
|
|
}
|
|
|
|
/** The license templates that take their vendor from it - id => name, archived. */
|
|
function vendorTemplateLicenseTemplates($vendor_template_id) {
|
|
global $mysqli;
|
|
$vendor_template_id = intval($vendor_template_id);
|
|
$templates = [];
|
|
$sql = mysqli_query($mysqli, "SELECT software_template_id, software_template_name, software_template_archived_at FROM software_templates
|
|
WHERE software_template_vendor_template_id = $vendor_template_id ORDER BY software_template_archived_at IS NOT NULL, software_template_name ASC");
|
|
while ($row = mysqli_fetch_assoc($sql)) {
|
|
$templates[intval($row['software_template_id'])] = ['name' => $row['software_template_name'], 'archived' => !empty($row['software_template_archived_at'])];
|
|
}
|
|
return $templates;
|
|
}
|
|
|
|
/** A template's tile - its initials on the grey of the other template lists. $size as vendorTile(). */
|
|
function vendorTemplateTile($name, $size = '') {
|
|
$size_class = in_array($size, ['xs', 'sm', 'lg'], true) ? " itflow-asset-icon-$size itflow-vendor-tile-$size" : '';
|
|
return '<span class="itflow-asset-icon itflow-vendor-tile itflow-asset-tone-other' . $size_class . '" aria-hidden="true">' . escapeHtml(vendorInitials($name)) . '</span>';
|
|
}
|
|
|
|
/** The support line as it reads - "(412) 555-0100, then 2" - or ''. Raw in, escaped out. */
|
|
function vendorTemplatePhoneHtml($phone, $country_code, $extension) {
|
|
if (preg_replace('/[^0-9]/', '', (string) $phone) === '') {
|
|
return '';
|
|
}
|
|
$html = escapeHtml(formatPhoneNumber($phone, $country_code));
|
|
$dial = vendorDialNumber($phone, $country_code);
|
|
if ($dial !== '') {
|
|
$html = '<a href="tel:' . escapeHtml($dial) . '">' . $html . '</a>';
|
|
}
|
|
return $html . (trim((string) $extension) !== '' ? ' <span class="text-body-secondary">then ' . escapeHtml($extension) . '</span>' : '');
|
|
}
|