diff --git a/config/schema/upei_roblib_ill.schema.yml b/config/schema/upei_roblib_ill.schema.yml index ab0fc4c..a7251a8 100644 --- a/config/schema/upei_roblib_ill.schema.yml +++ b/config/schema/upei_roblib_ill.schema.yml @@ -14,18 +14,9 @@ upei_roblib_ill.settings: ill_contact_phone: type: string label: 'Contact Phone Number' - rapid_ill_username: + sharepoint_webhook_url: type: string - label: 'RapidILL Username' - rapid_ill_password: - type: string - label: 'RapidILL Password' - rapid_ill_code: - type: string - label: 'RapidILL Rapid Code' - rapid_ill_branch_name: - type: string - label: 'RapidILL Branch Name' + label: 'Power Automate Webhook URL' evergreen_api_url: type: string label: 'Evergreen API URL' diff --git a/includes/db.inc b/includes/db.inc index ae9bf69..ce783b4 100644 --- a/includes/db.inc +++ b/includes/db.inc @@ -5,25 +5,23 @@ */ -use RapidIll\InsertRequest; - /** - * Log an ILL request + * Log an ILL request. * - * @param \RapidIll\InsertRequest $request - * The request sent to RapidILL as an InsertRequest object. + * @param array $payload + * The request payload as an associative array (SharePoint webhook format). * @param array|string $response - * The response from RapidILL or an error message. + * The response from the ILL service or an error message. */ -function upei_roblib_ill_log_request(InsertRequest $request, $response) { - $soap_data = $request->toSoapArray(); +function upei_roblib_ill_log_request(array $payload, $response) { $time_submitted = time(); - $relais_message = is_array($response) ? (isset($response['ConfirmMessage']) ? $response['ConfirmMessage'] : ($response['Problem']['Message'] ?? '')) : (string) $response; + $relais_message = is_array($response) ? ($response['ConfirmMessage'] ?? ($response['Problem']['Message'] ?? '')) : (string) $response; + // Parse patron name into first/last. $patron_firstname = ''; $patron_lastname = ''; - if (!empty($soap_data['PatronName'])) { - $parts = explode(' ', $soap_data['PatronName'], 2); + if (!empty($payload['patronName'])) { + $parts = explode(' ', $payload['patronName'], 2); $patron_firstname = trim($parts[0] ?? ''); $patron_lastname = trim($parts[1] ?? ''); } @@ -32,25 +30,25 @@ function upei_roblib_ill_log_request(InsertRequest $request, $response) { try { $connection->insert('upei_roblib_ill_request') ->fields([ - 'patron_id' => $soap_data['PatronId'] ?? '', + 'patron_id' => $payload['patronBarcode'] ?? '', 'patron_firstname' => $patron_firstname, 'patron_lastname' => $patron_lastname, - 'patron_type' => '', - 'patron_department' => $soap_data['PatronDepartment'] ?? '', - 'patron_email' => $soap_data['PatronEmail'] ?? '', - 'notes' => $soap_data['PatronNotes'] ?? '', - 'genre' => $soap_data['RapidRequestType'] ?? '', - 'doi' => '', - 'author' => $soap_data['ArticleAuthor'] ?? '', - 'citation_date' => $soap_data['PatronJournalYear'] ?? '', - 'title' => $soap_data['PatronJournalTitle'] ?? '', - 'atitle' => $soap_data['ArticleTitle'] ?? '', - 'issn' => $soap_data['SuggestedIssns']['string'][0] ?? '', - 'isbn' => $soap_data['SuggestedIsbns']['string'][0] ?? '', - 'article_author' => $soap_data['ArticleAuthor'] ?? '', - 'volume' => $soap_data['JournalVol'] ?? '', - 'issue' => $soap_data['JournalIssue'] ?? '', - 'pages_requested' => $soap_data['ArticlePages'] ?? '', + 'patron_type' => $payload['patronType'] ?? '', + 'patron_department' => $payload['patronDepartment'] ?? '', + 'patron_email' => $payload['patronEmail'] ?? '', + 'notes' => $payload['notes'] ?? '', + 'genre' => $payload['format'] ?? '', + 'doi' => $payload['doi_isbn'] ?? '', + 'author' => $payload['author'] ?? '', + 'citation_date' => $payload['year'] ?? '', + 'title' => $payload['title'] ?? '', + 'atitle' => $payload['articleTitle'] ?? '', + 'issn' => $payload['issn'] ?? '', + 'isbn' => $payload['isbn'] ?? '', + 'article_author' => $payload['author'] ?? '', + 'volume' => $payload['volume'] ?? '', + 'issue' => $payload['issue'] ?? '', + 'pages_requested' => $payload['pagesRequested'] ?? '', 'time_submitted' => $time_submitted, 'relais_request_id' => is_array($response) && isset($response['RequestNumber']) ? $response['RequestNumber'] : '-1', 'relais_message' => substr($relais_message, 0, 254), diff --git a/includes/sharepoint.inc b/includes/sharepoint.inc new file mode 100644 index 0000000..c45a5be --- /dev/null +++ b/includes/sharepoint.inc @@ -0,0 +1,206 @@ +loadInclude('upei_roblib_ill', 'inc', 'includes/sharepoint'); + * $handler = new SharePointRequestHandler(); + * $payload = $handler->buildPayload($form_state); + * $response = $handler->submitRequest($payload); + * @endcode + */ +class SharePointRequestHandler +{ + + /** + * Drupal config object for upei_roblib_ill.settings. + * + * @var \Drupal\Core\Config\ImmutableConfig + */ + protected $config; + + /** + * Constructs a new SharePointRequestHandler. + */ + public function __construct() + { + $this->config = \Drupal::config('upei_roblib_ill.settings'); + } + + /** + * Builds the webhook payload from the Drupal form state. + * + * Reads bibliographic data from form storage (step 1) and patron data + * from the current form values (step 2). + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current form state containing both steps of the ILL form. + * + * @return array + * An associative array matching the Power Automate webhook payload schema. + */ + public function buildPayload(FormStateInterface $form_state): array + { + $storage = $form_state->getStorage(); + $biblio = $storage['request'] ?? []; + $patron = $form_state->getValues(); + + $genre = $biblio['Genre'] ?? 'article'; + + // Determine the author: use ArticleAuthor for articles/chapters, Author for books. + $author = ''; + if ($genre !== 'book' && !empty($biblio['ArticleAuthor'])) { + $author = $biblio['ArticleAuthor']; + } + elseif (!empty($biblio['Author'])) { + $author = $biblio['Author']; + } + + // Combine DOI and ISBN into a single field. + $doi_isbn_parts = []; + if (!empty($biblio['doi'])) { + $doi_isbn_parts[] = $biblio['doi']; + } + if (!empty($biblio['ISBN'])) { + $doi_isbn_parts[] = $biblio['ISBN']; + } + if (!empty($biblio['ISSN'])) { + $doi_isbn_parts[] = $biblio['ISSN']; + } + $doi_isbn = implode('; ', $doi_isbn_parts); + + // Build patron full name. + $first_name = $patron['FirstName'] ?? ''; + $surname = $patron['Surname'] ?? ''; + $patron_name = trim("$first_name $surname"); + + // Build notes — pack all extra info that doesn't have its own schema field. + $notes_parts = []; + if (!empty($genre)) { + $notes_parts[] = 'Format: ' . $genre; + } + if (!empty($biblio['ArticleTitle'])) { + $notes_parts[] = 'Article/Chapter: ' . $biblio['ArticleTitle']; + } + if (!empty($biblio['Date'])) { + $notes_parts[] = 'Year: ' . $biblio['Date']; + } + if (!empty($biblio['Volume'])) { + $notes_parts[] = 'Vol: ' . $biblio['Volume']; + } + if (!empty($biblio['Issue'])) { + $notes_parts[] = 'Issue: ' . $biblio['Issue']; + } + if (!empty($biblio['PagesRequested'])) { + $notes_parts[] = 'Pages: ' . $biblio['PagesRequested']; + } + if (!empty($patron['patron_type'])) { + $notes_parts[] = 'Patron Type: ' . $patron['patron_type']; + } + if (!empty($patron['campus_id'])) { + $notes_parts[] = 'Campus ID: ' . $patron['campus_id']; + } + if (!empty($patron['Department'])) { + $notes_parts[] = 'Dept: ' . $patron['Department']; + } + if (!empty($patron['notes'])) { + $notes_parts[] = $patron['notes']; + } + + // All values MUST be strings — Power Automate rejects the request + // if any value type does not match the trigger's JSON schema. + return [ + 'title' => (string)($biblio['Title'] ?? ''), + 'author' => (string)$author, + 'doi_isbn' => (string)$doi_isbn, + 'callNumber' => (string)($patron['campus_id'] ?? ''), + 'year' => '2023', + 'instName' => (string)$patron_name, + 'instEmail' => (string)($patron['DeliveryAddress'] ?? ''), + 'shippingAddr' => (string)($patron['Department'] ?? ''), + 'notes' => (string)implode(' | ', $notes_parts), + 'submittedAt' => (string)gmdate('Y-m-d\TH:i:s') . '.000Z', + ]; + } + + /** + * Submits the payload to the configured Power Automate webhook. + * + * @param array $payload + * The payload array as built by buildPayload(). + * + * @return array + * An associative array with at least a 'ConfirmMessage' key, and + * optionally a 'RequestNumber' key on success. + */ + public function submitRequest(array $payload): array + { + \Drupal::moduleHandler()->loadInclude('upei_roblib_ill', 'inc', 'includes/db'); + + $webhook_url = $this->config->get('sharepoint_webhook_url'); + if (empty($webhook_url)) { + \Drupal::logger('upei_roblib_ill')->error( + 'SharePoint webhook URL is not configured. Please set it in the ILL settings.' + ); + $error_response = [ + 'ConfirmMessage' => 'SharePoint integration is not configured. Please contact the library.', + ]; + upei_roblib_ill_log_request($payload, $error_response); + return $error_response; + } + + // Log the outgoing payload for debugging. + \Drupal::logger('upei_roblib_ill')->notice( + 'Submitting ILL request to SharePoint webhook. Payload: @payload', + ['@payload' => json_encode($payload)] + ); + + try { + // Use 'json' option which handles JSON encoding and Content-Type automatically. + $response = \Drupal::httpClient()->post($webhook_url, [ + 'json' => $payload, + 'timeout' => 30, + ]); + + $status_code = $response->getStatusCode(); + $body = (string)$response->getBody(); + $response_data = json_decode($body, TRUE) ?: []; + + if ($status_code >= 200 && $status_code < 300) { + $result = [ + 'RequestNumber' => $response_data['requestId'] ?? date('YmdHis'), + 'ConfirmMessage' => 'Your Interlibrary Loan request has been successfully submitted.', + ]; + } + else { + $message = $response_data['error'] ?? 'Unexpected response from SharePoint (HTTP ' . $status_code . ').'; + $result = [ + 'ConfirmMessage' => 'SharePoint could not process your request: ' . $message, + ]; + } + } + catch (\Exception $e) { + \Drupal::logger('upei_roblib_ill')->error( + 'SharePoint webhook request failed: @message', + ['@message' => $e->getMessage()] + ); + $result = [ + 'ConfirmMessage' => 'Error communicating with SharePoint: ' . $e->getMessage(), + ]; + } + + upei_roblib_ill_log_request($payload, $result); + return $result; + } + +} \ No newline at end of file diff --git a/src/Form/RoblibIllLoanForm.php b/src/Form/RoblibIllLoanForm.php index 81a493a..f499047 100644 --- a/src/Form/RoblibIllLoanForm.php +++ b/src/Form/RoblibIllLoanForm.php @@ -50,18 +50,31 @@ class RoblibIllLoanForm extends FormBase $campus_id = $this->ill_form_message_id($values['campus_id']); - \Drupal::moduleHandler()->loadInclude('upei_roblib_ill', 'inc', 'includes/rapidill'); - $handler = new \RapidIllRequestHandler(); - $insert_request = $handler->buildInsertRequest($form_state); - $form_state->set('insert_request', $insert_request); + // Build the SharePoint payload for logging and later submission. + \Drupal::moduleHandler()->loadInclude('upei_roblib_ill', 'inc', 'includes/sharepoint'); + $handler = new \SharePointRequestHandler(); + $payload = $handler->buildPayload($form_state); + // Add extra fields for DB logging that aren't in the webhook payload. + $payload['patronType'] = $values['patron_type'] ?? ''; + $payload['patronDepartment'] = $values['Department'] ?? ''; + // Store individual biblio fields for DB logging. + $storage = $form_state->getStorage(); + $biblio = $storage['request'] ?? []; + $payload['articleTitle'] = $biblio['ArticleTitle'] ?? ''; + $payload['issn'] = $biblio['ISSN'] ?? ''; + $payload['isbn'] = $biblio['ISBN'] ?? ''; + $payload['volume'] = $biblio['Volume'] ?? ''; + $payload['issue'] = $biblio['Issue'] ?? ''; + $payload['pagesRequested'] = $biblio['PagesRequested'] ?? ''; + $form_state->set('sharepoint_payload', $payload); $auth = upei_roblib_ill_authenticate($campus_id, $values['Surname']); if (!$auth) { $err = 'UserID or Surname do not match or not found.'; $form_state->setErrorByName('Surname', $err); - // Log invalid requests, we need to build the full array here to log it, even though we won't send it yet as auth has failed. + // Log invalid requests. \Drupal::moduleHandler()->loadInclude('upei_roblib_ill', 'inc', 'includes/db'); - upei_roblib_ill_log_request($insert_request, $err); + upei_roblib_ill_log_request($payload, $err); } $trigger = $form_state->getTriggeringElement(); @@ -121,14 +134,14 @@ class RoblibIllLoanForm extends FormBase $form_state->setRebuild(TRUE); return; } - //process the form - \Drupal::moduleHandler()->loadInclude('upei_roblib_ill', 'inc', 'includes/rapidill'); - $handler = new \RapidIllRequestHandler(); - $insert_request = $form_state->get('insert_request'); - if (!$insert_request) { - $insert_request = $handler->buildInsertRequest($form_state); + // Submit the request to SharePoint via the Power Automate webhook. + \Drupal::moduleHandler()->loadInclude('upei_roblib_ill', 'inc', 'includes/sharepoint'); + $handler = new \SharePointRequestHandler(); + $payload = $form_state->get('sharepoint_payload'); + if (!$payload) { + $payload = $handler->buildPayload($form_state); } - $response = $handler->submitRequest($insert_request); + $response = $handler->submitRequest($payload); $error = isset($response['RequestNumber']) ? 'FALSE' : 'TRUE'; $parameters = [ 'message' => $response['ConfirmMessage'], @@ -362,8 +375,6 @@ class RoblibIllLoanForm extends FormBase /** * The patron portion of the ILL form. * - * The campus_id and email portion are also used in a request for an Relais aid. - * * @param array $form * An array representing a Drupal form. * @param array $form_state diff --git a/src/Form/RoblibIllSettingsForm.php b/src/Form/RoblibIllSettingsForm.php index a3d8524..4f95920 100644 --- a/src/Form/RoblibIllSettingsForm.php +++ b/src/Form/RoblibIllSettingsForm.php @@ -35,7 +35,7 @@ class RoblibIllSettingsForm extends FormBase '#type' => 'textfield', '#size' => 200, '#title' => t('Your ILL Library Symbol'), - '#description' => t('Your Relais Library Symbol'), + '#description' => t('Your Library Symbol'), '#default_value' => $config->get('ill_library_symbol'), ]; @@ -93,38 +93,20 @@ class RoblibIllSettingsForm extends FormBase '#size' => 200, ]; - // RapidILL settings. - $form['rapid_ill_fieldset'] = [ + // SharePoint Settings. + $form['sharepoint_fieldset'] = [ '#type' => 'fieldset', - '#title' => t('RapidILL Settings'), - '#description' => t('Credentials and configuration for submitting requests via RapidILL.'), + '#title' => t('SharePoint Settings'), + '#description' => t('Configuration for submitting ILL requests to SharePoint via Power Automate.'), ]; - $form['rapid_ill_fieldset']['rapid_ill_username'] = [ + $form['sharepoint_fieldset']['sharepoint_webhook_url'] = [ '#type' => 'textfield', - '#title' => t('RapidILL Username'), - '#description' => t('Your RapidILL API username.'), - '#default_value' => $config->get('rapid_ill_username'), - '#size' => 200, - ]; - $form['rapid_ill_fieldset']['rapid_ill_password'] = [ - '#type' => 'password', - '#title' => t('RapidILL Password'), - '#description' => t('Your RapidILL API password. Leave blank to keep the existing value.'), - '#size' => 200, - ]; - $form['rapid_ill_fieldset']['rapid_ill_code'] = [ - '#type' => 'textfield', - '#title' => t('RapidILL Rapid Code'), - '#description' => t('Your library Rapid code (e.g. YOUR_RAPID_CODE).'), - '#default_value' => $config->get('rapid_ill_code'), - '#size' => 200, - ]; - $form['rapid_ill_fieldset']['rapid_ill_branch_name'] = [ - '#type' => 'textfield', - '#title' => t('RapidILL Branch Name'), - '#description' => t('Your library branch name (e.g. Main).'), - '#default_value' => $config->get('rapid_ill_branch_name'), + '#title' => t('Power Automate Webhook URL'), + '#description' => t('The full URL of the Power Automate HTTP trigger that writes ILL requests to a SharePoint list.'), + '#default_value' => $config->get('sharepoint_webhook_url'), '#size' => 200, + '#required' => TRUE, + '#maxlength' => 1024, ]; $form['actions']['submit'] = [ @@ -163,15 +145,8 @@ class RoblibIllSettingsForm extends FormBase $config->set('evergreen_password', $eg_password)->save(); } - // RapidILL settings. - $config->set('rapid_ill_username', $form_state->getValue('rapid_ill_username'))->save(); - // Only update the password if a new value was entered. - $password = $form_state->getValue('rapid_ill_password'); - if (!empty($password)) { - $config->set('rapid_ill_password', $password)->save(); - } - $config->set('rapid_ill_code', $form_state->getValue('rapid_ill_code'))->save(); - $config->set('rapid_ill_branch_name', $form_state->getValue('rapid_ill_branch_name'))->save(); + // SharePoint settings. + $config->set('sharepoint_webhook_url', $form_state->getValue('sharepoint_webhook_url'))->save(); } } \ No newline at end of file diff --git a/upei_roblib_ill.install b/upei_roblib_ill.install index 8ec547a..fda2615 100644 --- a/upei_roblib_ill.install +++ b/upei_roblib_ill.install @@ -27,6 +27,19 @@ function upei_roblib_ill_update_9301() { ->save(); } +/** + * Remove obsolete RapidILL configuration keys. + */ +function upei_roblib_ill_update_9302() { + \Drupal::configFactory()->getEditable('upei_roblib_ill.settings') + ->clear('rapid_ill_username') + ->clear('rapid_ill_password') + ->clear('rapid_ill_code') + ->clear('rapid_ill_branch_name') + ->save(); +} + + /** * Implements hook_schema(). */