├── spec ├── data │ ├── zero_loans_response.json │ ├── item_no_loan_response.json │ ├── zero_fees_response.json │ ├── error_response.json │ ├── analytics_still_loading_response.xml │ ├── analytics_response_part4.xml │ ├── error_response.xml │ ├── per_second_threshold_error_response.json │ ├── item_barcode_error_response.json │ ├── report_not_found_response.xml │ ├── file_response.json │ ├── library_response.json │ ├── users_response.json │ ├── fee_response.json │ ├── e-collections_response.json │ ├── job_response.json │ ├── fees_response.json │ ├── create_loan_response.json │ ├── jobs_response.json │ ├── item_requests_response.json │ ├── jobinstance_response.json │ ├── portfolios_response.json │ ├── item_loan_response.json │ ├── representation_response.json │ ├── jobinstances_response.json │ ├── holdings_response.json │ ├── e-collection_response.json │ ├── representations_response.json │ ├── holding_response.json │ ├── holding_response.xml │ ├── portfolio_response.json │ ├── loans_response.json │ ├── requested-resources_response.json │ ├── bib_response_nz.xml │ ├── item_response.json │ ├── scanin_transit_response.json │ └── bib_response_iz.xml ├── TaskLists │ ├── ResourceSharingRequestSpec.php │ ├── RequestedResourceSpec.php │ ├── LendingRequestsSpec.php │ └── RequestedResourcesSpec.php ├── Users │ ├── RequestSpec.php │ ├── FeeSpec.php │ ├── LoanSpec.php │ ├── RequestsSpec.php │ ├── LoansSpec.php │ ├── FeesSpec.php │ ├── UsersSpec.php │ └── UserSpec.php ├── Conf │ ├── LibrarySpec.php │ ├── LocationSpec.php │ ├── JobInstanceSpec.php │ ├── JobSpec.php │ ├── JobsSpec.php │ ├── LocationsSpec.php │ ├── JobInstancesSpec.php │ └── LibrariesSpec.php ├── Bibs │ ├── FileSpec.php │ ├── PortfoliosSpec.php │ ├── RepresentationsSpec.php │ ├── FilesSpec.php │ ├── ElectronicCollectionsSpec.php │ ├── PortfolioSpec.php │ ├── RepresentationSpec.php │ ├── HoldingSpec.php │ ├── ItemsSpec.php │ ├── HoldingsSpec.php │ ├── BibSpec.php │ ├── BibsSpec.php │ └── ItemSpec.php ├── Analytics │ ├── AnalyticsSpec.php │ ├── RowSpec.php │ └── ReportSpec.php ├── Electronic │ └── CollectionSpec.php └── SpecHelper.php ├── .gitignore ├── .styleci.yml ├── phpspec-ci.yml ├── src ├── Exception │ ├── InvalidQuery.php │ ├── InvalidApiKey.php │ ├── ResourceNotFound.php │ ├── SruClientNotSetException.php │ ├── MaxNumberOfAttemptsExhausted.php │ ├── ClientException.php │ ├── NoLinkedNetworkZoneRecordException.php │ └── RequestFailed.php ├── Zones.php ├── Conf │ ├── Conf.php │ ├── Location.php │ ├── JobInstance.php │ ├── Library.php │ ├── Jobs.php │ ├── Job.php │ ├── Libraries.php │ ├── JobInstances.php │ └── Locations.php ├── Laravel │ ├── Facade.php │ └── ServiceProvider.php ├── TaskLists │ ├── ResourceSharingRequest.php │ ├── TaskLists.php │ ├── RequestedResource.php │ ├── RequestedResources.php │ └── LendingRequests.php ├── Analytics │ ├── Analytics.php │ └── Row.php ├── Model │ ├── ReadOnlyArrayAccess.php │ ├── IterableCollection.php │ ├── PaginatedListGenerator.php │ ├── PaginatedList.php │ ├── SimplePaginatedList.php │ ├── LazyResourceList.php │ ├── Model.php │ └── LazyResource.php ├── Users │ ├── Request.php │ ├── Loans.php │ ├── Requests.php │ ├── Fee.php │ ├── Loan.php │ ├── Fees.php │ ├── UserIdentifiers.php │ └── Users.php ├── Electronic │ └── Collection.php └── Bibs │ ├── Portfolio.php │ ├── Representation.php │ ├── File.php │ ├── Portfolios.php │ ├── Representations.php │ ├── ElectronicCollections.php │ ├── Holdings.php │ ├── Files.php │ ├── ScanInResponse.php │ ├── Holding.php │ ├── Items.php │ ├── Bibs.php │ └── Item.php ├── .scrutinizer.yml ├── .sonarcloud.properties ├── phpspec.yml ├── .travis.yml ├── ruleset.xml ├── phpunit.xml ├── .circleci └── config.yml ├── LICENSE ├── .overcommit.yml ├── config └── alma.php └── composer.json /spec/data/zero_loans_response.json: -------------------------------------------------------------------------------- 1 | {"total_record_count":0} -------------------------------------------------------------------------------- /spec/data/item_no_loan_response.json: -------------------------------------------------------------------------------- 1 | {"total_record_count":0} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /build/ 3 | composer.lock 4 | coverage.xml 5 | coverage 6 | -------------------------------------------------------------------------------- /spec/data/zero_fees_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "total_record_count": 0, 3 | "total_sum": 0, 4 | "currency": null 5 | } 6 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: recommended 2 | 3 | disabled: 4 | - concat_without_spaces 5 | 6 | enabled: 7 | - concat_with_spaces 8 | -------------------------------------------------------------------------------- /phpspec-ci.yml: -------------------------------------------------------------------------------- 1 | suites: 2 | alma_suite: 3 | namespace: Scriptotek\Alma 4 | psr4_prefix: Scriptotek\Alma 5 | formatter.name: pretty 6 | -------------------------------------------------------------------------------- /src/Exception/InvalidQuery.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | tests: 3 | override: 4 | - 5 | command: 'vendor/bin/phpspec run -f progress -c phpspec-ci.yml' 6 | # coverage: 7 | # file: 'coverage.clover' 8 | # format: 'php-clover' 9 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | # Path to sources 2 | #sonar.sources=. 3 | #sonar.exclusions= 4 | #sonar.inclusions= 5 | 6 | # Path to tests 7 | #sonar.tests= 8 | #sonar.test.exclusions= 9 | #sonar.test.inclusions= 10 | 11 | # Source encoding 12 | #sonar.sourceEncoding=UTF-8 13 | 14 | # Exclusions for copy-paste detection 15 | #sonar.cpd.exclusions= 16 | -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | suites: 2 | alma_suite: 3 | namespace: Scriptotek\Alma 4 | psr4_prefix: Scriptotek\Alma 5 | formatter.name: pretty 6 | # extensions: 7 | # PhpSpecCodeCoverage\CodeCoverageExtension: 8 | # format: 9 | # - html 10 | # - clover 11 | # output: 12 | # html: coverage 13 | # clover: coverage.xml 14 | -------------------------------------------------------------------------------- /src/Conf/Conf.php: -------------------------------------------------------------------------------- 1 | client = $client; 12 | $this->libraries = new Libraries($client); 13 | $this->jobs = new Jobs($client); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | dist: bionic 3 | 4 | php: 5 | - 7.2 6 | - 7.3 7 | - 7.4 8 | 9 | before_script: 10 | - echo 'date.timezone = "UTC"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 11 | - composer install --no-interaction --optimize-autoloader 12 | 13 | script: 14 | - vendor/bin/phpspec run --verbose --no-interaction 15 | -------------------------------------------------------------------------------- /spec/data/analytics_response_part4.xml: -------------------------------------------------------------------------------- 1 | true420033No more rows to fetchE01-2308152114-DU9TC-AWAE716956513 -------------------------------------------------------------------------------- /spec/data/error_response.xml: -------------------------------------------------------------------------------- 1 | true401664Mandatory field is missing: libraryE02-2807130150-8OWYS-AWAE533535741 -------------------------------------------------------------------------------- /spec/data/per_second_threshold_error_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorList": { 3 | "error": [ 4 | { 5 | "errorCode": "PER_SECOND_THRESHOLD", 6 | "errorMessage": "HTTP requests are more than allowed per second. See https://goo.gl/2x1c1M " 7 | } 8 | ] 9 | }, 10 | "errorsExist": true, 11 | "result": null 12 | } -------------------------------------------------------------------------------- /spec/data/item_barcode_error_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorList": { 3 | "error": [ 4 | { 5 | "errorCode": "401689", 6 | "errorMessage": "No items found for barcode 123.", 7 | "trackingId": "E02-2307205340-QCHPA-AWAE1768653911" 8 | } 9 | ] 10 | }, 11 | "errorsExist": true, 12 | "result": null 13 | } -------------------------------------------------------------------------------- /spec/data/report_not_found_response.xml: -------------------------------------------------------------------------------- 1 | trueINTERNAL_SERVER_ERRORPath not found (/test/path)E01-2907105336-JL6QZ-AWAE1528523551 -------------------------------------------------------------------------------- /spec/data/file_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "pid": "13216677500002204", 3 | "path": "47BIBSYS_UBO/storage/alma/36/3A/65/88/BA/6D/E0/92/B2/57/23/07/75/65/57/4E/75nb26737_side085.tif", 4 | "thumbnail_url": "https://eu01.alma.exlibrisgroup.com/view/delivery/thumbnail/47BIBSYS_UBO/13216677500002204", 5 | "label": "75nb26737_side085", 6 | "size": 16086776, 7 | "url": null, 8 | "fulltext": null 9 | } 10 | -------------------------------------------------------------------------------- /spec/TaskLists/ResourceSharingRequestSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $id); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PSR2 without namespace requirement. 4 | 5 | 6 | */database/* 7 | 8 | 9 | */tests/* 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Laravel/Facade.php: -------------------------------------------------------------------------------- 1 | request_id = $id; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | src 10 | 11 | 12 | 13 | 14 | 15 | tests/ 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /spec/data/library_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "campus": { 3 | "desc": "UiO Blindern", 4 | "value": "BLINDERN" 5 | }, 6 | "code": "1030310", 7 | "default_location": { 8 | "desc": "UREAL Boksamling", 9 | "value": "k00423" 10 | }, 11 | "description": "UiO Realfagsbiblioteket", 12 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/conf/libraries/1030310", 13 | "name": "UiO Realfagsbiblioteket", 14 | "path": "47BIBSYS.47BIBSYS_UBO.1030310", 15 | "proxy": "", 16 | "resource_sharing": true 17 | } -------------------------------------------------------------------------------- /spec/Users/RequestSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $user, '123'); 15 | } 16 | 17 | public function it_is_initializable() 18 | { 19 | $this->shouldHaveType(Request::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Analytics/Analytics.php: -------------------------------------------------------------------------------- 1 | client = $client; 21 | } 22 | 23 | public function get(...$args) 24 | { 25 | return new Report($this->client, ...$args); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Model/ReadOnlyArrayAccess.php: -------------------------------------------------------------------------------- 1 | get($offset)->exists(); 20 | } 21 | 22 | public function offsetGet($offset): mixed 23 | { 24 | return $this->get($offset); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # PHP CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-php/ for more details 4 | # 5 | --- 6 | version: 2 7 | jobs: 8 | build: 9 | docker: 10 | # Reference: https://circleci.com/docs/2.0/circleci-images/ 11 | - image: circleci/php:7.2-node-browsers 12 | 13 | working_directory: ~/repo 14 | 15 | steps: 16 | - checkout 17 | 18 | # Download dependencies 19 | # (no caching at this point since we use some in dev state) 20 | - run: composer install -n --prefer-dist 21 | 22 | # run tests! 23 | - run: vendor/bin/phpspec run --verbose --no-interaction 24 | -------------------------------------------------------------------------------- /spec/TaskLists/RequestedResourceSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $library, 'SUPER_DESK', $bib); 16 | } 17 | 18 | public function it_is_initializable() 19 | { 20 | $this->shouldHaveType(RequestedResource::class); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spec/data/users_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "total_record_count": 1, 3 | "user": [ 4 | { 5 | "fees": null, 6 | "first_name": "Dan", 7 | "gender": { 8 | "desc": "", 9 | "value": "" 10 | }, 11 | "last_name": "Banan", 12 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/users/1234567", 13 | "loans": null, 14 | "password": "", 15 | "primary_id": "1234567", 16 | "requests": null, 17 | "status": { 18 | "desc": "Active", 19 | "value": "ACTIVE" 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/Model/IterableCollection.php: -------------------------------------------------------------------------------- 1 | init()->resources[$this->position]; 12 | } 13 | 14 | public function key(): int 15 | { 16 | return $this->position; 17 | } 18 | 19 | public function next(): void 20 | { 21 | $this->position++; 22 | } 23 | 24 | public function rewind(): void 25 | { 26 | $this->position = 0; 27 | } 28 | 29 | public function valid(): bool 30 | { 31 | return $this->position < $this->count(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spec/Conf/LibrarySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $libraryCode); 16 | } 17 | 18 | public function it_is_initializable() 19 | { 20 | $this->shouldHaveType(Library::class); 21 | } 22 | 23 | public function it_should_have_locations() 24 | { 25 | $this->locations->shouldBeAnInstanceOf(Locations::class); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spec/data/fee_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "9854064491211104", 3 | "type": { 4 | "value": "LOSTITEMREPLACEMENTFEE", 5 | "desc": "Lost item replacement fee" 6 | }, 7 | "status": { 8 | "value": "ACTIVE", 9 | "desc": "Active" 10 | }, 11 | "balance": 750, 12 | "remaining_vat_amount": 0, 13 | "original_amount": 750, 14 | "original_vat_amount": 0, 15 | "creation_time": "2018-01-05T04:49:27.349Z", 16 | "status_time": "2018-01-05T04:49:27.349Z", 17 | "comment": null, 18 | "owner": { 19 | "value": "1030310", 20 | "desc": "UiO Realfagsbiblioteket" 21 | }, 22 | "title": "Knowledge concepts", 23 | "barcode": null, 24 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/users/12345/fees/9854064491211104" 25 | } 26 | -------------------------------------------------------------------------------- /spec/Conf/LocationSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $library, $location_code); 16 | } 17 | 18 | public function it_is_initializable() 19 | { 20 | $this->shouldHaveType(Location::class); 21 | } 22 | 23 | public function it_should_belong_to_a_library() 24 | { 25 | $this->library->shouldBeAnInstanceOf(Library::class); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spec/Conf/JobInstanceSpec.php: -------------------------------------------------------------------------------- 1 | job_id = '123'; 15 | $instanceId = '1108569450000121'; 16 | $this->beConstructedWith($client, $job, $instanceId); 17 | } 18 | 19 | public function it_is_initializable() 20 | { 21 | $this->shouldHaveType(JobInstance::class); 22 | } 23 | 24 | public function it_should_belong_to_a_job() 25 | { 26 | $this->job->shouldBeAnInstanceOf(Job::class); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spec/Bibs/FileSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = '990006312214702204'; 16 | $representation->representation_id = '22163771200002204'; 17 | $file_id = '23163771190002204'; 18 | 19 | $this->beConstructedWith($client, $bib, $representation, $file_id); 20 | } 21 | 22 | public function it_is_initializable() 23 | { 24 | $this->shouldHaveType(File::class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spec/Conf/JobSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, 'M26714670000011'); 15 | 16 | $client->getJSON('/conf/jobs/M26714670000011') 17 | ->willReturn(SpecHelper::getDummyData('job_response.json')); 18 | $client->postJSON('/conf/jobs/M26714670000011?op=run') 19 | ->willReturn(SpecHelper::getDummyData('job_response.json')); 20 | } 21 | 22 | public function it_is_initializable() 23 | { 24 | $this->shouldHaveType(Job::class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spec/data/e-collections_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "electronic_collection": [ 3 | { 4 | "id": "61173723710002204", 5 | "is_local": false, 6 | "public_name": "SpringerLink Books Complete", 7 | "description": "", 8 | "library": null, 9 | "type": { 10 | "value": "0", 11 | "desc": "Selective package" 12 | }, 13 | "license": null, 14 | "source": null, 15 | "creator": null, 16 | "url": null, 17 | "free": null, 18 | "proxy": null, 19 | "language": null, 20 | "category": null, 21 | "portfolios": { 22 | "value": 55900, 23 | "link": null 24 | }, 25 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/electronic/e-collections/61173723710002204" 26 | } 27 | ], 28 | "total_record_count": 1 29 | } 30 | -------------------------------------------------------------------------------- /src/TaskLists/TaskLists.php: -------------------------------------------------------------------------------- 1 | client = $client; 20 | } 21 | 22 | public function getLendingRequests(Library $library, $params = []) 23 | { 24 | return new LendingRequests($this->client, $library, $params); 25 | } 26 | 27 | public function getRequestedResources(Library $library, $circ_desk = 'DEFAULT_CIRC_DESK', $params = []) 28 | { 29 | return new RequestedResources($this->client, $library, $circ_desk, $params); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/TaskLists/RequestedResource.php: -------------------------------------------------------------------------------- 1 | library = $library; 28 | $this->circ_desk = $circ_desc; 29 | $this->bib = $bib; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spec/Users/FeeSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $user, $fee_id); 17 | } 18 | 19 | public function it_is_initializable() 20 | { 21 | $this->shouldHaveType(Fee::class); 22 | } 23 | 24 | public function it_can_exist() 25 | { 26 | $this->init(SpecHelper::getDummyData('fee_response.json')); 27 | 28 | $this->exists()->shouldBe(true); 29 | $this->balance->shouldBe(750); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spec/Analytics/AnalyticsSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($almaClient); 13 | } 14 | 15 | public function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Scriptotek\Alma\Analytics\Analytics'); 18 | } 19 | 20 | public function it_provides_an_interface_to_report_objects() 21 | { 22 | $path = 'UIO,Universitetsbiblioteket/Reports/RSS/Nyhetslister : Fransk'; // str_random(); 23 | 24 | $report = $this->get($path); 25 | 26 | $report->shouldHaveType('Scriptotek\Alma\Analytics\Report'); 27 | $report->path->shouldBe($path); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spec/Electronic/CollectionSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $collection_id); 16 | } 17 | 18 | protected function expectRequest($client) 19 | { 20 | $client->getJSON('/electronic/e-collections/123') 21 | ->shouldBeCalled() 22 | ->willReturn(SpecHelper::getDummyData('e-collection_response.json')); 23 | } 24 | 25 | public function it_is_initializable() 26 | { 27 | $this->shouldHaveType(Collection::class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spec/data/job_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "link": "string", 3 | "id": "M26714670000011", 4 | "name": "Export Physical Items", 5 | "description": "Export Physical Items", 6 | "type": { 7 | "desc": "Manual", 8 | "value": "MANUAL" 9 | }, 10 | "category": { 11 | "desc": "Normalization", 12 | "value": "NORMALIZATION" 13 | }, 14 | "content": { 15 | "desc": "All Titles", 16 | "value": "BIB_MMS" 17 | }, 18 | "schedule": { 19 | "desc": "Not scheduled", 20 | "value": "NOT_SCHEDULED" 21 | }, 22 | "creator": "string", 23 | "next_run": "2024-05-30T09:30:10Z", 24 | "parameter": [ 25 | { 26 | "name": { 27 | "desc": "string", 28 | "value": "string" 29 | }, 30 | "value": "string" 31 | } 32 | ], 33 | "related_profile": { 34 | "link": "string", 35 | "value": "ID123" 36 | }, 37 | "additional_info": { 38 | "link": "string", 39 | "value": "string" 40 | } 41 | } -------------------------------------------------------------------------------- /spec/Users/LoanSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $user, $item, $loan_id); 18 | } 19 | 20 | public function it_is_initializable() 21 | { 22 | $this->shouldHaveType(Loan::class); 23 | } 24 | 25 | public function it_can_exist() 26 | { 27 | $this->init(SpecHelper::getDummyData('item_loan_response.json')->item_loan[0]); 28 | 29 | $this->exists()->shouldBe(true); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spec/Users/RequestsSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $url); 16 | } 17 | 18 | public function it_is_countable(AlmaClient $client) 19 | { 20 | $client->getJSON('/bibs/1/holdings/2/items/3/requests') 21 | ->shouldBeCalled() 22 | ->willReturn(SpecHelper::getDummyData('item_requests_response.json')); 23 | 24 | $this->shouldHaveCount(1); 25 | } 26 | 27 | public function it_is_initializable() 28 | { 29 | $this->shouldHaveType(Requests::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spec/data/fees_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "fee": [ 3 | { 4 | "id": "9854064491211104", 5 | "type": { 6 | "value": "LOSTITEMREPLACEMENTFEE", 7 | "desc": "Lost item replacement fee" 8 | }, 9 | "status": { 10 | "value": "ACTIVE", 11 | "desc": "Active" 12 | }, 13 | "balance": 750, 14 | "remaining_vat_amount": 0, 15 | "original_amount": 750, 16 | "original_vat_amount": 0, 17 | "creation_time": "2018-01-05T04:49:27.349Z", 18 | "status_time": "2018-01-05T04:49:27.349Z", 19 | "comment": null, 20 | "owner": { 21 | "value": "1030310", 22 | "desc": "UiO Realfagsbiblioteket" 23 | }, 24 | "title": "Knowledge concepts", 25 | "barcode": null, 26 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/users/12345/fees/9854064491211104" 27 | } 28 | ], 29 | "total_record_count": 1, 30 | "total_sum": 750, 31 | "currency": "NOK" 32 | } 33 | -------------------------------------------------------------------------------- /src/Exception/RequestFailed.php: -------------------------------------------------------------------------------- 1 | errorCode = $errorCode; 24 | 25 | parent::__construct($message, 0, $previous); 26 | } 27 | 28 | /** 29 | * Returns the error code string. 30 | */ 31 | public function getErrorCode() 32 | { 33 | return $this->errorCode; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spec/data/create_loan_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Vollset, K.", 3 | "call_number": "Ung 839.82 Vol:Ban", 4 | "circ_desk": { 5 | "desc": "Utlånet Ureal", 6 | "value": "DEFAULT_CIRC_DESK" 7 | }, 8 | "description": null, 9 | "due_date": "2018-08-10T18:00:00Z", 10 | "item_barcode": "303011kj0", 11 | "item_policy": { 12 | "description": null, 13 | "value": null 14 | }, 15 | "library": { 16 | "desc": "UiO HumSam-biblioteket", 17 | "value": "1030300" 18 | }, 19 | "loan_date": "2018-07-13T17:31:39.800Z", 20 | "loan_id": "7329587120002204", 21 | "loan_status": "ACTIVE", 22 | "location_code": { 23 | "name": "UHS S-Litt", 24 | "value": "k00040" 25 | }, 26 | "mms_id": "990006312214702204", 27 | "process_status": "NORMAL", 28 | "publication_year": "2000", 29 | "renewable": true, 30 | "title": "Bananboken K. Vollset ; [illustrasjoner: Motorfinger og Jim]", 31 | "user_id": "Dan Michael" 32 | } -------------------------------------------------------------------------------- /spec/data/jobs_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "total_record_count": 1, 3 | "job": [ 4 | { 5 | "link": "string", 6 | "id": "M26714670000011", 7 | "name": "Export Physical Items", 8 | "description": "Export Physical Items", 9 | "type": { 10 | "desc": "Manual", 11 | "value": "MANUAL" 12 | }, 13 | "category": { 14 | "desc": "Normalization", 15 | "value": "NORMALIZATION" 16 | }, 17 | "content": { 18 | "desc": "All Titles", 19 | "value": "BIB_MMS" 20 | }, 21 | "schedule": { 22 | "desc": "Not scheduled", 23 | "value": "NOT_SCHEDULED" 24 | }, 25 | "creator": "string", 26 | "next_run": "2024-05-30T09:30:10Z", 27 | "parameter": [ 28 | { 29 | "name": { 30 | "desc": "string", 31 | "value": "string" 32 | }, 33 | "value": "string" 34 | } 35 | ], 36 | "related_profile": { 37 | "link": "string", 38 | "value": "ID123" 39 | }, 40 | "additional_info": { 41 | "link": "string", 42 | "value": "string" 43 | } 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /spec/Bibs/PortfoliosSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = 'abc'; 16 | $this->beConstructedWith($client, $bib); 17 | } 18 | 19 | public function it_is_initializable() 20 | { 21 | $this->shouldHaveType(Portfolios::class); 22 | } 23 | 24 | protected function expectRequest($client) 25 | { 26 | $client->getJSON('/bibs/abc/portfolios') 27 | ->shouldBeCalled() 28 | ->willReturn(SpecHelper::getDummyData('portfolios_response.json')); 29 | } 30 | 31 | public function it_is_countable(AlmaClient $client) 32 | { 33 | $this->expectRequest($client); 34 | 35 | $this->shouldHaveCount(1); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spec/Bibs/RepresentationsSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = 'abc'; 16 | $this->beConstructedWith($client, $bib); 17 | } 18 | 19 | public function it_is_initializable() 20 | { 21 | $this->shouldHaveType(Representations::class); 22 | } 23 | 24 | protected function expectRequest($client) 25 | { 26 | $client->getJSON('/bibs/abc/representations') 27 | ->shouldBeCalled() 28 | ->willReturn(SpecHelper::getDummyData('representations_response.json')); 29 | } 30 | 31 | public function it_is_countable(AlmaClient $client) 32 | { 33 | $this->expectRequest($client); 34 | 35 | $this->shouldHaveCount(2); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spec/TaskLists/LendingRequestsSpec.php: -------------------------------------------------------------------------------- 1 | code = 'SOME_LIBRARY'; 16 | $this->beConstructedWith($client, $library, [ 17 | 'printed' => 'N', 18 | 'status' => 'REQUEST_CREATED_LEND', 19 | ]); 20 | 21 | $client->getJSON('/task-lists/rs/lending-requests?printed=N&status=REQUEST_CREATED_LEND&library=SOME_LIBRARY') 22 | ->shouldBeCalledTimes(1) 23 | ->willReturn(SpecHelper::getDummyData('lending_requests_created.json')); 24 | 25 | $this->all()->shouldBeArray(); 26 | $this->all()->shouldHaveCount(3); 27 | $this->all()[0]->shouldBeAnInstanceOf(ResourceSharingRequest::class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Conf/Location.php: -------------------------------------------------------------------------------- 1 | library = $library; 23 | $this->code = $code; 24 | } 25 | 26 | /** 27 | * Check if we have the full representation of our data object. 28 | * 29 | * @param \stdClass $data 30 | * 31 | * @return bool 32 | */ 33 | protected function isInitialized($data) 34 | { 35 | return isset($data->location); 36 | } 37 | 38 | /** 39 | * Generate the base URL for this resource. 40 | * 41 | * @return string 42 | */ 43 | protected function urlBase() 44 | { 45 | return "/conf/libraries/{$this->library->code}/locations/{$this->code}"; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dan Michael O. Heggø 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/Conf/JobsSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client); 16 | } 17 | 18 | public function it_provides_a_lazy_interface_to_job_objects(AlmaClient $client) 19 | { 20 | SpecHelper::expectNoRequests($client); 21 | 22 | $job_id = 'M26714670000011'; 23 | $job = $this->get($job_id); 24 | 25 | $job->shouldBeAnInstanceOf(Job::class); 26 | $job->job_id->shouldBe($job_id); 27 | } 28 | 29 | public function it_provides_jobs(AlmaClient $client) 30 | { 31 | $client->getJSON(Argument::containingString('/conf/jobs?')) 32 | ->shouldBeCalled() 33 | ->willReturn(SpecHelper::getDummyData('jobs_response.json')); 34 | 35 | $this->all()->shouldBeArray(); 36 | $this->all()[0]->shouldBeAnInstanceOf(Job::class); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Users/Request.php: -------------------------------------------------------------------------------- 1 | user = $user; 23 | $this->request_id = $request_id; 24 | } 25 | 26 | /** 27 | * Check if we have the full representation of our data object. 28 | * 29 | * @param \stdClass $data 30 | * 31 | * @return bool 32 | */ 33 | protected function isInitialized($data) 34 | { 35 | return isset($data->request_type); 36 | } 37 | 38 | /** 39 | * Generate the base URL for this resource. 40 | * 41 | * @return string 42 | */ 43 | protected function urlBase() 44 | { 45 | return sprintf('/users/%s/requests/%s', rawurlencode($this->user->id), $this->request_id); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spec/data/item_requests_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "total_record_count": 1, 3 | "user_request": [ 4 | { 5 | "author": "Osipov, Andrey V.", 6 | "barcode": null, 7 | "comment": null, 8 | "description": null, 9 | "material_type": { 10 | "desc": null, 11 | "value": null 12 | }, 13 | "mms_id": "999919897211902204", 14 | "pickup_location": "UiO Realfagsbiblioteket", 15 | "pickup_location_library": "1030310", 16 | "pickup_location_type": "LIBRARY", 17 | "place_in_queue": 1, 18 | "request_date": "2018-07-31Z", 19 | "request_id": "1403795240001204", 20 | "request_status": "NOT_STARTED", 21 | "request_sub_type": { 22 | "desc": "Patron physical item request", 23 | "value": "PATRON_PHYSICAL" 24 | }, 25 | "request_type": "HOLD", 26 | "title": "Modern electromagnetic scattering theory with applications Andrey V. Osipov, Sergei A. Tretyakov", 27 | "user_primary_id": "Some User" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /spec/Bibs/FilesSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = 'abc'; 17 | $representation->representation_id = '123'; 18 | $this->beConstructedWith($client, $bib, $representation); 19 | } 20 | 21 | public function it_is_initializable() 22 | { 23 | $this->shouldHaveType(Files::class); 24 | } 25 | 26 | protected function expectRequest($client) 27 | { 28 | $client->getJSON('/bibs/abc/representations/123/files') 29 | ->shouldBeCalled() 30 | ->willReturn(SpecHelper::getDummyData('files_response.json')); 31 | } 32 | 33 | public function it_is_countable(AlmaClient $client) 34 | { 35 | $this->expectRequest($client); 36 | 37 | $this->shouldHaveCount(96); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spec/data/jobinstance_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "link": "string", 3 | "id": "1108569450000121", 4 | "external_id": "1108569450000121", 5 | "name": "Export Physical Items - war and love - 04/15/2015 16:07", 6 | "submitted_by": { 7 | "desc": "string", 8 | "value": "exl_impl" 9 | }, 10 | "submit_time": "2015-04-15T13:07:40.800Z", 11 | "start_time": "2015-04-15T13:07:40.868Z", 12 | "end_time": "2015-04-15T13:07:44.359Z", 13 | "progress": 100, 14 | "status": { 15 | "desc": "string", 16 | "value": "QUEUED" 17 | }, 18 | "status_date": "2015-07-20", 19 | "alert": [ 20 | { 21 | "desc": "string", 22 | "value": "alert_general_success" 23 | } 24 | ], 25 | "counter": [ 26 | { 27 | "type": { 28 | "desc": "string", 29 | "value": "4" 30 | }, 31 | "value": "string" 32 | } 33 | ], 34 | "action": [ 35 | "string" 36 | ], 37 | "job_info": { 38 | "link": "string", 39 | "id": "1108569450000121", 40 | "name": "Export Physical Items - war and love - 04/15/2015 16:07", 41 | "description": "string", 42 | "type": { 43 | "desc": "string", 44 | "value": "CREATE_SET" 45 | }, 46 | "category": { 47 | "desc": "string", 48 | "value": "NORMALIZATION" 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | # Use this file to configure the Overcommit hooks you wish to use. This will 2 | # extend the default configuration defined in: 3 | # https://github.com/brigade/overcommit/blob/master/config/default.yml 4 | # 5 | # At the topmost level of this YAML file is a key representing type of hook 6 | # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can 7 | # customize each hook, such as whether to only run it on certain files (via 8 | # `include`), whether to only display output if it fails (via `quiet`), etc. 9 | # 10 | # For a complete list of hooks, see: 11 | # https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook 12 | # 13 | # For a complete list of options that you can use to customize hooks, see: 14 | # https://github.com/brigade/overcommit#configuration 15 | # 16 | # Uncomment the following lines to make the configuration take effect. 17 | --- 18 | PreCommit: 19 | TrailingWhitespace: 20 | enabled: true 21 | problem_on_unmodified_line: warn 22 | PhpCs: 23 | enabled: true 24 | command: 'vendor/bin/phpcs' 25 | flags: ['--standard=ruleset.xml', '--report=csv', '-s'] 26 | problem_on_unmodified_line: warn 27 | exclude: 28 | - 'spec/**/*' 29 | PhpLint: 30 | enabled: true 31 | on_warn: fail 32 | 33 | -------------------------------------------------------------------------------- /spec/Conf/LocationsSpec.php: -------------------------------------------------------------------------------- 1 | code = 'LIB_CODE'; 16 | $this->beConstructedWith($client, $library); 17 | } 18 | 19 | public function it_provides_a_lazy_interface_to_location_objects(AlmaClient $client) 20 | { 21 | SpecHelper::expectNoRequests($client); 22 | 23 | $code = 'LOC_CODE'; 24 | $location = $this->get($code); 25 | 26 | $location->shouldBeAnInstanceOf(Location::class); 27 | $location->code->shouldBe($code); 28 | } 29 | 30 | public function it_provides_locations(AlmaClient $client) 31 | { 32 | $client->getJSON('/conf/libraries/LIB_CODE/locations') 33 | ->shouldBeCalled() 34 | ->willReturn(SpecHelper::getDummyData('locations_response.json')); 35 | 36 | $this->all()->shouldBeArray(); 37 | $this->all()[0]->shouldBeAnInstanceOf(Location::class); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spec/data/portfolios_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "portfolio": [ 3 | { 4 | "id": "53182555270002204", 5 | "is_local": false, 6 | "is_standalone": false, 7 | "resource_metadata": { 8 | "mms_id": { 9 | "value": "999919790879802204", 10 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999919790879802204" 11 | }, 12 | "title": "Elementary Mechanics Using Matlab A Modern Course Combining Analytical and Numerical Techniques /" 13 | }, 14 | "electronic_collection": { 15 | "id": { 16 | "value": "61173723710002204", 17 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/electronic/e-collections/61173723710002204" 18 | }, 19 | "service": { 20 | "value": "62173726760002204", 21 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/electronic/e-collections/61173723710002204/e-services/62173726760002204" 22 | } 23 | }, 24 | "availability": { 25 | "value": "11", 26 | "desc": "Available" 27 | }, 28 | "library": null, 29 | "license": null, 30 | "pda": null, 31 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999919790879802204/portfolios/53182555270002204" 32 | } 33 | ], 34 | "total_record_count": 1 35 | } 36 | -------------------------------------------------------------------------------- /spec/data/item_loan_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "item_loan": [ 3 | { 4 | "author": "Vollset, K.", 5 | "call_number": "Ung 839.82 Vol:Ban", 6 | "circ_desk": { 7 | "desc": "Utlånet Ureal", 8 | "value": "DEFAULT_CIRC_DESK" 9 | }, 10 | "description": null, 11 | "due_date": "2018-08-20T20:00:00Z", 12 | "item_barcode": "303011kj0", 13 | "item_policy": { 14 | "description": null, 15 | "value": null 16 | }, 17 | "library": { 18 | "desc": "UiO HumSam-biblioteket", 19 | "value": "1030300" 20 | }, 21 | "loan_date": "2018-07-23T20:25:26.347Z", 22 | "loan_id": "7378239410002204", 23 | "loan_status": "ACTIVE", 24 | "location_code": { 25 | "name": "UHS S-Litt", 26 | "value": "k00040" 27 | }, 28 | "mms_id": "990006312214702204", 29 | "process_status": "NORMAL", 30 | "publication_year": "2000", 31 | "renewable": true, 32 | "title": "Bananboken K. Vollset ; [illustrasjoner: Motorfinger og Jim]", 33 | "user_id": "Dan Michael" 34 | } 35 | ], 36 | "total_record_count": 1 37 | } -------------------------------------------------------------------------------- /src/Conf/JobInstance.php: -------------------------------------------------------------------------------- 1 | job = $job; 29 | $this->job_instance_id = $job_instance_id; 30 | parent::__construct($client); 31 | } 32 | 33 | /** 34 | * Check if we have the full representation of our data object. 35 | * 36 | * @param \stdClass $data 37 | * 38 | * @return bool 39 | */ 40 | protected function isInitialized($data) 41 | { 42 | return isset($data->job_info); 43 | } 44 | 45 | /** 46 | * Generate the base URL for this resource. 47 | * 48 | * @return string 49 | */ 50 | protected function urlBase() 51 | { 52 | return "/conf/jobs/{$this->job->job_id}/instances/{$this->job_instance_id}"; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /spec/Conf/JobInstancesSpec.php: -------------------------------------------------------------------------------- 1 | job_id = '1108569450000121'; 16 | $this->beConstructedWith($client, $job); 17 | } 18 | 19 | public function it_provides_a_lazy_interface_to_jobinstance_objects(AlmaClient $client) 20 | { 21 | SpecHelper::expectNoRequests($client); 22 | 23 | $jobId = '1108569450000121'; 24 | $instanceId = '1108569450000121'; 25 | $jobInstance = $this->get($instanceId); 26 | $jobInstance->shouldBeAnInstanceOf(JobInstance::class); 27 | } 28 | 29 | public function it_provides_job_instances(AlmaClient $client) 30 | { 31 | $jobId = '1108569450000121'; 32 | $client->getJSON("/conf/jobs/{$jobId}/instances?offset=0&limit=10") 33 | ->shouldBeCalled() 34 | ->willReturn(SpecHelper::getDummyData('jobinstances_response.json')); 35 | 36 | $this->all()->shouldBeArray(); 37 | $this->all()[0]->shouldBeAnInstanceOf(JobInstance::class); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Users/Loans.php: -------------------------------------------------------------------------------- 1 | user = $user; 20 | } 21 | 22 | /** 23 | * Get resource. 24 | * 25 | * @param string $loan_id 26 | * 27 | * @return Loan 28 | */ 29 | public function get($loan_id) 30 | { 31 | return Loan::make($this->client, $this->user, $loan_id); 32 | } 33 | 34 | /** 35 | * Convert a retrieved resource object to a model object. 36 | * 37 | * @param $data 38 | * 39 | * @return Loan 40 | */ 41 | public function convertToResource($data) 42 | { 43 | return $this->get($data->loan_id)->init($data); 44 | } 45 | 46 | /** 47 | * Generate the base URL for this resource. 48 | * 49 | * @return string 50 | */ 51 | protected function urlBase() 52 | { 53 | return sprintf('/users/%s/loans', $this->user->id); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spec/TaskLists/RequestedResourcesSpec.php: -------------------------------------------------------------------------------- 1 | code = 'SOME_LIBRARY'; 18 | $this->beConstructedWith($client, $library, 'DEFAULT_CIRC_DESK', [ 19 | 'printed' => 'N', 20 | ]); 21 | 22 | $client->bibs = $bibs; 23 | $bibs->get('991120800814702204') 24 | ->shouldBeCalled() 25 | ->willReturn($bib); 26 | 27 | $client->getJSON('/task-lists/requested-resources?printed=N&library=SOME_LIBRARY&circ_desk=DEFAULT_CIRC_DESK&offset=0&limit=10') 28 | ->shouldBeCalled() 29 | ->willReturn(SpecHelper::getDummyData('requested-resources_response.json')); 30 | 31 | $result = $this->all(); 32 | 33 | $result->shouldBeArray(); 34 | $result->shouldHaveCount(1); 35 | $result[0]->shouldBeAnInstanceOf(RequestedResource::class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Electronic/Collection.php: -------------------------------------------------------------------------------- 1 | collection_id = $collection_id; 23 | // FUTURE: $this->services = Services::make($this->client, $this); 24 | } 25 | 26 | /** 27 | * Generate the base URL for this resource. 28 | * 29 | * @return string 30 | */ 31 | protected function urlBase() 32 | { 33 | return "/electronic/e-collections/{$this->collection_id}"; 34 | } 35 | 36 | /** 37 | * Get the services for this collection. 38 | */ 39 | // public function getServices() 40 | // { 41 | // return $this->services; 42 | // } 43 | 44 | /** 45 | * Check if we have the full representation of our data object. 46 | * 47 | * @param \stdClass $data 48 | * 49 | * @return bool 50 | */ 51 | protected function isInitialized($data) 52 | { 53 | return isset($data->public_name); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Bibs/Portfolio.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 24 | $this->portfolio_id = $portfolio_id; 25 | } 26 | 27 | /** 28 | * Generate the base URL for this resource. 29 | * 30 | * @return string 31 | */ 32 | protected function urlBase() 33 | { 34 | return "/bibs/{$this->bib->mms_id}/portfolios/{$this->portfolio_id}"; 35 | } 36 | 37 | /** 38 | * Check if we have the full representation of our data object. 39 | * 40 | * @param \stdClass $data 41 | * 42 | * @return bool 43 | */ 44 | protected function isInitialized($data) 45 | { 46 | return isset($data->linking_details); 47 | } 48 | 49 | public function getElectronicCollection() 50 | { 51 | $this->init(); 52 | 53 | return new Collection($this->client, $this->data->electronic_collection->id->value); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Conf/Library.php: -------------------------------------------------------------------------------- 1 | code = $code; 29 | $this->locations = Locations::make($this->client, $this); 30 | } 31 | 32 | /** 33 | * Check if we have the full representation of our data object. 34 | * For libraries, it seems like we get the same data in the list 35 | * response as in the single-item response, so we just check some 36 | * random element. 37 | * 38 | * @param \stdClass $data 39 | * 40 | * @return bool 41 | */ 42 | protected function isInitialized($data) 43 | { 44 | return isset($data->path); 45 | } 46 | 47 | /** 48 | * Generate the base URL for this resource. 49 | * 50 | * @return string 51 | */ 52 | protected function urlBase() 53 | { 54 | return "/conf/libraries/{$this->code}"; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spec/data/representation_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "library": { 3 | "value": "1030300", 4 | "desc": "UiO HumSam-biblioteket" 5 | }, 6 | "id": "12216823140002204", 7 | "label": "High resolution TIFF images", 8 | "public_note": "Bare del 4 er digitalisert.", 9 | "usage_type": { 10 | "value": "PRESERVATION_MASTER", 11 | "desc": "Master" 12 | }, 13 | "active": { 14 | "value": "true", 15 | "desc": "Active" 16 | }, 17 | "author": null, 18 | "title": null, 19 | "volume": null, 20 | "issue": null, 21 | "date": null, 22 | "track": null, 23 | "number": null, 24 | "year": null, 25 | "access_rights_policy_id": { 26 | "value": "6863669220002204", 27 | "desc": "Fri tilgang!" 28 | }, 29 | "delivery_url": "https://eu01.alma.exlibrisgroup.com/view/delivery/47BIBSYS_UBO/12216823140002204", 30 | "thumbnail_url": "https://eu01.alma.exlibrisgroup.com/view/delivery/thumbnail/47BIBSYS_UBO/12216823140002204", 31 | "repository": { 32 | "value": "Alma", 33 | "desc": null 34 | }, 35 | "created_by": "System", 36 | "created_date": "2018-09-05Z", 37 | "last_modified_by": "System", 38 | "last_modified_date": "2018-09-13Z", 39 | "files": { 40 | "value": null, 41 | "total_record_count": 96, 42 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999814378424702204/representations/12216823140002204/files" 43 | }, 44 | "is_remote": false, 45 | "link": null 46 | } 47 | -------------------------------------------------------------------------------- /src/Users/Requests.php: -------------------------------------------------------------------------------- 1 | _urlBase = $url; 30 | } 31 | 32 | /** 33 | * Convert a data element to a resource object. 34 | * 35 | * @param $data 36 | * 37 | * @return Request 38 | */ 39 | protected function convertToResource($data) 40 | { 41 | return Request::make($this->client, User::make($this->client, $data->user_primary_id), $data->request_id) 42 | ->init($data); 43 | } 44 | 45 | /** 46 | * Generate the base URL for this resource. 47 | * 48 | * @return string 49 | */ 50 | protected function urlBase() 51 | { 52 | return $this->_urlBase; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Users/Fee.php: -------------------------------------------------------------------------------- 1 | user = $user; 24 | $this->id = $id; 25 | } 26 | 27 | /** 28 | * Generate the base URL for this resource. 29 | * 30 | * @return string 31 | */ 32 | protected function urlBase() 33 | { 34 | return sprintf('/users/%s/fees/%s', rawurlencode($this->user->id), $this->id); 35 | } 36 | 37 | /** 38 | * Check if we have the full representation of our data object. 39 | * 40 | * @param \stdClass $data 41 | * 42 | * @return bool 43 | */ 44 | protected function isInitialized($data) 45 | { 46 | return isset($data->link); 47 | } 48 | 49 | /** 50 | * Get the related Item, if any. 51 | * 52 | * @return Item|null 53 | */ 54 | public function getItem() 55 | { 56 | if (isset($this->barcode)) { 57 | return $this->client->items->fromBarcode($this->barcode); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spec/Conf/LibrariesSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client); 16 | } 17 | 18 | public function it_is_muh() 19 | { 20 | $this->shouldBeAnInstanceOf(Libraries::class); 21 | $this->shouldImplement(\Countable::class); 22 | $this->shouldImplement(\Iterator::class); 23 | } 24 | 25 | public function it_provides_a_lazy_interface_to_libary_objects(AlmaClient $client) 26 | { 27 | SpecHelper::expectNoRequests($client); 28 | 29 | $libraryCode = 'THAT_LIBRARY'; 30 | $library = $this->get($libraryCode); 31 | 32 | $library->shouldBeAnInstanceOf(Library::class); 33 | $library->code->shouldBe($libraryCode); 34 | } 35 | 36 | public function it_provides_libraries(AlmaClient $client) 37 | { 38 | $client->getJSON('/conf/libraries') 39 | ->shouldBeCalled() 40 | ->willReturn(SpecHelper::getDummyData('libraries_response.json')); 41 | 42 | $this->shouldHaveCount(27); 43 | $this->all()->shouldBeArray(); 44 | $this->all()[0]->shouldBeAnInstanceOf(Library::class); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /spec/Bibs/ElectronicCollectionsSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = 'abc'; 17 | $this->beConstructedWith($client, $bib); 18 | } 19 | 20 | public function it_is_initializable() 21 | { 22 | $this->shouldHaveType(ElectronicCollections::class); 23 | } 24 | 25 | protected function expectRequest($client) 26 | { 27 | $client->getJSON('/bibs/abc/e-collections') 28 | ->shouldBeCalled() 29 | ->willReturn(SpecHelper::getDummyData('e-collections_response.json')); 30 | } 31 | 32 | public function it_is_countable(AlmaClient $client) 33 | { 34 | $this->expectRequest($client); 35 | 36 | $this->shouldHaveCount(1); 37 | } 38 | 39 | public function it_provides_basic_data_without_loading_the_full_record(AlmaClient $client) 40 | { 41 | $this->expectRequest($client); 42 | 43 | $c = $this->all()[0]; 44 | 45 | $c->shouldHaveType(Collection::class); 46 | $c->public_name->shouldBe('SpringerLink Books Complete'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spec/Bibs/PortfolioSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = 'abc'; 17 | $portfolio_id = '123'; 18 | $this->beConstructedWith($client, $bib, $portfolio_id); 19 | } 20 | 21 | public function it_is_initializable() 22 | { 23 | $this->shouldHaveType(Portfolio::class); 24 | } 25 | 26 | protected function expectRequest($client) 27 | { 28 | $client->getJSON('/bibs/abc/portfolios/123') 29 | ->shouldBeCalled() 30 | ->willReturn(SpecHelper::getDummyData('portfolio_response.json')); 31 | } 32 | 33 | public function it_fetches_data_when_needed(AlmaClient $client) 34 | { 35 | $this->expectRequest($client); 36 | 37 | $this->availability->desc->shouldBe('Available'); 38 | } 39 | 40 | public function it_belongs_to_collection(AlmaClient $client) 41 | { 42 | $this->expectRequest($client); 43 | 44 | $this->getElectronicCollection()->shouldHaveType(Collection::class); 45 | $this->electronic_collection->shouldHaveType(Collection::class); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Bibs/Representation.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 26 | $this->representation_id = $representation_id; 27 | $this->files = Files::make($this->client, $bib, $this); 28 | } 29 | 30 | /** 31 | * Generate the base URL for this resource. 32 | * 33 | * @return string 34 | */ 35 | protected function urlBase() 36 | { 37 | return "/bibs/{$this->bib->mms_id}/representations/{$this->representation_id}"; 38 | } 39 | 40 | /** 41 | * Get the files for this representation. 42 | */ 43 | public function getFiles() 44 | { 45 | return $this->files; 46 | } 47 | 48 | /** 49 | * Check if we have the full representation of our data object. 50 | * 51 | * @param \stdClass $data 52 | * 53 | * @return bool 54 | */ 55 | protected function isInitialized($data) 56 | { 57 | return isset($data->delivery_url); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Conf/Jobs.php: -------------------------------------------------------------------------------- 1 | client, $job_id); 38 | } 39 | 40 | /** 41 | * Convert a data element to a resource object. 42 | * 43 | * @param $data 44 | * 45 | * @return Job 46 | */ 47 | protected function convertToResource($data) 48 | { 49 | return Job::make($this->client, $data->id) 50 | ->init($data); 51 | } 52 | 53 | /** 54 | * Generate the base URL for this resource. 55 | * 56 | * @return string 57 | */ 58 | protected function urlBase() 59 | { 60 | return '/conf/jobs'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spec/Users/LoansSpec.php: -------------------------------------------------------------------------------- 1 | id = '123435'; 17 | $this->beConstructedWith($client, $user); 18 | } 19 | 20 | public function it_is_initializable() 21 | { 22 | $this->shouldHaveType(Loans::class); 23 | } 24 | 25 | public function it_yields_loans(AlmaClient $client) 26 | { 27 | $client->getJSON('/users/123435/loans?offset=0&limit=10') 28 | ->shouldBeCalled() 29 | ->willReturn(SpecHelper::getDummyData('loans_response.json')); 30 | 31 | $this->rewind(); 32 | $this->valid()->shouldBe(true); 33 | $this->current()->shouldBeAnInstanceOf(Loan::class); 34 | 35 | $this->shouldHaveCount(2); 36 | } 37 | 38 | public function it_can_be_empty(AlmaClient $client) 39 | { 40 | $client->getJSON('/users/123435/loans?offset=0&limit=10') 41 | ->shouldBeCalled() 42 | ->willReturn(SpecHelper::getDummyData('zero_loans_response.json')); 43 | 44 | $this->rewind(); 45 | $this->valid()->shouldBe(false); 46 | 47 | $this->shouldHaveCount(0); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spec/Users/FeesSpec.php: -------------------------------------------------------------------------------- 1 | id = '123435'; 17 | $this->beConstructedWith($client, $user); 18 | } 19 | 20 | public function it_is_initializable() 21 | { 22 | $this->shouldHaveType(Fees::class); 23 | } 24 | 25 | public function it_yields_fees(AlmaClient $client) 26 | { 27 | $client->getJSON('/users/123435/fees') 28 | ->shouldBeCalled() 29 | ->willReturn(SpecHelper::getDummyData('fees_response.json')); 30 | 31 | $this->total_sum->shouldBe(750); 32 | 33 | $this->rewind(); 34 | $this->valid()->shouldBe(true); 35 | $this->current()->shouldBeAnInstanceOf(Fee::class); 36 | 37 | $this->shouldHaveCount(1); 38 | } 39 | 40 | public function it_can_be_empty(AlmaClient $client) 41 | { 42 | $client->getJSON('/users/123435/fees') 43 | ->shouldBeCalled() 44 | ->willReturn(SpecHelper::getDummyData('zero_fees_response.json')); 45 | 46 | $this->rewind(); 47 | $this->valid()->shouldBe(false); 48 | 49 | $this->shouldHaveCount(0); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Conf/Job.php: -------------------------------------------------------------------------------- 1 | job_id = $job_id; 29 | $this->instances = new JobInstances($client, $this); 30 | } 31 | 32 | /** 33 | * Submit the job for running. 34 | * 35 | * @return string The API response body 36 | */ 37 | public function submit() 38 | { 39 | return $this->client->post($this->url() . '?op=run', json_encode($this->jsonSerialize())); 40 | } 41 | 42 | /** 43 | * Check if we have the full representation of our data object. 44 | * 45 | * @param \stdClass $data 46 | * 47 | * @return bool 48 | */ 49 | protected function isInitialized($data) 50 | { 51 | return isset($data->name); 52 | } 53 | 54 | /** 55 | * Generate the base URL for this resource. 56 | * 57 | * @return string 58 | */ 59 | protected function urlBase() 60 | { 61 | return "/conf/jobs/{$this->job_id}"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Bibs/File.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 34 | $this->representation = $representation; 35 | $this->file_id = $file_id; 36 | } 37 | 38 | /** 39 | * Generate the base URL for this resource. 40 | * 41 | * @return string 42 | */ 43 | protected function urlBase() 44 | { 45 | return "/bibs/{$this->bib->mms_id}/representations/{$this->representation->representation_id}/files/{$this->file_id}"; 46 | } 47 | 48 | /** 49 | * Check if we have the full representation of our data object. 50 | * 51 | * @param \stdClass $data 52 | * 53 | * @return bool 54 | */ 55 | protected function isInitialized($data) 56 | { 57 | return isset($data->path); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Conf/Libraries.php: -------------------------------------------------------------------------------- 1 | client, $code); 38 | } 39 | 40 | /** 41 | * Convert a data element to a resource object. 42 | * 43 | * @param $data 44 | * 45 | * @return Library 46 | */ 47 | protected function convertToResource($data) 48 | { 49 | return Library::make($this->client, $data->code) 50 | ->init($data); 51 | } 52 | 53 | /** 54 | * Generate the base URL for this resource. 55 | * 56 | * @return string 57 | */ 58 | protected function urlBase() 59 | { 60 | return '/conf/libraries'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spec/data/jobinstances_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "total_record_count": 1, 3 | "job_instance": [ 4 | { 5 | "link": "string", 6 | "id": "1108569450000121", 7 | "external_id": "1108569450000121", 8 | "name": "Export Physical Items - war and love - 04/15/2015 16:07", 9 | "submitted_by": { 10 | "desc": "string", 11 | "value": "exl_impl" 12 | }, 13 | "submit_time": "2015-04-15T13:07:40.800Z", 14 | "start_time": "2015-04-15T13:07:40.868Z", 15 | "end_time": "2015-04-15T13:07:44.359Z", 16 | "progress": 100, 17 | "status": { 18 | "desc": "string", 19 | "value": "QUEUED" 20 | }, 21 | "status_date": "2015-07-20", 22 | "alert": [ 23 | { 24 | "desc": "string", 25 | "value": "alert_general_success" 26 | } 27 | ], 28 | "counter": [ 29 | { 30 | "type": { 31 | "desc": "string", 32 | "value": "4" 33 | }, 34 | "value": "string" 35 | } 36 | ], 37 | "action": [ 38 | { 39 | "link": "string", 40 | "type": { 41 | "desc": "string", 42 | "value": "CREATE_SET" 43 | }, 44 | "population": { 45 | "desc": "string", 46 | "value": "string" 47 | }, 48 | "members": 0 49 | } 50 | ], 51 | "job_info": { 52 | "link": "string", 53 | "id": "1108569450000121", 54 | "name": "Export Physical Items - war and love - 04/15/2015 16:07", 55 | "description": "string", 56 | "type": { 57 | "desc": "string", 58 | "value": "CREATE_SET" 59 | }, 60 | "category": { 61 | "desc": "string", 62 | "value": "NORMALIZATION" 63 | } 64 | } 65 | } 66 | ] 67 | } -------------------------------------------------------------------------------- /spec/data/holdings_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "bib_data": { 3 | "author": "Vollset, K.", 4 | "date_of_publication": "2000", 5 | "isbn": "8247806290", 6 | "issn": null, 7 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990006312214702204", 8 | "mms_id": "990006312214702204", 9 | "network_number": [ 10 | "(NO-TrBIB)092093302", 11 | "(NO-TrBIB)000631221", 12 | "000631221-47bibsys_network", 13 | "(EXLNZ-47BIBSYS_NETWORK)990006312214702201" 14 | ], 15 | "place_of_publication": "[Oslo]", 16 | "publisher": "Gyldendal Tiden", 17 | "title": "Bananboken" 18 | }, 19 | "holding": [ 20 | { 21 | "holding_id": "2221159990000121", 22 | "library": { 23 | "desc": "BURNS", 24 | "value": "65144020000121" 25 | }, 26 | "link": "/almaws/v1/bibs/99100383900121/holdings/2221159990000121", 27 | "location": { 28 | "desc": "UNASSIGNED location", 29 | "value": "UNASSIGNED" 30 | } 31 | }, 32 | { 33 | "holding_id": "2221410000000121", 34 | "library": { 35 | "desc": "Main Library", 36 | "value": "5867020000121" 37 | }, 38 | "link": "/almaws/v1/bibs/99100383900121/holdings/2221410000000121", 39 | "location": { 40 | "desc": "Microforms", 41 | "value": "MICR" 42 | } 43 | } 44 | ], 45 | "total_record_count": 1 46 | } -------------------------------------------------------------------------------- /src/TaskLists/RequestedResources.php: -------------------------------------------------------------------------------- 1 | library = $library; 26 | $params['library'] = $library->code; 27 | $params['circ_desk'] = $circ_desk; 28 | $this->params = $params; 29 | } 30 | 31 | /** 32 | * Generate the base URL for this resource. 33 | * 34 | * @return string 35 | */ 36 | protected function urlBase() 37 | { 38 | return '/task-lists/requested-resources'; 39 | } 40 | 41 | /** 42 | * Convert a data element to a resource object. 43 | * 44 | * @param $data 45 | * 46 | * @return mixed 47 | */ 48 | protected function convertToResource($data) 49 | { 50 | $bib = $this->client->bibs->get($data->resource_metadata->mms_id->value); 51 | 52 | return RequestedResource::make($this->client, $this->library, $this->params['circ_desk'], $bib) 53 | ->init($data); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spec/Bibs/RepresentationSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = 'abc'; 17 | $representation_id = '123'; 18 | $this->beConstructedWith($client, $bib, $representation_id); 19 | } 20 | 21 | protected function expectRequest($client) 22 | { 23 | $client->getJSON('/bibs/abc/representations/123') 24 | ->shouldBeCalled() 25 | ->willReturn(SpecHelper::getDummyData('representation_response.json')); 26 | } 27 | 28 | public function it_is_initializable() 29 | { 30 | $this->shouldHaveType(Representation::class); 31 | } 32 | 33 | public function it_fetches_data_when_needed(AlmaClient $client) 34 | { 35 | $this->expectRequest($client); 36 | 37 | $this->label->shouldBe('High resolution TIFF images'); 38 | } 39 | 40 | public function it_has_files(AlmaClient $client) 41 | { 42 | $client->getJSON('/bibs/abc/representations/123/files') 43 | ->shouldBeCalled() 44 | ->willReturn(SpecHelper::getDummyData('files_response.json')); 45 | 46 | $files = $this->files; 47 | $files->shouldHaveCount(96); 48 | 49 | $files->rewind(); 50 | $files->valid()->shouldBe(true); 51 | $files->current()->shouldHaveType(File::class); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Users/Loan.php: -------------------------------------------------------------------------------- 1 | user = $user; 24 | $this->loan_id = $loan_id; 25 | } 26 | 27 | /** 28 | * Generate the base URL for this resource. 29 | * 30 | * @return string 31 | */ 32 | protected function urlBase() 33 | { 34 | return sprintf('/users/%s/loans/%s', rawurlencode($this->user->id), $this->loan_id); 35 | } 36 | 37 | /** 38 | * Check if we have the full representation of our data object. 39 | * 40 | * @param \stdClass $data 41 | * 42 | * @return bool 43 | */ 44 | protected function isInitialized($data) 45 | { 46 | return isset($data->loan_id); 47 | } 48 | 49 | /** 50 | * Get the Item on loan. Since the response from the loan(s) API does not 51 | * include the `holding_id` and `item_pid`, we cannot initiate an Item object 52 | * directly, so we have to lookup the barcode instead. 53 | * 54 | * @see https://developers.exlibrisgroup.com/discussions#!/forum/posts/list/1397.page 55 | * 56 | * @return Item 57 | */ 58 | public function getItem() 59 | { 60 | return $this->client->items->fromBarcode($this->item_barcode); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Model/PaginatedListGenerator.php: -------------------------------------------------------------------------------- 1 | position > 0) { 18 | throw new \Exception('Cannot rewind a generator that was already run'); 19 | } 20 | } 21 | 22 | /** 23 | * Checks if current position is valid. 24 | * 25 | * @link http://php.net/manual/en/iterator.valid.php 26 | */ 27 | public function valid(): bool 28 | { 29 | if (!isset($this->resources[0])) { 30 | $this->fetchBatch(); 31 | } 32 | 33 | return isset($this->resources[0]); 34 | } 35 | 36 | /** 37 | * Return the current element. 38 | * 39 | * @link http://php.net/manual/en/iterator.current.php 40 | */ 41 | public function current(): mixed 42 | { 43 | return array_shift($this->resources); 44 | } 45 | 46 | /** 47 | * Move forward to next element. 48 | * 49 | * @link http://php.net/manual/en/iterator.next.php 50 | */ 51 | public function next(): void 52 | { 53 | $this->position++; 54 | } 55 | 56 | /** 57 | * Return the key of the current element. 58 | * 59 | * @link http://php.net/manual/en/iterator.key.php 60 | * 61 | * @return int|null Scalar on success, or null on failure. 62 | */ 63 | public function key(): mixed 64 | { 65 | return $this->position; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Users/Fees.php: -------------------------------------------------------------------------------- 1 | user = $user; 35 | } 36 | 37 | /** 38 | * Get a single Fee by id. 39 | * 40 | * @param string $id 41 | * 42 | * @return Fee 43 | */ 44 | public function get($id) 45 | { 46 | return Fee::make($this->client, $this->user, $id); 47 | } 48 | 49 | /** 50 | * Convert a retrieved resource object to a model object. 51 | * 52 | * @param $data 53 | * 54 | * @return Fee 55 | */ 56 | public function convertToResource($data) 57 | { 58 | return $this->get($data->id)->init($data); 59 | } 60 | 61 | /** 62 | * Generate the base URL for this resource. 63 | * 64 | * @return string 65 | */ 66 | protected function urlBase() 67 | { 68 | return sprintf('/users/%s/fees', rawurlencode($this->user->id)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Bibs/Portfolios.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 32 | } 33 | 34 | /** 35 | * Get a single portfolio record by id. 36 | * 37 | * @param string $id 38 | * 39 | * @return Portfolio 40 | */ 41 | public function get($id) 42 | { 43 | return Portfolio::make($this->client, $this->bib, $id); 44 | } 45 | 46 | /** 47 | * Convert a data element to a resource object. 48 | * 49 | * @param $data 50 | * 51 | * @return Portfolio 52 | */ 53 | protected function convertToResource($data) 54 | { 55 | return Portfolio::make($this->client, $this->bib, $data->id) 56 | ->init($data); 57 | } 58 | 59 | /** 60 | * Generate the base URL for this resource. 61 | * 62 | * @return string 63 | */ 64 | protected function urlBase() 65 | { 66 | return "/bibs/{$this->bib->mms_id}/portfolios"; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Model/PaginatedList.php: -------------------------------------------------------------------------------- 1 | position = 0; 20 | } 21 | 22 | /** 23 | * Checks if current position is valid. 24 | * 25 | * @link http://php.net/manual/en/iterator.valid.php 26 | * 27 | * @return bool 28 | */ 29 | public function valid() 30 | { 31 | if (!isset($this->resources[$this->position])) { 32 | $this->fetchBatch(); 33 | } 34 | 35 | return isset($this->resources[$this->position]); 36 | } 37 | 38 | /** 39 | * Return the current element. 40 | * 41 | * @link http://php.net/manual/en/iterator.current.php 42 | * 43 | * @return mixed 44 | */ 45 | public function current() 46 | { 47 | return $this->resources[$this->position]; 48 | } 49 | 50 | /** 51 | * Move forward to next element. 52 | * 53 | * @link http://php.net/manual/en/iterator.next.php 54 | * 55 | * @return void 56 | */ 57 | public function next() 58 | { 59 | $this->position++; 60 | } 61 | 62 | /** 63 | * Return the key of the current element. 64 | * 65 | * @link http://php.net/manual/en/iterator.key.php 66 | * 67 | * @return int|null Scalar on success, or null on failure. 68 | */ 69 | public function key() 70 | { 71 | return $this->position; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /spec/SpecHelper.php: -------------------------------------------------------------------------------- 1 | getJSON(Argument::any(), Argument::any()) 32 | ->shouldNotBeCalled(); 33 | 34 | $client->getXML(Argument::any(), Argument::any()) 35 | ->shouldNotBeCalled(); 36 | } 37 | 38 | public static function makeExceptionResponse( 39 | $body, 40 | $code = 400, 41 | $contentType = 'application/json;charset=utf-8', 42 | $cls = ClientErrorException::class 43 | ) { 44 | $requestFactory = new RequestFactory(); 45 | $responseFactory = new ResponseFactory(); 46 | 47 | return new $cls( 48 | 'Error 400', 49 | $requestFactory->createRequest('GET', ''), 50 | $responseFactory->createResponse($code, 'Bad Request') 51 | ->withHeader('Content-Type', $contentType) 52 | ->withBody(stream_for($body)) 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Conf/JobInstances.php: -------------------------------------------------------------------------------- 1 | job = $job; 31 | } 32 | 33 | /** 34 | * Get a single Job Instance by its instance_id. 35 | * 36 | * @param string $instance_id 37 | * 38 | * @return JobInstance 39 | */ 40 | public function get(string $instance_id) 41 | { 42 | return JobInstance::make($this->client, $this->job, $instance_id); 43 | } 44 | 45 | /** 46 | * Convert a data element to a resource object. 47 | * 48 | * @param $data 49 | * 50 | * @return JobInstance 51 | */ 52 | protected function convertToResource($data) 53 | { 54 | return JobInstance::make($this->client, $this->job, $data->id) 55 | ->init($data); 56 | } 57 | 58 | /** 59 | * Generate the base URL for this resource. 60 | * 61 | * @return string 62 | */ 63 | protected function urlBase() 64 | { 65 | return "/conf/jobs/{$this->job->job_id}/instances"; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /spec/Bibs/HoldingSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = 'abc'; 18 | $holdings_id = '123'; 19 | $this->beConstructedWith($client, $bib, $holdings_id); 20 | } 21 | 22 | protected function expectRequest($client) 23 | { 24 | $client->getXML('/bibs/abc/holdings/123') 25 | ->shouldBeCalled() 26 | ->willReturn(SpecHelper::getDummyData('holding_response.xml')); 27 | } 28 | 29 | public function it_is_initializable() 30 | { 31 | $this->shouldHaveType(Holding::class); 32 | } 33 | 34 | public function it_fetches_record_data_when_needed(AlmaClient $client) 35 | { 36 | $this->expectRequest($client); 37 | 38 | $this->created_by->shouldBe('import'); 39 | $this->created_date->shouldBe('2015-11-05Z'); 40 | 41 | $this->record->shouldHaveType(Record::class); 42 | } 43 | 44 | public function it_has_items(AlmaClient $client) 45 | { 46 | $client->getJSON('/bibs/abc/holdings/123/items') 47 | ->shouldBeCalled() 48 | ->willReturn(SpecHelper::getDummyData('items_response.json')); 49 | 50 | $items = $this->items; 51 | $items->shouldHaveCount(9); 52 | 53 | $items->rewind(); 54 | $items->valid()->shouldBe(true); 55 | $items->current()->shouldHaveType(Item::class); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Bibs/Representations.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 32 | } 33 | 34 | /** 35 | * Get a single representation record by id. 36 | * 37 | * @param string $id 38 | * 39 | * @return Representation 40 | */ 41 | public function get($id) 42 | { 43 | return Representation::make($this->client, $this->bib, $id); 44 | } 45 | 46 | /** 47 | * Convert a data element to a resource object. 48 | * 49 | * @param $data 50 | * 51 | * @return Representation 52 | */ 53 | protected function convertToResource($data) 54 | { 55 | return Representation::make($this->client, $this->bib, $data->id) 56 | ->init($data); 57 | } 58 | 59 | /** 60 | * Generate the base URL for this resource. 61 | * 62 | * @return string 63 | */ 64 | protected function urlBase() 65 | { 66 | return "/bibs/{$this->bib->mms_id}/representations"; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /spec/data/e-collection_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "61173723710002204", 3 | "is_local": false, 4 | "public_name": "SpringerLink Books Complete", 5 | "public_name_override": "", 6 | "description": "", 7 | "internal_description": "", 8 | "library": { 9 | "value": "", 10 | "link": null 11 | }, 12 | "type": { 13 | "value": "0", 14 | "desc": "Selective package" 15 | }, 16 | "interface": { 17 | "name": "Springer Link", 18 | "vendor": null 19 | }, 20 | "process_type": { 21 | "value": "", 22 | "desc": null 23 | }, 24 | "access_type": { 25 | "value": "", 26 | "desc": null 27 | }, 28 | "po_line": { 29 | "value": "", 30 | "link": null 31 | }, 32 | "activation_date": "2010-09-17Z", 33 | "license": { 34 | "value": "070001039", 35 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/acq/licenses/070001039" 36 | }, 37 | "alternative_title": "", 38 | "source": "", 39 | "creator": "", 40 | "url": "http://link.springer.com", 41 | "url_override": "", 42 | "free": { 43 | "value": "", 44 | "desc": null 45 | }, 46 | "proxy_enabled": { 47 | "value": "", 48 | "desc": null 49 | }, 50 | "proxy": "", 51 | "language": { 52 | "value": "", 53 | "desc": null 54 | }, 55 | "category": "", 56 | "resource_metadata": { 57 | "mms_id": { 58 | "value": "999919849150002204", 59 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999919849150002204" 60 | }, 61 | "title": "SpringerLink Books Complete", 62 | "author": "", 63 | "issn": "" 64 | }, 65 | "portfolios": { 66 | "value": 55900, 67 | "link": null 68 | }, 69 | "authentication_note": "", 70 | "public_note": "", 71 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/electronic/e-collections/61173723710002204" 72 | } 73 | -------------------------------------------------------------------------------- /src/Bibs/ElectronicCollections.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 33 | } 34 | 35 | /** 36 | * Get a single electronic collection record by id. 37 | * 38 | * @param string $id 39 | * 40 | * @return Representation 41 | */ 42 | public function get($id) 43 | { 44 | return Collection::make($this->client, $id); 45 | } 46 | 47 | /** 48 | * Convert a data element to a resource object. 49 | * 50 | * @param $data 51 | * 52 | * @return Representation 53 | */ 54 | protected function convertToResource($data) 55 | { 56 | return Collection::make($this->client, $data->id) 57 | ->init($data); 58 | } 59 | 60 | /** 61 | * Generate the base URL for this resource. 62 | * 63 | * @return string 64 | */ 65 | protected function urlBase() 66 | { 67 | return "/bibs/{$this->bib->mms_id}/e-collections"; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Bibs/Holdings.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 35 | } 36 | 37 | /** 38 | * Get a single holding record by id. 39 | * 40 | * @param string $holding_id 41 | * 42 | * @return Holding 43 | */ 44 | public function get($holding_id) 45 | { 46 | return Holding::make($this->client, $this->bib, $holding_id); 47 | } 48 | 49 | /** 50 | * Convert a data element to a resource object. 51 | * 52 | * @param $data 53 | * 54 | * @return Holding 55 | */ 56 | protected function convertToResource($data) 57 | { 58 | return Holding::make($this->client, $this->bib, $data->holding_id) 59 | ->init($data); 60 | } 61 | 62 | /** 63 | * Generate the base URL for this resource. 64 | * 65 | * @return string 66 | */ 67 | protected function urlBase() 68 | { 69 | return "/bibs/{$this->bib->mms_id}/holdings"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Conf/Locations.php: -------------------------------------------------------------------------------- 1 | library = $library; 35 | } 36 | 37 | /** 38 | * Get a single location by its location code. 39 | * 40 | * @param string $code 41 | * 42 | * @return Location 43 | */ 44 | public function get($code) 45 | { 46 | return Location::make($this->client, $this->library, $code); 47 | } 48 | 49 | /** 50 | * Convert a data element to a resource object. 51 | * 52 | * @param $data 53 | * 54 | * @return Location 55 | */ 56 | protected function convertToResource($data) 57 | { 58 | return Location::make($this->client, $this->library, $data->code) 59 | ->init($data); 60 | } 61 | 62 | /** 63 | * Generate the base URL for this resource. 64 | * 65 | * @return string 66 | */ 67 | protected function urlBase() 68 | { 69 | return "/conf/libraries/{$this->library->code}/locations"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /spec/data/representations_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "representation": [ 3 | { 4 | "id": "12216823130002204", 5 | "label": "PDF", 6 | "active": { 7 | "value": "true", 8 | "desc": "Active" 9 | }, 10 | "author": null, 11 | "title": null, 12 | "volume": null, 13 | "issue": null, 14 | "date": null, 15 | "track": null, 16 | "number": null, 17 | "year": null, 18 | "repository": { 19 | "value": "Alma", 20 | "desc": null 21 | }, 22 | "files": { 23 | "value": null, 24 | "total_record_count": 1, 25 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999814378424702204/representations/12216823130002204/files" 26 | }, 27 | "is_remote": false, 28 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999814378424702204/representations/12216823130002204" 29 | }, 30 | { 31 | "id": "12216823140002204", 32 | "label": "High resolution TIFF images", 33 | "active": { 34 | "value": "true", 35 | "desc": "Active" 36 | }, 37 | "author": null, 38 | "title": null, 39 | "volume": null, 40 | "issue": null, 41 | "date": null, 42 | "track": null, 43 | "number": null, 44 | "year": null, 45 | "repository": { 46 | "value": "Alma", 47 | "desc": null 48 | }, 49 | "files": { 50 | "value": null, 51 | "total_record_count": 96, 52 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999814378424702204/representations/12216823140002204/files" 53 | }, 54 | "is_remote": false, 55 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999814378424702204/representations/12216823140002204" 56 | } 57 | ], 58 | "total_record_count": 2 59 | } 60 | -------------------------------------------------------------------------------- /config/alma.php: -------------------------------------------------------------------------------- 1 | env('ALMA_REGION', 'eu'), 11 | 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Institution zone settings 15 | |-------------------------------------------------------------------------- 16 | */ 17 | 'iz' => [ 18 | // API key for institution zone 19 | 'key' => env('ALMA_IZ_KEY'), 20 | 21 | // SRU URL for institution zone 22 | 'sru' => env('ALMA_IZ_SRU_URL'), 23 | 24 | // Entry point URL. This only needs to be specified if you use a proxy 25 | // or other non-standard entry point. 26 | 'entrypoint' => null, 27 | 28 | // Optional list of extra headers to send with each request. 29 | 'headers' => [ 30 | // 'x-proxy-auth' => 'custom proxy auth' 31 | ], 32 | ], 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Network zone settings 37 | |-------------------------------------------------------------------------- 38 | */ 39 | 'nz' => [ 40 | // API key for institution zone 41 | 'key' => env('ALMA_NZ_KEY'), 42 | 43 | // SRU URL for institution zone 44 | 'sru' => env('ALMA_NZ_SRU_URL'), 45 | 46 | // Entry point URL. This only needs to be specified if you use a proxy 47 | // or other non-standard entry point. 48 | 'entrypoint' => null, 49 | 50 | // Optional list of extra headers to send with each request. 51 | 'headers' => [ 52 | // 'x-proxy-auth' => 'custom proxy auth' 53 | ], 54 | ], 55 | ]; 56 | -------------------------------------------------------------------------------- /spec/data/holding_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "anies": [ 3 | "\n\n \n 00370nx a2200133ui 4500\n h449730-47bibsys_ubo\n 000631221-47bibsys_ubo\n ta\n 1511020l||||||||4 uu\n kat\n \n 000631221\n (NO-TrBIB)\n \n \n 00kj16342-47bibsys_ubo\n \n \n 1030300\n k00040\n Ung 839.82 Vol:Ban\n \n \n 1030300\n k00040\n Ung 839.82 Vol:Ban\n 0\n 0\n 2015-11-02\n \n \n 00kj16342\n 20000529\n kat\n 303011kj0\n 00kj16342\n BOOK\n \n \n" 4 | ], 5 | "created_by": "import", 6 | "created_date": "2015-11-05Z", 7 | "holding_id": "22163771200002204", 8 | "originating_system": "ILS", 9 | "originating_system_id": "h449730-47bibsys_ubo", 10 | "suppress_from_publishing": "false" 11 | } -------------------------------------------------------------------------------- /src/Bibs/Files.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 43 | $this->representation = $representation; 44 | } 45 | 46 | /** 47 | * Convert a data element to a resource object. 48 | * 49 | * @param $data 50 | * 51 | * @return File 52 | */ 53 | protected function convertToResource($data) 54 | { 55 | return File::make($this->client, $this->bib, $this->representation, $data->pid) 56 | ->init($data); 57 | } 58 | 59 | /** 60 | * Generate the base URL for this resource. 61 | * 62 | * @return string 63 | */ 64 | protected function urlBase() 65 | { 66 | return "/bibs/{$this->bib->mms_id}/representations/{$this->representation->representation_id}/files"; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/TaskLists/LendingRequests.php: -------------------------------------------------------------------------------- 1 | library = $library; 35 | $params['library'] = $library->code; 36 | $this->params = $params; 37 | } 38 | 39 | /** 40 | * Generate the base URL for this resource. 41 | * 42 | * @return string 43 | */ 44 | protected function urlBase() 45 | { 46 | return '/task-lists/rs/lending-requests'; 47 | } 48 | 49 | /** 50 | * Convert a data element to a resource object. 51 | * 52 | * @param $data 53 | * 54 | * @return mixed 55 | */ 56 | protected function convertToResource($data) 57 | { 58 | return ResourceSharingRequest::make($this->client, $data->request_id) 59 | ->init($data); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scriptotek/alma-client", 3 | "type": "library", 4 | "description": "Package for interacting with some of the Alma APIs", 5 | "homepage": "http://github.com/scriptotek/php-alma-client", 6 | "keywords": ["alma"], 7 | "minimum-stability": "dev", 8 | "prefer-stable": true, 9 | "require": { 10 | "php" : ">=7.1", 11 | "ext-json": "*", 12 | "scriptotek/marc": "^2.0", 13 | "danmichaelo/quitesimplexmlelement": "^1.0", 14 | "scriptotek/sru-client": "^0.7.1", 15 | "psr/http-message": "^1.0", 16 | "psr/http-client-implementation": "^1.0", 17 | "psr/http-factory-implementation": "^1.0", 18 | "http-interop/http-factory-discovery": "^1.4", 19 | "php-http/client-common": "^1.9 || ^2.0", 20 | "symfony/polyfill-php73": "^1.11", 21 | "friends-of-phpspec/phpspec-expect": "^4.0" 22 | }, 23 | "require-dev": { 24 | "phpspec/phpspec": "^5.0 || ^6.0 || ^7.0", 25 | "wp-cli/php-cli-tools": "^0.11.1", 26 | "php-http/mock-client": "^1.0", 27 | "php-http/guzzle6-adapter": "^1.1 || ^2.0", 28 | "http-interop/http-factory-guzzle": "^1.0", 29 | "squizlabs/php_codesniffer": "^3.3" 30 | }, 31 | "license": "MIT", 32 | "authors": [ 33 | { 34 | "name": "Dan Michael O. Heggø", 35 | "email": "danmichaelo@gmail.com" 36 | } 37 | ], 38 | "autoload": { 39 | "psr-4": { 40 | "Scriptotek\\Alma\\": "src/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "spec\\Scriptotek\\Alma\\": "spec/" 46 | } 47 | }, 48 | "extra": { 49 | "laravel": { 50 | "providers": [ 51 | "Scriptotek\\Alma\\Laravel\\ServiceProvider" 52 | ], 53 | "aliases": { 54 | "Alma": "Scriptotek\\Alma\\Laravel\\Facade" 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Analytics/Row.php: -------------------------------------------------------------------------------- 1 | headers = $headers; 19 | foreach ($headers as $idx => $header) { 20 | $value = $data->text('rowset:Column' . ($idx + 1)) ?: null; 21 | $this->byIndex[$idx] = $value; 22 | $this->byHeader[$header] = $value; 23 | } 24 | } 25 | 26 | public function __get($name) 27 | { 28 | return $this->byHeader[$name]; 29 | } 30 | 31 | public function offsetSet($offset, $value): void 32 | { 33 | throw new \RuntimeException('Sorry, column values cannot be modified.'); 34 | } 35 | 36 | public function offsetExists($offset): bool 37 | { 38 | return isset($this->byIndex[$offset]) || isset($this->byHeader[$offset]); 39 | } 40 | 41 | public function offsetUnset($offset): void 42 | { 43 | throw new \RuntimeException('Sorry, column values cannot be modified.'); 44 | } 45 | 46 | public function offsetGet($offset): mixed 47 | { 48 | if (isset($this->byIndex[$offset])) { 49 | return $this->byIndex[$offset]; 50 | } 51 | if (isset($this->byHeader[$offset])) { 52 | return $this->byHeader[$offset]; 53 | } 54 | 55 | return null; 56 | } 57 | 58 | public function getIterator(): \Traversable 59 | { 60 | return new \ArrayIterator($this->byHeader); 61 | } 62 | 63 | public function count(): int 64 | { 65 | return count($this->byIndex); 66 | } 67 | 68 | public function toArray(): mixed 69 | { 70 | return $this->byHeader; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spec/data/holding_response.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22163771200002204 4 | import 5 | 2015-11-05Z 6 | System 7 | 2018-11-09Z 8 | ILS 9 | h449730-47bibsys_ubo 10 | false 11 | 12 | 00384nx a2200145ui 4500 13 | h449730-47bibsys_ubo 14 | 000631221-47bibsys_ubo 15 | ta 16 | 1511020l||||||||4 uu 17 | kat 18 | 20181109134730.0 19 | 20 | 000631221 21 | (NO-TrBIB) 22 | 23 | 24 | 00kj16342-47bibsys_ubo 25 | 26 | 27 | 1030300 28 | k00041 29 | 839.82 30 | Vol:Ban 31 | 32 | 33 | 00kj16342 34 | 20000529 35 | kat 36 | 303011kj0 37 | 00kj16342 38 | BOOK 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Bibs/ScanInResponse.php: -------------------------------------------------------------------------------- 1 | data->additional_info; 33 | } 34 | 35 | /** 36 | * Get the scanned-in item. 37 | * 38 | * @return Item 39 | */ 40 | public function getItem() 41 | { 42 | $bib = Bib::make($this->client, $this->data->bib_data->mms_id); 43 | $holding = Holding::make($this->client, $bib, $this->data->holding_data->holding_id); 44 | 45 | return Item::make( 46 | $this->client, 47 | $bib, 48 | $holding, 49 | $this->data->item_data->pid 50 | )->init($this->data->item_data); 51 | } 52 | 53 | /** 54 | * Get the Holding object for the scanned-in item. 55 | * 56 | * @return Holding 57 | */ 58 | public function getHolding() 59 | { 60 | $bib = Bib::make($this->client, $this->data->bib_data->mms_id); 61 | 62 | return Holding::make( 63 | $this->client, 64 | $bib, 65 | $this->data->holding_data->holding_id 66 | )->init($this->data->holding_data); 67 | } 68 | 69 | /** 70 | * Get the Bib object for the scanned-in item. 71 | * 72 | * @return Bib 73 | */ 74 | public function getBib() 75 | { 76 | return Bib::make( 77 | $this->client, 78 | $this->data->bib_data->mms_id 79 | )->init($this->data->bib_data); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /spec/Bibs/ItemsSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, $bib, $holding); 20 | $client->sru = $sru; 21 | } 22 | 23 | public function it_is_initializable(AlmaClient $client) 24 | { 25 | $this->beConstructedWith($client); 26 | $this->shouldHaveType(Items::class); 27 | } 28 | 29 | public function it_returns_a_lazy_loaded_item_object_given_a_barcode(AlmaClient $client) 30 | { 31 | $client->getRedirectLocation('/items', Argument::containing('303011kj0')) 32 | ->shouldBeCalled() 33 | ->willReturn('https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204'); 34 | 35 | $item = $this->fromBarcode('303011kj0'); 36 | $item->shouldHaveType(Item::class); 37 | } 38 | 39 | public function it_returns_an_item_object_given_a_barcode(AlmaClient $client) 40 | { 41 | $client->getRedirectLocation('/items', Argument::containing('303011kj0')) 42 | ->shouldBeCalled() 43 | ->willReturn('https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204'); 44 | 45 | $client->getJSON('/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204') 46 | ->shouldBeCalled() 47 | ->willReturn(SpecHelper::getDummyData('item_response.json')); 48 | 49 | $item = $this->fromBarcode('303011kj0'); 50 | $item->shouldHaveType(Item::class); 51 | $item->pid->shouldBe('23163771190002204'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spec/Users/UsersSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client); 17 | } 18 | 19 | public function it_is_initializable() 20 | { 21 | $this->shouldHaveType(Users::class); 22 | } 23 | 24 | public function it_provides_lazy_lookup_by_id(AlmaClient $client) 25 | { 26 | $client->getJSON('/users/12345', []) 27 | ->shouldNotBeCalled(); 28 | 29 | $user = $this->get('12345'); 30 | $user->shouldHaveType(User::class); 31 | } 32 | 33 | public function it_accepts_additional_parameters(AlmaClient $client) 34 | { 35 | $client->getJSON('/users/12345?expand=fees') 36 | ->shouldBeCalled() 37 | ->willReturn(SpecHelper::getDummyData('user_response.json')); 38 | 39 | $this->get('12345', ['expand' => 'fees'])->init(); 40 | } 41 | 42 | public function it_provides_lookup_by_id(AlmaClient $client) 43 | { 44 | $client->getJSON('/users/12345') 45 | ->shouldBeCalled() 46 | ->willReturn(SpecHelper::getDummyData('user_response.json')); 47 | 48 | $user = $this->get('12345'); 49 | 50 | $user->shouldHaveType(User::class); 51 | $user->primary_id->shouldBe('12345'); 52 | $user->primaryId->shouldBe('12345'); 53 | } 54 | 55 | public function it_provides_search(AlmaClient $client) 56 | { 57 | $client->getJSON(Argument::containingString('users'), Argument::any()) 58 | ->willReturn(SpecHelper::getDummyData('users_response.json')); 59 | 60 | $users = $this->search('last_name~banan'); 61 | $first = $users->current(); 62 | 63 | $first->shouldHaveType(User::class); 64 | $first->primary_id->shouldBe('1234567'); 65 | $first->primaryId->shouldBe('1234567'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /spec/data/portfolio_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "53182555270002204", 3 | "is_local": false, 4 | "is_standalone": false, 5 | "resource_metadata": { 6 | "mms_id": { 7 | "value": "999919790879802204", 8 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999919790879802204" 9 | }, 10 | "title": "Elementary Mechanics Using Matlab A Modern Course Combining Analytical and Numerical Techniques /" 11 | }, 12 | "electronic_collection": { 13 | "id": { 14 | "value": "61173723710002204", 15 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/electronic/e-collections/61173723710002204" 16 | }, 17 | "service": { 18 | "value": "62173726760002204", 19 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/electronic/e-collections/61173723710002204/e-services/62173726760002204" 20 | } 21 | }, 22 | "availability": { 23 | "value": "11", 24 | "desc": "Available" 25 | }, 26 | "material_type": { 27 | "value": "BOOK", 28 | "desc": "Book" 29 | }, 30 | "library": { 31 | "value": "", 32 | "link": null 33 | }, 34 | "linking_details": { 35 | "url": "", 36 | "url_type": { 37 | "value": "", 38 | "desc": null 39 | }, 40 | "dynamic_url": "", 41 | "static_url": "", 42 | "parser_parameters": "", 43 | "parser_parameters_override": "", 44 | "proxy_enabled": { 45 | "value": "false", 46 | "desc": "No" 47 | }, 48 | "proxy": "" 49 | }, 50 | "coverage_details": { 51 | "coverage_in_use": { 52 | "value": "0", 53 | "desc": "Only local" 54 | }, 55 | "global_date_coverage_parameters": [], 56 | "local_date_coverage_parameters": [] 57 | }, 58 | "po_line": { 59 | "value": "", 60 | "link": null 61 | }, 62 | "license": { 63 | "value": "070001039", 64 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/acq/licenses/070001039" 65 | }, 66 | "pda": { 67 | "value": "", 68 | "link": "" 69 | }, 70 | "authentication_note": "", 71 | "public_note": "", 72 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999919790879802204/portfolios/53182555270002204" 73 | } 74 | -------------------------------------------------------------------------------- /spec/Analytics/RowSpec.php: -------------------------------------------------------------------------------- 1 | 17 | 0 18 | col1 content 19 | col3 content 20 | '); 21 | $xml->registerXPathNamespace('rowset', 'urn:schemas-microsoft-com:xml-analysis:rowset'); 22 | $headers = ['mms_id', 'title', 'isbn']; 23 | $this->beConstructedWith($xml, $headers); 24 | } 25 | 26 | public function it_is_initializable() 27 | { 28 | $this->shouldHaveType(Row::class); 29 | } 30 | 31 | public function it_should_have_columns_accessible_by_name() 32 | { 33 | $this->mms_id->shouldBe('col1 content'); 34 | $this->title->shouldBe(null); 35 | $this->isbn->shouldBe('col3 content'); 36 | } 37 | 38 | public function it_should_have_columns_accessible_by_array_key() 39 | { 40 | $this['mms_id']->shouldBe('col1 content'); 41 | $this['title']->shouldBe(null); 42 | $this['isbn']->shouldBe('col3 content'); 43 | } 44 | 45 | public function it_should_have_columns_accessible_by_array_index() 46 | { 47 | $this[0]->shouldBe('col1 content'); 48 | $this[1]->shouldBe(null); 49 | $this[2]->shouldBe('col3 content'); 50 | } 51 | 52 | public function it_should_be_traversable() 53 | { 54 | $this->shouldBeAnInstanceOf('Traversable'); 55 | } 56 | 57 | public function it_should_be_countable() 58 | { 59 | $this->shouldHaveCount(3); 60 | } 61 | 62 | public function it_should_be_serializable_as_array() 63 | { 64 | $this->toArray()->shouldBeArray(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Model/SimplePaginatedList.php: -------------------------------------------------------------------------------- 1 | totalRecordCount) && $this->offset >= $this->totalRecordCount) { 24 | return; 25 | } 26 | 27 | $url = $this->url('', [ 28 | 'offset' => $this->offset, 29 | 'limit' => $this->limit, 30 | ]); 31 | 32 | $response = $this->client->getJSON($url); 33 | 34 | if (is_null($response)) { 35 | throw new \RuntimeException("Empty response from URL: $url"); 36 | } 37 | 38 | return $this->onData($response); 39 | } 40 | 41 | protected function fetchData() 42 | { 43 | do { 44 | $this->fetchBatch(); 45 | } while (!$this->isInitialized($this->resources)); 46 | } 47 | 48 | protected function onData($data) 49 | { 50 | $before = count($this->resources); 51 | parent::onData($data); 52 | $after = count($this->resources); 53 | $this->offset += $after - $before; 54 | } 55 | 56 | /** 57 | * Check if we have the full representation of our data object. 58 | * 59 | * @param \stdClass $data 60 | * 61 | * @return bool 62 | */ 63 | protected function isInitialized($data) 64 | { 65 | if (is_countable($data)) { 66 | return count($data) === $this->totalRecordCount; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | /** 73 | * Total number of resources. 74 | * 75 | * @link http://php.net/manual/en/countable.count.php 76 | * 77 | * @return int 78 | */ 79 | public function count() 80 | { 81 | if (is_null($this->totalRecordCount)) { 82 | $this->fetchBatch(); 83 | } 84 | 85 | return $this->totalRecordCount; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /spec/data/loans_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "item_loan": [ 3 | { 4 | "author": "Polkinghorne, John", 5 | "circ_desk": { 6 | "desc": null, 7 | "value": null 8 | }, 9 | "description": null, 10 | "due_date": "2018-08-14T20:00:00Z", 11 | "item_barcode": "025986na0", 12 | "item_policy": { 13 | "description": null, 14 | "value": null 15 | }, 16 | "library": { 17 | "desc": "UiO Realfagsbiblioteket", 18 | "value": "1030310" 19 | }, 20 | "loan_date": "2013-11-18T07:00:00Z", 21 | "loan_id": "1822301570002204", 22 | "loan_status": "ACTIVE", 23 | "location_code": { 24 | "name": "UREAL Samling 42", 25 | "value": "k00475" 26 | }, 27 | "mms_id": "998010107304702204", 28 | "process_status": "RENEW", 29 | "renewable": null, 30 | "title": "The particle play : an account of the ultimate constituents of matter J.C. Polkinghorne", 31 | "user_id": "123435" 32 | }, 33 | { 34 | "author": "Vollset, K.", 35 | "call_number": "Ung 839.82 Vol:Ban", 36 | "circ_desk": { 37 | "desc": "Utlånet Ureal", 38 | "value": "DEFAULT_CIRC_DESK" 39 | }, 40 | "description": null, 41 | "due_date": "2018-08-20T20:00:00Z", 42 | "item_barcode": "303011kj0", 43 | "item_policy": { 44 | "description": null, 45 | "value": null 46 | }, 47 | "library": { 48 | "desc": "UiO HumSam-biblioteket", 49 | "value": "1030300" 50 | }, 51 | "loan_date": "2018-07-23T20:25:26.347Z", 52 | "loan_id": "7378239410002204", 53 | "loan_status": "ACTIVE", 54 | "location_code": { 55 | "name": "UHS S-Litt", 56 | "value": "k00040" 57 | }, 58 | "mms_id": "990006312214702204", 59 | "process_status": "NORMAL", 60 | "publication_year": "2000", 61 | "renewable": null, 62 | "title": "Bananboken K. Vollset ; [illustrasjoner: Motorfinger og Jim]", 63 | "user_id": "123435" 64 | } 65 | ], 66 | "total_record_count": 2 67 | } -------------------------------------------------------------------------------- /spec/Bibs/HoldingsSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = 'abc'; 17 | $this->beConstructedWith($client, $bib); 18 | } 19 | 20 | public function it_is_initializable() 21 | { 22 | $this->shouldHaveType(Holdings::class); 23 | } 24 | 25 | protected function expectRequest($client) 26 | { 27 | $client->getJSON('/bibs/abc/holdings') 28 | ->shouldBeCalled() 29 | ->willReturn(SpecHelper::getDummyData('holdings_response.json')); 30 | } 31 | 32 | public function it_provides_a_lazy_interface_to_holding_objects(AlmaClient $client, Bib $bib) 33 | { 34 | SpecHelper::expectNoRequests($client); 35 | 36 | $holding_id = '12345'; // str_random(); 37 | $holding = $this->get($holding_id); 38 | 39 | $holding->shouldHaveType(Holding::class); 40 | $holding->bib->shouldBe($bib); 41 | $holding->holding_id->shouldBe($holding_id); 42 | } 43 | 44 | public function it_provides_a_lazy_array_interface_to_holding_objects(AlmaClient $client, Bib $bib) 45 | { 46 | SpecHelper::expectNoRequests($client); 47 | 48 | $holding_id = '90123'; // str_random(); 49 | $holding = $this[$holding_id]; 50 | 51 | $holding->shouldHaveType(Holding::class); 52 | $holding->bib->shouldBe($bib); 53 | $holding->holding_id->shouldBe($holding_id); 54 | } 55 | 56 | public function it_is_countable(AlmaClient $client) 57 | { 58 | $this->expectRequest($client); 59 | 60 | $this->shouldHaveCount(2); 61 | } 62 | 63 | public function it_provides_an_iterator_interface_to_holding_objects(AlmaClient $client) 64 | { 65 | $this->expectRequest($client); 66 | 67 | $this->shouldImplement('Iterator'); 68 | 69 | $this->current()->shouldHaveType(Holding::class); 70 | } 71 | 72 | public function it_provides_basic_data_without_loading_the_full_record(AlmaClient $client) 73 | { 74 | $this->expectRequest($client); 75 | 76 | $this->shouldHaveCount(2); 77 | $this->all()[0]->shouldHaveType(Holding::class); 78 | $this->all()[0]->library->desc->shouldBe('BURNS'); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Bibs/Holding.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 31 | $this->holding_id = $holding_id; 32 | $this->items = Items::make($this->client, $bib, $this); 33 | } 34 | 35 | /** 36 | * Get the model data. 37 | */ 38 | protected function fetchData() 39 | { 40 | return $this->client->getXML($this->url()); 41 | } 42 | 43 | /** 44 | * Check if we have the full representation of our data object. 45 | * 46 | * @param $data 47 | * 48 | * @return bool 49 | */ 50 | protected function isInitialized($data) 51 | { 52 | return is_a($data, QuiteSimpleXMLElement::class) && $data->has('record'); 53 | } 54 | 55 | /** 56 | * Update the MARC record on this holding object. Chainable method. 57 | * 58 | * @param string $xml 59 | * 60 | * @return Holding 61 | */ 62 | public function setMarcRecord($xml) 63 | { 64 | $this->_marc = MarcRecord::fromString($xml); 65 | 66 | return $this; 67 | } 68 | 69 | /** 70 | * Called when data is available to be processed. 71 | * 72 | * @param mixed $data 73 | */ 74 | protected function onData($data) 75 | { 76 | $this->setMarcRecord($data->first('record')->asXML()); 77 | } 78 | 79 | /** 80 | * Generate the base URL for this resource. 81 | * 82 | * @return string 83 | */ 84 | protected function urlBase() 85 | { 86 | return "/bibs/{$this->bib->mms_id}/holdings/{$this->holding_id}"; 87 | } 88 | 89 | /** 90 | * Get the MARC record for this holding object. 91 | */ 92 | public function getRecord() 93 | { 94 | return $this->init()->_marc; 95 | } 96 | 97 | /** 98 | * Get the items for this holding. 99 | */ 100 | public function getItems() 101 | { 102 | return $this->items; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /spec/Users/UserSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, '12345'); 18 | 19 | $client->getJSON('/users/12345') 20 | ->willReturn(SpecHelper::getDummyData('user_response.json')); 21 | } 22 | 23 | public function it_is_initializable() 24 | { 25 | $this->shouldHaveType(User::class); 26 | } 27 | 28 | public function it_has_primary_id() 29 | { 30 | $this->primaryId->shouldBe('12345'); 31 | } 32 | 33 | public function it_has_barcode() 34 | { 35 | $this->barcode->shouldBe('ub54321'); 36 | } 37 | 38 | public function it_has_barcodes() 39 | { 40 | $this->barcodes->shouldBe(['ub54321', 'ntb12897787']); 41 | } 42 | 43 | public function it_has_university_id() 44 | { 45 | $this->universityId->shouldBe('test@uio.no'); 46 | } 47 | 48 | public function it_has_university_ids() 49 | { 50 | $this->universityIds->shouldBe(['test@uio.no']); 51 | } 52 | 53 | public function it_has_identifiers() 54 | { 55 | $this->identifiers->all()->shouldBe(['12345', 'ub54321', 'ntb12897787', 'test@uio.no']); 56 | } 57 | 58 | public function it_has_loans(AlmaClient $client) 59 | { 60 | SpecHelper::expectNoRequests($client); 61 | $this->loans->shouldHaveType(Loans::class); 62 | } 63 | 64 | public function it_has_fees(AlmaClient $client) 65 | { 66 | SpecHelper::expectNoRequests($client); 67 | $this->fees->shouldHaveType(Fees::class); 68 | } 69 | 70 | public function it_has_requests() 71 | { 72 | $this->requests->shouldHaveType(Requests::class); 73 | } 74 | 75 | public function it_has_sms() 76 | { 77 | $this->getSmsNumber()->shouldBe('87654321'); 78 | } 79 | 80 | public function it_can_change_sms() 81 | { 82 | $this->setSmsNumber('12345678'); 83 | $this->getSmsNumber()->shouldBe('12345678'); 84 | } 85 | 86 | public function it_can_add_sms() 87 | { 88 | $this->setSmsNumber('9999999'); 89 | $this->getSmsNumber()->shouldBe('9999999'); 90 | } 91 | 92 | public function it_can_remove_sms() 93 | { 94 | $this->unsetSmsNumber(); 95 | $this->getSmsNumber()->shouldBe(null); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Model/LazyResourceList.php: -------------------------------------------------------------------------------- 1 | responseKey = $responseKey; 36 | } 37 | 38 | /** 39 | * Convert a data element to a resource object. 40 | * 41 | * @param $data 42 | * 43 | * @return mixed 44 | */ 45 | abstract protected function convertToResource($data); 46 | 47 | /** 48 | * Called when data is available on the object. 49 | * The resource classes can use this method to process the data. 50 | * 51 | * @param $data 52 | */ 53 | protected function onData($data) 54 | { 55 | if (is_null($this->totalRecordCount)) { 56 | $this->totalRecordCount = $data->total_record_count; 57 | } 58 | 59 | if (!isset($data->{$this->responseKey})) { 60 | return; 61 | } 62 | 63 | foreach ($data->{$this->responseKey} as $result) { 64 | $this->resources[] = $this->convertToResource($result); 65 | } 66 | } 67 | 68 | /** 69 | * Check if we have the full representation of our data object. 70 | * 71 | * @param \stdClass $data 72 | * 73 | * @return bool 74 | */ 75 | protected function isInitialized($data) 76 | { 77 | return isset($data->total_record_count); 78 | } 79 | 80 | /** 81 | * Get all the resources. 82 | * 83 | * @return array 84 | */ 85 | public function all() 86 | { 87 | return $this->init()->resources; 88 | } 89 | 90 | /** 91 | * Number of resources. 92 | * 93 | * @link http://php.net/manual/en/countable.count.php 94 | */ 95 | public function count(): int 96 | { 97 | return count($this->all()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Model/Model.php: -------------------------------------------------------------------------------- 1 | client = $client; 22 | $this->data = $data; 23 | } 24 | 25 | /** 26 | * @param Client $client 27 | * @param array ...$params 28 | * 29 | * @return static 30 | */ 31 | public static function make($client, ...$params) 32 | { 33 | return new static($client, ...$params); 34 | } 35 | 36 | /** 37 | * Load data onto this object. Chainable method. 38 | * 39 | * @param \stdClass|QuiteSimpleXMLElement $data 40 | * 41 | * @return self 42 | */ 43 | public function init($data = null) 44 | { 45 | if (!is_null($data)) { 46 | $this->data = $data; 47 | } 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * Get the raw data object. 54 | */ 55 | public function getData() 56 | { 57 | return $this->data; 58 | } 59 | 60 | /** 61 | * Magic! 62 | * 63 | * @param string $key 64 | * 65 | * @return mixed 66 | */ 67 | public function __get($key) 68 | { 69 | // Convert electronic_collections to ElectronicCollections 70 | $key_s = implode('', array_map(function ($x) { 71 | return ucfirst($x); 72 | }, explode('_', $key))); 73 | 74 | // If there's a getter method, call it. 75 | $method = 'get' . ucfirst($key_s); 76 | if (method_exists($this, $method)) { 77 | return $this->$method(); 78 | } 79 | 80 | // If the property is already defined on our data object, return it. 81 | if (isset($this->data->{$key})) { 82 | return $this->data->{$key}; 83 | } 84 | 85 | $this->init(); 86 | 87 | // If data comes from an XML response (Bib or Holding record) 88 | if (is_a($this->data, QuiteSimpleXMLElement::class)) { 89 | return $this->data->text($key); 90 | } 91 | 92 | // If the property is defined on our data object now, return it. 93 | if (isset($this->data->{$key})) { 94 | return $this->data->{$key}; 95 | } 96 | } 97 | 98 | public function jsonSerialize(): mixed 99 | { 100 | return $this->data; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Bibs/Items.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 43 | $this->holding = $holding; 44 | } 45 | 46 | /** 47 | * Convert a data element to a resource object. 48 | * 49 | * @param $data 50 | * 51 | * @return Item 52 | */ 53 | protected function convertToResource($data) 54 | { 55 | return Item::make($this->client, $this->bib, $this->holding, $data->item_data->pid) 56 | ->init($data); 57 | } 58 | 59 | /** 60 | * Generate the base URL for this resource. 61 | * 62 | * @return string 63 | */ 64 | protected function urlBase() 65 | { 66 | return "/bibs/{$this->bib->mms_id}/holdings/{$this->holding->holding_id}/items"; 67 | } 68 | 69 | /** 70 | * Get an Item object from a barcode. 71 | * 72 | * @param string $barcode 73 | * 74 | * @return Item|null 75 | */ 76 | public function fromBarcode($barcode) 77 | { 78 | $destinationUrl = $this->client->getRedirectLocation('/items', ['item_barcode' => $barcode]); 79 | 80 | // Extract the MMS ID from the redirect target URL. 81 | // Example: https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999211285764702204/holdings/22156746440002204/items/23156746430002204 82 | if (!is_null($destinationUrl) && preg_match('$bibs/([0-9]+)/holdings/([0-9]+)/items/([0-9]+)$', $destinationUrl, $matches)) { 83 | $mms_id = $matches[1]; 84 | $holding_id = $matches[2]; 85 | $item_id = $matches[3]; 86 | 87 | $bib = Bib::make($this->client, $mms_id); 88 | $holding = Holding::make($this->client, $bib, $holding_id); 89 | 90 | return Item::make($this->client, $bib, $holding, $item_id); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Users/UserIdentifiers.php: -------------------------------------------------------------------------------- 1 | data->primary_id]; 22 | foreach ($this->data->user_identifier as $identifier) { 23 | if (is_null($status) || $identifier->status == $status) { 24 | $ids[] = $identifier->value; 25 | } 26 | } 27 | 28 | return $ids; 29 | } 30 | 31 | /** 32 | * Get all active user identifiers of a given type, like 'BARCODE' or 'UNIV_ID'. 33 | * 34 | * @param string $value 35 | * @param string $status 36 | * 37 | * @return array 38 | */ 39 | public function allOfType($value, $status = 'ACTIVE') 40 | { 41 | $ids = []; 42 | foreach ($this->data->user_identifier as $identifier) { 43 | if ($identifier->id_type->value == $value && (is_null($status) || $identifier->status == $status)) { 44 | $ids[] = $identifier->value; 45 | } 46 | } 47 | 48 | return $ids; 49 | } 50 | 51 | /** 52 | * Get the first active user identifier of a given type, like 'BARCODE' or 'UNIV_ID'. 53 | * 54 | * @param string $value 55 | * @param string $status 56 | * 57 | * @return null|string 58 | */ 59 | public function firstOfType($value, $status = 'ACTIVE') 60 | { 61 | foreach ($this->data->user_identifier as $identifier) { 62 | if ($identifier->id_type->value == $value && (is_null($status) || $identifier->status == $status)) { 63 | return $identifier->value; 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * Get the first active barcode. 70 | * 71 | * @return null|string 72 | */ 73 | public function getBarcode() 74 | { 75 | return $this->firstOfType('BARCODE'); 76 | } 77 | 78 | /** 79 | * Get all active barcodes. 80 | * 81 | * @return string[] 82 | */ 83 | public function getBarcodes() 84 | { 85 | return $this->allOfType('BARCODE'); 86 | } 87 | 88 | /** 89 | * Get the first active university id. 90 | * 91 | * @return null|string 92 | */ 93 | public function getUniversityId() 94 | { 95 | return $this->firstOfType('UNIV_ID'); 96 | } 97 | 98 | /** 99 | * Get all active university ids. 100 | * 101 | * @return string[] 102 | */ 103 | public function getUniversityIds() 104 | { 105 | return $this->allOfType('UNIV_ID'); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /spec/data/requested-resources_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "requested_resource": [ 3 | { 4 | "location": { 5 | "call_number": "2011 TAN", 6 | "copy": [ 7 | { 8 | "alternative_call_number": "", 9 | "barcode": "049313na0", 10 | "base_status": { 11 | "desc": "Item in place", 12 | "value": "1" 13 | }, 14 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/991120800814702204/holdings/22166970490002204/items/23166970440002204", 15 | "pid": "23166970440002204", 16 | "storage_location_id": "" 17 | } 18 | ], 19 | "holding_id": { 20 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/991120800814702204/holdings/22166970490002204", 21 | "value": "22166970490002204" 22 | }, 23 | "library": { 24 | "desc": "UiO Realfagsbiblioteket", 25 | "value": "1030310" 26 | }, 27 | "shelving_location": "k00464" 28 | }, 29 | "request": [ 30 | { 31 | "destination": { 32 | "desc": "UiO Realfagsbiblioteket", 33 | "value": "1030310" 34 | }, 35 | "id": "7989649590002204", 36 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/991120800814702204/requests/7989344220002204", 37 | "printed": false, 38 | "reported": false, 39 | "request_date": "2018-10-10Z", 40 | "request_sub_type": { 41 | "desc": "Patron physical item request", 42 | "value": "PATRON_PHYSICAL" 43 | }, 44 | "request_type": "HOLD", 45 | "requester": { 46 | "desc": "Test User", 47 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/users/test-user", 48 | "value": null 49 | } 50 | } 51 | ], 52 | "resource_metadata": { 53 | "author": "Tanveer, Omar", 54 | "isbn": null, 55 | "issn": null, 56 | "mms_id": { 57 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/991120800814702204", 58 | "value": "991120800814702204" 59 | }, 60 | "publication_place": "Oslo", 61 | "publication_year": "2011", 62 | "publisher": "O Tanveer", 63 | "title": "Complexity assessment within IT portfolios Omar Tanveer" 64 | } 65 | } 66 | ], 67 | "total_record_count": 1 68 | } 69 | -------------------------------------------------------------------------------- /spec/Analytics/ReportSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($almaClient, '/test/path'); 17 | } 18 | 19 | public function it_is_initializable() 20 | { 21 | $this->shouldHaveType(Report::class); 22 | } 23 | 24 | public function it_supports_setting_headers(Client $almaClient) 25 | { 26 | $this->beConstructedWith($almaClient, '/test/path', ['a', 'b']); 27 | 28 | $this->headers->shouldBe(['a', 'b']); 29 | } 30 | 31 | public function it_supports_setting_filter(Client $almaClient) 32 | { 33 | $this->beConstructedWith($almaClient, '/test/path', ['a', 'b'], 'la la la'); 34 | 35 | $this->filter->shouldBe('la la la'); 36 | } 37 | 38 | public function it_parses_column_headers(Client $almaClient) 39 | { 40 | $almaClient->getXML('/analytics/reports?path=%2Ftest%2Fpath&limit=1000') 41 | ->shouldBeCalledTimes(1) 42 | ->willReturn(SpecHelper::getDummyData('analytics_response.xml')); 43 | 44 | // $this->getHeaders()->shouldHaveCount(11); 45 | $this->getHeaders()->shouldContain('Event Start Date and Time'); 46 | 47 | $firstRow = $this->current(); 48 | $firstRow->shouldHaveType(Row::class); 49 | $firstRow['Event Start Date and Time']->shouldBe('2017-08-29T15:43:53'); 50 | } 51 | 52 | public function it_supports_resumption(Client $almaClient) 53 | { 54 | $path = '/test/path'; 55 | 56 | // To speed up tests 57 | Report::$retryDelayTime = 0; 58 | 59 | $almaClient->getXML('/analytics/reports?path=%2Ftest%2Fpath&limit=1000') 60 | ->shouldBeCalledTimes(1) 61 | ->willReturn(SpecHelper::getDummyData('analytics_response_part1.xml')); 62 | 63 | $almaClient->getXML('/analytics/reports?limit=1000&token=9672D715A8E2EAAA6F30DD22FC52BE4CCAE35E29D921E0AC8BE8C6734C9E1571B4E48EEFCA4046EFF8CD7D1662C2D0A7677D3AD05EDC3CA7F06182E34E9D7A2F') 64 | ->shouldBeCalledTimes(3) 65 | ->willReturn( 66 | 67 | // If Analytics is having a bad day, we might get a "still loading" response 68 | // See: https://bitbucket.org/uwlib/uwlib-alma-analytic-tools/wiki/Understanding_Analytic_GET_Requests#!analytic-still-loading 69 | SpecHelper::getDummyData('analytics_still_loading_response.xml'), 70 | SpecHelper::getDummyData('analytics_response_part2.xml'), 71 | SpecHelper::getDummyData('analytics_response_part3.xml') 72 | ); 73 | 74 | $this->shouldHaveCount(150 + 150 + 88); 75 | } 76 | 77 | public function it_might_not_exist(Client $almaClient) 78 | { 79 | $almaClient->getXML('/analytics/reports?path=%2Ftest%2Fpath&limit=1000') 80 | ->shouldBeCalledTimes(1) 81 | ->willThrow(new ResourceNotFound('Path not found (/test/path)')); 82 | 83 | $this->exists()->shouldReturn(false); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /spec/Bibs/BibSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client, '999104760474702204'); 21 | } 22 | 23 | protected function expectRequest($client) 24 | { 25 | $client->getXML('/bibs/999104760474702204') 26 | ->shouldBeCalled() 27 | ->willReturn(SpecHelper::getDummyData('bib_response_iz.xml')); 28 | } 29 | 30 | public function it_is_lazy(AlmaClient $client) 31 | { 32 | SpecHelper::expectNoRequests($client); 33 | $this->shouldHaveType(Bib::class); 34 | } 35 | 36 | public function it_fetches_record_data_when_needed(AlmaClient $client) 37 | { 38 | $this->expectRequest($client); 39 | 40 | $this->created_by->shouldBe('import'); 41 | $this->created_date->shouldBe('2015-11-05Z'); 42 | } 43 | 44 | public function it_can_exist(AlmaClient $client) 45 | { 46 | $this->expectRequest($client); 47 | 48 | $this->exists()->shouldBe(true); 49 | } 50 | 51 | public function it_links_to_network_zone(AlmaClient $client, AlmaClient $nz, Bibs $bibs, Bib $nz_bib) 52 | { 53 | $this->expectRequest($client); 54 | 55 | $client->nz = $nz; 56 | $nz->bibs = $bibs; 57 | $bibs->get('999104760474702201') 58 | ->shouldBeCalled() 59 | ->willReturn($nz_bib); 60 | 61 | $this->getNzRecord()->shouldHaveType(Bib::class); 62 | } 63 | 64 | public function it_provides_lazy_access_to_holdings(AlmaClient $client) 65 | { 66 | SpecHelper::expectNoRequests($client); 67 | $this->holdings->shouldHaveType(Holdings::class); 68 | } 69 | 70 | public function it_has_a_MARC_record(AlmaClient $client) 71 | { 72 | $this->expectRequest($client); 73 | 74 | $this->record->shouldHaveType(Record::class); 75 | $this->record->getField('245')->getSubfield('a')->getData()->shouldBe('Lonely hearts of the cosmos :'); 76 | } 77 | 78 | public function it_can_be_edited(AlmaClient $client) 79 | { 80 | $this->expectRequest($client); 81 | 82 | $this->record->getField('245')->getSubfield('a')->setData('New title'); 83 | 84 | $client->putXML('/bibs/999104760474702204', Argument::containingString('New title')) 85 | ->shouldBeCalled(); 86 | 87 | $this->save(); 88 | } 89 | 90 | public function it_catches_resource_not_found(AlmaClient $client) 91 | { 92 | $client->getXML('/bibs/999104760474702204') 93 | ->shouldBeCalled() 94 | ->willThrow(ResourceNotFound::class); 95 | 96 | $this->exists()->shouldBe(false); 97 | } 98 | 99 | public function it_has_requests() 100 | { 101 | $this->requests->shouldHaveType(Requests::class); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Laravel/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 29 | __DIR__ . '/../../config/alma.php' => config_path('alma.php'), 30 | ]); 31 | } 32 | 33 | /** 34 | * Register the service provider. 35 | * 36 | * @return void 37 | */ 38 | public function register() 39 | { 40 | $this->mergeConfigFrom( 41 | __DIR__ . '/../../config/alma.php', 42 | 'alma' 43 | ); 44 | 45 | $this->app->singleton(AlmaClient::class, function ($app) { 46 | 47 | // Create Alma client 48 | $alma = new AlmaClient( 49 | $app['config']->get('alma.iz.key'), 50 | $app['config']->get('alma.region'), 51 | Zones::INSTITUTION, 52 | isset($app[HttpClientInterface::class]) ? $app[HttpClientInterface::class] : null, 53 | isset($app[RequestFactoryInterface::class]) ? $app[RequestFactoryInterface::class] : null, 54 | isset($app[UriFactoryInterface::class]) ? $app[UriFactoryInterface::class] : null 55 | ); 56 | 57 | if ($app['config']->get('alma.iz.entrypoint')) { 58 | $alma->setEntryPoint($app['config']->get('alma.iz.entrypoint')); 59 | } 60 | $alma->setExtraHeaders($app['config']->get('alma.iz.headers', [])); 61 | 62 | // Set network zone key, if any 63 | $alma->nz->setKey($app['config']->get('alma.nz.key')); 64 | 65 | if ($app['config']->get('alma.nz.entrypoint')) { 66 | $alma->nz->setEntryPoint($app['config']->get('alma.nz.entrypoint')); 67 | } 68 | $alma->nz->setExtraHeaders($app['config']->get('alma.nz.headers', [])); 69 | 70 | // Optionally, attach SRU client for institution zone 71 | if ($app['config']->get('alma.iz.sru')) { 72 | $alma->setSruClient(new SruClient( 73 | $app['config']->get('alma.iz.sru'), 74 | ['version' => '1.2', 'schema' => 'marcxml'] 75 | )); 76 | } 77 | 78 | // Optionally, attach SRU client for network zone 79 | if ($app['config']->get('alma.nz.sru')) { 80 | $alma->nz->setSruClient(new SruClient( 81 | $app['config']->get('alma.nz.sru'), 82 | ['version' => '1.2', 'schema' => 'marcxml'] 83 | )); 84 | } 85 | 86 | return $alma; 87 | }); 88 | } 89 | 90 | /** 91 | * Get the services provided by the provider. 92 | * 93 | * @return array 94 | */ 95 | public function provides() 96 | { 97 | return [AlmaClient::class]; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Bibs/Bibs.php: -------------------------------------------------------------------------------- 1 | client = $client; 20 | } 21 | 22 | /** 23 | * Get a Bib object. 24 | * 25 | * @param string $mms_id 26 | * @param array $expand Expand the bibliographic record with additional information. 27 | * 28 | * @return Bib 29 | */ 30 | public function get($mms_id, $expand = null) 31 | { 32 | $params = ['expand' => $expand]; 33 | 34 | return Bib::make($this->client, $mms_id) 35 | ->setParams($params); 36 | } 37 | 38 | /** 39 | * Get a Bib object from a item barcode. 40 | * 41 | * @param string $barcode 42 | * 43 | * @return Bib 44 | */ 45 | public function fromBarcode($barcode) 46 | { 47 | $destinationUrl = $this->client->getRedirectLocation('/items', ['item_barcode' => $barcode]); 48 | 49 | // Extract the MMS ID from the redirect target URL. 50 | // Example: https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/999211285764702204/holdings/22156746440002204/items/23156746430002204 51 | if (!is_null($destinationUrl) && preg_match('$bibs/([0-9]+)/holdings/([0-9]+)/items/([0-9]+)$', $destinationUrl, $matches)) { 52 | $mmsId = $matches[1]; 53 | 54 | return $this->get($mmsId); 55 | } 56 | } 57 | 58 | /** 59 | * Get a Bib object from a holdings ID. 60 | * 61 | * @param string $holdings_id 62 | * 63 | * @return Bib 64 | */ 65 | public function fromHoldingsId($holdings_id) 66 | { 67 | $data = $this->client->getXML('/bibs', ['holdings_id' => $holdings_id]); 68 | 69 | return $this->get($data->text('bib/mms_id')) 70 | ->init($data->first('bib')); 71 | } 72 | 73 | /** 74 | * Get Bib records from SRU search. You must have an SRU client connected 75 | * to the Alma client (see `Client::setSruClient()`). 76 | * Returns a generator that handles continuation under the hood. 77 | * 78 | * @param string $cql The CQL query 79 | * @param int $batchSize Number of records to return in each batch. 80 | * 81 | * @return \Generator|Bib[] 82 | */ 83 | public function search($cql, $batchSize = 10) 84 | { 85 | $this->client->assertHasSruClient(); 86 | 87 | foreach ($this->client->sru->all($cql, $batchSize) as $sruRecord) { 88 | yield Bib::fromSruRecord($sruRecord, $this->client); 89 | } 90 | } 91 | 92 | /** 93 | * Returns the first result from a SRU search or null if no results. 94 | * 95 | * @param string $cql 96 | * 97 | * @return Bib 98 | */ 99 | public function findOne($cql) 100 | { 101 | return $this->search($cql, 1)->current(); 102 | } 103 | 104 | /** 105 | * Get a Bib object from an ISBN value. Returns null if no Bib record found. 106 | * 107 | * @param string $isbn 108 | * 109 | * @return Bib 110 | */ 111 | public function fromIsbn($isbn) 112 | { 113 | return $this->findOne('alma.isbn="' . $isbn . '"'); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /spec/Bibs/BibsSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($client); 19 | $client->sru = $sru; 20 | } 21 | 22 | public function it_provides_a_lazy_interface_to_bib_objects(AlmaClient $client) 23 | { 24 | SpecHelper::expectNoRequests($client); 25 | 26 | $mms_id = '123'; // str_random(); 27 | $bib = $this->get($mms_id); 28 | 29 | $bib->shouldHaveType(Bib::class); 30 | $bib->mms_id->shouldBe($mms_id); 31 | } 32 | 33 | public function it_provides_a_lazy_array_interface_to_bib_objects(AlmaClient $client) 34 | { 35 | SpecHelper::expectNoRequests($client); 36 | 37 | $mms_id = '123'; // str_random(); 38 | $bib = $this[$mms_id]; 39 | 40 | $bib->shouldHaveType(Bib::class); 41 | $bib->mms_id->shouldBe($mms_id); 42 | } 43 | 44 | public function it_accepts_expand_parameter(AlmaClient $client) 45 | { 46 | $client->getXML('/bibs/12345?expand=p_avail') 47 | ->shouldBeCalled() 48 | ->willReturn(SpecHelper::getDummyData('bib_response_with_availability.xml')); 49 | 50 | $this->get('12345', 'p_avail')->record; 51 | } 52 | 53 | public function it_provides_lookup_by_isbn(AlmaClient $client, SruClient $sru) 54 | { 55 | SpecHelper::expectNoRequests($client); 56 | $client->assertHasSruClient()->shouldBeCalled()->willReturn(true); 57 | 58 | $sru->all('alma.isbn="123"', 1) 59 | ->shouldBeCalled() 60 | ->willReturn([SruRecord::make( 61 | 1, 62 | '990114012304702201' 63 | )]); 64 | 65 | $bib = $this->fromIsbn('123'); 66 | $bib->shouldHaveType(Bib::class); 67 | 68 | // This operation should be lazy 69 | $bib->mms_id->shouldBe('990114012304702201'); 70 | 71 | // This operation should also be lazy 72 | $bib->record->shouldBeAnInstanceOf(Record::class); 73 | } 74 | 75 | public function it_returns_null_given_unknown_isbn(AlmaClient $client, SruClient $sru) 76 | { 77 | SpecHelper::expectNoRequests($client); 78 | $client->assertHasSruClient()->shouldBeCalled()->willReturn(true); 79 | 80 | $sru->all('alma.isbn="123"', 1) 81 | ->shouldBeCalled() 82 | ->willReturn([]); 83 | 84 | $bib = $this->fromIsbn('123'); 85 | $bib->shouldBe(null); 86 | } 87 | 88 | public function it_supports_lookup_by_holding_id(AlmaClient $client) 89 | { 90 | $client->getXML('/bibs', Argument::containing('12345')) 91 | ->shouldBeCalled() 92 | ->willReturn(SpecHelper::getDummyData('bibs_holdings.xml')); 93 | 94 | $bib = $this->fromHoldingsId('12345'); 95 | $bib->shouldHaveType(Bib::class); 96 | $bib->mms_id->shouldBe('999900137074702204'); 97 | } 98 | 99 | /* 100 | public function it_returns_a_bib_object_given_a_barcode(AlmaClient $client) 101 | { 102 | } 103 | */ 104 | } 105 | -------------------------------------------------------------------------------- /spec/Bibs/ItemSpec.php: -------------------------------------------------------------------------------- 1 | mms_id = '990006312214702204'; 22 | $holding->holding_id = '22163771200002204'; 23 | $item_id = '23163771190002204'; 24 | 25 | $this->beConstructedWith($client, $bib, $holding, $item_id); 26 | } 27 | 28 | public function it_is_initializable() 29 | { 30 | $this->shouldHaveType(Item::class); 31 | } 32 | 33 | public function it_can_be_checked_out(AlmaClient $client, User $user, Library $library) 34 | { 35 | $client->postJSON( 36 | '/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204/loans?user_id=Dan+Michael', 37 | [ 38 | 'library' => ['value' => 'THAT LIBRARY'], 39 | 'circ_desk' => ['value' => 'DEFAULT_CIRC_DESK'], 40 | ] 41 | ) 42 | ->shouldBeCalled() 43 | ->willReturn(SpecHelper::getDummyData('create_loan_response.json')); 44 | 45 | $user->id = 'Dan Michael'; 46 | $library->code = 'THAT LIBRARY'; 47 | 48 | $this->checkOut($user, $library) 49 | ->shouldHaveType(Loan::class); 50 | } 51 | 52 | public function it_can_be_on_loan(AlmaClient $client, User $user, Library $library) 53 | { 54 | $client->getJSON('/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204/loans') 55 | ->shouldBeCalled() 56 | ->willReturn(SpecHelper::getDummyData('item_loan_response.json')); 57 | 58 | $this->getLoan()->shouldHaveType(Loan::class); 59 | $this->loan->shouldHaveType(Loan::class); 60 | } 61 | 62 | public function it_can_be_available(AlmaClient $client, User $user, Library $library) 63 | { 64 | $client->getJSON('/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204/loans') 65 | ->shouldBeCalled() 66 | ->willReturn(SpecHelper::getDummyData('item_no_loan_response.json')); 67 | 68 | $this->getLoan()->shouldBe(null); 69 | $this->loan->shouldBe(null); 70 | } 71 | 72 | public function it_can_be_scanned_in(AlmaClient $client, Library $library) 73 | { 74 | $client->postJSON('/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204?op=scan&library=THAT+LIBRARY&circ_desk=DEFAULT_CIRC_DESK') 75 | ->shouldBeCalled() 76 | ->willReturn(SpecHelper::getDummyData('scanin_transit_response.json')); 77 | 78 | $library->code = 'THAT LIBRARY'; 79 | 80 | $this->scanIn($library) 81 | ->shouldHaveType(ScanInResponse::class); 82 | } 83 | 84 | public function it_can_be_scanned_in_with_params(AlmaClient $client, Library $library) 85 | { 86 | $client->postJSON('/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204?place_on_hold_shelf=true&op=scan&library=THAT+LIBRARY&circ_desk=OTHER_DESK') 87 | ->shouldBeCalled() 88 | ->willReturn(SpecHelper::getDummyData('scanin_transit_response.json')); 89 | 90 | $library->code = 'THAT LIBRARY'; 91 | 92 | $this->scanIn($library, 'OTHER_DESK', ['place_on_hold_shelf' => 'true']) 93 | ->shouldHaveType(ScanInResponse::class); 94 | } 95 | 96 | public function it_has_requests() 97 | { 98 | $this->requests->shouldHaveType(Requests::class); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Model/LazyResource.php: -------------------------------------------------------------------------------- 1 | params = $params; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * Get the request query string parameters. 44 | * 45 | * @return array 46 | */ 47 | public function getParams() 48 | { 49 | return $this->params; 50 | } 51 | 52 | /** 53 | * Check if we have the full representation of our data object. 54 | * 55 | * @param \stdClass $data 56 | * 57 | * @return bool 58 | */ 59 | abstract protected function isInitialized($data); 60 | 61 | /** 62 | * Load data onto this object. Chainable method. 63 | * 64 | * @param \stdClass|QuiteSimpleXMLElement $data 65 | * 66 | * @return $this 67 | */ 68 | public function init($data = null) 69 | { 70 | if ($this->initialized) { 71 | return $this; 72 | } 73 | 74 | if (is_null($data)) { 75 | $data = $this->fetchData(); 76 | } 77 | 78 | if ($this->isInitialized($data)) { 79 | $this->initialized = true; 80 | } 81 | 82 | $this->data = $data; 83 | if ($this->initialized) { 84 | $this->onData($data); 85 | } 86 | 87 | return $this; 88 | } 89 | 90 | /** 91 | * Get and return the model data. 92 | * 93 | * @return object 94 | */ 95 | protected function fetchData() 96 | { 97 | return $this->client->getJSON($this->url()); 98 | } 99 | 100 | /** 101 | * Called when data is available on the object. 102 | * The resource classes can use this method to process the data. 103 | * 104 | * @param mixed $data 105 | */ 106 | protected function onData($data) 107 | { 108 | } 109 | 110 | /** 111 | * Get the raw data object. 112 | */ 113 | public function getData() 114 | { 115 | return $this->init()->data; 116 | } 117 | 118 | /** 119 | * Check if the object exists. 120 | */ 121 | public function exists() 122 | { 123 | try { 124 | $this->init(); 125 | } catch (ResourceNotFound $ex) { 126 | } 127 | 128 | return $this->initialized; 129 | } 130 | 131 | /** 132 | * Generate the base URL for this resource. 133 | * 134 | * @return string 135 | */ 136 | abstract protected function urlBase(); 137 | 138 | /** 139 | * Build a relative URL for a resource. 140 | * 141 | * @param string $path 142 | * @param array $query 143 | * 144 | * @return string 145 | */ 146 | protected function url($path = '', $query = []) 147 | { 148 | $path = $this->urlBase() . $path; 149 | $query = http_build_query(array_merge($this->params, $query)); 150 | 151 | $url = $path; 152 | if (!empty($query)) { 153 | $url .= '?' . $query; 154 | } 155 | 156 | return $url; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /spec/data/bib_response_nz.xml: -------------------------------------------------------------------------------- 1 | 999104760474702201marc21Lonely hearts of the cosmos : the scientific quest for the secret of the universeOverbye, Dennis0060159642(NO-TrBIB)910476047910476047-47bibsys_networkNew YorkHarper Collinsimport2015-11-02ZSystem2016-06-20ZfalseILS910476047-47bibsys_network01136cam a2200349 c 450099910476047470220120160620201908.0ta150121s1991 xx#|||||||||||000|0|eng|d0060159642ib.910476047-47bibsys_network(NO-TrBIB)910476047NO-TrBIBnobkatreg524.8(092)523.1NO-OsNB4/norFa:7utkOverbye, Dennis(NO-TrBIB)90545755Lonely hearts of the cosmos :the scientific quest for the secret of the universeDennis OverbyeNew YorkHarper Collinsc1991VIII, 438 s., pl.ill.Sandage, Allan(NO-TrBIB)90061406CosmologyAstronomersBiographyKosmologinoubomnUniversetnoubomnKosmologiBiografitekordUniversets opprinnelsetekorduniversetkosmologihistoriebiografierpopulærvitenskapBiografiernoubomnPopulærvitenskapnoubomnHistorienoubomn80 2 | -------------------------------------------------------------------------------- /spec/data/item_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "bib_data": { 3 | "author": "Vollset, K.", 4 | "complete_edition": "", 5 | "date_of_publication": "2000", 6 | "isbn": "8247806290", 7 | "issn": null, 8 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990006312214702204", 9 | "mms_id": "990006312214702204", 10 | "network_number": [ 11 | "(NO-TrBIB)092093302", 12 | "(NO-TrBIB)000631221", 13 | "000631221-47bibsys_network", 14 | "(EXLNZ-47BIBSYS_NETWORK)990006312214702201" 15 | ], 16 | "place_of_publication": "[Oslo]", 17 | "publisher_const": "Gyldendal Tiden", 18 | "title": "Bananboken" 19 | }, 20 | "holding_data": { 21 | "accession_number": "", 22 | "call_number": "Ung 839.82 Vol:Ban", 23 | "call_number_type": { 24 | "desc": "Other scheme", 25 | "value": "8" 26 | }, 27 | "copy_id": "", 28 | "holding_id": "22163771200002204", 29 | "in_temp_location": false, 30 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990006312214702204/holdings/22163771200002204", 31 | "temp_call_number": "", 32 | "temp_call_number_type": { 33 | "desc": null, 34 | "value": "" 35 | }, 36 | "temp_library": { 37 | "desc": null, 38 | "value": null 39 | }, 40 | "temp_location": { 41 | "desc": null, 42 | "value": null 43 | }, 44 | "temp_policy": { 45 | "desc": null, 46 | "value": "" 47 | } 48 | }, 49 | "item_data": { 50 | "alternative_call_number": "", 51 | "alternative_call_number_type": { 52 | "desc": null, 53 | "value": "" 54 | }, 55 | "arrival_date": "2000-05-29Z", 56 | "barcode": "303011kj0", 57 | "base_status": { 58 | "desc": "Item in place", 59 | "value": "1" 60 | }, 61 | "chronology_i": "", 62 | "chronology_j": "", 63 | "chronology_k": "", 64 | "chronology_l": "", 65 | "chronology_m": "", 66 | "creation_date": "2015-11-05Z", 67 | "description": "", 68 | "edition": null, 69 | "enumeration_a": "", 70 | "enumeration_b": "", 71 | "enumeration_c": "", 72 | "enumeration_d": "", 73 | "enumeration_e": "", 74 | "enumeration_f": "", 75 | "enumeration_g": "", 76 | "enumeration_h": "", 77 | "fulfillment_note": "", 78 | "imprint": null, 79 | "internal_note_1": "Status: kat - kat", 80 | "internal_note_2": "TIME FOR CIRC STATUS: 2015-11-02 | CIRC STATUS: 0", 81 | "internal_note_3": "", 82 | "inventory_number": "00kj16342", 83 | "is_magnetic": false, 84 | "language": null, 85 | "library": { 86 | "desc": "UiO HumSam-biblioteket", 87 | "value": "1030300" 88 | }, 89 | "location": { 90 | "desc": "UHS S-Litt", 91 | "value": "k00040" 92 | }, 93 | "modification_date": "2015-11-24Z", 94 | "pages": "", 95 | "physical_condition": { 96 | "desc": null, 97 | "value": null 98 | }, 99 | "physical_material_type": { 100 | "desc": "Book", 101 | "value": "BOOK" 102 | }, 103 | "pid": "23163771190002204", 104 | "pieces": "", 105 | "po_line": "", 106 | "policy": { 107 | "desc": null, 108 | "value": "" 109 | }, 110 | "process_type": { 111 | "desc": null, 112 | "value": "" 113 | }, 114 | "provenance": { 115 | "desc": null, 116 | "value": "" 117 | }, 118 | "public_note": "", 119 | "receiving_operator": "import", 120 | "requested": false, 121 | "statistics_note_1": "", 122 | "statistics_note_2": "", 123 | "statistics_note_3": "", 124 | "storage_location_id": "", 125 | "year_of_issue": "" 126 | }, 127 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204" 128 | } -------------------------------------------------------------------------------- /spec/data/scanin_transit_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "additional_info": "Item's destination is: UiO HumSam-biblioteket. Request/Process Type: Transit for reshelving. Requester: . Requester ID: . Place in Queue: 1", 3 | "bib_data": { 4 | "author": "Vollset, K.", 5 | "complete_edition": "", 6 | "date_of_publication": "2000", 7 | "isbn": "8247806290", 8 | "issn": null, 9 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990006312214702204", 10 | "mms_id": "990006312214702204", 11 | "network_number": [ 12 | "(NO-TrBIB)092093302", 13 | "(NO-TrBIB)000631221", 14 | "000631221-47bibsys_network", 15 | "(EXLNZ-47BIBSYS_NETWORK)990006312214702201" 16 | ], 17 | "place_of_publication": "[Oslo]", 18 | "publisher_const": "Gyldendal Tiden", 19 | "title": "Bananboken" 20 | }, 21 | "holding_data": { 22 | "accession_number": "", 23 | "call_number": "Ung 839.82 Vol:Ban", 24 | "call_number_type": { 25 | "desc": "Other scheme", 26 | "value": "8" 27 | }, 28 | "copy_id": "", 29 | "holding_id": "22163771200002204", 30 | "in_temp_location": false, 31 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990006312214702204/holdings/22163771200002204", 32 | "temp_call_number": "", 33 | "temp_call_number_type": { 34 | "desc": null, 35 | "value": "" 36 | }, 37 | "temp_library": { 38 | "desc": null, 39 | "value": null 40 | }, 41 | "temp_location": { 42 | "desc": null, 43 | "value": null 44 | }, 45 | "temp_policy": { 46 | "desc": null, 47 | "value": "" 48 | } 49 | }, 50 | "item_data": { 51 | "alternative_call_number": "", 52 | "alternative_call_number_type": { 53 | "desc": null, 54 | "value": "" 55 | }, 56 | "arrival_date": "2000-05-29Z", 57 | "barcode": "303011kj0", 58 | "base_status": { 59 | "desc": "Item not in place", 60 | "value": "0" 61 | }, 62 | "chronology_i": "", 63 | "chronology_j": "", 64 | "chronology_k": "", 65 | "chronology_l": "", 66 | "chronology_m": "", 67 | "creation_date": "2015-11-05Z", 68 | "description": "", 69 | "edition": null, 70 | "enumeration_a": "", 71 | "enumeration_b": "", 72 | "enumeration_c": "", 73 | "enumeration_d": "", 74 | "enumeration_e": "", 75 | "enumeration_f": "", 76 | "enumeration_g": "", 77 | "enumeration_h": "", 78 | "fulfillment_note": "", 79 | "imprint": null, 80 | "internal_note_1": "Status: kat - kat", 81 | "internal_note_2": "TIME FOR CIRC STATUS: 2015-11-02 | CIRC STATUS: 0", 82 | "internal_note_3": "", 83 | "inventory_number": "00kj16342", 84 | "is_magnetic": false, 85 | "language": null, 86 | "library": { 87 | "desc": "UiO HumSam-biblioteket", 88 | "value": "1030300" 89 | }, 90 | "location": { 91 | "desc": "UHS S-Litt", 92 | "value": "k00040" 93 | }, 94 | "modification_date": "2018-07-15Z", 95 | "pages": "", 96 | "physical_condition": { 97 | "desc": null, 98 | "value": null 99 | }, 100 | "physical_material_type": { 101 | "desc": "Book", 102 | "value": "BOOK" 103 | }, 104 | "pid": "23163771190002204", 105 | "pieces": "", 106 | "po_line": "", 107 | "policy": { 108 | "desc": null, 109 | "value": "" 110 | }, 111 | "process_type": { 112 | "desc": "Transit", 113 | "value": "TRANSIT" 114 | }, 115 | "provenance": { 116 | "desc": null, 117 | "value": "" 118 | }, 119 | "public_note": "", 120 | "receiving_operator": "import", 121 | "requested": null, 122 | "statistics_note_1": "", 123 | "statistics_note_2": "", 124 | "statistics_note_3": "", 125 | "storage_location_id": "", 126 | "year_of_issue": "" 127 | }, 128 | "link": "https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990006312214702204/holdings/22163771200002204/items/23163771190002204" 129 | } -------------------------------------------------------------------------------- /spec/data/bib_response_iz.xml: -------------------------------------------------------------------------------- 1 | 999104760474702204marc21999104760474702201Lonely hearts of the cosmos : the scientific quest for the secret of the universeOverbye, Dennis0060159642(NO-TrBIB)910476047910476047-47bibsys_network(EXLNZ-47BIBSYS_NETWORK)999104760474702201New Yorkc1991Harper Collinsimport2015-11-05Zsystem2018-07-05ZfalseILS910476047-47bibsys_network8001186cam a2200349 c 450099910476047470220420171207121755.0ta150121s1991 xx#|||||||||||000|0|eng|d0060159642ib.910476047-47bibsys_network(NO-TrBIB)910476047(EXLNZ-47BIBSYS_NETWORK)999104760474702201NO-TrBIBnobkatreg524.8(092)523.1NO-OsNB4/norFa:7utkfa1351LOCALOverbye, Dennis(NO-TrBIB)90545755Lonely hearts of the cosmos :the scientific quest for the secret of the universeDennis OverbyeNew YorkHarper Collinsc1991VIII, 438 s., pl.ill.Sandage, Allan(NO-TrBIB)90061406bareCosmologyAstronomersBiographyKosmologinoubomn(NO-TrBIB)REAL013436Universetnoubomn(NO-TrBIB)REAL013999KosmologiBiografitekordUniversets opprinnelsetekorduniversetkosmologihistoriebiografierpopulærvitenskapBiografiernoubomnPopulærvitenskapnoubomnHistorienoubomn80 -------------------------------------------------------------------------------- /src/Users/Users.php: -------------------------------------------------------------------------------- 1 | client = $client; 22 | } 23 | 24 | /** 25 | * Get a User object by id. 26 | * 27 | * @param $user_id int 28 | * @param $params array Additional query string parameters 29 | * 30 | * @return User 31 | */ 32 | public function get($user_id, $params = []) 33 | { 34 | return User::make($this->client, $user_id) 35 | ->setParams($params); 36 | } 37 | 38 | /** 39 | * Get the first user matching a given query, or NULL if not found. 40 | * 41 | * @param string $query 42 | * @param array $options 43 | * 44 | * @return User|null 45 | */ 46 | public function findOne($query, array $options = []) 47 | { 48 | return $this->search($query, $options)->current(); 49 | } 50 | 51 | /** 52 | * Iterates over all users matching the given query. 53 | * Handles continuation. 54 | * 55 | * @param string $query 56 | * @param array $options 57 | * 58 | * @return \Generator 59 | */ 60 | public function search($query, array $options = []) 61 | { 62 | // Max number of records to fetch. Set to 0 to fetch all. 63 | $limit = array_key_exists('limit', $options) ? $options['limit'] : 0; 64 | 65 | // Set to true to do a phrase search 66 | $phrase = array_key_exists('phrase', $options) ? $options['phrase'] : false; 67 | 68 | // Set to true to expand all query results to full records. 69 | // Please note that this will make queries significantly slower! 70 | $expand = array_key_exists('expand', $options) ? $options['expand'] : false; 71 | 72 | // Number of records to fetch each batch. Usually no need to change this. 73 | $batchSize = array_key_exists('batchSize', $options) ? $options['batchSize'] : 10; 74 | 75 | if ($limit != 0 && $limit < $batchSize) { 76 | $batchSize = $limit; 77 | } 78 | 79 | // The API will throw a 400 response if you include properly encoded spaces, 80 | // but underscores work as a substitute. 81 | $query = explode(' AND ', $query); 82 | $query = $phrase ? str_replace(' ', '_', $query) : str_replace(' ', ',', $query); 83 | $query = implode(' AND ', $query); 84 | 85 | $offset = 0; 86 | while (true) { 87 | $response = $this->client->getJSON('/users', ['q' => $query, 'limit' => $batchSize, 'offset' => $offset]); 88 | 89 | // The API sometimes returns total_record_count: -1, with no further error message. 90 | // Seems to indicate that the query was not understood. 91 | // See: https://github.com/scriptotek/php-alma-client/issues/8 92 | if ($response->total_record_count == -1) { 93 | throw new InvalidQuery($query); 94 | } 95 | 96 | if ($response->total_record_count == 0) { 97 | break; 98 | } 99 | 100 | if (!isset($response->user) || empty($response->user)) { 101 | // We cannot trust the value in 'total_record_count', so if there are no more records, 102 | // we have to assume the result set is depleted. 103 | // See: https://github.com/scriptotek/php-alma-client/issues/7 104 | break; 105 | } 106 | 107 | foreach ($response->user as $data) { 108 | $offset++; 109 | // Contacts without a primary identifier will have the primary_id 110 | // field populated with something weird like "no primary id (123456789023)". 111 | // We ignore those. 112 | // See: https://github.com/scriptotek/php-alma-client/issues/6 113 | if (strpos($data->primary_id, 'no primary id') === 0) { 114 | continue; 115 | } 116 | $user = User::make($this->client, $data->primary_id) 117 | ->init($data); 118 | if ($expand) { 119 | $user->init(); 120 | } 121 | yield $user; 122 | } 123 | if ($offset >= $response->total_record_count) { 124 | break; 125 | } 126 | if ($limit != 0 && $offset >= $limit) { 127 | break; 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Bibs/Item.php: -------------------------------------------------------------------------------- 1 | bib = $bib; 38 | $this->holding = $holding; 39 | $this->item_id = $item_id; 40 | $this->requests = Requests::make($this->client, $this->url('/requests')); 41 | } 42 | 43 | /** 44 | * Generate the base URL for this resource. 45 | * 46 | * @return string 47 | */ 48 | protected function urlBase() 49 | { 50 | return "/bibs/{$this->bib->mms_id}/holdings/{$this->holding->holding_id}/items/{$this->item_id}"; 51 | } 52 | 53 | /** 54 | * Check if we have the full representation of our data object. 55 | * 56 | * @param \stdClass $data 57 | * 58 | * @return bool 59 | */ 60 | protected function isInitialized($data) 61 | { 62 | return isset($data->item_data); 63 | } 64 | 65 | /** 66 | * Called when data is available to be processed. 67 | * 68 | * @param mixed $data 69 | */ 70 | protected function onData($data) 71 | { 72 | if (isset($this->bib_data)) { 73 | $this->bib->init($this->bib_data); 74 | } 75 | if (isset($this->holding_data)) { 76 | $this->holding->init($this->holding_data); 77 | } 78 | } 79 | 80 | /** 81 | * Create a new loan. 82 | * 83 | * @param User $user 84 | * @param Library $library 85 | * @param string $circ_desk 86 | * 87 | * @throws \Scriptotek\Alma\Exception\RequestFailed 88 | * 89 | * @return Loan 90 | */ 91 | public function checkOut(User $user, Library $library, $circ_desk = 'DEFAULT_CIRC_DESK') 92 | { 93 | $postData = [ 94 | 'library' => ['value' => $library->code], 95 | 'circ_desk' => ['value' => $circ_desk], 96 | ]; 97 | 98 | $data = $this->client->postJSON( 99 | $this->url('/loans', ['user_id' => $user->id]), 100 | $postData 101 | ); 102 | 103 | return Loan::make($this->client, $user, $data->loan_id) 104 | ->init($data); 105 | } 106 | 107 | /** 108 | * Perform scan-in on item. 109 | * 110 | * @param Library $library 111 | * @param string $circ_desk 112 | * @param array $params 113 | * 114 | * @throws \Scriptotek\Alma\Exception\RequestFailed 115 | * 116 | * @return ScanInResponse 117 | */ 118 | public function scanIn(Library $library, $circ_desk = 'DEFAULT_CIRC_DESK', $params = []) 119 | { 120 | $params['op'] = 'scan'; 121 | $params['library'] = $library->code; 122 | $params['circ_desk'] = $circ_desk; 123 | 124 | $data = $this->client->postJSON($this->url('', $params)); 125 | 126 | return ScanInResponse::make($this->client, $data); 127 | } 128 | 129 | /** 130 | * Get the current loan as a Loan object, or null if the item is not loaned out. 131 | * 132 | * @returns Loan|null 133 | */ 134 | public function getLoan() 135 | { 136 | $data = $this->client->getJSON($this->url('/loans')); 137 | 138 | if ($data->total_record_count == 1) { 139 | return Loan::make( 140 | $this->client, 141 | User::make($this->client, $data->item_loan[0]->user_id), 142 | $data->item_loan[0]->loan_id 143 | )->init($data->item_loan[0]); 144 | } 145 | } 146 | 147 | public function __get($key) 148 | { 149 | if ($key == 'loan') { 150 | return $this->getLoan(); 151 | } 152 | 153 | $this->init(); 154 | 155 | if (isset($this->data->item_data->{$key})) { 156 | return $this->data->item_data->{$key}; 157 | } 158 | if (isset($this->data->holding_data->{$key})) { 159 | return $this->data->holding_data->{$key}; 160 | } 161 | if (isset($this->data->bib_data->{$key})) { 162 | return $this->data->bib_data->{$key}; 163 | } 164 | 165 | return parent::__get($key); 166 | } 167 | } 168 | --------------------------------------------------------------------------------