Skip to content

Commit 5e18beb

Browse files
authored
Merge pull request #3383 from LeoStoyanov/DIS-1628-self-reg-udfs-polaris
DIS-1628: Added Polaris User Defined Fields for Self Registration
2 parents eabd0f6 + 05f5fb0 commit 5e18beb

File tree

6 files changed

+281
-7
lines changed

6 files changed

+281
-7
lines changed

code/web/Drivers/Polaris.php

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2439,7 +2439,7 @@ function getPatronUpdateForm($user) {
24392439
return $interface->fetch('DataObjectUtil/objectEditForm.tpl');
24402440
}
24412441

2442-
function getSelfRegistrationFields($type = 'selfReg') {
2442+
function getSelfRegistrationFields(string $type = 'selfReg'): array {
24432443
global $library;
24442444
$location = new Location();
24452445

@@ -2889,6 +2889,42 @@ function getSelfRegistrationFields($type = 'selfReg') {
28892889
'autocomplete' => false,
28902890
];
28912891
$fields['logonInformationSection'] = $logonInfoSection;
2892+
2893+
require_once ROOT_DIR . '/sys/LibraryLocation/LibraryUserDefinedField.php';
2894+
$udfProperties = [];
2895+
$userDefinedField = new LibraryUserDefinedField();
2896+
$userDefinedField->libraryId = $library->libraryId;
2897+
$userDefinedField->orderBy('fieldNumber');
2898+
$userDefinedField->find();
2899+
while ($userDefinedField->fetch()) {
2900+
if (!empty($userDefinedField->label)) {
2901+
// Extract the number from fieldNumber (e.g., "User Defined Field 1" -> "1").
2902+
preg_match('/(\d+)$/', $userDefinedField->fieldNumber, $matches);
2903+
$fieldNum = $matches[1] ?? '';
2904+
if (!empty($fieldNum)) {
2905+
$udfProperties['udf' . $fieldNum] = [
2906+
'property' => 'udf' . $fieldNum,
2907+
'type' => 'text',
2908+
'label' => $userDefinedField->label,
2909+
'description' => '',
2910+
'maxLength' => $userDefinedField->maxLength,
2911+
'required' => !empty($userDefinedField->required),
2912+
'autocomplete' => false,
2913+
];
2914+
}
2915+
}
2916+
}
2917+
2918+
if (!empty($udfProperties)) {
2919+
$fields['userDefinedFieldsSection'] = [
2920+
'property' => 'userDefinedFieldsSection',
2921+
'type' => 'section',
2922+
'label' => 'Additional Information',
2923+
'hideInLists' => true,
2924+
'expandByDefault' => true,
2925+
'properties' => $udfProperties,
2926+
];
2927+
}
28922928
}
28932929

28942930
return $fields;
@@ -2916,7 +2952,14 @@ public function selfRegister(): array {
29162952

29172953
$encodedBody = json_encode($body);
29182954
$response = $this->getWebServiceResponse($polarisUrl, 'POST', '', $encodedBody);
2919-
ExternalRequestLogEntry::logRequest('polaris.selfRegister', 'POST', $this->getWebServiceURL() . $polarisUrl, $this->apiCurlWrapper->getHeaders(), $encodedBody, $this->lastResponseCode, $response, []);
2955+
$dataToSanitize = [];
2956+
for ($i = 1; $i <= 5; $i++) {
2957+
$bodyProperty = 'User' . $i;
2958+
if (isset($body->$bodyProperty)) {
2959+
$dataToSanitize[$bodyProperty] = $body->$bodyProperty;
2960+
}
2961+
}
2962+
ExternalRequestLogEntry::logRequest('polaris.selfRegister', 'POST', $this->getWebServiceURL() . $polarisUrl, $this->apiCurlWrapper->getHeaders(), $encodedBody, $this->lastResponseCode, $response, $dataToSanitize);
29202963
if ($response && $this->lastResponseCode == 200) {
29212964
$jsonResult = json_decode($response);
29222965
if ($jsonResult->PAPIErrorCode != 0) {
@@ -2941,6 +2984,7 @@ public function selfRegister(): array {
29412984
}
29422985

29432986
/**
2987+
* @param $type
29442988
* @param stdClass $body
29452989
* @param Library $library
29462990
*/
@@ -2981,11 +3025,16 @@ private function setupBodyForSelfRegAndPatronUpdateCall($type, stdClass $body, L
29813025
if (isset($_REQUEST['middleName'])) {
29823026
$body->NameMiddle = $_REQUEST['middleName'];
29833027
}
2984-
//$body->User1 = '';
2985-
//$body->User2 = '';
2986-
//$body->User3 = '';
2987-
//$body->User4 = '';
2988-
//$body->User5 = '';
3028+
3029+
// Handle User-Defined Fields (UDFs)
3030+
for ($i = 1; $i <= 5; $i++) {
3031+
$udfField = 'udf' . $i;
3032+
if (isset($_REQUEST[$udfField])) {
3033+
$bodyProperty = 'User' . $i;
3034+
$body->$bodyProperty = $_REQUEST[$udfField];
3035+
}
3036+
}
3037+
29893038
//$body->Gender = '';
29903039
if (isset($_REQUEST['birthDate'])) {
29913040
if ($type == 'selfReg' && $library && $library->promptForBirthDateInSelfReg) {

code/web/release_notes/25.12.00.MD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@
160160

161161
### Polaris Updates
162162
- Fixed an issue where self registration required a hardcoded minimum age of 13 years; instead, it will use the "Minimum Age" field in Library System settings. (DIS-1621) (*LS*)
163+
- Added Polaris User Defined Fields (UDFs) support for self-registration, allowing libraries to collect up to five custom fields from patrons. (DIS-1628) (*LS*)
164+
165+
<div markdown="1" class="settings">
166+
167+
#### New Settings
168+
- Primary Configuration > Library Systems > Self Registration accordion > User Defined Fields table
169+
170+
</div>
163171

164172
### Ratings Updates
165173
- Fixed an issue where the rating filled in would not persist after the user hovered over the stars. (DIS-1566) (*LS*)

code/web/sys/DBMaintenance/version_updates/25.12.00.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,22 @@ function getUpdates25_12_00(): array {
120120
//Yanjun Li - ByWater
121121

122122
// Leo Stoyanov - BWS
123+
'library_user_defined_fields_table' => [
124+
'title' => 'Library User Defined Fields Table',
125+
'description' => 'Create table for library user defined fields.',
126+
'sql' => [
127+
"CREATE TABLE IF NOT EXISTS library_user_defined_field (
128+
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
129+
libraryId INT NOT NULL,
130+
fieldNumber VARCHAR(30) NOT NULL,
131+
label VARCHAR(255) DEFAULT '',
132+
required TINYINT(1) DEFAULT 0,
133+
maxLength INT DEFAULT 255,
134+
INDEX (libraryId),
135+
UNIQUE KEY library_field (libraryId, fieldNumber)
136+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
137+
],
138+
], //library_user_defined_fields_table
123139
'populate_location_facet_labels' => [
124140
'title' => 'Populate Location Facet Labels',
125141
'description' => 'Copy legacy "location" Translation Map values into the Location table facet labels based on matching codes.',

code/web/sys/DataObjectUtil.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,75 @@ static function preprocessOneToManySubObjects(mixed $object, array &$structure)
901901
$propName = $property['property'];
902902
$propValue = $object->$propName;
903903

904+
// Handle prefilled rows.
905+
if (!empty($property['prefilledRows']) && is_array($property['prefilledRows'])) {
906+
$subObjectType = $property['subObjectType'];
907+
$keyOther = $property['keyOther'];
908+
$primaryKey = $object->__primaryKey;
909+
910+
if (!empty($object->$primaryKey)) {
911+
// Parent object exists; load existing rows and create missing ones in database.
912+
$existingSubObject = new $subObjectType();
913+
$existingSubObject->$keyOther = $object->$primaryKey;
914+
$existingRows = [];
915+
$existingSubObject->find();
916+
while ($existingSubObject->fetch()) {
917+
// Create a unique key based on all the prefilled field values.
918+
$rowKey = [];
919+
foreach ($property['prefilledRows'][0] as $fieldName => $value) {
920+
if (isset($existingSubObject->$fieldName)) {
921+
$rowKey[] = $fieldName . ':' . $existingSubObject->$fieldName;
922+
}
923+
}
924+
$existingRows[implode('|', $rowKey)] = clone $existingSubObject;
925+
}
926+
927+
foreach ($property['prefilledRows'] as $prefilledRow) {
928+
// Generate the same key for comparison.
929+
$rowKey = [];
930+
foreach ($prefilledRow as $fieldName => $value) {
931+
$rowKey[] = $fieldName . ':' . $value;
932+
}
933+
$rowKeyString = implode('|', $rowKey);
934+
935+
if (!isset($existingRows[$rowKeyString])) {
936+
$newSubObject = new $subObjectType();
937+
$newSubObject->$keyOther = $object->$primaryKey;
938+
foreach ($prefilledRow as $fieldName => $value) {
939+
$newSubObject->$fieldName = $value;
940+
}
941+
$newSubObject->insert();
942+
$existingRows[$rowKeyString] = $newSubObject;
943+
}
944+
}
945+
946+
// Reload the property to include all rows.
947+
$reloadedSubObject = new $subObjectType();
948+
$reloadedSubObject->$keyOther = $object->$primaryKey;
949+
$propValue = [];
950+
$reloadedSubObject->find();
951+
while ($reloadedSubObject->fetch()) {
952+
$propValue[$reloadedSubObject->getPrimaryKeyValue()] = clone $reloadedSubObject;
953+
}
954+
} else {
955+
// Parent object is new (not yet saved); create temporary placeholder objects.
956+
$propValue = [];
957+
$tempId = -1;
958+
foreach ($property['prefilledRows'] as $prefilledRow) {
959+
$tempSubObject = new $subObjectType();
960+
// Set a temporary negative ID for the form.
961+
$tempSubObject->id = $tempId;
962+
$tempSubObject->$keyOther = null;
963+
foreach ($prefilledRow as $fieldName => $value) {
964+
$tempSubObject->$fieldName = $value;
965+
}
966+
$propValue[$tempId] = $tempSubObject;
967+
$tempId--;
968+
}
969+
}
970+
$object->$propName = $propValue;
971+
}
972+
904973
if (!empty($propValue) && is_array($propValue)) {
905974
foreach ($propValue as $subObject) {
906975
if (method_exists($subObject, 'updateStructureForEditingObject')) {

code/web/sys/LibraryLocation/Library.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require_once ROOT_DIR . '/sys/LibraryLocation/LibraryFacetSetting.php';
55
require_once ROOT_DIR . '/sys/LibraryLocation/LibraryCombinedResultSection.php';
66
require_once ROOT_DIR . '/sys/LibraryLocation/LibraryTheme.php';
7+
require_once ROOT_DIR . '/sys/LibraryLocation/LibraryUserDefinedField.php';
78
if (file_exists(ROOT_DIR . '/sys/Indexing/LibraryRecordToInclude.php')) {
89
require_once ROOT_DIR . '/sys/Indexing/LibraryRecordToInclude.php';
910
}
@@ -525,6 +526,8 @@ class Library extends DataObject {
525526
private $_sideLoadScopes;
526527
/** @var ILLItemType[] */
527528
private $_interLibraryLoanItemTypes;
529+
/** @var LibraryUserDefinedField[] */
530+
private $_userDefinedFields;
528531
/** @var LibraryLink[] */
529532
private $_libraryLinks;
530533
/** @var LibraryRecordToInclude[] */
@@ -2664,6 +2667,34 @@ static function getObjectStructure(string $context = ''): array {
26642667
'description' => 'Whether or not to log self registrations (to approve in Review Library Registrations) (Sierra only)',
26652668
'default' => false,
26662669
],
2670+
'userDefinedFields' => [
2671+
'property' => 'userDefinedFields',
2672+
'type' => 'oneToMany',
2673+
'label' => 'User Defined Fields',
2674+
'description' => 'User defined fields for self registration.',
2675+
'keyThis' => 'libraryId',
2676+
'keyOther' => 'libraryId',
2677+
'subObjectType' => 'LibraryUserDefinedField',
2678+
'structure' => LibraryUserDefinedField::getObjectStructure(),
2679+
'sortable' => false,
2680+
'storeDb' => true,
2681+
'allowEdit' => false,
2682+
'canEdit' => false,
2683+
'canAddNew' => false,
2684+
'canDelete' => false,
2685+
'hideInLists' => true,
2686+
'relatedIls' => ['polaris'],
2687+
'prefilledRows' => [
2688+
['fieldNumber' => 'User Defined Field 1'],
2689+
['fieldNumber' => 'User Defined Field 2'],
2690+
['fieldNumber' => 'User Defined Field 3'],
2691+
['fieldNumber' => 'User Defined Field 4'],
2692+
['fieldNumber' => 'User Defined Field 5'],
2693+
],
2694+
'noteBullets' => [
2695+
'Not every row must be filled out. Leave the "Label" field empty to hide that User Defined Field.',
2696+
],
2697+
],
26672698
],
26682699
],
26692700
'thirdPartyRegistrationSection' => [
@@ -4931,6 +4962,8 @@ public function __get($name) {
49314962
return $this->getCloudLibraryScope();
49324963
} elseif ($name == 'interLibraryLoanItemTypes') {
49334964
return $this->getILLItemTypes();
4965+
} elseif ($name == 'userDefinedFields') {
4966+
return $this->getUserDefinedFields();
49344967
} else {
49354968
return parent::__get($name);
49364969
}
@@ -4963,6 +4996,8 @@ public function __set($name, $value) {
49634996
$this->_cloudLibraryScope = $value;
49644997
} elseif ($name == 'interLibraryLoanItemTypes') {
49654998
$this->_interLibraryLoanItemTypes = $value;
4999+
} elseif ($name == 'userDefinedFields') {
5000+
$this->_userDefinedFields = $value;
49665001
} else {
49675002
parent::__set($name, $value);
49685003
}
@@ -5014,6 +5049,7 @@ public function update(string $context = '') : int|bool {
50145049
$this->saveCloudLibraryScopes();
50155050
$this->saveThemes();
50165051
$this->saveILLItemTypes();
5052+
$this->saveUserDefinedFields();
50175053
$this->saveTextBlockTranslations('paymentHistoryExplanation');
50185054
$this->saveTextBlockTranslations('costSavingsExplanationEnabled');
50195055
$this->saveTextBlockTranslations('costSavingsExplanationDisabled');
@@ -5090,6 +5126,7 @@ public function insert(string $context = '') : int|bool {
50905126
$this->saveCloudLibraryScopes();
50915127
$this->saveThemes();
50925128
$this->saveILLItemTypes();
5129+
$this->saveUserDefinedFields();
50935130
$this->saveTextBlockTranslations('paymentHistoryExplanation');
50945131
$this->saveTextBlockTranslations('costSavingsExplanationEnabled');
50955132
$this->saveTextBlockTranslations('costSavingsExplanationDisabled');
@@ -5544,6 +5581,32 @@ public function saveILLItemTypes() : void {
55445581
}
55455582
}
55465583

5584+
/**
5585+
* @return LibraryUserDefinedField[]
5586+
*/
5587+
public function getUserDefinedFields() : array {
5588+
if (!isset($this->_userDefinedFields)) {
5589+
$this->_userDefinedFields = [];
5590+
if (!empty($this->libraryId)) {
5591+
$userDefinedField = new LibraryUserDefinedField();
5592+
$userDefinedField->libraryId = $this->libraryId;
5593+
$userDefinedField->orderBy('fieldNumber');
5594+
$userDefinedField->find();
5595+
while ($userDefinedField->fetch()) {
5596+
$this->_userDefinedFields[$userDefinedField->id] = clone($userDefinedField);
5597+
}
5598+
}
5599+
}
5600+
return $this->_userDefinedFields;
5601+
}
5602+
5603+
public function saveUserDefinedFields() : void {
5604+
if (isset($this->_userDefinedFields) && is_array($this->_userDefinedFields)) {
5605+
$this->saveOneToManyOptions($this->_userDefinedFields, 'libraryId');
5606+
unset($this->_userDefinedFields);
5607+
}
5608+
}
5609+
55475610
public function getNumLocationsForLibrary() {
55485611
$location = new Location;
55495612
$location->libraryId = $this->libraryId;

0 commit comments

Comments
 (0)