From 52e7d24f7be2d5fe627544f6f184a81a8845825c Mon Sep 17 00:00:00 2001
From: Ian Hardy <ian.c.hardy@gmail.com>
Date: Thu, 25 Jun 2020 09:58:24 -0400
Subject: [PATCH] FOLIO paged results (#1636)

---
 module/VuFind/src/VuFind/ILS/Driver/Folio.php | 165 +++++++++---------
 1 file changed, 86 insertions(+), 79 deletions(-)

diff --git a/module/VuFind/src/VuFind/ILS/Driver/Folio.php b/module/VuFind/src/VuFind/ILS/Driver/Folio.php
index 8c5f9113832..b37fd6f002c 100644
--- a/module/VuFind/src/VuFind/ILS/Driver/Folio.php
+++ b/module/VuFind/src/VuFind/ILS/Driver/Folio.php
@@ -559,6 +559,48 @@ class Folio extends AbstractAPI implements
         return count($json->users) === 1 ? $json->users[0] : null;
     }
 
+    /**
+     * Helper function to retrieve paged results from FOLIO API
+     *
+     * @param string $responseKey Key containing values to collect in response
+     * @param string $interface   FOLIO api interface to call
+     * @param array  $query       CQL query
+     *
+     * @return array
+     */
+    protected function getPagedResults($responseKey, $interface, $query = [])
+    {
+        $count = 0;
+        $limit = 1000;
+        $offset = 0;
+
+        do {
+            $combinedQuery = array_merge($query, compact('offset', 'limit'));
+            $response = $this->makeRequest(
+                'GET',
+                $interface,
+                $combinedQuery
+            );
+            $json = json_decode($response->getBody());
+            if (!$response->isSuccess() || !$json) {
+                $msg = $json->errors[0]->message ?? json_last_error_msg();
+                throw new ILSException($msg);
+            }
+            $total = $json->totalRecords ?? 0;
+            $previousCount = $count;
+            foreach ($json->$responseKey ?? [] as $item) {
+                $count++;
+                if ($count % $limit == 0) {
+                    $offset += $limit;
+                }
+                yield $item ?? '';
+            }
+            // Continue until the count reaches the total records
+            // found, if count does not increase, something has gone
+            // wrong. Stop so we don't loop forever.
+        } while ($count < $total && $previousCount != $count);
+    }
+
     /**
      * Patron Login
      *
@@ -697,13 +739,10 @@ class Folio extends AbstractAPI implements
     public function getMyTransactions($patron)
     {
         $query = ['query' => 'userId==' . $patron['id'] . ' and status.name==Open'];
-        $response = $this->makeRequest("GET", '/circulation/loans', $query);
-        $json = json_decode($response->getBody());
-        if (count($json->loans) == 0) {
-            return [];
-        }
         $transactions = [];
-        foreach ($json->loans as $trans) {
+        foreach ($this->getPagedResults(
+            'loans', '/circulation/loans', $query
+        ) as $trans) {
             $date = date_create($trans->dueDate);
             $transactions[] = [
                 'duedate' => date_format($date, "j M Y"),
@@ -797,10 +836,10 @@ class Folio extends AbstractAPI implements
     public function getPickupLocations($patron)
     {
         $query = ['query' => 'pickupLocation=true'];
-        $response = $this->makeRequest('GET', '/service-points', $query);
-        $json = json_decode($response->getBody());
         $locations = [];
-        foreach ($json->servicepoints as $servicepoint) {
+        foreach ($this->getPagedResults(
+            'servicepoints', '/service-points', $query
+        ) as $servicepoint) {
             $locations[] = [
                 'locationID' => $servicepoint->id,
                 'locationDisplay' => $servicepoint->discoveryDisplayName
@@ -855,10 +894,10 @@ class Folio extends AbstractAPI implements
             'query' => '(requesterId == "' . $patron['id'] . '"  ' .
             'and status == Open*)'
         ];
-        $response = $this->makeRequest('GET', '/request-storage/requests', $query);
-        $json = json_decode($response->getBody());
         $holds = [];
-        foreach ($json->requests as $hold) {
+        foreach ($this->getPagedResults(
+            'requests', '/request-storage/requests', $query
+        ) as $hold) {
             $requestDate = date_create($hold->requestDate);
             // Set expire date if it was included in the response
             $expireDate = isset($hold->requestExpirationDate)
@@ -1015,28 +1054,14 @@ class Folio extends AbstractAPI implements
         $valueKey = 'name'
     ) {
         $retVal = [];
-        $limit = 1000; // how many records to retrieve at once
-        $offset = 0;
 
         // Results can be paginated, so let's loop until we've gotten everything:
-        do {
-            $response = $this->makeRequest(
-                'GET',
-                '/coursereserves/' . $type,
-                compact('offset', 'limit')
-            );
-            $json = json_decode($response->getBody());
-            $total = $json->totalRecords ?? 0;
-            $preCount = count($retVal);
-            foreach ($json->{$responseKey ?? $type} ?? [] as $item) {
-                $retVal[$item->id] = $item->$valueKey ?? '';
-            }
-            $postCount = count($retVal);
-            $offset += $limit;
-            // Loop has a safety valve: if the count of records doesn't change
-            // in a full iteration, something has gone wrong, and we should stop
-            // so we don't loop forever!
-        } while ($total && $postCount < $total && $preCount != $postCount);
+        foreach ($this->getPagedResults(
+            $responseKey ?? $type, '/coursereserves/' . $type
+        ) as $item) {
+            $retVal[$item->id] = $item->$valueKey ?? '';
+        }
+
         return $retVal;
     }
 
@@ -1139,51 +1164,36 @@ class Folio extends AbstractAPI implements
     public function findReserves($course, $inst, $dept)
     {
         $retVal = [];
-        $limit = 1000; // how many records to retrieve at once
-        $offset = 0;
 
         // Results can be paginated, so let's loop until we've gotten everything:
-        do {
-            $response = $this->makeRequest(
-                'GET',
-                '/coursereserves/reserves',
-                compact('offset', 'limit')
-            );
-            $json = json_decode($response->getBody());
-            $total = $json->totalRecords ?? 0;
-            $preCount = count($retVal);
-            foreach ($json->reserves ?? [] as $item) {
-                try {
-                    $bibId = $this->getBibId(null, null, $item->itemId);
-                } catch (\Exception $e) {
-                    $bibId = null;
-                }
-                if ($bibId !== null) {
-                    $courseData = $this->getCourseDetails(
-                        $item->courseListingId ?? null
-                    );
-                    $instructorIds = $this->getInstructorIds(
-                        $item->courseListingId ?? null
-                    );
-                    foreach ($courseData as $courseId => $departmentId) {
-                        foreach ($instructorIds as $instructorId) {
-                            $retVal[] = [
-                                'BIB_ID' => $bibId,
-                                'COURSE_ID' => $courseId == '' ? null : $courseId,
-                                'DEPARTMENT_ID' => $departmentId == ''
-                                    ? null : $departmentId,
-                                'INSTRUCTOR_ID' => $instructorId,
-                            ];
-                        }
+        foreach ($this->getPagedResults(
+            'reserves', '/coursereserves/reserves'
+        ) as $item) {
+            try {
+                $bibId = $this->getBibId(null, null, $item->itemId);
+            } catch (\Exception $e) {
+                $bibId = null;
+            }
+            if ($bibId !== null) {
+                $courseData = $this->getCourseDetails(
+                    $item->courseListingId ?? null
+                );
+                $instructorIds = $this->getInstructorIds(
+                    $item->courseListingId ?? null
+                );
+                foreach ($courseData as $courseId => $departmentId) {
+                    foreach ($instructorIds as $instructorId) {
+                        $retVal[] = [
+                            'BIB_ID' => $bibId,
+                            'COURSE_ID' => $courseId == '' ? null : $courseId,
+                            'DEPARTMENT_ID' => $departmentId == ''
+                                ? null : $departmentId,
+                            'INSTRUCTOR_ID' => $instructorId,
+                        ];
                     }
                 }
             }
-            $postCount = count($retVal);
-            $offset += $limit;
-            // Loop has a safety valve: if the count of records doesn't change
-            // in a full iteration, something has gone wrong, and we should stop
-            // so we don't loop forever!
-        } while ($total && $postCount < $total && $preCount != $postCount);
+        }
 
         // If the user has requested a filter, apply it now:
         if (!empty($course) || !empty($inst) || !empty($dept)) {
@@ -1255,14 +1265,11 @@ class Folio extends AbstractAPI implements
      */
     public function getMyFines($patron)
     {
-        $query = ['query' => 'userId==' . $patron['id'] . ' and status.name<>Closed'];
-        $response = $this->makeRequest("GET", '/accounts', $query);
-        $json = json_decode($response->getBody());
-        if (count($json->accounts) == 0) {
-            return [];
-        }
+        $query = ['query' => 'userId==' . $patron['id'] . ' and status.name==Closed'];
         $fines = [];
-        foreach ($json->accounts as $fine) {
+        foreach ($this->getPagedResults(
+            'accounts', '/accounts', $query
+        ) as $fine) {
             $date = date_create($fine->metadata->createdDate);
             $title = (isset($fine->title) ? $fine->title : null);
             $fines[] = [
-- 
GitLab