Skip to content

Commit da3c257

Browse files
Alexey PortnovAlexey Portnov
authored andcommitted
fix(employees): apply defaults and auto-generate SIP password on bulk import (#996)
CSV bulk import created broken extensions: rows with empty sip_secret column failed validation, and even rows with passwords could not authenticate because Asterisk silently rejected endpoints with empty dtmf_mode/transport enum values. Root cause: SaveEmployeeAction (legacy bulk path) and SaveRecordAction (REST POST path) both required sip_secret as a hard pre-validation field and never invoked DataStructure::applyDefaults() during create. CSV rows without these columns landed in DB with NULL dtmfmode, generated "dtmf_mode = " in pjsip.conf, and res_pjsip discarded the whole endpoint. Fixes: - SaveEmployeeAction::prepareData() now applies DataStructure defaults on create and auto-generates sip_secret via Sip::generateSipPassword() when missing, mirroring DataStructure::createForNewEmployee() used by the manual form. - SaveRecordAction::main() auto-generates sip_secret in Phase 1 before required-field validation (the required rule on sip_secret is now obsolete and removed). - Frontend extensions-bulk-upload.js updateRowStatus() surfaces the backend error message inline in the import preview row instead of hiding it in the browser console; message is HTML-escaped via jQuery .text() to prevent XSS.
1 parent 960712b commit da3c257

4 files changed

Lines changed: 44 additions & 11 deletions

File tree

sites/admin-cabinet/assets/js/pbx/Extensions/extensions-bulk-upload.js

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sites/admin-cabinet/assets/js/src/Extensions/extensions-bulk-upload.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,16 @@ const extensionsBulkUpload = {
10221022

10231023
// Update row class and status
10241024
$row.removeClass('positive negative warning active disabled').addClass(statusClass);
1025-
$statusCell.html(`<i class="${statusIcon} icon"></i> <span class="status-text">${statusText}</span>`);
1025+
// Surface backend error message inline (issue #996) — escape via jQuery .text() to prevent XSS.
1026+
let detailHtml = '';
1027+
if (status === 'error' && message) {
1028+
const safeMessage = $('<div>').text(message).html();
1029+
detailHtml = ` <span class="status-detail">— ${safeMessage}</span>`;
1030+
$statusCell.attr('title', message);
1031+
} else {
1032+
$statusCell.removeAttr('title');
1033+
}
1034+
$statusCell.html(`<i class="${statusIcon} icon"></i> <span class="status-text">${statusText}</span>${detailHtml}`);
10261035

10271036
console.log(`✅ [BulkUpload] Updated row ${number} to status: ${statusText}, class: ${statusClass}`);
10281037

src/PBXCoreREST/Lib/Employees/SaveEmployeeAction.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,19 @@ public static function prepareData(array $data): array
138138
'fwd_forwardingonunavailable',
139139
];
140140
$sanitizedData = self::sanitizeRoutingDestinations($sanitizedData, $routingFields, 20);
141-
141+
142+
// On create (e.g. CSV bulk import without all columns) apply DataStructure defaults
143+
// and auto-generate SIP password if missing. Mirrors SaveRecordAction Phase 4 / the
144+
// form path createForNewEmployee(). Without defaults, dtmfmode/transport remain NULL,
145+
// pjsip.conf gets `dtmf_mode = ` (empty enum), Asterisk silently rejects the endpoint
146+
// and SIP auth fails even though the row exists in DB and the file. Issue #996.
147+
if (empty($sanitizedData['id'])) {
148+
$sanitizedData = DataStructure::applyDefaults($sanitizedData);
149+
if (empty($sanitizedData['sip_secret'])) {
150+
$sanitizedData['sip_secret'] = Sip::generateSipPassword();
151+
}
152+
}
153+
142154
return $sanitizedData;
143155
}
144156

src/PBXCoreREST/Lib/Employees/SaveRecordAction.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ public static function main(array $data): PBXApiResult
100100
];
101101
$sanitizedData = self::sanitizeRoutingDestinations($sanitizedData, $routingFields, 20);
102102

103+
// Auto-generate SIP password on create when omitted (e.g. CSV bulk import without sip_secret column).
104+
// Mirrors DataStructure::createForNewEmployee() which fills the value for the manual-create form.
105+
// Must run before Phase 2 required-field validation.
106+
if (empty($sanitizedData['id']) && empty($sanitizedData['sip_secret'])) {
107+
$sanitizedData['sip_secret'] = Sip::generateSipPassword();
108+
}
109+
103110
} catch (\Exception $e) {
104111
$res->messages['error'][] = $e->getMessage();
105112
return $res;
@@ -120,13 +127,7 @@ public static function main(array $data): PBXApiResult
120127
],
121128
];
122129

123-
// sip_secret required only for CREATE (UPDATE can skip if keeping existing password)
124130
$isCreateOperation = empty($sanitizedData['id']);
125-
if ($isCreateOperation) {
126-
$validationRules['sip_secret'] = [
127-
['type' => 'required', 'message' => 'ex_ValidateSecretEmpty']
128-
];
129-
}
130131

131132
$validationErrors = self::validateRequiredFields($sanitizedData, $validationRules);
132133
if (!empty($validationErrors)) {

0 commit comments

Comments
 (0)