The libraries interlibrary loan form module
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

206 lines
6.5 KiB

<?php
/**
* @file
* Contains the SharePointRequestHandler class for submitting ILL requests
* to a SharePoint list via a Power Automate webhook.
*/
use Drupal\Core\Form\FormStateInterface;
/**
* Handles building and submitting ILL requests to SharePoint via Power Automate.
*
* Usage (from the form submit handler):
* @code
* \Drupal::moduleHandler()->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;
}
}