Skip to content

Commit c400202

Browse files
authored
Merge pull request #6512 from christianbeeznest/contidos-22802
User: Enforce unique extra‑field per URL on user creation - refs BT#22802
2 parents 52e70db + 75ef745 commit c400202

File tree

4 files changed

+217
-94
lines changed

4 files changed

+217
-94
lines changed

main/admin/user_import.php

Lines changed: 145 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -255,14 +255,13 @@ function complete_missing_data(array $user): array
255255
/**
256256
* Save the imported data.
257257
*
258-
* @uses \global variable $inserted_in_course, which returns the list of
259-
* courses the user was inserted in
258+
* @uses global $inserted_in_course, which returns the list of courses the user was inserted in
259+
* @param array $users
260+
* @param bool $sendMail
261+
* @param string|null $targetFolder
262+
* @return array The $users array, with 'message' and (for reused users) 'id' set
260263
*/
261-
function save_data(
262-
array $users,
263-
bool $sendMail = false,
264-
?string $targetFolder = null
265-
): array {
264+
function save_data(array $users, bool $sendMail = false, ?string $targetFolder = null): array {
266265
global $inserted_in_course, $extra_fields;
267266

268267
// Not all scripts declare the $inserted_in_course array (although they should).
@@ -281,107 +280,150 @@ function save_data(
281280

282281
$optionsByField = [];
283282

283+
// which extra‑field variable are we validating on?
284+
$uniqueField = api_get_configuration_value('extra_field_to_validate_on_user_registration');
285+
284286
foreach ($users as &$user) {
285287
if ($user['has_error']) {
286288
$userError[] = $user;
287289
continue;
288290
}
289291

290-
$user = complete_missing_data($user);
291-
$user['Status'] = api_status_key($user['Status']);
292-
$redirection = $user['Redirection'] ?? '';
292+
$returnMessage = '';
293+
$user_id = null;
294+
295+
// 1) If the CSV row has that unique extra‑field, try to look up an existing user
296+
if (!empty($uniqueField) && !empty($user[$uniqueField])) {
297+
// pass true to getUserId mode
298+
$existing = UserManager::isExtraFieldValueUniquePerUrl($user[$uniqueField], true);
299+
if ($existing !== null) {
300+
// existing user found → reuse
301+
$user_id = $existing;
302+
$returnMessage = Display::return_message(
303+
sprintf(
304+
'An existing user with the same %s was found (ID %d), enrolling instead of creating.',
305+
$uniqueField,
306+
$existing
307+
),
308+
'info'
309+
);
310+
}
311+
}
293312

294-
$user_id = UserManager::create_user(
295-
$user['FirstName'],
296-
$user['LastName'],
297-
$user['Status'],
298-
$user['Email'],
299-
$user['UserName'],
300-
$user['Password'],
301-
$user['OfficialCode'],
302-
$user['language'],
303-
$user['PhoneNumber'],
304-
'',
305-
$user['AuthSource'],
306-
$user['ExpiryDate'],
307-
1,
308-
0,
309-
null,
310-
null,
311-
$sendMail,
312-
false,
313-
'',
314-
false,
315-
null,
316-
null,
317-
null,
318-
$redirection
319-
);
313+
// 2) If not found, go through normal creation
314+
if ($user_id === null) {
315+
// fill in missing fields, generate password, etc.
316+
$user = complete_missing_data($user);
317+
$user['Status'] = api_status_key($user['Status']);
318+
$redirection = $user['Redirection'] ?? '';
319+
320+
$user_id = UserManager::create_user(
321+
$user['FirstName'],
322+
$user['LastName'],
323+
$user['Status'],
324+
$user['Email'],
325+
$user['UserName'],
326+
$user['Password'],
327+
$user['OfficialCode'],
328+
$user['language'],
329+
$user['PhoneNumber'],
330+
'',
331+
$user['AuthSource'],
332+
$user['ExpiryDate'],
333+
1,
334+
0,
335+
null,
336+
null,
337+
$sendMail,
338+
false,
339+
'',
340+
false,
341+
null,
342+
null,
343+
null,
344+
$redirection
345+
);
320346

321-
if ($user_id) {
322-
$returnMessage = Display::return_message(get_lang('UserAdded'), 'success');
323-
324-
if (isset($user['Courses']) && is_array($user['Courses'])) {
325-
foreach ($user['Courses'] as $course) {
326-
if (CourseManager::course_exists($course)) {
327-
$result = CourseManager::subscribeUser($user_id, $course, $user['Status']);
328-
if ($result) {
329-
$course_info = api_get_course_info($course);
330-
$inserted_in_course[$course] = $course_info['title'];
331-
}
332-
}
333-
}
347+
if ($user_id) {
348+
$returnMessage = Display::return_message(get_lang('UserAdded'), 'success');
349+
} else {
350+
$returnMessage = Display::return_message(get_lang('Error'), 'error');
351+
$userWarning[] = $user;
352+
$user['message'] = $returnMessage;
353+
continue;
334354
}
355+
}
335356

336-
if (isset($user['Sessions']) && is_array($user['Sessions'])) {
337-
foreach ($user['Sessions'] as $sessionId) {
338-
$sessionInfo = api_get_session_info($sessionId);
339-
if (!empty($sessionInfo)) {
340-
SessionManager::subscribeUsersToSession(
341-
$sessionId,
342-
[$user_id],
343-
SESSION_VISIBLE_READ_ONLY,
344-
false
345-
);
357+
// 3) At this point $user_id is either reused or newly created.
358+
// Enroll in courses:
359+
if (isset($user['Courses']) && is_array($user['Courses'])) {
360+
foreach ($user['Courses'] as $course) {
361+
if (CourseManager::course_exists($course)) {
362+
$result = CourseManager::subscribeUser($user_id, $course, $user['Status']);
363+
if ($result) {
364+
$info = api_get_course_info($course);
365+
$inserted_in_course[$course] = $info['title'];
346366
}
347367
}
348368
}
369+
}
349370

350-
if (!empty($user['ClassId'])) {
351-
$classId = explode('|', trim($user['ClassId']));
352-
foreach ($classId as $id) {
353-
$usergroup->subscribe_users_to_usergroup($id, [$user_id], false);
371+
// 4) Enroll in sessions:
372+
if (isset($user['Sessions']) && is_array($user['Sessions'])) {
373+
foreach ($user['Sessions'] as $sessionId) {
374+
$sessionInfo = api_get_session_info($sessionId);
375+
if (!empty($sessionInfo)) {
376+
SessionManager::subscribeUsersToSession(
377+
$sessionId,
378+
[$user_id],
379+
SESSION_VISIBLE_READ_ONLY,
380+
false
381+
);
354382
}
355383
}
384+
}
356385

357-
// We are sure that the extra field exists.
358-
foreach ($extra_fields as $extras) {
359-
if (!isset($user[$extras[1]])) {
360-
continue;
361-
}
362-
363-
$key = $extras[1];
364-
$value = $user[$key];
386+
// 5) Subscribe to usergroups:
387+
if (!empty($user['ClassId'])) {
388+
$classIds = explode('|', trim($user['ClassId']));
389+
foreach ($classIds as $id) {
390+
$usergroup->subscribe_users_to_usergroup($id, [$user_id], false);
391+
}
392+
}
365393

366-
if (!array_key_exists($key, $optionsByField)) {
367-
$optionsByField[$key] = $efo->getOptionsByFieldVariable($key);
394+
// 6) Update extra‑field values (for newly created or even reused users):
395+
foreach ($extra_fields as $extras) {
396+
$fieldVar = $extras[1];
397+
$matchedKey = null;
398+
foreach ($user as $colName => $colVal) {
399+
if (strtolower($colName) === strtolower($fieldVar)) {
400+
$matchedKey = $colName;
401+
break;
368402
}
403+
}
404+
if ($matchedKey === null) {
405+
continue;
406+
}
369407

370-
/** @var ExtraFieldOptions $option */
371-
foreach ($optionsByField[$key] as $option) {
372-
if ($option->getDisplayText() === $value) {
373-
$value = $option->getValue();
374-
}
408+
$value = $user[$matchedKey];
409+
if (!array_key_exists($matchedKey, $optionsByField)) {
410+
$optionsByField[$matchedKey] = $efo->getOptionsByFieldVariable($matchedKey);
411+
}
412+
/** @var ExtraFieldOptions $option */
413+
foreach ($optionsByField[$matchedKey] as $option) {
414+
if ($option->getDisplayText() === $value) {
415+
$value = $option->getValue();
416+
break;
375417
}
376-
377-
UserManager::update_extra_field_value($user_id, $key, $value);
378418
}
379-
$userSaved[] = $user;
380-
} else {
381-
$returnMessage = Display::return_message(get_lang('Error'), 'warning');
382-
$userWarning[] = $user;
419+
420+
UserManager::update_extra_field_value($user_id, $matchedKey, $value);
383421
}
422+
423+
// 7) Record success
424+
$user['id'] = $user_id;
384425
$user['message'] = $returnMessage;
426+
$userSaved[] = $user;
385427
}
386428

387429
// Save with success, error and warning users
@@ -708,6 +750,25 @@ function processUsers(array &$users, bool $sendMail, ?string $targetFolder = nul
708750

709751
Session::erase('user_import_data_'.$userId);
710752
$users = Import::csvToArray($_FILES['import_file']['tmp_name']);
753+
754+
$uniqueField = api_get_configuration_value('extra_field_to_validate_on_user_registration');
755+
if (!empty($uniqueField) && !empty($users)) {
756+
$firstRow = reset($users);
757+
$csvHeader = array_keys($firstRow);
758+
$csvHeaderLower = array_map('trim', array_map('strtolower', $csvHeader));
759+
760+
if (!in_array($uniqueField, $csvHeaderLower, true)) {
761+
Display::addFlash(
762+
Display::return_message(
763+
sprintf('The column "%s" is required in the CSV for this platform', $uniqueField),
764+
'error'
765+
)
766+
);
767+
header('Location: ' . api_get_self());
768+
exit;
769+
}
770+
}
771+
711772
$users = parse_csv_data(
712773
$users,
713774
$cleanFileName,

main/inc/lib/extra_field.lib.php

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -788,8 +788,8 @@ public function addElements(
788788
if (!empty($showOnlyTheseFields)) {
789789
$setData = [];
790790
foreach ($showOnlyTheseFields as $variable) {
791-
$extraName = 'extra_'.$variable;
792-
if (in_array($extraName, array_keys($extraData))) {
791+
$extraName = 'extra_' . $variable;
792+
if (array_key_exists($extraName, $extraData)) {
793793
$setData[$extraName] = $extraData[$extraName];
794794
}
795795
}
@@ -824,13 +824,26 @@ public function addElements(
824824
$help
825825
);
826826

827-
if (!empty($requiredFields)) {
828-
/** @var HTML_QuickForm_input $element */
829-
foreach ($form->getElements() as $element) {
830-
$name = str_replace('extra_', '', $element->getName());
831-
if (in_array($name, $requiredFields)) {
832-
$form->setRequired($element);
833-
}
827+
$requiredFields = is_array($requiredFields) ? $requiredFields : [];
828+
829+
$uniqueField = api_get_configuration_value('extra_field_to_validate_on_user_registration');
830+
if (!empty($uniqueField) && !in_array($uniqueField, $requiredFields, true)) {
831+
$requiredFields[] = $uniqueField;
832+
}
833+
834+
/** @var HTML_QuickForm_element $element */
835+
foreach ($form->getElements() as $element) {
836+
$name = str_replace('extra_', '', $element->getName());
837+
if (in_array($name, $requiredFields, true)) {
838+
$form->setRequired($element);
839+
}
840+
if (!empty($uniqueField) && $name === $uniqueField) {
841+
$form->addRule(
842+
'extra_' . $name,
843+
sprintf(get_lang('A user with the same %s already exists in this portal'), $name),
844+
'callback',
845+
['UserManager', 'isExtraFieldValueUniquePerUrl']
846+
);
834847
}
835848
}
836849

main/inc/lib/usermanager.lib.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8405,4 +8405,51 @@ private static function getGravatar(
84058405

84068406
return $url;
84078407
}
8408+
8409+
/**
8410+
* Check or fetch a user by extra‑field on this portal.
8411+
*
8412+
* @param string $value The extra‑field value to test (e.g. DNI).
8413+
* @param bool $returnId If true, return the existing user ID or null; otherwise return true/false for uniqueness.
8414+
* @return bool|int|null When $returnId===false: true if unique, false if already exists.
8415+
* When $returnId===true: existing user ID or null if none.
8416+
*/
8417+
public static function isExtraFieldValueUniquePerUrl(string $value, bool $returnId = false)
8418+
{
8419+
$field = api_get_configuration_value('extra_field_to_validate_on_user_registration');
8420+
if (empty($field) || $value === '') {
8421+
// If there's nothing to check, treat as “unique” or “no ID”
8422+
return $returnId ? null : true;
8423+
}
8424+
8425+
$accessUrlId = api_get_current_access_url_id();
8426+
8427+
$tUser = Database::get_main_table(TABLE_MAIN_USER);
8428+
$tField = Database::get_main_table(TABLE_EXTRA_FIELD);
8429+
$tValue = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
8430+
$tRelUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
8431+
8432+
$sql = "
8433+
SELECT u.id
8434+
FROM {$tUser} u
8435+
JOIN {$tValue} v ON v.item_id = u.id
8436+
JOIN {$tField} f ON f.id = v.field_id
8437+
JOIN {$tRelUrl} url ON url.user_id = u.id
8438+
WHERE f.variable = '" . Database::escape_string($field) . "'
8439+
AND v.value = '" . Database::escape_string($value) . "'
8440+
AND url.access_url_id = {$accessUrlId}
8441+
LIMIT 1
8442+
";
8443+
8444+
$result = Database::query($sql);
8445+
$row = Database::fetch_array($result, 'ASSOC');
8446+
8447+
if ($returnId) {
8448+
// return the existing user ID, or null if none
8449+
return $row['id'] ?? null;
8450+
}
8451+
8452+
// return true if no match was found (i.e. unique), false otherwise
8453+
return empty($row);
8454+
}
84088455
}

main/install/configuration.dist.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1948,7 +1948,7 @@
19481948

19491949
// Default items per page in main/mySpace/users.php
19501950
// $_configuration['my_space_users_items_per_page'] = 10;
1951-
1951+
19521952
//Add an expected theorical time spent in a course to show in main/mySpace/myStudents.php and main/session/resume_session.php
19531953
//Create an extra field for courses with identifier "theoretical_time"
19541954
//$_configuration['display_theoretical_time'] = false;
@@ -2700,3 +2700,5 @@
27002700
],
27012701
],
27022702
]; */
2703+
// Extra field variable name to validate as unique per URL during user registration (e.g. 'dni')
2704+
//$_configuration['extra_field_to_validate_on_user_registration'] = ''; // set in admin or directly (e.g. 'dni')

0 commit comments

Comments
 (0)