From 3264e10ccf712393ee29e07a536392b539ada50b Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Tue, 23 Jun 2015 12:23:41 +0200 Subject: [PATCH 01/32] trying to request gokb - WARNING: it doesn't work !!! --- admin/classes/domain/GOKbTools.php | 255 ++++ admin/classes/domain/composer.json | 14 + admin/classes/domain/composer.lock | 280 ++++ admin/classes/domain/composer.phar | Bin 0 -> 1107672 bytes admin/classes/domain/vendor/autoload.php | 7 + .../vendor/caseyamcl/phpoaipmh/CHANGELOG.md | 90 ++ .../caseyamcl/phpoaipmh/CONTRIBUTING.md | 21 + .../domain/vendor/caseyamcl/phpoaipmh/LICENSE | 20 + .../vendor/caseyamcl/phpoaipmh/README.md | 265 ++++ .../vendor/caseyamcl/phpoaipmh/UPGRADE.md | 20 + .../vendor/caseyamcl/phpoaipmh/composer.json | 36 + .../phpoaipmh/src/Phpoaipmh/Client.php | 159 ++ .../phpoaipmh/src/Phpoaipmh/Endpoint.php | 244 +++ .../src/Phpoaipmh/EndpointInterface.php | 104 ++ .../Exception/BaseOaipmhException.php | 29 + .../src/Phpoaipmh/Exception/HttpException.php | 51 + .../Exception/MalformedResponseException.php | 31 + .../Phpoaipmh/Exception/OaipmhException.php | 50 + .../phpoaipmh/src/Phpoaipmh/Granularity.php | 49 + .../src/Phpoaipmh/HttpAdapter/CurlAdapter.php | 96 ++ .../Phpoaipmh/HttpAdapter/GuzzleAdapter.php | 67 + .../HttpAdapter/HttpAdapterInterface.php | 22 + .../src/Phpoaipmh/RecordIterator.php | 303 ++++ .../domain/vendor/composer/ClassLoader.php | 413 +++++ .../vendor/composer/autoload_classmap.php | 9 + .../domain/vendor/composer/autoload_files.php | 10 + .../vendor/composer/autoload_namespaces.php | 10 + .../domain/vendor/composer/autoload_psr4.php | 14 + .../domain/vendor/composer/autoload_real.php | 55 + .../domain/vendor/composer/installed.json | 274 ++++ .../vendor/guzzlehttp/guzzle/.editorconfig | 11 + .../vendor/guzzlehttp/guzzle/.gitignore | 11 + .../vendor/guzzlehttp/guzzle/.travis.yml | 41 + .../vendor/guzzlehttp/guzzle/CHANGELOG.md | 1053 +++++++++++++ .../domain/vendor/guzzlehttp/guzzle/LICENSE | 19 + .../domain/vendor/guzzlehttp/guzzle/Makefile | 50 + .../domain/vendor/guzzlehttp/guzzle/README.md | 70 + .../vendor/guzzlehttp/guzzle/UPGRADING.md | 1050 +++++++++++++ .../guzzlehttp/guzzle/build/packager.php | 21 + .../vendor/guzzlehttp/guzzle/composer.json | 39 + .../vendor/guzzlehttp/guzzle/docs/Makefile | 153 ++ .../guzzle/docs/_static/guzzle-icon.png | Bin 0 -> 803 bytes .../guzzlehttp/guzzle/docs/_static/logo.png | Bin 0 -> 247678 bytes .../guzzle/docs/_templates/nav_links.html | 3 + .../vendor/guzzlehttp/guzzle/docs/clients.rst | 1326 +++++++++++++++++ .../vendor/guzzlehttp/guzzle/docs/conf.py | 28 + .../vendor/guzzlehttp/guzzle/docs/events.rst | 516 +++++++ .../vendor/guzzlehttp/guzzle/docs/faq.rst | 199 +++ .../guzzlehttp/guzzle/docs/handlers.rst | 43 + .../guzzlehttp/guzzle/docs/http-messages.rst | 483 ++++++ .../vendor/guzzlehttp/guzzle/docs/index.rst | 98 ++ .../guzzlehttp/guzzle/docs/overview.rst | 150 ++ .../guzzlehttp/guzzle/docs/quickstart.rst | 448 ++++++ .../guzzlehttp/guzzle/docs/requirements.txt | 2 + .../vendor/guzzlehttp/guzzle/docs/streams.rst | 213 +++ .../vendor/guzzlehttp/guzzle/docs/testing.rst | 232 +++ .../vendor/guzzlehttp/guzzle/phpunit.xml.dist | 17 + .../guzzlehttp/guzzle/src/BatchResults.php | 148 ++ .../vendor/guzzlehttp/guzzle/src/Client.php | 352 +++++ .../guzzlehttp/guzzle/src/ClientInterface.php | 150 ++ .../guzzlehttp/guzzle/src/Collection.php | 236 +++ .../guzzle/src/Cookie/CookieJar.php | 248 +++ .../guzzle/src/Cookie/CookieJarInterface.php | 75 + .../guzzle/src/Cookie/FileCookieJar.php | 86 ++ .../guzzle/src/Cookie/SessionCookieJar.php | 66 + .../guzzle/src/Cookie/SetCookie.php | 373 +++++ .../guzzle/src/Event/AbstractEvent.php | 20 + .../guzzle/src/Event/AbstractRequestEvent.php | 61 + .../src/Event/AbstractRetryableEvent.php | 40 + .../src/Event/AbstractTransferEvent.php | 63 + .../guzzle/src/Event/BeforeEvent.php | 26 + .../guzzle/src/Event/CompleteEvent.php | 14 + .../guzzlehttp/guzzle/src/Event/Emitter.php | 146 ++ .../guzzle/src/Event/EmitterInterface.php | 96 ++ .../guzzlehttp/guzzle/src/Event/EndEvent.php | 28 + .../guzzle/src/Event/ErrorEvent.php | 27 + .../guzzle/src/Event/EventInterface.php | 23 + .../guzzle/src/Event/HasEmitterInterface.php | 15 + .../guzzle/src/Event/HasEmitterTrait.php | 20 + .../src/Event/ListenerAttacherTrait.php | 88 ++ .../guzzle/src/Event/ProgressEvent.php | 51 + .../guzzle/src/Event/RequestEvents.php | 56 + .../guzzle/src/Event/SubscriberInterface.php | 34 + .../src/Exception/BadResponseException.php | 7 + .../guzzle/src/Exception/ClientException.php | 7 + .../guzzle/src/Exception/ConnectException.php | 4 + .../CouldNotRewindStreamException.php | 4 + .../guzzle/src/Exception/ParseException.php | 31 + .../guzzle/src/Exception/RequestException.php | 121 ++ .../guzzle/src/Exception/ServerException.php | 7 + .../guzzle/src/Exception/StateException.php | 4 + .../Exception/TooManyRedirectsException.php | 4 + .../src/Exception/TransferException.php | 4 + .../src/Exception/XmlParseException.php | 34 + .../guzzlehttp/guzzle/src/HasDataTrait.php | 75 + .../guzzle/src/Message/AbstractMessage.php | 253 ++++ .../src/Message/AppliesHeadersInterface.php | 24 + .../guzzle/src/Message/FutureResponse.php | 158 ++ .../guzzle/src/Message/MessageFactory.php | 364 +++++ .../src/Message/MessageFactoryInterface.php | 71 + .../guzzle/src/Message/MessageInterface.php | 136 ++ .../guzzle/src/Message/MessageParser.php | 171 +++ .../guzzlehttp/guzzle/src/Message/Request.php | 195 +++ .../guzzle/src/Message/RequestInterface.php | 136 ++ .../guzzle/src/Message/Response.php | 208 +++ .../guzzle/src/Message/ResponseInterface.php | 111 ++ .../guzzlehttp/guzzle/src/Mimetypes.php | 963 ++++++++++++ .../vendor/guzzlehttp/guzzle/src/Pool.php | 333 +++++ .../guzzle/src/Post/MultipartBody.php | 109 ++ .../guzzlehttp/guzzle/src/Post/PostBody.php | 287 ++++ .../guzzle/src/Post/PostBodyInterface.php | 109 ++ .../guzzlehttp/guzzle/src/Post/PostFile.php | 135 ++ .../guzzle/src/Post/PostFileInterface.php | 41 + .../vendor/guzzlehttp/guzzle/src/Query.php | 204 +++ .../guzzlehttp/guzzle/src/QueryParser.php | 163 ++ .../guzzlehttp/guzzle/src/RequestFsm.php | 153 ++ .../guzzlehttp/guzzle/src/RingBridge.php | 165 ++ .../guzzle/src/Subscriber/Cookie.php | 58 + .../guzzle/src/Subscriber/History.php | 172 +++ .../guzzle/src/Subscriber/HttpError.php | 36 + .../guzzlehttp/guzzle/src/Subscriber/Mock.php | 147 ++ .../guzzle/src/Subscriber/Prepare.php | 130 ++ .../guzzle/src/Subscriber/Redirect.php | 176 +++ .../guzzle/src/ToArrayInterface.php | 15 + .../guzzlehttp/guzzle/src/Transaction.php | 103 ++ .../guzzlehttp/guzzle/src/UriTemplate.php | 241 +++ .../vendor/guzzlehttp/guzzle/src/Url.php | 595 ++++++++ .../vendor/guzzlehttp/guzzle/src/Utils.php | 211 +++ .../guzzle/tests/BatchResultsTest.php | 58 + .../guzzlehttp/guzzle/tests/ClientTest.php | 647 ++++++++ .../guzzle/tests/CollectionTest.php | 416 ++++++ .../guzzle/tests/Cookie/CookieJarTest.php | 339 +++++ .../guzzle/tests/Cookie/FileCookieJarTest.php | 71 + .../tests/Cookie/SessionCookieJarTest.php | 76 + .../guzzle/tests/Cookie/SetCookieTest.php | 364 +++++ .../guzzle/tests/Event/AbstractEventTest.php | 14 + .../tests/Event/AbstractRequestEventTest.php | 33 + .../Event/AbstractRetryableEventTest.php | 37 + .../tests/Event/AbstractTransferEventTest.php | 59 + .../guzzle/tests/Event/BeforeEventTest.php | 26 + .../guzzle/tests/Event/EmitterTest.php | 363 +++++ .../guzzle/tests/Event/ErrorEventTest.php | 23 + .../tests/Event/HasEmitterTraitTest.php | 27 + .../tests/Event/ListenerAttacherTraitTest.php | 92 ++ .../guzzle/tests/Event/ProgressEventTest.php | 25 + .../guzzle/tests/Event/RequestEventsTest.php | 74 + .../tests/Exception/ParseExceptionTest.php | 20 + .../tests/Exception/RequestExceptionTest.php | 83 ++ .../tests/Exception/XmlParseExceptionTest.php | 19 + .../guzzle/tests/IntegrationTest.php | 123 ++ .../tests/Message/AbstractMessageTest.php | 269 ++++ .../tests/Message/FutureResponseTest.php | 160 ++ .../tests/Message/MessageFactoryTest.php | 601 ++++++++ .../tests/Message/MessageParserTest.php | 276 ++++ .../guzzle/tests/Message/RequestTest.php | 144 ++ .../guzzle/tests/Message/ResponseTest.php | 120 ++ .../guzzlehttp/guzzle/tests/MimetypesTest.php | 31 + .../guzzlehttp/guzzle/tests/PoolTest.php | 319 ++++ .../guzzle/tests/Post/MultipartBodyTest.php | 120 ++ .../guzzle/tests/Post/PostBodyTest.php | 255 ++++ .../guzzle/tests/Post/PostFileTest.php | 61 + .../guzzle/tests/QueryParserTest.php | 80 + .../guzzlehttp/guzzle/tests/QueryTest.php | 171 +++ .../guzzle/tests/RequestFsmTest.php | 187 +++ .../guzzle/tests/RingBridgeTest.php | 195 +++ .../vendor/guzzlehttp/guzzle/tests/Server.php | 107 ++ .../guzzle/tests/Subscriber/CookieTest.php | 74 + .../guzzle/tests/Subscriber/HistoryTest.php | 140 ++ .../guzzle/tests/Subscriber/HttpErrorTest.php | 60 + .../guzzle/tests/Subscriber/MockTest.php | 225 +++ .../guzzle/tests/Subscriber/PrepareTest.php | 213 +++ .../guzzle/tests/Subscriber/RedirectTest.php | 302 ++++ .../guzzle/tests/TransactionTest.php | 22 + .../guzzle/tests/UriTemplateTest.php | 202 +++ .../guzzlehttp/guzzle/tests/UrlTest.php | 364 +++++ .../guzzlehttp/guzzle/tests/UtilsTest.php | 40 + .../guzzlehttp/guzzle/tests/bootstrap.php | 11 + .../vendor/guzzlehttp/guzzle/tests/perf.php | 61 + .../vendor/guzzlehttp/ringphp/.gitignore | 4 + .../vendor/guzzlehttp/ringphp/.travis.yml | 22 + .../vendor/guzzlehttp/ringphp/CHANGELOG.md | 54 + .../domain/vendor/guzzlehttp/ringphp/LICENSE | 19 + .../domain/vendor/guzzlehttp/ringphp/Makefile | 46 + .../vendor/guzzlehttp/ringphp/README.rst | 46 + .../vendor/guzzlehttp/ringphp/composer.json | 39 + .../vendor/guzzlehttp/ringphp/docs/Makefile | 153 ++ .../ringphp/docs/client_handlers.rst | 173 +++ .../ringphp/docs/client_middleware.rst | 165 ++ .../vendor/guzzlehttp/ringphp/docs/conf.py | 23 + .../guzzlehttp/ringphp/docs/futures.rst | 164 ++ .../vendor/guzzlehttp/ringphp/docs/index.rst | 50 + .../guzzlehttp/ringphp/docs/requirements.txt | 1 + .../vendor/guzzlehttp/ringphp/docs/spec.rst | 311 ++++ .../guzzlehttp/ringphp/docs/testing.rst | 74 + .../guzzlehttp/ringphp/phpunit.xml.dist | 14 + .../ringphp/src/Client/ClientUtils.php | 74 + .../ringphp/src/Client/CurlFactory.php | 560 +++++++ .../ringphp/src/Client/CurlHandler.php | 135 ++ .../ringphp/src/Client/CurlMultiHandler.php | 250 ++++ .../ringphp/src/Client/Middleware.php | 58 + .../ringphp/src/Client/MockHandler.php | 52 + .../ringphp/src/Client/StreamHandler.php | 414 +++++ .../vendor/guzzlehttp/ringphp/src/Core.php | 364 +++++ .../src/Exception/CancelledException.php | 7 + .../CancelledFutureAccessException.php | 4 + .../src/Exception/ConnectException.php | 7 + .../ringphp/src/Exception/RingException.php | 4 + .../ringphp/src/Future/BaseFutureTrait.php | 125 ++ .../src/Future/CompletedFutureArray.php | 43 + .../src/Future/CompletedFutureValue.php | 57 + .../ringphp/src/Future/FutureArray.php | 40 + .../src/Future/FutureArrayInterface.php | 11 + .../ringphp/src/Future/FutureInterface.php | 40 + .../ringphp/src/Future/FutureValue.php | 12 + .../ringphp/src/Future/MagicFutureTrait.php | 32 + .../ringphp/tests/Client/CurlFactoryTest.php | 821 ++++++++++ .../ringphp/tests/Client/CurlHandlerTest.php | 96 ++ .../tests/Client/CurlMultiHandlerTest.php | 165 ++ .../ringphp/tests/Client/MiddlewareTest.php | 65 + .../ringphp/tests/Client/MockHandlerTest.php | 86 ++ .../ringphp/tests/Client/Server.php | 183 +++ .../tests/Client/StreamHandlerTest.php | 480 ++++++ .../guzzlehttp/ringphp/tests/Client/server.js | 241 +++ .../guzzlehttp/ringphp/tests/CoreTest.php | 336 +++++ .../tests/Future/CompletedFutureArrayTest.php | 21 + .../tests/Future/CompletedFutureValueTest.php | 46 + .../ringphp/tests/Future/FutureArrayTest.php | 56 + .../ringphp/tests/Future/FutureValueTest.php | 109 ++ .../guzzlehttp/ringphp/tests/bootstrap.php | 11 + .../vendor/guzzlehttp/streams/.gitignore | 6 + .../vendor/guzzlehttp/streams/.travis.yml | 17 + .../vendor/guzzlehttp/streams/CHANGELOG.rst | 94 ++ .../domain/vendor/guzzlehttp/streams/LICENSE | 19 + .../domain/vendor/guzzlehttp/streams/Makefile | 19 + .../vendor/guzzlehttp/streams/README.rst | 36 + .../vendor/guzzlehttp/streams/composer.json | 28 + .../guzzlehttp/streams/phpunit.xml.dist | 17 + .../guzzlehttp/streams/src/AppendStream.php | 220 +++ .../streams/src/AsyncReadStream.php | 207 +++ .../guzzlehttp/streams/src/BufferStream.php | 138 ++ .../guzzlehttp/streams/src/CachingStream.php | 122 ++ .../guzzlehttp/streams/src/DroppingStream.php | 42 + .../src/Exception/CannotAttachException.php | 4 + .../streams/src/Exception/SeekException.php | 27 + .../guzzlehttp/streams/src/FnStream.php | 147 ++ .../streams/src/GuzzleStreamWrapper.php | 117 ++ .../guzzlehttp/streams/src/InflateStream.php | 27 + .../guzzlehttp/streams/src/LazyOpenStream.php | 37 + .../guzzlehttp/streams/src/LimitStream.php | 161 ++ .../streams/src/MetadataStreamInterface.php | 11 + .../guzzlehttp/streams/src/NoSeekStream.php | 25 + .../guzzlehttp/streams/src/NullStream.php | 78 + .../guzzlehttp/streams/src/PumpStream.php | 161 ++ .../vendor/guzzlehttp/streams/src/Stream.php | 261 ++++ .../streams/src/StreamDecoratorTrait.php | 143 ++ .../streams/src/StreamInterface.php | 159 ++ .../vendor/guzzlehttp/streams/src/Utils.php | 196 +++ .../streams/tests/AppendStreamTest.php | 178 +++ .../streams/tests/AsyncReadStreamTest.php | 186 +++ .../streams/tests/BufferStreamTest.php | 69 + .../streams/tests/CachingStreamTest.php | 136 ++ .../streams/tests/DroppingStreamTest.php | 26 + .../tests/Exception/SeekExceptionTest.php | 16 + .../guzzlehttp/streams/tests/FnStreamTest.php | 89 ++ .../streams/tests/GuzzleStreamWrapperTest.php | 99 ++ .../streams/tests/InflateStreamTest.php | 16 + .../streams/tests/LazyOpenStreamTest.php | 64 + .../streams/tests/LimitStreamTest.php | 133 ++ .../streams/tests/NoSeekStreamTest.php | 41 + .../streams/tests/NullStreamTest.php | 39 + .../streams/tests/PumpStreamTest.php | 77 + .../tests/StreamDecoratorTraitTest.php | 147 ++ .../guzzlehttp/streams/tests/StreamTest.php | 252 ++++ .../guzzlehttp/streams/tests/UtilsTest.php | 155 ++ .../domain/vendor/react/promise/.gitignore | 4 + .../domain/vendor/react/promise/.travis.yml | 13 + .../domain/vendor/react/promise/CHANGELOG.md | 60 + .../domain/vendor/react/promise/LICENSE | 22 + .../domain/vendor/react/promise/README.md | 824 ++++++++++ .../domain/vendor/react/promise/composer.json | 22 + .../vendor/react/promise/phpunit.xml.dist | 25 + .../src/CancellablePromiseInterface.php | 11 + .../vendor/react/promise/src/Deferred.php | 60 + .../promise/src/ExtendedPromiseInterface.php | 26 + .../react/promise/src/FulfilledPromise.php | 68 + .../vendor/react/promise/src/LazyPromise.php | 58 + .../vendor/react/promise/src/Promise.php | 182 +++ .../react/promise/src/PromiseInterface.php | 11 + .../react/promise/src/PromisorInterface.php | 11 + .../react/promise/src/RejectedPromise.php | 74 + .../src/UnhandledRejectionException.php | 31 + .../vendor/react/promise/src/functions.php | 196 +++ .../react/promise/src/functions_include.php | 5 + .../react/promise/tests/DeferredTest.php | 42 + .../promise/tests/FulfilledPromiseTest.php | 50 + .../react/promise/tests/FunctionAllTest.php | 97 ++ .../react/promise/tests/FunctionAnyTest.php | 116 ++ .../tests/FunctionCheckTypehintTest.php | 118 ++ .../react/promise/tests/FunctionMapTest.php | 125 ++ .../react/promise/tests/FunctionRaceTest.php | 122 ++ .../promise/tests/FunctionReduceTest.php | 290 ++++ .../promise/tests/FunctionRejectTest.php | 64 + .../promise/tests/FunctionResolveTest.php | 102 ++ .../react/promise/tests/FunctionSomeTest.php | 126 ++ .../react/promise/tests/LazyPromiseTest.php | 107 ++ .../PromiseAdapter/CallbackPromiseAdapter.php | 40 + .../PromiseAdapterInterface.php | 14 + .../react/promise/tests/PromiseTest.php | 116 ++ .../tests/PromiseTest/CancelTestTrait.php | 206 +++ .../tests/PromiseTest/FullTestTrait.php | 15 + .../tests/PromiseTest/NotifyTestTrait.php | 336 +++++ .../PromiseTest/PromiseFulfilledTestTrait.php | 351 +++++ .../PromiseTest/PromisePendingTestTrait.php | 68 + .../PromiseTest/PromiseRejectedTestTrait.php | 492 ++++++ .../PromiseTest/PromiseSettledTestTrait.php | 86 ++ .../tests/PromiseTest/RejectTestTrait.php | 363 +++++ .../tests/PromiseTest/ResolveTestTrait.php | 258 ++++ .../promise/tests/RejectedPromiseTest.php | 50 + .../react/promise/tests/Stub/CallableStub.php | 10 + .../vendor/react/promise/tests/TestCase.php | 41 + .../vendor/react/promise/tests/bootstrap.php | 7 + ajax_forms/getKBSearchResults.php | 47 + ajax_forms/getNewResourceForm.php | 19 +- ajax_processing/searchResourceFromGokb.php | 41 + ajax_processing/submitNewResource.php | 2 +- images/loupe.png | Bin 0 -> 335 bytes js/forms/resourceNewForm.js | 32 +- resource.php | 2 +- 328 files changed, 42542 insertions(+), 11 deletions(-) create mode 100644 admin/classes/domain/GOKbTools.php create mode 100644 admin/classes/domain/composer.json create mode 100644 admin/classes/domain/composer.lock create mode 100755 admin/classes/domain/composer.phar create mode 100644 admin/classes/domain/vendor/autoload.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/CHANGELOG.md create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/CONTRIBUTING.md create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/LICENSE create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/README.md create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/UPGRADE.md create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/composer.json create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Client.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Endpoint.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/EndpointInterface.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/BaseOaipmhException.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/HttpException.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/MalformedResponseException.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/OaipmhException.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Granularity.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/CurlAdapter.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/GuzzleAdapter.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/HttpAdapterInterface.php create mode 100644 admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/RecordIterator.php create mode 100644 admin/classes/domain/vendor/composer/ClassLoader.php create mode 100644 admin/classes/domain/vendor/composer/autoload_classmap.php create mode 100644 admin/classes/domain/vendor/composer/autoload_files.php create mode 100644 admin/classes/domain/vendor/composer/autoload_namespaces.php create mode 100644 admin/classes/domain/vendor/composer/autoload_psr4.php create mode 100644 admin/classes/domain/vendor/composer/autoload_real.php create mode 100644 admin/classes/domain/vendor/composer/installed.json create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/.editorconfig create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/.gitignore create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/.travis.yml create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/CHANGELOG.md create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/LICENSE create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/Makefile create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/README.md create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/UPGRADING.md create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/build/packager.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/composer.json create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/Makefile create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_static/guzzle-icon.png create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_static/logo.png create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_templates/nav_links.html create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/clients.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/conf.py create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/events.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/faq.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/handlers.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/http-messages.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/index.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/overview.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/quickstart.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/requirements.txt create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/streams.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/docs/testing.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/phpunit.xml.dist create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/BatchResults.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Client.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/ClientInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Collection.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractEvent.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/BeforeEvent.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/CompleteEvent.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/Emitter.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/EmitterInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/EndEvent.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ErrorEvent.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/EventInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/HasEmitterInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/HasEmitterTrait.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ListenerAttacherTrait.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ProgressEvent.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/RequestEvents.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/SubscriberInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/ClientException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/ConnectException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/CouldNotRewindStreamException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/ParseException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/StateException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/TransferException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/XmlParseException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/HasDataTrait.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/AbstractMessage.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/FutureResponse.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageFactory.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageFactoryInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageParser.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/Request.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/RequestInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/Response.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/ResponseInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Mimetypes.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Pool.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/MultipartBody.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostBody.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostFile.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostFileInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Query.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/QueryParser.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/RequestFsm.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/RingBridge.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Cookie.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/History.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/HttpError.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Mock.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/ToArrayInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Transaction.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/UriTemplate.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Url.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/src/Utils.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/BatchResultsTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/ClientTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/CollectionTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/CookieJarTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/FileCookieJarTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/SessionCookieJarTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/SetCookieTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractEventTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractRequestEventTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractRetryableEventTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractTransferEventTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/BeforeEventTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/EmitterTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ErrorEventTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/HasEmitterTraitTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ListenerAttacherTraitTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ProgressEventTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/RequestEventsTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/ParseExceptionTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/RequestExceptionTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/XmlParseExceptionTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/IntegrationTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/AbstractMessageTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/FutureResponseTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/MessageFactoryTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/MessageParserTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/RequestTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/ResponseTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/MimetypesTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/PoolTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/MultipartBodyTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/PostBodyTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/PostFileTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/QueryParserTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/QueryTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/RequestFsmTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/RingBridgeTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Server.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/CookieTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/HistoryTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/HttpErrorTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/MockTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/PrepareTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/RedirectTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/TransactionTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UriTemplateTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UrlTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UtilsTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/bootstrap.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/guzzle/tests/perf.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/.gitignore create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/.travis.yml create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/CHANGELOG.md create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/LICENSE create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/Makefile create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/README.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/composer.json create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/docs/Makefile create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/docs/client_handlers.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/docs/client_middleware.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/docs/conf.py create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/docs/futures.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/docs/index.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/docs/requirements.txt create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/docs/spec.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/docs/testing.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/phpunit.xml.dist create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/CurlFactory.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/Middleware.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/MockHandler.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Core.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Exception/CancelledFutureAccessException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Exception/ConnectException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Exception/RingException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/BaseFutureTrait.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureValue.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/MagicFutureTrait.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/Server.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/server.js create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/CoreTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/ringphp/tests/bootstrap.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/.gitignore create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/.travis.yml create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/CHANGELOG.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/LICENSE create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/Makefile create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/README.rst create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/composer.json create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/phpunit.xml.dist create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/AppendStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/AsyncReadStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/BufferStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/CachingStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/DroppingStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/Exception/SeekException.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/FnStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/InflateStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/LazyOpenStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/LimitStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/NoSeekStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/NullStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/PumpStream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/Stream.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/StreamInterface.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/src/Utils.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/AppendStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/BufferStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/CachingStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/DroppingStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/FnStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/InflateStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/LimitStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/NullStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/PumpStreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/StreamTest.php create mode 100644 admin/classes/domain/vendor/guzzlehttp/streams/tests/UtilsTest.php create mode 100644 admin/classes/domain/vendor/react/promise/.gitignore create mode 100644 admin/classes/domain/vendor/react/promise/.travis.yml create mode 100644 admin/classes/domain/vendor/react/promise/CHANGELOG.md create mode 100644 admin/classes/domain/vendor/react/promise/LICENSE create mode 100644 admin/classes/domain/vendor/react/promise/README.md create mode 100644 admin/classes/domain/vendor/react/promise/composer.json create mode 100644 admin/classes/domain/vendor/react/promise/phpunit.xml.dist create mode 100644 admin/classes/domain/vendor/react/promise/src/CancellablePromiseInterface.php create mode 100644 admin/classes/domain/vendor/react/promise/src/Deferred.php create mode 100644 admin/classes/domain/vendor/react/promise/src/ExtendedPromiseInterface.php create mode 100644 admin/classes/domain/vendor/react/promise/src/FulfilledPromise.php create mode 100644 admin/classes/domain/vendor/react/promise/src/LazyPromise.php create mode 100644 admin/classes/domain/vendor/react/promise/src/Promise.php create mode 100644 admin/classes/domain/vendor/react/promise/src/PromiseInterface.php create mode 100644 admin/classes/domain/vendor/react/promise/src/PromisorInterface.php create mode 100644 admin/classes/domain/vendor/react/promise/src/RejectedPromise.php create mode 100644 admin/classes/domain/vendor/react/promise/src/UnhandledRejectionException.php create mode 100644 admin/classes/domain/vendor/react/promise/src/functions.php create mode 100644 admin/classes/domain/vendor/react/promise/src/functions_include.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/DeferredTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FulfilledPromiseTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FunctionAllTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FunctionAnyTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FunctionCheckTypehintTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FunctionMapTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FunctionRaceTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FunctionReduceTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FunctionRejectTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FunctionResolveTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/FunctionSomeTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/LazyPromiseTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest/FullTestTrait.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest/NotifyTestTrait.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/RejectedPromiseTest.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/Stub/CallableStub.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/TestCase.php create mode 100644 admin/classes/domain/vendor/react/promise/tests/bootstrap.php create mode 100644 ajax_forms/getKBSearchResults.php create mode 100644 ajax_processing/searchResourceFromGokb.php create mode 100644 images/loupe.png diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php new file mode 100644 index 0000000..6404e54 --- /dev/null +++ b/admin/classes/domain/GOKbTools.php @@ -0,0 +1,255 @@ +'; + if (is_null(self::$instance)){ + self::$instance = new GOKbTools(); + } + return self::$instance; + } + +// ------------------------------------------------------------------------- + /** + * private constuctor (design pattern singleton) + */ + private function __construct() + { + echo 'DEBUG_ constructeur tool _ START
'; + $this->titleClient = new Client(OAI_HOST.'titles'); + $this->packageClient = new Client(OAI_HOST.'packages'); + $this->titleEndpoint = new Endpoint($this->titleClient); + $this->packageEndpoint = new Endpoint($this->packageClient); + + + $this->httpClient = (class_exists('GuzzleHttp\Client')) ? new GuzzleAdapter() : new CurlAdapter(); + + echo 'DEBUG_ constructeur tools _ END
'; + } + +// ------------------------------------------------------------------------- + + /** + * Build and send a search query, return an array + * @param string $name the name searched + * @param string $type the type of searched resource + * @return array an array with all results [GOKb_identifer => name] + */ + public function searchByName($name, $type){ //voir comment avoir une unique fonction de génération de requete (prefix etc...), + switch ($type) { + case 'title': + $prefix = ''; + break; + case 'package': + $prefix = ''; + break; + default: + break; + } + + //query construction + $query = 'select distinct * where {'. + $query .= '?s a '.$prefix.' .'; + $query .= '?s ?o .'; + $query .= 'FILTER regex(?o, "'.$name.'", "i")} LIMIT 100'; + + //send the request and get results + $results = $this->sendSparqlQuery($query); + $res = $results->{"results"}->{"result"}; + + $tmp = array(); + foreach ($res as $a => $b ) { + $uri = $b->{'binding'}->{'uri'}; + $id = $this->UriToGokbId($uri); + $prefLabel = $b->{'binding'}[1]->{'literal'}; + $tmp["$id"] = $prefLabel; + } + + return $tmp; + + } + +// ------------------------------------------------------------------------- + + + +// ------------------------------------------------------------------------- + + /** + * Perform a request and return a OAI SimpleXML Document + * @param string $query the SPARQL request to perform + * @return \SimpleXMLElement An XML document + */ + private function sendSparqlQuery($query){ + $url = SPARQL_HOST.urlencode($query); + $url .= '&format=text%2Fxml&timeout=0&debug=on'; + + $tmpClient = new Client(); + try { + $response = $this->httpClient->request($url); + } catch (HttpException $e) { + $tmpClient->checkForOaipmhException($e); + $response = ''; + } + + $res = $tmpClient->decodeResponse($response); //decodeResponse protected: passé à public temporairement --> trouver une solution !! (héritage + surcharge ?) + return $res; + } + +// ------------------------------------------------------------------------- + /** + * Convert the resource's URI into its GOKb identifier + * http://www.gokb.org/data/packages/XX --> org.gokb.cred.Package:XX + * http://www.gokb.org/data/titles/XX --> org.gokb.cred.TitleInstance:XX + * http://www.gokb.org/data/orgs/XX --> org.gokb.cred.Org:XX + * @param string $uri resource's URI + * @return string resource's GOKb identifier + */ + + + private function UriToGokbId($uri) + { + $cut = explode('/', $uri); + $nb = count($cut); + + $gokbID = "org.gokb.cred."; + + switch ($cut[$nb-2]) { + case 'packages': + $gokbID .= 'Package'; + break; + case 'titles': + $gokbID .= 'TitleInstance'; + break; + case 'orgs': + $gokbID .= 'Org'; + break; + + default: + break; + } + + $gokbID .= ':'. $cut[$nb-1]; + + return $gokbID; + } +// ------------------------------------------------------------------------- + /** + * Perform a 'GetRecord' request + * @param string $type the resource's type + * @param string $gokbID the resource's GOKb identifier + * @return /XMLSimpleElement An XML document + */ + public function getDetails($type, $gokbID) + { + echo 'DEBUG_ getDetails('.$type.','.$gokbID.')
'; + switch ($type) { + case 'title': + $record = $this->titleEndpoint->getRecord($gokbID, 'gokb'); + break; + case 'package': + $record = $this->packageEndpoint->getRecord($gokbID, 'gokb'); + break; + default: + break; + } + + + $rec = $record->{'GetRecord'}->{'record'}->{'metadata'}->{'gokb'}->{$type}; + return $rec; + } + + +// ------------------------------------------------------------------------- + /** + * Extract and display the results included in an xml document + * _ Recursive function _ + * @param /SimpleXMLElement XML document to treat + * @return string content + */ + + public function displayRecord($xml){ + $string = ""; + if (count($xml->children()) > 0) { + $string = ""; + foreach ($xml->children() as $tag => $child) { + $string .= ''; + $string .= ''; + $string .= ''; + } + $string .='
'.$tag.''.$this->displayRecord($child).'
'; + } + else{ + if ($xml->getName() == 'identifier') + { + $string = ''; + foreach ($xml->attributes() as $key => $value) { + $string .= ''.$value.''; + } + + $string .= ''; + } elseif ($xml != "") { + $string = $xml; + } else { + $string = "Empty"; + } + } + return $string; + } +// ------------------------------------------------------------------------- +// ------------------------------------------------------------------------- + +} + + + +?> \ No newline at end of file diff --git a/admin/classes/domain/composer.json b/admin/classes/domain/composer.json new file mode 100644 index 0000000..7dbf539 --- /dev/null +++ b/admin/classes/domain/composer.json @@ -0,0 +1,14 @@ + + +{ + "require": { + "caseyamcl/phpoaipmh": "~2.0", + "guzzlehttp/guzzle": "~5.0" + }, + "autoload": { + "psr-0": { + "Phpoaipmh": "vendor/caseyamcl/phpoaipmh/src" + } + } +} + diff --git a/admin/classes/domain/composer.lock b/admin/classes/domain/composer.lock new file mode 100644 index 0000000..41cd578 --- /dev/null +++ b/admin/classes/domain/composer.lock @@ -0,0 +1,280 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "b880d2805471a908265960c193177fec", + "packages": [ + { + "name": "caseyamcl/phpoaipmh", + "version": "v2.4", + "source": { + "type": "git", + "url": "https://github.com/caseyamcl/phpoaipmh.git", + "reference": "8a8a10e34e6d6b7f30849617aa7100b52331d0ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/caseyamcl/phpoaipmh/zipball/8a8a10e34e6d6b7f30849617aa7100b52331d0ef", + "reference": "8a8a10e34e6d6b7f30849617aa7100b52331d0ef", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "~5.0", + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0", + "symfony/config": "~2.5", + "symfony/console": "~2.5", + "symfony/dependency-injection": "~2.5", + "symfony/yaml": "~2.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "Phpoaipmh": [ + "src/", + "tests" + ] + }, + "psr-4": { + "Phpoaipmh\\Example\\": "example/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Casey McLaughlin", + "email": "caseyamcl@gmail.com", + "homepage": "http://caseymclaughlin.com", + "role": "Developer" + } + ], + "description": "A PHP OAI-PMH 2.0 Harvester library", + "homepage": "https://github.com/caseyamcl/phpoaipmh", + "keywords": [ + "Harvester", + "OAI", + "OAI-PMH" + ], + "time": "2015-05-18 14:40:02" + }, + { + "name": "guzzlehttp/guzzle", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f3c8c22471cb55475105c14769644a49c3262b93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f3c8c22471cb55475105c14769644a49c3262b93", + "reference": "f3c8c22471cb55475105c14769644a49c3262b93", + "shasum": "" + }, + "require": { + "guzzlehttp/ringphp": "^1.1", + "php": ">=5.4.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0", + "psr/log": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-05-20 03:47:55" + }, + { + "name": "guzzlehttp/ringphp", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/RingPHP.git", + "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", + "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", + "shasum": "" + }, + "require": { + "guzzlehttp/streams": "~3.0", + "php": ">=5.4.0", + "react/promise": "~2.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Ring\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", + "time": "2015-05-20 03:37:09" + }, + { + "name": "guzzlehttp/streams", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/streams.git", + "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Provides a simple abstraction over streams of data", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "Guzzle", + "stream" + ], + "time": "2014-10-12 19:18:40" + }, + { + "name": "react/promise", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/365fcee430dfa4ace1fbc75737ca60ceea7eeeef", + "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@googlemail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "time": "2014-12-30 13:32:42" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/admin/classes/domain/composer.phar b/admin/classes/domain/composer.phar new file mode 100755 index 0000000000000000000000000000000000000000..c611c4e5fba57c9e6f8d97ecf853b2df4da61ef8 GIT binary patch literal 1107672 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�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 +var_dump($result); + +// Results will be iterator of SimpleXMLElement objects +$results = $myEndpoint->listMetadataFormats(); +foreach($results as $item) { + var_dump($item); +} +``` + +Get a lists of records: + + +```php +// Recs will be an iterator of SimpleXMLElement objects +$recs = $myEndpoint->listRecords('someMetaDataFormat'); + +// The iterator will continue retrieving items across multiple HTTP requests. +// You can keep running this loop through the *entire* collection you +// are harvesting. All OAI-PMH and HTTP pagination logic is hidden neatly +// behind the iterator API. +foreach($recs as $rec) { + var_dump($rec); +} +``` + +Optionally, specify a date/time granularity level to use for date-based queries: + +```php +use Phpoaipmh\Client, + Phpoaipmh\Endpoint, + Phpoaipmh\Granularity; + +$client = new Client('http://some.service.com/oai'); +$myEndpoint = new Endpoint($client, Granularity::DATE_AND_TIME); +``` + +Handling Results +---------------- +Depending on the verb you use, the library will send back either a `SimpleXMLELement` +or an iterator containing `SimpleXMLElement` objects. + +* For `identify` and `getRecord`, a `SimpleXMLElement` object is returned +* For `listMetadataFormats`, `listSets`, `listIdentifiers`, and `listRecords` a `Phpoaipmh\ResponseIterator` is returned + +The `Phpoaipmh\ResponseIterator` object encapsulates the logic to iterate through paginated sets of records. + + +Handling Errors +--------------- + +This library will throw different exceptions under different circumstances: + +* HTTP request errors will generate a `Phpoaipmh\Exception\HttpException` +* Response body parsing issues (e.g. invalid XML) will generate a `Phpoaipmh\Exception\MalformedResponseException` +* OAI-PMH protocol errors (e.g. invalid verb or missing params) will generate a `Phpoaipmh\Exception\OaipmhException` + +All exceptions extend the `Phpoaipmh\Exception\BaseoaipmhException` class. + +Customizing Default Request Parameters +-------------------------------------- + +You can customize the default request parameters (for example, request timeout) for both cURL and Guzzle +clients by building the adapter objects manually. + +To customize cURL parameters, pass them in as an array of key/value items to `CurlAdapter::setCurlOpts()`: + +```php +use Phpoaipmh\Client, + Phpoaipmh\HttpAdapter\CurlAdapter; + +$adapter = new CurlAdapter(); +$adapter->setCurlOpts([CURLOPT_TIMEOUT => 120]); +$client = new Client('http://some.service.com/oai', $adapter); + +$myEndpoint = new Endpoint($client); +``` + +If you're using Guzzle, you can set the parameters in a similar way: + +```php +use Phpoaipmh\Client, + Phpoaipmh\HttpAdapter\GuzzleAdapter; + +$adapter = new GuzzleAdapter(); +$adapter->getGuzzleClient()->setDefaultOption('timeout', 120); +$client = new Client('http://some.service.com/oai', $adapter); + +$myEndpoint = new Endpoint($client); +``` + +Dealing with XML Namespaces +--------------------------- + +Many OAI-PMH XML documents make use of XML Namespaces. For non-XML experts, it can be confusing to implement +these in PHP. SitePoint has a brief but excellent [overview of how to use Namespaces in SimpleXML](http://www.sitepoint.com/simplexml-and-namespaces/). + + +Iterator Metadata +----------------- + +The `Phpoaipmh\RecordIterator` iterator contains some helper methods: + +* `getNumRequests()` - Returns the number of HTTP requests made thus far +* `getNumRetrieved()` - Returns the number of individual records retrieved +* `getTotalRecordsInCollection()` - Returns the total number of records in the collection + * *Note* - This number should be treated as an estimate at best. The number of records + can change while the records are being retrieved, so it is not guaranteed to be accurate. + Also, many OAI-PMH endpoints do not provide this information, in which case, this method will + return `null`. +* `reset()` - Resets the iterator, which will restart the record retrieval from scratch. + + +Handling 503 `Retry-After` Responses +------------------------------------ + +Some OAI-PMH endpoints employ rate-limiting so that you can only make X number +of requests in a given time period. These endpoints will return a `503 Retry-AFter` +HTTP status code if your code generates too many HTTP requests too quickly. + +If you have installed [Guzzle](http://guzzlephp.org), then you can use the +[Retry-Subscriber](https://github.com/guzzle/retry-subscriber) to automatically +adhere to the OAI-PMH endpoint rate-limiting rules. + +First, make sure you include the retry-subscriber as a dependency in your +`composer.json`: + + require: { + /* ... */ + "guzzlehttp/retry-subscriber": "~2.0" + } + +Then, when loading the Phpoaipmh libraries, instantiate the Guzzle adapter +manually, and add the subscriber as indicated in the code below: + +```php +// Create a Retry Guzzle Subscriber +$retrySubscriber = new \GuzzleHttp\Subscriber\Retry\RetrySubscriber([ + 'delay' => function($numRetries, \GuzzleHttp\Event\AbstractTransferEvent $event) { + $waitSecs = $event->getResponse()->getHeader('Retry-After') ?: '5'; + return ($waitSecs * 1000) + 1000; // wait one second longer than the server said to + }, + 'filter' => \GuzzleHttp\Subscriber\Retry\RetrySubscriber::createStatusFilter(), +]); + +// Manually create a Guzzle HTTP adapter +$guzzleAdapter = new \Phpoaipmh\HttpAdapter\Guzzle(); +$guzzleAdapter->getGuzzleClient()->getEmitter()->attach($retrySubscriber); + +$client = new \Phpoaipmh\Client('http://some.service.com/oai', $guzzleAdapter); +``` + +This will create a client that adheres to the rate-limiting rules enforced by the OAI-PMH record provider. + + +Sending Arbitrary Query Parameters +---------------------------------- + +If you wish to send arbitrary HTTP query parameters with your requests, you can +send them via the `\Phpoaipmh\Client` class: + + $client = new \Phpoaipmh\Client('http://some.service.com/oai'); + $client->request('Identify', ['some' => 'extra-param']); + +Alternatively, if you wish to send arbitrary parameters while taking advantage of the +convenience of the `\Phpoaipmh\Endpoint` class, you can use the Guzzle event system: + +```php +// Create a function or class to add parameters to a request +$addParamsListener = function(\GuzzleHttp\Event\BeforeEvent $event) { + $req = $event->getRequest(); + $req->getQuery()->add('api_key', 'xyz123'); + + // You could do other things to the request here, too, like adding a header.. + $req->addHeader('Some-Header', 'some-header-value'); +}; + +// Manually create a Guzzle HTTP adapter +$guzzleAdapter = new \Phpoaipmh\HttpAdapter\Guzzle(); +$guzzleAdapter->getGuzzleClient()->getEmitter()->on('before', $addParamsListener); + +$client = new \Phpoaipmh\Client('http://some.service.com/oai', $guzzleAdapter); +``` + +Implementation Tips +------------------- + +Harvesting data from a OAI-PMH endpoint can be a time-consuming task, especially when there are lots of records. +Typically, this kind of task is done via a CLI script or background process that can run for a long time. +It is not normally a good idea to make it part of a web request. + +Credits +------- + +* [Casey McLaughlin](http://github.com/caseyamcl) +* [Christian Scheb](https://github.com/scheb) +* [Matthias Vandermaesen](https://github.com/netsensei) +* [All Contributors](https://github.com/caseyamcl/phpoaipmh/contributors) + +License +------- + +MIT License; see [LICENSE](LICENSE) file for details diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/UPGRADE.md b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/UPGRADE.md new file mode 100644 index 0000000..283dda2 --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/UPGRADE.md @@ -0,0 +1,20 @@ +Upgrading from Version 1.x to 2.x +================================= + +* Usages of `Phpoaipmh\Http\Guzzle` should now instead use `Phpoaipmh\HttpAdapter\GuzzleAdapter`. +* Usages of `Phpoaipmh\Http\Curl` should now instead use `Phpoaipmh\HttpAdapter\CurlAdapter`. +* Any class that implemets the `Phpoaipmh\Http\Client` interface should now instead implement `Phpoaipmh\HttpAdapter\HttpAdapterInteraface`. +* Change typhints or references for `Phpoaipmh\ResponseList` to `Phpoaipmh\RecordIterator`. +* If using Guzzle, ensure that you upgrade to Version 5 or later. +* Remove any usage of the `Phpoaipmh\Endpoint::processList()` method. It is no longer necessary, since + all methods now return an iterator object by default. + * If you absolutely must convert the iterator to an array, use PHP's built-in `iterator_to_array()` function. However, + this is not recommended, since it may take a very long time to execute. +* Exception class names have changed: + * `Phpoaipmh\OaipmhRequestException` is now `Phpoaipmh\Exception\OaipmhException` + * `Phpoaipmh\Client\RequestException` is now `Phpoaipmh\Exception\HttpException` + * `Phpoaipmh\Exception\OaipmhException` is now `Phpoaipmh\Exception\BaseOaipmhException` + * Previously, malformed XML would throw a `Phpoaipmh\OaipmhRequestException`. It now throws a + `Phpoaipmh\Exception\MalformedResponseException`. + * All exceptions extend the `Phpoaipmh\Exception\BaseOaipmhException`, so you can use that as a catch-all. +* Added example \ No newline at end of file diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/composer.json b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/composer.json new file mode 100644 index 0000000..1e7fc16 --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/composer.json @@ -0,0 +1,36 @@ +{ + "name": "caseyamcl/phpoaipmh", + "type": "library", + "description": "A PHP OAI-PMH 2.0 Harvester library", + "keywords": ["OAI", "Harvester", "OAI-PMH"], + "homepage": "https://github.com/caseyamcl/phpoaipmh", + "authors": [ + { + "name": "Casey McLaughlin", + "email": "caseyamcl@gmail.com", + "homepage": "http://caseymclaughlin.com", + "role": "Developer" + } + ], + "license": "MIT", + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "~5.0", + "phpunit/phpunit": "~4.0", + "mockery/mockery": "~0.9", + "symfony/console": "~2.5", + "symfony/dependency-injection": "~2.5", + "symfony/config": "~2.5", + "symfony/yaml": "~2.5" + }, + "autoload": { + "psr-0": { + "Phpoaipmh": ["src/", "tests"] + }, + "psr-4": { + "Phpoaipmh\\Example\\": "example/src/" + } + } +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Client.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Client.php new file mode 100644 index 0000000..729dd50 --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Client.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * ------------------------------------------------------------------ + */ + +namespace Phpoaipmh; + +use Phpoaipmh\Exception\HttpException; +use Phpoaipmh\Exception\OaipmhException; +use Phpoaipmh\Exception\MalformedResponseException; +use Phpoaipmh\HttpAdapter\CurlAdapter; +use Phpoaipmh\HttpAdapter\GuzzleAdapter; +use Phpoaipmh\HttpAdapter\HttpAdapterInterface; +use RuntimeException; + +/** + * OAI-PMH Client class retrieves and decodes OAI-PMH from a given URL + * + * @since v1.0 + * @author Casey McLaughlin + */ +class Client +{ + /** + * @var string + */ + private $url; + + /** + * @var HttpAdapterInterface + */ + private $httpClient; + + // ------------------------------------------------------------------------- + + /** + * Constructor + * + * @param string $url The URL of the OAI-PMH Endpoint + * @param HttpAdapterInterface $httpClient Optional HTTP HttpAdapterInterface class; attempt to auto-build dependency if not passed + */ + public function __construct($url = null, HttpAdapterInterface $httpClient = null) + { + $this->setUrl($url); + + if ($httpClient) { + $this->httpClient = $httpClient; + } else { + $this->httpClient = (class_exists('GuzzleHttp\Client')) + ? new GuzzleAdapter() + : new CurlAdapter(); + } + } + + // ------------------------------------------------------------------------- + + /** + * Set the URL + * + * @param string $url + */ + public function setUrl($url) + { + $this->url = $url; + } + + // ------------------------------------------------------------------------- + + /** + * Perform a request and return a OAI SimpleXML Document + * + * @param string $verb Which OAI-PMH verb to use + * @param array $params An array of key/value parameters + * @return \SimpleXMLElement An XML document + */ + public function request($verb, array $params = array()) + { + if (! $this->url) { + throw new RuntimeException("Cannot perform request when URL not set. Use setUrl() method"); + } + + //Build the URL + $params = array_merge(array('verb' => $verb), $params); + $url = $this->url . (parse_url($this->url, PHP_URL_QUERY) ? '&' : '?') . http_build_query($params); + + //Do the request + try { + $resp = $this->httpClient->request($url); + } catch (HttpException $e) { + $this->checkForOaipmhException($e); + $resp = ''; + } + + return $this->decodeResponse($resp); + } + + // ------------------------------------------------------------------------- + + /** + * Check for OAI-PMH Exception from HTTP Exception + * + * Converts a HttpException into an OAI-PMH exception if there is an + * OAI-PMH Error Code. + * + * @param HttpException $httpException + */ + private function checkForOaipmhException(HttpException $httpException) + { + try { + if ($resp = $httpException->getBody()) { + $this->decodeResponse($resp); // Throw OaipmhException in case of an error + } + } catch (MalformedResponseException $e) { + // There was no valid OAI error in the response, therefore re-throw HttpException + } + + throw $httpException; + } + + // ------------------------------------------------------------------------- + + /** + * Decode the response into XML + * + * @param string $resp The response body from a HTTP request + * @return \SimpleXMLElement An XML document + */ + public function decodeResponse($resp) + { + //Setup a SimpleXML Document + try { + $xml = @new \SimpleXMLElement($resp); + } catch (\Exception $e) { + throw new MalformedResponseException(sprintf("Could not decode XML Response: %s", $e->getMessage())); + } + + //If we get back a OAI-PMH error, throw a OaipmhException + if (isset($xml->error)) { + $code = (string) $xml->error['code']; + $msg = (string) $xml->error; + + throw new OaipmhException($code, $msg); + } + + return $xml; + } +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Endpoint.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Endpoint.php new file mode 100644 index 0000000..759997f --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Endpoint.php @@ -0,0 +1,244 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * ------------------------------------------------------------------ + */ + +namespace Phpoaipmh; + +use DateTime; + +/** + * OAI-PMH Endpoint Class + * + * @since v1.0 + * @author Casey McLaughlin + */ +class Endpoint implements EndpointInterface +{ + const AUTO = null; + + // --------------------------------------------------------------- + + /** + * @var Client + */ + private $client; + + /** + * @var string + */ + private $granularity; + + // ------------------------------------------------------------------------- + + /** + * Constructor + * + * @param Client $client Optional; will attempt to auto-build dependency if not passed + * @param string $granularity Optional; the OAI date format for fetching records, use constants from Granularity class + */ + public function __construct(Client $client = null, $granularity = self::AUTO) + { + $this->client = $client ?: new Client(); + $this->granularity = $granularity; + } + + // ------------------------------------------------------------------------- + + /** + * Set the URL in the client + * + * @param string $url + */ + public function setUrl($url) + { + $this->client->setUrl($url); + } + + // ------------------------------------------------------------------------- + + /** + * Identify the OAI-PMH Endpoint + * + * @return \SimpleXMLElement A XML document with attributes describing the repository + */ + public function identify() + { + $resp = $this->client->request('Identify'); + + return $resp; + } + + // ------------------------------------------------------------------------- + + /** + * List Metadata Formats + * + * Return the list of supported metadata format for a particular record (if $identifier + * is provided), or the entire repository (if no arguments are provided) + * + * @param string $identifier If specified, will return only those metadata formats that a particular record supports + * @return RecordIterator + */ + public function listMetadataFormats($identifier = null) + { + $params = ($identifier) ? array('identifier' => $identifier) : array(); + + return new RecordIterator($this->client, 'ListMetadataFormats', $params); + } + + // ------------------------------------------------------------------------- + + /** + * List Record Sets + * + * @return RecordIterator + */ + public function listSets() + { + return new RecordIterator($this->client, 'ListSets'); + } + + // ------------------------------------------------------------------------- + + /** + * Get a single record + * + * @param string $id Record Identifier + * @param string $metadataPrefix Required by OAI-PMH endpoint + * @return \SimpleXMLElement An XML document corresponding to the record + */ + public function getRecord($id, $metadataPrefix) + { + $params = array( + 'identifier' => $id, + 'metadataPrefix' => $metadataPrefix + ); + + return $this->client->request('GetRecord', $params); + } + + // ------------------------------------------------------------------------- + + /** + * List Record identifiers + * + * Corresponds to OAI Verb to list record identifiers + * + * @param string $metadataPrefix Required by OAI-PMH endpoint + * @param \DateTime $from An optional 'from' date for selective harvesting + * @param \DateTime $until An optional 'until' date for selective harvesting + * @param string $set An optional setSpec for selective harvesting + * @return RecordIterator + */ + public function listIdentifiers($metadataPrefix, $from = null, $until = null, $set = null) + { + return $this->createRecordIterator("ListIdentifiers", $metadataPrefix, $from, $until, $set); + } + + // ------------------------------------------------------------------------- + + /** + * List Records + * + * Corresponds to OAI Verb to list records + * + * @param string $metadataPrefix Required by OAI-PMH endpoint + * @param \DateTime $from An optional 'from' date for selective harvesting + * @param \DateTime $until An optional 'from' date for selective harvesting + * @param string $set An optional setSpec for selective harvesting + * @return RecordIterator + */ + public function listRecords($metadataPrefix, $from = null, $until = null, $set = null) + { + return $this->createRecordIterator("ListRecords", $metadataPrefix, $from, $until, $set); + } + + // ------------------------------------------------------------------------- + + /** + * Create a record iterator + * + * @param string $verb OAI Verb + * @param string $metadataPrefix Required by OAI-PMH endpoint + * @param \DateTime|null $from An optional 'from' date for selective harvesting + * @param \DateTime|null $until An optional 'from' date for selective harvesting + * @param string $set An optional setSpec for selective harvesting + * + * @return RecordIterator + */ + private function createRecordIterator($verb, $metadataPrefix, $from, $until, $set) + { + $params = array('metadataPrefix' => $metadataPrefix); + + if ($from instanceof \DateTime) { + $params['from'] = Granularity::formatDate($from, $this->getGranularity()); + } elseif (null !== $from) { + trigger_error(sprintf( + 'Deprecated: %s::%s \'from\' parameter should be an instance of \DateTime (string param support to be removed in v3.0)', + get_called_class(), + lcfirst($verb) + ), E_USER_DEPRECATED); + } + + if ($until instanceof \DateTime) { + $params['until'] = Granularity::formatDate($until, $this->getGranularity()); + } elseif (null !== $until) { + trigger_error(sprintf( + 'Deprecated: %s::%s \'until\' parameter should be an instance of \DateTime (string param support to be removed in v3.0)', + get_called_class(), + lcfirst($verb) + ), E_USER_DEPRECATED); + } + + if ($set) { + $params['set'] = $set; + } + + return new RecordIterator($this->client, $verb, $params); + } + + // --------------------------------------------------------------- + + /** + * Lazy load granularity from Identify, if not specified + * + * @return string + */ + private function getGranularity() + { + if ($this->granularity === null) { + $this->granularity = $this->fetchGranularity(); + } + + return $this->granularity; + } + + // --------------------------------------------------------------- + + /** + * Attempt to fetch date format from Identify endpoint (or use default) + * + * @return string + */ + private function fetchGranularity() + { + $response = $this->identify(); + + return (isset($response->Identify->granularity)) + ? (string) $response->Identify->granularity + : Granularity::DATE; + } +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/EndpointInterface.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/EndpointInterface.php new file mode 100644 index 0000000..dbb97bd --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/EndpointInterface.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * ------------------------------------------------------------------ + */ + +namespace Phpoaipmh; + +/** + * OAI-PMH Endpoint Interface + * + * @since v2.3 + * @author Casey McLaughlin + */ +interface EndpointInterface +{ + /** + * Set the URL in the client + * + * @param string $url + */ + public function setUrl($url); + + /** + * Identify the OAI-PMH Endpoint + * + * @return \SimpleXMLElement A XML document with attributes describing the + * repository + */ + public function identify(); + + /** + * List Metadata Formats + * + * Return the list of supported metadata format for a particular record (if + * $identifier is provided), or the entire repository (if no arguments are + * provided) + * + * @param string $identifier If specified, will return only those + * metadata formats that a particular + * record supports + * @return RecordIterator + */ + public function listMetadataFormats($identifier = null); + + /** + * List Record Sets + * + * @return RecordIterator + */ + public function listSets(); + + /** + * Get a single record + * + * @param string $id Record Identifier + * @param string $metadataPrefix Required by OAI-PMH endpoint + * @return \SimpleXMLElement An XML document corresponding to the record + */ + public function getRecord($id, $metadataPrefix); + + /** + * List Record identifiers + * + * Corresponds to OAI Verb to list record identifiers + * + * @param string $metadataPrefix Required by OAI-PMH endpoint + * @param \DateTime $from An optional 'from' date for + * selective harvesting + * @param \DateTime $until An optional 'until' date for + * selective harvesting + * @param string $set An optional setSpec for selective + * harvesting + * @return RecordIterator + */ + public function listIdentifiers($metadataPrefix, $from = null, $until = null, $set = null); + + /** + * List Records + * + * Corresponds to OAI Verb to list records + * + * @param string $metadataPrefix Required by OAI-PMH endpoint + * @param \DateTime $from An optional 'from' date for + * selective harvesting + * @param \DateTime $until An optional 'from' date for + * selective harvesting + * @param string $set An optional setSpec for selective + * harvesting + * @return RecordIterator + */ + public function listRecords($metadataPrefix, $from = null, $until = null, $set = null); +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/BaseOaipmhException.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/BaseOaipmhException.php new file mode 100644 index 0000000..716e1e9 --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/BaseOaipmhException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * ------------------------------------------------------------------ + */ + +namespace Phpoaipmh\Exception; + +/** + * Base exception class for Phpoaipmh Library + * + * @author Casey McLaughlin + * @since v2.0 + */ +class BaseOaipmhException extends \RuntimeException +{ + // pass.. +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/HttpException.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/HttpException.php new file mode 100644 index 0000000..9a453ab --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/HttpException.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * ------------------------------------------------------------------ + */ + +namespace Phpoaipmh\Exception; + +/** + * HttpAdapter Protocol Exception Class thrown when HTTP transmission errors occur + * + * @author Casey McLaughlin + * @since v2.0 + */ +class HttpException extends BaseOaipmhException +{ + private $body; + + /** + * Constructor + * + * @param string $responseBody Empty if no response body provided + * @param int $message + * @param int $code + * @param \Exception $previous + */ + public function __construct($responseBody, $message, $code = 0, \Exception $previous = null) + { + $this->body = $responseBody; + parent::__construct($message, $code, $previous); + } + + /** + * @return string + */ + public function getBody() + { + return $this->body; + } +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/MalformedResponseException.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/MalformedResponseException.php new file mode 100644 index 0000000..a005d14 --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/MalformedResponseException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * ------------------------------------------------------------------ + */ + +namespace Phpoaipmh\Exception; + +/** + * Class MalformedResponseException + * + * Thrown when the HTTP response body cannot be parsed into valid OAI-PMH (usually XML errors) + * + * @author Casey McLaughlin + * @since v2.0 + */ +class MalformedResponseException extends BaseOaipmhException +{ + // pass.. +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/OaipmhException.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/OaipmhException.php new file mode 100644 index 0000000..639c8c0 --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Exception/OaipmhException.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * ------------------------------------------------------------------ + */ + +namespace Phpoaipmh\Exception; + +/** + * OAI-PMH protocol Exception Class thrown when OAI-PMH protocol errors occur + * + * @author Casey McLaughlin + * @since v2.0 + */ +class OaipmhException extends BaseOaipmhException +{ + private $oaiErrorCode; + + // ------------------------------------------------------------------------- + + public function __construct($oaiErrorCode, $message, $code = 0, \Exception $previous = null) + { + $this->oaiErrorCode = $oaiErrorCode; + parent::__construct($message, $code, $previous); + } + + // ------------------------------------------------------------------------- + + public function __toString() + { + $refl = new \ReflectionClass($this); + return $refl->getShortName() . ": [{$this->code}]: ({$this->oaiErrorCode}) {$this->message}"; + } + + public function getOaiErrorCode() + { + return $this->oaiErrorCode; + } +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Granularity.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Granularity.php new file mode 100644 index 0000000..a4c6016 --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/Granularity.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * ------------------------------------------------------------------ + */ + +/** + * Granularity class provides utility for specifying date and constraint precision + * + * @author Christian Scheb + */ +class Granularity +{ + const DATE = "YYYY-MM-DD"; + const DATE_AND_TIME = "YYYY-MM-DDThh:mm:ssZ"; + + // --------------------------------------------------------------- + + /** + * Format DateTime string based on granularity + * + * @param \DateTime $dateTime + * @param string $format Either self::DATE or self::DATE_AND_TIME + * + * @return string + */ + public static function formatDate(\DateTime $dateTime, $format) + { + $phpFormats = array( + self::DATE => "Y-m-d", + self::DATE_AND_TIME => 'Y-m-d\TH:i:s\Z', + ); + $phpFormat = $phpFormats[$format]; + + return $dateTime->format($phpFormat); + } +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/CurlAdapter.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/CurlAdapter.php new file mode 100644 index 0000000..114d50f --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/CurlAdapter.php @@ -0,0 +1,96 @@ + true, + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_DNS_CACHE_TIMEOUT => 10, + CURLOPT_TIMEOUT => 60, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 3, + CURLOPT_USERAGENT => 'PHP OAI-PMH Library', + ]; + + // ------------------------------------------------------------------------- + + /** + * Constructor + * + * Checks for CURL libraries + * + * @param array $curlOpts Array of CURL directives and values (e.g. [CURLOPT_TIMEOUT => 120]) + * @throws \Exception If CURL not installed. + */ + public function __construct(array $curlOpts = []) + { + if (! is_callable('curl_exec')) { + throw new \Exception("OAI-PMH CurlAdapter HTTP HttpAdapterInterface requires the CURL PHP Extension"); + } + + $this->setCurlOpts($curlOpts); + } + + // --------------------------------------------------------------- + + /** + * Set cURL Options at runtime + * + * Sets cURL options. If $merge is true, then merges desired params with existing. + * If $merge is false, then clobbers the existing cURL options + * + * @param array $opts + * @param bool $merge + */ + public function setCurlOpts(array $opts, $merge = true) + { + $this->curlOpts = ($merge) + ? array_replace($this->curlOpts, $opts) + : $opts; + } + + // ------------------------------------------------------------------------- + + /** + * Do CURL Request + * + * @param string $url The full URL + * @return string The response body + */ + public function request($url) + { + $curlOpts = array_replace($this->curlOpts, [CURLOPT_URL => $url]); + + $ch = curl_init(); + foreach ($curlOpts as $opt => $optVal) { + curl_setopt($ch, $opt, $optVal); + } + + $resp = curl_exec($ch); + $info = (object) curl_getinfo($ch); + curl_close($ch); + + //Check response + $httpCode = (string) $info->http_code; + if ($httpCode{0} != '2') { + $msg = sprintf('HTTP Request Failed (code %s): %s', $info->http_code, $resp); + throw new HttpException($resp, $msg, $httpCode); + } elseif (strlen(trim($resp)) == 0) { + throw new HttpException($resp, 'HTTP Response Empty'); + } + + return $resp; + } +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/GuzzleAdapter.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/GuzzleAdapter.php new file mode 100644 index 0000000..4200c3f --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/GuzzleAdapter.php @@ -0,0 +1,67 @@ +guzzle = $guzzle ?: new GuzzleClient(); + } + + // ---------------------------------------------------------------- + + /** + * Get the Guzzle Client + * + * @return GuzzleClient + */ + public function getGuzzleClient() + { + return $this->guzzle; + } + + // ---------------------------------------------------------------- + + /** + * Do the request with GuzzleAdapter + * + * @param string $url + * @return string + * @throws HttpException + */ + public function request($url) + { + try { + $resp = $this->guzzle->get($url); + return (string) $resp->getBody(); + } catch (RequestException $e) { + $response = $e->getResponse(); + throw new HttpException($response ? $response->getBody() : null, $e->getMessage(), $e->getCode(), $e); + } catch (TransferException $e) { + throw new HttpException('', $e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/HttpAdapterInterface.php b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/HttpAdapterInterface.php new file mode 100644 index 0000000..d88f745 --- /dev/null +++ b/admin/classes/domain/vendor/caseyamcl/phpoaipmh/src/Phpoaipmh/HttpAdapter/HttpAdapterInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * ------------------------------------------------------------------ + */ + +namespace Phpoaipmh; + +use Phpoaipmh\Exception\BaseOaipmhException; +use Phpoaipmh\Exception\MalformedResponseException; + +/** + * Response List Entity iterates over records returned from an OAI-PMH Endpoint + * + * @author Casey McLaughlin + * @since v2.0 + */ +class RecordIterator implements \Iterator +{ + /** + * @var Client + */ + private $httpClient; + + /** + * @var string The verb to use + */ + private $verb; + + /** + * @var array OAI-PMH parameters passed as part of the request + */ + private $params; + + /** + * @var int The number of total entities (if available) + */ + private $totalRecordsInCollection; + + /** + * @var \DateTime Recordset expiration date (if specified) + */ + private $expireDate; + + /** + * @var string The resumption token + */ + private $resumptionToken; + + /** + * @var array Array of records + */ + private $batch; + + /** + * @var int Total number of records processed + */ + private $numProcessed = 0; + + /** + * @var boolean Total number of requests made + */ + private $numRequests = 0; + + /** + * @var \SimpleXMLElement|null Used for tracking the iterator + */ + private $currItem; + + // ------------------------------------------------------------------------- + + /** + * Constructor + * + * @param Client $httpClient The client to use + * @param string $verb The verb to use when retrieving results from the client + * @param array $params Optional parameters passed to OAI-PMH + */ + public function __construct(Client $httpClient, $verb, array $params = array()) + { + //Set parameters + $this->httpClient = $httpClient; + $this->verb = $verb; + $this->params = $params; + + //Node name error? + if (! $this->getItemNodeName()) { + throw new BaseOaipmhException('Cannot determine item name for verb: ' . $this->verb); + } + } + + // ------------------------------------------------------------------------- + + /** + * Get the total number of requests made during this run + * + * @return int The number of HTTP requests made + */ + public function getNumRequests() + { + return $this->numRequests; + } + + // ------------------------------------------------------------------------- + + /** + * Get the total number of records processed during this run + * + * @return int The number of records processed + */ + public function getNumRetrieved() + { + return $this->numProcessed; + } + + // ------------------------------------------------------------------------- + + /** + * Get the total number of records in the collection if available + * + * This only returns a value if the OAI-PMH server provides this information + * in the response, which not all servers do (it is optional in the OAI-PMH spec) + * + * Also, the number of records may change during the requests, so it should + * be treated as an estimate + * + * @return int|null + */ + public function getTotalRecordsInCollection() + { + if ($this->currItem === null) { + $this->next(); + } + + return $this->totalRecordsInCollection; + } + + // ------------------------------------------------------------------------- + + /** + * Get the next item + * + * Return an item from the currently-retrieved batch, get next batch and + * return first record from it, or return false if no more records + * + * @return \SimpleXMLElement|bool + */ + public function nextItem() + { + //If no items in batch, and we have a resumptionToken or need to make initial request... + if (count($this->batch) == 0 && ($this->resumptionToken or $this->numRequests == 0)) { + $this->retrieveBatch(); + } + + //if still items in current batch, return one + if (count($this->batch) > 0) { + $this->numProcessed++; + + $item = array_shift($this->batch); + $this->currItem = new \SimpleXMLElement($item->asXML()); + } else { + $this->currItem = false; + } + + return $this->currItem; + } + + // ------------------------------------------------------------------------- + + /** + * Do a request to get the next batch of items + * + * @return int The number of items in the batch after the retrieve + */ + private function retrieveBatch() + { + // Set OAI-PMH parameters for request + // If resumptionToken, then we ignore params and just use that + $params = ($this->resumptionToken) + ? ['resumptionToken' => $this->resumptionToken] + : $this->params; + + // Node name and verb + $nodeName = $this->getItemNodeName(); + $verb = $this->verb; + + //Do it.. + $resp = $this->httpClient->request($verb, $params); + $this->numRequests++; + + //Result format error? + if (! isset($resp->$verb->$nodeName)) { + throw new MalformedResponseException(sprintf("Expected XML element list '%s' missing for verb '%s'", $nodeName, $verb)); + } + + //Process the results + foreach ($resp->$verb->$nodeName as $node) { + $this->batch[] = $node; + } + + //Set the resumption token and expiration date, if specified in the response + if (isset($resp->$verb->resumptionToken)) { + $this->resumptionToken = (string) $resp->$verb->resumptionToken; + + if (isset($resp->$verb->resumptionToken['completeListSize'])) { + $this->totalRecordsInCollection = (int) $resp->$verb->resumptionToken['completeListSize']; + } + if (isset($resp->$verb->resumptionToken['expirationDate'])) { + $t = $resp->$verb->resumptionToken['expirationDate']; + $this->expireDate = \DateTime::createFromFormat(\DateTime::ISO8601, $t); + } + } else { + //Unset the resumption token when we're at the end of the list + $this->resumptionToken = null; + } + + //Return a count + return count($this->batch); + } + + // ------------------------------------------------------------------------- + + /** + * Get Item Node Name + * + * Map the item node name based on the verb + * + * @return string|boolean The element name for the mapping, or false if unmapped + */ + private function getItemNodeName() + { + $mappings = array( + 'ListMetadataFormats' => 'metadataFormat', + 'ListSets' => 'set', + 'ListIdentifiers' => 'header', + 'ListRecords' => 'record' + ); + + return (isset($mappings[$this->verb])) ? $mappings[$this->verb] : false; + } + + // ---------------------------------------------------------------- + + /** + * Reset the request state + */ + public function reset() + { + $this->numRequests = 0; + $this->numProcessed = 0; + + $this->currItem = null; + $this->resumptionToken = null; + $this->totalRecordsInCollection = null; + $this->expireDate = null; + + $this->batch = []; + } + + // ---------------------------------------------------------------- + + public function current() + { + return ($this->currItem === null) + ? $this->nextItem() + : $this->currItem; + } + + public function next() + { + return $this->nextItem(); + } + + public function key() + { + if ($this->currItem === null) { + $this->nextItem(); + } + + return $this->getNumRetrieved(); + } + + public function valid() + { + return ($this->currItem !== false); + } + + public function rewind() + { + $this->reset(); + } +} diff --git a/admin/classes/domain/vendor/composer/ClassLoader.php b/admin/classes/domain/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..4e05d3b --- /dev/null +++ b/admin/classes/domain/vendor/composer/ClassLoader.php @@ -0,0 +1,413 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + private $classMapAuthoritative = false; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-0 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/admin/classes/domain/vendor/composer/autoload_classmap.php b/admin/classes/domain/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/admin/classes/domain/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/caseyamcl/phpoaipmh/src', $vendorDir . '/caseyamcl/phpoaipmh/src', $vendorDir . '/caseyamcl/phpoaipmh/tests'), +); diff --git a/admin/classes/domain/vendor/composer/autoload_psr4.php b/admin/classes/domain/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..1ae73db --- /dev/null +++ b/admin/classes/domain/vendor/composer/autoload_psr4.php @@ -0,0 +1,14 @@ + array($vendorDir . '/react/promise/src'), + 'Phpoaipmh\\Example\\' => array($vendorDir . '/caseyamcl/phpoaipmh/example/src'), + 'GuzzleHttp\\Stream\\' => array($vendorDir . '/guzzlehttp/streams/src'), + 'GuzzleHttp\\Ring\\' => array($vendorDir . '/guzzlehttp/ringphp/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), +); diff --git a/admin/classes/domain/vendor/composer/autoload_real.php b/admin/classes/domain/vendor/composer/autoload_real.php new file mode 100644 index 0000000..99dadca --- /dev/null +++ b/admin/classes/domain/vendor/composer/autoload_real.php @@ -0,0 +1,55 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + $includeFiles = require __DIR__ . '/autoload_files.php'; + foreach ($includeFiles as $file) { + composerRequire97895f88b67d99b60b78f68d45e2b7d2($file); + } + + return $loader; + } +} + +function composerRequire97895f88b67d99b60b78f68d45e2b7d2($file) +{ + require $file; +} diff --git a/admin/classes/domain/vendor/composer/installed.json b/admin/classes/domain/vendor/composer/installed.json new file mode 100644 index 0000000..0124f1d --- /dev/null +++ b/admin/classes/domain/vendor/composer/installed.json @@ -0,0 +1,274 @@ +[ + { + "name": "caseyamcl/phpoaipmh", + "version": "v2.4", + "version_normalized": "2.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/caseyamcl/phpoaipmh.git", + "reference": "8a8a10e34e6d6b7f30849617aa7100b52331d0ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/caseyamcl/phpoaipmh/zipball/8a8a10e34e6d6b7f30849617aa7100b52331d0ef", + "reference": "8a8a10e34e6d6b7f30849617aa7100b52331d0ef", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "~5.0", + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0", + "symfony/config": "~2.5", + "symfony/console": "~2.5", + "symfony/dependency-injection": "~2.5", + "symfony/yaml": "~2.5" + }, + "time": "2015-05-18 14:40:02", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Phpoaipmh": [ + "src/", + "tests" + ] + }, + "psr-4": { + "Phpoaipmh\\Example\\": "example/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Casey McLaughlin", + "email": "caseyamcl@gmail.com", + "homepage": "http://caseymclaughlin.com", + "role": "Developer" + } + ], + "description": "A PHP OAI-PMH 2.0 Harvester library", + "homepage": "https://github.com/caseyamcl/phpoaipmh", + "keywords": [ + "Harvester", + "OAI", + "OAI-PMH" + ] + }, + { + "name": "react/promise", + "version": "v2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/365fcee430dfa4ace1fbc75737ca60ceea7eeeef", + "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "time": "2014-12-30 13:32:42", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@googlemail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP" + }, + { + "name": "guzzlehttp/streams", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/streams.git", + "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2014-10-12 19:18:40", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Provides a simple abstraction over streams of data", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "Guzzle", + "stream" + ] + }, + { + "name": "guzzlehttp/ringphp", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/RingPHP.git", + "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", + "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", + "shasum": "" + }, + "require": { + "guzzlehttp/streams": "~3.0", + "php": ">=5.4.0", + "react/promise": "~2.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, + "time": "2015-05-20 03:37:09", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Ring\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function." + }, + { + "name": "guzzlehttp/guzzle", + "version": "5.3.0", + "version_normalized": "5.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f3c8c22471cb55475105c14769644a49c3262b93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f3c8c22471cb55475105c14769644a49c3262b93", + "reference": "f3c8c22471cb55475105c14769644a49c3262b93", + "shasum": "" + }, + "require": { + "guzzlehttp/ringphp": "^1.1", + "php": ">=5.4.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0", + "psr/log": "^1.0" + }, + "time": "2015-05-20 03:47:55", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ] + } +] diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/.editorconfig b/admin/classes/domain/vendor/guzzlehttp/guzzle/.editorconfig new file mode 100644 index 0000000..eab48cc --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending for every file +# Indent with 4 spaces +[php] +end_of_line = lf +indent_style = space +indent_size = 4 diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/.gitignore b/admin/classes/domain/vendor/guzzlehttp/guzzle/.gitignore new file mode 100644 index 0000000..83ec41e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/.gitignore @@ -0,0 +1,11 @@ +phpunit.xml +composer.phar +composer.lock +composer-test.lock +vendor/ +build/artifacts/ +artifacts/ +docs/_build +docs/*.pyc +.idea +.DS_STORE diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/.travis.yml b/admin/classes/domain/vendor/guzzlehttp/guzzle/.travis.yml new file mode 100644 index 0000000..55ec892 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/.travis.yml @@ -0,0 +1,41 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +before_script: + - curl --version + - pear config-set php_ini ~/.phpenv/versions/`php -r 'echo phpversion();'`/etc/php.ini || echo 'Error modifying PEAR' + - pecl install uri_template || echo 'Error installing uri_template' + - composer self-update + - composer install --no-interaction --prefer-source --dev + - ~/.nvm/nvm.sh install v0.6.14 + - ~/.nvm/nvm.sh run v0.6.14 + +script: make test + +matrix: + allow_failures: + - php: hhvm + - php: 7.0 + fast_finish: true + +before_deploy: + - make package + +deploy: + provider: releases + api_key: + secure: UpypqlYgsU68QT/x40YzhHXvzWjFwCNo9d+G8KAdm7U9+blFfcWhV1aMdzugvPMl6woXgvJj7qHq5tAL4v6oswCORhpSBfLgOQVFaica5LiHsvWlAedOhxGmnJqMTwuepjBCxXhs3+I8Kof1n4oUL9gKytXjOVCX/f7XU1HiinU= + file: + - build/artifacts/guzzle.phar + - build/artifacts/guzzle.zip + on: + repo: guzzle/guzzle + tags: true + all_branches: true + php: 5.4 diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/CHANGELOG.md b/admin/classes/domain/vendor/guzzlehttp/guzzle/CHANGELOG.md new file mode 100644 index 0000000..b46278b --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -0,0 +1,1053 @@ +# CHANGELOG + +## 5.3.0 - 2015-05-19 + +* Mock now supports `save_to` +* Marked `AbstractRequestEvent::getTransaction()` as public. +* Fixed a bug in which multiple headers using different casing would overwrite + previous headers in the associative array. +* Added `Utils::getDefaultHandler()` +* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated. +* URL scheme is now always lowercased. + +## 5.2.0 - 2015-01-27 + +* Added `AppliesHeadersInterface` to make applying headers to a request based + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + RingBridge. +* Added a guard in the Pool class to not use recursion for request retries. + +## 5.1.0 - 2014-12-19 + +* Pool class no longer uses recursion when a request is intercepted. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle + specific exceptions if necessary. + +## 5.0.3 - 2014-11-03 + +This change updates query strings so that they are treated as un-encoded values +by default where the value represents an un-encoded value to send over the +wire. A Query object then encodes the value before sending over the wire. This +means that even value query string values (e.g., ":") are url encoded. This +makes the Query class match PHP's http_build_query function. However, if you +want to send requests over the wire using valid query string characters that do +not need to be encoded, then you can provide a string to Url::setQuery() and +pass true as the second argument to specify that the query string is a raw +string that should not be parsed or encoded (unless a call to getQuery() is +subsequently made, forcing the query-string to be converted into a Query +object). + +## 5.0.2 - 2014-10-30 + +* Added a trailing `\r\n` to multipart/form-data payloads. See + https://github.com/guzzle/guzzle/pull/871 +* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. +* Status codes are now returned as integers. See + https://github.com/guzzle/guzzle/issues/881 +* No longer overwriting an existing `application/x-www-form-urlencoded` header + when sending POST requests, allowing for customized headers. See + https://github.com/guzzle/guzzle/issues/877 +* Improved path URL serialization. + + * No longer double percent-encoding characters in the path or query string if + they are already encoded. + * Now properly encoding the supplied path to a URL object, instead of only + encoding ' ' and '?'. + * Note: This has been changed in 5.0.3 to now encode query string values by + default unless the `rawString` argument is provided when setting the query + string on a URL: Now allowing many more characters to be present in the + query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A + +## 5.0.1 - 2014-10-16 + +Bugfix release. + +* Fixed an issue where connection errors still returned response object in + error and end events event though the response is unusable. This has been + corrected so that a response is not returned in the `getResponse` method of + these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 +* Fixed an issue where transfer statistics were not being populated in the + RingBridge. https://github.com/guzzle/guzzle/issues/866 + +## 5.0.0 - 2014-10-12 + +Adding support for non-blocking responses and some minor API cleanup. + +### New Features + +* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. +* Added a public API for creating a default HTTP adapter. +* Updated the redirect plugin to be non-blocking so that redirects are sent + concurrently. Other plugins like this can now be updated to be non-blocking. +* Added a "progress" event so that you can get upload and download progress + events. +* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers + requests concurrently using a capped pool size as efficiently as possible. +* Added `hasListeners()` to EmitterInterface. +* Removed `GuzzleHttp\ClientInterface::sendAll` and marked + `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the + recommended way). + +### Breaking changes + +The breaking changes in this release are relatively minor. The biggest thing to +look out for is that request and response objects no longer implement fluent +interfaces. + +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of + why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/. + This also makes the Guzzle message interfaces compatible with the current + PSR-7 message proposal. +* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except + for the HTTP request functions from function.php, these functions are now + implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` + moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to + `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to + `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php + caused problems for many users: they aren't PSR-4 compliant, require an + explicit include, and needed an if-guard to ensure that the functions are not + declared multiple times. +* Rewrote adapter layer. + * Removing all classes from `GuzzleHttp\Adapter`, these are now + implemented as callables that are stored in `GuzzleHttp\Ring\Client`. + * Removed the concept of "parallel adapters". Sending requests serially or + concurrently is now handled using a single adapter. + * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The + Transaction object now exposes the request, response, and client as public + properties. The getters and setters have been removed. +* Removed the "headers" event. This event was only useful for changing the + body a response once the headers of the response were known. You can implement + a similar behavior in a number of ways. One example might be to use a + FnStream that has access to the transaction being sent. For example, when the + first byte is written, you could check if the response headers match your + expectations, and if so, change the actual stream body that is being + written to. +* Removed the `asArray` parameter from + `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with + the PSR-7 interfaces. +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add + custom request options using double-dispatch (this was an implementation + detail). Instead, you should now provide an associative array to the + constructor which is a mapping of the request option name mapping to a + function that applies the option value to a request. +* Removed the concept of "throwImmediately" from exceptions and error events. + This control mechanism was used to stop a transfer of concurrent requests + from completing. This can now be handled by throwing the exception or by + cancelling a pool of requests or each outstanding future request individually. +* Updated to "GuzzleHttp\Streams" 3.0. + * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a + `maxLen` parameter. This update makes the Guzzle streams project + compatible with the current PSR-7 proposal. + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second + argument. They now accept an associative array of options, including the + "size" key and "metadata" key which can be used to provide custom metadata. + +## 4.2.2 - 2014-09-08 + +* Fixed a memory leak in the CurlAdapter when reusing cURL handles. +* No longer using `request_fulluri` in stream adapter proxies. +* Relative redirects are now based on the last response, not the first response. + +## 4.2.1 - 2014-08-19 + +* Ensuring that the StreamAdapter does not always add a Content-Type header +* Adding automated github releases with a phar and zip + +## 4.2.0 - 2014-08-17 + +* Now merging in default options using a case-insensitive comparison. + Closes https://github.com/guzzle/guzzle/issues/767 +* Added the ability to automatically decode `Content-Encoding` response bodies + using the `decode_content` request option. This is set to `true` by default + to decode the response body if it comes over the wire with a + `Content-Encoding`. Set this value to `false` to disable decoding the + response content, and pass a string to provide a request `Accept-Encoding` + header and turn on automatic response decoding. This feature now allows you + to pass an `Accept-Encoding` header in the headers of a request but still + disable automatic response decoding. + Closes https://github.com/guzzle/guzzle/issues/764 +* Added the ability to throw an exception immediately when transferring + requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 +* Updating guzzlehttp/streams dependency to ~2.1 +* No longer utilizing the now deprecated namespaced methods from the stream + package. + +## 4.1.8 - 2014-08-14 + +* Fixed an issue in the CurlFactory that caused setting the `stream=false` + request option to throw an exception. + See: https://github.com/guzzle/guzzle/issues/769 +* TransactionIterator now calls rewind on the inner iterator. + See: https://github.com/guzzle/guzzle/pull/765 +* You can now set the `Content-Type` header to `multipart/form-data` + when creating POST requests to force multipart bodies. + See https://github.com/guzzle/guzzle/issues/768 + +## 4.1.7 - 2014-08-07 + +* Fixed an error in the HistoryPlugin that caused the same request and response + to be logged multiple times when an HTTP protocol error occurs. +* Ensuring that cURL does not add a default Content-Type when no Content-Type + has been supplied by the user. This prevents the adapter layer from modifying + the request that is sent over the wire after any listeners may have already + put the request in a desired state (e.g., signed the request). +* Throwing an exception when you attempt to send requests that have the + "stream" set to true in parallel using the MultiAdapter. +* Only calling curl_multi_select when there are active cURL handles. This was + previously changed and caused performance problems on some systems due to PHP + always selecting until the maximum select timeout. +* Fixed a bug where multipart/form-data POST fields were not correctly + aggregated (e.g., values with "&"). + +## 4.1.6 - 2014-08-03 + +* Added helper methods to make it easier to represent messages as strings, + including getting the start line and getting headers as a string. + +## 4.1.5 - 2014-08-02 + +* Automatically retrying cURL "Connection died, retrying a fresh connect" + errors when possible. +* cURL implementation cleanup +* Allowing multiple event subscriber listeners to be registered per event by + passing an array of arrays of listener configuration. + +## 4.1.4 - 2014-07-22 + +* Fixed a bug that caused multi-part POST requests with more than one field to + serialize incorrectly. +* Paths can now be set to "0" +* `ResponseInterface::xml` now accepts a `libxml_options` option and added a + missing default argument that was required when parsing XML response bodies. +* A `save_to` stream is now created lazily, which means that files are not + created on disk unless a request succeeds. + +## 4.1.3 - 2014-07-15 + +* Various fixes to multipart/form-data POST uploads +* Wrapping function.php in an if-statement to ensure Guzzle can be used + globally and in a Composer install +* Fixed an issue with generating and merging in events to an event array +* POST headers are only applied before sending a request to allow you to change + the query aggregator used before uploading +* Added much more robust query string parsing +* Fixed various parsing and normalization issues with URLs +* Fixing an issue where multi-valued headers were not being utilized correctly + in the StreamAdapter + +## 4.1.2 - 2014-06-18 + +* Added support for sending payloads with GET requests + +## 4.1.1 - 2014-06-08 + +* Fixed an issue related to using custom message factory options in subclasses +* Fixed an issue with nested form fields in a multi-part POST +* Fixed an issue with using the `json` request option for POST requests +* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` + +## 4.1.0 - 2014-05-27 + +* Added a `json` request option to easily serialize JSON payloads. +* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. +* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. +* Added the ability to provide an emitter to a client in the client constructor. +* Added the ability to persist a cookie session using $_SESSION. +* Added a trait that can be used to add event listeners to an iterator. +* Removed request method constants from RequestInterface. +* Fixed warning when invalid request start-lines are received. +* Updated MessageFactory to work with custom request option methods. +* Updated cacert bundle to latest build. + +4.0.2 (2014-04-16) +------------------ + +* Proxy requests using the StreamAdapter now properly use request_fulluri (#632) +* Added the ability to set scalars as POST fields (#628) + +## 4.0.1 - 2014-04-04 + +* The HTTP status code of a response is now set as the exception code of + RequestException objects. +* 303 redirects will now correctly switch from POST to GET requests. +* The default parallel adapter of a client now correctly uses the MultiAdapter. +* HasDataTrait now initializes the internal data array as an empty array so + that the toArray() method always returns an array. + +## 4.0.0 - 2014-03-29 + +* For more information on the 4.0 transition, see: + http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/ +* For information on changes and upgrading, see: + https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 +* Added `GuzzleHttp\batch()` as a convenience function for sending requests in + parallel without needing to write asynchronous code. +* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. + You can now pass a callable or an array of associative arrays where each + associative array contains the "fn", "priority", and "once" keys. + +## 4.0.0.rc-2 - 2014-03-25 + +* Removed `getConfig()` and `setConfig()` from clients to avoid confusion + around whether things like base_url, message_factory, etc. should be able to + be retrieved or modified. +* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface +* functions.php functions were renamed using snake_case to match PHP idioms +* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and + `GUZZLE_CURL_SELECT_TIMEOUT` environment variables +* Added the ability to specify custom `sendAll()` event priorities +* Added the ability to specify custom stream context options to the stream + adapter. +* Added a functions.php function for `get_path()` and `set_path()` +* CurlAdapter and MultiAdapter now use a callable to generate curl resources +* MockAdapter now properly reads a body and emits a `headers` event +* Updated Url class to check if a scheme and host are set before adding ":" + and "//". This allows empty Url (e.g., "") to be serialized as "". +* Parsing invalid XML no longer emits warnings +* Curl classes now properly throw AdapterExceptions +* Various performance optimizations +* Streams are created with the faster `Stream\create()` function +* Marked deprecation_proxy() as internal +* Test server is now a collection of static methods on a class + +## 4.0.0-rc.1 - 2014-03-15 + +* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + +## 3.8.1 - 2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc.). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc.). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it'sdeprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 3.4.0 - 2013-04-11 + +* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289 +* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 +* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 +* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc. +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/LICENSE b/admin/classes/domain/vendor/guzzlehttp/guzzle/LICENSE new file mode 100644 index 0000000..71d3b78 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/Makefile b/admin/classes/domain/vendor/guzzlehttp/guzzle/Makefile new file mode 100644 index 0000000..69bf327 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/Makefile @@ -0,0 +1,50 @@ +all: clean coverage docs + +start-server: + cd vendor/guzzlehttp/ringphp && make start-server + +stop-server: + cd vendor/guzzlehttp/ringphp && make stop-server + +test: start-server + vendor/bin/phpunit + $(MAKE) stop-server + +coverage: start-server + vendor/bin/phpunit --coverage-html=artifacts/coverage + $(MAKE) stop-server + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* + +docs: + cd docs && make html && cd .. + +view-docs: + open docs/_build/html/index.html + +tag: + $(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1")) + @echo Tagging $(TAG) + chag update $(TAG) + sed -i '' -e "s/VERSION = '.*'/VERSION = '$(TAG)'/" src/ClientInterface.php + php -l src/ClientInterface.php + git add -A + git commit -m '$(TAG) release' + chag tag + +perf: start-server + php tests/perf.php + $(MAKE) stop-server + +package: burgomaster + php build/packager.php + +burgomaster: + mkdir -p build/artifacts + curl -s https://raw.githubusercontent.com/mtdowling/Burgomaster/0.0.2/src/Burgomaster.php > build/artifacts/Burgomaster.php + +.PHONY: docs burgomaster diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/README.md b/admin/classes/domain/vendor/guzzlehttp/guzzle/README.md new file mode 100644 index 0000000..d41e7e7 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,70 @@ +Guzzle, PHP HTTP client and webservice framework +================================================ + +[![Build Status](https://secure.travis-ci.org/guzzle/guzzle.svg?branch=master)](http://travis-ci.org/guzzle/guzzle) + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Manages things like persistent connections, represents query strings as + collections, simplifies sending streaming POST requests with fields and + files, and abstracts away the underlying HTTP transport layer. +- Can send both synchronous and asynchronous requests using the same interface + without requiring a dependency on a specific event loop. +- Pluggable HTTP adapters allows Guzzle to integrate with any method you choose + for sending HTTP requests over the wire (e.g., cURL, sockets, PHP's stream + wrapper, non-blocking event loops like ReactPHP. +- Guzzle makes it so that you no longer need to fool around with cURL options, + stream contexts, or sockets. + +```php +$client = new GuzzleHttp\Client(); +$response = $client->get('http://guzzlephp.org'); +$res = $client->get('https://api.github.com/user', ['auth' => ['user', 'pass']]); +echo $res->getStatusCode(); +// "200" +echo $res->getHeader('content-type'); +// 'application/json; charset=utf8' +echo $res->getBody(); +// {"type":"User"...' +var_export($res->json()); +// Outputs the JSON decoded data + +// Send an asynchronous request. +$req = $client->createRequest('GET', 'http://httpbin.org', ['future' => true]); +$client->send($req)->then(function ($response) { + echo 'I completed! ' . $response; +}); +``` + +Get more information and answers with the +[Documentation](http://guzzlephp.org/), +[Forums](https://groups.google.com/forum/?hl=en#!forum/guzzle), +and [Gitter](https://gitter.im/guzzle/guzzle). + +### Installing via Composer + +The recommended way to install Guzzle is through +[Composer](http://getcomposer.org). + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php +``` + +Next, run the Composer command to install the latest stable version of Guzzle: + +```bash +composer.phar require guzzlehttp/guzzle +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` + +### Documentation + +More information can be found in the online documentation at +http://guzzlephp.org/. diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/UPGRADING.md b/admin/classes/domain/vendor/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 0000000..2b3877f --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1050 @@ +Guzzle Upgrade Guide +==================== + +4.x to 5.0 +---------- + +## Rewritten Adapter Layer + +Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send +HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor +is still supported, but it has now been renamed to `handler`. Instead of +passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP +`callable` that follows the RingPHP specification. + +## Removed Fluent Interfaces + +[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil) +from the following classes: + +- `GuzzleHttp\Collection` +- `GuzzleHttp\Url` +- `GuzzleHttp\Query` +- `GuzzleHttp\Post\PostBody` +- `GuzzleHttp\Cookie\SetCookie` + +## Removed functions.php + +Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following +functions can be used as replacements. + +- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` +- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` +- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` +- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, + deprecated in favor of using `GuzzleHttp\Pool::batch()`. + +The "procedural" global client has been removed with no replacement (e.g., +`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client` +object as a replacement. + +## `throwImmediately` has been removed + +The concept of "throwImmediately" has been removed from exceptions and error +events. This control mechanism was used to stop a transfer of concurrent +requests from completing. This can now be handled by throwing the exception or +by cancelling a pool of requests or each outstanding future request +individually. + +## headers event has been removed + +Removed the "headers" event. This event was only useful for changing the +body a response once the headers of the response were known. You can implement +a similar behavior in a number of ways. One example might be to use a +FnStream that has access to the transaction being sent. For example, when the +first byte is written, you could check if the response headers match your +expectations, and if so, change the actual stream body that is being +written to. + +## Updates to HTTP Messages + +Removed the `asArray` parameter from +`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header +value as an array, then use the newly added `getHeaderAsArray()` method of +`MessageInterface`. This change makes the Guzzle interfaces compatible with +the PSR-7 interfaces. + +3.x to 4.0 +---------- + +## Overarching changes: + +- Now requires PHP 5.4 or greater. +- No longer requires cURL to send requests. +- Guzzle no longer wraps every exception it throws. Only exceptions that are + recoverable are now wrapped by Guzzle. +- Various namespaces have been removed or renamed. +- No longer requiring the Symfony EventDispatcher. A custom event dispatcher + based on the Symfony EventDispatcher is + now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant + speed and functionality improvements). + +Changes per Guzzle 3.x namespace are described below. + +## Batch + +The `Guzzle\Batch` namespace has been removed. This is best left to +third-parties to implement on top of Guzzle's core HTTP library. + +## Cache + +The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement +has been implemented yet, but hoping to utilize a PSR cache interface). + +## Common + +- Removed all of the wrapped exceptions. It's better to use the standard PHP + library for unrecoverable exceptions. +- `FromConfigInterface` has been removed. +- `Guzzle\Common\Version` has been removed. The VERSION constant can be found + at `GuzzleHttp\ClientInterface::VERSION`. + +### Collection + +- `getAll` has been removed. Use `toArray` to convert a collection to an array. +- `inject` has been removed. +- `keySearch` has been removed. +- `getPath` no longer supports wildcard expressions. Use something better like + JMESPath for this. +- `setPath` now supports appending to an existing array via the `[]` notation. + +### Events + +Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses +`GuzzleHttp\Event\Emitter`. + +- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by + `GuzzleHttp\Event\EmitterInterface`. +- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by + `GuzzleHttp\Event\Emitter`. +- `Symfony\Component\EventDispatcher\Event` is replaced by + `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in + `GuzzleHttp\Event\EventInterface`. +- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and + `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the + event emitter of a request, client, etc. now uses the `getEmitter` method + rather than the `getDispatcher` method. + +#### Emitter + +- Use the `once()` method to add a listener that automatically removes itself + the first time it is invoked. +- Use the `listeners()` method to retrieve a list of event listeners rather than + the `getListeners()` method. +- Use `emit()` instead of `dispatch()` to emit an event from an emitter. +- Use `attach()` instead of `addSubscriber()` and `detach()` instead of + `removeSubscriber()`. + +```php +$mock = new Mock(); +// 3.x +$request->getEventDispatcher()->addSubscriber($mock); +$request->getEventDispatcher()->removeSubscriber($mock); +// 4.x +$request->getEmitter()->attach($mock); +$request->getEmitter()->detach($mock); +``` + +Use the `on()` method to add a listener rather than the `addListener()` method. + +```php +// 3.x +$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); +// 4.x +$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); +``` + +## Http + +### General changes + +- The cacert.pem certificate has been moved to `src/cacert.pem`. +- Added the concept of adapters that are used to transfer requests over the + wire. +- Simplified the event system. +- Sending requests in parallel is still possible, but batching is no longer a + concept of the HTTP layer. Instead, you must use the `complete` and `error` + events to asynchronously manage parallel request transfers. +- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. +- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. +- QueryAggregators have been rewritten so that they are simply callable + functions. +- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in + `functions.php` for an easy to use static client instance. +- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from + `GuzzleHttp\Exception\TransferException`. + +### Client + +Calling methods like `get()`, `post()`, `head()`, etc. no longer create and +return a request, but rather creates a request, sends the request, and returns +the response. + +```php +// 3.0 +$request = $client->get('/'); +$response = $request->send(); + +// 4.0 +$response = $client->get('/'); + +// or, to mirror the previous behavior +$request = $client->createRequest('GET', '/'); +$response = $client->send($request); +``` + +`GuzzleHttp\ClientInterface` has changed. + +- The `send` method no longer accepts more than one request. Use `sendAll` to + send multiple requests in parallel. +- `setUserAgent()` has been removed. Use a default request option instead. You + could, for example, do something like: + `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. +- `setSslVerification()` has been removed. Use default request options instead, + like `$client->setConfig('defaults/verify', true)`. + +`GuzzleHttp\Client` has changed. + +- The constructor now accepts only an associative array. You can include a + `base_url` string or array to use a URI template as the base URL of a client. + You can also specify a `defaults` key that is an associative array of default + request options. You can pass an `adapter` to use a custom adapter, + `batch_adapter` to use a custom adapter for sending requests in parallel, or + a `message_factory` to change the factory used to create HTTP requests and + responses. +- The client no longer emits a `client.create_request` event. +- Creating requests with a client no longer automatically utilize a URI + template. You must pass an array into a creational method (e.g., + `createRequest`, `get`, `put`, etc.) in order to expand a URI template. + +### Messages + +Messages no longer have references to their counterparts (i.e., a request no +longer has a reference to it's response, and a response no loger has a +reference to its request). This association is now managed through a +`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to +these transaction objects using request events that are emitted over the +lifecycle of a request. + +#### Requests with a body + +- `GuzzleHttp\Message\EntityEnclosingRequest` and + `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The + separation between requests that contain a body and requests that do not + contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` + handles both use cases. +- Any method that previously accepts a `GuzzleHttp\Response` object now accept a + `GuzzleHttp\Message\ResponseInterface`. +- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to + `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create + both requests and responses and is implemented in + `GuzzleHttp\Message\MessageFactory`. +- POST field and file methods have been removed from the request object. You + must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` + to control the format of a POST body. Requests that are created using a + standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use + a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if + the method is POST and no body is provided. + +```php +$request = $client->createRequest('POST', '/'); +$request->getBody()->setField('foo', 'bar'); +$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); +``` + +#### Headers + +- `GuzzleHttp\Message\Header` has been removed. Header values are now simply + represented by an array of values or as a string. Header values are returned + as a string by default when retrieving a header value from a message. You can + pass an optional argument of `true` to retrieve a header value as an array + of strings instead of a single concatenated string. +- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to + `GuzzleHttp\Post`. This interface has been simplified and now allows the + addition of arbitrary headers. +- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most + of the custom headers are now handled separately in specific + subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has + been updated to properly handle headers that contain parameters (like the + `Link` header). + +#### Responses + +- `GuzzleHttp\Message\Response::getInfo()` and + `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event + system to retrieve this type of information. +- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. +- `GuzzleHttp\Message\Response::getMessage()` has been removed. +- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific + methods have moved to the CacheSubscriber. +- Header specific helper functions like `getContentMd5()` have been removed. + Just use `getHeader('Content-MD5')` instead. +- `GuzzleHttp\Message\Response::setRequest()` and + `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event + system to work with request and response objects as a transaction. +- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the + Redirect subscriber instead. +- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have + been removed. Use `getStatusCode()` instead. + +#### Streaming responses + +Streaming requests can now be created by a client directly, returning a +`GuzzleHttp\Message\ResponseInterface` object that contains a body stream +referencing an open PHP HTTP stream. + +```php +// 3.0 +use Guzzle\Stream\PhpStreamRequestFactory; +$request = $client->get('/'); +$factory = new PhpStreamRequestFactory(); +$stream = $factory->fromRequest($request); +$data = $stream->read(1024); + +// 4.0 +$response = $client->get('/', ['stream' => true]); +// Read some data off of the stream in the response body +$data = $response->getBody()->read(1024); +``` + +#### Redirects + +The `configureRedirects()` method has been removed in favor of a +`allow_redirects` request option. + +```php +// Standard redirects with a default of a max of 5 redirects +$request = $client->createRequest('GET', '/', ['allow_redirects' => true]); + +// Strict redirects with a custom number of redirects +$request = $client->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 5, 'strict' => true] +]); +``` + +#### EntityBody + +EntityBody interfaces and classes have been removed or moved to +`GuzzleHttp\Stream`. All classes and interfaces that once required +`GuzzleHttp\EntityBodyInterface` now require +`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no +longer uses `GuzzleHttp\EntityBody::factory` but now uses +`GuzzleHttp\Stream\Stream::factory` or even better: +`GuzzleHttp\Stream\create()`. + +- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` +- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` +- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` +- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` +- `Guzzle\Http\IoEmittyinEntityBody` has been removed. + +#### Request lifecycle events + +Requests previously submitted a large number of requests. The number of events +emitted over the lifecycle of a request has been significantly reduced to make +it easier to understand how to extend the behavior of a request. All events +emitted during the lifecycle of a request now emit a custom +`GuzzleHttp\Event\EventInterface` object that contains context providing +methods and a way in which to modify the transaction at that specific point in +time (e.g., intercept the request and set a response on the transaction). + +- `request.before_send` has been renamed to `before` and now emits a + `GuzzleHttp\Event\BeforeEvent` +- `request.complete` has been renamed to `complete` and now emits a + `GuzzleHttp\Event\CompleteEvent`. +- `request.sent` has been removed. Use `complete`. +- `request.success` has been removed. Use `complete`. +- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. +- `request.exception` has been removed. Use `error`. +- `request.receive.status_line` has been removed. +- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to + maintain a status update. +- `curl.callback.write` has been removed. Use a custom `StreamInterface` to + intercept writes. +- `curl.callback.read` has been removed. Use a custom `StreamInterface` to + intercept reads. + +`headers` is a new event that is emitted after the response headers of a +request have been received before the body of the response is downloaded. This +event emits a `GuzzleHttp\Event\HeadersEvent`. + +You can intercept a request and inject a response using the `intercept()` event +of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and +`GuzzleHttp\Event\ErrorEvent` event. + +See: http://docs.guzzlephp.org/en/latest/events.html + +## Inflection + +The `Guzzle\Inflection` namespace has been removed. This is not a core concern +of Guzzle. + +## Iterator + +The `Guzzle\Iterator` namespace has been removed. + +- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and + `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of + Guzzle itself. +- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent + class is shipped with PHP 5.4. +- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because + it's easier to just wrap an iterator in a generator that maps values. + +For a replacement of these iterators, see https://github.com/nikic/iter + +## Log + +The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The +`Guzzle\Log` namespace has been removed. Guzzle now relies on +`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been +moved to `GuzzleHttp\Subscriber\Log\Formatter`. + +## Parser + +The `Guzzle\Parser` namespace has been removed. This was previously used to +make it possible to plug in custom parsers for cookies, messages, URI +templates, and URLs; however, this level of complexity is not needed in Guzzle +so it has been removed. + +- Cookie: Cookie parsing logic has been moved to + `GuzzleHttp\Cookie\SetCookie::fromString`. +- Message: Message parsing logic for both requests and responses has been moved + to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only + used in debugging or deserializing messages, so it doesn't make sense for + Guzzle as a library to add this level of complexity to parsing messages. +- UriTemplate: URI template parsing has been moved to + `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL + URI template library if it is installed. +- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously + it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, + then developers are free to subclass `GuzzleHttp\Url`. + +## Plugin + +The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. +Several plugins are shipping with the core Guzzle library under this namespace. + +- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar + code has moved to `GuzzleHttp\Cookie`. +- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. +- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is + received. +- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. +- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before + sending. This subscriber is attached to all requests by default. +- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. + +The following plugins have been removed (third-parties are free to re-implement +these if needed): + +- `GuzzleHttp\Plugin\Async` has been removed. +- `GuzzleHttp\Plugin\CurlAuth` has been removed. +- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This + functionality should instead be implemented with event listeners that occur + after normal response parsing occurs in the guzzle/command package. + +The following plugins are not part of the core Guzzle package, but are provided +in separate repositories: + +- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be muchs simpler + to build custom retry policies using simple functions rather than various + chained classes. See: https://github.com/guzzle/retry-subscriber +- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to + https://github.com/guzzle/cache-subscriber +- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to + https://github.com/guzzle/log-subscriber +- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to + https://github.com/guzzle/message-integrity-subscriber +- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to + `GuzzleHttp\Subscriber\MockSubscriber`. +- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to + https://github.com/guzzle/oauth-subscriber + +## Service + +The service description layer of Guzzle has moved into two separate packages: + +- http://github.com/guzzle/command Provides a high level abstraction over web + services by representing web service operations using commands. +- http://github.com/guzzle/guzzle-services Provides an implementation of + guzzle/command that provides request serialization and response parsing using + Guzzle service descriptions. + +## Stream + +Stream have moved to a separate package available at +https://github.com/guzzle/streams. + +`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take +on the responsibilities of `Guzzle\Http\EntityBody` and +`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number +of methods implemented by the `StreamInterface` has been drastically reduced to +allow developers to more easily extend and decorate stream behavior. + +## Removed methods from StreamInterface + +- `getStream` and `setStream` have been removed to better encapsulate streams. +- `getMetadata` and `setMetadata` have been removed in favor of + `GuzzleHttp\Stream\MetadataStreamInterface`. +- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been + removed. This data is accessible when + using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. +- `rewind` has been removed. Use `seek(0)` for a similar behavior. + +## Renamed methods + +- `detachStream` has been renamed to `detach`. +- `feof` has been renamed to `eof`. +- `ftell` has been renamed to `tell`. +- `readLine` has moved from an instance method to a static class method of + `GuzzleHttp\Stream\Stream`. + +## Metadata streams + +`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams +that contain additional metadata accessible via `getMetadata()`. +`GuzzleHttp\Stream\StreamInterface::getMetadata` and +`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. + +## StreamRequestFactory + +The entire concept of the StreamRequestFactory has been removed. The way this +was used in Guzzle 3 broke the actual interface of sending streaming requests +(instead of getting back a Response, you got a StreamInterface). Streeaming +PHP requests are now implemented throught the `GuzzleHttp\Adapter\StreamAdapter`. + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc.). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +3.3 to 3.4 +---------- + +Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/build/packager.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/build/packager.php new file mode 100644 index 0000000..724bf63 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/build/packager.php @@ -0,0 +1,21 @@ +deepCopy($file, $file); +} + +// Copy each dependency to the staging directory. Copy *.php and *.pem files. +$packager->recursiveCopy('src', 'GuzzleHttp', ['php']); +$packager->recursiveCopy('vendor/react/promise/src', 'React/Promise'); +$packager->recursiveCopy('vendor/guzzlehttp/ringphp/src', 'GuzzleHttp/Ring'); +$packager->recursiveCopy('vendor/guzzlehttp/streams/src', 'GuzzleHttp/Stream'); +$packager->createAutoloader(['React/Promise/functions.php']); +$packager->createPhar(__DIR__ . '/artifacts/guzzle.phar'); +$packager->createZip(__DIR__ . '/artifacts/guzzle.zip'); diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/composer.json b/admin/classes/domain/vendor/guzzlehttp/guzzle/composer.json new file mode 100644 index 0000000..6ec7dad --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,39 @@ +{ + "name": "guzzlehttp/guzzle", + "type": "library", + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.4.0", + "guzzlehttp/ringphp": "^1.1" + }, + "require-dev": { + "ext-curl": "*", + "psr/log": "^1.0", + "phpunit/phpunit": "^4.0" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/Makefile b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/Makefile new file mode 100644 index 0000000..d92e03f --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Guzzle.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Guzzle.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Guzzle" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Guzzle" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_static/guzzle-icon.png b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_static/guzzle-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f1017f7e6028c14a9e0694c66a6cfbb2d546adf5 GIT binary patch literal 803 zcmV+;1Kj+HP)@ zosv>1reIXPlo1y){RjSw2_NWGX5O-#W+NK3tBQ_IN%bh7xZ1Yehws80|4azI;aWIJ z_%xhlcTubTO7Dbx z)F-R8gg5MzGv|t4=e_El4GCwW0m6?C;0bG4DRC^TH6-pa>y8_h*QBud6Ms>Qf{oN> z=Q($Dn|DINB{`Ea{)h&^x;i{)XQ{?Z&id71eOkRPqgl17&E%|dJIC&SCwnd zY4oUR`OVorB8A||QZjLWS~cr&OD?HEtM@^bIovNUC5An6?z`xDgJL2H2Mya@dku<1YUfi&QvS8KS8=~uOs!oaF z8OMF7-5yyh}yDkaCp7Ob8b;wv(27WLL#lglguF0fh3d(d@ zP%vrDIA~G}dL)X;YnCMSE4ZM-gfVsYTLItd3J`~_vw^k=W%C_MlG002ovPDHLkV1oLqbt3=( literal 0 HcmV?d00001 diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_static/logo.png b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..965a4ef4139180ea7d33594feba2394dfce6774a GIT binary patch literal 247678 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%0ARzX0ax0O>7%athq>_G8o_) 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 Q^3R@b2)1Zk0&5+MNyUSR-V=Sej)GD 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�{XQapLL3s@DiJOewb9vk!Lb} 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

kl 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

2-cn6gsGIE2zEilUdg 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

Z5%j-C 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%0A=e`l9AQljxQ1J>rUJsDX<`+@ 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 literal 0 HcmV?d00001 diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_templates/nav_links.html b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_templates/nav_links.html new file mode 100644 index 0000000..7950a0f --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/_templates/nav_links.html @@ -0,0 +1,3 @@ +

  • GitHub
  • +
  • Forum
  • +
  • IRC
  • diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/clients.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/clients.rst new file mode 100644 index 0000000..35a9806 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/clients.rst @@ -0,0 +1,1326 @@ +======= +Clients +======= + +Clients are used to create requests, create transactions, send requests +through an HTTP handler, and return a response. You can add default request +options to a client that are applied to every request (e.g., default headers, +default query string parameters, etc.), and you can add event listeners and +subscribers to every request created by a client. + +Creating a client +================= + +The constructor of a client accepts an associative array of configuration +options. + +base_url + Configures a base URL for the client so that requests created + using a relative URL are combined with the ``base_url`` of the client + according to section `5.2 of RFC 3986 `_. + + .. code-block:: php + + // Create a client with a base URL + $client = new GuzzleHttp\Client(['base_url' => 'https://github.com']); + // Send a request to https://github.com/notifications + $response = $client->get('/notifications'); + + Don't feel like reading RFC 3986? Here are some quick examples on how a + ``base_url`` is resolved with another URI. + + ======================= ================== =============================== + base_url URI Result + ======================= ================== =============================== + ``http://foo.com`` ``/bar`` ``http://foo.com/bar`` + ``http://foo.com/foo`` ``/bar`` ``http://foo.com/bar`` + ``http://foo.com/foo`` ``bar`` ``http://foo.com/bar`` + ``http://foo.com/foo/`` ``bar`` ``http://foo.com/foo/bar`` + ``http://foo.com`` ``http://baz.com`` ``http://baz.com`` + ``http://foo.com/?bar`` ``bar`` ``http://foo.com/bar`` + ======================= ================== =============================== + +handler + Configures the `RingPHP handler `_ + used to transfer the HTTP requests of a client. Guzzle will, by default, + utilize a stacked handlers that chooses the best handler to use based on the + provided request options and based on the extensions available in the + environment. + +message_factory + Specifies the factory used to create HTTP requests and responses + (``GuzzleHttp\Message\MessageFactoryInterface``). + +defaults + Associative array of :ref:`request-options` that are applied to every + request created by the client. This allows you to specify things like + default headers (e.g., User-Agent), default query string parameters, SSL + configurations, and any other supported request options. + +emitter + Specifies an event emitter (``GuzzleHttp\Event\EmitterInterface``) instance + to be used by the client to emit request events. This option is useful if + you need to inject an emitter with listeners/subscribers already attached. + +Here's an example of creating a client with various options. + +.. code-block:: php + + use GuzzleHttp\Client; + + $client = new Client([ + 'base_url' => ['https://api.twitter.com/{version}/', ['version' => 'v1.1']], + 'defaults' => [ + 'headers' => ['Foo' => 'Bar'], + 'query' => ['testing' => '123'], + 'auth' => ['username', 'password'], + 'proxy' => 'tcp://localhost:80' + ] + ]); + +Sending Requests +================ + +Requests can be created using various methods of a client. You can create +**and** send requests using one of the following methods: + +- ``GuzzleHttp\Client::get``: Sends a GET request. +- ``GuzzleHttp\Client::head``: Sends a HEAD request +- ``GuzzleHttp\Client::post``: Sends a POST request +- ``GuzzleHttp\Client::put``: Sends a PUT request +- ``GuzzleHttp\Client::delete``: Sends a DELETE request +- ``GuzzleHttp\Client::options``: Sends an OPTIONS request + +Each of the above methods accepts a URL as the first argument and an optional +associative array of :ref:`request-options` as the second argument. + +Synchronous Requests +-------------------- + +Guzzle sends synchronous (blocking) requests when the ``future`` request option +is not specified. This means that the request will complete immediately, and if +an error is encountered, a ``GuzzleHttp\Exception\RequestException`` will be +thrown. + +.. code-block:: php + + $client = new GuzzleHttp\Client(); + + $client->put('http://httpbin.org', [ + 'headers' => ['X-Foo' => 'Bar'], + 'body' => 'this is the body!', + 'save_to' => '/path/to/local/file', + 'allow_redirects' => false, + 'timeout' => 5 + ]); + +Synchronous Error Handling +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a recoverable error is encountered while calling the ``send()`` method of +a client, a ``GuzzleHttp\Exception\RequestException`` is thrown. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Exception\RequestException; + + $client = new Client(); + + try { + $client->get('http://httpbin.org'); + } catch (RequestException $e) { + echo $e->getRequest() . "\n"; + if ($e->hasResponse()) { + echo $e->getResponse() . "\n"; + } + } + +``GuzzleHttp\Exception\RequestException`` always contains a +``GuzzleHttp\Message\RequestInterface`` object that can be accessed using the +exception's ``getRequest()`` method. + +A response might be present in the exception. In the event of a networking +error, no response will be received. You can check if a ``RequestException`` +has a response using the ``hasResponse()`` method. If the exception has a +response, then you can access the associated +``GuzzleHttp\Message\ResponseInterface`` using the ``getResponse()`` method of +the exception. + +Asynchronous Requests +--------------------- + +You can send asynchronous requests by setting the ``future`` request option +to ``true`` (or a string that your handler understands). This creates a +``GuzzleHttp\Message\FutureResponse`` object that has not yet completed. Once +you have a future response, you can use a promise object obtained by calling +the ``then`` method of the response to take an action when the response has +completed or encounters an error. + +.. code-block:: php + + $response = $client->put('http://httpbin.org/get', ['future' => true]); + + // Call the function when the response completes + $response->then(function ($response) { + echo $response->getStatusCode(); + }); + +You can call the ``wait()`` method of a future response to block until it has +completed. You also use a future response object just like a normal response +object by accessing the methods of the response. Using a future response like a +normal response object, also known as *dereferencing*, will block until the +response has completed. + +.. code-block:: php + + $response = $client->put('http://httpbin.org/get', ['future' => true]); + + // Block until the response has completed + echo $response->getStatusCode(); + +.. important:: + + If an exception occurred while transferring the future response, then the + exception encountered will be thrown when dereferencing. + +.. note:: + + It depends on the RingPHP handler used by a client, but you typically need + to use the same RingPHP handler in order to utilize asynchronous requests + across multiple clients. + +Asynchronous Error Handling +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Handling errors with future response object promises is a bit different. When +using a promise, exceptions are forwarded to the ``$onError`` function provided +to the second argument of the ``then()`` function. + +.. code-block:: php + + $response = $client->put('http://httpbin.org/get', ['future' => true]); + + $response + ->then( + function ($response) { + // This is called when the request succeeded + echo 'Success: ' . $response->getStatusCode(); + // Returning a value will forward the value to the next promise + // in the chain. + return $response; + }, + function ($error) { + // This is called when the exception failed. + echo 'Exception: ' . $error->getMessage(); + // Throwing will "forward" the exception to the next promise + // in the chain. + throw $error; + } + ) + ->then( + function($response) { + // This is called after the first promise in the chain. It + // receives the value returned from the first promise. + echo $response->getReasonPhrase(); + }, + function ($error) { + // This is called if the first promise error handler in the + // chain rethrows the exception. + echo 'Error: ' . $error->getMessage(); + } + ); + +Please see the `React/Promises project documentation `_ +for more information on how promise resolution and rejection forwarding works. + +HTTP Errors +----------- + +If the ``exceptions`` request option is not set to ``false``, then exceptions +are thrown for HTTP protocol errors as well: +``GuzzleHttp\Exception\ClientErrorResponseException`` for 4xx level HTTP +responses and ``GuzzleHttp\Exception\ServerException`` for 5xx level responses, +both of which extend from ``GuzzleHttp\Exception\BadResponseException``. + +Creating Requests +----------------- + +You can create a request without sending it. This is useful for building up +requests over time or sending requests in concurrently. + +.. code-block:: php + + $request = $client->createRequest('GET', 'http://httpbin.org', [ + 'headers' => ['X-Foo' => 'Bar'] + ]); + + // Modify the request as needed + $request->setHeader('Baz', 'bar'); + +After creating a request, you can send it with the client's ``send()`` method. + +.. code-block:: php + + $response = $client->send($request); + +Sending Requests With a Pool +============================ + +You can send requests concurrently using a fixed size pool via the +``GuzzleHttp\Pool`` class. The Pool class is an implementation of +``GuzzleHttp\Ring\Future\FutureInterface``, meaning it can be dereferenced at a +later time or cancelled before sending. The Pool constructor accepts a client +object, iterator or array that yields ``GuzzleHttp\Message\RequestInterface`` +objects, and an optional associative array of options that can be used to +affect the transfer. + +.. code-block:: php + + use GuzzleHttp\Pool; + + $requests = [ + $client->createRequest('GET', 'http://httpbin.org'), + $client->createRequest('DELETE', 'http://httpbin.org/delete'), + $client->createRequest('PUT', 'http://httpbin.org/put', ['body' => 'test']) + ]; + + $options = []; + + // Create a pool. Note: the options array is optional. + $pool = new Pool($client, $requests, $options); + + // Send the requests + $pool->wait(); + +The Pool constructor accepts the following associative array of options: + +- **pool_size**: Integer representing the maximum number of requests that are + allowed to be sent concurrently. +- **before**: Callable or array representing the event listeners to add to + each request's :ref:`before_event` event. +- **complete**: Callable or array representing the event listeners to add to + each request's :ref:`complete_event` event. +- **error**: Callable or array representing the event listeners to add to + each request's :ref:`error_event` event. +- **end**: Callable or array representing the event listeners to add to + each request's :ref:`end_event` event. + +The "before", "complete", "error", and "end" event options accept a callable or +an array of associative arrays where each associative array contains a "fn" key +with a callable value, an optional "priority" key representing the event +priority (with a default value of 0), and an optional "once" key that can be +set to true so that the event listener will be removed from the request after +it is first triggered. + +.. code-block:: php + + use GuzzleHttp\Pool; + use GuzzleHttp\Event\CompleteEvent; + + // Add a single event listener using a callable. + Pool::send($client, $requests, [ + 'complete' => function (CompleteEvent $event) { + echo 'Completed request to ' . $event->getRequest()->getUrl() . "\n"; + echo 'Response: ' . $event->getResponse()->getBody() . "\n\n"; + } + ]); + + // The above is equivalent to the following, but the following structure + // allows you to add multiple event listeners to the same event name. + Pool::send($client, $requests, [ + 'complete' => [ + [ + 'fn' => function (CompleteEvent $event) { /* ... */ }, + 'priority' => 0, // Optional + 'once' => false // Optional + ] + ] + ]); + +Asynchronous Response Handling +------------------------------ + +When sending requests concurrently using a pool, the request/response/error +lifecycle must be handled asynchronously. This means that you give the Pool +multiple requests and handle the response or errors that is associated with the +request using event callbacks. + +.. code-block:: php + + use GuzzleHttp\Pool; + use GuzzleHttp\Event\ErrorEvent; + + Pool::send($client, $requests, [ + 'complete' => function (CompleteEvent $event) { + echo 'Completed request to ' . $event->getRequest()->getUrl() . "\n"; + echo 'Response: ' . $event->getResponse()->getBody() . "\n\n"; + // Do something with the completion of the request... + }, + 'error' => function (ErrorEvent $event) { + echo 'Request failed: ' . $event->getRequest()->getUrl() . "\n"; + echo $event->getException(); + // Do something to handle the error... + } + ]); + +The ``GuzzleHttp\Event\ErrorEvent`` event object is emitted when an error +occurs during a transfer. With this event, you have access to the request that +was sent, the response that was received (if one was received), access to +transfer statistics, and the ability to intercept the exception with a +different ``GuzzleHttp\Message\ResponseInterface`` object. See :doc:`events` +for more information. + +Handling Errors After Transferring +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It sometimes might be easier to handle all of the errors that occurred during a +transfer after all of the requests have been sent. Here we are adding each +failed request to an array that we can use to process errors later. + +.. code-block:: php + + use GuzzleHttp\Pool; + use GuzzleHttp\Event\ErrorEvent; + + $errors = []; + Pool::send($client, $requests, [ + 'error' => function (ErrorEvent $event) use (&$errors) { + $errors[] = $event; + } + ]); + + foreach ($errors as $error) { + // Handle the error... + } + +.. _batch-requests: + +Batching Requests +----------------- + +Sometimes you just want to send a few requests concurrently and then process +the results all at once after they've been sent. Guzzle provides a convenience +function ``GuzzleHttp\Pool::batch()`` that makes this very simple: + +.. code-block:: php + + use GuzzleHttp\Pool; + use GuzzleHttp\Client; + + $client = new Client(); + + $requests = [ + $client->createRequest('GET', 'http://httpbin.org/get'), + $client->createRequest('HEAD', 'http://httpbin.org/get'), + $client->createRequest('PUT', 'http://httpbin.org/put'), + ]; + + // Results is a GuzzleHttp\BatchResults object. + $results = Pool::batch($client, $requests); + + // Can be accessed by index. + echo $results[0]->getStatusCode(); + + // Can be accessed by request. + echo $results->getResult($requests[0])->getStatusCode(); + + // Retrieve all successful responses + foreach ($results->getSuccessful() as $response) { + echo $response->getStatusCode() . "\n"; + } + + // Retrieve all failures. + foreach ($results->getFailures() as $requestException) { + echo $requestException->getMessage() . "\n"; + } + +``GuzzleHttp\Pool::batch()`` accepts an optional associative array of options +in the third argument that allows you to specify the 'before', 'complete', +'error', and 'end' events as well as specify the maximum number of requests to +send concurrently using the 'pool_size' option key. + +.. _request-options: + +Request Options +=============== + +You can customize requests created by a client using **request options**. +Request options control various aspects of a request including, headers, +query string parameters, timeout settings, the body of a request, and much +more. + +All of the following examples use the following client: + +.. code-block:: php + + $client = new GuzzleHttp\Client(['base_url' => 'http://httpbin.org']); + +headers +------- + +:Summary: Associative array of headers to add to the request. Each key is the + name of a header, and each value is a string or array of strings + representing the header field values. +:Types: array +:Defaults: None + +.. code-block:: php + + // Set various headers on a request + $client->get('/get', [ + 'headers' => [ + 'User-Agent' => 'testing/1.0', + 'Accept' => 'application/json', + 'X-Foo' => ['Bar', 'Baz'] + ] + ]); + +body +---- + +:Summary: The ``body`` option is used to control the body of an entity + enclosing request (e.g., PUT, POST, PATCH). +:Types: + - string + - ``fopen()`` resource + - ``GuzzleHttp\Stream\StreamInterface`` + - ``GuzzleHttp\Post\PostBodyInterface`` +:Default: None + +This setting can be set to any of the following types: + +- string + + .. code-block:: php + + // You can send requests that use a string as the message body. + $client->put('/put', ['body' => 'foo']); + +- resource returned from ``fopen()`` + + .. code-block:: php + + // You can send requests that use a stream resource as the body. + $resource = fopen('http://httpbin.org', 'r'); + $client->put('/put', ['body' => $resource]); + +- Array + + Use an array to send POST style requests that use a + ``GuzzleHttp\Post\PostBodyInterface`` object as the body. + + .. code-block:: php + + // You can send requests that use a POST body containing fields & files. + $client->post('/post', [ + 'body' => [ + 'field' => 'abc', + 'other_field' => '123', + 'file_name' => fopen('/path/to/file', 'r') + ] + ]); + +- ``GuzzleHttp\Stream\StreamInterface`` + + .. code-block:: php + + // You can send requests that use a Guzzle stream object as the body + $stream = GuzzleHttp\Stream\Stream::factory('contents...'); + $client->post('/post', ['body' => $stream]); + +json +---- + +:Summary: The ``json`` option is used to easily upload JSON encoded data as the + body of a request. A Content-Type header of ``application/json`` will be + added if no Content-Type header is already present on the message. +:Types: + Any PHP type that can be operated on by PHP's ``json_encode()`` function. +:Default: None + +.. code-block:: php + + $request = $client->createRequest('PUT', '/put', ['json' => ['foo' => 'bar']]); + echo $request->getHeader('Content-Type'); + // application/json + echo $request->getBody(); + // {"foo":"bar"} + +.. note:: + + This request option does not support customizing the Content-Type header + or any of the options from PHP's `json_encode() `_ + function. If you need to customize these settings, then you must pass the + JSON encoded data into the request yourself using the ``body`` request + option and you must specify the correct Content-Type header using the + ``headers`` request option. + +query +----- + +:Summary: Associative array of query string values to add to the request. +:Types: + - array + - ``GuzzleHttp\Query`` +:Default: None + +.. code-block:: php + + // Send a GET request to /get?foo=bar + $client->get('/get', ['query' => ['foo' => 'bar']]); + +Query strings specified in the ``query`` option are combined with any query +string values that are parsed from the URL. + +.. code-block:: php + + // Send a GET request to /get?abc=123&foo=bar + $client->get('/get?abc=123', ['query' => ['foo' => 'bar']]); + +auth +---- + +:Summary: Pass an array of HTTP authentication parameters to use with the + request. The array must contain the username in index [0], the password in + index [1], and you can optionally provide a built-in authentication type in + index [2]. Pass ``null`` to disable authentication for a request. +:Types: + - array + - string + - null +:Default: None + +The built-in authentication types are as follows: + +basic + Use `basic HTTP authentication `_ in + the ``Authorization`` header (the default setting used if none is + specified). + + .. code-block:: php + + $client->get('/get', ['auth' => ['username', 'password']]); + +digest + Use `digest authentication `_ (must be + supported by the HTTP handler). + + .. code-block:: php + + $client->get('/get', ['auth' => ['username', 'password', 'digest']]); + + *This is currently only supported when using the cURL handler, but creating + a replacement that can be used with any HTTP handler is planned.* + +.. important:: + + The authentication type (whether it's provided as a string or as the third + option in an array) is always converted to a lowercase string. Take this + into account when implementing custom authentication types and when + implementing custom message factories. + +Custom Authentication Schemes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also provide a string representing a custom authentication type name. +When using a custom authentication type string, you will need to implement +the authentication method in an event listener that checks the ``auth`` request +option of a request before it is sent. Authentication listeners that require +a request is not modified after they are signed should have a very low priority +to ensure that they are fired last or near last in the event chain. + +.. code-block:: php + + use GuzzleHttp\Event\BeforeEvent; + use GuzzleHttp\Event\RequestEvents; + + /** + * Custom authentication listener that handles the "foo" auth type. + * + * Listens to the "before" event of a request and only modifies the request + * when the "auth" config setting of the request is "foo". + */ + class FooAuth implements GuzzleHttp\Event\SubscriberInterface + { + private $password; + + public function __construct($password) + { + $this->password = $password; + } + + public function getEvents() + { + return ['before' => ['sign', RequestEvents::SIGN_REQUEST]]; + } + + public function sign(BeforeEvent $e) + { + if ($e->getRequest()->getConfig()['auth'] == 'foo') { + $e->getRequest()->setHeader('X-Foo', 'Foo ' . $this->password); + } + } + } + + $client->getEmitter()->attach(new FooAuth('password')); + $client->get('/', ['auth' => 'foo']); + +Adapter Specific Authentication Schemes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you need to use authentication methods provided by cURL (e.g., NTLM, GSS, +etc.), then you need to specify a curl handler option in the ``options`` +request option array. See :ref:`config-option` for more information. + +.. _cookies-option: + +cookies +------- + +:Summary: Specifies whether or not cookies are used in a request or what cookie + jar to use or what cookies to send. +:Types: + - bool + - array + - ``GuzzleHttp\Cookie\CookieJarInterface`` +:Default: None + +Set to ``true`` to use a shared cookie session associated with the client. + +.. code-block:: php + + // Enable cookies using the shared cookie jar of the client. + $client->get('/get', ['cookies' => true]); + +Pass an associative array containing cookies to send in the request and start a +new cookie session. + +.. code-block:: php + + // Enable cookies and send specific cookies + $client->get('/get', ['cookies' => ['foo' => 'bar']]); + +Set to a ``GuzzleHttp\Cookie\CookieJarInterface`` object to use an existing +cookie jar. + +.. code-block:: php + + $jar = new GuzzleHttp\Cookie\CookieJar(); + $client->get('/get', ['cookies' => $jar]); + +.. _allow_redirects-option: + +allow_redirects +--------------- + +:Summary: Describes the redirect behavior of a request +:Types: + - bool + - array +:Default: + :: + + [ + 'max' => 5, + 'strict' => false, + 'referer' => true, + 'protocols' => ['http', 'https'] + ] + +Set to ``false`` to disable redirects. + +.. code-block:: php + + $res = $client->get('/redirect/3', ['allow_redirects' => false]); + echo $res->getStatusCode(); + // 302 + +Set to ``true`` (the default setting) to enable normal redirects with a maximum +number of 5 redirects. + +.. code-block:: php + + $res = $client->get('/redirect/3'); + echo $res->getStatusCode(); + // 200 + +Pass an associative array containing the 'max' key to specify the maximum +number of redirects, provide a 'strict' key value to specify whether or not to +use strict RFC compliant redirects (meaning redirect POST requests with POST +requests vs. doing what most browsers do which is redirect POST requests with +GET requests), provide a 'referer' key to specify whether or not the "Referer" +header should be added when redirecting, and provide a 'protocols' array that +specifies which protocols are supported for redirects (defaults to +``['http', 'https']``). + +.. code-block:: php + + $res = $client->get('/redirect/3', [ + 'allow_redirects' => [ + 'max' => 10, // allow at most 10 redirects. + 'strict' => true, // use "strict" RFC compliant redirects. + 'referer' => true, // add a Referer header + 'protocols' => ['https'] // only allow https URLs + ] + ]); + echo $res->getStatusCode(); + // 200 + +decode_content +-------------- + +:Summary: Specify whether or not ``Content-Encoding`` responses (gzip, + deflate, etc.) are automatically decoded. +:Types: + - string + - bool +:Default: ``true`` + +This option can be used to control how content-encoded response bodies are +handled. By default, ``decode_content`` is set to true, meaning any gzipped +or deflated response will be decoded by Guzzle. + +When set to ``false``, the body of a response is never decoded, meaning the +bytes pass through the handler unchanged. + +.. code-block:: php + + // Request gzipped data, but do not decode it while downloading + $client->get('/foo.js', [ + 'headers' => ['Accept-Encoding' => 'gzip'], + 'decode_content' => false + ]); + +When set to a string, the bytes of a response are decoded and the string value +provided to the ``decode_content`` option is passed as the ``Accept-Encoding`` +header of the request. + +.. code-block:: php + + // Pass "gzip" as the Accept-Encoding header. + $client->get('/foo.js', ['decode_content' => 'gzip']); + +.. _save_to-option: + +save_to +------- + +:Summary: Specify where the body of a response will be saved. +:Types: + - string + - ``fopen()`` resource + - ``GuzzleHttp\Stream\StreamInterface`` +:Default: PHP temp stream + +Pass a string to specify the path to a file that will store the contents of the +response body: + +.. code-block:: php + + $client->get('/stream/20', ['save_to' => '/path/to/file']); + +Pass a resource returned from ``fopen()`` to write the response to a PHP stream: + +.. code-block:: php + + $resource = fopen('/path/to/file', 'w'); + $client->get('/stream/20', ['save_to' => $resource]); + +Pass a ``GuzzleHttp\Stream\StreamInterface`` object to stream the response body +to an open Guzzle stream: + +.. code-block:: php + + $resource = fopen('/path/to/file', 'w'); + $stream = GuzzleHttp\Stream\Stream::factory($resource); + $client->get('/stream/20', ['save_to' => $stream]); + +.. _events-option: + +events +------ + +:Summary: An associative array mapping event names to a callable. Or an + associative array containing the 'fn' key that maps to a callable, an + optional 'priority' key used to specify the event priority, and an optional + 'once' key used to specify if the event should remove itself the first time + it is triggered. +:Types: array +:Default: None + +.. code-block:: php + + use GuzzleHttp\Event\BeforeEvent; + use GuzzleHttp\Event\HeadersEvent; + use GuzzleHttp\Event\CompleteEvent; + use GuzzleHttp\Event\ErrorEvent; + + $client->get('/', [ + 'events' => [ + 'before' => function (BeforeEvent $e) { echo 'Before'; }, + 'complete' => function (CompleteEvent $e) { echo 'Complete'; }, + 'error' => function (ErrorEvent $e) { echo 'Error'; }, + ] + ]); + +Here's an example of using the associative array format for control over the +priority and whether or not an event should be triggered more than once. + +.. code-block:: php + + $client->get('/', [ + 'events' => [ + 'before' => [ + 'fn' => function (BeforeEvent $e) { echo 'Before'; }, + 'priority' => 100, + 'once' => true + ] + ] + ]); + +.. _subscribers-option: + +subscribers +----------- + +:Summary: Array of event subscribers to add to the request. Each value in the + array must be an instance of ``GuzzleHttp\Event\SubscriberInterface``. +:Types: array +:Default: None + +.. code-block:: php + + use GuzzleHttp\Subscriber\History; + use GuzzleHttp\Subscriber\Mock; + use GuzzleHttp\Message\Response; + + $history = new History(); + $mock = new Mock([new Response(200)]); + $client->get('/', ['subscribers' => [$history, $mock]]); + + echo $history; + // Outputs the request and response history + +.. _exceptions-option: + +exceptions +---------- + +:Summary: Set to ``false`` to disable throwing exceptions on an HTTP protocol + errors (i.e., 4xx and 5xx responses). Exceptions are thrown by default when + HTTP protocol errors are encountered. +:Types: bool +:Default: ``true`` + +.. code-block:: php + + $client->get('/status/500'); + // Throws a GuzzleHttp\Exception\ServerException + + $res = $client->get('/status/500', ['exceptions' => false]); + echo $res->getStatusCode(); + // 500 + +.. _timeout-option: + +timeout +------- + +:Summary: Float describing the timeout of the request in seconds. Use ``0`` + to wait indefinitely (the default behavior). +:Types: float +:Default: ``0`` + +.. code-block:: php + + // Timeout if a server does not return a response in 3.14 seconds. + $client->get('/delay/5', ['timeout' => 3.14]); + // PHP Fatal error: Uncaught exception 'GuzzleHttp\Exception\RequestException' + +.. _connect_timeout-option: + +connect_timeout +--------------- + +:Summary: Float describing the number of seconds to wait while trying to connect + to a server. Use ``0`` to wait indefinitely (the default behavior). +:Types: float +:Default: ``0`` + +.. code-block:: php + + // Timeout if the client fails to connect to the server in 3.14 seconds. + $client->get('/delay/5', ['connect_timeout' => 3.14]); + +.. note:: + + This setting must be supported by the HTTP handler used to send a request. + ``connect_timeout`` is currently only supported by the built-in cURL + handler. + +.. _verify-option: + +verify +------ + +:Summary: Describes the SSL certificate verification behavior of a request. + + - Set to ``true`` to enable SSL certificate verification and use the default + CA bundle provided by operating system. + - Set to ``false`` to disable certificate verification (this is insecure!). + - Set to a string to provide the path to a CA bundle to enable verification + using a custom certificate. +:Types: + - bool + - string +:Default: ``true`` + +.. code-block:: php + + // Use the system's CA bundle (this is the default setting) + $client->get('/', ['verify' => true]); + + // Use a custom SSL certificate on disk. + $client->get('/', ['verify' => '/path/to/cert.pem']); + + // Disable validation entirely (don't do this!). + $client->get('/', ['verify' => false]); + +Not all system's have a known CA bundle on disk. For example, Windows and +OS X do not have a single common location for CA bundles. When setting +"verify" to ``true``, Guzzle will do its best to find the most appropriate +CA bundle on your system. When using cURL or the PHP stream wrapper on PHP +versions >= 5.6, this happens by default. When using the PHP stream +wrapper on versions < 5.6, Guzzle tries to find your CA bundle in the +following order: + +1. Check if ``openssl.cafile`` is set in your php.ini file. +2. Check if ``curl.cainfo`` is set in your php.ini file. +3. Check if ``/etc/pki/tls/certs/ca-bundle.crt`` exists (Red Hat, CentOS, + Fedora; provided by the ca-certificates package) +4. Check if ``/etc/ssl/certs/ca-certificates.crt`` exists (Ubuntu, Debian; + provided by the ca-certificates package) +5. Check if ``/usr/local/share/certs/ca-root-nss.crt`` exists (FreeBSD; + provided by the ca_root_nss package) +6. Check if ``/usr/local/etc/openssl/cert.pem`` (OS X; provided by homebrew) +7. Check if ``C:\windows\system32\curl-ca-bundle.crt`` exists (Windows) +8. Check if ``C:\windows\curl-ca-bundle.crt`` exists (Windows) + +The result of this lookup is cached in memory so that subsequent calls +in the same process will return very quickly. However, when sending only +a single request per-process in something like Apache, you should consider +setting the ``openssl.cafile`` environment variable to the path on disk +to the file so that this entire process is skipped. + +If you do not need a specific certificate bundle, then Mozilla provides a +commonly used CA bundle which can be downloaded +`here `_ +(provided by the maintainer of cURL). Once you have a CA bundle available on +disk, you can set the "openssl.cafile" PHP ini setting to point to the path to +the file, allowing you to omit the "verify" request option. Much more detail on +SSL certificates can be found on the +`cURL website `_. + +.. _cert-option: + +cert +---- + +:Summary: Set to a string to specify the path to a file containing a PEM + formatted client side certificate. If a password is required, then set to + an array containing the path to the PEM file in the first array element + followed by the password required for the certificate in the second array + element. +:Types: + - string + - array +:Default: None + +.. code-block:: php + + $client->get('/', ['cert' => ['/path/server.pem', 'password']]); + +.. _ssl_key-option: + +ssl_key +------- + +:Summary: Specify the path to a file containing a private SSL key in PEM + format. If a password is required, then set to an array containing the path + to the SSL key in the first array element followed by the password required + for the certificate in the second element. +:Types: + - string + - array +:Default: None + +.. note:: + + ``ssl_key`` is implemented by HTTP handlers. This is currently only + supported by the cURL handler, but might be supported by other third-part + handlers. + +.. _proxy-option: + +proxy +----- + +:Summary: Pass a string to specify an HTTP proxy, or an array to specify + different proxies for different protocols. +:Types: + - string + - array +:Default: None + +Pass a string to specify a proxy for all protocols. + +.. code-block:: php + + $client->get('/', ['proxy' => 'tcp://localhost:8125']); + +Pass an associative array to specify HTTP proxies for specific URI schemes +(i.e., "http", "https"). + +.. code-block:: php + + $client->get('/', [ + 'proxy' => [ + 'http' => 'tcp://localhost:8125', // Use this proxy with "http" + 'https' => 'tcp://localhost:9124' // Use this proxy with "https" + ] + ]); + +.. note:: + + You can provide proxy URLs that contain a scheme, username, and password. + For example, ``"http://username:password@192.168.16.1:10"``. + +.. _debug-option: + +debug +----- + +:Summary: Set to ``true`` or set to a PHP stream returned by ``fopen()`` to + enable debug output with the handler used to send a request. For example, + when using cURL to transfer requests, cURL's verbose of ``CURLOPT_VERBOSE`` + will be emitted. When using the PHP stream wrapper, stream wrapper + notifications will be emitted. If set to true, the output is written to + PHP's STDOUT. If a PHP stream is provided, output is written to the stream. +:Types: + - bool + - ``fopen()`` resource +:Default: None + +.. code-block:: php + + $client->get('/get', ['debug' => true]); + +Running the above example would output something like the following: + +:: + + * About to connect() to httpbin.org port 80 (#0) + * Trying 107.21.213.98... * Connected to httpbin.org (107.21.213.98) port 80 (#0) + > GET /get HTTP/1.1 + Host: httpbin.org + User-Agent: Guzzle/4.0 curl/7.21.4 PHP/5.5.7 + + < HTTP/1.1 200 OK + < Access-Control-Allow-Origin: * + < Content-Type: application/json + < Date: Sun, 16 Feb 2014 06:50:09 GMT + < Server: gunicorn/0.17.4 + < Content-Length: 335 + < Connection: keep-alive + < + * Connection #0 to host httpbin.org left intact + +.. _stream-option: + +stream +------ + +:Summary: Set to ``true`` to stream a response rather than download it all + up-front. +:Types: bool +:Default: ``false`` + +.. code-block:: php + + $response = $client->get('/stream/20', ['stream' => true]); + // Read bytes off of the stream until the end of the stream is reached + $body = $response->getBody(); + while (!$body->eof()) { + echo $body->read(1024); + } + +.. note:: + + Streaming response support must be implemented by the HTTP handler used by + a client. This option might not be supported by every HTTP handler, but the + interface of the response object remains the same regardless of whether or + not it is supported by the handler. + +.. _expect-option: + +expect +------ + +:Summary: Controls the behavior of the "Expect: 100-Continue" header. +:Types: + - bool + - integer +:Default: ``1048576`` + +Set to ``true`` to enable the "Expect: 100-Continue" header for all requests +that sends a body. Set to ``false`` to disable the "Expect: 100-Continue" +header for all requests. Set to a number so that the size of the payload must +be greater than the number in order to send the Expect header. Setting to a +number will send the Expect header for all requests in which the size of the +payload cannot be determined or where the body is not rewindable. + +By default, Guzzle will add the "Expect: 100-Continue" header when the size of +the body of a request is greater than 1 MB and a request is using HTTP/1.1. + +.. note:: + + This option only takes effect when using HTTP/1.1. The HTTP/1.0 and + HTTP/2.0 protocols do not support the "Expect: 100-Continue" header. + Support for handling the "Expect: 100-Continue" workflow must be + implemented by Guzzle HTTP handlers used by a client. + +.. _version-option: + +version +------- + +:Summary: Protocol version to use with the request. +:Types: string, float +:Default: ``1.1`` + +.. code-block:: php + + // Force HTTP/1.0 + $request = $client->createRequest('GET', '/get', ['version' => 1.0]); + echo $request->getProtocolVersion(); + // 1.0 + +.. _config-option: + +config +------ + +:Summary: Associative array of config options that are forwarded to a request's + configuration collection. These values are used as configuration options + that can be consumed by plugins and handlers. +:Types: array +:Default: None + +.. code-block:: php + + $request = $client->createRequest('GET', '/get', ['config' => ['foo' => 'bar']]); + echo $request->getConfig('foo'); + // 'bar' + +Some HTTP handlers allow you to specify custom handler-specific settings. For +example, you can pass custom cURL options to requests by passing an associative +array in the ``config`` request option under the ``curl`` key. + +.. code-block:: php + + // Use custom cURL options with the request. This example uses NTLM auth + // to authenticate with a server. + $client->get('/', [ + 'config' => [ + 'curl' => [ + CURLOPT_HTTPAUTH => CURLAUTH_NTLM, + CURLOPT_USERPWD => 'username:password' + ] + ] + ]); + +future +------ + +:Summary: Specifies whether or not a response SHOULD be an instance of a + ``GuzzleHttp\Message\FutureResponse`` object. +:Types: + - bool + - string +:Default: ``false`` + +By default, Guzzle requests should be synchronous. You can create asynchronous +future responses by passing the ``future`` request option as ``true``. The +response will only be executed when it is used like a normal response, the +``wait()`` method of the response is called, or the corresponding handler that +created the response is destructing and there are futures that have not been +resolved. + +.. important:: + + This option only has an effect if your handler can create and return future + responses. However, even if a response is completed synchronously, Guzzle + will ensure that a FutureResponse object is returned for API consistency. + +.. code-block:: php + + $response = $client->get('/foo', ['future' => true]) + ->then(function ($response) { + echo 'I got a response! ' . $response; + }); + +Event Subscribers +================= + +Requests emit lifecycle events when they are transferred. A client object has a +``GuzzleHttp\Common\EventEmitter`` object that can be used to add event +*listeners* and event *subscribers* to all requests created by the client. + +.. important:: + + **Every** event listener or subscriber added to a client will be added to + every request created by the client. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Event\BeforeEvent; + + $client = new Client(); + + // Add a listener that will echo out requests before they are sent + $client->getEmitter()->on('before', function (BeforeEvent $e) { + echo 'About to send request: ' . $e->getRequest(); + }); + + $client->get('http://httpbin.org/get'); + // Outputs the request as a string because of the event + +See :doc:`events` for more information on the event system used in Guzzle. + +Environment Variables +===================== + +Guzzle exposes a few environment variables that can be used to customize the +behavior of the library. + +``GUZZLE_CURL_SELECT_TIMEOUT`` + Controls the duration in seconds that a curl_multi_* handler will use when + selecting on curl handles using ``curl_multi_select()``. Some systems + have issues with PHP's implementation of ``curl_multi_select()`` where + calling this function always results in waiting for the maximum duration of + the timeout. +``HTTP_PROXY`` + Defines the proxy to use when sending requests using the "http" protocol. +``HTTPS_PROXY`` + Defines the proxy to use when sending requests using the "https" protocol. + +Relevant ini Settings +--------------------- + +Guzzle can utilize PHP ini settings when configuring clients. + +``openssl.cafile`` + Specifies the path on disk to a CA file in PEM format to use when sending + requests over "https". See: https://wiki.php.net/rfc/tls-peer-verification#phpini_defaults diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/conf.py b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/conf.py new file mode 100644 index 0000000..917bdf4 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/conf.py @@ -0,0 +1,28 @@ +import sys, os +from sphinx.highlighting import lexers +from pygments.lexers.web import PhpLexer + + +lexers['php'] = PhpLexer(startinline=True, linenos=1) +lexers['php-annotations'] = PhpLexer(startinline=True, linenos=1) +primary_domain = 'php' + +extensions = [] +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' +project = u'Guzzle' +copyright = u'2014, Michael Dowling' +version = '5.0.0' +html_title = "Guzzle Documentation" +html_short_title = "Guzzle" + +exclude_patterns = ['_build'] +html_static_path = ['_static'] + +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/events.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/events.rst new file mode 100644 index 0000000..30afdb6 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/events.rst @@ -0,0 +1,516 @@ +============ +Event System +============ + +Guzzle uses an event emitter to allow you to easily extend the behavior of a +request, change the response associated with a request, and implement custom +error handling. All events in Guzzle are managed and emitted by an +**event emitter**. + +Event Emitters +============== + +Clients, requests, and any other class that implements the +``GuzzleHttp\Event\HasEmitterInterface`` interface have a +``GuzzleHttp\Event\Emitter`` object. You can add event *listeners* and +event *subscribers* to an event *emitter*. + +emitter + An object that implements ``GuzzleHttp\Event\EmitterInterface``. This + object emits named events to event listeners. You may register event + listeners on subscribers on an emitter. + +event listeners + Callable functions that are registered on an event emitter for specific + events. Event listeners are registered on an emitter with a *priority* + setting. If no priority is provided, ``0`` is used by default. + +event subscribers + Classes that tell an event emitter what methods to listen to and what + functions on the class to invoke when the event is triggered. Event + subscribers subscribe event listeners to an event emitter. They should be + used when creating more complex event based logic in applications (i.e., + cookie handling is implemented using an event subscriber because it's + easier to share a subscriber than an anonymous function and because + handling cookies is a complex process). + +priority + Describes the order in which event listeners are invoked when an event is + emitted. The higher a priority value, the earlier the event listener will + be invoked (a higher priority means the listener is more important). If + no priority is provided, the priority is assumed to be ``0``. + + When specifying an event priority, you can pass ``"first"`` or ``"last"`` to + dynamically specify the priority based on the current event priorities + associated with the given event name in the emitter. Use ``"first"`` to set + the priority to the current highest priority plus one. Use ``"last"`` to + set the priority to the current lowest event priority minus one. It is + important to remember that these dynamic priorities are calculated only at + the point of insertion into the emitter and they are not rearranged after + subsequent listeners are added to an emitter. + +propagation + Describes whether or not other event listeners are triggered. Event + emitters will trigger every event listener registered to a specific event + in priority order until all of the listeners have been triggered **or** + until the propagation of an event is stopped. + +Getting an EventEmitter +----------------------- + +You can get the event emitter of ``GuzzleHttp\Event\HasEmitterInterface`` +object using the the ``getEmitter()`` method. Here's an example of getting a +client object's event emitter. + +.. code-block:: php + + $client = new GuzzleHttp\Client(); + $emitter = $client->getEmitter(); + +.. note:: + + You'll notice that the event emitter used in Guzzle is very similar to the + `Symfony2 EventDispatcher component `_. + This is because the Guzzle event system is based on the Symfony2 event + system with several changes. Guzzle uses its own event emitter to improve + performance, isolate Guzzle from changes to the Symfony, and provide a few + improvements that make it easier to use for an HTTP client (e.g., the + addition of the ``once()`` method). + +Adding Event Listeners +---------------------- + +After you have the emitter, you can register event listeners that listen to +specific events using the ``on()`` method. When registering an event listener, +you must tell the emitter what event to listen to (e.g., "before", "after", +"progress", "complete", "error", etc.), what callable to invoke when the +event is triggered, and optionally provide a priority. + +.. code-block:: php + + use GuzzleHttp\Event\BeforeEvent; + + $emitter->on('before', function (BeforeEvent $event) { + echo $event->getRequest(); + }); + +When a listener is triggered, it is passed an event that implements the +``GuzzleHttp\Event\EventInterface`` interface, the name of the event, and the +event emitter itself. The above example could more verbosely be written as +follows: + +.. code-block:: php + + use GuzzleHttp\Event\BeforeEvent; + + $emitter->on('before', function (BeforeEvent $event, $name) { + echo $event->getRequest(); + }); + +You can add an event listener that automatically removes itself after it is +triggered using the ``once()`` method of an event emitter. + +.. code-block:: php + + $client = new GuzzleHttp\Client(); + $client->getEmitter()->once('before', function () { + echo 'This will only happen once... per request!'; + }); + +Event Propagation +----------------- + +Event listeners can prevent other event listeners from being triggered by +stopping an event's propagation. + +Stopping event propagation can be useful, for example, if an event listener has +changed the state of the subject to such an extent that allowing subsequent +event listeners to be triggered could place the subject in an inconsistent +state. This technique is used in Guzzle extensively when intercepting error +events with responses. + +You can stop the propagation of an event using the ``stopPropagation()`` method +of a ``GuzzleHttp\Event\EventInterface`` object: + +.. code-block:: php + + use GuzzleHttp\Event\ErrorEvent; + + $emitter->on('error', function (ErrorEvent $event) { + $event->stopPropagation(); + }); + +After stopping the propagation of an event, any subsequent event listeners that +have not yet been triggered will not be triggered. You can check to see if the +propagation of an event was stopped using the ``isPropagationStopped()`` method +of the event. + +.. code-block:: php + + $client = new GuzzleHttp\Client(); + $emitter = $client->getEmitter(); + // Note: assume that the $errorEvent was created + if ($emitter->emit('error', $errorEvent)->isPropagationStopped()) { + echo 'It was stopped!'; + } + +.. hint:: + + When emitting events, the event that was emitted is returned from the + emitter. This allows you to easily chain calls as shown in the above + example. + +Event Subscribers +----------------- + +Event subscribers are classes that implement the +``GuzzleHttp\Event\SubscriberInterface`` object. They are used to register +one or more event listeners to methods of the class. Event subscribers tell +event emitters exactly which events to listen to and what method to invoke on +the class when the event is triggered by called the ``getEvents()`` method of +a subscriber. + +The following example registers event listeners to the ``before`` and +``complete`` event of a request. When the ``before`` event is emitted, the +``onBefore`` instance method of the subscriber is invoked. When the +``complete`` event is emitted, the ``onComplete`` event of the subscriber is +invoked. Each array value in the ``getEvents()`` return value MUST +contain the name of the method to invoke and can optionally contain the +priority of the listener (as shown in the ``before`` listener in the example). + +.. code-block:: php + + use GuzzleHttp\Event\EmitterInterface; + use GuzzleHttp\Event\SubscriberInterface; + use GuzzleHttp\Event\BeforeEvent; + use GuzzleHttp\Event\CompleteEvent; + + class SimpleSubscriber implements SubscriberInterface + { + public function getEvents() + { + return [ + // Provide name and optional priority + 'before' => ['onBefore', 100], + 'complete' => ['onComplete'], + // You can pass a list of listeners with different priorities + 'error' => [['beforeError', 'first'], ['afterError', 'last']] + ]; + } + + public function onBefore(BeforeEvent $event, $name) + { + echo 'Before!'; + } + + public function onComplete(CompleteEvent $event, $name) + { + echo 'Complete!'; + } + } + +To register the listeners the subscriber needs to be attached to the emitter: + +.. code-block:: php + + $client = new GuzzleHttp\Client(); + $emitter = $client->getEmitter(); + $subscriber = new SimpleSubscriber(); + $emitter->attach($subscriber); + + //to remove the listeners + $emitter->detach($subscriber); + +.. note:: + + You can specify event priorities using integers or ``"first"`` and + ``"last"`` to dynamically determine the priority. + +Event Priorities +================ + +When adding event listeners or subscribers, you can provide an optional event +priority. This priority is used to determine how early or late a listener is +triggered. Specifying the correct priority is an important aspect of ensuring +a listener behaves as expected. For example, if you wanted to ensure that +cookies associated with a redirect were added to a cookie jar, you'd need to +make sure that the listener that collects the cookies is triggered before the +listener that performs the redirect. + +In order to help make the process of determining the correct event priority of +a listener easier, Guzzle provides several pre-determined named event +priorities. These priorities are exposed as constants on the +``GuzzleHttp\Event\RequestEvents`` object. + +last + Use ``"last"`` as an event priority to set the priority to the current + lowest event priority minus one. + +first + Use ``"first"`` as an event priority to set the priority to the current + highest priority plus one. + +``GuzzleHttp\Event\RequestEvents::EARLY`` + Used when you want a listener to be triggered as early as possible in the + event chain. + +``GuzzleHttp\Event\RequestEvents::LATE`` + Used when you want a listener to be to be triggered as late as possible in + the event chain. + +``GuzzleHttp\Event\RequestEvents::PREPARE_REQUEST`` + Used when you want a listener to be trigger while a request is being + prepared during the ``before`` event. This event priority is used by the + ``GuzzleHttp\Subscriber\Prepare`` event subscriber which is responsible for + guessing a Content-Type, Content-Length, and Expect header of a request. + You should subscribe after this event is triggered if you want to ensure + that this subscriber has already been triggered. + +``GuzzleHttp\Event\RequestEvents::SIGN_REQUEST`` + Used when you want a listener to be triggered when a request is about to be + signed. Any listener triggered at this point should expect that the request + object will no longer be mutated. If you are implementing a custom + signature subscriber, then you should use this event priority to sign + requests. + +``GuzzleHttp\Event\RequestEvents::VERIFY_RESPONSE`` + Used when you want a listener to be triggered when a response is being + validated during the ``complete`` event. The + ``GuzzleHttp\Subscriber\HttpError`` event subscriber uses this event + priority to check if an exception should be thrown due to a 4xx or 5xx + level response status code. If you are doing any kind of verification of a + response during the complete event, it should happen at this priority. + +``GuzzleHttp\Event\RequestEvents::REDIRECT_RESPONSE`` + Used when you want a listener to be triggered when a response is being + redirected during the ``complete`` event. The + ``GuzzleHttp\Subscriber\Redirect`` event subscriber uses this event + priority when performing redirects. + +You can use the above event priorities as a guideline for determining the +priority of you event listeners. You can use these constants and add to or +subtract from them to ensure that a listener happens before or after the named +priority. + +.. note:: + + "first" and "last" priorities are not adjusted after they added to an + emitter. For example, if you add a listener with a priority of "first", + you can still add subsequent listeners with a higher priority which would + be triggered before the listener added with a priority of "first". + +Working With Request Events +=========================== + +Requests emit lifecycle events when they are transferred. + +.. important:: + + Excluding the ``end`` event, request lifecycle events may be triggered + multiple times due to redirects, retries, or reusing a request multiple + times. Use the ``once()`` method want the event to be triggered once. You + can also remove an event listener from an emitter by using the emitter which + is provided to the listener. + +.. _before_event: + +before +------ + +The ``before`` event is emitted before a request is sent. The event emitted is +a ``GuzzleHttp\Event\BeforeEvent``. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Event\EmitterInterface; + use GuzzleHttp\Event\BeforeEvent; + + $client = new Client(['base_url' => 'http://httpbin.org']); + $request = $client->createRequest('GET', '/'); + $request->getEmitter()->on( + 'before', + function (BeforeEvent $e, $name) { + echo $name . "\n"; + // "before" + echo $e->getRequest()->getMethod() . "\n"; + // "GET" / "POST" / "PUT" / etc. + echo get_class($e->getClient()); + // "GuzzleHttp\Client" + } + ); + +You can intercept a request with a response before the request is sent over the +wire. The ``intercept()`` method of the ``BeforeEvent`` accepts a +``GuzzleHttp\Message\ResponseInterface``. Intercepting the event will prevent +the request from being sent over the wire and stops the propagation of the +``before`` event, preventing subsequent event listeners from being invoked. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Event\BeforeEvent; + use GuzzleHttp\Message\Response; + + $client = new Client(['base_url' => 'http://httpbin.org']); + $request = $client->createRequest('GET', '/status/500'); + $request->getEmitter()->on('before', function (BeforeEvent $e) { + $response = new Response(200); + $e->intercept($response); + }); + + $response = $client->send($request); + echo $response->getStatusCode(); + // 200 + +.. attention:: + + Any exception encountered while executing the ``before`` event will trigger + the ``error`` event of a request. + +.. _complete_event: + +complete +-------- + +The ``complete`` event is emitted after a transaction completes and an entire +response has been received. The event is a ``GuzzleHttp\Event\CompleteEvent``. + +You can intercept the ``complete`` event with a different response if needed +using the ``intercept()`` method of the event. This can be useful, for example, +for changing the response for caching. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Event\CompleteEvent; + use GuzzleHttp\Message\Response; + + $client = new Client(['base_url' => 'http://httpbin.org']); + $request = $client->createRequest('GET', '/status/302'); + $cachedResponse = new Response(200); + + $request->getEmitter()->on( + 'complete', + function (CompleteEvent $e) use ($cachedResponse) { + if ($e->getResponse()->getStatusCode() == 302) { + // Intercept the original transaction with the new response + $e->intercept($cachedResponse); + } + } + ); + + $response = $client->send($request); + echo $response->getStatusCode(); + // 200 + +.. attention:: + + Any ``GuzzleHttp\Exception\RequestException`` encountered while executing + the ``complete`` event will trigger the ``error`` event of a request. + +.. _error_event: + +error +----- + +The ``error`` event is emitted when a request fails (whether it's from a +networking error or an HTTP protocol error). The event emitted is a +``GuzzleHttp\Event\ErrorEvent``. + +This event is useful for retrying failed requests. Here's an example of +retrying failed basic auth requests by re-sending the original request with +a username and password. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Event\ErrorEvent; + + $client = new Client(['base_url' => 'http://httpbin.org']); + $request = $client->createRequest('GET', '/basic-auth/foo/bar'); + $request->getEmitter()->on('error', function (ErrorEvent $e) { + if ($e->getResponse()->getStatusCode() == 401) { + // Add authentication stuff as needed and retry the request + $e->getRequest()->setHeader('Authorization', 'Basic ' . base64_encode('foo:bar')); + // Get the client of the event and retry the request + $newResponse = $e->getClient()->send($e->getRequest()); + // Intercept the original transaction with the new response + $e->intercept($newResponse); + } + }); + +.. attention:: + + If an ``error`` event is intercepted with a response, then the ``complete`` + event of a request is triggered. If the ``complete`` event fails, then the + ``error`` event is triggered once again. + +.. _progress_event: + +progress +-------- + +The ``progress`` event is emitted when data is uploaded or downloaded. The +event emitted is a ``GuzzleHttp\Event\ProgressEvent``. + +You can access the emitted progress values using the corresponding public +properties of the event object: + +- ``$downloadSize``: The number of bytes that will be downloaded (if known) +- ``$downloaded``: The number of bytes that have been downloaded +- ``$uploadSize``: The number of bytes that will be uploaded (if known) +- ``$uploaded``: The number of bytes that have been uploaded + +This event cannot be intercepted. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Event\ProgressEvent; + + $client = new Client(['base_url' => 'http://httpbin.org']); + $request = $client->createRequest('PUT', '/put', [ + 'body' => str_repeat('.', 100000) + ]); + + $request->getEmitter()->on('progress', function (ProgressEvent $e) { + echo 'Downloaded ' . $e->downloaded . ' of ' . $e->downloadSize . ' ' + . 'Uploaded ' . $e->uploaded . ' of ' . $e->uploadSize . "\r"; + }); + + $client->send($request); + echo "\n"; + +.. _end_event: + +end +--- + +The ``end`` event is a terminal event, emitted once per request, that provides +access to the response that was received or the exception that was encountered. +The event emitted is a ``GuzzleHttp\Event\EndEvent``. + +This event can be intercepted, but keep in mind that the ``complete`` event +will not fire after intercepting this event. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Event\EndEvent; + + $client = new Client(['base_url' => 'http://httpbin.org']); + $request = $client->createRequest('PUT', '/put', [ + 'body' => str_repeat('.', 100000) + ]); + + $request->getEmitter()->on('end', function (EndEvent $e) { + if ($e->getException()) { + echo 'Error: ' . $e->getException()->getMessage(); + } else { + echo 'Response: ' . $e->getResponse(); + } + }); + + $client->send($request); + echo "\n"; diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/faq.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/faq.rst new file mode 100644 index 0000000..a8e9ad0 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/faq.rst @@ -0,0 +1,199 @@ +=== +FAQ +=== + +Why should I use Guzzle? +======================== + +Guzzle makes it easy to send HTTP requests and super simple to integrate with +web services. Guzzle manages things like persistent connections, represents +query strings as collections, makes it simple to send streaming POST requests +with fields and files, and abstracts away the underlying HTTP transport layer. +By providing an object oriented interface for HTTP clients, requests, responses, +headers, and message bodies, Guzzle makes it so that you no longer need to fool +around with cURL options, stream contexts, or sockets. + +**Asynchronous and Synchronous Requests** + +Guzzle allows you to send both asynchronous and synchronous requests using the +same interface and no direct dependency on an event loop. This flexibility +allows Guzzle to send an HTTP request using the most appropriate HTTP handler +based on the request being sent. For example, when sending synchronous +requests, Guzzle will by default send requests using cURL easy handles to +ensure you're using the fastest possible method for serially transferring HTTP +requests. When sending asynchronous requests, Guzzle might use cURL's multi +interface or any other asynchronous handler you configure. When you request +streaming data, Guzzle will by default use PHP's stream wrapper. + +**Streams** + +Request and response message bodies use :doc:`Guzzle Streams `, +allowing you to stream data without needing to load it all into memory. +Guzzle's stream layer provides a large suite of functionality: + +- You can modify streams at runtime using custom or a number of + pre-made decorators. +- You can emit progress events as data is read from a stream. +- You can validate the integrity of a stream using a rolling hash as data is + read from a stream. + +**Event System and Plugins** + +Guzzle's event system allows you to completely modify the behavior of a client +or request at runtime to cater them for any API. You can send a request with a +client, and the client can do things like automatically retry your request if +it fails, automatically redirect, log HTTP messages that are sent over the +wire, emit progress events as data is uploaded and downloaded, sign requests +using OAuth 1.0, verify the integrity of messages before and after they are +sent over the wire, and anything else you might need. + +**Testable** + +Another important aspect of Guzzle is that it's really +:doc:`easy to test clients `. You can mock HTTP responses and when +testing an handler implementation, Guzzle provides a mock node.js web server. + +**Ecosystem** + +Guzzle has a large `ecosystem of plugins `_, +including `service descriptions `_ +which allows you to abstract web services using service descriptions. These +service descriptions define how to serialize an HTTP request and how to parse +an HTTP response into a more meaningful model object. + +- `Guzzle Command `_: Provides the building + blocks for service description abstraction. +- `Guzzle Services `_: Provides an + implementation of "Guzzle Command" that utilizes Guzzle's service description + format. + +Does Guzzle require cURL? +========================= + +No. Guzzle can use any HTTP handler to send requests. This means that Guzzle +can be used with cURL, PHP's stream wrapper, sockets, and non-blocking libraries +like `React `_. You just need to configure a +`RingPHP `_ handler to use a +different method of sending requests. + +.. note:: + + Guzzle has historically only utilized cURL to send HTTP requests. cURL is + an amazing HTTP client (arguably the best), and Guzzle will continue to use + it by default when it is available. It is rare, but some developers don't + have cURL installed on their systems or run into version specific issues. + By allowing swappable HTTP handlers, Guzzle is now much more customizable + and able to adapt to fit the needs of more developers. + +Can Guzzle send asynchronous requests? +====================================== + +Yes. Pass the ``future`` true request option to a request to send it +asynchronously. Guzzle will then return a ``GuzzleHttp\Message\FutureResponse`` +object that can be used synchronously by accessing the response object like a +normal response, and it can be used asynchronously using a promise that is +notified when the response is resolved with a real response or rejected with an +exception. + +.. code-block:: php + + $request = $client->createRequest('GET', ['future' => true]); + $client->send($request)->then(function ($response) { + echo 'Got a response! ' . $response; + }); + +You can force an asynchronous response to complete using the ``wait()`` method +of a response. + +.. code-block:: php + + $request = $client->createRequest('GET', ['future' => true]); + $futureResponse = $client->send($request); + $futureResponse->wait(); + +How can I add custom cURL options? +================================== + +cURL offer a huge number of `customizable options `_. +While Guzzle normalizes many of these options across different handlers, there +are times when you need to set custom cURL options. This can be accomplished +by passing an associative array of cURL settings in the **curl** key of the +**config** request option. + +For example, let's say you need to customize the outgoing network interface +used with a client. + +.. code-block:: php + + $client->get('/', [ + 'config' => [ + 'curl' => [ + CURLOPT_INTERFACE => 'xxx.xxx.xxx.xxx' + ] + ] + ]); + +How can I add custom stream context options? +============================================ + +You can pass custom `stream context options `_ +using the **stream_context** key of the **config** request option. The +**stream_context** array is an associative array where each key is a PHP +transport, and each value is an associative array of transport options. + +For example, let's say you need to customize the outgoing network interface +used with a client and allow self-signed certificates. + +.. code-block:: php + + $client->get('/', [ + 'stream' => true, + 'config' => [ + 'stream_context' => [ + 'ssl' => [ + 'allow_self_signed' => true + ], + 'socket' => [ + 'bindto' => 'xxx.xxx.xxx.xxx' + ] + ] + ] + ]); + +Why am I getting an SSL verification error? +=========================================== + +You need to specify the path on disk to the CA bundle used by Guzzle for +verifying the peer certificate. See :ref:`verify-option`. + +What is this Maximum function nesting error? +============================================ + + Maximum function nesting level of '100' reached, aborting + +You could run into this error if you have the XDebug extension installed and +you execute a lot of requests in callbacks. This error message comes +specifically from the XDebug extension. PHP itself does not have a function +nesting limit. Change this setting in your php.ini to increase the limit:: + + xdebug.max_nesting_level = 1000 + +Why am I getting a 417 error response? +====================================== + +This can occur for a number of reasons, but if you are sending PUT, POST, or +PATCH requests with an ``Expect: 100-Continue`` header, a server that does not +support this header will return a 417 response. You can work around this by +setting the ``expect`` request option to ``false``: + +.. code-block:: php + + $client = new GuzzleHttp\Client(); + + // Disable the expect header on a single request + $response = $client->put('/', [], 'the body', [ + 'expect' => false + ]); + + // Disable the expect header on all client requests + $client->setDefaultOption('expect', false) diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/handlers.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/handlers.rst new file mode 100644 index 0000000..d452003 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/handlers.rst @@ -0,0 +1,43 @@ +================ +RingPHP Handlers +================ + +Guzzle uses RingPHP handlers to send HTTP requests over the wire. +RingPHP provides a low-level library that can be used to "glue" Guzzle with +any transport method you choose. By default, Guzzle utilizes cURL and PHP's +stream wrappers to send HTTP requests. + +RingPHP handlers makes it extremely simple to integrate Guzzle with any +HTTP transport. For example, you could quite easily bridge Guzzle and React +to use Guzzle in React's event loop. + +Using a handler +--------------- + +You can change the handler used by a client using the ``handler`` option in the +``GuzzleHttp\Client`` constructor. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Ring\Client\MockHandler; + + // Create a mock handler that always returns a 200 response. + $handler = new MockHandler(['status' => 200]); + + // Configure to client to use the mock handler. + $client = new Client(['handler' => $handler]); + +At its core, handlers are simply PHP callables that accept a request array +and return a ``GuzzleHttp\Ring\Future\FutureArrayInterface``. This future array +can be used just like a normal PHP array, causing it to block, or you can use +the promise interface using the ``then()`` method of the future. Guzzle hooks +up to the RingPHP project using a very simple bridge class +(``GuzzleHttp\RingBridge``). + +Creating a handler +------------------ + +See the `RingPHP `_ project +documentation for more information on creating custom handlers that can be +used with Guzzle clients. diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/http-messages.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/http-messages.rst new file mode 100644 index 0000000..0c6527a --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/http-messages.rst @@ -0,0 +1,483 @@ +============================= +Request and Response Messages +============================= + +Guzzle is an HTTP client that sends HTTP requests to a server and receives HTTP +responses. Both requests and responses are referred to as messages. + +Headers +======= + +Both request and response messages contain HTTP headers. + +Complex Headers +--------------- + +Some headers contain additional key value pair information. For example, Link +headers contain a link and several key value pairs: + +:: + + ; rel="thing"; type="image/jpeg" + +Guzzle provides a convenience feature that can be used to parse these types of +headers: + +.. code-block:: php + + use GuzzleHttp\Message\Request; + + $request = new Request('GET', '/', [ + 'Link' => '; rel="front"; type="image/jpeg"' + ]); + + $parsed = Request::parseHeader($request, 'Link'); + var_export($parsed); + +Will output: + +.. code-block:: php + + array ( + 0 => + array ( + 0 => '', + 'rel' => 'front', + 'type' => 'image/jpeg', + ), + ) + +The result contains a hash of key value pairs. Header values that have no key +(i.e., the link) are indexed numerically while headers parts that form a key +value pair are added as a key value pair. + +See :ref:`headers` for information on how the headers of a request and response +can be accessed and modified. + +Body +==== + +Both request and response messages can contain a body. + +You can check to see if a request or response has a body using the +``getBody()`` method: + +.. code-block:: php + + $response = GuzzleHttp\get('http://httpbin.org/get'); + if ($response->getBody()) { + echo $response->getBody(); + // JSON string: { ... } + } + +The body used in request and response objects is a +``GuzzleHttp\Stream\StreamInterface``. This stream is used for both uploading +data and downloading data. Guzzle will, by default, store the body of a message +in a stream that uses PHP temp streams. When the size of the body exceeds +2 MB, the stream will automatically switch to storing data on disk rather than +in memory (protecting your application from memory exhaustion). + +You can change the body used in a request or response using the ``setBody()`` +method: + +.. code-block:: php + + use GuzzleHttp\Stream\Stream; + $request = $client->createRequest('PUT', 'http://httpbin.org/put'); + $request->setBody(Stream::factory('foo')); + +The easiest way to create a body for a request is using the static +``GuzzleHttp\Stream\Stream::factory()`` method. This method accepts various +inputs like strings, resources returned from ``fopen()``, and other +``GuzzleHttp\Stream\StreamInterface`` objects. + +The body of a request or response can be cast to a string or you can read and +write bytes off of the stream as needed. + +.. code-block:: php + + use GuzzleHttp\Stream\Stream; + $request = $client->createRequest('PUT', 'http://httpbin.org/put', ['body' => 'testing...']); + + echo $request->getBody()->read(4); + // test + echo $request->getBody()->read(4); + // ing. + echo $request->getBody()->read(1024); + // .. + var_export($request->eof()); + // true + +You can find out more about Guzzle stream objects in :doc:`streams`. + +Requests +======== + +Requests are sent from a client to a server. Requests include the method to +be applied to a resource, the identifier of the resource, and the protocol +version to use. + +Clients are used to create request messages. More precisely, clients use +a ``GuzzleHttp\Message\MessageFactoryInterface`` to create request messages. +You create requests with a client using the ``createRequest()`` method. + +.. code-block:: php + + // Create a request but don't send it immediately + $request = $client->createRequest('GET', 'http://httpbin.org/get'); + +Request Methods +--------------- + +When creating a request, you are expected to provide the HTTP method you wish +to perform. You can specify any method you'd like, including a custom method +that might not be part of RFC 7231 (like "MOVE"). + +.. code-block:: php + + // Create a request using a completely custom HTTP method + $request = $client->createRequest('MOVE', 'http://httpbin.org/move', ['exceptions' => false]); + + echo $request->getMethod(); + // MOVE + + $response = $client->send($request); + echo $response->getStatusCode(); + // 405 + +You can create and send a request using methods on a client that map to the +HTTP method you wish to use. + +:GET: ``$client->get('http://httpbin.org/get', [/** options **/])`` +:POST: ``$client->post('http://httpbin.org/post', [/** options **/])`` +:HEAD: ``$client->head('http://httpbin.org/get', [/** options **/])`` +:PUT: ``$client->put('http://httpbin.org/put', [/** options **/])`` +:DELETE: ``$client->delete('http://httpbin.org/delete', [/** options **/])`` +:OPTIONS: ``$client->options('http://httpbin.org/get', [/** options **/])`` +:PATCH: ``$client->patch('http://httpbin.org/put', [/** options **/])`` + +.. code-block:: php + + $response = $client->patch('http://httpbin.org/patch', ['body' => 'content']); + +Request URI +----------- + +The resource you are requesting with an HTTP request is identified by the +path of the request, the query string, and the "Host" header of the request. + +When creating a request, you can provide the entire resource URI as a URL. + +.. code-block:: php + + $response = $client->get('http://httbin.org/get?q=foo'); + +Using the above code, you will send a request that uses ``httpbin.org`` as +the Host header, sends the request over port 80, uses ``/get`` as the path, +and sends ``?q=foo`` as the query string. All of this is parsed automatically +from the provided URI. + +Sometimes you don't know what the entire request will be when it is created. +In these cases, you can modify the request as needed before sending it using +the ``createRequest()`` method of the client and methods on the request that +allow you to change it. + +.. code-block:: php + + $request = $client->createRequest('GET', 'http://httbin.org'); + +You can change the path of the request using ``setPath()``: + +.. code-block:: php + + $request->setPath('/get'); + echo $request->getPath(); + // /get + echo $request->getUrl(); + // http://httpbin.com/get + +Scheme +~~~~~~ + +The `scheme `_ of a request +specifies the protocol to use when sending the request. When using Guzzle, the +scheme can be set to "http" or "https". + +You can change the scheme of the request using the ``setScheme()`` method: + +.. code-block:: php + + $request = $client->createRequest('GET', 'http://httbin.org'); + $request->setScheme('https'); + echo $request->getScheme(); + // https + echo $request->getUrl(); + // https://httpbin.com/get + +Port +~~~~ + +No port is necessary when using the "http" or "https" schemes, but you can +override the port using ``setPort()``. If you need to modify the port used with +the specified scheme from the default setting, then you must use the +``setPort()`` method. + +.. code-block:: php + + $request = $client->createRequest('GET', 'http://httbin.org'); + $request->setPort(8080); + echo $request->getPort(); + // 8080 + echo $request->getUrl(); + // https://httpbin.com:8080/get + + // Set the port back to the default value for the scheme + $request->setPort(443); + echo $request->getUrl(); + // https://httpbin.com/get + +Query string +~~~~~~~~~~~~ + +You can get the query string of the request using the ``getQuery()`` method. +This method returns a ``GuzzleHttp\Query`` object. A Query object can be +accessed like a PHP array, iterated in a foreach statement like a PHP array, +and cast to a string. + +.. code-block:: php + + $request = $client->createRequest('GET', 'http://httbin.org'); + $query = $request->getQuery(); + $query['foo'] = 'bar'; + $query['baz'] = 'bam'; + $query['bam'] = ['test' => 'abc']; + + echo $request->getQuery(); + // foo=bar&baz=bam&bam%5Btest%5D=abc + + echo $request->getQuery()['foo']; + // bar + echo $request->getQuery()->get('foo'); + // bar + echo $request->getQuery()->get('foo'); + // bar + + var_export($request->getQuery()['bam']); + // array('test' => 'abc') + + foreach ($query as $key => $value) { + var_export($value); + } + + echo $request->getUrl(); + // https://httpbin.com/get?foo=bar&baz=bam&bam%5Btest%5D=abc + +Query Aggregators +^^^^^^^^^^^^^^^^^ + +Query objects can store scalar values or arrays of values. When an array of +values is added to a query object, the query object uses a query aggregator to +convert the complex structure into a string. Query objects will use +`PHP style query strings `_ when complex +query string parameters are converted to a string. You can customize how +complex query string parameters are aggregated using the ``setAggregator()`` +method of a query string object. + +.. code-block:: php + + $query->setAggregator($query::duplicateAggregator()); + +In the above example, we've changed the query object to use the +"duplicateAggregator". This aggregator will allow duplicate entries to appear +in a query string rather than appending "[n]" to each value. So if you had a +query string with ``['a' => ['b', 'c']]``, the duplicate aggregator would +convert this to "a=b&a=c" while the default aggregator would convert this to +"a[0]=b&a[1]=c" (with urlencoded brackets). + +The ``setAggregator()`` method accepts a ``callable`` which is used to convert +a deeply nested array of query string variables into a flattened array of key +value pairs. The callable accepts an array of query data and returns a +flattened array of key value pairs where each value is an array of strings. +You can use the ``GuzzleHttp\Query::walkQuery()`` static function to easily +create custom query aggregators. + +Host +~~~~ + +You can change the host header of the request in a predictable way using the +``setHost()`` method of a request: + +.. code-block:: php + + $request->setHost('www.google.com'); + echo $request->getHost(); + // www.google.com + echo $request->getUrl(); + // https://www.google.com/get?foo=bar&baz=bam + +.. note:: + + The Host header can also be changed by modifying the Host header of a + request directly, but modifying the Host header directly could result in + sending a request to a different Host than what is specified in the Host + header (sometimes this is actually the desired behavior). + +Resource +~~~~~~~~ + +You can use the ``getResource()`` method of a request to return the path and +query string of a request in a single string. + +.. code-block:: php + + $request = $client->createRequest('GET', 'http://httpbin.org/get?baz=bar'); + echo $request->getResource(); + // /get?baz=bar + +Request Config +-------------- + +Request messages contain a configuration collection that can be used by +event listeners and HTTP handlers to modify how a request behaves or is +transferred over the wire. For example, many of the request options that are +specified when creating a request are actually set as config options that are +only acted upon by handlers and listeners when the request is sent. + +You can get access to the request's config object using the ``getConfig()`` +method of a request. + +.. code-block:: php + + $request = $client->createRequest('GET', '/'); + $config = $request->getConfig(); + +The config object is a ``GuzzleHttp\Collection`` object that acts like +an associative array. You can grab values from the collection using array like +access. You can also modify and remove values using array like access. + +.. code-block:: php + + $config['foo'] = 'bar'; + echo $config['foo']; + // bar + + var_export(isset($config['foo'])); + // true + + unset($config['foo']); + var_export(isset($config['foo'])); + // false + + var_export($config['foo']); + // NULL + +HTTP handlers and event listeners can expose additional customization options +through request config settings. For example, in order to specify custom cURL +options to the cURL handler, you need to specify an associative array in the +``curl`` ``config`` request option. + +.. code-block:: php + + $client->get('/', [ + 'config' => [ + 'curl' => [ + CURLOPT_HTTPAUTH => CURLAUTH_NTLM, + CURLOPT_USERPWD => 'username:password' + ] + ] + ]); + +Consult the HTTP handlers and event listeners you are using to see if they +allow customization through request configuration options. + +Event Emitter +------------- + +Request objects implement ``GuzzleHttp\Event\HasEmitterInterface``, so they +have a method called ``getEmitter()`` that can be used to get an event emitter +used by the request. Any listener or subscriber attached to a request will only +be triggered for the lifecycle events of a specific request. Conversely, adding +an event listener or subscriber to a client will listen to all lifecycle events +of all requests created by the client. + +See :doc:`events` for more information. + +Responses +========= + +Responses are the HTTP messages a client receives from a server after sending +an HTTP request message. + +Start-Line +---------- + +The start-line of a response contains the protocol and protocol version, +status code, and reason phrase. + +.. code-block:: php + + $response = GuzzleHttp\get('http://httpbin.org/get'); + echo $response->getStatusCode(); + // 200 + echo $response->getReasonPhrase(); + // OK + echo $response->getProtocolVersion(); + // 1.1 + +Body +---- + +As described earlier, you can get the body of a response using the +``getBody()`` method. + +.. code-block:: php + + if ($body = $response->getBody()) { + echo $body; + // Cast to a string: { ... } + $body->seek(0); + // Rewind the body + $body->read(1024); + // Read bytes of the body + } + +When working with JSON responses, you can use the ``json()`` method of a +response: + +.. code-block:: php + + $json = $response->json(); + +.. note:: + + Guzzle uses the ``json_decode()`` method of PHP and uses arrays rather than + ``stdClass`` objects for objects. + +You can use the ``xml()`` method when working with XML data. + +.. code-block:: php + + $xml = $response->xml(); + +.. note:: + + Guzzle uses the ``SimpleXMLElement`` objects when converting response + bodies to XML. + +Effective URL +------------- + +The URL that was ultimately accessed that returned a response can be accessed +using the ``getEffectiveUrl()`` method of a response. This method will return +the URL of a request or the URL of the last redirected URL if any redirects +occurred while transferring a request. + +.. code-block:: php + + $response = GuzzleHttp\get('http://httpbin.org/get'); + echo $response->getEffectiveUrl(); + // http://httpbin.org/get + + $response = GuzzleHttp\get('http://httpbin.org/redirect-to?url=http://www.google.com'); + echo $response->getEffectiveUrl(); + // http://www.google.com diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/index.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/index.rst new file mode 100644 index 0000000..d456a5f --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/index.rst @@ -0,0 +1,98 @@ +.. title:: Guzzle | PHP HTTP client and framework for consuming RESTful web services + +====== +Guzzle +====== + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Manages things like persistent connections, represents query strings as + collections, simplifies sending streaming POST requests with fields and + files, and abstracts away the underlying HTTP transport layer. +- Can send both synchronous and asynchronous requests using the same interface + without requiring a dependency on a specific event loop. +- Pluggable HTTP handlers allows Guzzle to integrate with any method you choose + for sending HTTP requests over the wire (e.g., cURL, sockets, PHP's stream + wrapper, non-blocking event loops like `React `_, etc.). +- Guzzle makes it so that you no longer need to fool around with cURL options, + stream contexts, or sockets. + +.. code-block:: php + + $client = new GuzzleHttp\Client(); + $response = $client->get('http://guzzlephp.org'); + $res = $client->get('https://api.github.com/user', ['auth' => ['user', 'pass']]); + echo $res->getStatusCode(); + // "200" + echo $res->getHeader('content-type'); + // 'application/json; charset=utf8' + echo $res->getBody(); + // {"type":"User"...' + var_export($res->json()); + // Outputs the JSON decoded data + + // Send an asynchronous request. + $req = $client->createRequest('GET', 'http://httpbin.org', ['future' => true]); + $client->send($req)->then(function ($response) { + echo 'I completed! ' . $response; + }); + +User guide +---------- + +.. toctree:: + :maxdepth: 2 + + overview + quickstart + clients + http-messages + events + streams + handlers + testing + faq + +HTTP Components +--------------- + +There are a number of optional libraries you can use along with Guzzle's HTTP +layer to add capabilities to the client. + +`Log Subscriber `_ + Logs HTTP requests and responses sent over the wire using customizable + log message templates. + +`OAuth Subscriber `_ + Signs requests using OAuth 1.0. + +`Cache Subscriber `_ + Implements a private transparent proxy cache that caches HTTP responses. + +`Retry Subscriber `_ + Retries failed requests using customizable retry strategies (e.g., retry + based on response status code, cURL error codes, etc.) + +`Message Integrity Subscriber `_ + Verifies the message integrity of HTTP responses using customizable + validators. This plugin can be used, for example, to verify the Content-MD5 + headers of responses. + +Service Description Commands +---------------------------- + +You can use the **Guzzle Command** library to encapsulate interaction with a +web service using command objects. Building on top of Guzzle's command +abstraction allows you to easily implement things like service description that +can be used to serialize requests and parse responses using a meta-description +of a web service. + +`Guzzle Command `_ + Provides the foundational elements used to build high-level, command based, + web service clients with Guzzle. + +`Guzzle Services `_ + Provides an implementation of the *Guzzle Command* library that uses + Guzzle service descriptions to describe web services, serialize requests, + and parse responses into easy to use model structures. diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/overview.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/overview.rst new file mode 100644 index 0000000..1355afa --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/overview.rst @@ -0,0 +1,150 @@ +======== +Overview +======== + +Requirements +============ + +#. PHP 5.4.0 +#. To use the PHP stream handler, ``allow_url_fopen`` must be enabled in your + system's php.ini. +#. To use the cURL handler, you must have a recent version of cURL >= 7.16.2 + compiled with OpenSSL and zlib. + +.. note:: + + Guzzle no longer requires cURL in order to send HTTP requests. Guzzle will + use the PHP stream wrapper to send HTTP requests if cURL is not installed. + Alternatively, you can provide your own HTTP handler used to send requests. + +.. _installation: + +Installation +============ + +The recommended way to install Guzzle is with `Composer `_. Composer is a dependency +management tool for PHP that allows you to declare the dependencies your project needs and installs them into your +project. + +.. code-block:: bash + + # Install Composer + curl -sS https://getcomposer.org/installer | php + +You can add Guzzle as a dependency using the composer.phar CLI: + +.. code-block:: bash + + php composer.phar require guzzlehttp/guzzle:~5.0 + +Alternatively, you can specify Guzzle as a dependency in your project's +existing composer.json file: + +.. code-block:: js + + { + "require": { + "guzzlehttp/guzzle": "~5.0" + } + } + +After installing, you need to require Composer's autoloader: + +.. code-block:: php + + require 'vendor/autoload.php'; + +You can find out more on how to install Composer, configure autoloading, and +other best-practices for defining dependencies at `getcomposer.org `_. + +Bleeding edge +------------- + +During your development, you can keep up with the latest changes on the master +branch by setting the version requirement for Guzzle to ``~5.0@dev``. + +.. code-block:: js + + { + "require": { + "guzzlehttp/guzzle": "~5.0@dev" + } + } + +License +======= + +Licensed using the `MIT license `_. + + Copyright (c) 2014 Michael Dowling + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +Contributing +============ + +Guidelines +---------- + +1. Guzzle follows PSR-0, PSR-1, and PSR-2. +2. Guzzle is meant to be lean and fast with very few dependencies. +3. Guzzle has a minimum PHP version requirement of PHP 5.4. Pull requests must + not require a PHP version greater than PHP 5.4. +4. All pull requests must include unit tests to ensure the change works as + expected and to prevent regressions. + +Running the tests +----------------- + +In order to contribute, you'll need to checkout the source from GitHub and +install Guzzle's dependencies using Composer: + +.. code-block:: bash + + git clone https://github.com/guzzle/guzzle.git + cd guzzle && curl -s http://getcomposer.org/installer | php && ./composer.phar install --dev + +Guzzle is unit tested with PHPUnit. Run the tests using the vendored PHPUnit +binary: + +.. code-block:: bash + + vendor/bin/phpunit + +.. note:: + + You'll need to install node.js v0.5.0 or newer in order to perform + integration tests on Guzzle's HTTP handlers. + +Reporting a security vulnerability +================================== + +We want to ensure that Guzzle is a secure HTTP client library for everyone. If +you've discovered a security vulnerability in Guzzle, we appreciate your help +in disclosing it to us in a `responsible manner `_. + +Publicly disclosing a vulnerability can put the entire community at risk. If +you've discovered a security concern, please email us at +security@guzzlephp.org. We'll work with you to make sure that we understand the +scope of the issue, and that we fully address your concern. We consider +correspondence sent to security@guzzlephp.org our highest priority, and work to +address any issues that arise as quickly as possible. + +After a security vulnerability has been corrected, a security hotfix release will +be deployed as soon as possible. diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/quickstart.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/quickstart.rst new file mode 100644 index 0000000..65a70ed --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/quickstart.rst @@ -0,0 +1,448 @@ +========== +Quickstart +========== + +This page provides a quick introduction to Guzzle and introductory examples. +If you have not already installed, Guzzle, head over to the :ref:`installation` +page. + +Make a Request +============== + +You can send requests with Guzzle using a ``GuzzleHttp\ClientInterface`` +object. + +Creating a Client +----------------- + +The procedural API is simple but not very testable; it's best left for quick +prototyping. If you want to use Guzzle in a more flexible and testable way, +then you'll need to use a ``GuzzleHttp\ClientInterface`` object. + +.. code-block:: php + + use GuzzleHttp\Client; + + $client = new Client(); + $response = $client->get('http://httpbin.org/get'); + + // You can use the same methods you saw in the procedural API + $response = $client->delete('http://httpbin.org/delete'); + $response = $client->head('http://httpbin.org/get'); + $response = $client->options('http://httpbin.org/get'); + $response = $client->patch('http://httpbin.org/patch'); + $response = $client->post('http://httpbin.org/post'); + $response = $client->put('http://httpbin.org/put'); + +You can create a request with a client and then send the request with the +client when you're ready. + +.. code-block:: php + + $request = $client->createRequest('GET', 'http://www.foo.com'); + $response = $client->send($request); + +Client objects provide a great deal of flexibility in how request are +transferred including default request options, subscribers that are attached +to each request, and a base URL that allows you to send requests with relative +URLs. You can find out all about clients in the :doc:`clients` page of the +documentation. + +Using Responses +=============== + +In the previous examples, we retrieved a ``$response`` variable. This value is +actually a ``GuzzleHttp\Message\ResponseInterface`` object and contains lots +of helpful information. + +You can get the status code and reason phrase of the response. + +.. code-block:: php + + $code = $response->getStatusCode(); + // 200 + + $reason = $response->getReasonPhrase(); + // OK + +By providing the ``future`` request option to a request, you can send requests +asynchronously using the promise interface of a future response. + +.. code-block:: php + + $client->get('http://httpbin.org', ['future' => true]) + ->then(function ($response) { + echo $response->getStatusCode(); + }); + +Response Body +------------- + +The body of a response can be retrieved and cast to a string. + +.. code-block:: php + + $body = $response->getBody(); + echo $body; + // { "some_json_data" ...} + +You can also read read bytes from body of a response like a stream. + +.. code-block:: php + + $body = $response->getBody(); + + while (!$body->eof()) { + echo $body->read(1024); + } + +JSON Responses +~~~~~~~~~~~~~~ + +You can more easily work with JSON responses using the ``json()`` method of a +response. + +.. code-block:: php + + $response = $client->get('http://httpbin.org/get'); + $json = $response->json(); + var_dump($json[0]['origin']); + +Guzzle internally uses PHP's ``json_decode()`` function to parse responses. If +Guzzle is unable to parse the JSON response body, then a +``GuzzleHttp\Exception\ParseException`` is thrown. + +XML Responses +~~~~~~~~~~~~~ + +You can use a response's ``xml()`` method to more easily work with responses +that contain XML data. + +.. code-block:: php + + $response = $client->get('https://github.com/mtdowling.atom'); + $xml = $response->xml(); + echo $xml->id; + // tag:github.com,2008:/mtdowling + +Guzzle internally uses a ``SimpleXMLElement`` object to parse responses. If +Guzzle is unable to parse the XML response body, then a +``GuzzleHttp\Exception\ParseException`` is thrown. + +Query String Parameters +======================= + +Sending query string parameters with a request is easy. You can set query +string parameters in the request's URL. + +.. code-block:: php + + $response = $client->get('http://httpbin.org?foo=bar'); + +You can also specify the query string parameters using the ``query`` request +option. + +.. code-block:: php + + $client->get('http://httpbin.org', [ + 'query' => ['foo' => 'bar'] + ]); + +And finally, you can build up the query string of a request as needed by +calling the ``getQuery()`` method of a request and modifying the request's +``GuzzleHttp\Query`` object as needed. + +.. code-block:: php + + $request = $client->createRequest('GET', 'http://httpbin.org'); + $query = $request->getQuery(); + $query->set('foo', 'bar'); + + // You can use the query string object like an array + $query['baz'] = 'bam'; + + // The query object can be cast to a string + echo $query; + // foo=bar&baz=bam + + // Setting a value to false or null will cause the "=" sign to be omitted + $query['empty'] = null; + echo $query; + // foo=bar&baz=bam&empty + + // Use an empty string to include the "=" sign with an empty value + $query['empty'] = ''; + echo $query; + // foo=bar&baz=bam&empty= + +.. _headers: + +Request and Response Headers +---------------------------- + +You can specify request headers when sending or creating requests with a +client. In the following example, we send the ``X-Foo-Header`` with a value of +``value`` by setting the ``headers`` request option. + +.. code-block:: php + + $response = $client->get('http://httpbin.org/get', [ + 'headers' => ['X-Foo-Header' => 'value'] + ]); + +You can view the headers of a response using header specific methods of a +response class. Headers work exactly the same way for request and response +object. + +You can retrieve a header from a request or response using the ``getHeader()`` +method of the object. This method is case-insensitive and by default will +return a string containing the header field value. + +.. code-block:: php + + $response = $client->get('http://www.yahoo.com'); + $length = $response->getHeader('Content-Length'); + +Header fields that contain multiple values can be retrieved as a string or as +an array. Retrieving the field values as a string will naively concatenate all +of the header values together with a comma. Because not all header fields +should be represented this way (e.g., ``Set-Cookie``), you can pass an optional +flag to the ``getHeader()`` method to retrieve the header values as an array. + +.. code-block:: php + + $values = $response->getHeader('Set-Cookie', true); + foreach ($values as $value) { + echo $value; + } + +You can test if a request or response has a specific header using the +``hasHeader()`` method. This method accepts a case-insensitive string and +returns true if the header is present or false if it is not. + +You can retrieve all of the headers of a message using the ``getHeaders()`` +method of a request or response. The return value is an associative array where +the keys represent the header name as it will be sent over the wire, and each +value is an array of strings associated with the header. + +.. code-block:: php + + $headers = $response->getHeaders(); + foreach ($message->getHeaders() as $name => $values) { + echo $name . ": " . implode(", ", $values); + } + +Modifying headers +----------------- + +The headers of a message can be modified using the ``setHeader()``, +``addHeader()``, ``setHeaders()``, and ``removeHeader()`` methods of a request +or response object. + +.. code-block:: php + + $request = $client->createRequest('GET', 'http://httpbin.org/get'); + + // Set a single value for a header + $request->setHeader('User-Agent', 'Testing!'); + + // Set multiple values for a header in one call + $request->setHeader('X-Foo', ['Baz', 'Bar']); + + // Add a header to the message + $request->addHeader('X-Foo', 'Bam'); + + echo $request->getHeader('X-Foo'); + // Baz, Bar, Bam + + // Remove a specific header using a case-insensitive name + $request->removeHeader('x-foo'); + echo $request->getHeader('X-Foo'); + // Echoes an empty string: '' + +Uploading Data +============== + +Guzzle provides several methods of uploading data. + +You can send requests that contain a stream of data by passing a string, +resource returned from ``fopen``, or a ``GuzzleHttp\Stream\StreamInterface`` +object to the ``body`` request option. + +.. code-block:: php + + $r = $client->post('http://httpbin.org/post', ['body' => 'raw data']); + +You can easily upload JSON data using the ``json`` request option. + +.. code-block:: php + + $r = $client->put('http://httpbin.org/put', ['json' => ['foo' => 'bar']]); + +POST Requests +------------- + +In addition to specifying the raw data of a request using the ``body`` request +option, Guzzle provides helpful abstractions over sending POST data. + +Sending POST Fields +~~~~~~~~~~~~~~~~~~~ + +Sending ``application/x-www-form-urlencoded`` POST requests requires that you +specify the body of a POST request as an array. + +.. code-block:: php + + $response = $client->post('http://httpbin.org/post', [ + 'body' => [ + 'field_name' => 'abc', + 'other_field' => '123' + ] + ]); + +You can also build up POST requests before sending them. + +.. code-block:: php + + $request = $client->createRequest('POST', 'http://httpbin.org/post'); + $postBody = $request->getBody(); + + // $postBody is an instance of GuzzleHttp\Post\PostBodyInterface + $postBody->setField('foo', 'bar'); + echo $postBody->getField('foo'); + // 'bar' + + echo json_encode($postBody->getFields()); + // {"foo": "bar"} + + // Send the POST request + $response = $client->send($request); + +Sending POST Files +~~~~~~~~~~~~~~~~~~ + +Sending ``multipart/form-data`` POST requests (POST requests that contain +files) is the same as sending ``application/x-www-form-urlencoded``, except +some of the array values of the POST fields map to PHP ``fopen`` resources, or +``GuzzleHttp\Stream\StreamInterface``, or +``GuzzleHttp\Post\PostFileInterface`` objects. + +.. code-block:: php + + use GuzzleHttp\Post\PostFile; + + $response = $client->post('http://httpbin.org/post', [ + 'body' => [ + 'field_name' => 'abc', + 'file_filed' => fopen('/path/to/file', 'r'), + 'other_file' => new PostFile('other_file', 'this is the content') + ] + ]); + +Just like when sending POST fields, you can also build up POST requests with +files before sending them. + +.. code-block:: php + + use GuzzleHttp\Post\PostFile; + + $request = $client->createRequest('POST', 'http://httpbin.org/post'); + $postBody = $request->getBody(); + $postBody->setField('foo', 'bar'); + $postBody->addFile(new PostFile('test', fopen('/path/to/file', 'r'))); + $response = $client->send($request); + +Cookies +======= + +Guzzle can maintain a cookie session for you if instructed using the +``cookies`` request option. + +- Set to ``true`` to use a shared cookie session associated with the client. +- Pass an associative array containing cookies to send in the request and start + a new cookie session. +- Set to a ``GuzzleHttp\Subscriber\CookieJar\CookieJarInterface`` object to use + an existing cookie jar. + +Redirects +========= + +Guzzle will automatically follow redirects unless you tell it not to. You can +customize the redirect behavior using the ``allow_redirects`` request option. + +- Set to true to enable normal redirects with a maximum number of 5 redirects. + This is the default setting. +- Set to false to disable redirects. +- Pass an associative array containing the 'max' key to specify the maximum + number of redirects and optionally provide a 'strict' key value to specify + whether or not to use strict RFC compliant redirects (meaning redirect POST + requests with POST requests vs. doing what most browsers do which is + redirect POST requests with GET requests). + +.. code-block:: php + + $response = $client->get('http://github.com'); + echo $response->getStatusCode(); + // 200 + echo $response->getEffectiveUrl(); + // 'https://github.com/' + +The following example shows that redirects can be disabled. + +.. code-block:: php + + $response = $client->get('http://github.com', ['allow_redirects' => false]); + echo $response->getStatusCode(); + // 301 + echo $response->getEffectiveUrl(); + // 'http://github.com/' + +Exceptions +========== + +Guzzle throws exceptions for errors that occur during a transfer. + +- In the event of a networking error (connection timeout, DNS errors, etc.), + a ``GuzzleHttp\Exception\RequestException`` is thrown. This exception + extends from ``GuzzleHttp\Exception\TransferException``. Catching this + exception will catch any exception that can be thrown while transferring + (non-parallel) requests. + + .. code-block:: php + + use GuzzleHttp\Exception\RequestException; + + try { + $client->get('https://github.com/_abc_123_404'); + } catch (RequestException $e) { + echo $e->getRequest(); + if ($e->hasResponse()) { + echo $e->getResponse(); + } + } + +- A ``GuzzleHttp\Exception\ClientException`` is thrown for 400 + level errors if the ``exceptions`` request option is set to true. This + exception extends from ``GuzzleHttp\Exception\BadResponseException`` and + ``GuzzleHttp\Exception\BadResponseException`` extends from + ``GuzzleHttp\Exception\RequestException``. + + .. code-block:: php + + use GuzzleHttp\Exception\ClientException; + + try { + $client->get('https://github.com/_abc_123_404'); + } catch (ClientException $e) { + echo $e->getRequest(); + echo $e->getResponse(); + } + +- A ``GuzzleHttp\Exception\ServerException`` is thrown for 500 level + errors if the ``exceptions`` request option is set to true. This + exception extends from ``GuzzleHttp\Exception\BadResponseException``. +- A ``GuzzleHttp\Exception\TooManyRedirectsException`` is thrown when too + many redirects are followed. This exception extends from ``GuzzleHttp\Exception\RequestException``. + +All of the above exceptions extend from +``GuzzleHttp\Exception\TransferException``. diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/requirements.txt b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/requirements.txt new file mode 100644 index 0000000..fe7a4ea --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/requirements.txt @@ -0,0 +1,2 @@ +Sphinx>=1.2b1 +guzzle_sphinx_theme>=0.6.0 diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/streams.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/streams.rst new file mode 100644 index 0000000..8fe9a69 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/streams.rst @@ -0,0 +1,213 @@ +======= +Streams +======= + +Guzzle uses stream objects to represent request and response message bodies. +These stream objects allow you to work with various types of data all using a +common interface. + +HTTP messages consist of a start-line, headers, and a body. The body of an HTTP +message can be very small or extremely large. Attempting to represent the body +of a message as a string can easily consume more memory than intended because +the body must be stored completely in memory. Attempting to store the body of a +request or response in memory would preclude the use of that implementation from +being able to work with large message bodies. The StreamInterface is used in +order to hide the implementation details of where a stream of data is read from +or written to. + +Guzzle's StreamInterface exposes several methods that enable streams to be read +from, written to, and traversed effectively. + +Streams expose their capabilities using three methods: ``isReadable()``, +``isWritable()``, and ``isSeekable()``. These methods can be used by stream +collaborators to determine if a stream is capable of their requirements. + +Each stream instance has various capabilities: they can be read-only, +write-only, read-write, allow arbitrary random access (seeking forwards or +backwards to any location), or only allow sequential access (for example in the +case of a socket or pipe). + +Creating Streams +================ + +The best way to create a stream is using the static factory method, +``GuzzleHttp\Stream\Stream::factory()``. This factory accepts strings, +resources returned from ``fopen()``, an object that implements +``__toString()``, and an object that implements +``GuzzleHttp\Stream\StreamInterface``. + +.. code-block:: php + + use GuzzleHttp\Stream\Stream; + + $stream = Stream::factory('string data'); + echo $stream; + // string data + echo $stream->read(3); + // str + echo $stream->getContents(); + // ing data + var_export($stream->eof()); + // true + var_export($stream->tell()); + // 11 + +Metadata +======== + +Guzzle streams expose stream metadata through the ``getMetadata()`` method. +This method provides the data you would retrieve when calling PHP's +`stream_get_meta_data() function `_, +and can optionally expose other custom data. + +.. code-block:: php + + use GuzzleHttp\Stream\Stream; + + $resource = fopen('/path/to/file', 'r'); + $stream = Stream::factory($resource); + echo $stream->getMetadata('uri'); + // /path/to/file + var_export($stream->isReadable()); + // true + var_export($stream->isWritable()); + // false + var_export($stream->isSeekable()); + // true + +Stream Decorators +================= + +With the small and focused interface, add custom functionality to streams is +very simple with stream decorators. Guzzle provides several built-in decorators +that provide additional stream functionality. + +CachingStream +------------- + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +.. code-block:: php + + use GuzzleHttp\Stream\Stream; + use GuzzleHttp\Stream\CachingStream; + + $original = Stream::factory(fopen('http://www.google.com', 'r')); + $stream = new CachingStream($original); + + $stream->read(1024); + echo $stream->tell(); + // 1024 + + $stream->seek(0); + echo $stream->tell(); + // 0 + +LimitStream +----------- + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +.. code-block:: php + + use GuzzleHttp\Stream\Stream; + use GuzzleHttp\Stream\LimitStream; + + $original = Stream::factory(fopen('/tmp/test.txt', 'r+')); + echo $original->getSize(); + // >>> 1048576 + + // Limit the size of the body to 1024 bytes and start reading from byte 2048 + $stream = new LimitStream($original, 1024, 2048); + echo $stream->getSize(); + // >>> 1024 + echo $stream->tell(); + // >>> 0 + +NoSeekStream +------------ + +NoSeekStream wraps a stream and does not allow seeking. + +.. code-block:: php + + use GuzzleHttp\Stream\Stream; + use GuzzleHttp\Stream\LimitStream; + + $original = Stream::factory('foo'); + $noSeek = new NoSeekStream($original); + + echo $noSeek->read(3); + // foo + var_export($noSeek->isSeekable()); + // false + $noSeek->seek(0); + var_export($noSeek->read(3)); + // NULL + +Creating Custom Decorators +-------------------------- + +Creating a stream decorator is very easy thanks to the +``GuzzleHttp\Stream\StreamDecoratorTrait``. This trait provides methods that +implement ``GuzzleHttp\Stream\StreamInterface`` by proxying to an underlying +stream. Just ``use`` the ``StreamDecoratorTrait`` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +``read()`` method. + +.. code-block:: php + + use GuzzleHttp\Stream\StreamDecoratorTrait; + + class EofCallbackStream implements StreamInterface + { + use StreamDecoratorTrait; + + private $callback; + + public function __construct(StreamInterface $stream, callable $callback) + { + $this->stream = $stream; + $this->callback = $callback; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + call_user_func($this->callback); + } + + return $result; + } + } + +This decorator could be added to any existing stream and used like so: + +.. code-block:: php + + use GuzzleHttp\Stream\Stream; + + $original = Stream::factory('foo'); + $eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; + }); + + $eofStream->read(2); + $eofStream->read(1); + // echoes "EOF!" + $eofStream->seek(0); + $eofStream->read(3); + // echoes "EOF!" diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/testing.rst b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/testing.rst new file mode 100644 index 0000000..03bcc2e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/docs/testing.rst @@ -0,0 +1,232 @@ +====================== +Testing Guzzle Clients +====================== + +Guzzle provides several tools that will enable you to easily mock the HTTP +layer without needing to send requests over the internet. + +* Mock subscriber +* Mock handler +* Node.js web server for integration testing + +Mock Subscriber +=============== + +When testing HTTP clients, you often need to simulate specific scenarios like +returning a successful response, returning an error, or returning specific +responses in a certain order. Because unit tests need to be predictable, easy +to bootstrap, and fast, hitting an actual remote API is a test smell. + +Guzzle provides a mock subscriber that can be attached to clients or requests +that allows you to queue up a list of responses to use rather than hitting a +remote API. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Subscriber\Mock; + use GuzzleHttp\Message\Response; + + $client = new Client(); + + // Create a mock subscriber and queue two responses. + $mock = new Mock([ + new Response(200, ['X-Foo' => 'Bar']), // Use response object + "HTTP/1.1 202 OK\r\nContent-Length: 0\r\n\r\n" // Use a response string + ]); + + // Add the mock subscriber to the client. + $client->getEmitter()->attach($mock); + // The first request is intercepted with the first response. + echo $client->get('/')->getStatusCode(); + //> 200 + // The second request is intercepted with the second response. + echo $client->get('/')->getStatusCode(); + //> 202 + +When no more responses are in the queue and a request is sent, an +``OutOfBoundsException`` is thrown. + +History Subscriber +================== + +When using things like the ``Mock`` subscriber, you often need to know if the +requests you expected to send were sent exactly as you intended. While the mock +subscriber responds with mocked responses, the ``GuzzleHttp\Subscriber\History`` +subscriber maintains a history of the requests that were sent by a client. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Subscriber\History; + + $client = new Client(); + $history = new History(); + + // Add the history subscriber to the client. + $client->getEmitter()->attach($history); + + $client->get('http://httpbin.org/get'); + $client->head('http://httpbin.org/get'); + + // Count the number of transactions + echo count($history); + //> 2 + // Get the last request + $lastRequest = $history->getLastRequest(); + // Get the last response + $lastResponse = $history->getLastResponse(); + + // Iterate over the transactions that were sent + foreach ($history as $transaction) { + echo $transaction['request']->getMethod(); + //> GET, HEAD + echo $transaction['response']->getStatusCode(); + //> 200, 200 + } + +The history subscriber can also be printed, revealing the requests and +responses that were sent as a string, in order. + +.. code-block:: php + + echo $history; + +:: + + > GET /get HTTP/1.1 + Host: httpbin.org + User-Agent: Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8 + + < HTTP/1.1 200 OK + Access-Control-Allow-Origin: * + Content-Type: application/json + Date: Tue, 25 Mar 2014 03:53:27 GMT + Server: gunicorn/0.17.4 + Content-Length: 270 + Connection: keep-alive + + { + "headers": { + "Connection": "close", + "X-Request-Id": "3d0f7d5c-c937-4394-8248-2b8e03fcccdb", + "User-Agent": "Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8", + "Host": "httpbin.org" + }, + "origin": "76.104.247.1", + "args": {}, + "url": "http://httpbin.org/get" + } + + > HEAD /get HTTP/1.1 + Host: httpbin.org + User-Agent: Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8 + + < HTTP/1.1 200 OK + Access-Control-Allow-Origin: * + Content-length: 270 + Content-Type: application/json + Date: Tue, 25 Mar 2014 03:53:27 GMT + Server: gunicorn/0.17.4 + Connection: keep-alive + +Mock Adapter +============ + +In addition to using the Mock subscriber, you can use the +``GuzzleHttp\Ring\Client\MockHandler`` as the handler of a client to return the +same response over and over or return the result of a callable function. + +Test Web Server +=============== + +Using mock responses is almost always enough when testing a web service client. +When implementing custom :doc:`HTTP handlers `, you'll need to send +actual HTTP requests in order to sufficiently test the handler. However, a +best practice is to contact a local web server rather than a server over the +internet. + +- Tests are more reliable +- Tests do not require a network connection +- Tests have no external dependencies + +Using the test server +--------------------- + +.. warning:: + + The following functionality is provided to help developers of Guzzle + develop HTTP handlers. There is no promise of backwards compatibility + when it comes to the node.js test server or the ``GuzzleHttp\Tests\Server`` + class. If you are using the test server or ``Server`` class outside of + guzzlehttp/guzzle, then you will need to configure autoloading and + ensure the web server is started manually. + +.. hint:: + + You almost never need to use this test web server. You should only ever + consider using it when developing HTTP handlers. The test web server + is not necessary for mocking requests. For that, please use the + Mock subcribers and History subscriber. + +Guzzle ships with a node.js test server that receives requests and returns +responses from a queue. The test server exposes a simple API that is used to +enqueue responses and inspect the requests that it has received. + +Any operation on the ``Server`` object will ensure that +the server is running and wait until it is able to receive requests before +returning. + +.. code-block:: php + + use GuzzleHttp\Client; + use GuzzleHttp\Tests\Server; + + // Start the server and queue a response + Server::enqueue("HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n"); + + $client = new Client(['base_url' => Server::$url]); + echo $client->get('/foo')->getStatusCode(); + // 200 + +``GuzzleHttp\Tests\Server`` provides a static interface to the test server. You +can queue an HTTP response or an array of responses by calling +``Server::enqueue()``. This method accepts a string representing an HTTP +response message, a ``GuzzleHttp\Message\ResponseInterface``, or an array of +HTTP message strings / ``GuzzleHttp\Message\ResponseInterface`` objects. + +.. code-block:: php + + // Queue single response + Server::enqueue("HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n"); + + // Clear the queue and queue an array of responses + Server::enqueue([ + "HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n", + "HTTP/1.1 404 Not Found\r\n\Content-Length: 0r\n\r\n" + ]); + +When a response is queued on the test server, the test server will remove any +previously queued responses. As the server receives requests, queued responses +are dequeued and returned to the request. When the queue is empty, the server +will return a 500 response. + +You can inspect the requests that the server has retrieved by calling +``Server::received()``. This method accepts an optional ``$hydrate`` parameter +that specifies if you are retrieving an array of HTTP requests as strings or an +array of ``GuzzleHttp\Message\RequestInterface`` objects. + +.. code-block:: php + + foreach (Server::received() as $response) { + echo $response; + } + +You can clear the list of received requests from the web server using the +``Server::flush()`` method. + +.. code-block:: php + + Server::flush(); + echo count(Server::received()); + // 0 diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/phpunit.xml.dist b/admin/classes/domain/vendor/guzzlehttp/guzzle/phpunit.xml.dist new file mode 100644 index 0000000..500cd53 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + + tests + + + + + src + + src/ + + + + diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/BatchResults.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/BatchResults.php new file mode 100644 index 0000000..e5af433 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/BatchResults.php @@ -0,0 +1,148 @@ +hash = $hash; + } + + /** + * Get the keys that are available on the batch result. + * + * @return array + */ + public function getKeys() + { + return iterator_to_array($this->hash); + } + + /** + * Gets a result from the container for the given object. When getting + * results for a batch of requests, provide the request object. + * + * @param object $forObject Object to retrieve the result for. + * + * @return mixed|null + */ + public function getResult($forObject) + { + return isset($this->hash[$forObject]) ? $this->hash[$forObject] : null; + } + + /** + * Get an array of successful results. + * + * @return array + */ + public function getSuccessful() + { + $results = []; + foreach ($this->hash as $key) { + if (!($this->hash[$key] instanceof \Exception)) { + $results[] = $this->hash[$key]; + } + } + + return $results; + } + + /** + * Get an array of failed results. + * + * @return array + */ + public function getFailures() + { + $results = []; + foreach ($this->hash as $key) { + if ($this->hash[$key] instanceof \Exception) { + $results[] = $this->hash[$key]; + } + } + + return $results; + } + + /** + * Allows iteration over all batch result values. + * + * @return \ArrayIterator + */ + public function getIterator() + { + $results = []; + foreach ($this->hash as $key) { + $results[] = $this->hash[$key]; + } + + return new \ArrayIterator($results); + } + + /** + * Counts the number of elements in the batch result. + * + * @return int + */ + public function count() + { + return count($this->hash); + } + + /** + * Checks if the batch contains a specific numerical array index. + * + * @param int $key Index to access + * + * @return bool + */ + public function offsetExists($key) + { + return $key < count($this->hash); + } + + /** + * Allows access of the batch using a numerical array index. + * + * @param int $key Index to access. + * + * @return mixed|null + */ + public function offsetGet($key) + { + $i = -1; + foreach ($this->hash as $obj) { + if ($key === ++$i) { + return $this->hash[$obj]; + } + } + + return null; + } + + public function offsetUnset($key) + { + throw new \RuntimeException('Not implemented'); + } + + public function offsetSet($key, $value) + { + throw new \RuntimeException('Not implemented'); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Client.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Client.php new file mode 100644 index 0000000..b5ed11f --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Client.php @@ -0,0 +1,352 @@ + [ + * 'http://www.foo.com/{version}/', + * ['version' => '123'] + * ], + * 'defaults' => [ + * 'timeout' => 10, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ] + * ]); + * + * @param array $config Client configuration settings + * - base_url: Base URL of the client that is merged into relative URLs. + * Can be a string or an array that contains a URI template followed + * by an associative array of expansion variables to inject into the + * URI template. + * - handler: callable RingPHP handler used to transfer requests + * - message_factory: Factory used to create request and response object + * - defaults: Default request options to apply to each request + * - emitter: Event emitter used for request events + * - fsm: (internal use only) The request finite state machine. A + * function that accepts a transaction and optional final state. The + * function is responsible for transitioning a request through its + * lifecycle events. + */ + public function __construct(array $config = []) + { + $this->configureBaseUrl($config); + $this->configureDefaults($config); + + if (isset($config['emitter'])) { + $this->emitter = $config['emitter']; + } + + $this->messageFactory = isset($config['message_factory']) + ? $config['message_factory'] + : new MessageFactory(); + + if (isset($config['fsm'])) { + $this->fsm = $config['fsm']; + } else { + if (isset($config['handler'])) { + $handler = $config['handler']; + } elseif (isset($config['adapter'])) { + $handler = $config['adapter']; + } else { + $handler = Utils::getDefaultHandler(); + } + $this->fsm = new RequestFsm($handler, $this->messageFactory); + } + } + + public function getDefaultOption($keyOrPath = null) + { + return $keyOrPath === null + ? $this->defaults + : Utils::getPath($this->defaults, $keyOrPath); + } + + public function setDefaultOption($keyOrPath, $value) + { + Utils::setPath($this->defaults, $keyOrPath, $value); + } + + public function getBaseUrl() + { + return (string) $this->baseUrl; + } + + public function createRequest($method, $url = null, array $options = []) + { + $options = $this->mergeDefaults($options); + // Use a clone of the client's emitter + $options['config']['emitter'] = clone $this->getEmitter(); + $url = $url || (is_string($url) && strlen($url)) + ? $this->buildUrl($url) + : (string) $this->baseUrl; + + return $this->messageFactory->createRequest($method, $url, $options); + } + + public function get($url = null, $options = []) + { + return $this->send($this->createRequest('GET', $url, $options)); + } + + public function head($url = null, array $options = []) + { + return $this->send($this->createRequest('HEAD', $url, $options)); + } + + public function delete($url = null, array $options = []) + { + return $this->send($this->createRequest('DELETE', $url, $options)); + } + + public function put($url = null, array $options = []) + { + return $this->send($this->createRequest('PUT', $url, $options)); + } + + public function patch($url = null, array $options = []) + { + return $this->send($this->createRequest('PATCH', $url, $options)); + } + + public function post($url = null, array $options = []) + { + return $this->send($this->createRequest('POST', $url, $options)); + } + + public function options($url = null, array $options = []) + { + return $this->send($this->createRequest('OPTIONS', $url, $options)); + } + + public function send(RequestInterface $request) + { + $isFuture = $request->getConfig()->get('future'); + $trans = new Transaction($this, $request, $isFuture); + $fn = $this->fsm; + + try { + $fn($trans); + if ($isFuture) { + // Turn the normal response into a future if needed. + return $trans->response instanceof FutureInterface + ? $trans->response + : new FutureResponse(new FulfilledPromise($trans->response)); + } + // Resolve deep futures if this is not a future + // transaction. This accounts for things like retries + // that do not have an immediate side-effect. + while ($trans->response instanceof FutureInterface) { + $trans->response = $trans->response->wait(); + } + return $trans->response; + } catch (\Exception $e) { + if ($isFuture) { + // Wrap the exception in a promise + return new FutureResponse(new RejectedPromise($e)); + } + throw RequestException::wrapException($trans->request, $e); + } + } + + /** + * Get an array of default options to apply to the client + * + * @return array + */ + protected function getDefaultOptions() + { + $settings = [ + 'allow_redirects' => true, + 'exceptions' => true, + 'decode_content' => true, + 'verify' => true + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set + if ($proxy = getenv('HTTP_PROXY')) { + $settings['proxy']['http'] = $proxy; + } + + if ($proxy = getenv('HTTPS_PROXY')) { + $settings['proxy']['https'] = $proxy; + } + + return $settings; + } + + /** + * Expand a URI template and inherit from the base URL if it's relative + * + * @param string|array $url URL or an array of the URI template to expand + * followed by a hash of template varnames. + * @return string + * @throws \InvalidArgumentException + */ + private function buildUrl($url) + { + // URI template (absolute or relative) + if (!is_array($url)) { + return strpos($url, '://') + ? (string) $url + : (string) $this->baseUrl->combine($url); + } + + if (!isset($url[1])) { + throw new \InvalidArgumentException('You must provide a hash of ' + . 'varname options in the second element of a URL array.'); + } + + // Absolute URL + if (strpos($url[0], '://')) { + return Utils::uriTemplate($url[0], $url[1]); + } + + // Combine the relative URL with the base URL + return (string) $this->baseUrl->combine( + Utils::uriTemplate($url[0], $url[1]) + ); + } + + private function configureBaseUrl(&$config) + { + if (!isset($config['base_url'])) { + $this->baseUrl = new Url('', ''); + } elseif (!is_array($config['base_url'])) { + $this->baseUrl = Url::fromString($config['base_url']); + } elseif (count($config['base_url']) < 2) { + throw new \InvalidArgumentException('You must provide a hash of ' + . 'varname options in the second element of a base_url array.'); + } else { + $this->baseUrl = Url::fromString( + Utils::uriTemplate( + $config['base_url'][0], + $config['base_url'][1] + ) + ); + $config['base_url'] = (string) $this->baseUrl; + } + } + + private function configureDefaults($config) + { + if (!isset($config['defaults'])) { + $this->defaults = $this->getDefaultOptions(); + } else { + $this->defaults = array_replace( + $this->getDefaultOptions(), + $config['defaults'] + ); + } + + // Add the default user-agent header + if (!isset($this->defaults['headers'])) { + $this->defaults['headers'] = [ + 'User-Agent' => Utils::getDefaultUserAgent() + ]; + } elseif (!Core::hasHeader($this->defaults, 'User-Agent')) { + // Add the User-Agent header if one was not already set + $this->defaults['headers']['User-Agent'] = Utils::getDefaultUserAgent(); + } + } + + /** + * Merges default options into the array passed by reference. + * + * @param array $options Options to modify by reference + * + * @return array + */ + private function mergeDefaults($options) + { + $defaults = $this->defaults; + + // Case-insensitively merge in default headers if both defaults and + // options have headers specified. + if (!empty($defaults['headers']) && !empty($options['headers'])) { + // Create a set of lowercased keys that are present. + $lkeys = []; + foreach (array_keys($options['headers']) as $k) { + $lkeys[strtolower($k)] = true; + } + // Merge in lowercase default keys when not present in above set. + foreach ($defaults['headers'] as $key => $value) { + if (!isset($lkeys[strtolower($key)])) { + $options['headers'][$key] = $value; + } + } + // No longer need to merge in headers. + unset($defaults['headers']); + } + + $result = array_replace_recursive($defaults, $options); + foreach ($options as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * @deprecated Use {@see GuzzleHttp\Pool} instead. + * @see GuzzleHttp\Pool + */ + public function sendAll($requests, array $options = []) + { + Pool::send($this, $requests, $options); + } + + /** + * @deprecated Use GuzzleHttp\Utils::getDefaultHandler + */ + public static function getDefaultHandler() + { + return Utils::getDefaultHandler(); + } + + /** + * @deprecated Use GuzzleHttp\Utils::getDefaultUserAgent + */ + public static function getDefaultUserAgent() + { + return Utils::getDefaultUserAgent(); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 0000000..63f0174 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,150 @@ +data = $data; + } + + /** + * Create a new collection from an array, validate the keys, and add default + * values where missing + * + * @param array $config Configuration values to apply. + * @param array $defaults Default parameters + * @param array $required Required parameter names + * + * @return self + * @throws \InvalidArgumentException if a parameter is missing + */ + public static function fromConfig( + array $config = [], + array $defaults = [], + array $required = [] + ) { + $data = $config + $defaults; + + if ($missing = array_diff($required, array_keys($data))) { + throw new \InvalidArgumentException( + 'Config is missing the following keys: ' . + implode(', ', $missing)); + } + + return new self($data); + } + + /** + * Removes all key value pairs + */ + public function clear() + { + $this->data = []; + } + + /** + * Get a specific key value. + * + * @param string $key Key to retrieve. + * + * @return mixed|null Value of the key or NULL + */ + public function get($key) + { + return isset($this->data[$key]) ? $this->data[$key] : null; + } + + /** + * Set a key value pair + * + * @param string $key Key to set + * @param mixed $value Value to set + */ + public function set($key, $value) + { + $this->data[$key] = $value; + } + + /** + * Add a value to a key. If a key of the same name has already been added, + * the key value will be converted into an array and the new value will be + * pushed to the end of the array. + * + * @param string $key Key to add + * @param mixed $value Value to add to the key + */ + public function add($key, $value) + { + if (!array_key_exists($key, $this->data)) { + $this->data[$key] = $value; + } elseif (is_array($this->data[$key])) { + $this->data[$key][] = $value; + } else { + $this->data[$key] = array($this->data[$key], $value); + } + } + + /** + * Remove a specific key value pair + * + * @param string $key A key to remove + */ + public function remove($key) + { + unset($this->data[$key]); + } + + /** + * Get all keys in the collection + * + * @return array + */ + public function getKeys() + { + return array_keys($this->data); + } + + /** + * Returns whether or not the specified key is present. + * + * @param string $key The key for which to check the existence. + * + * @return bool + */ + public function hasKey($key) + { + return array_key_exists($key, $this->data); + } + + /** + * Checks if any keys contains a certain value + * + * @param string $value Value to search for + * + * @return mixed Returns the key if the value was found FALSE if the value + * was not found. + */ + public function hasValue($value) + { + return array_search($value, $this->data, true); + } + + /** + * Replace the data of the object with the value of an array + * + * @param array $data Associative array of data + */ + public function replace(array $data) + { + $this->data = $data; + } + + /** + * Add and merge in a Collection or array of key value pair data. + * + * @param Collection|array $data Associative array of key value pair data + */ + public function merge($data) + { + foreach ($data as $key => $value) { + $this->add($key, $value); + } + } + + /** + * Overwrite key value pairs in this collection with all of the data from + * an array or collection. + * + * @param array|\Traversable $data Values to override over this config + */ + public function overwriteWith($data) + { + if (is_array($data)) { + $this->data = $data + $this->data; + } elseif ($data instanceof Collection) { + $this->data = $data->toArray() + $this->data; + } else { + foreach ($data as $key => $value) { + $this->data[$key] = $value; + } + } + } + + /** + * Returns a Collection containing all the elements of the collection after + * applying the callback function to each one. + * + * The callable should accept three arguments: + * - (string) $key + * - (string) $value + * - (array) $context + * + * The callable must return a the altered or unaltered value. + * + * @param callable $closure Map function to apply + * @param array $context Context to pass to the callable + * + * @return Collection + */ + public function map(callable $closure, array $context = []) + { + $collection = new static(); + foreach ($this as $key => $value) { + $collection[$key] = $closure($key, $value, $context); + } + + return $collection; + } + + /** + * Iterates over each key value pair in the collection passing them to the + * callable. If the callable returns true, the current value from input is + * returned into the result Collection. + * + * The callable must accept two arguments: + * - (string) $key + * - (string) $value + * + * @param callable $closure Evaluation function + * + * @return Collection + */ + public function filter(callable $closure) + { + $collection = new static(); + foreach ($this->data as $key => $value) { + if ($closure($key, $value)) { + $collection[$key] = $value; + } + } + + return $collection; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 0000000..f8ac7dd --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,248 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + * + * @return self + */ + public static function fromArray(array $cookies, $domain) + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * Quote the cookie value if it is not already quoted and it contains + * problematic characters. + * + * @param string $value Value that may or may not need to be quoted + * + * @return string + */ + public static function getCookieValue($value) + { + if (substr($value, 0, 1) !== '"' && + substr($value, -1, 1) !== '"' && + strpbrk($value, ';,') + ) { + $value = '"' . $value . '"'; + } + + return $value; + } + + public function toArray() + { + return array_map(function (SetCookie $cookie) { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + public function clear($domain = null, $path = null, $name = null) + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + public function clearSessionCookies() + { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + public function setCookie(SetCookie $cookie) + { + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } else { + $this->removeCookieIfEmpty($cookie); + return false; + } + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count() + { + return count($this->cookies); + } + + public function getIterator() + { + return new \ArrayIterator(array_values($this->cookies)); + } + + public function extractCookies( + RequestInterface $request, + ResponseInterface $response + ) { + if ($cookieHeader = $response->getHeaderAsArray('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getHost()); + } + $this->setCookie($sc); + } + } + } + + public function addCookieHeader(RequestInterface $request) + { + $values = []; + $scheme = $request->getScheme(); + $host = $request->getHost(); + $path = $request->getPath(); + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme == 'https') + ) { + $values[] = $cookie->getName() . '=' + . self::getCookieValue($cookie->getValue()); + } + } + + if ($values) { + $request->setHeader('Cookie', implode('; ', $values)); + } + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + * + * @param SetCookie $cookie + */ + private function removeCookieIfEmpty(SetCookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 0000000..4ea8567 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,75 @@ +filename = $cookieFile; + + if (file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * @throws \RuntimeException if the file cannot be found or created + */ + public function save($filename) + { + $json = []; + foreach ($this as $cookie) { + if ($cookie->getExpires() && !$cookie->getDiscard()) { + $json[] = $cookie->toArray(); + } + } + + if (false === file_put_contents($filename, json_encode($json))) { + // @codeCoverageIgnoreStart + throw new \RuntimeException("Unable to save file {$filename}"); + // @codeCoverageIgnoreEnd + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load($filename) + { + $json = file_get_contents($filename); + if (false === $json) { + // @codeCoverageIgnoreStart + throw new \RuntimeException("Unable to load file {$filename}"); + // @codeCoverageIgnoreEnd + } + + $data = Utils::jsonDecode($json, true); + if (is_array($data)) { + foreach (Utils::jsonDecode($json, true) as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 0000000..71a02d5 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,66 @@ +sessionKey = $sessionKey; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save() + { + $json = []; + foreach ($this as $cookie) { + if ($cookie->getExpires() && !$cookie->getDiscard()) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load() + { + $cookieJar = isset($_SESSION[$this->sessionKey]) + ? $_SESSION[$this->sessionKey] + : null; + + $data = Utils::jsonDecode($cookieJar, true); + if (is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie data"); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 0000000..ac9a890 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,373 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false + ]; + + /** @var array Cookie data */ + private $data; + + /** + * Create a new SetCookie object from a string + * + * @param string $cookie Set-Cookie header string + * + * @return self + */ + public static function fromString($cookie) + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + // The name of the cookie (first kvp) must include an equal sign. + if (empty($pieces) || !strpos($pieces[0], '=')) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? trim($cookieParts[1], " \n\r\t\0\x0B\"") + : true; + + // Only check for non-cookies when cookies have been found + if (empty($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (array_keys(self::$defaults) as $search) { + if (!strcasecmp($search, $key)) { + $data[$search] = $value; + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + $this->data = array_replace(self::$defaults, $data); + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(time() + $this->getMaxAge()); + } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { + $this->setExpires($this->getExpires()); + } + } + + public function __toString() + { + $str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; + foreach ($this->data as $k => $v) { + if ($k != 'Name' && $k != 'Value' && $v !== null && $v !== false) { + if ($k == 'Expires') { + $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + } + } + } + + return rtrim($str, '; '); + } + + public function toArray() + { + return $this->data; + } + + /** + * Get the cookie name + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name + * + * @param string $name Cookie name + */ + public function setName($name) + { + $this->data['Name'] = $name; + } + + /** + * Get the cookie value + * + * @return string + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value + * + * @param string $value Cookie value + */ + public function setValue($value) + { + $this->data['Value'] = $value; + } + + /** + * Get the domain + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie + * + * @param string $domain + */ + public function setDomain($domain) + { + $this->data['Domain'] = $domain; + } + + /** + * Get the path + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie + * + * @param string $path Path of the cookie + */ + public function setPath($path) + { + $this->data['Path'] = $path; + } + + /** + * Maximum lifetime of the cookie in seconds + * + * @return int|null + */ + public function getMaxAge() + { + return $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie + * + * @param int $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge) + { + $this->data['Max-Age'] = $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires + * + * @return mixed + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire + * + * @param int $timestamp Unix timestamp + */ + public function setExpires($timestamp) + { + $this->data['Expires'] = is_numeric($timestamp) + ? (int) $timestamp + : strtotime($timestamp); + } + + /** + * Get whether or not this is a secure cookie + * + * @return null|bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure) + { + $this->data['Secure'] = $secure; + } + + /** + * Get whether or not this is a session cookie + * + * @return null|bool + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard) + { + $this->data['Discard'] = $discard; + } + + /** + * Get whether or not this is an HTTP only cookie + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly) + { + $this->data['HttpOnly'] = $httpOnly; + } + + /** + * Check if the cookie matches a path value + * + * @param string $path Path to check against + * + * @return bool + */ + public function matchesPath($path) + { + return !$this->getPath() || 0 === stripos($path, $this->getPath()); + } + + /** + * Check if the cookie matches a domain value + * + * @param string $domain Domain to check against + * + * @return bool + */ + public function matchesDomain($domain) + { + // Remove the leading '.' as per spec in RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim($this->getDomain(), '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.1.3 + if (filter_var($domain, FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/i', $domain); + } + + /** + * Check if the cookie is expired + * + * @return bool + */ + public function isExpired() + { + return $this->getExpires() && time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265 + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + // Names must not be empty, but can be 0 + $name = $this->getName(); + if (empty($name) && !is_numeric($name)) { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + return "Cookie name must not cannot invalid characters: =,; \\t\\r\\n\\013\\014"; + } + + // Value must not be empty, but can be 0 + $value = $this->getValue(); + if (empty($value) && !is_numeric($value)) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0 + // A "0" is not a valid internet domain, but may be used as server name + // in a private network. + $domain = $this->getDomain(); + if (empty($domain) && !is_numeric($domain)) { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractEvent.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractEvent.php new file mode 100644 index 0000000..0d2f4db --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractEvent.php @@ -0,0 +1,20 @@ +propagationStopped; + } + + public function stopPropagation() + { + $this->propagationStopped = true; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php new file mode 100644 index 0000000..8a6ee47 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php @@ -0,0 +1,61 @@ +transaction = $transaction; + } + + /** + * Get the HTTP client associated with the event. + * + * @return ClientInterface + */ + public function getClient() + { + return $this->transaction->client; + } + + /** + * Get the request object + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->transaction->request; + } + + /** + * Get the number of transaction retries. + * + * @return int + */ + public function getRetryCount() + { + return $this->transaction->retries; + } + + /** + * @return Transaction + */ + public function getTransaction() + { + return $this->transaction; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php new file mode 100644 index 0000000..bbbdfaf --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php @@ -0,0 +1,40 @@ +transaction->state = 'retry'; + + if ($afterDelay) { + $this->transaction->request->getConfig()->set('delay', $afterDelay); + } + + $this->stopPropagation(); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php new file mode 100644 index 0000000..3b106df --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php @@ -0,0 +1,63 @@ +transaction->transferInfo; + } + + return isset($this->transaction->transferInfo[$name]) + ? $this->transaction->transferInfo[$name] + : null; + } + + /** + * Returns true/false if a response is available. + * + * @return bool + */ + public function hasResponse() + { + return !($this->transaction->response instanceof FutureInterface); + } + + /** + * Get the response. + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->hasResponse() ? $this->transaction->response : null; + } + + /** + * Intercept the request and associate a response + * + * @param ResponseInterface $response Response to set + */ + public function intercept(ResponseInterface $response) + { + $this->transaction->response = $response; + $this->transaction->exception = null; + $this->stopPropagation(); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/BeforeEvent.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/BeforeEvent.php new file mode 100644 index 0000000..f313c37 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/BeforeEvent.php @@ -0,0 +1,26 @@ +transaction->response = $response; + $this->transaction->exception = null; + $this->stopPropagation(); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/CompleteEvent.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/CompleteEvent.php new file mode 100644 index 0000000..56cc557 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/CompleteEvent.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @link https://github.com/symfony/symfony/tree/master/src/Symfony/Component/EventDispatcher + */ +class Emitter implements EmitterInterface +{ + /** @var array */ + private $listeners = []; + + /** @var array */ + private $sorted = []; + + public function on($eventName, callable $listener, $priority = 0) + { + if ($priority === 'first') { + $priority = isset($this->listeners[$eventName]) + ? max(array_keys($this->listeners[$eventName])) + 1 + : 1; + } elseif ($priority === 'last') { + $priority = isset($this->listeners[$eventName]) + ? min(array_keys($this->listeners[$eventName])) - 1 + : -1; + } + + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName]); + } + + public function once($eventName, callable $listener, $priority = 0) + { + $onceListener = function ( + EventInterface $event, + $eventName + ) use (&$onceListener, $eventName, $listener, $priority) { + $this->removeListener($eventName, $onceListener); + $listener($event, $eventName); + }; + + $this->on($eventName, $onceListener, $priority); + } + + public function removeListener($eventName, callable $listener) + { + if (empty($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + unset( + $this->listeners[$eventName][$priority][$key], + $this->sorted[$eventName] + ); + } + } + } + + public function listeners($eventName = null) + { + // Return all events in a sorted priority order + if ($eventName === null) { + foreach (array_keys($this->listeners) as $eventName) { + if (empty($this->sorted[$eventName])) { + $this->listeners($eventName); + } + } + return $this->sorted; + } + + // Return the listeners for a specific event, sorted in priority order + if (empty($this->sorted[$eventName])) { + $this->sorted[$eventName] = []; + if (isset($this->listeners[$eventName])) { + krsort($this->listeners[$eventName], SORT_NUMERIC); + foreach ($this->listeners[$eventName] as $listeners) { + foreach ($listeners as $listener) { + $this->sorted[$eventName][] = $listener; + } + } + } + } + + return $this->sorted[$eventName]; + } + + public function hasListeners($eventName) + { + return !empty($this->listeners[$eventName]); + } + + public function emit($eventName, EventInterface $event) + { + if (isset($this->listeners[$eventName])) { + foreach ($this->listeners($eventName) as $listener) { + $listener($event, $eventName); + if ($event->isPropagationStopped()) { + break; + } + } + } + + return $event; + } + + public function attach(SubscriberInterface $subscriber) + { + foreach ($subscriber->getEvents() as $eventName => $listeners) { + if (is_array($listeners[0])) { + foreach ($listeners as $listener) { + $this->on( + $eventName, + [$subscriber, $listener[0]], + isset($listener[1]) ? $listener[1] : 0 + ); + } + } else { + $this->on( + $eventName, + [$subscriber, $listeners[0]], + isset($listeners[1]) ? $listeners[1] : 0 + ); + } + } + } + + public function detach(SubscriberInterface $subscriber) + { + foreach ($subscriber->getEvents() as $eventName => $listener) { + $this->removeListener($eventName, [$subscriber, $listener[0]]); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/EmitterInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/EmitterInterface.php new file mode 100644 index 0000000..9783efd --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/EmitterInterface.php @@ -0,0 +1,96 @@ +transaction->exception; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ErrorEvent.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ErrorEvent.php new file mode 100644 index 0000000..7432134 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ErrorEvent.php @@ -0,0 +1,27 @@ +transaction->exception; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/EventInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/EventInterface.php new file mode 100644 index 0000000..97247e8 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/EventInterface.php @@ -0,0 +1,23 @@ +emitter) { + $this->emitter = new Emitter(); + } + + return $this->emitter; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ListenerAttacherTrait.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ListenerAttacherTrait.php new file mode 100644 index 0000000..407dc92 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ListenerAttacherTrait.php @@ -0,0 +1,88 @@ +getEmitter(); + foreach ($listeners as $el) { + if ($el['once']) { + $emitter->once($el['name'], $el['fn'], $el['priority']); + } else { + $emitter->on($el['name'], $el['fn'], $el['priority']); + } + } + } + + /** + * Extracts the allowed events from the provided array, and ignores anything + * else in the array. The event listener must be specified as a callable or + * as an array of event listener data ("name", "fn", "priority", "once"). + * + * @param array $source Array containing callables or hashes of data to be + * prepared as event listeners. + * @param array $events Names of events to look for in the provided $source + * array. Other keys are ignored. + * @return array + */ + private function prepareListeners(array $source, array $events) + { + $listeners = []; + foreach ($events as $name) { + if (isset($source[$name])) { + $this->buildListener($name, $source[$name], $listeners); + } + } + + return $listeners; + } + + /** + * Creates a complete event listener definition from the provided array of + * listener data. Also works recursively if more than one listeners are + * contained in the provided array. + * + * @param string $name Name of the event the listener is for. + * @param array|callable $data Event listener data to prepare. + * @param array $listeners Array of listeners, passed by reference. + * + * @throws \InvalidArgumentException if the event data is malformed. + */ + private function buildListener($name, $data, &$listeners) + { + static $defaults = ['priority' => 0, 'once' => false]; + + // If a callable is provided, normalize it to the array format. + if (is_callable($data)) { + $data = ['fn' => $data]; + } + + // Prepare the listener and add it to the array, recursively. + if (isset($data['fn'])) { + $data['name'] = $name; + $listeners[] = $data + $defaults; + } elseif (is_array($data)) { + foreach ($data as $listenerData) { + $this->buildListener($name, $listenerData, $listeners); + } + } else { + throw new \InvalidArgumentException('Each event listener must be a ' + . 'callable or an associative array containing a "fn" key.'); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ProgressEvent.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ProgressEvent.php new file mode 100644 index 0000000..3fd0de4 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/ProgressEvent.php @@ -0,0 +1,51 @@ +downloadSize = $downloadSize; + $this->downloaded = $downloaded; + $this->uploadSize = $uploadSize; + $this->uploaded = $uploaded; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/RequestEvents.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/RequestEvents.php new file mode 100644 index 0000000..f51d420 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Event/RequestEvents.php @@ -0,0 +1,56 @@ + ['methodName']] + * - ['eventName' => ['methodName', $priority]] + * - ['eventName' => [['methodName'], ['otherMethod']] + * - ['eventName' => [['methodName'], ['otherMethod', $priority]] + * - ['eventName' => [['methodName', $priority], ['otherMethod', $priority]] + * + * @return array + */ + public function getEvents(); +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 0000000..fd78431 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,7 @@ +response = $response; + } + /** + * Get the associated response + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php new file mode 100644 index 0000000..3f052d3 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php @@ -0,0 +1,121 @@ +getStatusCode() + : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + } + + /** + * Wrap non-RequestExceptions with a RequestException + * + * @param RequestInterface $request + * @param \Exception $e + * + * @return RequestException + */ + public static function wrapException(RequestInterface $request, \Exception $e) + { + if ($e instanceof RequestException) { + return $e; + } elseif ($e instanceof ConnectException) { + return new HttpConnectException($e->getMessage(), $request, null, $e); + } else { + return new RequestException($e->getMessage(), $request, null, $e); + } + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request + * @param ResponseInterface $response Response received + * @param \Exception $previous Previous exception + * + * @return self + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $previous = null + ) { + if (!$response) { + return new self('Error completing request', $request, null, $previous); + } + + $level = floor($response->getStatusCode() / 100); + if ($level == '4') { + $label = 'Client error response'; + $className = __NAMESPACE__ . '\\ClientException'; + } elseif ($level == '5') { + $label = 'Server error response'; + $className = __NAMESPACE__ . '\\ServerException'; + } else { + $label = 'Unsuccessful response'; + $className = __CLASS__; + } + + $message = $label . ' [url] ' . $request->getUrl() + . ' [status code] ' . $response->getStatusCode() + . ' [reason phrase] ' . $response->getReasonPhrase(); + + return new $className($message, $request, $response, $previous); + } + + /** + * Get the request that caused the exception + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the associated response + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Check if a response was received + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000..7cdd340 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,7 @@ +error = $error; + } + + /** + * Get the associated error + * + * @return \LibXMLError|null + */ + public function getError() + { + return $this->error; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/HasDataTrait.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/HasDataTrait.php new file mode 100644 index 0000000..020dfc9 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/HasDataTrait.php @@ -0,0 +1,75 @@ +data); + } + + public function offsetGet($offset) + { + return isset($this->data[$offset]) ? $this->data[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + + public function toArray() + { + return $this->data; + } + + public function count() + { + return count($this->data); + } + + /** + * Get a value from the collection using a path syntax to retrieve nested + * data. + * + * @param string $path Path to traverse and retrieve a value from + * + * @return mixed|null + */ + public function getPath($path) + { + return Utils::getPath($this->data, $path); + } + + /** + * Set a value into a nested array key. Keys will be created as needed to + * set the value. + * + * @param string $path Path to set + * @param mixed $value Value to set at the key + * + * @throws \RuntimeException when trying to setPath using a nested path + * that travels through a scalar value + */ + public function setPath($path, $value) + { + Utils::setPath($this->data, $path, $value); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/AbstractMessage.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/AbstractMessage.php new file mode 100644 index 0000000..0c67575 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/AbstractMessage.php @@ -0,0 +1,253 @@ +getBody(); + } + + public function getProtocolVersion() + { + return $this->protocolVersion; + } + + public function getBody() + { + return $this->body; + } + + public function setBody(StreamInterface $body = null) + { + if ($body === null) { + // Setting a null body will remove the body of the request + $this->removeHeader('Content-Length'); + $this->removeHeader('Transfer-Encoding'); + } + + $this->body = $body; + } + + public function addHeader($header, $value) + { + if (is_array($value)) { + $current = array_merge($this->getHeaderAsArray($header), $value); + } else { + $current = $this->getHeaderAsArray($header); + $current[] = (string) $value; + } + + $this->setHeader($header, $current); + } + + public function addHeaders(array $headers) + { + foreach ($headers as $name => $header) { + $this->addHeader($name, $header); + } + } + + public function getHeader($header) + { + $name = strtolower($header); + return isset($this->headers[$name]) + ? implode(', ', $this->headers[$name]) + : ''; + } + + public function getHeaderAsArray($header) + { + $name = strtolower($header); + return isset($this->headers[$name]) ? $this->headers[$name] : []; + } + + public function getHeaders() + { + $headers = []; + foreach ($this->headers as $name => $values) { + $headers[$this->headerNames[$name]] = $values; + } + + return $headers; + } + + public function setHeader($header, $value) + { + $header = trim($header); + $name = strtolower($header); + $this->headerNames[$name] = $header; + + if (is_array($value)) { + foreach ($value as &$v) { + $v = trim($v); + } + $this->headers[$name] = $value; + } else { + $this->headers[$name] = [trim($value)]; + } + } + + public function setHeaders(array $headers) + { + $this->headers = $this->headerNames = []; + foreach ($headers as $key => $value) { + $this->setHeader($key, $value); + } + } + + public function hasHeader($header) + { + return isset($this->headers[strtolower($header)]); + } + + public function removeHeader($header) + { + $name = strtolower($header); + unset($this->headers[$name], $this->headerNames[$name]); + } + + /** + * Parse an array of header values containing ";" separated data into an + * array of associative arrays representing the header key value pair + * data of the header. When a parameter does not contain a value, but just + * contains a key, this function will inject a key with a '' string value. + * + * @param MessageInterface $message That contains the header + * @param string $header Header to retrieve from the message + * + * @return array Returns the parsed header values. + */ + public static function parseHeader(MessageInterface $message, $header) + { + static $trimmed = "\"' \n\t\r"; + $params = $matches = []; + + foreach (self::normalizeHeader($message, $header) as $val) { + $part = []; + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param MessageInterface $message That contains the header + * @param string $header Header to retrieve from the message + * + * @return array Returns the normalized header field values. + */ + public static function normalizeHeader(MessageInterface $message, $header) + { + $h = $message->getHeaderAsArray($header); + for ($i = 0, $total = count($h); $i < $total; $i++) { + if (strpos($h[$i], ',') === false) { + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $h[$i]) as $v) { + $h[] = trim($v); + } + unset($h[$i]); + } + + return $h; + } + + /** + * Gets the start-line and headers of a message as a string + * + * @param MessageInterface $message + * + * @return string + */ + public static function getStartLineAndHeaders(MessageInterface $message) + { + return static::getStartLine($message) + . self::getHeadersAsString($message); + } + + /** + * Gets the headers of a message as a string + * + * @param MessageInterface $message + * + * @return string + */ + public static function getHeadersAsString(MessageInterface $message) + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= "\r\n{$name}: " . implode(', ', $values); + } + + return $result; + } + + /** + * Gets the start line of a message + * + * @param MessageInterface $message + * + * @return string + * @throws \InvalidArgumentException + */ + public static function getStartLine(MessageInterface $message) + { + if ($message instanceof RequestInterface) { + return trim($message->getMethod() . ' ' + . $message->getResource()) + . ' HTTP/' . $message->getProtocolVersion(); + } elseif ($message instanceof ResponseInterface) { + return 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + } + + /** + * Accepts and modifies the options provided to the message in the + * constructor. + * + * Can be overridden in subclasses as necessary. + * + * @param array $options Options array passed by reference. + */ + protected function handleOptions(array &$options) + { + if (isset($options['protocol_version'])) { + $this->protocolVersion = $options['protocol_version']; + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php new file mode 100644 index 0000000..ca42f20 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php @@ -0,0 +1,24 @@ +then($onFulfilled, $onRejected, $onProgress), + [$future, 'wait'], + [$future, 'cancel'] + ); + } + + public function getStatusCode() + { + return $this->_value->getStatusCode(); + } + + public function setStatusCode($code) + { + $this->_value->setStatusCode($code); + } + + public function getReasonPhrase() + { + return $this->_value->getReasonPhrase(); + } + + public function setReasonPhrase($phrase) + { + $this->_value->setReasonPhrase($phrase); + } + + public function getEffectiveUrl() + { + return $this->_value->getEffectiveUrl(); + } + + public function setEffectiveUrl($url) + { + $this->_value->setEffectiveUrl($url); + } + + public function json(array $config = []) + { + return $this->_value->json($config); + } + + public function xml(array $config = []) + { + return $this->_value->xml($config); + } + + public function __toString() + { + try { + return $this->_value->__toString(); + } catch (\Exception $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + return ''; + } + } + + public function getProtocolVersion() + { + return $this->_value->getProtocolVersion(); + } + + public function setBody(StreamInterface $body = null) + { + $this->_value->setBody($body); + } + + public function getBody() + { + return $this->_value->getBody(); + } + + public function getHeaders() + { + return $this->_value->getHeaders(); + } + + public function getHeader($header) + { + return $this->_value->getHeader($header); + } + + public function getHeaderAsArray($header) + { + return $this->_value->getHeaderAsArray($header); + } + + public function hasHeader($header) + { + return $this->_value->hasHeader($header); + } + + public function removeHeader($header) + { + $this->_value->removeHeader($header); + } + + public function addHeader($header, $value) + { + $this->_value->addHeader($header, $value); + } + + public function addHeaders(array $headers) + { + $this->_value->addHeaders($headers); + } + + public function setHeader($header, $value) + { + $this->_value->setHeader($header, $value); + } + + public function setHeaders(array $headers) + { + $this->_value->setHeaders($headers); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageFactory.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageFactory.php new file mode 100644 index 0000000..85984e2 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageFactory.php @@ -0,0 +1,364 @@ + 1, 'timeout' => 1, 'verify' => 1, 'ssl_key' => 1, + 'cert' => 1, 'proxy' => 1, 'debug' => 1, 'save_to' => 1, 'stream' => 1, + 'expect' => 1, 'future' => 1 + ]; + + /** @var array Default allow_redirects request option settings */ + private static $defaultRedirect = [ + 'max' => 5, + 'strict' => false, + 'referer' => false, + 'protocols' => ['http', 'https'] + ]; + + /** + * @param array $customOptions Associative array of custom request option + * names mapping to functions used to apply + * the option. The function accepts the request + * and the option value to apply. + */ + public function __construct(array $customOptions = []) + { + $this->errorPlugin = new HttpError(); + $this->redirectPlugin = new Redirect(); + $this->customOptions = $customOptions; + } + + public function createResponse( + $statusCode, + array $headers = [], + $body = null, + array $options = [] + ) { + if (null !== $body) { + $body = Stream::factory($body); + } + + return new Response($statusCode, $headers, $body, $options); + } + + public function createRequest($method, $url, array $options = []) + { + // Handle the request protocol version option that needs to be + // specified in the request constructor. + if (isset($options['version'])) { + $options['config']['protocol_version'] = $options['version']; + unset($options['version']); + } + + $request = new Request($method, $url, [], null, + isset($options['config']) ? $options['config'] : []); + + unset($options['config']); + + // Use a POST body by default + if ($method == 'POST' + && !isset($options['body']) + && !isset($options['json']) + ) { + $options['body'] = []; + } + + if ($options) { + $this->applyOptions($request, $options); + } + + return $request; + } + + /** + * Create a request or response object from an HTTP message string + * + * @param string $message Message to parse + * + * @return RequestInterface|ResponseInterface + * @throws \InvalidArgumentException if unable to parse a message + */ + public function fromMessage($message) + { + static $parser; + if (!$parser) { + $parser = new MessageParser(); + } + + // Parse a response + if (strtoupper(substr($message, 0, 4)) == 'HTTP') { + $data = $parser->parseResponse($message); + return $this->createResponse( + $data['code'], + $data['headers'], + $data['body'] === '' ? null : $data['body'], + $data + ); + } + + // Parse a request + if (!($data = ($parser->parseRequest($message)))) { + throw new \InvalidArgumentException('Unable to parse request'); + } + + return $this->createRequest( + $data['method'], + Url::buildUrl($data['request_url']), + [ + 'headers' => $data['headers'], + 'body' => $data['body'] === '' ? null : $data['body'], + 'config' => [ + 'protocol_version' => $data['protocol_version'] + ] + ] + ); + } + + /** + * Apply POST fields and files to a request to attempt to give an accurate + * representation. + * + * @param RequestInterface $request Request to update + * @param array $body Body to apply + */ + protected function addPostData(RequestInterface $request, array $body) + { + static $fields = ['string' => true, 'array' => true, 'NULL' => true, + 'boolean' => true, 'double' => true, 'integer' => true]; + + $post = new PostBody(); + foreach ($body as $key => $value) { + if (isset($fields[gettype($value)])) { + $post->setField($key, $value); + } elseif ($value instanceof PostFileInterface) { + $post->addFile($value); + } else { + $post->addFile(new PostFile($key, $value)); + } + } + + if ($request->getHeader('Content-Type') == 'multipart/form-data') { + $post->forceMultipartUpload(true); + } + + $request->setBody($post); + } + + protected function applyOptions( + RequestInterface $request, + array $options = [] + ) { + $config = $request->getConfig(); + $emitter = $request->getEmitter(); + + foreach ($options as $key => $value) { + + if (isset(self::$configMap[$key])) { + $config[$key] = $value; + continue; + } + + switch ($key) { + + case 'allow_redirects': + + if ($value === false) { + continue; + } + + if ($value === true) { + $value = self::$defaultRedirect; + } elseif (!is_array($value)) { + throw new Iae('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $value += self::$defaultRedirect; + } + + $config['redirect'] = $value; + $emitter->attach($this->redirectPlugin); + break; + + case 'decode_content': + + if ($value === false) { + continue; + } + + $config['decode_content'] = true; + if ($value !== true) { + $request->setHeader('Accept-Encoding', $value); + } + break; + + case 'headers': + + if (!is_array($value)) { + throw new Iae('header value must be an array'); + } + foreach ($value as $k => $v) { + $request->setHeader($k, $v); + } + break; + + case 'exceptions': + + if ($value === true) { + $emitter->attach($this->errorPlugin); + } + break; + + case 'body': + + if (is_array($value)) { + $this->addPostData($request, $value); + } elseif ($value !== null) { + $request->setBody(Stream::factory($value)); + } + break; + + case 'auth': + + if (!$value) { + continue; + } + + if (is_array($value)) { + $type = isset($value[2]) ? strtolower($value[2]) : 'basic'; + } else { + $type = strtolower($value); + } + + $config['auth'] = $value; + + if ($type == 'basic') { + $request->setHeader( + 'Authorization', + 'Basic ' . base64_encode("$value[0]:$value[1]") + ); + } elseif ($type == 'digest') { + // @todo: Do not rely on curl + $config->setPath('curl/' . CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + $config->setPath('curl/' . CURLOPT_USERPWD, "$value[0]:$value[1]"); + } + break; + + case 'query': + + if ($value instanceof Query) { + $original = $request->getQuery(); + // Do not overwrite existing query string variables by + // overwriting the object with the query string data passed + // in the URL + $value->overwriteWith($original->toArray()); + $request->setQuery($value); + } elseif (is_array($value)) { + // Do not overwrite existing query string variables + $query = $request->getQuery(); + foreach ($value as $k => $v) { + if (!isset($query[$k])) { + $query[$k] = $v; + } + } + } else { + throw new Iae('query must be an array or Query object'); + } + break; + + case 'cookies': + + if ($value === true) { + static $cookie = null; + if (!$cookie) { + $cookie = new Cookie(); + } + $emitter->attach($cookie); + } elseif (is_array($value)) { + $emitter->attach( + new Cookie(CookieJar::fromArray($value, $request->getHost())) + ); + } elseif ($value instanceof CookieJarInterface) { + $emitter->attach(new Cookie($value)); + } elseif ($value !== false) { + throw new Iae('cookies must be an array, true, or CookieJarInterface'); + } + break; + + case 'events': + + if (!is_array($value)) { + throw new Iae('events must be an array'); + } + + $this->attachListeners($request, + $this->prepareListeners( + $value, + ['before', 'complete', 'error', 'progress', 'end'] + ) + ); + break; + + case 'subscribers': + + if (!is_array($value)) { + throw new Iae('subscribers must be an array'); + } + + foreach ($value as $subscribers) { + $emitter->attach($subscribers); + } + break; + + case 'json': + + $request->setBody(Stream::factory(json_encode($value))); + if (!$request->hasHeader('Content-Type')) { + $request->setHeader('Content-Type', 'application/json'); + } + break; + + default: + + // Check for custom handler functions. + if (isset($this->customOptions[$key])) { + $fn = $this->customOptions[$key]; + $fn($request, $value); + continue; + } + + throw new Iae("No method can handle the {$key} config key"); + } + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageFactoryInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageFactoryInterface.php new file mode 100644 index 0000000..86ae9c7 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageFactoryInterface.php @@ -0,0 +1,71 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * @return array Returns an associative array of the message's headers. + */ + public function getHeaders(); + + /** + * Retrieve a header by the given case-insensitive name. + * + * @param string $header Case-insensitive header name. + * + * @return string + */ + public function getHeader($header); + + /** + * Retrieves a header by the given case-insensitive name as an array of strings. + * + * @param string $header Case-insensitive header name. + * + * @return string[] + */ + public function getHeaderAsArray($header); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $header Case-insensitive header name. + * + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($header); + + /** + * Remove a specific header by case-insensitive name. + * + * @param string $header Case-insensitive header name. + */ + public function removeHeader($header); + + /** + * Appends a header value to any existing values associated with the + * given header name. + * + * @param string $header Header name to add + * @param string $value Value of the header + */ + public function addHeader($header, $value); + + /** + * Merges in an associative array of headers. + * + * Each array key MUST be a string representing the case-insensitive name + * of a header. Each value MUST be either a string or an array of strings. + * For each value, the value is appended to any existing header of the same + * name, or, if a header does not already exist by the given name, then the + * header is added. + * + * @param array $headers Associative array of headers to add to the message + */ + public function addHeaders(array $headers); + + /** + * Sets a header, replacing any existing values of any headers with the + * same case-insensitive name. + * + * The header values MUST be a string or an array of strings. + * + * @param string $header Header name + * @param string|array $value Header value(s) + */ + public function setHeader($header, $value); + + /** + * Sets headers, replacing any headers that have already been set on the + * message. + * + * The array keys MUST be a string. The array values must be either a + * string or an array of strings. + * + * @param array $headers Headers to set. + */ + public function setHeaders(array $headers); +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageParser.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageParser.php new file mode 100644 index 0000000..c3cc195 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/MessageParser.php @@ -0,0 +1,171 @@ +parseMessage($message))) { + return false; + } + + // Parse the protocol and protocol version + if (isset($parts['start_line'][2])) { + $startParts = explode('/', $parts['start_line'][2]); + $protocol = strtoupper($startParts[0]); + $version = isset($startParts[1]) ? $startParts[1] : '1.1'; + } else { + $protocol = 'HTTP'; + $version = '1.1'; + } + + $parsed = [ + 'method' => strtoupper($parts['start_line'][0]), + 'protocol' => $protocol, + 'protocol_version' => $version, + 'headers' => $parts['headers'], + 'body' => $parts['body'] + ]; + + $parsed['request_url'] = $this->getUrlPartsFromMessage( + (isset($parts['start_line'][1]) ? $parts['start_line'][1] : ''), $parsed); + + return $parsed; + } + + /** + * Parse an HTTP response message into an associative array of parts. + * + * @param string $message HTTP response to parse + * + * @return array|bool Returns false if the message is invalid + */ + public function parseResponse($message) + { + if (!($parts = $this->parseMessage($message))) { + return false; + } + + list($protocol, $version) = explode('/', trim($parts['start_line'][0])); + + return [ + 'protocol' => $protocol, + 'protocol_version' => $version, + 'code' => $parts['start_line'][1], + 'reason_phrase' => isset($parts['start_line'][2]) ? $parts['start_line'][2] : '', + 'headers' => $parts['headers'], + 'body' => $parts['body'] + ]; + } + + /** + * Parse a message into parts + * + * @param string $message Message to parse + * + * @return array|bool + */ + private function parseMessage($message) + { + if (!$message) { + return false; + } + + $startLine = null; + $headers = []; + $body = ''; + + // Iterate over each line in the message, accounting for line endings + $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { + + $line = $lines[$i]; + + // If two line breaks were encountered, then this is the end of body + if (empty($line)) { + if ($i < $totalLines - 1) { + $body = implode('', array_slice($lines, $i + 2)); + } + break; + } + + // Parse message headers + if (!$startLine) { + $startLine = explode(' ', $line, 3); + } elseif (strpos($line, ':')) { + $parts = explode(':', $line, 2); + $key = trim($parts[0]); + $value = isset($parts[1]) ? trim($parts[1]) : ''; + if (!isset($headers[$key])) { + $headers[$key] = $value; + } elseif (!is_array($headers[$key])) { + $headers[$key] = [$headers[$key], $value]; + } else { + $headers[$key][] = $value; + } + } + } + + return [ + 'start_line' => $startLine, + 'headers' => $headers, + 'body' => $body + ]; + } + + /** + * Create URL parts from HTTP message parts + * + * @param string $requestUrl Associated URL + * @param array $parts HTTP message parts + * + * @return array + */ + private function getUrlPartsFromMessage($requestUrl, array $parts) + { + // Parse the URL information from the message + $urlParts = ['path' => $requestUrl, 'scheme' => 'http']; + + // Check for the Host header + if (isset($parts['headers']['Host'])) { + $urlParts['host'] = $parts['headers']['Host']; + } elseif (isset($parts['headers']['host'])) { + $urlParts['host'] = $parts['headers']['host']; + } else { + $urlParts['host'] = null; + } + + if (false === strpos($urlParts['host'], ':')) { + $urlParts['port'] = ''; + } else { + $hostParts = explode(':', $urlParts['host']); + $urlParts['host'] = trim($hostParts[0]); + $urlParts['port'] = (int) trim($hostParts[1]); + if ($urlParts['port'] == 443) { + $urlParts['scheme'] = 'https'; + } + } + + // Check if a query is present + $path = $urlParts['path']; + $qpos = strpos($path, '?'); + if ($qpos) { + $urlParts['query'] = substr($path, $qpos + 1); + $urlParts['path'] = substr($path, 0, $qpos); + } else { + $urlParts['query'] = ''; + } + + return $urlParts; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/Request.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/Request.php new file mode 100644 index 0000000..38714af --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/Request.php @@ -0,0 +1,195 @@ +setUrl($url); + $this->method = strtoupper($method); + $this->handleOptions($options); + $this->transferOptions = new Collection($options); + $this->addPrepareEvent(); + + if ($body !== null) { + $this->setBody($body); + } + + if ($headers) { + foreach ($headers as $key => $value) { + $this->addHeader($key, $value); + } + } + } + + public function __clone() + { + if ($this->emitter) { + $this->emitter = clone $this->emitter; + } + $this->transferOptions = clone $this->transferOptions; + $this->url = clone $this->url; + } + + public function setUrl($url) + { + $this->url = $url instanceof Url ? $url : Url::fromString($url); + $this->updateHostHeaderFromUrl(); + } + + public function getUrl() + { + return (string) $this->url; + } + + public function setQuery($query) + { + $this->url->setQuery($query); + } + + public function getQuery() + { + return $this->url->getQuery(); + } + + public function setMethod($method) + { + $this->method = strtoupper($method); + } + + public function getMethod() + { + return $this->method; + } + + public function getScheme() + { + return $this->url->getScheme(); + } + + public function setScheme($scheme) + { + $this->url->setScheme($scheme); + } + + public function getPort() + { + return $this->url->getPort(); + } + + public function setPort($port) + { + $this->url->setPort($port); + $this->updateHostHeaderFromUrl(); + } + + public function getHost() + { + return $this->url->getHost(); + } + + public function setHost($host) + { + $this->url->setHost($host); + $this->updateHostHeaderFromUrl(); + } + + public function getPath() + { + return '/' . ltrim($this->url->getPath(), '/'); + } + + public function setPath($path) + { + $this->url->setPath($path); + } + + public function getResource() + { + $resource = $this->getPath(); + if ($query = (string) $this->url->getQuery()) { + $resource .= '?' . $query; + } + + return $resource; + } + + public function getConfig() + { + return $this->transferOptions; + } + + protected function handleOptions(array &$options) + { + parent::handleOptions($options); + // Use a custom emitter if one is specified, and remove it from + // options that are exposed through getConfig() + if (isset($options['emitter'])) { + $this->emitter = $options['emitter']; + unset($options['emitter']); + } + } + + /** + * Adds a subscriber that ensures a request's body is prepared before + * sending. + */ + private function addPrepareEvent() + { + static $subscriber; + if (!$subscriber) { + $subscriber = new Prepare(); + } + + $this->getEmitter()->attach($subscriber); + } + + private function updateHostHeaderFromUrl() + { + $port = $this->url->getPort(); + $scheme = $this->url->getScheme(); + if ($host = $this->url->getHost()) { + if (($port == 80 && $scheme == 'http') || + ($port == 443 && $scheme == 'https') + ) { + $this->setHeader('Host', $host); + } else { + $this->setHeader('Host', "{$host}:{$port}"); + } + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/RequestInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/RequestInterface.php new file mode 100644 index 0000000..f6a69d1 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/RequestInterface.php @@ -0,0 +1,136 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Reserved for WebDAV advanced collections expired proposal', + 426 => 'Upgrade required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** @var string The reason phrase of the response (human readable code) */ + private $reasonPhrase; + + /** @var string The status code of the response */ + private $statusCode; + + /** @var string The effective URL that returned this response */ + private $effectiveUrl; + + /** + * @param int|string $statusCode The response status code (e.g. 200) + * @param array $headers The response headers + * @param StreamInterface $body The body of the response + * @param array $options Response message options + * - reason_phrase: Set a custom reason phrase + * - protocol_version: Set a custom protocol version + */ + public function __construct( + $statusCode, + array $headers = [], + StreamInterface $body = null, + array $options = [] + ) { + $this->statusCode = (int) $statusCode; + $this->handleOptions($options); + + // Assume a reason phrase if one was not applied as an option + if (!$this->reasonPhrase && + isset(self::$statusTexts[$this->statusCode]) + ) { + $this->reasonPhrase = self::$statusTexts[$this->statusCode]; + } + + if ($headers) { + $this->setHeaders($headers); + } + + if ($body) { + $this->setBody($body); + } + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function setStatusCode($code) + { + return $this->statusCode = (int) $code; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + public function setReasonPhrase($phrase) + { + return $this->reasonPhrase = $phrase; + } + + public function json(array $config = []) + { + try { + return Utils::jsonDecode( + (string) $this->getBody(), + isset($config['object']) ? !$config['object'] : true, + 512, + isset($config['big_int_strings']) ? JSON_BIGINT_AS_STRING : 0 + ); + } catch (\InvalidArgumentException $e) { + throw new ParseException( + $e->getMessage(), + $this + ); + } + } + + public function xml(array $config = []) + { + $disableEntities = libxml_disable_entity_loader(true); + $internalErrors = libxml_use_internal_errors(true); + + try { + // Allow XML to be retrieved even if there is no response body + $xml = new \SimpleXMLElement( + (string) $this->getBody() ?: '', + isset($config['libxml_options']) ? $config['libxml_options'] : LIBXML_NONET, + false, + isset($config['ns']) ? $config['ns'] : '', + isset($config['ns_is_prefix']) ? $config['ns_is_prefix'] : false + ); + libxml_disable_entity_loader($disableEntities); + libxml_use_internal_errors($internalErrors); + } catch (\Exception $e) { + libxml_disable_entity_loader($disableEntities); + libxml_use_internal_errors($internalErrors); + throw new XmlParseException( + 'Unable to parse response body into XML: ' . $e->getMessage(), + $this, + $e, + (libxml_get_last_error()) ?: null + ); + } + + return $xml; + } + + public function getEffectiveUrl() + { + return $this->effectiveUrl; + } + + public function setEffectiveUrl($url) + { + $this->effectiveUrl = $url; + } + + /** + * Accepts and modifies the options provided to the response in the + * constructor. + * + * @param array $options Options array passed by reference. + */ + protected function handleOptions(array &$options = []) + { + parent::handleOptions($options); + if (isset($options['reason_phrase'])) { + $this->reasonPhrase = $options['reason_phrase']; + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/ResponseInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/ResponseInterface.php new file mode 100644 index 0000000..c0ae9be --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Message/ResponseInterface.php @@ -0,0 +1,111 @@ + 'text/vnd.in3d.3dml', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/pkix-attr-cert', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'apk' => 'application/vnd.android.package-archive', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'asa' => 'text/plain', + 'asax' => 'application/octet-stream', + 'asc' => 'application/pgp-signature', + 'ascx' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'ashx' => 'text/plain', + 'asm' => 'text/x-asm', + 'asmx' => 'text/plain', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asp' => 'text/plain', + 'aspx' => 'text/plain', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'aw' => 'application/applixware', + 'axd' => 'text/plain', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azw' => 'application/vnd.amazon.ebook', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'bmi' => 'application/vnd.bmi', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'btif' => 'image/prs.btif', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'cab' => 'application/vnd.ms-cab-compressed', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cc' => 'text/x-c', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfc' => 'application/x-coldfusion', + 'cfm' => 'application/x-coldfusion', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/java-vm', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'cs' => 'text/plain', + 'csh' => 'application/x-csh', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/x-msdownload', + 'dmg' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.document.macroenabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'exe' => 'application/x-msdownload', + 'exi' => 'application/exi', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/x-f4v', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gdl' => 'model/vnd.gdl', + 'geo' => 'application/vnd.dynageo', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gph' => 'application/vnd.flographit', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxt' => 'application/vnd.geonext', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/x-c', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hta' => 'application/octet-stream', + 'htc' => 'text/html', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/octet-stream', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'java' => 'text/x-java-source', + 'jisp' => 'application/vnd.jisp', + 'jlt' => 'application/vnd.hp-jlyt', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', + 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/mp4', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msty' => 'application/vnd.muvee.style', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nsf' => 'application/vnd.lotus-notes', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'oprc' => 'application/vnd.palm', + 'org' => 'application/vnd.lotus-organizer', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'application/x-font-otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/vnd.palm', + 'pdf' => 'application/pdf', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp-encrypted', + 'php' => 'text/x-php', + 'phps' => 'application/x-httpd-phps', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-mobipocket-ebook', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'image/vnd.adobe.photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rb' => 'text/plain', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'resx' => 'text/xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rnc' => 'application/relax-ng-compact-syntax', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'rtx' => 'text/richtext', + 's' => 'text/x-asm', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shf' => 'application/shf+xml', + 'sig' => 'application/pgp-signature', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'src' => 'application/x-wais-source', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'application/vnd.ms-pki.stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sub' => 'image/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tmo' => 'application/vnd.tmobile-livetv', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'application/x-font-ttf', + 'ttf' => 'application/x-font-ttf', + 'ttl' => 'text/turtle', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvx' => 'application/vnd.dece.unspecified', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'application/x-msmetafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-ms-wmz', + 'woff' => 'application/x-font-woff', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', + 'x3d' => 'application/vnd.hzn-3d-crossword', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xml' => 'application/xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml' + ); + + /** + * Get a singleton instance of the class + * + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Get a mimetype value from a file extension + * + * @param string $extension File extension + * + * @return string|null + * + */ + public function fromExtension($extension) + { + $extension = strtolower($extension); + + return isset($this->mimetypes[$extension]) + ? $this->mimetypes[$extension] + : null; + } + + /** + * Get a mimetype from a filename + * + * @param string $filename Filename to generate a mimetype from + * + * @return string|null + */ + public function fromFilename($filename) + { + return $this->fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Pool.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 0000000..7b9d83a --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,333 @@ +client = $client; + $this->iter = $this->coerceIterable($requests); + $this->deferred = new Deferred(); + $this->promise = $this->deferred->promise(); + $this->poolSize = isset($options['pool_size']) + ? $options['pool_size'] : 25; + $this->eventListeners = $this->prepareListeners( + $options, + ['before', 'complete', 'error', 'end'] + ); + } + + /** + * Sends multiple requests in parallel and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send in parallel + * @param array $options Passes through the options available in + * {@see GuzzleHttp\Pool::__construct} + * + * @return BatchResults Returns a container for the results. + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch( + ClientInterface $client, + $requests, + array $options = [] + ) { + $hash = new \SplObjectStorage(); + foreach ($requests as $request) { + $hash->attach($request); + } + + // In addition to the normally run events when requests complete, add + // and event to continuously track the results of transfers in the hash. + (new self($client, $requests, RequestEvents::convertEventArray( + $options, + ['end'], + [ + 'priority' => RequestEvents::LATE, + 'fn' => function (EndEvent $e) use ($hash) { + $hash[$e->getRequest()] = $e->getException() + ? $e->getException() + : $e->getResponse(); + } + ] + )))->wait(); + + return new BatchResults($hash); + } + + /** + * Creates a Pool and immediately sends the requests. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send in parallel + * @param array $options Passes through the options available in + * {@see GuzzleHttp\Pool::__construct} + */ + public static function send( + ClientInterface $client, + $requests, + array $options = [] + ) { + $pool = new self($client, $requests, $options); + $pool->wait(); + } + + private function getPoolSize() + { + return is_callable($this->poolSize) + ? call_user_func($this->poolSize, count($this->waitQueue)) + : $this->poolSize; + } + + /** + * Add as many requests as possible up to the current pool limit. + */ + private function addNextRequests() + { + $limit = max($this->getPoolSize() - count($this->waitQueue), 0); + while ($limit--) { + if (!$this->addNextRequest()) { + break; + } + } + } + + public function wait() + { + if ($this->isRealized) { + return false; + } + + // Seed the pool with N number of requests. + $this->addNextRequests(); + + // Stop if the pool was cancelled while transferring requests. + if ($this->isRealized) { + return false; + } + + // Wait on any outstanding FutureResponse objects. + while ($response = array_pop($this->waitQueue)) { + try { + $response->wait(); + } catch (\Exception $e) { + // Eat exceptions because they should be handled asynchronously + } + $this->addNextRequests(); + } + + // Clean up no longer needed state. + $this->isRealized = true; + $this->waitQueue = $this->eventListeners = []; + $this->client = $this->iter = null; + $this->deferred->resolve(true); + + return true; + } + + /** + * {@inheritdoc} + * + * Attempt to cancel all outstanding requests (requests that are queued for + * dereferencing). Returns true if all outstanding requests can be + * cancelled. + * + * @return bool + */ + public function cancel() + { + if ($this->isRealized) { + return false; + } + + $success = $this->isRealized = true; + foreach ($this->waitQueue as $response) { + if (!$response->cancel()) { + $success = false; + } + } + + return $success; + } + + /** + * Returns a promise that is invoked when the pool completed. There will be + * no passed value. + * + * {@inheritdoc} + */ + public function then( + callable $onFulfilled = null, + callable $onRejected = null, + callable $onProgress = null + ) { + return $this->promise->then($onFulfilled, $onRejected, $onProgress); + } + + public function promise() + { + return $this->promise; + } + + private function coerceIterable($requests) + { + if ($requests instanceof \Iterator) { + return $requests; + } elseif (is_array($requests)) { + return new \ArrayIterator($requests); + } + + throw new \InvalidArgumentException('Expected Iterator or array. ' + . 'Found ' . Core::describeType($requests)); + } + + /** + * Adds the next request to pool and tracks what requests need to be + * dereferenced when completing the pool. + */ + private function addNextRequest() + { + add_next: + + if ($this->isRealized || !$this->iter || !$this->iter->valid()) { + return false; + } + + $request = $this->iter->current(); + $this->iter->next(); + + if (!($request instanceof RequestInterface)) { + throw new \InvalidArgumentException(sprintf( + 'All requests in the provided iterator must implement ' + . 'RequestInterface. Found %s', + Core::describeType($request) + )); + } + + // Be sure to use "lazy" futures, meaning they do not send right away. + $request->getConfig()->set('future', 'lazy'); + $hash = spl_object_hash($request); + $this->attachListeners($request, $this->eventListeners); + $request->getEmitter()->on('before', [$this, '_trackRetries'], RequestEvents::EARLY); + $response = $this->client->send($request); + $this->waitQueue[$hash] = $response; + $promise = $response->promise(); + + // Don't recursively call itself for completed or rejected responses. + if ($promise instanceof FulfilledPromise + || $promise instanceof RejectedPromise + ) { + try { + $this->finishResponse($request, $response->wait(), $hash); + } catch (\Exception $e) { + $this->finishResponse($request, $e, $hash); + } + goto add_next; + } + + // Use this function for both resolution and rejection. + $thenFn = function ($value) use ($request, $hash) { + $this->finishResponse($request, $value, $hash); + if (!$request->getConfig()->get('_pool_retries')) { + $this->addNextRequests(); + } + }; + + $promise->then($thenFn, $thenFn); + + return true; + } + + public function _trackRetries(BeforeEvent $e) + { + $e->getRequest()->getConfig()->set('_pool_retries', $e->getRetryCount()); + } + + private function finishResponse($request, $value, $hash) + { + unset($this->waitQueue[$hash]); + $result = $value instanceof ResponseInterface + ? ['request' => $request, 'response' => $value, 'error' => null] + : ['request' => $request, 'response' => null, 'error' => $value]; + $this->deferred->notify($result); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/MultipartBody.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/MultipartBody.php new file mode 100644 index 0000000..1149e62 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/MultipartBody.php @@ -0,0 +1,109 @@ +boundary = $boundary ?: uniqid(); + $this->stream = $this->createStream($fields, $files); + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + public function isWritable() + { + return false; + } + + /** + * Get the string needed to transfer a POST field + */ + private function getFieldString($name, $value) + { + return sprintf( + "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", + $this->boundary, + $name, + $value + ); + } + + /** + * Get the headers needed before transferring the content of a POST file + */ + private function getFileHeaders(PostFileInterface $file) + { + $headers = ''; + foreach ($file->getHeaders() as $key => $value) { + $headers .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($headers) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $fields, array $files) + { + $stream = new AppendStream(); + + foreach ($fields as $name => $fieldValues) { + foreach ((array) $fieldValues as $value) { + $stream->addStream( + Stream::factory($this->getFieldString($name, $value)) + ); + } + } + + foreach ($files as $file) { + + if (!$file instanceof PostFileInterface) { + throw new \InvalidArgumentException('All POST fields must ' + . 'implement PostFieldInterface'); + } + + $stream->addStream( + Stream::factory($this->getFileHeaders($file)) + ); + $stream->addStream($file->getContent()); + $stream->addStream(Stream::factory("\r\n")); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Stream::factory("--{$this->boundary}--\r\n")); + + return $stream; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostBody.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostBody.php new file mode 100644 index 0000000..ed14d1f --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostBody.php @@ -0,0 +1,287 @@ +files || $this->forceMultipart) { + $request->setHeader( + 'Content-Type', + 'multipart/form-data; boundary=' . $this->getBody()->getBoundary() + ); + } elseif ($this->fields && !$request->hasHeader('Content-Type')) { + $request->setHeader( + 'Content-Type', + 'application/x-www-form-urlencoded' + ); + } + + if ($size = $this->getSize()) { + $request->setHeader('Content-Length', $size); + } + } + + public function forceMultipartUpload($force) + { + $this->forceMultipart = $force; + } + + public function setAggregator(callable $aggregator) + { + $this->aggregator = $aggregator; + } + + public function setField($name, $value) + { + $this->fields[$name] = $value; + $this->mutate(); + } + + public function replaceFields(array $fields) + { + $this->fields = $fields; + $this->mutate(); + } + + public function getField($name) + { + return isset($this->fields[$name]) ? $this->fields[$name] : null; + } + + public function removeField($name) + { + unset($this->fields[$name]); + $this->mutate(); + } + + public function getFields($asString = false) + { + if (!$asString) { + return $this->fields; + } + + $query = new Query($this->fields); + $query->setEncodingType(Query::RFC1738); + $query->setAggregator($this->getAggregator()); + + return (string) $query; + } + + public function hasField($name) + { + return isset($this->fields[$name]); + } + + public function getFile($name) + { + foreach ($this->files as $file) { + if ($file->getName() == $name) { + return $file; + } + } + + return null; + } + + public function getFiles() + { + return $this->files; + } + + public function addFile(PostFileInterface $file) + { + $this->files[] = $file; + $this->mutate(); + } + + public function clearFiles() + { + $this->files = []; + $this->mutate(); + } + + /** + * Returns the numbers of fields + files + * + * @return int + */ + public function count() + { + return count($this->files) + count($this->fields); + } + + public function __toString() + { + return (string) $this->getBody(); + } + + public function getContents($maxLength = -1) + { + return $this->getBody()->getContents(); + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->detached = true; + $this->fields = $this->files = []; + + if ($this->body) { + $this->body->close(); + $this->body = null; + } + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function eof() + { + return $this->getBody()->eof(); + } + + public function tell() + { + return $this->body ? $this->body->tell() : 0; + } + + public function isSeekable() + { + return true; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function getSize() + { + return $this->getBody()->getSize(); + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->getBody()->seek($offset, $whence); + } + + public function read($length) + { + return $this->getBody()->read($length); + } + + public function write($string) + { + return false; + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } + + /** + * Return a stream object that is built from the POST fields and files. + * + * If one has already been created, the previously created stream will be + * returned. + */ + private function getBody() + { + if ($this->body) { + return $this->body; + } elseif ($this->files || $this->forceMultipart) { + return $this->body = $this->createMultipart(); + } elseif ($this->fields) { + return $this->body = $this->createUrlEncoded(); + } else { + return $this->body = Stream::factory(); + } + } + + /** + * Get the aggregator used to join multi-valued field parameters + * + * @return callable + */ + final protected function getAggregator() + { + if (!$this->aggregator) { + $this->aggregator = Query::phpAggregator(); + } + + return $this->aggregator; + } + + /** + * Creates a multipart/form-data body stream + * + * @return MultipartBody + */ + private function createMultipart() + { + // Flatten the nested query string values using the correct aggregator + return new MultipartBody( + call_user_func($this->getAggregator(), $this->fields), + $this->files + ); + } + + /** + * Creates an application/x-www-form-urlencoded stream body + * + * @return StreamInterface + */ + private function createUrlEncoded() + { + return Stream::factory($this->getFields(true)); + } + + /** + * Get rid of any cached data + */ + private function mutate() + { + $this->body = null; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php new file mode 100644 index 0000000..c2ec9a6 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php @@ -0,0 +1,109 @@ +headers = $headers; + $this->name = $name; + $this->prepareContent($content); + $this->prepareFilename($filename); + $this->prepareDefaultHeaders(); + } + + public function getName() + { + return $this->name; + } + + public function getFilename() + { + return $this->filename; + } + + public function getContent() + { + return $this->content; + } + + public function getHeaders() + { + return $this->headers; + } + + /** + * Prepares the contents of a POST file. + * + * @param mixed $content Content of the POST file + */ + private function prepareContent($content) + { + $this->content = $content; + + if (!($this->content instanceof StreamInterface)) { + $this->content = Stream::factory($this->content); + } elseif ($this->content instanceof MultipartBody) { + if (!$this->hasHeader('Content-Disposition')) { + $disposition = 'form-data; name="' . $this->name .'"'; + $this->headers['Content-Disposition'] = $disposition; + } + + if (!$this->hasHeader('Content-Type')) { + $this->headers['Content-Type'] = sprintf( + "multipart/form-data; boundary=%s", + $this->content->getBoundary() + ); + } + } + } + + /** + * Applies a file name to the POST file based on various checks. + * + * @param string|null $filename Filename to apply (or null to guess) + */ + private function prepareFilename($filename) + { + $this->filename = $filename; + + if (!$this->filename) { + $this->filename = $this->content->getMetadata('uri'); + } + + if (!$this->filename || substr($this->filename, 0, 6) === 'php://') { + $this->filename = $this->name; + } + } + + /** + * Applies default Content-Disposition and Content-Type headers if needed. + */ + private function prepareDefaultHeaders() + { + // Set a default content-disposition header if one was no provided + if (!$this->hasHeader('Content-Disposition')) { + $this->headers['Content-Disposition'] = sprintf( + 'form-data; name="%s"; filename="%s"', + $this->name, + basename($this->filename) + ); + } + + // Set a default Content-Type if one was not supplied + if (!$this->hasHeader('Content-Type')) { + $this->headers['Content-Type'] = Mimetypes::getInstance() + ->fromFilename($this->filename) ?: 'text/plain'; + } + } + + /** + * Check if a specific header exists on the POST file by name. + * + * @param string $name Case-insensitive header to check + * + * @return bool + */ + private function hasHeader($name) + { + return isset(array_change_key_case($this->headers)[strtolower($name)]); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostFileInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostFileInterface.php new file mode 100644 index 0000000..2e816c0 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Post/PostFileInterface.php @@ -0,0 +1,41 @@ +setEncodingType($urlEncoding); + } + + $qp->parseInto($q, $query, $urlEncoding); + + return $q; + } + + /** + * Convert the query string parameters to a query string string + * + * @return string + */ + public function __toString() + { + if (!$this->data) { + return ''; + } + + // The default aggregator is statically cached + static $defaultAggregator; + + if (!$this->aggregator) { + if (!$defaultAggregator) { + $defaultAggregator = self::phpAggregator(); + } + $this->aggregator = $defaultAggregator; + } + + $result = ''; + $aggregator = $this->aggregator; + $encoder = $this->encoding; + + foreach ($aggregator($this->data) as $key => $values) { + foreach ($values as $value) { + if ($result) { + $result .= '&'; + } + $result .= $encoder($key); + if ($value !== null) { + $result .= '=' . $encoder($value); + } + } + } + + return $result; + } + + /** + * Controls how multi-valued query string parameters are aggregated into a + * string. + * + * $query->setAggregator($query::duplicateAggregator()); + * + * @param callable $aggregator Callable used to convert a deeply nested + * array of query string variables into a flattened array of key value + * pairs. The callable accepts an array of query data and returns a + * flattened array of key value pairs where each value is an array of + * strings. + */ + public function setAggregator(callable $aggregator) + { + $this->aggregator = $aggregator; + } + + /** + * Specify how values are URL encoded + * + * @param string|bool $type One of 'RFC1738', 'RFC3986', or false to disable encoding + * + * @throws \InvalidArgumentException + */ + public function setEncodingType($type) + { + switch ($type) { + case self::RFC3986: + $this->encoding = 'rawurlencode'; + break; + case self::RFC1738: + $this->encoding = 'urlencode'; + break; + case false: + $this->encoding = function ($v) { return $v; }; + break; + default: + throw new \InvalidArgumentException('Invalid URL encoding type'); + } + } + + /** + * Query string aggregator that does not aggregate nested query string + * values and allows duplicates in the resulting array. + * + * Example: http://test.com?q=1&q=2 + * + * @return callable + */ + public static function duplicateAggregator() + { + return function (array $data) { + return self::walkQuery($data, '', function ($key, $prefix) { + return is_int($key) ? $prefix : "{$prefix}[{$key}]"; + }); + }; + } + + /** + * Aggregates nested query string variables using the same technique as + * ``http_build_query()``. + * + * @param bool $numericIndices Pass false to not include numeric indices + * when multi-values query string parameters are present. + * + * @return callable + */ + public static function phpAggregator($numericIndices = true) + { + return function (array $data) use ($numericIndices) { + return self::walkQuery( + $data, + '', + function ($key, $prefix) use ($numericIndices) { + return !$numericIndices && is_int($key) + ? "{$prefix}[]" + : "{$prefix}[{$key}]"; + } + ); + }; + } + + /** + * Easily create query aggregation functions by providing a key prefix + * function to this query string array walker. + * + * @param array $query Query string to walk + * @param string $keyPrefix Key prefix (start with '') + * @param callable $prefixer Function used to create a key prefix + * + * @return array + */ + public static function walkQuery(array $query, $keyPrefix, callable $prefixer) + { + $result = []; + foreach ($query as $key => $value) { + if ($keyPrefix) { + $key = $prefixer($key, $keyPrefix); + } + if (is_array($value)) { + $result += self::walkQuery($value, $key, $prefixer); + } elseif (isset($result[$key])) { + $result[$key][] = $value; + } else { + $result[$key] = array($value); + } + } + + return $result; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/QueryParser.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/QueryParser.php new file mode 100644 index 0000000..90727cc --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/QueryParser.php @@ -0,0 +1,163 @@ +duplicates = false; + $this->numericIndices = true; + $decoder = self::getDecoder($urlEncoding); + + foreach (explode('&', $str) as $kvp) { + + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + + // Special handling needs to be taken for PHP nested array syntax + if (strpos($key, '[') !== false) { + $this->parsePhpValue($key, $value, $result); + continue; + } + + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + $this->duplicates = true; + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + $query->replace($result); + + if (!$this->numericIndices) { + $query->setAggregator(Query::phpAggregator(false)); + } elseif ($this->duplicates) { + $query->setAggregator(Query::duplicateAggregator()); + } + } + + /** + * Returns a callable that is used to URL decode query keys and values. + * + * @param string|bool $type One of true, false, RFC3986, and RFC1738 + * + * @return callable|string + */ + private static function getDecoder($type) + { + if ($type === true) { + return function ($value) { + return rawurldecode(str_replace('+', ' ', $value)); + }; + } elseif ($type == Query::RFC3986) { + return 'rawurldecode'; + } elseif ($type == Query::RFC1738) { + return 'urldecode'; + } else { + return function ($str) { return $str; }; + } + } + + /** + * Parses a PHP style key value pair. + * + * @param string $key Key to parse (e.g., "foo[a][b]") + * @param string|null $value Value to set + * @param array $result Result to modify by reference + */ + private function parsePhpValue($key, $value, array &$result) + { + $node =& $result; + $keyBuffer = ''; + + for ($i = 0, $t = strlen($key); $i < $t; $i++) { + switch ($key[$i]) { + case '[': + if ($keyBuffer) { + $this->prepareNode($node, $keyBuffer); + $node =& $node[$keyBuffer]; + $keyBuffer = ''; + } + break; + case ']': + $k = $this->cleanKey($node, $keyBuffer); + $this->prepareNode($node, $k); + $node =& $node[$k]; + $keyBuffer = ''; + break; + default: + $keyBuffer .= $key[$i]; + break; + } + } + + if (isset($node)) { + $this->duplicates = true; + $node[] = $value; + } else { + $node = $value; + } + } + + /** + * Prepares a value in the array at the given key. + * + * If the key already exists, the key value is converted into an array. + * + * @param array $node Result node to modify + * @param string $key Key to add or modify in the node + */ + private function prepareNode(&$node, $key) + { + if (!isset($node[$key])) { + $node[$key] = null; + } elseif (!is_array($node[$key])) { + $node[$key] = [$node[$key]]; + } + } + + /** + * Returns the appropriate key based on the node and key. + */ + private function cleanKey($node, $key) + { + if ($key === '') { + $key = $node ? (string) count($node) : 0; + // Found a [] key, so track this to ensure that we disable numeric + // indexing of keys in the resolved query aggregator. + $this->numericIndices = false; + } + + return $key; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/RequestFsm.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/RequestFsm.php new file mode 100644 index 0000000..b37c190 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/RequestFsm.php @@ -0,0 +1,153 @@ +mf = $messageFactory; + $this->maxTransitions = $maxTransitions; + $this->handler = $handler; + } + + /** + * Runs the state machine until a terminal state is entered or the + * optionally supplied $finalState is entered. + * + * @param Transaction $trans Transaction being transitioned. + * + * @throws \Exception if a terminal state throws an exception. + */ + public function __invoke(Transaction $trans) + { + $trans->_transitionCount = 0; + + if (!$trans->state) { + $trans->state = 'before'; + } + + transition: + + if (++$trans->_transitionCount > $this->maxTransitions) { + throw new StateException("Too many state transitions were " + . "encountered ({$trans->_transitionCount}). This likely " + . "means that a combination of event listeners are in an " + . "infinite loop."); + } + + switch ($trans->state) { + case 'before': goto before; + case 'complete': goto complete; + case 'error': goto error; + case 'retry': goto retry; + case 'send': goto send; + case 'end': goto end; + default: throw new StateException("Invalid state: {$trans->state}"); + } + + before: { + try { + $trans->request->getEmitter()->emit('before', new BeforeEvent($trans)); + $trans->state = 'send'; + if ((bool) $trans->response) { + $trans->state = 'complete'; + } + } catch (\Exception $e) { + $trans->state = 'error'; + $trans->exception = $e; + } + goto transition; + } + + complete: { + try { + if ($trans->response instanceof FutureInterface) { + // Futures will have their own end events emitted when + // dereferenced. + return; + } + $trans->state = 'end'; + $trans->response->setEffectiveUrl($trans->request->getUrl()); + $trans->request->getEmitter()->emit('complete', new CompleteEvent($trans)); + } catch (\Exception $e) { + $trans->state = 'error'; + $trans->exception = $e; + } + goto transition; + } + + error: { + try { + // Convert non-request exception to a wrapped exception + $trans->exception = RequestException::wrapException( + $trans->request, $trans->exception + ); + $trans->state = 'end'; + $trans->request->getEmitter()->emit('error', new ErrorEvent($trans)); + // An intercepted request (not retried) transitions to complete + if (!$trans->exception && $trans->state !== 'retry') { + $trans->state = 'complete'; + } + } catch (\Exception $e) { + $trans->state = 'end'; + $trans->exception = $e; + } + goto transition; + } + + retry: { + $trans->retries++; + $trans->response = null; + $trans->exception = null; + $trans->state = 'before'; + goto transition; + } + + send: { + $fn = $this->handler; + $trans->response = FutureResponse::proxy( + $fn(RingBridge::prepareRingRequest($trans)), + function ($value) use ($trans) { + RingBridge::completeRingResponse($trans, $value, $this->mf, $this); + $this($trans); + return $trans->response; + } + ); + return; + } + + end: { + $trans->request->getEmitter()->emit('end', new EndEvent($trans)); + // Throw exceptions in the terminal event if the exception + // was not handled by an "end" event listener. + if ($trans->exception) { + if (!($trans->exception instanceof RequestException)) { + $trans->exception = RequestException::wrapException( + $trans->request, $trans->exception + ); + } + throw $trans->exception; + } + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/RingBridge.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/RingBridge.php new file mode 100644 index 0000000..bc6841d --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/RingBridge.php @@ -0,0 +1,165 @@ +getConfig()->toArray(); + $url = $request->getUrl(); + // No need to calculate the query string twice (in URL and query). + $qs = ($pos = strpos($url, '?')) ? substr($url, $pos + 1) : null; + + return [ + 'scheme' => $request->getScheme(), + 'http_method' => $request->getMethod(), + 'url' => $url, + 'uri' => $request->getPath(), + 'headers' => $request->getHeaders(), + 'body' => $request->getBody(), + 'version' => $request->getProtocolVersion(), + 'client' => $options, + 'query_string' => $qs, + 'future' => isset($options['future']) ? $options['future'] : false + ]; + } + + /** + * Creates a Ring request from a request object AND prepares the callbacks. + * + * @param Transaction $trans Transaction to update. + * + * @return array Converted Guzzle Ring request. + */ + public static function prepareRingRequest(Transaction $trans) + { + // Clear out the transaction state when initiating. + $trans->exception = null; + $request = self::createRingRequest($trans->request); + + // Emit progress events if any progress listeners are registered. + if ($trans->request->getEmitter()->hasListeners('progress')) { + $emitter = $trans->request->getEmitter(); + $request['client']['progress'] = function ($a, $b, $c, $d) use ($trans, $emitter) { + $emitter->emit('progress', new ProgressEvent($trans, $a, $b, $c, $d)); + }; + } + + return $request; + } + + /** + * Handles the process of processing a response received from a ring + * handler. The created response is added to the transaction, and the + * transaction stat is set appropriately. + * + * @param Transaction $trans Owns request and response. + * @param array $response Ring response array + * @param MessageFactoryInterface $messageFactory Creates response objects. + */ + public static function completeRingResponse( + Transaction $trans, + array $response, + MessageFactoryInterface $messageFactory + ) { + $trans->state = 'complete'; + $trans->transferInfo = isset($response['transfer_stats']) + ? $response['transfer_stats'] : []; + + if (!empty($response['status'])) { + $options = []; + if (isset($response['version'])) { + $options['protocol_version'] = $response['version']; + } + if (isset($response['reason'])) { + $options['reason_phrase'] = $response['reason']; + } + $trans->response = $messageFactory->createResponse( + $response['status'], + isset($response['headers']) ? $response['headers'] : [], + isset($response['body']) ? $response['body'] : null, + $options + ); + if (isset($response['effective_url'])) { + $trans->response->setEffectiveUrl($response['effective_url']); + } + } elseif (empty($response['error'])) { + // When nothing was returned, then we need to add an error. + $response['error'] = self::getNoRingResponseException($trans->request); + } + + if (isset($response['error'])) { + $trans->state = 'error'; + $trans->exception = $response['error']; + } + } + + /** + * Creates a Guzzle request object using a ring request array. + * + * @param array $request Ring request + * + * @return Request + * @throws \InvalidArgumentException for incomplete requests. + */ + public static function fromRingRequest(array $request) + { + $options = []; + if (isset($request['version'])) { + $options['protocol_version'] = $request['version']; + } + + if (!isset($request['http_method'])) { + throw new \InvalidArgumentException('No http_method'); + } + + return new Request( + $request['http_method'], + Core::url($request), + isset($request['headers']) ? $request['headers'] : [], + isset($request['body']) ? Stream::factory($request['body']) : null, + $options + ); + } + + /** + * Get an exception that can be used when a RingPHP handler does not + * populate a response. + * + * @param RequestInterface $request + * + * @return RequestException + */ + public static function getNoRingResponseException(RequestInterface $request) + { + $message = <<cookieJar = $cookieJar ?: new CookieJar(); + } + + public function getEvents() + { + // Fire the cookie plugin complete event before redirecting + return [ + 'before' => ['onBefore'], + 'complete' => ['onComplete', RequestEvents::REDIRECT_RESPONSE + 10] + ]; + } + + /** + * Get the cookie cookieJar + * + * @return CookieJarInterface + */ + public function getCookieJar() + { + return $this->cookieJar; + } + + public function onBefore(BeforeEvent $event) + { + $this->cookieJar->addCookieHeader($event->getRequest()); + } + + public function onComplete(CompleteEvent $event) + { + $this->cookieJar->extractCookies( + $event->getRequest(), + $event->getResponse() + ); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/History.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/History.php new file mode 100644 index 0000000..5cf0611 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/History.php @@ -0,0 +1,172 @@ +limit = $limit; + } + + public function getEvents() + { + return [ + 'complete' => ['onComplete', RequestEvents::EARLY], + 'error' => ['onError', RequestEvents::EARLY], + ]; + } + + /** + * Convert to a string that contains all request and response headers + * + * @return string + */ + public function __toString() + { + $lines = array(); + foreach ($this->transactions as $entry) { + $response = isset($entry['response']) ? $entry['response'] : ''; + $lines[] = '> ' . trim($entry['sent_request']) + . "\n\n< " . trim($response) . "\n"; + } + + return implode("\n", $lines); + } + + public function onComplete(CompleteEvent $event) + { + $this->add($event->getRequest(), $event->getResponse()); + } + + public function onError(ErrorEvent $event) + { + // Only track when no response is present, meaning this didn't ever + // emit a complete event + if (!$event->getResponse()) { + $this->add($event->getRequest()); + } + } + + /** + * Returns an Iterator that yields associative array values where each + * associative array contains the following key value pairs: + * + * - request: Representing the actual request that was received. + * - sent_request: A clone of the request that will not be mutated. + * - response: The response that was received (if available). + * + * @return \Iterator + */ + public function getIterator() + { + return new \ArrayIterator($this->transactions); + } + + /** + * Get all of the requests sent through the plugin. + * + * Requests can be modified after they are logged by the history + * subscriber. By default this method will return the actual request + * instances that were received. Pass true to this method if you wish to + * get copies of the requests that represent the request state when it was + * initially logged by the history subscriber. + * + * @param bool $asSent Set to true to get clones of the requests that have + * not been mutated since the request was received by + * the history subscriber. + * + * @return RequestInterface[] + */ + public function getRequests($asSent = false) + { + return array_map(function ($t) use ($asSent) { + return $asSent ? $t['sent_request'] : $t['request']; + }, $this->transactions); + } + + /** + * Get the number of requests in the history + * + * @return int + */ + public function count() + { + return count($this->transactions); + } + + /** + * Get the last request sent. + * + * Requests can be modified after they are logged by the history + * subscriber. By default this method will return the actual request + * instance that was received. Pass true to this method if you wish to get + * a copy of the request that represents the request state when it was + * initially logged by the history subscriber. + * + * @param bool $asSent Set to true to get a clone of the last request that + * has not been mutated since the request was received + * by the history subscriber. + * + * @return RequestInterface + */ + public function getLastRequest($asSent = false) + { + return $asSent + ? end($this->transactions)['sent_request'] + : end($this->transactions)['request']; + } + + /** + * Get the last response in the history + * + * @return ResponseInterface|null + */ + public function getLastResponse() + { + return end($this->transactions)['response']; + } + + /** + * Clears the history + */ + public function clear() + { + $this->transactions = array(); + } + + /** + * Add a request to the history + * + * @param RequestInterface $request Request to add + * @param ResponseInterface $response Response of the request + */ + private function add( + RequestInterface $request, + ResponseInterface $response = null + ) { + $this->transactions[] = [ + 'request' => $request, + 'sent_request' => clone $request, + 'response' => $response + ]; + if (count($this->transactions) > $this->limit) { + array_shift($this->transactions); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/HttpError.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/HttpError.php new file mode 100644 index 0000000..ed9de5b --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/HttpError.php @@ -0,0 +1,36 @@ + ['onComplete', RequestEvents::VERIFY_RESPONSE]]; + } + + /** + * Throw a RequestException on an HTTP protocol error + * + * @param CompleteEvent $event Emitted event + * @throws RequestException + */ + public function onComplete(CompleteEvent $event) + { + $code = (string) $event->getResponse()->getStatusCode(); + // Throw an exception for an unsuccessful response + if ($code[0] >= 4) { + throw RequestException::create( + $event->getRequest(), + $event->getResponse() + ); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Mock.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Mock.php new file mode 100644 index 0000000..2af4d37 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Mock.php @@ -0,0 +1,147 @@ +factory = new MessageFactory(); + $this->readBodies = $readBodies; + $this->addMultiple($items); + } + + public function getEvents() + { + // Fire the event last, after signing + return ['before' => ['onBefore', RequestEvents::SIGN_REQUEST - 10]]; + } + + /** + * @throws \OutOfBoundsException|\Exception + */ + public function onBefore(BeforeEvent $event) + { + if (!$item = array_shift($this->queue)) { + throw new \OutOfBoundsException('Mock queue is empty'); + } elseif ($item instanceof RequestException) { + throw $item; + } + + // Emulate reading a response body + $request = $event->getRequest(); + if ($this->readBodies && $request->getBody()) { + while (!$request->getBody()->eof()) { + $request->getBody()->read(8096); + } + } + + $saveTo = $event->getRequest()->getConfig()->get('save_to'); + + if (null !== $saveTo) { + $body = $item->getBody(); + + if (is_resource($saveTo)) { + fwrite($saveTo, $body); + } elseif (is_string($saveTo)) { + file_put_contents($saveTo, $body); + } elseif ($saveTo instanceof StreamInterface) { + $saveTo->write($body); + } + } + + $event->intercept($item); + } + + public function count() + { + return count($this->queue); + } + + /** + * Add a response to the end of the queue + * + * @param string|ResponseInterface $response Response or path to response file + * + * @return self + * @throws \InvalidArgumentException if a string or Response is not passed + */ + public function addResponse($response) + { + if (is_string($response)) { + $response = file_exists($response) + ? $this->factory->fromMessage(file_get_contents($response)) + : $this->factory->fromMessage($response); + } elseif (!($response instanceof ResponseInterface)) { + throw new \InvalidArgumentException('Response must a message ' + . 'string, response object, or path to a file'); + } + + $this->queue[] = $response; + + return $this; + } + + /** + * Add an exception to the end of the queue + * + * @param RequestException $e Exception to throw when the request is executed + * + * @return self + */ + public function addException(RequestException $e) + { + $this->queue[] = $e; + + return $this; + } + + /** + * Add multiple items to the queue + * + * @param array $items Items to add + */ + public function addMultiple(array $items) + { + foreach ($items as $item) { + if ($item instanceof RequestException) { + $this->addException($item); + } else { + $this->addResponse($item); + } + } + } + + /** + * Clear the queue + */ + public function clearQueue() + { + $this->queue = []; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php new file mode 100644 index 0000000..b5ed4e2 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php @@ -0,0 +1,130 @@ + ['onBefore', RequestEvents::PREPARE_REQUEST]]; + } + + public function onBefore(BeforeEvent $event) + { + $request = $event->getRequest(); + + // Set the appropriate Content-Type for a request if one is not set and + // there are form fields + if (!($body = $request->getBody())) { + return; + } + + $this->addContentLength($request, $body); + + if ($body instanceof AppliesHeadersInterface) { + // Synchronize the body with the request headers + $body->applyRequestHeaders($request); + } elseif (!$request->hasHeader('Content-Type')) { + $this->addContentType($request, $body); + } + + $this->addExpectHeader($request, $body); + } + + private function addContentType( + RequestInterface $request, + StreamInterface $body + ) { + if (!($uri = $body->getMetadata('uri'))) { + return; + } + + // Guess the content-type based on the stream's "uri" metadata value. + // The file extension is used to determine the appropriate mime-type. + if ($contentType = Mimetypes::getInstance()->fromFilename($uri)) { + $request->setHeader('Content-Type', $contentType); + } + } + + private function addContentLength( + RequestInterface $request, + StreamInterface $body + ) { + // Set the Content-Length header if it can be determined, and never + // send a Transfer-Encoding: chunked and Content-Length header in + // the same request. + if ($request->hasHeader('Content-Length')) { + // Remove transfer-encoding if content-length is set. + $request->removeHeader('Transfer-Encoding'); + return; + } + + if ($request->hasHeader('Transfer-Encoding')) { + return; + } + + if (null !== ($size = $body->getSize())) { + $request->setHeader('Content-Length', $size); + $request->removeHeader('Transfer-Encoding'); + } elseif ('1.1' == $request->getProtocolVersion()) { + // Use chunked Transfer-Encoding if there is no determinable + // content-length header and we're using HTTP/1.1. + $request->setHeader('Transfer-Encoding', 'chunked'); + $request->removeHeader('Content-Length'); + } + } + + private function addExpectHeader( + RequestInterface $request, + StreamInterface $body + ) { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = $request->getConfig()['expect']; + + // Return if disabled or if you're not using HTTP/1.1 + if ($expect === false || $request->getProtocolVersion() !== '1.1') { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $request->setHeader('Expect', '100-Continue'); + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $size = $body->getSize(); + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $request->setHeader('Expect', '100-Continue'); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php new file mode 100644 index 0000000..ff99226 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php @@ -0,0 +1,176 @@ + ['onComplete', RequestEvents::REDIRECT_RESPONSE]]; + } + + /** + * Rewind the entity body of the request if needed + * + * @param RequestInterface $redirectRequest + * @throws CouldNotRewindStreamException + */ + public static function rewindEntityBody(RequestInterface $redirectRequest) + { + // Rewind the entity body of the request if needed + if ($body = $redirectRequest->getBody()) { + // Only rewind the body if some of it has been read already, and + // throw an exception if the rewind fails + if ($body->tell() && !$body->seek(0)) { + throw new CouldNotRewindStreamException( + 'Unable to rewind the non-seekable request body after redirecting', + $redirectRequest + ); + } + } + } + + /** + * Called when a request receives a redirect response + * + * @param CompleteEvent $event Event emitted + * @throws TooManyRedirectsException + */ + public function onComplete(CompleteEvent $event) + { + $response = $event->getResponse(); + + if (substr($response->getStatusCode(), 0, 1) != '3' + || !$response->hasHeader('Location') + ) { + return; + } + + $request = $event->getRequest(); + $config = $request->getConfig(); + + // Increment the redirect and initialize the redirect state. + if ($redirectCount = $config['redirect_count']) { + $config['redirect_count'] = ++$redirectCount; + } else { + $config['redirect_scheme'] = $request->getScheme(); + $config['redirect_count'] = $redirectCount = 1; + } + + $max = $config->getPath('redirect/max') ?: 5; + + if ($redirectCount > $max) { + throw new TooManyRedirectsException( + "Will not follow more than {$redirectCount} redirects", + $request + ); + } + + $this->modifyRedirectRequest($request, $response); + $event->retry(); + } + + private function modifyRedirectRequest( + RequestInterface $request, + ResponseInterface $response + ) { + $config = $request->getConfig(); + $protocols = $config->getPath('redirect/protocols') ?: ['http', 'https']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || + ($statusCode <= 302 && $request->getBody() && !$config->getPath('redirect/strict')) + ) { + $request->setMethod('GET'); + $request->setBody(null); + } + + $previousUrl = $request->getUrl(); + $this->setRedirectUrl($request, $response, $protocols); + $this->rewindEntityBody($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($config->getPath('redirect/referer') + && ($request->getScheme() == 'https' || $request->getScheme() == $config['redirect_scheme']) + ) { + $url = Url::fromString($previousUrl); + $url->setUsername(null); + $url->setPassword(null); + $request->setHeader('Referer', (string) $url); + } else { + $request->removeHeader('Referer'); + } + } + + /** + * Set the appropriate URL on the request based on the location header + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array $protocols + */ + private function setRedirectUrl( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ) { + $location = $response->getHeader('Location'); + $location = Url::fromString($location); + + // Combine location with the original URL if it is not absolute. + if (!$location->isAbsolute()) { + $originalUrl = Url::fromString($request->getUrl()); + // Remove query string parameters and just take what is present on + // the redirect Location header + $originalUrl->getQuery()->clear(); + $location = $originalUrl->combine($location); + } + + // Ensure that the redirect URL is allowed based on the protocols. + if (!in_array($location->getScheme(), $protocols)) { + throw new BadResponseException( + sprintf( + 'Redirect URL, %s, does not use one of the allowed redirect protocols: %s', + $location, + implode(', ', $protocols) + ), + $request, + $response + ); + } + + $request->setUrl($location); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/ToArrayInterface.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/ToArrayInterface.php new file mode 100644 index 0000000..d57c022 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/ToArrayInterface.php @@ -0,0 +1,15 @@ +client = $client; + $this->request = $request; + $this->_future = $future; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/UriTemplate.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/UriTemplate.php new file mode 100644 index 0000000..55dfeb5 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/UriTemplate.php @@ -0,0 +1,241 @@ + array('prefix' => '', 'joiner' => ',', 'query' => false), + '+' => array('prefix' => '', 'joiner' => ',', 'query' => false), + '#' => array('prefix' => '#', 'joiner' => ',', 'query' => false), + '.' => array('prefix' => '.', 'joiner' => '.', 'query' => false), + '/' => array('prefix' => '/', 'joiner' => '/', 'query' => false), + ';' => array('prefix' => ';', 'joiner' => ';', 'query' => true), + '?' => array('prefix' => '?', 'joiner' => '&', 'query' => true), + '&' => array('prefix' => '&', 'joiner' => '&', 'query' => true) + ); + + /** @var array Delimiters */ + private static $delims = array(':', '/', '?', '#', '[', ']', '@', '!', '$', + '&', '\'', '(', ')', '*', '+', ',', ';', '='); + + /** @var array Percent encoded delimiters */ + private static $delimsPct = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D', + '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', + '%3B', '%3D'); + + public function expand($template, array $variables) + { + if (false === strpos($template, '{')) { + return $template; + } + + $this->template = $template; + $this->variables = $variables; + + return preg_replace_callback( + '/\{([^\}]+)\}/', + [$this, 'expandMatch'], + $this->template + ); + } + + /** + * Parse an expression into parts + * + * @param string $expression Expression to parse + * + * @return array Returns an associative array of parts + */ + private function parseExpression($expression) + { + $result = array(); + + if (isset(self::$operatorHash[$expression[0]])) { + $result['operator'] = $expression[0]; + $expression = substr($expression, 1); + } else { + $result['operator'] = ''; + } + + foreach (explode(',', $expression) as $value) { + $value = trim($value); + $varspec = array(); + if ($colonPos = strpos($value, ':')) { + $varspec['value'] = substr($value, 0, $colonPos); + $varspec['modifier'] = ':'; + $varspec['position'] = (int) substr($value, $colonPos + 1); + } elseif (substr($value, -1) == '*') { + $varspec['modifier'] = '*'; + $varspec['value'] = substr($value, 0, -1); + } else { + $varspec['value'] = (string) $value; + $varspec['modifier'] = ''; + } + $result['values'][] = $varspec; + } + + return $result; + } + + /** + * Process an expansion + * + * @param array $matches Matches met in the preg_replace_callback + * + * @return string Returns the replacement string + */ + private function expandMatch(array $matches) + { + static $rfc1738to3986 = array('+' => '%20', '%7e' => '~'); + + $replacements = array(); + $parsed = self::parseExpression($matches[1]); + $prefix = self::$operatorHash[$parsed['operator']]['prefix']; + $joiner = self::$operatorHash[$parsed['operator']]['joiner']; + $useQuery = self::$operatorHash[$parsed['operator']]['query']; + + foreach ($parsed['values'] as $value) { + + if (!isset($this->variables[$value['value']])) { + continue; + } + + $variable = $this->variables[$value['value']]; + $actuallyUseQuery = $useQuery; + $expanded = ''; + + if (is_array($variable)) { + + $isAssoc = $this->isAssoc($variable); + $kvp = array(); + foreach ($variable as $key => $var) { + + if ($isAssoc) { + $key = rawurlencode($key); + $isNestedArray = is_array($var); + } else { + $isNestedArray = false; + } + + if (!$isNestedArray) { + $var = rawurlencode($var); + if ($parsed['operator'] == '+' || + $parsed['operator'] == '#' + ) { + $var = $this->decodeReserved($var); + } + } + + if ($value['modifier'] == '*') { + if ($isAssoc) { + if ($isNestedArray) { + // Nested arrays must allow for deeply nested + // structures. + $var = strtr( + http_build_query([$key => $var]), + $rfc1738to3986 + ); + } else { + $var = $key . '=' . $var; + } + } elseif ($key > 0 && $actuallyUseQuery) { + $var = $value['value'] . '=' . $var; + } + } + + $kvp[$key] = $var; + } + + if (empty($variable)) { + $actuallyUseQuery = false; + } elseif ($value['modifier'] == '*') { + $expanded = implode($joiner, $kvp); + if ($isAssoc) { + // Don't prepend the value name when using the explode + // modifier with an associative array. + $actuallyUseQuery = false; + } + } else { + if ($isAssoc) { + // When an associative array is encountered and the + // explode modifier is not set, then the result must be + // a comma separated list of keys followed by their + // respective values. + foreach ($kvp as $k => &$v) { + $v = $k . ',' . $v; + } + } + $expanded = implode(',', $kvp); + } + + } else { + if ($value['modifier'] == ':') { + $variable = substr($variable, 0, $value['position']); + } + $expanded = rawurlencode($variable); + if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { + $expanded = $this->decodeReserved($expanded); + } + } + + if ($actuallyUseQuery) { + if (!$expanded && $joiner != '&') { + $expanded = $value['value']; + } else { + $expanded = $value['value'] . '=' . $expanded; + } + } + + $replacements[] = $expanded; + } + + $ret = implode($joiner, $replacements); + if ($ret && $prefix) { + return $prefix . $ret; + } + + return $ret; + } + + /** + * Determines if an array is associative. + * + * This makes the assumption that input arrays are sequences or hashes. + * This assumption is a tradeoff for accuracy in favor of speed, but it + * should work in almost every case where input is supplied for a URI + * template. + * + * @param array $array Array to check + * + * @return bool + */ + private function isAssoc(array $array) + { + return $array && array_keys($array)[0] !== 0; + } + + /** + * Removes percent encoding on reserved characters (used with + and # + * modifiers). + * + * @param string $string String to fix + * + * @return string + */ + private function decodeReserved($string) + { + return str_replace(self::$delimsPct, self::$delims, $string); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Url.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Url.php new file mode 100644 index 0000000..637f60c --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Url.php @@ -0,0 +1,595 @@ + 80, 'https' => 443, 'ftp' => 21]; + private static $pathPattern = '/[^a-zA-Z0-9\-\._~!\$&\'\(\)\*\+,;=%:@\/]+|%(?![A-Fa-f0-9]{2})/'; + private static $queryPattern = '/[^a-zA-Z0-9\-\._~!\$\'\(\)\*\+,;%:@\/\?=&]+|%(?![A-Fa-f0-9]{2})/'; + /** @var Query|string Query part of the URL */ + private $query; + + /** + * Factory method to create a new URL from a URL string + * + * @param string $url Full URL used to create a Url object + * + * @return Url + * @throws \InvalidArgumentException + */ + public static function fromString($url) + { + static $defaults = ['scheme' => null, 'host' => null, + 'path' => null, 'port' => null, 'query' => null, + 'user' => null, 'pass' => null, 'fragment' => null]; + + if (false === ($parts = parse_url($url))) { + throw new \InvalidArgumentException('Unable to parse malformed ' + . 'url: ' . $url); + } + + $parts += $defaults; + + // Convert the query string into a Query object + if ($parts['query'] || 0 !== strlen($parts['query'])) { + $parts['query'] = Query::fromString($parts['query']); + } + + return new static($parts['scheme'], $parts['host'], $parts['user'], + $parts['pass'], $parts['port'], $parts['path'], $parts['query'], + $parts['fragment']); + } + + /** + * Build a URL from parse_url parts. The generated URL will be a relative + * URL if a scheme or host are not provided. + * + * @param array $parts Array of parse_url parts + * + * @return string + */ + public static function buildUrl(array $parts) + { + $url = $scheme = ''; + + if (!empty($parts['scheme'])) { + $scheme = $parts['scheme']; + $url .= $scheme . ':'; + } + + if (!empty($parts['host'])) { + $url .= '//'; + if (isset($parts['user'])) { + $url .= $parts['user']; + if (isset($parts['pass'])) { + $url .= ':' . $parts['pass']; + } + $url .= '@'; + } + + $url .= $parts['host']; + + // Only include the port if it is not the default port of the scheme + if (isset($parts['port']) && + (!isset(self::$defaultPorts[$scheme]) || + $parts['port'] != self::$defaultPorts[$scheme]) + ) { + $url .= ':' . $parts['port']; + } + } + + // Add the path component if present + if (isset($parts['path']) && strlen($parts['path'])) { + // Always ensure that the path begins with '/' if set and something + // is before the path + if (!empty($parts['host']) && $parts['path'][0] != '/') { + $url .= '/'; + } + $url .= $parts['path']; + } + + // Add the query string if present + if (isset($parts['query'])) { + $queryStr = (string) $parts['query']; + if ($queryStr || $queryStr === '0') { + $url .= '?' . $queryStr; + } + } + + // Ensure that # is only added to the url if fragment contains anything. + if (isset($parts['fragment'])) { + $url .= '#' . $parts['fragment']; + } + + return $url; + } + + /** + * Create a new URL from URL parts + * + * @param string $scheme Scheme of the URL + * @param string $host Host of the URL + * @param string $username Username of the URL + * @param string $password Password of the URL + * @param int $port Port of the URL + * @param string $path Path of the URL + * @param Query|array|string $query Query string of the URL + * @param string $fragment Fragment of the URL + */ + public function __construct( + $scheme, + $host, + $username = null, + $password = null, + $port = null, + $path = null, + $query = null, + $fragment = null + ) { + $this->scheme = strtolower($scheme); + $this->host = $host; + $this->port = $port; + $this->username = $username; + $this->password = $password; + $this->fragment = $fragment; + + if ($query) { + $this->setQuery($query); + } + + $this->setPath($path); + } + + /** + * Clone the URL + */ + public function __clone() + { + if ($this->query instanceof Query) { + $this->query = clone $this->query; + } + } + + /** + * Returns the URL as a URL string + * + * @return string + */ + public function __toString() + { + return static::buildUrl($this->getParts()); + } + + /** + * Get the parts of the URL as an array + * + * @return array + */ + public function getParts() + { + return array( + 'scheme' => $this->scheme, + 'user' => $this->username, + 'pass' => $this->password, + 'host' => $this->host, + 'port' => $this->port, + 'path' => $this->path, + 'query' => $this->query, + 'fragment' => $this->fragment, + ); + } + + /** + * Set the host of the request. + * + * @param string $host Host to set (e.g. www.yahoo.com, yahoo.com) + * + * @return Url + */ + public function setHost($host) + { + if (strpos($host, ':') === false) { + $this->host = $host; + } else { + list($host, $port) = explode(':', $host); + $this->host = $host; + $this->setPort($port); + } + } + + /** + * Get the host part of the URL + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the scheme part of the URL (http, https, ftp, etc.) + * + * @param string $scheme Scheme to set + */ + public function setScheme($scheme) + { + // Remove the default port if one is specified + if ($this->port + && isset(self::$defaultPorts[$this->scheme]) + && self::$defaultPorts[$this->scheme] == $this->port + ) { + $this->port = null; + } + + $this->scheme = strtolower($scheme); + } + + /** + * Get the scheme part of the URL + * + * @return string + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Set the port part of the URL + * + * @param int $port Port to set + */ + public function setPort($port) + { + $this->port = $port; + } + + /** + * Get the port part of the URl. + * + * If no port was set, this method will return the default port for the + * scheme of the URI. + * + * @return int|null + */ + public function getPort() + { + if ($this->port) { + return $this->port; + } elseif (isset(self::$defaultPorts[$this->scheme])) { + return self::$defaultPorts[$this->scheme]; + } + + return null; + } + + /** + * Set the path part of the URL. + * + * The provided URL is URL encoded as necessary. + * + * @param string $path Path string to set + */ + public function setPath($path) + { + $this->path = self::encodePath($path); + } + + /** + * Removes dot segments from a URL + * @link http://tools.ietf.org/html/rfc3986#section-5.2.4 + */ + public function removeDotSegments() + { + static $noopPaths = ['' => true, '/' => true, '*' => true]; + static $ignoreSegments = ['.' => true, '..' => true]; + + if (isset($noopPaths[$this->path])) { + return; + } + + $results = []; + $segments = $this->getPathSegments(); + foreach ($segments as $segment) { + if ($segment == '..') { + array_pop($results); + } elseif (!isset($ignoreSegments[$segment])) { + $results[] = $segment; + } + } + + $newPath = implode('/', $results); + + // Add the leading slash if necessary + if (substr($this->path, 0, 1) === '/' && + substr($newPath, 0, 1) !== '/' + ) { + $newPath = '/' . $newPath; + } + + // Add the trailing slash if necessary + if ($newPath != '/' && isset($ignoreSegments[end($segments)])) { + $newPath .= '/'; + } + + $this->path = $newPath; + } + + /** + * Add a relative path to the currently set path. + * + * @param string $relativePath Relative path to add + */ + public function addPath($relativePath) + { + if ($relativePath != '/' && + is_string($relativePath) && + strlen($relativePath) > 0 + ) { + // Add a leading slash if needed + if ($relativePath[0] !== '/' && + substr($this->path, -1, 1) !== '/' + ) { + $relativePath = '/' . $relativePath; + } + + $this->setPath($this->path . $relativePath); + } + } + + /** + * Get the path part of the URL + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Get the path segments of the URL as an array + * + * @return array + */ + public function getPathSegments() + { + return explode('/', $this->path); + } + + /** + * Set the password part of the URL + * + * @param string $password Password to set + */ + public function setPassword($password) + { + $this->password = $password; + } + + /** + * Get the password part of the URL + * + * @return null|string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set the username part of the URL + * + * @param string $username Username to set + */ + public function setUsername($username) + { + $this->username = $username; + } + + /** + * Get the username part of the URl + * + * @return null|string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Get the query part of the URL as a Query object + * + * @return Query + */ + public function getQuery() + { + // Convert the query string to a query object if not already done. + if (!$this->query instanceof Query) { + $this->query = $this->query === null + ? new Query() + : Query::fromString($this->query); + } + + return $this->query; + } + + /** + * Set the query part of the URL. + * + * You may provide a query string as a string and pass $rawString as true + * to provide a query string that is not parsed until a call to getQuery() + * is made. Setting a raw query string will still encode invalid characters + * in a query string. + * + * @param Query|string|array $query Query string value to set. Can + * be a string that will be parsed into a Query object, an array + * of key value pairs, or a Query object. + * @param bool $rawString Set to true when providing a raw query string. + * + * @throws \InvalidArgumentException + */ + public function setQuery($query, $rawString = false) + { + if ($query instanceof Query) { + $this->query = $query; + } elseif (is_string($query)) { + if (!$rawString) { + $this->query = Query::fromString($query); + } else { + // Ensure the query does not have illegal characters. + $this->query = preg_replace_callback( + self::$queryPattern, + [__CLASS__, 'encodeMatch'], + $query + ); + } + + } elseif (is_array($query)) { + $this->query = new Query($query); + } else { + throw new \InvalidArgumentException('Query must be a Query, ' + . 'array, or string. Got ' . Core::describeType($query)); + } + } + + /** + * Get the fragment part of the URL + * + * @return null|string + */ + public function getFragment() + { + return $this->fragment; + } + + /** + * Set the fragment part of the URL + * + * @param string $fragment Fragment to set + */ + public function setFragment($fragment) + { + $this->fragment = $fragment; + } + + /** + * Check if this is an absolute URL + * + * @return bool + */ + public function isAbsolute() + { + return $this->scheme && $this->host; + } + + /** + * Combine the URL with another URL and return a new URL instance. + * + * Follows the rules specific in RFC 3986 section 5.4. + * + * @param string $url Relative URL to combine with + * + * @return Url + * @throws \InvalidArgumentException + * @link http://tools.ietf.org/html/rfc3986#section-5.4 + */ + public function combine($url) + { + $url = static::fromString($url); + + // Use the more absolute URL as the base URL + if (!$this->isAbsolute() && $url->isAbsolute()) { + $url = $url->combine($this); + } + + $parts = $url->getParts(); + + // Passing a URL with a scheme overrides everything + if ($parts['scheme']) { + return clone $url; + } + + // Setting a host overrides the entire rest of the URL + if ($parts['host']) { + return new static( + $this->scheme, + $parts['host'], + $parts['user'], + $parts['pass'], + $parts['port'], + $parts['path'], + $parts['query'] instanceof Query + ? clone $parts['query'] + : $parts['query'], + $parts['fragment'] + ); + } + + if (!$parts['path'] && $parts['path'] !== '0') { + // The relative URL has no path, so check if it is just a query + $path = $this->path ?: ''; + $query = $parts['query'] ?: $this->query; + } else { + $query = $parts['query']; + if ($parts['path'][0] == '/' || !$this->path) { + // Overwrite the existing path if the rel path starts with "/" + $path = $parts['path']; + } else { + // If the relative URL does not have a path or the base URL + // path does not end in a "/" then overwrite the existing path + // up to the last "/" + $path = substr($this->path, 0, strrpos($this->path, '/') + 1) . $parts['path']; + } + } + + $result = new self( + $this->scheme, + $this->host, + $this->username, + $this->password, + $this->port, + $path, + $query instanceof Query ? clone $query : $query, + $parts['fragment'] + ); + + if ($path) { + $result->removeDotSegments(); + } + + return $result; + } + + /** + * Encodes the path part of a URL without double-encoding percent-encoded + * key value pairs. + * + * @param string $path Path to encode + * + * @return string + */ + public static function encodePath($path) + { + static $cb = [__CLASS__, 'encodeMatch']; + return preg_replace_callback(self::$pathPattern, $cb, $path); + } + + private static function encodeMatch(array $match) + { + return rawurlencode($match[0]); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Utils.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 0000000..1c89661 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,211 @@ +expand($template, $variables); + } + + /** + * Wrapper for JSON decode that implements error detection with helpful + * error messages. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * @param int $options Bitmask of JSON decode options. + * + * @return mixed + * @throws \InvalidArgumentException if the JSON cannot be parsed. + * @link http://www.php.net/manual/en/function.json-decode.php + */ + public static function jsonDecode($json, $assoc = false, $depth = 512, $options = 0) + { + static $jsonErrors = [ + JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ]; + + $data = \json_decode($json, $assoc, $depth, $options); + + if (JSON_ERROR_NONE !== json_last_error()) { + $last = json_last_error(); + throw new \InvalidArgumentException( + 'Unable to parse JSON data: ' + . (isset($jsonErrors[$last]) + ? $jsonErrors[$last] + : 'Unknown error') + ); + } + + return $data; + } + + /** + * Get the default User-Agent string to use with Guzzle + * + * @return string + */ + public static function getDefaultUserAgent() + { + static $defaultAgent = ''; + if (!$defaultAgent) { + $defaultAgent = 'Guzzle/' . ClientInterface::VERSION; + if (extension_loaded('curl')) { + $defaultAgent .= ' curl/' . curl_version()['version']; + } + $defaultAgent .= ' PHP/' . PHP_VERSION; + } + + return $defaultAgent; + } + + /** + * Create a default handler to use based on the environment + * + * @throws \RuntimeException if no viable Handler is available. + */ + public static function getDefaultHandler() + { + $default = $future = null; + + if (extension_loaded('curl')) { + $config = [ + 'select_timeout' => getenv('GUZZLE_CURL_SELECT_TIMEOUT') ?: 1 + ]; + if ($maxHandles = getenv('GUZZLE_CURL_MAX_HANDLES')) { + $config['max_handles'] = $maxHandles; + } + if (function_exists('curl_reset')) { + $default = new CurlHandler(); + $future = new CurlMultiHandler($config); + } else { + $default = new CurlMultiHandler($config); + } + } + + if (ini_get('allow_url_fopen')) { + $default = !$default + ? new StreamHandler() + : Middleware::wrapStreaming($default, new StreamHandler()); + } elseif (!$default) { + throw new \RuntimeException('Guzzle requires cURL, the ' + . 'allow_url_fopen ini setting, or a custom HTTP handler.'); + } + + return $future ? Middleware::wrapFuture($default, $future) : $default; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/BatchResultsTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/BatchResultsTest.php new file mode 100644 index 0000000..080d44c --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/BatchResultsTest.php @@ -0,0 +1,58 @@ +assertCount(3, $batch); + $this->assertEquals([$a, $b, $c], $batch->getKeys()); + $this->assertEquals([$hash[$c]], $batch->getFailures()); + $this->assertEquals(['1', '2'], $batch->getSuccessful()); + $this->assertEquals('1', $batch->getResult($a)); + $this->assertNull($batch->getResult(new \stdClass())); + $this->assertTrue(isset($batch[0])); + $this->assertFalse(isset($batch[10])); + $this->assertEquals('1', $batch[0]); + $this->assertEquals('2', $batch[1]); + $this->assertNull($batch[100]); + $this->assertInstanceOf('Exception', $batch[2]); + + $results = iterator_to_array($batch); + $this->assertEquals(['1', '2', $hash[$c]], $results); + } + + /** + * @expectedException \RuntimeException + */ + public function testCannotSetByIndex() + { + $hash = new \SplObjectStorage(); + $batch = new BatchResults($hash); + $batch[10] = 'foo'; + } + + /** + * @expectedException \RuntimeException + */ + public function testCannotUnsetByIndex() + { + $hash = new \SplObjectStorage(); + $batch = new BatchResults($hash); + unset($batch[10]); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/ClientTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/ClientTest.php new file mode 100644 index 0000000..02db3eb --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/ClientTest.php @@ -0,0 +1,647 @@ +ma = function () { + throw new \RuntimeException('Should not have been called.'); + }; + } + + public function testUsesDefaultDefaultOptions() + { + $client = new Client(); + $this->assertTrue($client->getDefaultOption('allow_redirects')); + $this->assertTrue($client->getDefaultOption('exceptions')); + $this->assertTrue($client->getDefaultOption('verify')); + } + + public function testUsesProvidedDefaultOptions() + { + $client = new Client([ + 'defaults' => [ + 'allow_redirects' => false, + 'query' => ['foo' => 'bar'] + ] + ]); + $this->assertFalse($client->getDefaultOption('allow_redirects')); + $this->assertTrue($client->getDefaultOption('exceptions')); + $this->assertTrue($client->getDefaultOption('verify')); + $this->assertEquals(['foo' => 'bar'], $client->getDefaultOption('query')); + } + + public function testCanSpecifyBaseUrl() + { + $this->assertSame('', (new Client())->getBaseUrl()); + $this->assertEquals('http://foo', (new Client([ + 'base_url' => 'http://foo' + ]))->getBaseUrl()); + } + + public function testCanSpecifyBaseUrlUriTemplate() + { + $client = new Client(['base_url' => ['http://foo.com/{var}/', ['var' => 'baz']]]); + $this->assertEquals('http://foo.com/baz/', $client->getBaseUrl()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesUriTemplateValue() + { + new Client(['base_url' => ['http://foo.com/']]); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Foo + */ + public function testCanSpecifyHandler() + { + $client = new Client(['handler' => function () { + throw new \Exception('Foo'); + }]); + $client->get('http://httpbin.org'); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Foo + */ + public function testCanSpecifyHandlerAsAdapter() + { + $client = new Client(['adapter' => function () { + throw new \Exception('Foo'); + }]); + $client->get('http://httpbin.org'); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Foo + */ + public function testCanSpecifyMessageFactory() + { + $factory = $this->getMockBuilder('GuzzleHttp\Message\MessageFactoryInterface') + ->setMethods(['createRequest']) + ->getMockForAbstractClass(); + $factory->expects($this->once()) + ->method('createRequest') + ->will($this->throwException(new \Exception('Foo'))); + $client = new Client(['message_factory' => $factory]); + $client->get(); + } + + public function testCanSpecifyEmitter() + { + $emitter = $this->getMockBuilder('GuzzleHttp\Event\EmitterInterface') + ->setMethods(['listeners']) + ->getMockForAbstractClass(); + $emitter->expects($this->once()) + ->method('listeners') + ->will($this->returnValue('foo')); + + $client = new Client(['emitter' => $emitter]); + $this->assertEquals('foo', $client->getEmitter()->listeners()); + } + + public function testAddsDefaultUserAgentHeaderWithDefaultOptions() + { + $client = new Client(['defaults' => ['allow_redirects' => false]]); + $this->assertFalse($client->getDefaultOption('allow_redirects')); + $this->assertEquals( + ['User-Agent' => Utils::getDefaultUserAgent()], + $client->getDefaultOption('headers') + ); + } + + public function testAddsDefaultUserAgentHeaderWithoutDefaultOptions() + { + $client = new Client(); + $this->assertEquals( + ['User-Agent' => Utils::getDefaultUserAgent()], + $client->getDefaultOption('headers') + ); + } + + private function getRequestClient() + { + $client = $this->getMockBuilder('GuzzleHttp\Client') + ->setMethods(['send']) + ->getMock(); + $client->expects($this->once()) + ->method('send') + ->will($this->returnArgument(0)); + + return $client; + } + + public function requestMethodProvider() + { + return [ + ['GET', false], + ['HEAD', false], + ['DELETE', false], + ['OPTIONS', false], + ['POST', 'foo'], + ['PUT', 'foo'], + ['PATCH', 'foo'] + ]; + } + + /** + * @dataProvider requestMethodProvider + */ + public function testClientProvidesMethodShortcut($method, $body) + { + $client = $this->getRequestClient(); + if ($body) { + $request = $client->{$method}('http://foo.com', [ + 'headers' => ['X-Baz' => 'Bar'], + 'body' => $body, + 'query' => ['a' => 'b'] + ]); + } else { + $request = $client->{$method}('http://foo.com', [ + 'headers' => ['X-Baz' => 'Bar'], + 'query' => ['a' => 'b'] + ]); + } + $this->assertEquals($method, $request->getMethod()); + $this->assertEquals('Bar', $request->getHeader('X-Baz')); + $this->assertEquals('a=b', $request->getQuery()); + if ($body) { + $this->assertEquals($body, $request->getBody()); + } + } + + public function testClientMergesDefaultOptionsWithRequestOptions() + { + $f = $this->getMockBuilder('GuzzleHttp\Message\MessageFactoryInterface') + ->setMethods(array('createRequest')) + ->getMockForAbstractClass(); + + $o = null; + // Intercept the creation + $f->expects($this->once()) + ->method('createRequest') + ->will($this->returnCallback( + function ($method, $url, array $options = []) use (&$o) { + $o = $options; + return (new MessageFactory())->createRequest($method, $url, $options); + } + )); + + $client = new Client([ + 'message_factory' => $f, + 'defaults' => [ + 'headers' => ['Foo' => 'Bar'], + 'query' => ['baz' => 'bam'], + 'exceptions' => false + ] + ]); + + $request = $client->createRequest('GET', 'http://foo.com?a=b', [ + 'headers' => ['Hi' => 'there', '1' => 'one'], + 'allow_redirects' => false, + 'query' => ['t' => 1] + ]); + + $this->assertFalse($o['allow_redirects']); + $this->assertFalse($o['exceptions']); + $this->assertEquals('Bar', $request->getHeader('Foo')); + $this->assertEquals('there', $request->getHeader('Hi')); + $this->assertEquals('one', $request->getHeader('1')); + $this->assertEquals('a=b&baz=bam&t=1', $request->getQuery()); + } + + public function testClientMergesDefaultHeadersCaseInsensitively() + { + $client = new Client(['defaults' => ['headers' => ['Foo' => 'Bar']]]); + $request = $client->createRequest('GET', 'http://foo.com?a=b', [ + 'headers' => ['foo' => 'custom', 'user-agent' => 'test'] + ]); + $this->assertEquals('test', $request->getHeader('User-Agent')); + $this->assertEquals('custom', $request->getHeader('Foo')); + } + + public function testCanOverrideDefaultOptionWithNull() + { + $client = new Client(['defaults' => ['proxy' => 'invalid!']]); + $request = $client->createRequest('GET', 'http://foo.com?a=b', [ + 'proxy' => null + ]); + $this->assertFalse($request->getConfig()->hasKey('proxy')); + } + + public function testDoesNotOverwriteExistingUA() + { + $client = new Client(['defaults' => [ + 'headers' => ['User-Agent' => 'test'] + ]]); + $this->assertEquals( + ['User-Agent' => 'test'], + $client->getDefaultOption('headers') + ); + } + + public function testUsesBaseUrlWhenNoUrlIsSet() + { + $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); + $this->assertEquals( + 'http://www.foo.com/baz?bam=bar', + $client->createRequest('GET')->getUrl() + ); + } + + public function testUsesBaseUrlCombinedWithProvidedUrl() + { + $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); + $this->assertEquals( + 'http://www.foo.com/bar/bam', + $client->createRequest('GET', 'bar/bam')->getUrl() + ); + } + + public function testFalsyPathsAreCombinedWithBaseUrl() + { + $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); + $this->assertEquals( + 'http://www.foo.com/0', + $client->createRequest('GET', '0')->getUrl() + ); + } + + public function testUsesBaseUrlCombinedWithProvidedUrlViaUriTemplate() + { + $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); + $this->assertEquals( + 'http://www.foo.com/bar/123', + $client->createRequest('GET', ['bar/{bam}', ['bam' => '123']])->getUrl() + ); + } + + public function testSettingAbsoluteUrlOverridesBaseUrl() + { + $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); + $this->assertEquals( + 'http://www.foo.com/foo', + $client->createRequest('GET', '/foo')->getUrl() + ); + } + + public function testSettingAbsoluteUriTemplateOverridesBaseUrl() + { + $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); + $this->assertEquals( + 'http://goo.com/1', + $client->createRequest( + 'GET', + ['http://goo.com/{bar}', ['bar' => '1']] + )->getUrl() + ); + } + + public function testCanSetRelativeUrlStartingWithHttp() + { + $client = new Client(['base_url' => 'http://www.foo.com']); + $this->assertEquals( + 'http://www.foo.com/httpfoo', + $client->createRequest('GET', 'httpfoo')->getUrl() + ); + } + + /** + * Test that base URLs ending with a slash are resolved as per RFC3986. + * + * @link http://tools.ietf.org/html/rfc3986#section-5.2.3 + */ + public function testMultipleSubdirectoryWithSlash() + { + $client = new Client(['base_url' => 'http://www.foo.com/bar/bam/']); + $this->assertEquals( + 'http://www.foo.com/bar/bam/httpfoo', + $client->createRequest('GET', 'httpfoo')->getUrl() + ); + } + + /** + * Test that base URLs ending without a slash are resolved as per RFC3986. + * + * @link http://tools.ietf.org/html/rfc3986#section-5.2.3 + */ + public function testMultipleSubdirectoryNoSlash() + { + $client = new Client(['base_url' => 'http://www.foo.com/bar/bam']); + $this->assertEquals( + 'http://www.foo.com/bar/httpfoo', + $client->createRequest('GET', 'httpfoo')->getUrl() + ); + } + + public function testClientSendsRequests() + { + $mock = new MockHandler(['status' => 200, 'headers' => []]); + $client = new Client(['handler' => $mock]); + $response = $client->get('http://test.com'); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('http://test.com', $response->getEffectiveUrl()); + } + + public function testSendingRequestCanBeIntercepted() + { + $response = new Response(200); + $client = new Client(['handler' => $this->ma]); + $client->getEmitter()->on( + 'before', + function (BeforeEvent $e) use ($response) { + $e->intercept($response); + } + ); + $this->assertSame($response, $client->get('http://test.com')); + $this->assertEquals('http://test.com', $response->getEffectiveUrl()); + } + + /** + * @expectedException \GuzzleHttp\Exception\RequestException + * @expectedExceptionMessage Argument 1 passed to GuzzleHttp\Message\FutureResponse::proxy() must implement interface GuzzleHttp\Ring\Future\FutureInterface + */ + public function testEnsuresResponseIsPresentAfterSending() + { + $handler = function () {}; + $client = new Client(['handler' => $handler]); + $client->get('http://httpbin.org'); + } + + /** + * @expectedException \GuzzleHttp\Exception\RequestException + * @expectedExceptionMessage Waiting did not resolve future + */ + public function testEnsuresResponseIsPresentAfterDereferencing() + { + $deferred = new Deferred(); + $handler = new MockHandler(function () use ($deferred) { + return new FutureArray( + $deferred->promise(), + function () {} + ); + }); + $client = new Client(['handler' => $handler]); + $response = $client->get('http://httpbin.org'); + $response->wait(); + } + + public function testClientHandlesErrorsDuringBeforeSend() + { + $client = new Client(); + $client->getEmitter()->on('before', function ($e) { + throw new \Exception('foo'); + }); + $client->getEmitter()->on('error', function (ErrorEvent $e) { + $e->intercept(new Response(200)); + }); + $this->assertEquals( + 200, + $client->get('http://test.com')->getStatusCode() + ); + } + + /** + * @expectedException \GuzzleHttp\Exception\RequestException + * @expectedExceptionMessage foo + */ + public function testClientHandlesErrorsDuringBeforeSendAndThrowsIfUnhandled() + { + $client = new Client(); + $client->getEmitter()->on('before', function (BeforeEvent $e) { + throw new RequestException('foo', $e->getRequest()); + }); + $client->get('http://httpbin.org'); + } + + /** + * @expectedException \GuzzleHttp\Exception\RequestException + * @expectedExceptionMessage foo + */ + public function testClientWrapsExceptions() + { + $client = new Client(); + $client->getEmitter()->on('before', function (BeforeEvent $e) { + throw new \Exception('foo'); + }); + $client->get('http://httpbin.org'); + } + + public function testCanInjectResponseForFutureError() + { + $calledFuture = false; + $deferred = new Deferred(); + $future = new FutureArray( + $deferred->promise(), + function () use ($deferred, &$calledFuture) { + $calledFuture = true; + $deferred->resolve(['error' => new \Exception('Noo!')]); + } + ); + $mock = new MockHandler($future); + $client = new Client(['handler' => $mock]); + $called = 0; + $response = $client->get('http://localhost:123/foo', [ + 'future' => true, + 'events' => [ + 'error' => function (ErrorEvent $e) use (&$called) { + $called++; + $e->intercept(new Response(200)); + } + ] + ]); + $this->assertEquals(0, $called); + $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $response); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertTrue($calledFuture); + $this->assertEquals(1, $called); + } + + public function testCanReturnFutureResults() + { + $called = false; + $deferred = new Deferred(); + $future = new FutureArray( + $deferred->promise(), + function () use ($deferred, &$called) { + $called = true; + $deferred->resolve(['status' => 201, 'headers' => []]); + } + ); + $mock = new MockHandler($future); + $client = new Client(['handler' => $mock]); + $response = $client->get('http://localhost:123/foo', ['future' => true]); + $this->assertFalse($called); + $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $response); + $this->assertEquals(201, $response->getStatusCode()); + $this->assertTrue($called); + } + + public function testThrowsExceptionsWhenDereferenced() + { + $calledFuture = false; + $deferred = new Deferred(); + $future = new FutureArray( + $deferred->promise(), + function () use ($deferred, &$calledFuture) { + $calledFuture = true; + $deferred->resolve(['error' => new \Exception('Noop!')]); + } + ); + $client = new Client(['handler' => new MockHandler($future)]); + try { + $res = $client->get('http://localhost:123/foo', ['future' => true]); + $res->wait(); + $this->fail('Did not throw'); + } catch (RequestException $e) { + $this->assertEquals(1, $calledFuture); + } + } + + /** + * @expectedExceptionMessage Noo! + * @expectedException \GuzzleHttp\Exception\RequestException + */ + public function testThrowsExceptionsSynchronously() + { + $client = new Client([ + 'handler' => new MockHandler(['error' => new \Exception('Noo!')]) + ]); + $client->get('http://localhost:123/foo'); + } + + public function testCanSetDefaultValues() + { + $client = new Client(['foo' => 'bar']); + $client->setDefaultOption('headers/foo', 'bar'); + $this->assertNull($client->getDefaultOption('foo')); + $this->assertEquals('bar', $client->getDefaultOption('headers/foo')); + } + + public function testSendsAllInParallel() + { + $client = new Client(); + $client->getEmitter()->attach(new Mock([ + new Response(200), + new Response(201), + new Response(202), + ])); + $history = new History(); + $client->getEmitter()->attach($history); + + $requests = [ + $client->createRequest('GET', 'http://test.com'), + $client->createRequest('POST', 'http://test.com'), + $client->createRequest('PUT', 'http://test.com') + ]; + + $client->sendAll($requests); + $requests = array_map(function($r) { + return $r->getMethod(); + }, $history->getRequests()); + $this->assertContains('GET', $requests); + $this->assertContains('POST', $requests); + $this->assertContains('PUT', $requests); + } + + public function testCanDisableAuthPerRequest() + { + $client = new Client(['defaults' => ['auth' => 'foo']]); + $request = $client->createRequest('GET', 'http://test.com'); + $this->assertEquals('foo', $request->getConfig()['auth']); + $request = $client->createRequest('GET', 'http://test.com', ['auth' => null]); + $this->assertFalse($request->getConfig()->hasKey('auth')); + } + + public function testUsesProxyEnvironmentVariables() + { + $http = getenv('HTTP_PROXY'); + $https = getenv('HTTPS_PROXY'); + + $client = new Client(); + $this->assertNull($client->getDefaultOption('proxy')); + + putenv('HTTP_PROXY=127.0.0.1'); + $client = new Client(); + $this->assertEquals( + ['http' => '127.0.0.1'], + $client->getDefaultOption('proxy') + ); + + putenv('HTTPS_PROXY=127.0.0.2'); + $client = new Client(); + $this->assertEquals( + ['http' => '127.0.0.1', 'https' => '127.0.0.2'], + $client->getDefaultOption('proxy') + ); + + putenv("HTTP_PROXY=$http"); + putenv("HTTPS_PROXY=$https"); + } + + public function testReturnsFutureForErrorWhenRequested() + { + $client = new Client(['handler' => new MockHandler(['status' => 404])]); + $request = $client->createRequest('GET', 'http://localhost:123/foo', [ + 'future' => true + ]); + $res = $client->send($request); + $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $res); + try { + $res->wait(); + $this->fail('did not throw'); + } catch (RequestException $e) { + $this->assertContains('404', $e->getMessage()); + } + } + + public function testReturnsFutureForResponseWhenRequested() + { + $client = new Client(['handler' => new MockHandler(['status' => 200])]); + $request = $client->createRequest('GET', 'http://localhost:123/foo', [ + 'future' => true + ]); + $res = $client->send($request); + $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $res); + $this->assertEquals(200, $res->getStatusCode()); + } + + public function testCanUseUrlWithCustomQuery() + { + $client = new Client(); + $url = Url::fromString('http://foo.com/bar'); + $query = new Query(['baz' => '123%20']); + $query->setEncodingType(false); + $url->setQuery($query); + $r = $client->createRequest('GET', $url); + $this->assertEquals('http://foo.com/bar?baz=123%20', $r->getUrl()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/CollectionTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/CollectionTest.php new file mode 100644 index 0000000..8c532aa --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/CollectionTest.php @@ -0,0 +1,416 @@ +coll = new Collection(); + } + + public function testConstructorCanBeCalledWithNoParams() + { + $this->coll = new Collection(); + $p = $this->coll->toArray(); + $this->assertEmpty($p, '-> Collection must be empty when no data is passed'); + } + + public function testConstructorCanBeCalledWithParams() + { + $testData = array( + 'test' => 'value', + 'test_2' => 'value2' + ); + $this->coll = new Collection($testData); + $this->assertEquals($this->coll->toArray(), $testData); + $this->assertEquals($this->coll->toArray(), $this->coll->toArray()); + } + + public function testImplementsIteratorAggregate() + { + $this->coll->set('key', 'value'); + $this->assertInstanceOf('ArrayIterator', $this->coll->getIterator()); + $this->assertEquals(1, count($this->coll)); + $total = 0; + foreach ($this->coll as $key => $value) { + $this->assertEquals('key', $key); + $this->assertEquals('value', $value); + $total++; + } + $this->assertEquals(1, $total); + } + + public function testCanAddValuesToExistingKeysByUsingArray() + { + $this->coll->add('test', 'value1'); + $this->assertEquals($this->coll->toArray(), array('test' => 'value1')); + $this->coll->add('test', 'value2'); + $this->assertEquals($this->coll->toArray(), array('test' => array('value1', 'value2'))); + $this->coll->add('test', 'value3'); + $this->assertEquals($this->coll->toArray(), array('test' => array('value1', 'value2', 'value3'))); + } + + public function testHandlesMergingInDisparateDataSources() + { + $params = array( + 'test' => 'value1', + 'test2' => 'value2', + 'test3' => array('value3', 'value4') + ); + $this->coll->merge($params); + $this->assertEquals($this->coll->toArray(), $params); + $this->coll->merge(new Collection(['test4' => 'hi'])); + $this->assertEquals( + $this->coll->toArray(), + $params + ['test4' => 'hi'] + ); + } + + public function testCanClearAllDataOrSpecificKeys() + { + $this->coll->merge(array( + 'test' => 'value1', + 'test2' => 'value2' + )); + + // Clear a specific parameter by name + $this->coll->remove('test'); + + $this->assertEquals($this->coll->toArray(), array( + 'test2' => 'value2' + )); + + // Clear all parameters + $this->coll->clear(); + + $this->assertEquals($this->coll->toArray(), array()); + } + + public function testProvidesKeys() + { + $this->assertEquals(array(), $this->coll->getKeys()); + $this->coll->merge(array( + 'test1' => 'value1', + 'test2' => 'value2' + )); + $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys()); + // Returns the cached array previously returned + $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys()); + $this->coll->remove('test1'); + $this->assertEquals(array('test2'), $this->coll->getKeys()); + $this->coll->add('test3', 'value3'); + $this->assertEquals(array('test2', 'test3'), $this->coll->getKeys()); + } + + public function testChecksIfHasKey() + { + $this->assertFalse($this->coll->hasKey('test')); + $this->coll->add('test', 'value'); + $this->assertEquals(true, $this->coll->hasKey('test')); + $this->coll->add('test2', 'value2'); + $this->assertEquals(true, $this->coll->hasKey('test')); + $this->assertEquals(true, $this->coll->hasKey('test2')); + $this->assertFalse($this->coll->hasKey('testing')); + $this->assertEquals(false, $this->coll->hasKey('AB-C', 'junk')); + } + + public function testChecksIfHasValue() + { + $this->assertFalse($this->coll->hasValue('value')); + $this->coll->add('test', 'value'); + $this->assertEquals('test', $this->coll->hasValue('value')); + $this->coll->add('test2', 'value2'); + $this->assertEquals('test', $this->coll->hasValue('value')); + $this->assertEquals('test2', $this->coll->hasValue('value2')); + $this->assertFalse($this->coll->hasValue('val')); + } + + public function testImplementsCount() + { + $data = new Collection(); + $this->assertEquals(0, $data->count()); + $data->add('key', 'value'); + $this->assertEquals(1, count($data)); + $data->add('key', 'value2'); + $this->assertEquals(1, count($data)); + $data->add('key_2', 'value3'); + $this->assertEquals(2, count($data)); + } + + public function testAddParamsByMerging() + { + $params = array( + 'test' => 'value1', + 'test2' => 'value2', + 'test3' => array('value3', 'value4') + ); + + // Add some parameters + $this->coll->merge($params); + + // Add more parameters by merging them in + $this->coll->merge(array( + 'test' => 'another', + 'different_key' => 'new value' + )); + + $this->assertEquals(array( + 'test' => array('value1', 'another'), + 'test2' => 'value2', + 'test3' => array('value3', 'value4'), + 'different_key' => 'new value' + ), $this->coll->toArray()); + } + + public function testAllowsFunctionalFilter() + { + $this->coll->merge(array( + 'fruit' => 'apple', + 'number' => 'ten', + 'prepositions' => array('about', 'above', 'across', 'after'), + 'same_number' => 'ten' + )); + + $filtered = $this->coll->filter(function ($key, $value) { + return $value == 'ten'; + }); + + $this->assertNotSame($filtered, $this->coll); + + $this->assertEquals(array( + 'number' => 'ten', + 'same_number' => 'ten' + ), $filtered->toArray()); + } + + public function testAllowsFunctionalMapping() + { + $this->coll->merge(array( + 'number_1' => 1, + 'number_2' => 2, + 'number_3' => 3 + )); + + $mapped = $this->coll->map(function ($key, $value) { + return $value * $value; + }); + + $this->assertNotSame($mapped, $this->coll); + + $this->assertEquals(array( + 'number_1' => 1, + 'number_2' => 4, + 'number_3' => 9 + ), $mapped->toArray()); + } + + public function testImplementsArrayAccess() + { + $this->coll->merge(array( + 'k1' => 'v1', + 'k2' => 'v2' + )); + + $this->assertTrue($this->coll->offsetExists('k1')); + $this->assertFalse($this->coll->offsetExists('Krull')); + + $this->coll->offsetSet('k3', 'v3'); + $this->assertEquals('v3', $this->coll->offsetGet('k3')); + $this->assertEquals('v3', $this->coll->get('k3')); + + $this->coll->offsetUnset('k1'); + $this->assertFalse($this->coll->offsetExists('k1')); + } + + public function testCanReplaceAllData() + { + $this->coll->replace(array('a' => '123')); + $this->assertEquals(array('a' => '123'), $this->coll->toArray()); + } + + public function testPreparesFromConfig() + { + $c = Collection::fromConfig(array( + 'a' => '123', + 'base_url' => 'http://www.test.com/' + ), array( + 'a' => 'xyz', + 'b' => 'lol' + ), array('a')); + + $this->assertInstanceOf('GuzzleHttp\Collection', $c); + $this->assertEquals(array( + 'a' => '123', + 'b' => 'lol', + 'base_url' => 'http://www.test.com/' + ), $c->toArray()); + + try { + Collection::fromConfig(array(), array(), array('a')); + $this->fail('Exception not throw when missing config'); + } catch (\InvalidArgumentException $e) { + } + } + + function falseyDataProvider() + { + return array( + array(false, false), + array(null, null), + array('', ''), + array(array(), array()), + array(0, 0), + ); + } + + /** + * @dataProvider falseyDataProvider + */ + public function testReturnsCorrectData($a, $b) + { + $c = new Collection(array('value' => $a)); + $this->assertSame($b, $c->get('value')); + } + + public function testRetrievesNestedKeysUsingPath() + { + $data = array( + 'foo' => 'bar', + 'baz' => array( + 'mesa' => array( + 'jar' => 'jar' + ) + ) + ); + $collection = new Collection($data); + $this->assertEquals('bar', $collection->getPath('foo')); + $this->assertEquals('jar', $collection->getPath('baz/mesa/jar')); + $this->assertNull($collection->getPath('wewewf')); + $this->assertNull($collection->getPath('baz/mesa/jar/jar')); + } + + public function testFalseyKeysStillDescend() + { + $collection = new Collection(array( + '0' => array( + 'a' => 'jar' + ), + 1 => 'other' + )); + $this->assertEquals('jar', $collection->getPath('0/a')); + $this->assertEquals('other', $collection->getPath('1')); + } + + public function getPathProvider() + { + $data = array( + 'foo' => 'bar', + 'baz' => array( + 'mesa' => array( + 'jar' => 'jar', + 'array' => array('a', 'b', 'c') + ), + 'bar' => array( + 'baz' => 'bam', + 'array' => array('d', 'e', 'f') + ) + ), + 'bam' => array( + array('foo' => 1), + array('foo' => 2), + array('array' => array('h', 'i')) + ) + ); + $c = new Collection($data); + + return array( + // Simple path selectors + array($c, 'foo', 'bar'), + array($c, 'baz', $data['baz']), + array($c, 'bam', $data['bam']), + array($c, 'baz/mesa', $data['baz']['mesa']), + array($c, 'baz/mesa/jar', 'jar'), + // Does not barf on missing keys + array($c, 'fefwfw', null), + array($c, 'baz/mesa/array', $data['baz']['mesa']['array']) + ); + } + + /** + * @dataProvider getPathProvider + */ + public function testGetPath(Collection $c, $path, $expected, $separator = '/') + { + $this->assertEquals($expected, $c->getPath($path, $separator)); + } + + public function testOverridesSettings() + { + $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); + $c->overwriteWith(array('foo' => 10, 'bar' => 300)); + $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->toArray()); + } + + public function testOverwriteWithCollection() + { + $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); + $b = new Collection(array('foo' => 10, 'bar' => 300)); + $c->overwriteWith($b); + $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->toArray()); + } + + public function testOverwriteWithTraversable() + { + $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); + $b = new Collection(array('foo' => 10, 'bar' => 300)); + $c->overwriteWith($b->getIterator()); + $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->toArray()); + } + + public function testCanSetNestedPathValueThatDoesNotExist() + { + $c = new Collection(array()); + $c->setPath('foo/bar/baz/123', 'hi'); + $this->assertEquals('hi', $c['foo']['bar']['baz']['123']); + } + + public function testCanSetNestedPathValueThatExists() + { + $c = new Collection(array('foo' => array('bar' => 'test'))); + $c->setPath('foo/bar', 'hi'); + $this->assertEquals('hi', $c['foo']['bar']); + } + + /** + * @expectedException \RuntimeException + */ + public function testVerifiesNestedPathIsValidAtExactLevel() + { + $c = new Collection(array('foo' => 'bar')); + $c->setPath('foo/bar', 'hi'); + $this->assertEquals('hi', $c['foo']['bar']); + } + + /** + * @expectedException \RuntimeException + */ + public function testVerifiesThatNestedPathIsValidAtAnyLevel() + { + $c = new Collection(array('foo' => 'bar')); + $c->setPath('foo/bar/baz', 'test'); + } + + public function testCanAppendToNestedPathValues() + { + $c = new Collection(); + $c->setPath('foo/bar/[]', 'a'); + $c->setPath('foo/bar/[]', 'b'); + $this->assertEquals(['a', 'b'], $c['foo']['bar']); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/CookieJarTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/CookieJarTest.php new file mode 100644 index 0000000..1360419 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/CookieJarTest.php @@ -0,0 +1,339 @@ +jar = new CookieJar(); + } + + protected function getTestCookies() + { + return [ + new SetCookie(['Name' => 'foo', 'Value' => 'bar', 'Domain' => 'foo.com', 'Path' => '/', 'Discard' => true]), + new SetCookie(['Name' => 'test', 'Value' => '123', 'Domain' => 'baz.com', 'Path' => '/foo', 'Expires' => 2]), + new SetCookie(['Name' => 'you', 'Value' => '123', 'Domain' => 'bar.com', 'Path' => '/boo', 'Expires' => time() + 1000]) + ]; + } + + public function testQuotesBadCookieValues() + { + $this->assertEquals('foo', CookieJar::getCookieValue('foo')); + $this->assertEquals('"foo,bar"', CookieJar::getCookieValue('foo,bar')); + } + + public function testCreatesFromArray() + { + $jar = CookieJar::fromArray([ + 'foo' => 'bar', + 'baz' => 'bam' + ], 'example.com'); + $this->assertCount(2, $jar); + } + + /** + * Provides test data for cookie cookieJar retrieval + */ + public function getCookiesDataProvider() + { + return [ + [['foo', 'baz', 'test', 'muppet', 'googoo'], '', '', '', false], + [['foo', 'baz', 'muppet', 'googoo'], '', '', '', true], + [['googoo'], 'www.example.com', '', '', false], + [['muppet', 'googoo'], 'test.y.example.com', '', '', false], + [['foo', 'baz'], 'example.com', '', '', false], + [['muppet'], 'x.y.example.com', '/acme/', '', false], + [['muppet'], 'x.y.example.com', '/acme/test/', '', false], + [['googoo'], 'x.y.example.com', '/test/acme/test/', '', false], + [['foo', 'baz'], 'example.com', '', '', false], + [['baz'], 'example.com', '', 'baz', false], + ]; + } + + public function testStoresAndRetrievesCookies() + { + $cookies = $this->getTestCookies(); + foreach ($cookies as $cookie) { + $this->assertTrue($this->jar->setCookie($cookie)); + } + + $this->assertEquals(3, count($this->jar)); + $this->assertEquals(3, count($this->jar->getIterator())); + $this->assertEquals($cookies, $this->jar->getIterator()->getArrayCopy()); + } + + public function testRemovesTemporaryCookies() + { + $cookies = $this->getTestCookies(); + foreach ($this->getTestCookies() as $cookie) { + $this->jar->setCookie($cookie); + } + $this->jar->clearSessionCookies(); + $this->assertEquals( + [$cookies[1], $cookies[2]], + $this->jar->getIterator()->getArrayCopy() + ); + } + + public function testRemovesSelectively() + { + foreach ($this->getTestCookies() as $cookie) { + $this->jar->setCookie($cookie); + } + + // Remove foo.com cookies + $this->jar->clear('foo.com'); + $this->assertEquals(2, count($this->jar)); + // Try again, removing no further cookies + $this->jar->clear('foo.com'); + $this->assertEquals(2, count($this->jar)); + + // Remove bar.com cookies with path of /boo + $this->jar->clear('bar.com', '/boo'); + $this->assertEquals(1, count($this->jar)); + + // Remove cookie by name + $this->jar->clear(null, null, 'test'); + $this->assertEquals(0, count($this->jar)); + } + + public function testDoesNotAddIncompleteCookies() + { + $this->assertEquals(false, $this->jar->setCookie(new SetCookie())); + $this->assertFalse($this->jar->setCookie(new SetCookie(array( + 'Name' => 'foo' + )))); + $this->assertFalse($this->jar->setCookie(new SetCookie(array( + 'Name' => false + )))); + $this->assertFalse($this->jar->setCookie(new SetCookie(array( + 'Name' => true + )))); + $this->assertFalse($this->jar->setCookie(new SetCookie(array( + 'Name' => 'foo', + 'Domain' => 'foo.com' + )))); + } + + public function testDoesAddValidCookies() + { + $this->assertTrue($this->jar->setCookie(new SetCookie(array( + 'Name' => 'foo', + 'Domain' => 'foo.com', + 'Value' => 0 + )))); + $this->assertTrue($this->jar->setCookie(new SetCookie(array( + 'Name' => 'foo', + 'Domain' => 'foo.com', + 'Value' => 0.0 + )))); + $this->assertTrue($this->jar->setCookie(new SetCookie(array( + 'Name' => 'foo', + 'Domain' => 'foo.com', + 'Value' => '0' + )))); + } + + public function testOverwritesCookiesThatAreOlderOrDiscardable() + { + $t = time() + 1000; + $data = array( + 'Name' => 'foo', + 'Value' => 'bar', + 'Domain' => '.example.com', + 'Path' => '/', + 'Max-Age' => '86400', + 'Secure' => true, + 'Discard' => true, + 'Expires' => $t + ); + + // Make sure that the discard cookie is overridden with the non-discard + $this->assertTrue($this->jar->setCookie(new SetCookie($data))); + $this->assertEquals(1, count($this->jar)); + + $data['Discard'] = false; + $this->assertTrue($this->jar->setCookie(new SetCookie($data))); + $this->assertEquals(1, count($this->jar)); + + $c = $this->jar->getIterator()->getArrayCopy(); + $this->assertEquals(false, $c[0]->getDiscard()); + + // Make sure it doesn't duplicate the cookie + $this->jar->setCookie(new SetCookie($data)); + $this->assertEquals(1, count($this->jar)); + + // Make sure the more future-ful expiration date supersede the other + $data['Expires'] = time() + 2000; + $this->assertTrue($this->jar->setCookie(new SetCookie($data))); + $this->assertEquals(1, count($this->jar)); + $c = $this->jar->getIterator()->getArrayCopy(); + $this->assertNotEquals($t, $c[0]->getExpires()); + } + + public function testOverwritesCookiesThatHaveChanged() + { + $t = time() + 1000; + $data = array( + 'Name' => 'foo', + 'Value' => 'bar', + 'Domain' => '.example.com', + 'Path' => '/', + 'Max-Age' => '86400', + 'Secure' => true, + 'Discard' => true, + 'Expires' => $t + ); + + // Make sure that the discard cookie is overridden with the non-discard + $this->assertTrue($this->jar->setCookie(new SetCookie($data))); + + $data['Value'] = 'boo'; + $this->assertTrue($this->jar->setCookie(new SetCookie($data))); + $this->assertEquals(1, count($this->jar)); + + // Changing the value plus a parameter also must overwrite the existing one + $data['Value'] = 'zoo'; + $data['Secure'] = false; + $this->assertTrue($this->jar->setCookie(new SetCookie($data))); + $this->assertEquals(1, count($this->jar)); + + $c = $this->jar->getIterator()->getArrayCopy(); + $this->assertEquals('zoo', $c[0]->getValue()); + } + + public function testAddsCookiesFromResponseWithRequest() + { + $response = new Response(200, array( + 'Set-Cookie' => "fpc=d=.Hm.yh4.1XmJWjJfs4orLQzKzPImxklQoxXSHOZATHUSEFciRueW_7704iYUtsXNEXq0M92Px2glMdWypmJ7HIQl6XIUvrZimWjQ3vIdeuRbI.FNQMAfcxu_XN1zSx7l.AcPdKL6guHc2V7hIQFhnjRW0rxm2oHY1P4bGQxFNz7f.tHm12ZD3DbdMDiDy7TBXsuP4DM-&v=2; expires=Fri, 02-Mar-2019 02:17:40 GMT;" + )); + $request = new Request('GET', 'http://www.example.com'); + $this->jar->extractCookies($request, $response); + $this->assertEquals(1, count($this->jar)); + } + + public function getMatchingCookiesDataProvider() + { + return array( + array('https://example.com', 'foo=bar; baz=foobar'), + array('http://example.com', ''), + array('https://example.com:8912', 'foo=bar; baz=foobar'), + array('https://foo.example.com', 'foo=bar; baz=foobar'), + array('http://foo.example.com/test/acme/', 'googoo=gaga') + ); + } + + /** + * @dataProvider getMatchingCookiesDataProvider + */ + public function testReturnsCookiesMatchingRequests($url, $cookies) + { + $bag = [ + new SetCookie([ + 'Name' => 'foo', + 'Value' => 'bar', + 'Domain' => 'example.com', + 'Path' => '/', + 'Max-Age' => '86400', + 'Secure' => true + ]), + new SetCookie([ + 'Name' => 'baz', + 'Value' => 'foobar', + 'Domain' => 'example.com', + 'Path' => '/', + 'Max-Age' => '86400', + 'Secure' => true + ]), + new SetCookie([ + 'Name' => 'test', + 'Value' => '123', + 'Domain' => 'www.foobar.com', + 'Path' => '/path/', + 'Discard' => true + ]), + new SetCookie([ + 'Name' => 'muppet', + 'Value' => 'cookie_monster', + 'Domain' => '.y.example.com', + 'Path' => '/acme/', + 'Expires' => time() + 86400 + ]), + new SetCookie([ + 'Name' => 'googoo', + 'Value' => 'gaga', + 'Domain' => '.example.com', + 'Path' => '/test/acme/', + 'Max-Age' => 1500 + ]) + ]; + + foreach ($bag as $cookie) { + $this->jar->setCookie($cookie); + } + + $request = new Request('GET', $url); + $this->jar->addCookieHeader($request); + $this->assertEquals($cookies, $request->getHeader('Cookie')); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Invalid cookie: Cookie name must not cannot invalid characters: + */ + public function testThrowsExceptionWithStrictMode() + { + $a = new CookieJar(true); + $a->setCookie(new SetCookie(['Name' => "abc\n", 'Value' => 'foo', 'Domain' => 'bar'])); + } + + public function testDeletesCookiesByName() + { + $cookies = $this->getTestCookies(); + $cookies[] = new SetCookie([ + 'Name' => 'other', + 'Value' => '123', + 'Domain' => 'bar.com', + 'Path' => '/boo', + 'Expires' => time() + 1000 + ]); + $jar = new CookieJar(); + foreach ($cookies as $cookie) { + $jar->setCookie($cookie); + } + $this->assertCount(4, $jar); + $jar->clear('bar.com', '/boo', 'other'); + $this->assertCount(3, $jar); + $names = array_map(function (SetCookie $c) { + return $c->getName(); + }, $jar->getIterator()->getArrayCopy()); + $this->assertEquals(['foo', 'test', 'you'], $names); + } + + public function testCanConvertToAndLoadFromArray() + { + $jar = new CookieJar(true); + foreach ($this->getTestCookies() as $cookie) { + $jar->setCookie($cookie); + } + $this->assertCount(3, $jar); + $arr = $jar->toArray(); + $this->assertCount(3, $arr); + $newCookieJar = new CookieJar(false, $arr); + $this->assertCount(3, $newCookieJar); + $this->assertSame($jar->toArray(), $newCookieJar->toArray()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/FileCookieJarTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/FileCookieJarTest.php new file mode 100644 index 0000000..1d11337 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/FileCookieJarTest.php @@ -0,0 +1,71 @@ +file = tempnam('/tmp', 'file-cookies'); + } + + /** + * @expectedException \RuntimeException + */ + public function testValidatesCookieFile() + { + file_put_contents($this->file, 'true'); + new FileCookieJar($this->file); + } + + public function testLoadsFromFileFile() + { + $jar = new FileCookieJar($this->file); + $this->assertEquals([], $jar->getIterator()->getArrayCopy()); + unlink($this->file); + } + + public function testPersistsToFileFile() + { + $jar = new FileCookieJar($this->file); + $jar->setCookie(new SetCookie([ + 'Name' => 'foo', + 'Value' => 'bar', + 'Domain' => 'foo.com', + 'Expires' => time() + 1000 + ])); + $jar->setCookie(new SetCookie([ + 'Name' => 'baz', + 'Value' => 'bar', + 'Domain' => 'foo.com', + 'Expires' => time() + 1000 + ])); + $jar->setCookie(new SetCookie([ + 'Name' => 'boo', + 'Value' => 'bar', + 'Domain' => 'foo.com', + ])); + + $this->assertEquals(3, count($jar)); + unset($jar); + + // Make sure it wrote to the file + $contents = file_get_contents($this->file); + $this->assertNotEmpty($contents); + + // Load the cookieJar from the file + $jar = new FileCookieJar($this->file); + + // Weeds out temporary and session cookies + $this->assertEquals(2, count($jar)); + unset($jar); + unlink($this->file); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/SessionCookieJarTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/SessionCookieJarTest.php new file mode 100644 index 0000000..ccc6d4e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/SessionCookieJarTest.php @@ -0,0 +1,76 @@ +sessionVar = 'sessionKey'; + + if (!isset($_SESSION)) { + $_SESSION = array(); + } + } + + /** + * @expectedException \RuntimeException + */ + public function testValidatesCookieSession() + { + $_SESSION[$this->sessionVar] = 'true'; + new SessionCookieJar($this->sessionVar); + } + + public function testLoadsFromSession() + { + $jar = new SessionCookieJar($this->sessionVar); + $this->assertEquals([], $jar->getIterator()->getArrayCopy()); + unset($_SESSION[$this->sessionVar]); + } + + public function testPersistsToSession() + { + $jar = new SessionCookieJar($this->sessionVar); + $jar->setCookie(new SetCookie([ + 'Name' => 'foo', + 'Value' => 'bar', + 'Domain' => 'foo.com', + 'Expires' => time() + 1000 + ])); + $jar->setCookie(new SetCookie([ + 'Name' => 'baz', + 'Value' => 'bar', + 'Domain' => 'foo.com', + 'Expires' => time() + 1000 + ])); + $jar->setCookie(new SetCookie([ + 'Name' => 'boo', + 'Value' => 'bar', + 'Domain' => 'foo.com', + ])); + + $this->assertEquals(3, count($jar)); + unset($jar); + + // Make sure it wrote to the sessionVar in $_SESSION + $contents = $_SESSION[$this->sessionVar]; + $this->assertNotEmpty($contents); + + // Load the cookieJar from the file + $jar = new SessionCookieJar($this->sessionVar); + + // Weeds out temporary and session cookies + $this->assertEquals(2, count($jar)); + unset($jar); + unset($_SESSION[$this->sessionVar]); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/SetCookieTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/SetCookieTest.php new file mode 100644 index 0000000..3ddd082 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Cookie/SetCookieTest.php @@ -0,0 +1,364 @@ +assertEquals('/', $cookie->getPath()); + } + + public function testConvertsDateTimeMaxAgeToUnixTimestamp() + { + $cookie = new SetCookie(['Expires' => 'November 20, 1984']); + $this->assertInternalType('integer', $cookie->getExpires()); + } + + public function testAddsExpiresBasedOnMaxAge() + { + $t = time(); + $cookie = new SetCookie(['Max-Age' => 100]); + $this->assertEquals($t + 100, $cookie->getExpires()); + } + + public function testHoldsValues() + { + $t = time(); + $data = array( + 'Name' => 'foo', + 'Value' => 'baz', + 'Path' => '/bar', + 'Domain' => 'baz.com', + 'Expires' => $t, + 'Max-Age' => 100, + 'Secure' => true, + 'Discard' => true, + 'HttpOnly' => true, + 'foo' => 'baz', + 'bar' => 'bam' + ); + + $cookie = new SetCookie($data); + $this->assertEquals($data, $cookie->toArray()); + + $this->assertEquals('foo', $cookie->getName()); + $this->assertEquals('baz', $cookie->getValue()); + $this->assertEquals('baz.com', $cookie->getDomain()); + $this->assertEquals('/bar', $cookie->getPath()); + $this->assertEquals($t, $cookie->getExpires()); + $this->assertEquals(100, $cookie->getMaxAge()); + $this->assertTrue($cookie->getSecure()); + $this->assertTrue($cookie->getDiscard()); + $this->assertTrue($cookie->getHttpOnly()); + $this->assertEquals('baz', $cookie->toArray()['foo']); + $this->assertEquals('bam', $cookie->toArray()['bar']); + + $cookie->setName('a'); + $cookie->setValue('b'); + $cookie->setPath('c'); + $cookie->setDomain('bar.com'); + $cookie->setExpires(10); + $cookie->setMaxAge(200); + $cookie->setSecure(false); + $cookie->setHttpOnly(false); + $cookie->setDiscard(false); + + $this->assertEquals('a', $cookie->getName()); + $this->assertEquals('b', $cookie->getValue()); + $this->assertEquals('c', $cookie->getPath()); + $this->assertEquals('bar.com', $cookie->getDomain()); + $this->assertEquals(10, $cookie->getExpires()); + $this->assertEquals(200, $cookie->getMaxAge()); + $this->assertFalse($cookie->getSecure()); + $this->assertFalse($cookie->getDiscard()); + $this->assertFalse($cookie->getHttpOnly()); + } + + public function testDeterminesIfExpired() + { + $c = new SetCookie(); + $c->setExpires(10); + $this->assertTrue($c->isExpired()); + $c->setExpires(time() + 10000); + $this->assertFalse($c->isExpired()); + } + + public function testMatchesDomain() + { + $cookie = new SetCookie(); + $this->assertTrue($cookie->matchesDomain('baz.com')); + + $cookie->setDomain('baz.com'); + $this->assertTrue($cookie->matchesDomain('baz.com')); + $this->assertFalse($cookie->matchesDomain('bar.com')); + + $cookie->setDomain('.baz.com'); + $this->assertTrue($cookie->matchesDomain('.baz.com')); + $this->assertTrue($cookie->matchesDomain('foo.baz.com')); + $this->assertFalse($cookie->matchesDomain('baz.bar.com')); + $this->assertTrue($cookie->matchesDomain('baz.com')); + + $cookie->setDomain('.127.0.0.1'); + $this->assertTrue($cookie->matchesDomain('127.0.0.1')); + + $cookie->setDomain('127.0.0.1'); + $this->assertTrue($cookie->matchesDomain('127.0.0.1')); + + $cookie->setDomain('.com.'); + $this->assertFalse($cookie->matchesDomain('baz.com')); + + $cookie->setDomain('.local'); + $this->assertTrue($cookie->matchesDomain('example.local')); + } + + public function testMatchesPath() + { + $cookie = new SetCookie(); + $this->assertTrue($cookie->matchesPath('/foo')); + + $cookie->setPath('/foo'); + $this->assertTrue($cookie->matchesPath('/foo')); + $this->assertTrue($cookie->matchesPath('/foo/bar')); + $this->assertFalse($cookie->matchesPath('/bar')); + } + + public function cookieValidateProvider() + { + return array( + array('foo', 'baz', 'bar', true), + array('0', '0', '0', true), + array('', 'baz', 'bar', 'The cookie name must not be empty'), + array('foo', '', 'bar', 'The cookie value must not be empty'), + array('foo', 'baz', '', 'The cookie domain must not be empty'), + array("foo\r", 'baz', '0', 'Cookie name must not cannot invalid characters: =,; \t\r\n\013\014'), + ); + } + + /** + * @dataProvider cookieValidateProvider + */ + public function testValidatesCookies($name, $value, $domain, $result) + { + $cookie = new SetCookie(array( + 'Name' => $name, + 'Value' => $value, + 'Domain' => $domain + )); + $this->assertSame($result, $cookie->validate()); + } + + public function testDoesNotMatchIp() + { + $cookie = new SetCookie(['Domain' => '192.168.16.']); + $this->assertFalse($cookie->matchesDomain('192.168.16.121')); + } + + public function testConvertsToString() + { + $t = 1382916008; + $cookie = new SetCookie([ + 'Name' => 'test', + 'Value' => '123', + 'Domain' => 'foo.com', + 'Expires' => $t, + 'Path' => '/abc', + 'HttpOnly' => true, + 'Secure' => true + ]); + $this->assertEquals( + 'test=123; Domain=foo.com; Path=/abc; Expires=Sun, 27 Oct 2013 23:20:08 GMT; Secure; HttpOnly', + (string) $cookie + ); + } + + /** + * Provides the parsed information from a cookie + * + * @return array + */ + public function cookieParserDataProvider() + { + return array( + array( + 'ASIHTTPRequestTestCookie=This+is+the+value; expires=Sat, 26-Jul-2008 17:00:42 GMT; path=/tests; domain=allseeing-i.com; PHPSESSID=6c951590e7a9359bcedde25cda73e43c; path=/";', + array( + 'Domain' => 'allseeing-i.com', + 'Path' => '/', + 'PHPSESSID' => '6c951590e7a9359bcedde25cda73e43c', + 'Max-Age' => NULL, + 'Expires' => 'Sat, 26-Jul-2008 17:00:42 GMT', + 'Secure' => NULL, + 'Discard' => NULL, + 'Name' => 'ASIHTTPRequestTestCookie', + 'Value' => 'This+is+the+value', + 'HttpOnly' => false + ) + ), + array('', []), + array('foo', []), + // Test setting a blank value for a cookie + array(array( + 'foo=', 'foo =', 'foo =;', 'foo= ;', 'foo =', 'foo= '), + array( + 'Name' => 'foo', + 'Value' => '', + 'Discard' => null, + 'Domain' => null, + 'Expires' => null, + 'Max-Age' => null, + 'Path' => '/', + 'Secure' => null, + 'HttpOnly' => false + ) + ), + // Test setting a value and removing quotes + array(array( + 'foo=1', 'foo =1', 'foo =1;', 'foo=1 ;', 'foo =1', 'foo= 1', 'foo = 1 ;', 'foo="1"', 'foo="1";', 'foo= "1";'), + array( + 'Name' => 'foo', + 'Value' => '1', + 'Discard' => null, + 'Domain' => null, + 'Expires' => null, + 'Max-Age' => null, + 'Path' => '/', + 'Secure' => null, + 'HttpOnly' => false + ) + ), + // Some of the following tests are based on http://framework.zend.com/svn/framework/standard/trunk/tests/Zend/Http/CookieTest.php + array( + 'justacookie=foo; domain=example.com', + array( + 'Name' => 'justacookie', + 'Value' => 'foo', + 'Domain' => 'example.com', + 'Discard' => null, + 'Expires' => null, + 'Max-Age' => null, + 'Path' => '/', + 'Secure' => null, + 'HttpOnly' => false + ) + ), + array( + 'expires=tomorrow; secure; path=/Space Out/; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=.example.com', + array( + 'Name' => 'expires', + 'Value' => 'tomorrow', + 'Domain' => '.example.com', + 'Path' => '/Space Out/', + 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'Discard' => null, + 'Secure' => true, + 'Max-Age' => null, + 'HttpOnly' => false + ) + ), + array( + 'domain=unittests; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=example.com; path=/some value/', + array( + 'Name' => 'domain', + 'Value' => 'unittests', + 'Domain' => 'example.com', + 'Path' => '/some value/', + 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'Secure' => false, + 'Discard' => null, + 'Max-Age' => null, + 'HttpOnly' => false + ) + ), + array( + 'path=indexAction; path=/; domain=.foo.com; expires=Tue, 21-Nov-2006 08:33:44 GMT', + array( + 'Name' => 'path', + 'Value' => 'indexAction', + 'Domain' => '.foo.com', + 'Path' => '/', + 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'Secure' => false, + 'Discard' => null, + 'Max-Age' => null, + 'HttpOnly' => false + ) + ), + array( + 'secure=sha1; secure; SECURE; domain=some.really.deep.domain.com; version=1; Max-Age=86400', + array( + 'Name' => 'secure', + 'Value' => 'sha1', + 'Domain' => 'some.really.deep.domain.com', + 'Path' => '/', + 'Secure' => true, + 'Discard' => null, + 'Expires' => time() + 86400, + 'Max-Age' => 86400, + 'HttpOnly' => false, + 'version' => '1' + ) + ), + array( + 'PHPSESSID=123456789+abcd%2Cef; secure; discard; domain=.localdomain; path=/foo/baz; expires=Tue, 21-Nov-2006 08:33:44 GMT;', + array( + 'Name' => 'PHPSESSID', + 'Value' => '123456789+abcd%2Cef', + 'Domain' => '.localdomain', + 'Path' => '/foo/baz', + 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'Secure' => true, + 'Discard' => true, + 'Max-Age' => null, + 'HttpOnly' => false + ) + ), + ); + } + + /** + * @dataProvider cookieParserDataProvider + */ + public function testParseCookie($cookie, $parsed) + { + foreach ((array) $cookie as $v) { + $c = SetCookie::fromString($v); + $p = $c->toArray(); + + if (isset($p['Expires'])) { + // Remove expires values from the assertion if they are relatively equal + if (abs($p['Expires'] != strtotime($parsed['Expires'])) < 40) { + unset($p['Expires']); + unset($parsed['Expires']); + } + } + + if (!empty($parsed)) { + foreach ($parsed as $key => $value) { + $this->assertEquals($parsed[$key], $p[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true)); + } + foreach ($p as $key => $value) { + $this->assertEquals($p[$key], $parsed[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true)); + } + } else { + $this->assertEquals([ + 'Name' => null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false, + ], $p); + } + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractEventTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractEventTest.php new file mode 100644 index 0000000..b8c06f1 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractEventTest.php @@ -0,0 +1,14 @@ +getMockBuilder('GuzzleHttp\Event\AbstractEvent') + ->getMockForAbstractClass(); + $this->assertFalse($e->isPropagationStopped()); + $e->stopPropagation(); + $this->assertTrue($e->isPropagationStopped()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractRequestEventTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractRequestEventTest.php new file mode 100644 index 0000000..50536c5 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractRequestEventTest.php @@ -0,0 +1,33 @@ +getMockBuilder('GuzzleHttp\Event\AbstractRequestEvent') + ->setConstructorArgs([$t]) + ->getMockForAbstractClass(); + $this->assertSame($t->client, $e->getClient()); + $this->assertSame($t->request, $e->getRequest()); + } + + public function testHasTransaction() + { + $t = new Transaction(new Client(), new Request('GET', '/')); + $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractRequestEvent') + ->setConstructorArgs([$t]) + ->getMockForAbstractClass(); + $r = new \ReflectionMethod($e, 'getTransaction'); + $r->setAccessible(true); + $this->assertSame($t, $r->invoke($e)); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractRetryableEventTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractRetryableEventTest.php new file mode 100644 index 0000000..6a39d8b --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractRetryableEventTest.php @@ -0,0 +1,37 @@ +transferInfo = ['foo' => 'bar']; + $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractRetryableEvent') + ->setConstructorArgs([$t]) + ->getMockForAbstractClass(); + $e->retry(); + $this->assertTrue($e->isPropagationStopped()); + $this->assertEquals('retry', $t->state); + } + + public function testCanRetryAfterDelay() + { + $t = new Transaction(new Client(), new Request('GET', '/')); + $t->transferInfo = ['foo' => 'bar']; + $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractRetryableEvent') + ->setConstructorArgs([$t]) + ->getMockForAbstractClass(); + $e->retry(10); + $this->assertTrue($e->isPropagationStopped()); + $this->assertEquals('retry', $t->state); + $this->assertEquals(10, $t->request->getConfig()->get('delay')); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractTransferEventTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractTransferEventTest.php new file mode 100644 index 0000000..5313c8e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/AbstractTransferEventTest.php @@ -0,0 +1,59 @@ +transferInfo = ['foo' => 'bar']; + $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent') + ->setConstructorArgs([$t]) + ->getMockForAbstractClass(); + $this->assertNull($e->getTransferInfo('baz')); + $this->assertEquals('bar', $e->getTransferInfo('foo')); + $this->assertEquals($t->transferInfo, $e->getTransferInfo()); + } + + public function testHasResponse() + { + $t = new Transaction(new Client(), new Request('GET', '/')); + $t->response = new Response(200); + $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent') + ->setConstructorArgs([$t]) + ->getMockForAbstractClass(); + $this->assertTrue($e->hasResponse()); + $this->assertSame($t->response, $e->getResponse()); + } + + public function testCanInterceptWithResponse() + { + $t = new Transaction(new Client(), new Request('GET', '/')); + $r = new Response(200); + $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent') + ->setConstructorArgs([$t]) + ->getMockForAbstractClass(); + $e->intercept($r); + $this->assertSame($t->response, $r); + $this->assertSame($t->response, $e->getResponse()); + $this->assertTrue($e->isPropagationStopped()); + } + + public function testReturnsNumberOfRetries() + { + $t = new Transaction(new Client(), new Request('GET', '/')); + $t->retries = 2; + $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent') + ->setConstructorArgs([$t]) + ->getMockForAbstractClass(); + $this->assertEquals(2, $e->getRetryCount()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/BeforeEventTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/BeforeEventTest.php new file mode 100644 index 0000000..469e4e2 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/BeforeEventTest.php @@ -0,0 +1,26 @@ +exception = new \Exception('foo'); + $e = new BeforeEvent($t); + $response = new Response(200); + $e->intercept($response); + $this->assertTrue($e->isPropagationStopped()); + $this->assertSame($t->response, $response); + $this->assertNull($t->exception); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/EmitterTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/EmitterTest.php new file mode 100644 index 0000000..5b7061b --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/EmitterTest.php @@ -0,0 +1,363 @@ +emitter = new Emitter(); + $this->listener = new TestEventListener(); + } + + protected function tearDown() + { + $this->emitter = null; + $this->listener = null; + } + + public function testInitialState() + { + $this->assertEquals(array(), $this->emitter->listeners()); + } + + public function testAddListener() + { + $this->emitter->on('pre.foo', array($this->listener, 'preFoo')); + $this->emitter->on('post.foo', array($this->listener, 'postFoo')); + $this->assertTrue($this->emitter->hasListeners(self::preFoo)); + $this->assertTrue($this->emitter->hasListeners(self::preFoo)); + $this->assertCount(1, $this->emitter->listeners(self::postFoo)); + $this->assertCount(1, $this->emitter->listeners(self::postFoo)); + $this->assertCount(2, $this->emitter->listeners()); + } + + public function testGetListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener1->name = '1'; + $listener2->name = '2'; + $listener3->name = '3'; + + $this->emitter->on('pre.foo', array($listener1, 'preFoo'), -10); + $this->emitter->on('pre.foo', array($listener2, 'preFoo'), 10); + $this->emitter->on('pre.foo', array($listener3, 'preFoo')); + + $expected = array( + array($listener2, 'preFoo'), + array($listener3, 'preFoo'), + array($listener1, 'preFoo'), + ); + + $this->assertSame($expected, $this->emitter->listeners('pre.foo')); + } + + public function testGetAllListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener4 = new TestEventListener(); + $listener5 = new TestEventListener(); + $listener6 = new TestEventListener(); + + $this->emitter->on('pre.foo', [$listener1, 'preFoo'], -10); + $this->emitter->on('pre.foo', [$listener2, 'preFoo']); + $this->emitter->on('pre.foo', [$listener3, 'preFoo'], 10); + $this->emitter->on('post.foo', [$listener4, 'preFoo'], -10); + $this->emitter->on('post.foo', [$listener5, 'preFoo']); + $this->emitter->on('post.foo', [$listener6, 'preFoo'], 10); + + $expected = [ + 'pre.foo' => [[$listener3, 'preFoo'], [$listener2, 'preFoo'], [$listener1, 'preFoo']], + 'post.foo' => [[$listener6, 'preFoo'], [$listener5, 'preFoo'], [$listener4, 'preFoo']], + ]; + + $this->assertSame($expected, $this->emitter->listeners()); + } + + public function testDispatch() + { + $this->emitter->on('pre.foo', array($this->listener, 'preFoo')); + $this->emitter->on('post.foo', array($this->listener, 'postFoo')); + $this->emitter->emit(self::preFoo, $this->getEvent()); + $this->assertTrue($this->listener->preFooInvoked); + $this->assertFalse($this->listener->postFooInvoked); + $this->assertInstanceOf('GuzzleHttp\Event\EventInterface', $this->emitter->emit(self::preFoo, $this->getEvent())); + $event = $this->getEvent(); + $return = $this->emitter->emit(self::preFoo, $event); + $this->assertSame($event, $return); + } + + public function testDispatchForClosure() + { + $invoked = 0; + $listener = function () use (&$invoked) { + $invoked++; + }; + $this->emitter->on('pre.foo', $listener); + $this->emitter->on('post.foo', $listener); + $this->emitter->emit(self::preFoo, $this->getEvent()); + $this->assertEquals(1, $invoked); + } + + public function testStopEventPropagation() + { + $otherListener = new TestEventListener(); + + // postFoo() stops the propagation, so only one listener should + // be executed + // Manually set priority to enforce $this->listener to be called first + $this->emitter->on('post.foo', array($this->listener, 'postFoo'), 10); + $this->emitter->on('post.foo', array($otherListener, 'preFoo')); + $this->emitter->emit(self::postFoo, $this->getEvent()); + $this->assertTrue($this->listener->postFooInvoked); + $this->assertFalse($otherListener->postFooInvoked); + } + + public function testDispatchByPriority() + { + $invoked = array(); + $listener1 = function () use (&$invoked) { + $invoked[] = '1'; + }; + $listener2 = function () use (&$invoked) { + $invoked[] = '2'; + }; + $listener3 = function () use (&$invoked) { + $invoked[] = '3'; + }; + $this->emitter->on('pre.foo', $listener1, -10); + $this->emitter->on('pre.foo', $listener2); + $this->emitter->on('pre.foo', $listener3, 10); + $this->emitter->emit(self::preFoo, $this->getEvent()); + $this->assertEquals(array('3', '2', '1'), $invoked); + } + + public function testRemoveListener() + { + $this->emitter->on('pre.bar', [$this->listener, 'preFoo']); + $this->assertNotEmpty($this->emitter->listeners(self::preBar)); + $this->emitter->removeListener('pre.bar', [$this->listener, 'preFoo']); + $this->assertEmpty($this->emitter->listeners(self::preBar)); + $this->emitter->removeListener('notExists', [$this->listener, 'preFoo']); + } + + public function testAddSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->emitter->attach($eventSubscriber); + $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); + $this->assertNotEmpty($this->emitter->listeners(self::postFoo)); + } + + public function testAddSubscriberWithMultiple() + { + $eventSubscriber = new TestEventSubscriberWithMultiple(); + $this->emitter->attach($eventSubscriber); + $listeners = $this->emitter->listeners('pre.foo'); + $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); + $this->assertCount(2, $listeners); + } + + public function testAddSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriber(); + $this->emitter->attach($eventSubscriber); + + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->emitter->attach($eventSubscriber); + + $listeners = $this->emitter->listeners('pre.foo'); + $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertInstanceOf('GuzzleHttp\Tests\Event\TestEventSubscriberWithPriorities', $listeners[0][0]); + } + + public function testdetach() + { + $eventSubscriber = new TestEventSubscriber(); + $this->emitter->attach($eventSubscriber); + $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); + $this->assertNotEmpty($this->emitter->listeners(self::postFoo)); + $this->emitter->detach($eventSubscriber); + $this->assertEmpty($this->emitter->listeners(self::preFoo)); + $this->assertEmpty($this->emitter->listeners(self::postFoo)); + } + + public function testdetachWithPriorities() + { + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->emitter->attach($eventSubscriber); + $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); + $this->assertNotEmpty($this->emitter->listeners(self::postFoo)); + $this->emitter->detach($eventSubscriber); + $this->assertEmpty($this->emitter->listeners(self::preFoo)); + $this->assertEmpty($this->emitter->listeners(self::postFoo)); + } + + public function testEventReceivesEventNameAsArgument() + { + $listener = new TestWithDispatcher(); + $this->emitter->on('test', array($listener, 'foo')); + $this->assertNull($listener->name); + $this->emitter->emit('test', $this->getEvent()); + $this->assertEquals('test', $listener->name); + } + + /** + * @see https://bugs.php.net/bug.php?id=62976 + * + * This bug affects: + * - The PHP 5.3 branch for versions < 5.3.18 + * - The PHP 5.4 branch for versions < 5.4.8 + * - The PHP 5.5 branch is not affected + */ + public function testWorkaroundForPhpBug62976() + { + $dispatcher = new Emitter(); + $dispatcher->on('bug.62976', new CallableClass()); + $dispatcher->removeListener('bug.62976', function () {}); + $this->assertNotEmpty($dispatcher->listeners('bug.62976')); + } + + public function testRegistersEventsOnce() + { + $this->emitter->once('pre.foo', array($this->listener, 'preFoo')); + $this->emitter->on('pre.foo', array($this->listener, 'preFoo')); + $this->assertCount(2, $this->emitter->listeners(self::preFoo)); + $this->emitter->emit(self::preFoo, $this->getEvent()); + $this->assertTrue($this->listener->preFooInvoked); + $this->assertCount(1, $this->emitter->listeners(self::preFoo)); + } + + public function testReturnsEmptyArrayForNonExistentEvent() + { + $this->assertEquals([], $this->emitter->listeners('doesnotexist')); + } + + public function testCanAddFirstAndLastListeners() + { + $b = ''; + $this->emitter->on('foo', function () use (&$b) { $b .= 'a'; }, 'first'); // 1 + $this->emitter->on('foo', function () use (&$b) { $b .= 'b'; }, 'last'); // 0 + $this->emitter->on('foo', function () use (&$b) { $b .= 'c'; }, 'first'); // 2 + $this->emitter->on('foo', function () use (&$b) { $b .= 'd'; }, 'first'); // 3 + $this->emitter->on('foo', function () use (&$b) { $b .= 'e'; }, 'first'); // 4 + $this->emitter->on('foo', function () use (&$b) { $b .= 'f'; }); // 0 + $this->emitter->emit('foo', $this->getEvent()); + $this->assertEquals('edcabf', $b); + } + + /** + * @return \GuzzleHttp\Event\EventInterface + */ + private function getEvent() + { + return $this->getMockBuilder('GuzzleHttp\Event\AbstractEvent') + ->getMockForAbstractClass(); + } +} + +class CallableClass +{ + public function __invoke() + { + } +} + +class TestEventListener +{ + public $preFooInvoked = false; + public $postFooInvoked = false; + + /* Listener methods */ + + public function preFoo(EventInterface $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(EventInterface $e) + { + $this->postFooInvoked = true; + + $e->stopPropagation(); + } + + /** + * @expectedException \PHPUnit_Framework_Error_Deprecated + */ + public function testHasDeprecatedAddListener() + { + $emitter = new Emitter(); + $emitter->addListener('foo', function () {}); + } + + /** + * @expectedException \PHPUnit_Framework_Error_Deprecated + */ + public function testHasDeprecatedAddSubscriber() + { + $emitter = new Emitter(); + $emitter->addSubscriber('foo', new TestEventSubscriber()); + } +} + +class TestWithDispatcher +{ + public $name; + + public function foo(EventInterface $e, $name) + { + $this->name = $name; + } +} + +class TestEventSubscriber extends TestEventListener implements SubscriberInterface +{ + public function getEvents() + { + return [ + 'pre.foo' => ['preFoo'], + 'post.foo' => ['postFoo'] + ]; + } +} + +class TestEventSubscriberWithPriorities extends TestEventListener implements SubscriberInterface +{ + public function getEvents() + { + return [ + 'pre.foo' => ['preFoo', 10], + 'post.foo' => ['postFoo'] + ]; + } +} + +class TestEventSubscriberWithMultiple extends TestEventListener implements SubscriberInterface +{ + public function getEvents() + { + return ['pre.foo' => [['preFoo', 10],['preFoo', 20]]]; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ErrorEventTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ErrorEventTest.php new file mode 100644 index 0000000..e91b7f0 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ErrorEventTest.php @@ -0,0 +1,23 @@ +request); + $t->exception = $except; + $e = new ErrorEvent($t); + $this->assertSame($e->getException(), $t->exception); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/HasEmitterTraitTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/HasEmitterTraitTest.php new file mode 100644 index 0000000..4709918 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/HasEmitterTraitTest.php @@ -0,0 +1,27 @@ +getMockBuilder('GuzzleHttp\Tests\Event\AbstractHasEmitter') + ->getMockForAbstractClass(); + + $result = $mock->getEmitter(); + $this->assertInstanceOf('GuzzleHttp\Event\EmitterInterface', $result); + $result2 = $mock->getEmitter(); + $this->assertSame($result, $result2); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ListenerAttacherTraitTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ListenerAttacherTraitTest.php new file mode 100644 index 0000000..0b5d348 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ListenerAttacherTraitTest.php @@ -0,0 +1,92 @@ +listeners = $this->prepareListeners($args, ['foo', 'bar']); + $this->attachListeners($this, $this->listeners); + } +} + +class ListenerAttacherTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testRegistersEvents() + { + $fn = function () {}; + $o = new ObjectWithEvents([ + 'foo' => $fn, + 'bar' => $fn, + ]); + + $this->assertEquals([ + ['name' => 'foo', 'fn' => $fn, 'priority' => 0, 'once' => false], + ['name' => 'bar', 'fn' => $fn, 'priority' => 0, 'once' => false], + ], $o->listeners); + + $this->assertCount(1, $o->getEmitter()->listeners('foo')); + $this->assertCount(1, $o->getEmitter()->listeners('bar')); + } + + public function testRegistersEventsWithPriorities() + { + $fn = function () {}; + $o = new ObjectWithEvents([ + 'foo' => ['fn' => $fn, 'priority' => 99, 'once' => true], + 'bar' => ['fn' => $fn, 'priority' => 50], + ]); + + $this->assertEquals([ + ['name' => 'foo', 'fn' => $fn, 'priority' => 99, 'once' => true], + ['name' => 'bar', 'fn' => $fn, 'priority' => 50, 'once' => false], + ], $o->listeners); + } + + public function testRegistersMultipleEvents() + { + $fn = function () {}; + $eventArray = [['fn' => $fn], ['fn' => $fn]]; + $o = new ObjectWithEvents([ + 'foo' => $eventArray, + 'bar' => $eventArray, + ]); + + $this->assertEquals([ + ['name' => 'foo', 'fn' => $fn, 'priority' => 0, 'once' => false], + ['name' => 'foo', 'fn' => $fn, 'priority' => 0, 'once' => false], + ['name' => 'bar', 'fn' => $fn, 'priority' => 0, 'once' => false], + ['name' => 'bar', 'fn' => $fn, 'priority' => 0, 'once' => false], + ], $o->listeners); + + $this->assertCount(2, $o->getEmitter()->listeners('foo')); + $this->assertCount(2, $o->getEmitter()->listeners('bar')); + } + + public function testRegistersEventsWithOnce() + { + $called = 0; + $fn = function () use (&$called) { $called++; }; + $o = new ObjectWithEvents(['foo' => ['fn' => $fn, 'once' => true]]); + $ev = $this->getMock('GuzzleHttp\Event\EventInterface'); + $o->getEmitter()->emit('foo', $ev); + $o->getEmitter()->emit('foo', $ev); + $this->assertEquals(1, $called); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesEvents() + { + new ObjectWithEvents(['foo' => 'bar']); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ProgressEventTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ProgressEventTest.php new file mode 100644 index 0000000..664f8b6 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/ProgressEventTest.php @@ -0,0 +1,25 @@ +assertSame($t->request, $p->getRequest()); + $this->assertSame($t->client, $p->getClient()); + $this->assertEquals(2, $p->downloadSize); + $this->assertEquals(1, $p->downloaded); + $this->assertEquals(3, $p->uploadSize); + $this->assertEquals(0, $p->uploaded); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/RequestEventsTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/RequestEventsTest.php new file mode 100644 index 0000000..b3b9666 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Event/RequestEventsTest.php @@ -0,0 +1,74 @@ + [$cb]]], + [ + ['complete' => $cb], + ['complete'], + $cb, + ['complete' => [$cb, $cb]] + ], + [ + ['prepare' => []], + ['error', 'foo'], + $cb, + [ + 'prepare' => [], + 'error' => [$cb], + 'foo' => [$cb] + ] + ], + [ + ['prepare' => []], + ['prepare'], + $cb, + [ + 'prepare' => [$cb] + ] + ], + [ + ['prepare' => ['fn' => $cb]], + ['prepare'], $cb, + [ + 'prepare' => [ + ['fn' => $cb], + $cb + ] + ] + ], + ]; + } + + /** + * @dataProvider prepareEventProvider + */ + public function testConvertsEventArrays( + array $in, + array $events, + $add, + array $out + ) { + $result = RequestEvents::convertEventArray($in, $events, $add); + $this->assertEquals($out, $result); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesEventFormat() + { + RequestEvents::convertEventArray(['foo' => false], ['foo'], []); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/ParseExceptionTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/ParseExceptionTest.php new file mode 100644 index 0000000..4ff9bfb --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/ParseExceptionTest.php @@ -0,0 +1,20 @@ +assertSame($res, $e->getResponse()); + $this->assertEquals('foo', $e->getMessage()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/RequestExceptionTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/RequestExceptionTest.php new file mode 100644 index 0000000..bea9077 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/RequestExceptionTest.php @@ -0,0 +1,83 @@ +assertSame($req, $e->getRequest()); + $this->assertSame($res, $e->getResponse()); + $this->assertTrue($e->hasResponse()); + $this->assertEquals('foo', $e->getMessage()); + } + + public function testCreatesGenerateException() + { + $e = RequestException::create(new Request('GET', '/')); + $this->assertEquals('Error completing request', $e->getMessage()); + $this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $e); + } + + public function testCreatesClientErrorResponseException() + { + $e = RequestException::create(new Request('GET', '/'), new Response(400)); + $this->assertEquals( + 'Client error response [url] / [status code] 400 [reason phrase] Bad Request', + $e->getMessage() + ); + $this->assertInstanceOf('GuzzleHttp\Exception\ClientException', $e); + } + + public function testCreatesServerErrorResponseException() + { + $e = RequestException::create(new Request('GET', '/'), new Response(500)); + $this->assertEquals( + 'Server error response [url] / [status code] 500 [reason phrase] Internal Server Error', + $e->getMessage() + ); + $this->assertInstanceOf('GuzzleHttp\Exception\ServerException', $e); + } + + public function testCreatesGenericErrorResponseException() + { + $e = RequestException::create(new Request('GET', '/'), new Response(600)); + $this->assertEquals( + 'Unsuccessful response [url] / [status code] 600 [reason phrase] ', + $e->getMessage() + ); + $this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $e); + } + + public function testHasStatusCodeAsExceptionCode() { + $e = RequestException::create(new Request('GET', '/'), new Response(442)); + $this->assertEquals(442, $e->getCode()); + } + + public function testWrapsRequestExceptions() + { + $e = new \Exception('foo'); + $r = new Request('GET', 'http://www.oo.com'); + $ex = RequestException::wrapException($r, $e); + $this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $ex); + $this->assertSame($e, $ex->getPrevious()); + } + + public function testWrapsConnectExceptions() + { + $e = new ConnectException('foo'); + $r = new Request('GET', 'http://www.oo.com'); + $ex = RequestException::wrapException($r, $e); + $this->assertInstanceOf('GuzzleHttp\Exception\ConnectException', $ex); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/XmlParseExceptionTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/XmlParseExceptionTest.php new file mode 100644 index 0000000..51b9742 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Exception/XmlParseExceptionTest.php @@ -0,0 +1,19 @@ +assertSame($error, $e->getError()); + $this->assertEquals('foo', $e->getMessage()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/IntegrationTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/IntegrationTest.php new file mode 100644 index 0000000..e26c64d --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/IntegrationTest.php @@ -0,0 +1,123 @@ +createRequest( + 'GET', + Server::$url, + [ + 'timeout' => 1, + 'connect_timeout' => 1, + 'proxy' => 'http://127.0.0.1:123/foo' + ] + ); + + $events = []; + $fn = function(AbstractTransferEvent $event) use (&$events) { + $events[] = [ + get_class($event), + $event->hasResponse(), + $event->getResponse() + ]; + }; + + $pool = new Pool($c, [$r], [ + 'error' => $fn, + 'end' => $fn + ]); + + $pool->wait(); + + $this->assertCount(2, $events); + $this->assertEquals('GuzzleHttp\Event\ErrorEvent', $events[0][0]); + $this->assertFalse($events[0][1]); + $this->assertNull($events[0][2]); + + $this->assertEquals('GuzzleHttp\Event\EndEvent', $events[1][0]); + $this->assertFalse($events[1][1]); + $this->assertNull($events[1][2]); + } + + /** + * @issue https://github.com/guzzle/guzzle/issues/866 + */ + public function testProperyGetsTransferStats() + { + $transfer = []; + Server::enqueue([new Response(200)]); + $c = new Client(); + $response = $c->get(Server::$url . '/foo', [ + 'events' => [ + 'end' => function (EndEvent $e) use (&$transfer) { + $transfer = $e->getTransferInfo(); + } + ] + ]); + $this->assertEquals(Server::$url . '/foo', $response->getEffectiveUrl()); + $this->assertNotEmpty($transfer); + $this->assertArrayHasKey('url', $transfer); + } + + public function testNestedFutureResponsesAreResolvedWhenSending() + { + $c = new Client(); + $total = 3; + Server::enqueue([ + new Response(200), + new Response(201), + new Response(202) + ]); + $c->getEmitter()->on( + 'complete', + function (CompleteEvent $e) use (&$total) { + if (--$total) { + $e->retry(); + } + } + ); + $response = $c->get(Server::$url); + $this->assertEquals(202, $response->getStatusCode()); + $this->assertEquals('GuzzleHttp\Message\Response', get_class($response)); + } + + public function testNestedFutureErrorsAreResolvedWhenSending() + { + $c = new Client(); + $total = 3; + Server::enqueue([ + new Response(500), + new Response(501), + new Response(502) + ]); + $c->getEmitter()->on( + 'error', + function (ErrorEvent $e) use (&$total) { + if (--$total) { + $e->retry(); + } + } + ); + try { + $c->get(Server::$url); + $this->fail('Did not throw!'); + } catch (RequestException $e) { + $this->assertEquals(502, $e->getResponse()->getStatusCode()); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/AbstractMessageTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/AbstractMessageTest.php new file mode 100644 index 0000000..f02a576 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/AbstractMessageTest.php @@ -0,0 +1,269 @@ +assertEquals(1.1, $m->getProtocolVersion()); + } + + public function testHasHeaders() + { + $m = new Request('GET', 'http://foo.com'); + $this->assertFalse($m->hasHeader('foo')); + $m->addHeader('foo', 'bar'); + $this->assertTrue($m->hasHeader('foo')); + } + + public function testInitializesMessageWithProtocolVersionOption() + { + $m = new Request('GET', '/', [], null, [ + 'protocol_version' => '10' + ]); + $this->assertEquals(10, $m->getProtocolVersion()); + } + + public function testHasBody() + { + $m = new Request('GET', 'http://foo.com'); + $this->assertNull($m->getBody()); + $s = Stream::factory('test'); + $m->setBody($s); + $this->assertSame($s, $m->getBody()); + $this->assertFalse($m->hasHeader('Content-Length')); + } + + public function testCanRemoveBodyBySettingToNullAndRemovesCommonBodyHeaders() + { + $m = new Request('GET', 'http://foo.com'); + $m->setBody(Stream::factory('foo')); + $m->setHeader('Content-Length', 3); + $m->setHeader('Transfer-Encoding', 'chunked'); + $m->setBody(null); + $this->assertNull($m->getBody()); + $this->assertFalse($m->hasHeader('Content-Length')); + $this->assertFalse($m->hasHeader('Transfer-Encoding')); + } + + public function testCastsToString() + { + $m = new Request('GET', 'http://foo.com'); + $m->setHeader('foo', 'bar'); + $m->setBody(Stream::factory('baz')); + $this->assertEquals("GET / HTTP/1.1\r\nHost: foo.com\r\nfoo: bar\r\n\r\nbaz", (string) $m); + } + + public function parseParamsProvider() + { + $res1 = array( + array( + '', + 'rel' => 'front', + 'type' => 'image/jpeg', + ), + array( + '', + 'rel' => 'back', + 'type' => 'image/jpeg', + ), + ); + + return array( + array( + '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"', + $res1 + ), + array( + '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"', + $res1 + ), + array( + 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"', + array( + array('foo' => 'baz', 'bar' => '123'), + array('boo'), + array('test' => '123'), + array('foobar' => 'foo;bar') + ) + ), + array( + '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"', + array( + array('', 'rel' => 'side', 'type' => 'image/jpeg'), + array('', 'rel' => 'side', 'type' => 'image/jpeg') + ) + ), + array( + '', + array() + ) + ); + } + + /** + * @dataProvider parseParamsProvider + */ + public function testParseParams($header, $result) + { + $request = new Request('GET', '/', ['foo' => $header]); + $this->assertEquals($result, Request::parseHeader($request, 'foo')); + } + + public function testAddsHeadersWhenNotPresent() + { + $h = new Request('GET', 'http://foo.com'); + $h->addHeader('foo', 'bar'); + $this->assertInternalType('string', $h->getHeader('foo')); + $this->assertEquals('bar', $h->getHeader('foo')); + } + + public function testAddsHeadersWhenPresentSameCase() + { + $h = new Request('GET', 'http://foo.com'); + $h->addHeader('foo', 'bar'); + $h->addHeader('foo', 'baz'); + $this->assertEquals('bar, baz', $h->getHeader('foo')); + $this->assertEquals(['bar', 'baz'], $h->getHeaderAsArray('foo')); + } + + public function testAddsMultipleHeaders() + { + $h = new Request('GET', 'http://foo.com'); + $h->addHeaders([ + 'foo' => ' bar', + 'baz' => [' bam ', 'boo'] + ]); + $this->assertEquals([ + 'foo' => ['bar'], + 'baz' => ['bam', 'boo'], + 'Host' => ['foo.com'] + ], $h->getHeaders()); + } + + public function testAddsHeadersWhenPresentDifferentCase() + { + $h = new Request('GET', 'http://foo.com'); + $h->addHeader('Foo', 'bar'); + $h->addHeader('fOO', 'baz'); + $this->assertEquals('bar, baz', $h->getHeader('foo')); + } + + public function testAddsHeadersWithArray() + { + $h = new Request('GET', 'http://foo.com'); + $h->addHeader('Foo', ['bar', 'baz']); + $this->assertEquals('bar, baz', $h->getHeader('foo')); + } + + public function testGetHeadersReturnsAnArrayOfOverTheWireHeaderValues() + { + $h = new Request('GET', 'http://foo.com'); + $h->addHeader('foo', 'bar'); + $h->addHeader('Foo', 'baz'); + $h->addHeader('boO', 'test'); + $result = $h->getHeaders(); + $this->assertInternalType('array', $result); + $this->assertArrayHasKey('Foo', $result); + $this->assertArrayNotHasKey('foo', $result); + $this->assertArrayHasKey('boO', $result); + $this->assertEquals(['bar', 'baz'], $result['Foo']); + $this->assertEquals(['test'], $result['boO']); + } + + public function testSetHeaderOverwritesExistingValues() + { + $h = new Request('GET', 'http://foo.com'); + $h->setHeader('foo', 'bar'); + $this->assertEquals('bar', $h->getHeader('foo')); + $h->setHeader('Foo', 'baz'); + $this->assertEquals('baz', $h->getHeader('foo')); + $this->assertArrayHasKey('Foo', $h->getHeaders()); + } + + public function testSetHeaderOverwritesExistingValuesUsingHeaderArray() + { + $h = new Request('GET', 'http://foo.com'); + $h->setHeader('foo', ['bar']); + $this->assertEquals('bar', $h->getHeader('foo')); + } + + public function testSetHeaderOverwritesExistingValuesUsingArray() + { + $h = new Request('GET', 'http://foo.com'); + $h->setHeader('foo', ['bar']); + $this->assertEquals('bar', $h->getHeader('foo')); + } + + public function testSetHeadersOverwritesAllHeaders() + { + $h = new Request('GET', 'http://foo.com'); + $h->setHeader('foo', 'bar'); + $h->setHeaders(['foo' => 'a', 'boo' => 'b']); + $this->assertEquals(['foo' => ['a'], 'boo' => ['b']], $h->getHeaders()); + } + + public function testChecksIfCaseInsensitiveHeaderIsPresent() + { + $h = new Request('GET', 'http://foo.com'); + $h->setHeader('foo', 'bar'); + $this->assertTrue($h->hasHeader('foo')); + $this->assertTrue($h->hasHeader('Foo')); + $h->setHeader('fOo', 'bar'); + $this->assertTrue($h->hasHeader('Foo')); + } + + public function testRemovesHeaders() + { + $h = new Request('GET', 'http://foo.com'); + $h->setHeader('foo', 'bar'); + $h->removeHeader('foo'); + $this->assertFalse($h->hasHeader('foo')); + $h->setHeader('Foo', 'bar'); + $h->removeHeader('FOO'); + $this->assertFalse($h->hasHeader('foo')); + } + + public function testReturnsCorrectTypeWhenMissing() + { + $h = new Request('GET', 'http://foo.com'); + $this->assertInternalType('string', $h->getHeader('foo')); + $this->assertInternalType('array', $h->getHeaderAsArray('foo')); + } + + public function testSetsIntegersAndFloatsAsHeaders() + { + $h = new Request('GET', 'http://foo.com'); + $h->setHeader('foo', 10); + $h->setHeader('bar', 10.5); + $h->addHeader('foo', 10); + $h->addHeader('bar', 10.5); + $this->assertSame('10, 10', $h->getHeader('foo')); + $this->assertSame('10.5, 10.5', $h->getHeader('bar')); + } + + public function testGetsResponseStartLine() + { + $m = new Response(200); + $this->assertEquals('HTTP/1.1 200 OK', Response::getStartLine($m)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testThrowsWhenMessageIsUnknown() + { + $m = $this->getMockBuilder('GuzzleHttp\Message\AbstractMessage') + ->getMockForAbstractClass(); + AbstractMessage::getStartLine($m); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/FutureResponseTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/FutureResponseTest.php new file mode 100644 index 0000000..771631d --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/FutureResponseTest.php @@ -0,0 +1,160 @@ +foo; + } + + public function testDoesTheSameAsResponseWhenDereferenced() + { + $str = Stream::factory('foo'); + $response = new Response(200, ['Foo' => 'bar'], $str); + $future = MockTest::createFuture(function () use ($response) { + return $response; + }); + $this->assertFalse($this->readAttribute($future, 'isRealized')); + $this->assertEquals(200, $future->getStatusCode()); + $this->assertTrue($this->readAttribute($future, 'isRealized')); + // Deref again does nothing. + $future->wait(); + $this->assertTrue($this->readAttribute($future, 'isRealized')); + $this->assertEquals('bar', $future->getHeader('Foo')); + $this->assertEquals(['bar'], $future->getHeaderAsarray('Foo')); + $this->assertSame($response->getHeaders(), $future->getHeaders()); + $this->assertSame( + $response->getBody(), + $future->getBody() + ); + $this->assertSame( + $response->getProtocolVersion(), + $future->getProtocolVersion() + ); + $this->assertSame( + $response->getEffectiveUrl(), + $future->getEffectiveUrl() + ); + $future->setEffectiveUrl('foo'); + $this->assertEquals('foo', $response->getEffectiveUrl()); + $this->assertSame( + $response->getReasonPhrase(), + $future->getReasonPhrase() + ); + + $this->assertTrue($future->hasHeader('foo')); + + $future->removeHeader('Foo'); + $this->assertFalse($future->hasHeader('foo')); + $this->assertFalse($response->hasHeader('foo')); + + $future->setBody(Stream::factory('true')); + $this->assertEquals('true', (string) $response->getBody()); + $this->assertTrue($future->json()); + $this->assertSame((string) $response, (string) $future); + + $future->setBody(Stream::factory('c')); + $this->assertEquals('c', (string) $future->xml()->b); + + $future->addHeader('a', 'b'); + $this->assertEquals('b', $future->getHeader('a')); + + $future->addHeaders(['a' => '2']); + $this->assertEquals('b, 2', $future->getHeader('a')); + + $future->setHeader('a', '2'); + $this->assertEquals('2', $future->getHeader('a')); + + $future->setHeaders(['a' => '3']); + $this->assertEquals(['a' => ['3']], $future->getHeaders()); + } + + public function testCanDereferenceManually() + { + $response = new Response(200, ['Foo' => 'bar']); + $future = MockTest::createFuture(function () use ($response) { + return $response; + }); + $this->assertSame($response, $future->wait()); + $this->assertTrue($this->readAttribute($future, 'isRealized')); + } + + public function testCanCancel() + { + $c = false; + $deferred = new Deferred(); + $future = new FutureResponse( + $deferred->promise(), + function () {}, + function () use (&$c) { + $c = true; + return true; + } + ); + + $this->assertFalse($this->readAttribute($future, 'isRealized')); + $future->cancel(); + $this->assertTrue($this->readAttribute($future, 'isRealized')); + $future->cancel(); + } + + public function testCanCancelButReturnsFalseForNoCancelFunction() + { + $future = MockTest::createFuture(function () {}); + $future->cancel(); + $this->assertTrue($this->readAttribute($future, 'isRealized')); + } + + /** + * @expectedException \GuzzleHttp\Ring\Exception\CancelledFutureAccessException + */ + public function testAccessingCancelledResponseThrows() + { + $future = MockTest::createFuture(function () {}); + $future->cancel(); + $future->getStatusCode(); + } + + public function testExceptionInToStringTriggersError() + { + $future = MockTest::createFuture(function () { + throw new \Exception('foo'); + }); + $err = ''; + set_error_handler(function () use (&$err) { + $err = func_get_args()[1]; + }); + echo $future; + restore_error_handler(); + $this->assertContains('foo', $err); + } + + public function testProxiesSetters() + { + $str = Stream::factory('foo'); + $response = new Response(200, ['Foo' => 'bar'], $str); + $future = MockTest::createFuture(function () use ($response) { + return $response; + }); + + $future->setStatusCode(202); + $this->assertEquals(202, $future->getStatusCode()); + $this->assertEquals(202, $response->getStatusCode()); + + $future->setReasonPhrase('foo'); + $this->assertEquals('foo', $future->getReasonPhrase()); + $this->assertEquals('foo', $response->getReasonPhrase()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/MessageFactoryTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/MessageFactoryTest.php new file mode 100644 index 0000000..390f010 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/MessageFactoryTest.php @@ -0,0 +1,601 @@ +createResponse(200, ['foo' => 'bar'], 'test', [ + 'protocol_version' => 1.0 + ]); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(['foo' => ['bar']], $response->getHeaders()); + $this->assertEquals('test', $response->getBody()); + $this->assertEquals(1.0, $response->getProtocolVersion()); + } + + public function testCreatesRequestFromMessage() + { + $f = new MessageFactory(); + $req = $f->fromMessage("GET / HTTP/1.1\r\nBaz: foo\r\n\r\n"); + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('/', $req->getPath()); + $this->assertEquals('foo', $req->getHeader('Baz')); + $this->assertNull($req->getBody()); + } + + public function testCreatesRequestFromMessageWithBody() + { + $req = (new MessageFactory())->fromMessage("GET / HTTP/1.1\r\nBaz: foo\r\n\r\ntest"); + $this->assertEquals('test', $req->getBody()); + } + + public function testCreatesRequestWithPostBody() + { + $req = (new MessageFactory())->createRequest('GET', 'http://www.foo.com', ['body' => ['abc' => '123']]); + $this->assertEquals('abc=123', $req->getBody()); + } + + public function testCreatesRequestWithPostBodyScalars() + { + $req = (new MessageFactory())->createRequest( + 'GET', + 'http://www.foo.com', + ['body' => [ + 'abc' => true, + '123' => false, + 'foo' => null, + 'baz' => 10, + 'bam' => 1.5, + 'boo' => [1]] + ] + ); + $this->assertEquals( + 'abc=1&123=&foo&baz=10&bam=1.5&boo%5B0%5D=1', + (string) $req->getBody() + ); + } + + public function testCreatesRequestWithPostBodyAndPostFiles() + { + $pf = fopen(__FILE__, 'r'); + $pfi = new PostFile('ghi', 'abc', __FILE__); + $req = (new MessageFactory())->createRequest('GET', 'http://www.foo.com', [ + 'body' => [ + 'abc' => '123', + 'def' => $pf, + 'ghi' => $pfi + ] + ]); + $this->assertInstanceOf('GuzzleHttp\Post\PostBody', $req->getBody()); + $s = (string) $req; + $this->assertContains('testCreatesRequestWithPostBodyAndPostFiles', $s); + $this->assertContains('multipart/form-data', $s); + $this->assertTrue(in_array($pfi, $req->getBody()->getFiles(), true)); + } + + public function testCreatesResponseFromMessage() + { + $response = (new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals('4', $response->getHeader('Content-Length')); + $this->assertEquals('test', $response->getBody(true)); + } + + public function testCanCreateHeadResponses() + { + $response = (new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n"); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals(null, $response->getBody()); + $this->assertEquals('4', $response->getHeader('Content-Length')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFactoryRequiresMessageForRequest() + { + (new MessageFactory())->fromMessage(''); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage foo + */ + public function testValidatesOptionsAreImplemented() + { + (new MessageFactory())->createRequest('GET', 'http://test.com', ['foo' => 'bar']); + } + + public function testOptionsAddsRequestOptions() + { + $request = (new MessageFactory())->createRequest( + 'GET', 'http://test.com', ['config' => ['baz' => 'bar']] + ); + $this->assertEquals('bar', $request->getConfig()->get('baz')); + } + + public function testCanDisableRedirects() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => false]); + $this->assertEmpty($request->getEmitter()->listeners('complete')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesRedirects() + { + (new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => 'foo']); + } + + public function testCanEnableStrictRedirectsAndSpecifyMax() + { + $request = (new MessageFactory())->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 10, 'strict' => true] + ]); + $this->assertTrue($request->getConfig()['redirect']['strict']); + $this->assertEquals(10, $request->getConfig()['redirect']['max']); + } + + public function testCanAddCookiesFromHash() + { + $request = (new MessageFactory())->createRequest('GET', 'http://www.test.com/', [ + 'cookies' => ['Foo' => 'Bar'] + ]); + $cookies = null; + foreach ($request->getEmitter()->listeners('before') as $l) { + if ($l[0] instanceof Cookie) { + $cookies = $l[0]; + break; + } + } + if (!$cookies) { + $this->fail('Did not add cookie listener'); + } else { + $this->assertCount(1, $cookies->getCookieJar()); + } + } + + public function testAddsCookieUsingTrue() + { + $factory = new MessageFactory(); + $request1 = $factory->createRequest('GET', '/', ['cookies' => true]); + $request2 = $factory->createRequest('GET', '/', ['cookies' => true]); + $listeners = function ($r) { + return array_filter($r->getEmitter()->listeners('before'), function ($l) { + return $l[0] instanceof Cookie; + }); + }; + $this->assertSame($listeners($request1), $listeners($request2)); + } + + public function testAddsCookieFromCookieJar() + { + $jar = new CookieJar(); + $request = (new MessageFactory())->createRequest('GET', '/', ['cookies' => $jar]); + foreach ($request->getEmitter()->listeners('before') as $l) { + if ($l[0] instanceof Cookie) { + $this->assertSame($jar, $l[0]->getCookieJar()); + } + } + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesCookies() + { + (new MessageFactory())->createRequest('GET', '/', ['cookies' => 'baz']); + } + + public function testCanAddQuery() + { + $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ + 'query' => ['Foo' => 'Bar'] + ]); + $this->assertEquals('Bar', $request->getQuery()->get('Foo')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesQuery() + { + (new MessageFactory())->createRequest('GET', 'http://foo.com', [ + 'query' => 'foo' + ]); + } + + public function testCanSetDefaultQuery() + { + $request = (new MessageFactory())->createRequest('GET', 'http://foo.com?test=abc', [ + 'query' => ['Foo' => 'Bar', 'test' => 'def'] + ]); + $this->assertEquals('Bar', $request->getQuery()->get('Foo')); + $this->assertEquals('abc', $request->getQuery()->get('test')); + } + + public function testCanSetDefaultQueryWithObject() + { + $request = (new MessageFactory)->createRequest( + 'GET', + 'http://foo.com?test=abc', [ + 'query' => new Query(['Foo' => 'Bar', 'test' => 'def']) + ] + ); + $this->assertEquals('Bar', $request->getQuery()->get('Foo')); + $this->assertEquals('abc', $request->getQuery()->get('test')); + } + + public function testCanAddBasicAuth() + { + $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ + 'auth' => ['michael', 'test'] + ]); + $this->assertTrue($request->hasHeader('Authorization')); + } + + public function testCanAddDigestAuth() + { + $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ + 'auth' => ['michael', 'test', 'digest'] + ]); + $this->assertEquals('michael:test', $request->getConfig()->getPath('curl/' . CURLOPT_USERPWD)); + $this->assertEquals(CURLAUTH_DIGEST, $request->getConfig()->getPath('curl/' . CURLOPT_HTTPAUTH)); + } + + public function testCanDisableAuth() + { + $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ + 'auth' => false + ]); + $this->assertFalse($request->hasHeader('Authorization')); + } + + public function testCanSetCustomAuth() + { + $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ + 'auth' => 'foo' + ]); + $this->assertEquals('foo', $request->getConfig()['auth']); + } + + public function testCanAddEvents() + { + $foo = null; + $client = new Client(); + $client->getEmitter()->attach(new Mock([new Response(200)])); + $client->get('http://test.com', [ + 'events' => [ + 'before' => function () use (&$foo) { $foo = true; } + ] + ]); + $this->assertTrue($foo); + } + + public function testCanAddEventsWithPriority() + { + $foo = null; + $client = new Client(); + $client->getEmitter()->attach(new Mock(array(new Response(200)))); + $request = $client->createRequest('GET', 'http://test.com', [ + 'events' => [ + 'before' => [ + 'fn' => function () use (&$foo) { $foo = true; }, + 'priority' => 123 + ] + ] + ]); + $client->send($request); + $this->assertTrue($foo); + $l = $this->readAttribute($request->getEmitter(), 'listeners'); + $this->assertArrayHasKey(123, $l['before']); + } + + public function testCanAddEventsOnce() + { + $foo = 0; + $client = new Client(); + $client->getEmitter()->attach(new Mock([ + new Response(200), + new Response(200), + ])); + $fn = function () use (&$foo) { ++$foo; }; + $request = $client->createRequest('GET', 'http://test.com', [ + 'events' => ['before' => ['fn' => $fn, 'once' => true]] + ]); + $client->send($request); + $this->assertEquals(1, $foo); + $client->send($request); + $this->assertEquals(1, $foo); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesEventContainsFn() + { + $client = new Client(['base_url' => 'http://test.com']); + $client->createRequest('GET', '/', ['events' => ['before' => ['foo' => 'bar']]]); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesEventIsArray() + { + $client = new Client(['base_url' => 'http://test.com']); + $client->createRequest('GET', '/', ['events' => ['before' => '123']]); + } + + public function testCanAddSubscribers() + { + $mock = new Mock([new Response(200)]); + $client = new Client(); + $client->getEmitter()->attach($mock); + $client->get('http://test.com', ['subscribers' => [$mock]]); + } + + public function testCanDisableExceptions() + { + $client = new Client(); + $this->assertEquals(500, $client->get('http://test.com', [ + 'subscribers' => [new Mock([new Response(500)])], + 'exceptions' => false + ])->getStatusCode()); + } + + public function testCanChangeSaveToLocation() + { + $saveTo = Stream::factory(); + $request = (new MessageFactory())->createRequest('GET', '/', ['save_to' => $saveTo]); + $this->assertSame($saveTo, $request->getConfig()->get('save_to')); + } + + public function testCanSetProxy() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['proxy' => '192.168.16.121']); + $this->assertEquals('192.168.16.121', $request->getConfig()->get('proxy')); + } + + public function testCanSetHeadersOption() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['headers' => ['Foo' => 'Bar']]); + $this->assertEquals('Bar', (string) $request->getHeader('Foo')); + } + + public function testCanSetHeaders() + { + $request = (new MessageFactory())->createRequest('GET', '/', [ + 'headers' => ['Foo' => ['Baz', 'Bar'], 'Test' => '123'] + ]); + $this->assertEquals('Baz, Bar', $request->getHeader('Foo')); + $this->assertEquals('123', $request->getHeader('Test')); + } + + public function testCanSetTimeoutOption() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['timeout' => 1.5]); + $this->assertEquals(1.5, $request->getConfig()->get('timeout')); + } + + public function testCanSetConnectTimeoutOption() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['connect_timeout' => 1.5]); + $this->assertEquals(1.5, $request->getConfig()->get('connect_timeout')); + } + + public function testCanSetDebug() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['debug' => true]); + $this->assertTrue($request->getConfig()->get('debug')); + } + + public function testCanSetVerifyToOff() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['verify' => false]); + $this->assertFalse($request->getConfig()->get('verify')); + } + + public function testCanSetVerifyToOn() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['verify' => true]); + $this->assertTrue($request->getConfig()->get('verify')); + } + + public function testCanSetVerifyToPath() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['verify' => '/foo.pem']); + $this->assertEquals('/foo.pem', $request->getConfig()->get('verify')); + } + + public function inputValidation() + { + return array_map(function ($option) { return array($option); }, array( + 'headers', 'events', 'subscribers', 'params' + )); + } + + /** + * @dataProvider inputValidation + * @expectedException \InvalidArgumentException + */ + public function testValidatesInput($option) + { + (new MessageFactory())->createRequest('GET', '/', [$option => 'foo']); + } + + public function testCanAddSslKey() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['ssl_key' => '/foo.pem']); + $this->assertEquals('/foo.pem', $request->getConfig()->get('ssl_key')); + } + + public function testCanAddSslKeyPassword() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['ssl_key' => ['/foo.pem', 'bar']]); + $this->assertEquals(['/foo.pem', 'bar'], $request->getConfig()->get('ssl_key')); + } + + public function testCanAddSslCert() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['cert' => '/foo.pem']); + $this->assertEquals('/foo.pem', $request->getConfig()->get('cert')); + } + + public function testCanAddSslCertPassword() + { + $request = (new MessageFactory())->createRequest('GET', '/', ['cert' => ['/foo.pem', 'bar']]); + $this->assertEquals(['/foo.pem', 'bar'], $request->getConfig()->get('cert')); + } + + public function testCreatesBodyWithoutZeroString() + { + $request = (new MessageFactory())->createRequest('PUT', 'http://test.com', ['body' => '0']); + $this->assertSame('0', (string) $request->getBody()); + } + + public function testCanSetProtocolVersion() + { + $request = (new MessageFactory())->createRequest('GET', 'http://t.com', ['version' => 1.0]); + $this->assertEquals(1.0, $request->getProtocolVersion()); + } + + public function testCanAddJsonData() + { + $request = (new MessageFactory())->createRequest('PUT', 'http://f.com', [ + 'json' => ['foo' => 'bar'] + ]); + $this->assertEquals( + 'application/json', + $request->getHeader('Content-Type') + ); + $this->assertEquals('{"foo":"bar"}', (string) $request->getBody()); + } + + public function testCanAddJsonDataToAPostRequest() + { + $request = (new MessageFactory())->createRequest('POST', 'http://f.com', [ + 'json' => ['foo' => 'bar'] + ]); + $this->assertEquals( + 'application/json', + $request->getHeader('Content-Type') + ); + $this->assertEquals('{"foo":"bar"}', (string) $request->getBody()); + } + + public function testCanAddJsonDataAndNotOverwriteContentType() + { + $request = (new MessageFactory())->createRequest('PUT', 'http://f.com', [ + 'headers' => ['Content-Type' => 'foo'], + 'json' => null + ]); + $this->assertEquals('foo', $request->getHeader('Content-Type')); + $this->assertEquals('null', (string) $request->getBody()); + } + + public function testCanUseCustomRequestOptions() + { + $c = false; + $f = new MessageFactory([ + 'foo' => function (RequestInterface $request, $value) use (&$c) { + $c = true; + $this->assertEquals('bar', $value); + } + ]); + + $f->createRequest('PUT', 'http://f.com', [ + 'headers' => ['Content-Type' => 'foo'], + 'foo' => 'bar' + ]); + + $this->assertTrue($c); + } + + /** + * @ticket https://github.com/guzzle/guzzle/issues/706 + */ + public function testDoesNotApplyPostBodyRightAway() + { + $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [ + 'body' => ['foo' => ['bar', 'baz']] + ]); + $this->assertEquals('', $request->getHeader('Content-Type')); + $this->assertEquals('', $request->getHeader('Content-Length')); + $request->getBody()->setAggregator(Query::duplicateAggregator()); + $request->getBody()->applyRequestHeaders($request); + $this->assertEquals('foo=bar&foo=baz', $request->getBody()); + } + + public function testCanForceMultipartUploadWithContentType() + { + $client = new Client(); + $client->getEmitter()->attach(new Mock([new Response(200)])); + $history = new History(); + $client->getEmitter()->attach($history); + $client->post('http://foo.com', [ + 'headers' => ['Content-Type' => 'multipart/form-data'], + 'body' => ['foo' => 'bar'] + ]); + $this->assertContains( + 'multipart/form-data; boundary=', + $history->getLastRequest()->getHeader('Content-Type') + ); + $this->assertContains( + "Content-Disposition: form-data; name=\"foo\"\r\n\r\nbar", + (string) $history->getLastRequest()->getBody() + ); + } + + public function testDecodeDoesNotForceAcceptHeader() + { + $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [ + 'decode_content' => true + ]); + $this->assertEquals('', $request->getHeader('Accept-Encoding')); + $this->assertTrue($request->getConfig()->get('decode_content')); + } + + public function testDecodeCanAddAcceptHeader() + { + $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [ + 'decode_content' => 'gzip' + ]); + $this->assertEquals('gzip', $request->getHeader('Accept-Encoding')); + $this->assertTrue($request->getConfig()->get('decode_content')); + } + + public function testCanDisableDecoding() + { + $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [ + 'decode_content' => false + ]); + $this->assertEquals('', $request->getHeader('Accept-Encoding')); + $this->assertNull($request->getConfig()->get('decode_content')); + } +} + +class ExtendedFactory extends MessageFactory +{ + protected function add_foo() {} +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/MessageParserTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/MessageParserTest.php new file mode 100644 index 0000000..0bcc943 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/MessageParserTest.php @@ -0,0 +1,276 @@ +compareRequestResults($parts, $parser->parseRequest($message)); + } + + /** + * @dataProvider responseProvider + */ + public function testParsesResponses($message, $parts) + { + $parser = new MessageParser(); + $this->compareResponseResults($parts, $parser->parseResponse($message)); + } + + public function testParsesRequestsWithMissingProtocol() + { + $parser = new MessageParser(); + $parts = $parser->parseRequest("GET /\r\nHost: Foo.com\r\n\r\n"); + $this->assertEquals('GET', $parts['method']); + $this->assertEquals('HTTP', $parts['protocol']); + $this->assertEquals('1.1', $parts['protocol_version']); + } + + public function testParsesRequestsWithMissingVersion() + { + $parser = new MessageParser(); + $parts = $parser->parseRequest("GET / HTTP\r\nHost: Foo.com\r\n\r\n"); + $this->assertEquals('GET', $parts['method']); + $this->assertEquals('HTTP', $parts['protocol']); + $this->assertEquals('1.1', $parts['protocol_version']); + } + + public function testParsesResponsesWithMissingReasonPhrase() + { + $parser = new MessageParser(); + $parts = $parser->parseResponse("HTTP/1.1 200\r\n\r\n"); + $this->assertEquals('200', $parts['code']); + $this->assertEquals('', $parts['reason_phrase']); + $this->assertEquals('HTTP', $parts['protocol']); + $this->assertEquals('1.1', $parts['protocol_version']); + } + + public function requestProvider() + { + $auth = base64_encode('michael:foo'); + + return array( + + // Empty request + array('', false), + + // Converts casing of request. Does not require host header. + array("GET / HTTP/1.1\r\n\r\n", array( + 'method' => 'GET', + 'protocol' => 'HTTP', + 'protocol_version' => '1.1', + 'request_url' => array( + 'scheme' => 'http', + 'host' => '', + 'port' => '', + 'path' => '/', + 'query' => '' + ), + 'headers' => array(), + 'body' => '' + )), + // Path and query string, multiple header values per header and case sensitive storage + array("HEAD /path?query=foo HTTP/1.0\r\nHost: example.com\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\nX-Foo: Baz\r\n\r\n", array( + 'method' => 'HEAD', + 'protocol' => 'HTTP', + 'protocol_version' => '1.0', + 'request_url' => array( + 'scheme' => 'http', + 'host' => 'example.com', + 'port' => '', + 'path' => '/path', + 'query' => 'query=foo' + ), + 'headers' => array( + 'Host' => 'example.com', + 'X-Foo' => array('foo', 'foo', 'Baz'), + 'x-foo' => 'Bar' + ), + 'body' => '' + )), + // Includes a body + array("PUT / HTTP/1.0\r\nhost: example.com:443\r\nContent-Length: 4\r\n\r\ntest", array( + 'method' => 'PUT', + 'protocol' => 'HTTP', + 'protocol_version' => '1.0', + 'request_url' => array( + 'scheme' => 'https', + 'host' => 'example.com', + 'port' => '443', + 'path' => '/', + 'query' => '' + ), + 'headers' => array( + 'host' => 'example.com:443', + 'Content-Length' => '4' + ), + 'body' => 'test' + )), + // Includes Authorization headers + array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nAuthorization: Basic {$auth}\r\n\r\n", array( + 'method' => 'GET', + 'protocol' => 'HTTP', + 'protocol_version' => '1.1', + 'request_url' => array( + 'scheme' => 'http', + 'host' => 'example.com', + 'port' => '8080', + 'path' => '/', + 'query' => '' + ), + 'headers' => array( + 'Host' => 'example.com:8080', + 'Authorization' => "Basic {$auth}" + ), + 'body' => '' + )), + // Include authorization header + array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nauthorization: Basic {$auth}\r\n\r\n", array( + 'method' => 'GET', + 'protocol' => 'HTTP', + 'protocol_version' => '1.1', + 'request_url' => array( + 'scheme' => 'http', + 'host' => 'example.com', + 'port' => '8080', + 'path' => '/', + 'query' => '' + ), + 'headers' => array( + 'Host' => 'example.com:8080', + 'authorization' => "Basic {$auth}" + ), + 'body' => '' + )), + ); + } + + public function responseProvider() + { + return array( + // Empty request + array('', false), + + array("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", array( + 'protocol' => 'HTTP', + 'protocol_version' => '1.1', + 'code' => '200', + 'reason_phrase' => 'OK', + 'headers' => array( + 'Content-Length' => 0 + ), + 'body' => '' + )), + array("HTTP/1.0 400 Bad Request\r\nContent-Length: 0\r\n\r\n", array( + 'protocol' => 'HTTP', + 'protocol_version' => '1.0', + 'code' => '400', + 'reason_phrase' => 'Bad Request', + 'headers' => array( + 'Content-Length' => 0 + ), + 'body' => '' + )), + array("HTTP/1.0 100 Continue\r\n\r\n", array( + 'protocol' => 'HTTP', + 'protocol_version' => '1.0', + 'code' => '100', + 'reason_phrase' => 'Continue', + 'headers' => array(), + 'body' => '' + )), + array("HTTP/1.1 204 No Content\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\n\r\n", array( + 'protocol' => 'HTTP', + 'protocol_version' => '1.1', + 'code' => '204', + 'reason_phrase' => 'No Content', + 'headers' => array( + 'X-Foo' => array('foo', 'foo'), + 'x-foo' => 'Bar' + ), + 'body' => '' + )), + array("HTTP/1.1 200 Ok that is great!\r\nContent-Length: 4\r\n\r\nTest", array( + 'protocol' => 'HTTP', + 'protocol_version' => '1.1', + 'code' => '200', + 'reason_phrase' => 'Ok that is great!', + 'headers' => array( + 'Content-Length' => 4 + ), + 'body' => 'Test' + )), + ); + } + + public function compareRequestResults($result, $expected) + { + if (!$result) { + $this->assertFalse($expected); + return; + } + + $this->assertEquals($result['method'], $expected['method']); + $this->assertEquals($result['protocol'], $expected['protocol']); + $this->assertEquals($result['protocol_version'], $expected['protocol_version']); + $this->assertEquals($result['request_url'], $expected['request_url']); + $this->assertEquals($result['body'], $expected['body']); + $this->compareHttpHeaders($result['headers'], $expected['headers']); + } + + public function compareResponseResults($result, $expected) + { + if (!$result) { + $this->assertFalse($expected); + return; + } + + $this->assertEquals($result['protocol'], $expected['protocol']); + $this->assertEquals($result['protocol_version'], $expected['protocol_version']); + $this->assertEquals($result['code'], $expected['code']); + $this->assertEquals($result['reason_phrase'], $expected['reason_phrase']); + $this->assertEquals($result['body'], $expected['body']); + $this->compareHttpHeaders($result['headers'], $expected['headers']); + } + + protected function normalizeHeaders($headers) + { + $normalized = array(); + foreach ($headers as $key => $value) { + $key = strtolower($key); + if (!isset($normalized[$key])) { + $normalized[$key] = $value; + } elseif (!is_array($normalized[$key])) { + $normalized[$key] = array($value); + } else { + $normalized[$key][] = $value; + } + } + + foreach ($normalized as $key => &$value) { + if (is_array($value)) { + sort($value); + } + } + + return $normalized; + } + + public function compareHttpHeaders($result, $expected) + { + // Aggregate all headers case-insensitively + $result = $this->normalizeHeaders($result); + $expected = $this->normalizeHeaders($expected); + $this->assertEquals($result, $expected); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/RequestTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/RequestTest.php new file mode 100644 index 0000000..4e670a4 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/RequestTest.php @@ -0,0 +1,144 @@ + '123'], Stream::factory('foo')); + $this->assertEquals('PUT', $r->getMethod()); + $this->assertEquals('/test', $r->getUrl()); + $this->assertEquals('123', $r->getHeader('test')); + $this->assertEquals('foo', $r->getBody()); + } + + public function testConstructorInitializesMessageWithMixedCaseHeaders() + { + $r = new Request('GET', '/test', [ + 'Set-Cookie' => 'foo=bar, baz=bam', + 'Set-cookie' => 'hi=there', + 'other' => ['1', '2'] + ]); + + $this->assertEquals('foo=bar, baz=bam, hi=there', $r->getHeader('Set-Cookie')); + $this->assertEquals('1, 2', $r->getHeader('other')); + } + + public function testConstructorInitializesMessageWithProtocolVersion() + { + $r = new Request('GET', '', [], null, ['protocol_version' => 10]); + $this->assertEquals(10, $r->getProtocolVersion()); + } + + public function testConstructorInitializesMessageWithEmitter() + { + $e = new Emitter(); + $r = new Request('GET', '', [], null, ['emitter' => $e]); + $this->assertSame($r->getEmitter(), $e); + } + + public function testCloneIsDeep() + { + $r = new Request('GET', '/test', ['foo' => 'baz'], Stream::factory('foo')); + $r2 = clone $r; + + $this->assertNotSame($r->getEmitter(), $r2->getEmitter()); + $this->assertEquals('foo', $r2->getBody()); + + $r->getConfig()->set('test', 123); + $this->assertFalse($r2->getConfig()->hasKey('test')); + + $r->setPath('/abc'); + $this->assertEquals('/test', $r2->getPath()); + } + + public function testCastsToString() + { + $r = new Request('GET', 'http://test.com/test', ['foo' => 'baz'], Stream::factory('body')); + $s = explode("\r\n", (string) $r); + $this->assertEquals("GET /test HTTP/1.1", $s[0]); + $this->assertContains('Host: test.com', $s); + $this->assertContains('foo: baz', $s); + $this->assertContains('', $s); + $this->assertContains('body', $s); + } + + public function testSettingUrlOverridesHostHeaders() + { + $r = new Request('GET', 'http://test.com/test'); + $r->setUrl('https://baz.com/bar'); + $this->assertEquals('baz.com', $r->getHost()); + $this->assertEquals('baz.com', $r->getHeader('Host')); + $this->assertEquals('/bar', $r->getPath()); + $this->assertEquals('https', $r->getScheme()); + } + + public function testQueryIsMutable() + { + $r = new Request('GET', 'http://www.foo.com?baz=bar'); + $this->assertEquals('baz=bar', $r->getQuery()); + $this->assertInstanceOf('GuzzleHttp\Query', $r->getQuery()); + $r->getQuery()->set('hi', 'there'); + $this->assertEquals('/?baz=bar&hi=there', $r->getResource()); + } + + public function testQueryCanChange() + { + $r = new Request('GET', 'http://www.foo.com?baz=bar'); + $r->setQuery(new Query(['foo' => 'bar'])); + $this->assertEquals('foo=bar', $r->getQuery()); + } + + public function testCanChangeMethod() + { + $r = new Request('GET', 'http://www.foo.com'); + $r->setMethod('put'); + $this->assertEquals('PUT', $r->getMethod()); + } + + public function testCanChangeSchemeWithPort() + { + $r = new Request('GET', 'http://www.foo.com:80'); + $r->setScheme('https'); + $this->assertEquals('https://www.foo.com', $r->getUrl()); + } + + public function testCanChangeScheme() + { + $r = new Request('GET', 'http://www.foo.com'); + $r->setScheme('https'); + $this->assertEquals('https://www.foo.com', $r->getUrl()); + } + + public function testCanChangeHost() + { + $r = new Request('GET', 'http://www.foo.com:222'); + $r->setHost('goo'); + $this->assertEquals('http://goo:222', $r->getUrl()); + $this->assertEquals('goo:222', $r->getHeader('host')); + $r->setHost('goo:80'); + $this->assertEquals('http://goo', $r->getUrl()); + $this->assertEquals('goo', $r->getHeader('host')); + } + + public function testCanChangePort() + { + $r = new Request('GET', 'http://www.foo.com:222'); + $this->assertSame(222, $r->getPort()); + $this->assertEquals('www.foo.com', $r->getHost()); + $this->assertEquals('www.foo.com:222', $r->getHeader('host')); + $r->setPort(80); + $this->assertSame(80, $r->getPort()); + $this->assertEquals('www.foo.com', $r->getHost()); + $this->assertEquals('www.foo.com', $r->getHeader('host')); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/ResponseTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/ResponseTest.php new file mode 100644 index 0000000..bbae24a --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Message/ResponseTest.php @@ -0,0 +1,120 @@ + 'hi!']); + $this->assertEquals(999, $response->getStatusCode()); + $this->assertEquals('hi!', $response->getReasonPhrase()); + } + + public function testConvertsToString() + { + $response = new Response(200); + $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", (string) $response); + // Add another header + $response = new Response(200, ['X-Test' => 'Guzzle']); + $this->assertEquals("HTTP/1.1 200 OK\r\nX-Test: Guzzle\r\n\r\n", (string) $response); + $response = new Response(200, ['Content-Length' => 4], Stream::factory('test')); + $this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", (string) $response); + } + + public function testConvertsToStringAndSeeksToByteZero() + { + $response = new Response(200); + $s = Stream::factory('foo'); + $s->read(1); + $response->setBody($s); + $this->assertEquals("HTTP/1.1 200 OK\r\n\r\nfoo", (string) $response); + } + + public function testParsesJsonResponses() + { + $json = '{"foo": "bar"}'; + $response = new Response(200, [], Stream::factory($json)); + $this->assertEquals(['foo' => 'bar'], $response->json()); + $this->assertEquals(json_decode($json), $response->json(['object' => true])); + + $response = new Response(200); + $this->assertEquals(null, $response->json()); + } + + /** + * @expectedException \GuzzleHttp\Exception\ParseException + * @expectedExceptionMessage Unable to parse JSON data: JSON_ERROR_SYNTAX - Syntax error, malformed JSON + */ + public function testThrowsExceptionWhenFailsToParseJsonResponse() + { + $response = new Response(200, [], Stream::factory('{"foo": "')); + $response->json(); + } + + public function testParsesXmlResponses() + { + $response = new Response(200, [], Stream::factory('bar')); + $this->assertEquals('bar', (string) $response->xml()->foo); + // Always return a SimpleXMLElement from the xml method + $response = new Response(200); + $this->assertEmpty((string) $response->xml()->foo); + } + + /** + * @expectedException \GuzzleHttp\Exception\XmlParseException + * @expectedExceptionMessage Unable to parse response body into XML: String could not be parsed as XML + */ + public function testThrowsExceptionWhenFailsToParseXmlResponse() + { + $response = new Response(200, [], Stream::factory('xml(); + } catch (XmlParseException $e) { + $xmlParseError = $e->getError(); + $this->assertInstanceOf('\LibXMLError', $xmlParseError); + $this->assertContains("Couldn't find end of Start Tag abc line 1", $xmlParseError->message); + throw $e; + } + } + + public function testHasEffectiveUrl() + { + $r = new Response(200); + $this->assertNull($r->getEffectiveUrl()); + $r->setEffectiveUrl('http://www.test.com'); + $this->assertEquals('http://www.test.com', $r->getEffectiveUrl()); + } + + public function testPreventsComplexExternalEntities() + { + $xml = ']>&test;'; + $response = new Response(200, [], Stream::factory($xml)); + + $oldCwd = getcwd(); + chdir(__DIR__); + try { + $xml = $response->xml(); + chdir($oldCwd); + $this->markTestIncomplete('Did not throw the expected exception! XML resolved as: ' . $xml->asXML()); + } catch (\Exception $e) { + chdir($oldCwd); + } + } + + public function testStatusAndReasonAreMutable() + { + $response = new Response(200); + $response->setStatusCode(201); + $this->assertEquals(201, $response->getStatusCode()); + $response->setReasonPhrase('Foo'); + $this->assertEquals('Foo', $response->getReasonPhrase()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/MimetypesTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/MimetypesTest.php new file mode 100644 index 0000000..a18ec38 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/MimetypesTest.php @@ -0,0 +1,31 @@ +assertEquals('text/x-php', Mimetypes::getInstance()->fromExtension('php')); + } + + public function testGetsFromFilename() + { + $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(__FILE__)); + } + + public function testGetsFromCaseInsensitiveFilename() + { + $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(strtoupper(__FILE__))); + } + + public function testReturnsNullWhenNoMatchFound() + { + $this->assertNull(Mimetypes::getInstance()->fromExtension('foobar')); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/PoolTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/PoolTest.php new file mode 100644 index 0000000..b5f02ad --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/PoolTest.php @@ -0,0 +1,319 @@ + 10]); + $this->assertSame($c, $this->readAttribute($p, 'client')); + $this->assertEquals(10, $this->readAttribute($p, 'poolSize')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesEachElement() + { + $c = new Client(); + $requests = ['foo']; + $p = new Pool($c, new \ArrayIterator($requests)); + $p->wait(); + } + + public function testSendsAndRealizesFuture() + { + $c = $this->getClient(); + $p = new Pool($c, [$c->createRequest('GET', 'http://foo.com')]); + $this->assertTrue($p->wait()); + $this->assertFalse($p->wait()); + $this->assertTrue($this->readAttribute($p, 'isRealized')); + $this->assertFalse($p->cancel()); + } + + public function testSendsManyRequestsInCappedPool() + { + $c = $this->getClient(); + $p = new Pool($c, [$c->createRequest('GET', 'http://foo.com')]); + $this->assertTrue($p->wait()); + $this->assertFalse($p->wait()); + } + + public function testSendsRequestsThatHaveNotBeenRealized() + { + $c = $this->getClient(); + $p = new Pool($c, [$c->createRequest('GET', 'http://foo.com')]); + $this->assertTrue($p->wait()); + $this->assertFalse($p->wait()); + $this->assertFalse($p->cancel()); + } + + public function testCancelsInFlightRequests() + { + $c = $this->getClient(); + $h = new History(); + $c->getEmitter()->attach($h); + $p = new Pool($c, [ + $c->createRequest('GET', 'http://foo.com'), + $c->createRequest('GET', 'http://foo.com', [ + 'events' => [ + 'before' => [ + 'fn' => function () use (&$p) { + $this->assertTrue($p->cancel()); + }, + 'priority' => RequestEvents::EARLY + ] + ] + ]) + ]); + ob_start(); + $p->wait(); + $contents = ob_get_clean(); + $this->assertEquals(1, count($h)); + $this->assertEquals('Cancelling', $contents); + } + + private function getClient() + { + $deferred = new Deferred(); + $future = new FutureArray( + $deferred->promise(), + function() use ($deferred) { + $deferred->resolve(['status' => 200, 'headers' => []]); + }, function () { + echo 'Cancelling'; + } + ); + + return new Client(['handler' => new MockHandler($future)]); + } + + public function testBatchesRequests() + { + $client = new Client(['handler' => function () { + throw new \RuntimeException('No network access'); + }]); + + $responses = [ + new Response(301, ['Location' => 'http://foo.com/bar']), + new Response(200), + new Response(200), + new Response(404) + ]; + + $client->getEmitter()->attach(new Mock($responses)); + $requests = [ + $client->createRequest('GET', 'http://foo.com/baz'), + $client->createRequest('HEAD', 'http://httpbin.org/get'), + $client->createRequest('PUT', 'http://httpbin.org/put'), + ]; + + $a = $b = $c = $d = 0; + $result = Pool::batch($client, $requests, [ + 'before' => function (BeforeEvent $e) use (&$a) { $a++; }, + 'complete' => function (CompleteEvent $e) use (&$b) { $b++; }, + 'error' => function (ErrorEvent $e) use (&$c) { $c++; }, + 'end' => function (EndEvent $e) use (&$d) { $d++; } + ]); + + $this->assertEquals(4, $a); + $this->assertEquals(2, $b); + $this->assertEquals(1, $c); + $this->assertEquals(3, $d); + $this->assertCount(3, $result); + $this->assertInstanceOf('GuzzleHttp\BatchResults', $result); + + // The first result is actually the second (redirect) response. + $this->assertSame($responses[1], $result[0]); + // The second result is a 1:1 request:response map + $this->assertSame($responses[2], $result[1]); + // The third entry is the 404 RequestException + $this->assertSame($responses[3], $result[2]->getResponse()); + } + + public function testBatchesRequestsWithDynamicPoolSize() + { + $client = new Client(['handler' => function () { + throw new \RuntimeException('No network access'); + }]); + + $responses = [ + new Response(301, ['Location' => 'http://foo.com/bar']), + new Response(200), + new Response(200), + new Response(404) + ]; + + $client->getEmitter()->attach(new Mock($responses)); + $requests = [ + $client->createRequest('GET', 'http://foo.com/baz'), + $client->createRequest('HEAD', 'http://httpbin.org/get'), + $client->createRequest('PUT', 'http://httpbin.org/put'), + ]; + + $a = $b = $c = $d = 0; + $result = Pool::batch($client, $requests, [ + 'before' => function (BeforeEvent $e) use (&$a) { $a++; }, + 'complete' => function (CompleteEvent $e) use (&$b) { $b++; }, + 'error' => function (ErrorEvent $e) use (&$c) { $c++; }, + 'end' => function (EndEvent $e) use (&$d) { $d++; }, + 'pool_size' => function ($queueSize) { + static $options = [1, 2, 1]; + static $queued = 0; + + $this->assertEquals( + $queued, + $queueSize, + 'The number of queued requests should be equal to the sum of pool sizes so far.' + ); + + $next = array_shift($options); + $queued += $next; + + return $next; + } + ]); + + $this->assertEquals(4, $a); + $this->assertEquals(2, $b); + $this->assertEquals(1, $c); + $this->assertEquals(3, $d); + $this->assertCount(3, $result); + $this->assertInstanceOf('GuzzleHttp\BatchResults', $result); + + // The first result is actually the second (redirect) response. + $this->assertSame($responses[1], $result[0]); + // The second result is a 1:1 request:response map + $this->assertSame($responses[2], $result[1]); + // The third entry is the 404 RequestException + $this->assertSame($responses[3], $result[2]->getResponse()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Each event listener must be a callable or + */ + public function testBatchValidatesTheEventFormat() + { + $client = new Client(); + $requests = [$client->createRequest('GET', 'http://foo.com/baz')]; + Pool::batch($client, $requests, ['complete' => 'foo']); + } + + public function testEmitsProgress() + { + $client = new Client(['handler' => function () { + throw new \RuntimeException('No network access'); + }]); + + $responses = [new Response(200), new Response(404)]; + $client->getEmitter()->attach(new Mock($responses)); + $requests = [ + $client->createRequest('GET', 'http://foo.com/baz'), + $client->createRequest('HEAD', 'http://httpbin.org/get') + ]; + + $pool = new Pool($client, $requests); + $count = 0; + $thenned = null; + $pool->then( + function ($value) use (&$thenned) { + $thenned = $value; + }, + null, + function ($result) use (&$count, $requests) { + $this->assertSame($requests[$count], $result['request']); + if ($count == 0) { + $this->assertNull($result['error']); + $this->assertEquals(200, $result['response']->getStatusCode()); + } else { + $this->assertInstanceOf( + 'GuzzleHttp\Exception\ClientException', + $result['error'] + ); + } + $count++; + } + ); + + $pool->wait(); + $this->assertEquals(2, $count); + $this->assertEquals(true, $thenned); + } + + public function testDoesNotThrowInErrorEvent() + { + $client = new Client(); + $responses = [new Response(404)]; + $client->getEmitter()->attach(new Mock($responses)); + $requests = [$client->createRequest('GET', 'http://foo.com/baz')]; + $result = Pool::batch($client, $requests); + $this->assertCount(1, $result); + $this->assertInstanceOf('GuzzleHttp\Exception\ClientException', $result[0]); + } + + public function testHasSendMethod() + { + $client = new Client(); + $responses = [new Response(404)]; + $history = new History(); + $client->getEmitter()->attach($history); + $client->getEmitter()->attach(new Mock($responses)); + $requests = [$client->createRequest('GET', 'http://foo.com/baz')]; + Pool::send($client, $requests); + $this->assertCount(1, $history); + } + + public function testDoesNotInfinitelyRecurse() + { + $client = new Client(['handler' => function () { + throw new \RuntimeException('No network access'); + }]); + + $last = null; + $client->getEmitter()->on( + 'before', + function (BeforeEvent $e) use (&$last) { + $e->intercept(new Response(200)); + if (function_exists('xdebug_get_stack_depth')) { + if ($last) { + $this->assertEquals($last, xdebug_get_stack_depth()); + } else { + $last = xdebug_get_stack_depth(); + } + } + } + ); + + $requests = []; + for ($i = 0; $i < 100; $i++) { + $requests[] = $client->createRequest('GET', 'http://foo.com'); + } + + $pool = new Pool($client, $requests); + $pool->wait(); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/MultipartBodyTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/MultipartBodyTest.php new file mode 100644 index 0000000..4b3b391 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/MultipartBodyTest.php @@ -0,0 +1,120 @@ + 'bar'], [ + new PostFile('foo', 'abc', 'foo.txt') + ], 'abcdef'); + } + + public function testConstructorAddsFieldsAndFiles() + { + $b = $this->getTestBody(); + $this->assertEquals('abcdef', $b->getBoundary()); + $c = (string) $b; + $this->assertContains("--abcdef\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n", $c); + $this->assertContains("--abcdef\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"foo.txt\"\r\n" + . "Content-Type: text/plain\r\n\r\nabc\r\n--abcdef--", $c); + } + + public function testDoesNotModifyFieldFormat() + { + $m = new MultipartBody(['foo+baz' => 'bar+bam %20 boo'], [ + new PostFile('foo+bar', 'abc %20 123', 'foo.txt') + ], 'abcdef'); + $this->assertContains('name="foo+baz"', (string) $m); + $this->assertContains('name="foo+bar"', (string) $m); + $this->assertContains('bar+bam %20 boo', (string) $m); + $this->assertContains('abc %20 123', (string) $m); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructorValidatesFiles() + { + new MultipartBody([], ['bar']); + } + + public function testConstructorCanCreateBoundary() + { + $b = new MultipartBody(); + $this->assertNotNull($b->getBoundary()); + } + + public function testWrapsStreamMethods() + { + $b = $this->getTestBody(); + $this->assertFalse($b->write('foo')); + $this->assertFalse($b->isWritable()); + $this->assertTrue($b->isReadable()); + $this->assertTrue($b->isSeekable()); + $this->assertEquals(0, $b->tell()); + } + + public function testCanDetachFieldsAndFiles() + { + $b = $this->getTestBody(); + $b->detach(); + $b->close(); + $this->assertEquals('', (string) $b); + } + + public function testIsSeekableReturnsTrueIfAllAreSeekable() + { + $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['isSeekable', 'isReadable']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('isSeekable') + ->will($this->returnValue(false)); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(true)); + $p = new PostFile('foo', $s, 'foo.php'); + $b = new MultipartBody([], [$p]); + $this->assertFalse($b->isSeekable()); + $this->assertFalse($b->seek(10)); + } + + public function testReadsFromBuffer() + { + $b = $this->getTestBody(); + $c = $b->read(1); + $c .= $b->read(1); + $c .= $b->read(1); + $c .= $b->read(1); + $c .= $b->read(1); + $this->assertEquals('--abc', $c); + } + + public function testCalculatesSize() + { + $b = $this->getTestBody(); + $this->assertEquals(strlen($b), $b->getSize()); + } + + public function testCalculatesSizeAndReturnsNullForUnknown() + { + $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['getSize', 'isReadable']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(null)); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(true)); + $b = new MultipartBody([], [new PostFile('foo', $s, 'foo.php')]); + $this->assertNull($b->getSize()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/PostBodyTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/PostBodyTest.php new file mode 100644 index 0000000..0283a5e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/PostBodyTest.php @@ -0,0 +1,255 @@ +assertTrue($b->isSeekable()); + $this->assertTrue($b->isReadable()); + $this->assertFalse($b->isWritable()); + $this->assertFalse($b->write('foo')); + } + + public function testApplyingWithNothingDoesNothing() + { + $b = new PostBody(); + $m = new Request('POST', '/'); + $b->applyRequestHeaders($m); + $this->assertFalse($m->hasHeader('Content-Length')); + $this->assertFalse($m->hasHeader('Content-Type')); + } + + public function testCanForceMultipartUploadsWhenApplying() + { + $b = new PostBody(); + $b->forceMultipartUpload(true); + $m = new Request('POST', '/'); + $b->applyRequestHeaders($m); + $this->assertContains( + 'multipart/form-data', + $m->getHeader('Content-Type') + ); + } + + public function testApplyingWithFilesAddsMultipartUpload() + { + $b = new PostBody(); + $p = new PostFile('foo', fopen(__FILE__, 'r')); + $b->addFile($p); + $this->assertEquals([$p], $b->getFiles()); + $this->assertNull($b->getFile('missing')); + $this->assertSame($p, $b->getFile('foo')); + $m = new Request('POST', '/'); + $b->applyRequestHeaders($m); + $this->assertContains( + 'multipart/form-data', + $m->getHeader('Content-Type') + ); + $this->assertTrue($m->hasHeader('Content-Length')); + } + + public function testApplyingWithFieldsAddsMultipartUpload() + { + $b = new PostBody(); + $b->setField('foo', 'bar'); + $this->assertEquals(['foo' => 'bar'], $b->getFields()); + $m = new Request('POST', '/'); + $b->applyRequestHeaders($m); + $this->assertContains( + 'application/x-www-form', + $m->getHeader('Content-Type') + ); + $this->assertTrue($m->hasHeader('Content-Length')); + } + + public function testMultipartWithNestedFields() + { + $b = new PostBody(); + $b->setField('foo', ['bar' => 'baz']); + $b->forceMultipartUpload(true); + $this->assertEquals(['foo' => ['bar' => 'baz']], $b->getFields()); + $m = new Request('POST', '/'); + $b->applyRequestHeaders($m); + $this->assertContains( + 'multipart/form-data', + $m->getHeader('Content-Type') + ); + $this->assertTrue($m->hasHeader('Content-Length')); + $contents = $b->getContents(); + $this->assertContains('name="foo[bar]"', $contents); + $this->assertNotContains('name="foo"', $contents); + } + + public function testCountProvidesFieldsAndFiles() + { + $b = new PostBody(); + $b->setField('foo', 'bar'); + $b->addFile(new PostFile('foo', fopen(__FILE__, 'r'))); + $this->assertEquals(2, count($b)); + $b->clearFiles(); + $b->removeField('foo'); + $this->assertEquals(0, count($b)); + $this->assertEquals([], $b->getFiles()); + $this->assertEquals([], $b->getFields()); + } + + public function testHasFields() + { + $b = new PostBody(); + $b->setField('foo', 'bar'); + $b->setField('baz', '123'); + $this->assertEquals('bar', $b->getField('foo')); + $this->assertEquals('123', $b->getField('baz')); + $this->assertNull($b->getField('ahh')); + $this->assertTrue($b->hasField('foo')); + $this->assertFalse($b->hasField('test')); + $b->replaceFields(['abc' => '123']); + $this->assertFalse($b->hasField('foo')); + $this->assertTrue($b->hasField('abc')); + } + + public function testConvertsFieldsToQueryStyleBody() + { + $b = new PostBody(); + $b->setField('foo', 'bar'); + $b->setField('baz', '123'); + $this->assertEquals('foo=bar&baz=123', $b); + $this->assertEquals(15, $b->getSize()); + $b->seek(0); + $this->assertEquals('foo=bar&baz=123', $b->getContents()); + $b->seek(0); + $this->assertEquals('foo=bar&baz=123', $b->read(1000)); + $this->assertEquals(15, $b->tell()); + } + + public function testCanSpecifyQueryAggregator() + { + $b = new PostBody(); + $b->setField('foo', ['baz', 'bar']); + $this->assertEquals('foo%5B0%5D=baz&foo%5B1%5D=bar', (string) $b); + $b = new PostBody(); + $b->setField('foo', ['baz', 'bar']); + $agg = Query::duplicateAggregator(); + $b->setAggregator($agg); + $this->assertEquals('foo=baz&foo=bar', (string) $b); + } + + public function testDetachesAndCloses() + { + $b = new PostBody(); + $b->setField('foo', 'bar'); + $b->detach(); + $b->close(); + $this->assertEquals('', $b->read(10)); + } + + public function testDetachesWhenBodyIsPresent() + { + $b = new PostBody(); + $b->setField('foo', 'bar'); + $b->getContents(); + $b->detach(); + } + + public function testFlushAndMetadataPlaceholders() + { + $b = new PostBody(); + $this->assertEquals([], $b->getMetadata()); + $this->assertNull($b->getMetadata('foo')); + } + + public function testCreatesMultipartUploadWithMultiFields() + { + $b = new PostBody(); + $b->setField('testing', ['baz', 'bar']); + $b->setField('other', 'hi'); + $b->setField('third', 'there'); + $b->addFile(new PostFile('foo', fopen(__FILE__, 'r'))); + $s = (string) $b; + $this->assertContains(file_get_contents(__FILE__), $s); + $this->assertContains('testing=bar', $s); + $this->assertContains( + 'Content-Disposition: form-data; name="third"', + $s + ); + $this->assertContains( + 'Content-Disposition: form-data; name="other"', + $s + ); + } + + public function testMultipartWithBase64Fields() + { + $b = new PostBody(); + $b->setField('foo64', '/xA2JhWEqPcgyLRDdir9WSRi/khpb2Lh3ooqv+5VYoc='); + $b->forceMultipartUpload(true); + $this->assertEquals( + ['foo64' => '/xA2JhWEqPcgyLRDdir9WSRi/khpb2Lh3ooqv+5VYoc='], + $b->getFields() + ); + $m = new Request('POST', '/'); + $b->applyRequestHeaders($m); + $this->assertContains( + 'multipart/form-data', + $m->getHeader('Content-Type') + ); + $this->assertTrue($m->hasHeader('Content-Length')); + $contents = $b->getContents(); + $this->assertContains('name="foo64"', $contents); + $this->assertContains( + '/xA2JhWEqPcgyLRDdir9WSRi/khpb2Lh3ooqv+5VYoc=', + $contents + ); + } + + public function testMultipartWithAmpersandInValue() + { + $b = new PostBody(); + $b->setField('a', 'b&c=d'); + $b->forceMultipartUpload(true); + $this->assertEquals(['a' => 'b&c=d'], $b->getFields()); + $m = new Request('POST', '/'); + $b->applyRequestHeaders($m); + $this->assertContains( + 'multipart/form-data', + $m->getHeader('Content-Type') + ); + $this->assertTrue($m->hasHeader('Content-Length')); + $contents = $b->getContents(); + $this->assertContains('name="a"', $contents); + $this->assertContains('b&c=d', $contents); + } + + /** + * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException + */ + public function testCannotAttach() + { + $b = new PostBody(); + $b->attach('foo'); + } + + public function testDoesNotOverwriteExistingHeaderForUrlencoded() + { + $m = new Request('POST', 'http://foo.com', [ + 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8' + ]); + $b = new PostBody(); + $b->setField('foo', 'bar'); + $b->applyRequestHeaders($m); + $this->assertEquals( + 'application/x-www-form-urlencoded; charset=utf-8', + $m->getHeader('Content-Type') + ); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/PostFileTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/PostFileTest.php new file mode 100644 index 0000000..800cee5 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Post/PostFileTest.php @@ -0,0 +1,61 @@ +assertInstanceOf('GuzzleHttp\Post\PostFileInterface', $p); + $this->assertEquals('hi', $p->getContent()); + $this->assertEquals('foo', $p->getName()); + $this->assertEquals('/path/to/test.php', $p->getFilename()); + $this->assertEquals( + 'form-data; name="foo"; filename="test.php"', + $p->getHeaders()['Content-Disposition'] + ); + } + + public function testGetsFilenameFromMetadata() + { + $p = new PostFile('foo', fopen(__FILE__, 'r')); + $this->assertEquals(__FILE__, $p->getFilename()); + } + + public function testDefaultsToNameWhenNoFilenameExists() + { + $p = new PostFile('foo', 'bar'); + $this->assertEquals('foo', $p->getFilename()); + } + + public function testCreatesFromMultipartFormData() + { + $mp = new MultipartBody([], [], 'baz'); + $p = new PostFile('foo', $mp); + $this->assertEquals( + 'form-data; name="foo"', + $p->getHeaders()['Content-Disposition'] + ); + $this->assertEquals( + 'multipart/form-data; boundary=baz', + $p->getHeaders()['Content-Type'] + ); + } + + public function testCanAddHeaders() + { + $p = new PostFile('foo', Stream::factory('hi'), 'test.php', [ + 'X-Foo' => '123', + 'Content-Disposition' => 'bar' + ]); + $this->assertEquals('bar', $p->getHeaders()['Content-Disposition']); + $this->assertEquals('123', $p->getHeaders()['X-Foo']); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/QueryParserTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/QueryParserTest.php new file mode 100644 index 0000000..e9075a8 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/QueryParserTest.php @@ -0,0 +1,80 @@ + ['a', 'b']]], + // Can parse multi-valued items that use numeric indices + ['q[0]=a&q[1]=b', ['q' => ['a', 'b']]], + // Can parse duplicates and does not include numeric indices + ['q[]=a&q[]=b', ['q' => ['a', 'b']]], + // Ensures that the value of "q" is an array even though one value + ['q[]=a', ['q' => ['a']]], + // Does not modify "." to "_" like PHP's parse_str() + ['q.a=a&q.b=b', ['q.a' => 'a', 'q.b' => 'b']], + // Can decode %20 to " " + ['q%20a=a%20b', ['q a' => 'a b']], + // Can parse funky strings with no values by assigning each to null + ['q&a', ['q' => null, 'a' => null]], + // Does not strip trailing equal signs + ['data=abc=', ['data' => 'abc=']], + // Can store duplicates without affecting other values + ['foo=a&foo=b&?µ=c', ['foo' => ['a', 'b'], '?µ' => 'c']], + // Sets value to null when no "=" is present + ['foo', ['foo' => null]], + // Preserves "0" keys. + ['0', ['0' => null]], + // Sets the value to an empty string when "=" is present + ['0=', ['0' => '']], + // Preserves falsey keys + ['var=0', ['var' => '0']], + // Can deeply nest and store duplicate PHP values + ['a[b][c]=1&a[b][c]=2', [ + 'a' => ['b' => ['c' => ['1', '2']]] + ]], + // Can parse PHP style arrays + ['a[b]=c&a[d]=e', ['a' => ['b' => 'c', 'd' => 'e']]], + // Ensure it doesn't leave things behind with repeated values + // Can parse mult-values items + ['q=a&q=b&q=c', ['q' => ['a', 'b', 'c']]], + ]; + } + + /** + * @dataProvider parseQueryProvider + */ + public function testParsesQueries($input, $output) + { + $query = Query::fromString($input); + $this->assertEquals($output, $query->toArray()); + // Normalize the input and output + $query->setEncodingType(false); + $this->assertEquals(rawurldecode($input), (string) $query); + } + + public function testConvertsPlusSymbolsToSpacesByDefault() + { + $query = Query::fromString('var=foo+bar', true); + $this->assertEquals('foo bar', $query->get('var')); + } + + public function testCanControlDecodingType() + { + $qp = new QueryParser(); + $q = new Query(); + $qp->parseInto($q, 'var=foo+bar', Query::RFC3986); + $this->assertEquals('foo+bar', $q->get('var')); + $qp->parseInto($q, 'var=foo+bar', Query::RFC1738); + $this->assertEquals('foo bar', $q->get('var')); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/QueryTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/QueryTest.php new file mode 100644 index 0000000..8b9d344 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/QueryTest.php @@ -0,0 +1,171 @@ + 'baz', 'bar' => 'bam boozle']); + $this->assertEquals('foo=baz&bar=bam%20boozle', (string) $q); + } + + public function testCanDisableUrlEncoding() + { + $q = new Query(['bar' => 'bam boozle']); + $q->setEncodingType(false); + $this->assertEquals('bar=bam boozle', (string) $q); + } + + public function testCanSpecifyRfc1783UrlEncodingType() + { + $q = new Query(['bar abc' => 'bam boozle']); + $q->setEncodingType(Query::RFC1738); + $this->assertEquals('bar+abc=bam+boozle', (string) $q); + } + + public function testCanSpecifyRfc3986UrlEncodingType() + { + $q = new Query(['bar abc' => 'bam boozle', 'ሴ' => 'hi']); + $q->setEncodingType(Query::RFC3986); + $this->assertEquals('bar%20abc=bam%20boozle&%E1%88%B4=hi', (string) $q); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesEncodingType() + { + (new Query(['bar' => 'bam boozle']))->setEncodingType('foo'); + } + + public function testAggregatesMultipleValues() + { + $q = new Query(['foo' => ['bar', 'baz']]); + $this->assertEquals('foo%5B0%5D=bar&foo%5B1%5D=baz', (string) $q); + } + + public function testCanSetAggregator() + { + $q = new Query(['foo' => ['bar', 'baz']]); + $q->setAggregator(function (array $data) { + return ['foo' => ['barANDbaz']]; + }); + $this->assertEquals('foo=barANDbaz', (string) $q); + } + + public function testAllowsMultipleValuesPerKey() + { + $q = new Query(); + $q->add('facet', 'size'); + $q->add('facet', 'width'); + $q->add('facet.field', 'foo'); + // Use the duplicate aggregator + $q->setAggregator($q::duplicateAggregator()); + $this->assertEquals('facet=size&facet=width&facet.field=foo', (string) $q); + } + + public function testAllowsZeroValues() + { + $query = new Query(array( + 'foo' => 0, + 'baz' => '0', + 'bar' => null, + 'boo' => false + )); + $this->assertEquals('foo=0&baz=0&bar&boo=', (string) $query); + } + + private $encodeData = [ + 't' => [ + 'v1' => ['a', '1'], + 'v2' => 'b', + 'v3' => ['v4' => 'c', 'v5' => 'd'] + ] + ]; + + public function testEncodesDuplicateAggregator() + { + $agg = Query::duplicateAggregator(); + $result = $agg($this->encodeData); + $this->assertEquals(array( + 't[v1]' => ['a', '1'], + 't[v2]' => ['b'], + 't[v3][v4]' => ['c'], + 't[v3][v5]' => ['d'], + ), $result); + } + + public function testDuplicateEncodesNoNumericIndices() + { + $agg = Query::duplicateAggregator(); + $result = $agg($this->encodeData); + $this->assertEquals(array( + 't[v1]' => ['a', '1'], + 't[v2]' => ['b'], + 't[v3][v4]' => ['c'], + 't[v3][v5]' => ['d'], + ), $result); + } + + public function testEncodesPhpAggregator() + { + $agg = Query::phpAggregator(); + $result = $agg($this->encodeData); + $this->assertEquals(array( + 't[v1][0]' => ['a'], + 't[v1][1]' => ['1'], + 't[v2]' => ['b'], + 't[v3][v4]' => ['c'], + 't[v3][v5]' => ['d'], + ), $result); + } + + public function testPhpEncodesNoNumericIndices() + { + $agg = Query::phpAggregator(false); + $result = $agg($this->encodeData); + $this->assertEquals(array( + 't[v1][]' => ['a', '1'], + 't[v2]' => ['b'], + 't[v3][v4]' => ['c'], + 't[v3][v5]' => ['d'], + ), $result); + } + + public function testCanDisableUrlEncodingDecoding() + { + $q = Query::fromString('foo=bar+baz boo%20', false); + $this->assertEquals('bar+baz boo%20', $q['foo']); + $this->assertEquals('foo=bar+baz boo%20', (string) $q); + } + + public function testCanChangeUrlEncodingDecodingToRfc1738() + { + $q = Query::fromString('foo=bar+baz', Query::RFC1738); + $this->assertEquals('bar baz', $q['foo']); + $this->assertEquals('foo=bar+baz', (string) $q); + } + + public function testCanChangeUrlEncodingDecodingToRfc3986() + { + $q = Query::fromString('foo=bar%20baz', Query::RFC3986); + $this->assertEquals('bar baz', $q['foo']); + $this->assertEquals('foo=bar%20baz', (string) $q); + } + + public function testQueryStringsAllowSlashButDoesNotDecodeWhenDisable() + { + $q = Query::fromString('foo=bar%2Fbaz&bam=boo%20boo', Query::RFC3986); + $q->setEncodingType(false); + $this->assertEquals('foo=bar/baz&bam=boo boo', (string) $q); + } + + public function testQueryStringsAllowDecodingEncodingCompletelyDisabled() + { + $q = Query::fromString('foo=bar%2Fbaz&bam=boo boo!', false); + $this->assertEquals('foo=bar%2Fbaz&bam=boo boo!', (string) $q); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/RequestFsmTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/RequestFsmTest.php new file mode 100644 index 0000000..dd67684 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/RequestFsmTest.php @@ -0,0 +1,187 @@ +mf = new MessageFactory(); + } + + public function testEmitsBeforeEventInTransition() + { + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 200]); + }, $this->mf); + $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); + $c = false; + $t->request->getEmitter()->on('before', function (BeforeEvent $e) use (&$c) { + $c = true; + }); + $fsm($t); + $this->assertTrue($c); + } + + public function testEmitsCompleteEventInTransition() + { + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 200]); + }, $this->mf); + $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); + $t->response = new Response(200); + $t->state = 'complete'; + $c = false; + $t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) { + $c = true; + }); + $fsm($t); + $this->assertTrue($c); + } + + public function testDoesNotEmitCompleteForFuture() + { + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 200]); + }, $this->mf); + $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); + $deferred = new Deferred(); + $t->response = new FutureResponse($deferred->promise()); + $t->state = 'complete'; + $c = false; + $t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) { + $c = true; + }); + $fsm($t); + $this->assertFalse($c); + } + + public function testTransitionsThroughSuccessfulTransfer() + { + $client = new Client(); + $client->getEmitter()->attach(new Mock([new Response(200)])); + $request = $client->createRequest('GET', 'http://ewfewwef.com'); + $this->addListeners($request, $calls); + $client->send($request); + $this->assertEquals(['before', 'complete', 'end'], $calls); + } + + public function testTransitionsThroughErrorsInBefore() + { + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 200]); + }, $this->mf); + $client = new Client(); + $request = $client->createRequest('GET', 'http://ewfewwef.com'); + $t = new Transaction($client, $request); + $calls = []; + $this->addListeners($t->request, $calls); + $t->request->getEmitter()->on('before', function (BeforeEvent $e) { + throw new \Exception('foo'); + }); + try { + $fsm($t); + $this->fail('did not throw'); + } catch (RequestException $e) { + $this->assertContains('foo', $t->exception->getMessage()); + $this->assertEquals(['before', 'error', 'end'], $calls); + } + } + + public function testTransitionsThroughErrorsInComplete() + { + $client = new Client(); + $client->getEmitter()->attach(new Mock([new Response(200)])); + $request = $client->createRequest('GET', 'http://ewfewwef.com'); + $this->addListeners($request, $calls); + $request->getEmitter()->once('complete', function (CompleteEvent $e) { + throw new \Exception('foo'); + }); + try { + $client->send($request); + $this->fail('did not throw'); + } catch (RequestException $e) { + $this->assertContains('foo', $e->getMessage()); + $this->assertEquals(['before', 'complete', 'error', 'end'], $calls); + } + } + + public function testTransitionsThroughErrorInterception() + { + $fsm = new RequestFsm(function () { + return new CompletedFutureArray(['status' => 404]); + }, $this->mf); + $client = new Client(); + $request = $client->createRequest('GET', 'http://ewfewwef.com'); + $t = new Transaction($client, $request); + $calls = []; + $this->addListeners($t->request, $calls); + $t->request->getEmitter()->on('error', function (ErrorEvent $e) { + $e->intercept(new Response(200)); + }); + $fsm($t); + $this->assertEquals(200, $t->response->getStatusCode()); + $this->assertNull($t->exception); + $this->assertEquals(['before', 'complete', 'error', 'complete', 'end'], $calls); + } + + private function addListeners(RequestInterface $request, &$calls) + { + $request->getEmitter()->on('before', function (BeforeEvent $e) use (&$calls) { + $calls[] = 'before'; + }, RequestEvents::EARLY); + $request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$calls) { + $calls[] = 'complete'; + }, RequestEvents::EARLY); + $request->getEmitter()->on('error', function (ErrorEvent $e) use (&$calls) { + $calls[] = 'error'; + }, RequestEvents::EARLY); + $request->getEmitter()->on('end', function (EndEvent $e) use (&$calls) { + $calls[] = 'end'; + }, RequestEvents::EARLY); + } + + /** + * @expectedException \GuzzleHttp\Exception\RequestException + * @expectedExceptionMessage Too many state transitions + */ + public function testDetectsInfiniteLoops() + { + $client = new Client([ + 'fsm' => $fsm = new RequestFsm( + function () { + return new CompletedFutureArray(['status' => 200]); + }, + new MessageFactory(), + 3 + ) + ]); + $request = $client->createRequest('GET', 'http://foo.com:123'); + $request->getEmitter()->on('before', function () { + throw new \Exception('foo'); + }); + $request->getEmitter()->on('error', function ($e) { + $e->retry(); + }); + $client->send($request); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/RingBridgeTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/RingBridgeTest.php new file mode 100644 index 0000000..dc26a42 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/RingBridgeTest.php @@ -0,0 +1,195 @@ + 'hello' + ], $stream); + $request->getConfig()->set('foo', 'bar'); + $trans = new Transaction(new Client(), $request); + $factory = new MessageFactory(); + $fsm = new RequestFsm(function () {}, new MessageFactory()); + $r = RingBridge::prepareRingRequest($trans, $factory, $fsm); + $this->assertEquals('http', $r['scheme']); + $this->assertEquals('1.1', $r['version']); + $this->assertEquals('GET', $r['http_method']); + $this->assertEquals('http://httpbin.org/get?a=b', $r['url']); + $this->assertEquals('/get', $r['uri']); + $this->assertEquals('a=b', $r['query_string']); + $this->assertEquals([ + 'Host' => ['httpbin.org'], + 'test' => ['hello'] + ], $r['headers']); + $this->assertSame($stream, $r['body']); + $this->assertEquals(['foo' => 'bar'], $r['client']); + $this->assertFalse($r['future']); + } + + public function testCreatesRingRequestsWithNullQueryString() + { + $request = new Request('GET', 'http://httpbin.org'); + $trans = new Transaction(new Client(), $request); + $factory = new MessageFactory(); + $fsm = new RequestFsm(function () {}, new MessageFactory()); + $r = RingBridge::prepareRingRequest($trans, $factory, $fsm); + $this->assertNull($r['query_string']); + $this->assertEquals('/', $r['uri']); + $this->assertEquals(['Host' => ['httpbin.org']], $r['headers']); + $this->assertNull($r['body']); + $this->assertEquals([], $r['client']); + } + + public function testAddsProgress() + { + Server::enqueue([new Response(200)]); + $client = new Client(['base_url' => Server::$url]); + $request = $client->createRequest('GET'); + $called = false; + $request->getEmitter()->on( + 'progress', + function (ProgressEvent $e) use (&$called) { + $called = true; + } + ); + $this->assertEquals(200, $client->send($request)->getStatusCode()); + $this->assertTrue($called); + } + + public function testGetsResponseProtocolVersionAndEffectiveUrlAndReason() + { + $client = new Client([ + 'handler' => new MockHandler([ + 'status' => 200, + 'reason' => 'test', + 'headers' => [], + 'version' => '1.0', + 'effective_url' => 'http://foo.com' + ]) + ]); + $request = $client->createRequest('GET', 'http://foo.com'); + $response = $client->send($request); + $this->assertEquals('1.0', $response->getProtocolVersion()); + $this->assertEquals('http://foo.com', $response->getEffectiveUrl()); + $this->assertEquals('test', $response->getReasonPhrase()); + } + + public function testGetsStreamFromResponse() + { + $res = fopen('php://temp', 'r+'); + fwrite($res, 'foo'); + rewind($res); + $client = new Client([ + 'handler' => new MockHandler([ + 'status' => 200, + 'headers' => [], + 'body' => $res + ]) + ]); + $request = $client->createRequest('GET', 'http://foo.com'); + $response = $client->send($request); + $this->assertEquals('foo', (string) $response->getBody()); + } + + public function testEmitsErrorEventOnError() + { + $client = new Client(['base_url' => 'http://127.0.0.1:123']); + $request = $client->createRequest('GET'); + $called = false; + $request->getEmitter()->on('error', function () use (&$called) { + $called = true; + }); + $request->getConfig()['timeout'] = 0.001; + $request->getConfig()['connect_timeout'] = 0.001; + try { + $client->send($request); + $this->fail('did not throw'); + } catch (RequestException $e) { + $this->assertSame($request, $e->getRequest()); + $this->assertContains('cURL error', $e->getMessage()); + $this->assertTrue($called); + } + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesRingRequest() + { + RingBridge::fromRingRequest([]); + } + + public function testCreatesRequestFromRing() + { + $request = RingBridge::fromRingRequest([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => [ + 'foo' => ['bar'], + 'host' => ['foo.com'] + ], + 'body' => 'test', + 'version' => '1.0' + ]); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('http://foo.com/', $request->getUrl()); + $this->assertEquals('1.0', $request->getProtocolVersion()); + $this->assertEquals('test', (string) $request->getBody()); + $this->assertEquals('bar', $request->getHeader('foo')); + } + + public function testCanInterceptException() + { + $client = new Client(['base_url' => 'http://127.0.0.1:123']); + $request = $client->createRequest('GET'); + $called = false; + $request->getEmitter()->on( + 'error', + function (ErrorEvent $e) use (&$called) { + $called = true; + $e->intercept(new Response(200)); + } + ); + $request->getConfig()['timeout'] = 0.001; + $request->getConfig()['connect_timeout'] = 0.001; + $this->assertEquals(200, $client->send($request)->getStatusCode()); + $this->assertTrue($called); + } + + public function testCreatesLongException() + { + $r = new Request('GET', 'http://www.google.com'); + $e = RingBridge::getNoRingResponseException($r); + $this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $e); + $this->assertSame($r, $e->getRequest()); + } + + public function testEnsuresResponseOrExceptionWhenCompletingResponse() + { + $trans = new Transaction(new Client(), new Request('GET', 'http://f.co')); + $f = new MessageFactory(); + $fsm = new RequestFsm(function () {}, new MessageFactory()); + try { + RingBridge::completeRingResponse($trans, [], $f, $fsm); + } catch (RequestException $e) { + $this->assertSame($trans->request, $e->getRequest()); + $this->assertContains('RingPHP', $e->getMessage()); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Server.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Server.php new file mode 100644 index 0000000..1de20e3 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Server.php @@ -0,0 +1,107 @@ +fromMessage($response); + } elseif (!($response instanceof ResponseInterface)) { + throw new \Exception('Responses must be strings or Responses'); + } + $data[] = self::convertResponse($response); + } + + TestServer::enqueue($data); + } + + /** + * Get all of the received requests + * + * @param bool $hydrate Set to TRUE to turn the messages into + * actual {@see RequestInterface} objects. If $hydrate is FALSE, + * requests will be returned as strings. + * + * @return array + * @throws \RuntimeException + */ + public static function received($hydrate = false) + { + $response = TestServer::received(); + + if ($hydrate) { + $c = new Client(); + $factory = new MessageFactory(); + $response = array_map(function($message) use ($factory, $c) { + return RingBridge::fromRingRequest($message); + }, $response); + } + + return $response; + } + + public static function flush() + { + TestServer::flush(); + } + + public static function stop() + { + TestServer::stop(); + } + + public static function wait($maxTries = 5) + { + TestServer::wait($maxTries); + } + + public static function start() + { + TestServer::start(); + } + + private static function convertResponse(Response $response) + { + $headers = array_map(function ($h) { + return implode(', ', $h); + }, $response->getHeaders()); + + return [ + 'status' => $response->getStatusCode(), + 'reason' => $response->getReasonPhrase(), + 'headers' => $headers, + 'body' => base64_encode((string) $response->getBody()) + ]; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/CookieTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/CookieTest.php new file mode 100644 index 0000000..bc17e2d --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/CookieTest.php @@ -0,0 +1,74 @@ +getMockBuilder('GuzzleHttp\Cookie\CookieJar') + ->setMethods(array('extractCookies')) + ->getMock(); + + $mock->expects($this->exactly(1)) + ->method('extractCookies') + ->with($request, $response); + + $plugin = new Cookie($mock); + $t = new Transaction(new Client(), $request); + $t->response = $response; + $plugin->onComplete(new CompleteEvent($t)); + } + + public function testProvidesCookieJar() + { + $jar = new CookieJar(); + $plugin = new Cookie($jar); + $this->assertSame($jar, $plugin->getCookieJar()); + } + + public function testCookiesAreExtractedFromRedirectResponses() + { + $jar = new CookieJar(); + $cookie = new Cookie($jar); + $history = new History(); + $mock = new Mock([ + "HTTP/1.1 302 Moved Temporarily\r\n" . + "Set-Cookie: test=583551; Domain=www.foo.com; Expires=Wednesday, 23-Mar-2050 19:49:45 GMT; Path=/\r\n" . + "Location: /redirect\r\n\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Length: 0\r\n\r\n" + ]); + $client = new Client(['base_url' => 'http://www.foo.com']); + $client->getEmitter()->attach($cookie); + $client->getEmitter()->attach($mock); + $client->getEmitter()->attach($history); + + $client->get(); + $request = $client->createRequest('GET', '/'); + $client->send($request); + + $this->assertEquals('test=583551', $request->getHeader('Cookie')); + $requests = $history->getRequests(); + // Confirm subsequent requests have the cookie. + $this->assertEquals('test=583551', $requests[2]->getHeader('Cookie')); + // Confirm the redirected request has the cookie. + $this->assertEquals('test=583551', $requests[1]->getHeader('Cookie')); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/HistoryTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/HistoryTest.php new file mode 100644 index 0000000..d28e301 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/HistoryTest.php @@ -0,0 +1,140 @@ +response = $response; + $e = new RequestException('foo', $request, $response); + $ev = new ErrorEvent($t, $e); + $h = new History(2); + $h->onError($ev); + // Only tracks when no response is present + $this->assertEquals([], $h->getRequests()); + } + + public function testLogsConnectionErrors() + { + $request = new Request('GET', '/'); + $t = new Transaction(new Client(), $request); + $e = new RequestException('foo', $request); + $ev = new ErrorEvent($t, $e); + $h = new History(); + $h->onError($ev); + $this->assertEquals([$request], $h->getRequests()); + } + + public function testMaintainsLimitValue() + { + $request = new Request('GET', '/'); + $response = new Response(200); + $t = new Transaction(new Client(), $request); + $t->response = $response; + $ev = new CompleteEvent($t); + $h = new History(2); + $h->onComplete($ev); + $h->onComplete($ev); + $h->onComplete($ev); + $this->assertEquals(2, count($h)); + $this->assertSame($request, $h->getLastRequest()); + $this->assertSame($response, $h->getLastResponse()); + foreach ($h as $trans) { + $this->assertInstanceOf('GuzzleHttp\Message\RequestInterface', $trans['request']); + $this->assertInstanceOf('GuzzleHttp\Message\ResponseInterface', $trans['response']); + } + return $h; + } + + /** + * @depends testMaintainsLimitValue + */ + public function testClearsHistory($h) + { + $this->assertEquals(2, count($h)); + $h->clear(); + $this->assertEquals(0, count($h)); + } + + public function testWorksWithMock() + { + $client = new Client(['base_url' => 'http://localhost/']); + $h = new History(); + $client->getEmitter()->attach($h); + $mock = new Mock([new Response(200), new Response(201), new Response(202)]); + $client->getEmitter()->attach($mock); + $request = $client->createRequest('GET', '/'); + $client->send($request); + $request->setMethod('PUT'); + $client->send($request); + $request->setMethod('POST'); + $client->send($request); + $this->assertEquals(3, count($h)); + + $result = implode("\n", array_map(function ($line) { + return strpos($line, 'User-Agent') === 0 + ? 'User-Agent:' + : trim($line); + }, explode("\n", $h))); + + $this->assertEquals("> GET / HTTP/1.1 +Host: localhost +User-Agent: + +< HTTP/1.1 200 OK + +> PUT / HTTP/1.1 +Host: localhost +User-Agent: + +< HTTP/1.1 201 Created + +> POST / HTTP/1.1 +Host: localhost +User-Agent: + +< HTTP/1.1 202 Accepted +", $result); + } + + public function testCanCastToString() + { + $client = new Client(['base_url' => 'http://localhost/']); + $h = new History(); + $client->getEmitter()->attach($h); + + $mock = new Mock(array( + new Response(301, array('Location' => '/redirect1', 'Content-Length' => 0)), + new Response(307, array('Location' => '/redirect2', 'Content-Length' => 0)), + new Response(200, array('Content-Length' => '2'), Stream::factory('HI')) + )); + + $client->getEmitter()->attach($mock); + $request = $client->createRequest('GET', '/'); + $client->send($request); + $this->assertEquals(3, count($h)); + + $h = str_replace("\r", '', $h); + $this->assertContains("> GET / HTTP/1.1\nHost: localhost\nUser-Agent:", $h); + $this->assertContains("< HTTP/1.1 301 Moved Permanently\nLocation: /redirect1", $h); + $this->assertContains("< HTTP/1.1 307 Temporary Redirect\nLocation: /redirect2", $h); + $this->assertContains("< HTTP/1.1 200 OK\nContent-Length: 2\n\nHI", $h); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/HttpErrorTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/HttpErrorTest.php new file mode 100644 index 0000000..b026634 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/HttpErrorTest.php @@ -0,0 +1,60 @@ +getEvent(); + $event->intercept(new Response(200)); + (new HttpError())->onComplete($event); + } + + /** + * @expectedException \GuzzleHttp\Exception\ClientException + */ + public function testThrowsClientExceptionOnFailure() + { + $event = $this->getEvent(); + $event->intercept(new Response(403)); + (new HttpError())->onComplete($event); + } + + /** + * @expectedException \GuzzleHttp\Exception\ServerException + */ + public function testThrowsServerExceptionOnFailure() + { + $event = $this->getEvent(); + $event->intercept(new Response(500)); + (new HttpError())->onComplete($event); + } + + private function getEvent() + { + return new CompleteEvent(new Transaction(new Client(), new Request('PUT', '/'))); + } + + /** + * @expectedException \GuzzleHttp\Exception\ClientException + */ + public function testFullTransaction() + { + $client = new Client(); + $client->getEmitter()->attach(new Mock([ + new Response(403) + ])); + $client->get('http://httpbin.org'); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/MockTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/MockTest.php new file mode 100644 index 0000000..936edf2 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/MockTest.php @@ -0,0 +1,225 @@ +promise(), + function () use ($deferred, $wait) { + $deferred->resolve($wait()); + }, + $cancel + ); + } + + public function testDescribesSubscribedEvents() + { + $mock = new Mock(); + $this->assertInternalType('array', $mock->getEvents()); + } + + public function testIsCountable() + { + $plugin = new Mock(); + $plugin->addResponse((new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $this->assertEquals(1, count($plugin)); + } + + public function testCanClearQueue() + { + $plugin = new Mock(); + $plugin->addResponse((new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $plugin->clearQueue(); + $this->assertEquals(0, count($plugin)); + } + + public function testRetrievesResponsesFromFiles() + { + $tmp = tempnam('/tmp', 'tfile'); + file_put_contents($tmp, "HTTP/1.1 201 OK\r\nContent-Length: 0\r\n\r\n"); + $plugin = new Mock(); + $plugin->addResponse($tmp); + unlink($tmp); + $this->assertEquals(1, count($plugin)); + $q = $this->readAttribute($plugin, 'queue'); + $this->assertEquals(201, $q[0]->getStatusCode()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testThrowsExceptionWhenInvalidResponse() + { + (new Mock())->addResponse(false); + } + + public function testAddsMockResponseToRequestFromClient() + { + $response = new Response(200); + $t = new Transaction(new Client(), new Request('GET', '/')); + $m = new Mock([$response]); + $ev = new BeforeEvent($t); + $m->onBefore($ev); + $this->assertSame($response, $t->response); + } + + /** + * @expectedException \OutOfBoundsException + */ + public function testUpdateThrowsExceptionWhenEmpty() + { + $p = new Mock(); + $ev = new BeforeEvent(new Transaction(new Client(), new Request('GET', '/'))); + $p->onBefore($ev); + } + + public function testReadsBodiesFromMockedRequests() + { + $m = new Mock([new Response(200)]); + $client = new Client(['base_url' => 'http://test.com']); + $client->getEmitter()->attach($m); + $body = Stream::factory('foo'); + $client->put('/', ['body' => $body]); + $this->assertEquals(3, $body->tell()); + } + + public function testCanMockBadRequestExceptions() + { + $client = new Client(['base_url' => 'http://test.com']); + $request = $client->createRequest('GET', '/'); + $ex = new RequestException('foo', $request); + $mock = new Mock([$ex]); + $this->assertCount(1, $mock); + $request->getEmitter()->attach($mock); + + try { + $client->send($request); + $this->fail('Did not dequeue an exception'); + } catch (RequestException $e) { + $this->assertSame($e, $ex); + $this->assertSame($request, $ex->getRequest()); + } + } + + public function testCanMockFutureResponses() + { + $client = new Client(['base_url' => 'http://test.com']); + $request = $client->createRequest('GET', '/', ['future' => true]); + $response = new Response(200); + $future = self::createFuture(function () use ($response) { + return $response; + }); + $mock = new Mock([$future]); + $this->assertCount(1, $mock); + $request->getEmitter()->attach($mock); + $res = $client->send($request); + $this->assertSame($future, $res); + $this->assertFalse($this->readAttribute($res, 'isRealized')); + $this->assertSame($response, $res->wait()); + } + + public function testCanMockExceptionFutureResponses() + { + $client = new Client(['base_url' => 'http://test.com']); + $request = $client->createRequest('GET', '/', ['future' => true]); + $future = self::createFuture(function () use ($request) { + throw new RequestException('foo', $request); + }); + + $mock = new Mock([$future]); + $request->getEmitter()->attach($mock); + $response = $client->send($request); + $this->assertSame($future, $response); + $this->assertFalse($this->readAttribute($response, 'isRealized')); + + try { + $response->wait(); + $this->fail('Did not throw'); + } catch (RequestException $e) { + $this->assertContains('foo', $e->getMessage()); + } + } + + public function testSaveToFile() + { + $filename = sys_get_temp_dir().'/mock_test_'.uniqid(); + $file = tmpfile(); + $stream = new Stream(tmpfile()); + + $m = new Mock([ + new Response(200, [], Stream::factory('TEST FILENAME')), + new Response(200, [], Stream::factory('TEST FILE')), + new Response(200, [], Stream::factory('TEST STREAM')), + ]); + + $client = new Client(); + $client->getEmitter()->attach($m); + + $client->get('/', ['save_to' => $filename]); + $client->get('/', ['save_to' => $file]); + $client->get('/', ['save_to' => $stream]); + + $this->assertFileExists($filename); + $this->assertEquals('TEST FILENAME', file_get_contents($filename)); + + $meta = stream_get_meta_data($file); + + $this->assertFileExists($meta['uri']); + $this->assertEquals('TEST FILE', file_get_contents($meta['uri'])); + + $this->assertFileExists($stream->getMetadata('uri')); + $this->assertEquals('TEST STREAM', file_get_contents($stream->getMetadata('uri'))); + + unlink($filename); + } + + public function testCanMockFailedFutureResponses() + { + $client = new Client(['base_url' => 'http://test.com']); + $request = $client->createRequest('GET', '/', ['future' => true]); + + // The first mock will be a mocked future response. + $future = self::createFuture(function () use ($client) { + // When dereferenced, we will set a mocked response and send + // another request. + $client->get('http://httpbin.org', ['events' => [ + 'before' => function (BeforeEvent $e) { + $e->intercept(new Response(404)); + } + ]]); + }); + + $mock = new Mock([$future]); + $request->getEmitter()->attach($mock); + $response = $client->send($request); + $this->assertSame($future, $response); + $this->assertFalse($this->readAttribute($response, 'isRealized')); + + try { + $response->wait(); + $this->fail('Did not throw'); + } catch (RequestException $e) { + $this->assertEquals(404, $e->getResponse()->getStatusCode()); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/PrepareTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/PrepareTest.php new file mode 100644 index 0000000..d07fdb4 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/PrepareTest.php @@ -0,0 +1,213 @@ +getTrans(); + $s->onBefore(new BeforeEvent($t)); + $this->assertFalse($t->request->hasHeader('Expect')); + } + + public function testAppliesPostBody() + { + $s = new Prepare(); + $t = $this->getTrans(); + $p = $this->getMockBuilder('GuzzleHttp\Post\PostBody') + ->setMethods(['applyRequestHeaders']) + ->getMockForAbstractClass(); + $p->expects($this->once()) + ->method('applyRequestHeaders'); + $t->request->setBody($p); + $s->onBefore(new BeforeEvent($t)); + } + + public function testAddsExpectHeaderWithTrue() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->getConfig()->set('expect', true); + $t->request->setBody(Stream::factory('foo')); + $s->onBefore(new BeforeEvent($t)); + $this->assertEquals('100-Continue', $t->request->getHeader('Expect')); + } + + public function testAddsExpectHeaderBySize() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->getConfig()->set('expect', 2); + $t->request->setBody(Stream::factory('foo')); + $s->onBefore(new BeforeEvent($t)); + $this->assertTrue($t->request->hasHeader('Expect')); + } + + public function testDoesNotModifyExpectHeaderIfPresent() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->setHeader('Expect', 'foo'); + $t->request->setBody(Stream::factory('foo')); + $s->onBefore(new BeforeEvent($t)); + $this->assertEquals('foo', $t->request->getHeader('Expect')); + } + + public function testDoesAddExpectHeaderWhenSetToFalse() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->getConfig()->set('expect', false); + $t->request->setBody(Stream::factory('foo')); + $s->onBefore(new BeforeEvent($t)); + $this->assertFalse($t->request->hasHeader('Expect')); + } + + public function testDoesNotAddExpectHeaderBySize() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->getConfig()->set('expect', 10); + $t->request->setBody(Stream::factory('foo')); + $s->onBefore(new BeforeEvent($t)); + $this->assertFalse($t->request->hasHeader('Expect')); + } + + public function testAddsExpectHeaderForNonSeekable() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->setBody(new NoSeekStream(Stream::factory('foo'))); + $s->onBefore(new BeforeEvent($t)); + $this->assertTrue($t->request->hasHeader('Expect')); + } + + public function testRemovesContentLengthWhenSendingWithChunked() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->setBody(Stream::factory('foo')); + $t->request->setHeader('Transfer-Encoding', 'chunked'); + $s->onBefore(new BeforeEvent($t)); + $this->assertFalse($t->request->hasHeader('Content-Length')); + } + + public function testUsesProvidedContentLengthAndRemovesXferEncoding() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->setBody(Stream::factory('foo')); + $t->request->setHeader('Content-Length', '3'); + $t->request->setHeader('Transfer-Encoding', 'chunked'); + $s->onBefore(new BeforeEvent($t)); + $this->assertEquals(3, $t->request->getHeader('Content-Length')); + $this->assertFalse($t->request->hasHeader('Transfer-Encoding')); + } + + public function testSetsContentTypeIfPossibleFromStream() + { + $body = $this->getMockBody(); + $sub = new Prepare(); + $t = $this->getTrans(); + $t->request->setBody($body); + $sub->onBefore(new BeforeEvent($t)); + $this->assertEquals( + 'image/jpeg', + $t->request->getHeader('Content-Type') + ); + $this->assertEquals(4, $t->request->getHeader('Content-Length')); + } + + public function testDoesNotOverwriteExistingContentType() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->setBody($this->getMockBody()); + $t->request->setHeader('Content-Type', 'foo/baz'); + $s->onBefore(new BeforeEvent($t)); + $this->assertEquals( + 'foo/baz', + $t->request->getHeader('Content-Type') + ); + } + + public function testSetsContentLengthIfPossible() + { + $s = new Prepare(); + $t = $this->getTrans(); + $t->request->setBody($this->getMockBody()); + $s->onBefore(new BeforeEvent($t)); + $this->assertEquals(4, $t->request->getHeader('Content-Length')); + } + + public function testSetsTransferEncodingChunkedIfNeeded() + { + $r = new Request('PUT', '/'); + $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['getSize']) + ->getMockForAbstractClass(); + $s->expects($this->exactly(2)) + ->method('getSize') + ->will($this->returnValue(null)); + $r->setBody($s); + $t = $this->getTrans($r); + $s = new Prepare(); + $s->onBefore(new BeforeEvent($t)); + $this->assertEquals('chunked', $r->getHeader('Transfer-Encoding')); + } + + public function testContentLengthIntegrationTest() + { + Server::flush(); + Server::enqueue([new Response(200)]); + $client = new Client(['base_url' => Server::$url]); + $this->assertEquals(200, $client->put('/', [ + 'body' => 'test' + ])->getStatusCode()); + $request = Server::received(true)[0]; + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('4', $request->getHeader('Content-Length')); + $this->assertEquals('test', (string) $request->getBody()); + } + + private function getTrans($request = null) + { + return new Transaction( + new Client(), + $request ?: new Request('PUT', '/') + ); + } + + /** + * @return \GuzzleHttp\Stream\StreamInterface + */ + private function getMockBody() + { + $s = $this->getMockBuilder('GuzzleHttp\Stream\MetadataStreamInterface') + ->setMethods(['getMetadata', 'getSize']) + ->getMockForAbstractClass(); + $s->expects($this->any()) + ->method('getMetadata') + ->with('uri') + ->will($this->returnValue('/foo/baz/bar.jpg')); + $s->expects($this->exactly(2)) + ->method('getSize') + ->will($this->returnValue(4)); + + return $s; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/RedirectTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/RedirectTest.php new file mode 100644 index 0000000..bd12af7 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/Subscriber/RedirectTest.php @@ -0,0 +1,302 @@ +addMultiple([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + ]); + + $client = new Client(['base_url' => 'http://test.com']); + $client->getEmitter()->attach($history); + $client->getEmitter()->attach($mock); + + $request = $client->createRequest('GET', '/foo'); + // Ensure "end" is called only once + $called = 0; + $request->getEmitter()->on('end', function () use (&$called) { + $called++; + }); + $response = $client->send($request); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertContains('/redirect2', $response->getEffectiveUrl()); + + // Ensure that two requests were sent + $requests = $history->getRequests(true); + + $this->assertEquals('/foo', $requests[0]->getPath()); + $this->assertEquals('GET', $requests[0]->getMethod()); + $this->assertEquals('/redirect1', $requests[1]->getPath()); + $this->assertEquals('GET', $requests[1]->getMethod()); + $this->assertEquals('/redirect2', $requests[2]->getPath()); + $this->assertEquals('GET', $requests[2]->getMethod()); + + $this->assertEquals(1, $called); + } + + /** + * @expectedException \GuzzleHttp\Exception\TooManyRedirectsException + * @expectedExceptionMessage Will not follow more than + */ + public function testCanLimitNumberOfRedirects() + { + $mock = new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect3\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect4\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect5\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect6\r\nContent-Length: 0\r\n\r\n" + ]); + $client = new Client(); + $client->getEmitter()->attach($mock); + $client->get('http://www.example.com/foo'); + } + + public function testDefaultBehaviorIsToRedirectWithGetForEntityEnclosingRequests() + { + $h = new History(); + $mock = new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + ]); + $client = new Client(); + $client->getEmitter()->attach($mock); + $client->getEmitter()->attach($h); + $client->post('http://test.com/foo', [ + 'headers' => ['X-Baz' => 'bar'], + 'body' => 'testing' + ]); + + $requests = $h->getRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('GET', $requests[1]->getMethod()); + $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz')); + $this->assertEquals('GET', $requests[2]->getMethod()); + } + + public function testCanRedirectWithStrictRfcCompliance() + { + $h = new History(); + $mock = new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + ]); + $client = new Client(['base_url' => 'http://test.com']); + $client->getEmitter()->attach($mock); + $client->getEmitter()->attach($h); + $client->post('/foo', [ + 'headers' => ['X-Baz' => 'bar'], + 'body' => 'testing', + 'allow_redirects' => ['max' => 10, 'strict' => true] + ]); + + $requests = $h->getRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('POST', $requests[1]->getMethod()); + $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz')); + $this->assertEquals('POST', $requests[2]->getMethod()); + } + + public function testRewindsStreamWhenRedirectingIfNeeded() + { + $h = new History(); + $mock = new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + ]); + $client = new Client(['base_url' => 'http://test.com']); + $client->getEmitter()->attach($mock); + $client->getEmitter()->attach($h); + + $body = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['seek', 'read', 'eof', 'tell']) + ->getMockForAbstractClass(); + $body->expects($this->once())->method('tell')->will($this->returnValue(1)); + $body->expects($this->once())->method('seek')->will($this->returnValue(true)); + $body->expects($this->any())->method('eof')->will($this->returnValue(true)); + $body->expects($this->any())->method('read')->will($this->returnValue('foo')); + $client->post('/foo', [ + 'body' => $body, + 'allow_redirects' => ['max' => 5, 'strict' => true] + ]); + } + + /** + * @expectedException \GuzzleHttp\Exception\CouldNotRewindStreamException + * @expectedExceptionMessage Unable to rewind the non-seekable request body after redirecting + */ + public function testThrowsExceptionWhenStreamCannotBeRewound() + { + $h = new History(); + $mock = new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + ]); + $client = new Client(); + $client->getEmitter()->attach($mock); + $client->getEmitter()->attach($h); + + $body = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['seek', 'read', 'eof', 'tell']) + ->getMockForAbstractClass(); + $body->expects($this->once())->method('tell')->will($this->returnValue(1)); + $body->expects($this->once())->method('seek')->will($this->returnValue(false)); + $body->expects($this->any())->method('eof')->will($this->returnValue(true)); + $body->expects($this->any())->method('read')->will($this->returnValue('foo')); + $client->post('http://example.com/foo', [ + 'body' => $body, + 'allow_redirects' => ['max' => 10, 'strict' => true] + ]); + } + + public function testRedirectsCanBeDisabledPerRequest() + { + $client = new Client(['base_url' => 'http://test.com']); + $client->getEmitter()->attach(new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + ])); + $response = $client->put('/', ['body' => 'test', 'allow_redirects' => false]); + $this->assertEquals(301, $response->getStatusCode()); + } + + public function testCanRedirectWithNoLeadingSlashAndQuery() + { + $h = new History(); + $client = new Client(['base_url' => 'http://www.foo.com']); + $client->getEmitter()->attach(new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect?foo=bar\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + ])); + $client->getEmitter()->attach($h); + $client->get('?foo=bar'); + $requests = $h->getRequests(true); + $this->assertEquals('http://www.foo.com?foo=bar', $requests[0]->getUrl()); + $this->assertEquals('http://www.foo.com/redirect?foo=bar', $requests[1]->getUrl()); + } + + public function testHandlesRedirectsWithSpacesProperly() + { + $client = new Client(['base_url' => 'http://www.foo.com']); + $client->getEmitter()->attach(new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect 1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + ])); + $h = new History(); + $client->getEmitter()->attach($h); + $client->get('/foo'); + $reqs = $h->getRequests(true); + $this->assertEquals('/redirect%201', $reqs[1]->getResource()); + } + + public function testAddsRefererWhenPossible() + { + $client = new Client(['base_url' => 'http://www.foo.com']); + $client->getEmitter()->attach(new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /bar\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + ])); + $h = new History(); + $client->getEmitter()->attach($h); + $client->get('/foo', ['allow_redirects' => ['max' => 5, 'referer' => true]]); + $reqs = $h->getRequests(true); + $this->assertEquals('http://www.foo.com/foo', $reqs[1]->getHeader('Referer')); + } + + public function testDoesNotAddRefererWhenChangingProtocols() + { + $client = new Client(['base_url' => 'https://www.foo.com']); + $client->getEmitter()->attach(new Mock([ + "HTTP/1.1 301 Moved Permanently\r\n" + . "Location: http://www.foo.com/foo\r\n" + . "Content-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + ])); + $h = new History(); + $client->getEmitter()->attach($h); + $client->get('/foo', ['allow_redirects' => ['max' => 5, 'referer' => true]]); + $reqs = $h->getRequests(true); + $this->assertFalse($reqs[1]->hasHeader('Referer')); + } + + public function testRedirectsWithGetOn303() + { + $h = new History(); + $mock = new Mock([ + "HTTP/1.1 303 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + ]); + $client = new Client(); + $client->getEmitter()->attach($mock); + $client->getEmitter()->attach($h); + $client->post('http://test.com/foo', ['body' => 'testing']); + $requests = $h->getRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('GET', $requests[1]->getMethod()); + } + + public function testRelativeLinkBasedLatestRequest() + { + $client = new Client(['base_url' => 'http://www.foo.com']); + $client->getEmitter()->attach(new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.bar.com\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + ])); + $response = $client->get('/'); + $this->assertEquals( + 'http://www.bar.com/redirect', + $response->getEffectiveUrl() + ); + } + + public function testUpperCaseScheme() + { + $client = new Client(['base_url' => 'http://www.foo.com']); + $client->getEmitter()->attach(new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: HTTP://www.bar.com\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + ])); + $response = $client->get('/'); + $this->assertEquals( + 'http://www.bar.com', + $response->getEffectiveUrl() + ); + } + + /** + * @expectedException \GuzzleHttp\Exception\BadResponseException + * @expectedExceptionMessage Redirect URL, https://foo.com/redirect2, does not use one of the allowed redirect protocols: http + */ + public function testThrowsWhenRedirectingToInvalidUrlProtocol() + { + $mock = new Mock([ + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: https://foo.com/redirect2\r\nContent-Length: 0\r\n\r\n" + ]); + $client = new Client(); + $client->getEmitter()->attach($mock); + $client->get('http://www.example.com/foo', [ + 'allow_redirects' => [ + 'protocols' => ['http'] + ] + ]); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/TransactionTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/TransactionTest.php new file mode 100644 index 0000000..42965b1 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/TransactionTest.php @@ -0,0 +1,22 @@ +assertSame($client, $t->client); + $this->assertSame($request, $t->request); + $response = new Response(200); + $t->response = $response; + $this->assertSame($response, $t->response); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UriTemplateTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UriTemplateTest.php new file mode 100644 index 0000000..3f7a7f0 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UriTemplateTest.php @@ -0,0 +1,202 @@ + 'value', + 'hello' => 'Hello World!', + 'empty' => '', + 'path' => '/foo/bar', + 'x' => '1024', + 'y' => '768', + 'null' => null, + 'list' => array('red', 'green', 'blue'), + 'keys' => array( + "semi" => ';', + "dot" => '.', + "comma" => ',' + ), + 'empty_keys' => array(), + ); + + return array_map(function ($t) use ($params) { + $t[] = $params; + return $t; + }, array( + array('foo', 'foo'), + array('{var}', 'value'), + array('{hello}', 'Hello%20World%21'), + array('{+var}', 'value'), + array('{+hello}', 'Hello%20World!'), + array('{+path}/here', '/foo/bar/here'), + array('here?ref={+path}', 'here?ref=/foo/bar'), + array('X{#var}', 'X#value'), + array('X{#hello}', 'X#Hello%20World!'), + array('map?{x,y}', 'map?1024,768'), + array('{x,hello,y}', '1024,Hello%20World%21,768'), + array('{+x,hello,y}', '1024,Hello%20World!,768'), + array('{+path,x}/here', '/foo/bar,1024/here'), + array('{#x,hello,y}', '#1024,Hello%20World!,768'), + array('{#path,x}/here', '#/foo/bar,1024/here'), + array('X{.var}', 'X.value'), + array('X{.x,y}', 'X.1024.768'), + array('{/var}', '/value'), + array('{/var,x}/here', '/value/1024/here'), + array('{;x,y}', ';x=1024;y=768'), + array('{;x,y,empty}', ';x=1024;y=768;empty'), + array('{?x,y}', '?x=1024&y=768'), + array('{?x,y,empty}', '?x=1024&y=768&empty='), + array('?fixed=yes{&x}', '?fixed=yes&x=1024'), + array('{&x,y,empty}', '&x=1024&y=768&empty='), + array('{var:3}', 'val'), + array('{var:30}', 'value'), + array('{list}', 'red,green,blue'), + array('{list*}', 'red,green,blue'), + array('{keys}', 'semi,%3B,dot,.,comma,%2C'), + array('{keys*}', 'semi=%3B,dot=.,comma=%2C'), + array('{+path:6}/here', '/foo/b/here'), + array('{+list}', 'red,green,blue'), + array('{+list*}', 'red,green,blue'), + array('{+keys}', 'semi,;,dot,.,comma,,'), + array('{+keys*}', 'semi=;,dot=.,comma=,'), + array('{#path:6}/here', '#/foo/b/here'), + array('{#list}', '#red,green,blue'), + array('{#list*}', '#red,green,blue'), + array('{#keys}', '#semi,;,dot,.,comma,,'), + array('{#keys*}', '#semi=;,dot=.,comma=,'), + array('X{.var:3}', 'X.val'), + array('X{.list}', 'X.red,green,blue'), + array('X{.list*}', 'X.red.green.blue'), + array('X{.keys}', 'X.semi,%3B,dot,.,comma,%2C'), + array('X{.keys*}', 'X.semi=%3B.dot=..comma=%2C'), + array('{/var:1,var}', '/v/value'), + array('{/list}', '/red,green,blue'), + array('{/list*}', '/red/green/blue'), + array('{/list*,path:4}', '/red/green/blue/%2Ffoo'), + array('{/keys}', '/semi,%3B,dot,.,comma,%2C'), + array('{/keys*}', '/semi=%3B/dot=./comma=%2C'), + array('{;hello:5}', ';hello=Hello'), + array('{;list}', ';list=red,green,blue'), + array('{;list*}', ';list=red;list=green;list=blue'), + array('{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'), + array('{;keys*}', ';semi=%3B;dot=.;comma=%2C'), + array('{?var:3}', '?var=val'), + array('{?list}', '?list=red,green,blue'), + array('{?list*}', '?list=red&list=green&list=blue'), + array('{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'), + array('{?keys*}', '?semi=%3B&dot=.&comma=%2C'), + array('{&var:3}', '&var=val'), + array('{&list}', '&list=red,green,blue'), + array('{&list*}', '&list=red&list=green&list=blue'), + array('{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'), + array('{&keys*}', '&semi=%3B&dot=.&comma=%2C'), + array('{.null}', ''), + array('{.null,var}', '.value'), + array('X{.empty_keys*}', 'X'), + array('X{.empty_keys}', 'X'), + // Test that missing expansions are skipped + array('test{&missing*}', 'test'), + // Test that multiple expansions can be set + array('http://{var}/{var:2}{?keys*}', 'http://value/va?semi=%3B&dot=.&comma=%2C'), + // Test more complex query string stuff + array('http://www.test.com{+path}{?var,keys*}', 'http://www.test.com/foo/bar?var=value&semi=%3B&dot=.&comma=%2C') + )); + } + + /** + * @dataProvider templateProvider + */ + public function testExpandsUriTemplates($template, $expansion, $params) + { + $uri = new UriTemplate($template); + $this->assertEquals($expansion, $uri->expand($template, $params)); + } + + public function expressionProvider() + { + return array( + array( + '{+var*}', array( + 'operator' => '+', + 'values' => array( + array('value' => 'var', 'modifier' => '*') + ) + ), + ), + array( + '{?keys,var,val}', array( + 'operator' => '?', + 'values' => array( + array('value' => 'keys', 'modifier' => ''), + array('value' => 'var', 'modifier' => ''), + array('value' => 'val', 'modifier' => '') + ) + ), + ), + array( + '{+x,hello,y}', array( + 'operator' => '+', + 'values' => array( + array('value' => 'x', 'modifier' => ''), + array('value' => 'hello', 'modifier' => ''), + array('value' => 'y', 'modifier' => '') + ) + ) + ) + ); + } + + /** + * @dataProvider expressionProvider + */ + public function testParsesExpressions($exp, $data) + { + $template = new UriTemplate($exp); + + // Access the config object + $class = new \ReflectionClass($template); + $method = $class->getMethod('parseExpression'); + $method->setAccessible(true); + + $exp = substr($exp, 1, -1); + $this->assertEquals($data, $method->invokeArgs($template, array($exp))); + } + + /** + * @ticket https://github.com/guzzle/guzzle/issues/90 + */ + public function testAllowsNestedArrayExpansion() + { + $template = new UriTemplate(); + + $result = $template->expand('http://example.com{+path}{/segments}{?query,data*,foo*}', array( + 'path' => '/foo/bar', + 'segments' => array('one', 'two'), + 'query' => 'test', + 'data' => array( + 'more' => array('fun', 'ice cream') + ), + 'foo' => array( + 'baz' => array( + 'bar' => 'fizz', + 'test' => 'buzz' + ), + 'bam' => 'boo' + ) + )); + + $this->assertEquals('http://example.com/foo/bar/one,two?query=test&more%5B0%5D=fun&more%5B1%5D=ice%20cream&baz%5Bbar%5D=fizz&baz%5Btest%5D=buzz&bam=boo', $result); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UrlTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UrlTest.php new file mode 100644 index 0000000..22bf7e4 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UrlTest.php @@ -0,0 +1,364 @@ +assertEquals('', (string) $url); + } + + public function testPortIsDeterminedFromScheme() + { + $this->assertEquals(80, Url::fromString('http://www.test.com/')->getPort()); + $this->assertEquals(443, Url::fromString('https://www.test.com/')->getPort()); + $this->assertEquals(21, Url::fromString('ftp://www.test.com/')->getPort()); + $this->assertEquals(8192, Url::fromString('http://www.test.com:8192/')->getPort()); + $this->assertEquals(null, Url::fromString('foo://www.test.com/')->getPort()); + } + + public function testRemovesDefaultPortWhenSettingScheme() + { + $url = Url::fromString('http://www.test.com/'); + $url->setPort(80); + $url->setScheme('https'); + $this->assertEquals(443, $url->getPort()); + } + + public function testCloneCreatesNewInternalObjects() + { + $u1 = Url::fromString('http://www.test.com/'); + $u2 = clone $u1; + $this->assertNotSame($u1->getQuery(), $u2->getQuery()); + } + + public function testValidatesUrlPartsInFactory() + { + $url = Url::fromString('/index.php'); + $this->assertEquals('/index.php', (string) $url); + $this->assertFalse($url->isAbsolute()); + + $url = 'http://michael:test@test.com:80/path/123?q=abc#test'; + $u = Url::fromString($url); + $this->assertEquals('http://michael:test@test.com/path/123?q=abc#test', (string) $u); + $this->assertTrue($u->isAbsolute()); + } + + public function testAllowsFalsyUrlParts() + { + $url = Url::fromString('http://a:50/0?0#0'); + $this->assertSame('a', $url->getHost()); + $this->assertEquals(50, $url->getPort()); + $this->assertSame('/0', $url->getPath()); + $this->assertEquals('0', (string) $url->getQuery()); + $this->assertSame('0', $url->getFragment()); + $this->assertEquals('http://a:50/0?0#0', (string) $url); + + $url = Url::fromString(''); + $this->assertSame('', (string) $url); + + $url = Url::fromString('0'); + $this->assertSame('0', (string) $url); + } + + public function testBuildsRelativeUrlsWithFalsyParts() + { + $url = Url::buildUrl(['path' => '/0']); + $this->assertSame('/0', $url); + + $url = Url::buildUrl(['path' => '0']); + $this->assertSame('0', $url); + + $url = Url::buildUrl(['host' => '', 'path' => '0']); + $this->assertSame('0', $url); + } + + public function testUrlStoresParts() + { + $url = Url::fromString('http://test:pass@www.test.com:8081/path/path2/?a=1&b=2#fragment'); + $this->assertEquals('http', $url->getScheme()); + $this->assertEquals('test', $url->getUsername()); + $this->assertEquals('pass', $url->getPassword()); + $this->assertEquals('www.test.com', $url->getHost()); + $this->assertEquals(8081, $url->getPort()); + $this->assertEquals('/path/path2/', $url->getPath()); + $this->assertEquals('fragment', $url->getFragment()); + $this->assertEquals('a=1&b=2', (string) $url->getQuery()); + + $this->assertEquals(array( + 'fragment' => 'fragment', + 'host' => 'www.test.com', + 'pass' => 'pass', + 'path' => '/path/path2/', + 'port' => 8081, + 'query' => 'a=1&b=2', + 'scheme' => 'http', + 'user' => 'test' + ), $url->getParts()); + } + + public function testHandlesPathsCorrectly() + { + $url = Url::fromString('http://www.test.com'); + $this->assertEquals('', $url->getPath()); + $url->setPath('test'); + $this->assertEquals('test', $url->getPath()); + + $url->setPath('/test/123/abc'); + $this->assertEquals(array('', 'test', '123', 'abc'), $url->getPathSegments()); + + $parts = parse_url('http://www.test.com/test'); + $parts['path'] = ''; + $this->assertEquals('http://www.test.com', Url::buildUrl($parts)); + $parts['path'] = 'test'; + $this->assertEquals('http://www.test.com/test', Url::buildUrl($parts)); + } + + public function testAddsQueryIfPresent() + { + $this->assertEquals('?foo=bar', Url::buildUrl(array( + 'query' => 'foo=bar' + ))); + } + + public function testAddsToPath() + { + // Does nothing here + $url = Url::fromString('http://e.com/base?a=1'); + $url->addPath(false); + $this->assertEquals('http://e.com/base?a=1', $url); + $url = Url::fromString('http://e.com/base?a=1'); + $url->addPath(''); + $this->assertEquals('http://e.com/base?a=1', $url); + $url = Url::fromString('http://e.com/base?a=1'); + $url->addPath('/'); + $this->assertEquals('http://e.com/base?a=1', $url); + $url = Url::fromString('http://e.com/base'); + $url->addPath('0'); + $this->assertEquals('http://e.com/base/0', $url); + + $url = Url::fromString('http://e.com/base?a=1'); + $url->addPath('relative'); + $this->assertEquals('http://e.com/base/relative?a=1', $url); + $url = Url::fromString('http://e.com/base?a=1'); + $url->addPath('/relative'); + $this->assertEquals('http://e.com/base/relative?a=1', $url); + } + + /** + * URL combination data provider + * + * @return array + */ + public function urlCombineDataProvider() + { + return [ + // Specific test cases + ['http://www.example.com/', 'http://www.example.com/', 'http://www.example.com/'], + ['http://www.example.com/path', '/absolute', 'http://www.example.com/absolute'], + ['http://www.example.com/path', '/absolute?q=2', 'http://www.example.com/absolute?q=2'], + ['http://www.example.com/', '?q=1', 'http://www.example.com/?q=1'], + ['http://www.example.com/path', 'http://test.com', 'http://test.com'], + ['http://www.example.com:8080/path', 'http://test.com', 'http://test.com'], + ['http://www.example.com:8080/path', '?q=2#abc', 'http://www.example.com:8080/path?q=2#abc'], + ['http://www.example.com/path', 'http://u:a@www.example.com/', 'http://u:a@www.example.com/'], + ['/path?q=2', 'http://www.test.com/', 'http://www.test.com/path?q=2'], + ['http://api.flickr.com/services/', 'http://www.flickr.com/services/oauth/access_token', 'http://www.flickr.com/services/oauth/access_token'], + ['https://www.example.com/path', '//foo.com/abc', 'https://foo.com/abc'], + ['https://www.example.com/0/', 'relative/foo', 'https://www.example.com/0/relative/foo'], + ['', '0', '0'], + // RFC 3986 test cases + [self::RFC3986_BASE, 'g:h', 'g:h'], + [self::RFC3986_BASE, 'g', 'http://a/b/c/g'], + [self::RFC3986_BASE, './g', 'http://a/b/c/g'], + [self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'], + [self::RFC3986_BASE, '/g', 'http://a/g'], + [self::RFC3986_BASE, '//g', 'http://g'], + [self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'], + [self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'], + [self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'], + [self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'], + [self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'], + [self::RFC3986_BASE, ';x', 'http://a/b/c/;x'], + [self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'], + [self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'], + [self::RFC3986_BASE, '', self::RFC3986_BASE], + [self::RFC3986_BASE, '.', 'http://a/b/c/'], + [self::RFC3986_BASE, './', 'http://a/b/c/'], + [self::RFC3986_BASE, '..', 'http://a/b/'], + [self::RFC3986_BASE, '../', 'http://a/b/'], + [self::RFC3986_BASE, '../g', 'http://a/b/g'], + [self::RFC3986_BASE, '../..', 'http://a/'], + [self::RFC3986_BASE, '../../', 'http://a/'], + [self::RFC3986_BASE, '../../g', 'http://a/g'], + [self::RFC3986_BASE, '../../../g', 'http://a/g'], + [self::RFC3986_BASE, '../../../../g', 'http://a/g'], + [self::RFC3986_BASE, '/./g', 'http://a/g'], + [self::RFC3986_BASE, '/../g', 'http://a/g'], + [self::RFC3986_BASE, 'g.', 'http://a/b/c/g.'], + [self::RFC3986_BASE, '.g', 'http://a/b/c/.g'], + [self::RFC3986_BASE, 'g..', 'http://a/b/c/g..'], + [self::RFC3986_BASE, '..g', 'http://a/b/c/..g'], + [self::RFC3986_BASE, './../g', 'http://a/b/g'], + [self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'], + [self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'], + [self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'], + [self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'], + [self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'], + [self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'], + [self::RFC3986_BASE, 'http:g', 'http:g'], + ]; + } + + /** + * @dataProvider urlCombineDataProvider + */ + public function testCombinesUrls($a, $b, $c) + { + $this->assertEquals($c, (string) Url::fromString($a)->combine($b)); + } + + public function testHasGettersAndSetters() + { + $url = Url::fromString('http://www.test.com/'); + $url->setHost('example.com'); + $this->assertEquals('example.com', $url->getHost()); + $url->setPort(8080); + $this->assertEquals('8080', $url->getPort()); + $url->setPath('/foo/bar'); + $this->assertEquals('/foo/bar', $url->getPath()); + $url->setPassword('a'); + $this->assertEquals('a', $url->getPassword()); + $url->setUsername('b'); + $this->assertEquals('b', $url->getUsername()); + $url->setFragment('abc'); + $this->assertEquals('abc', $url->getFragment()); + $url->setScheme('https'); + $this->assertEquals('https', $url->getScheme()); + $url->setQuery('a=123'); + $this->assertEquals('a=123', (string) $url->getQuery()); + $this->assertEquals( + 'https://b:a@example.com:8080/foo/bar?a=123#abc', + (string) $url + ); + $url->setQuery(new Query(['b' => 'boo'])); + $this->assertEquals('b=boo', $url->getQuery()); + $this->assertEquals( + 'https://b:a@example.com:8080/foo/bar?b=boo#abc', + (string) $url + ); + + $url->setQuery('a%20=bar!', true); + $this->assertEquals( + 'https://b:a@example.com:8080/foo/bar?a%20=bar!#abc', + (string) $url + ); + } + + public function testSetQueryAcceptsArray() + { + $url = Url::fromString('http://www.test.com'); + $url->setQuery(array('a' => 'b')); + $this->assertEquals('http://www.test.com?a=b', (string) $url); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testQueryMustBeValid() + { + $url = Url::fromString('http://www.test.com'); + $url->setQuery(false); + } + + public function testDefersParsingAndEncodingQueryUntilNecessary() + { + $url = Url::fromString('http://www.test.com'); + // Note that invalid characters are encoded. + $url->setQuery('foo#bar/', true); + $this->assertEquals('http://www.test.com?foo%23bar/', (string) $url); + $this->assertInternalType('string', $this->readAttribute($url, 'query')); + $this->assertEquals('foo%23bar%2F', (string) $url->getQuery()); + $this->assertInstanceOf('GuzzleHttp\Query', $this->readAttribute($url, 'query')); + } + + public function urlProvider() + { + return array( + array('/foo/..', '/'), + array('//foo//..', '//foo/'), + array('/foo//', '/foo//'), + array('/foo/../..', '/'), + array('/foo/../.', '/'), + array('/./foo/..', '/'), + array('/./foo', '/foo'), + array('/./foo/', '/foo/'), + array('*', '*'), + array('/foo', '/foo'), + array('/abc/123/../foo/', '/abc/foo/'), + array('/a/b/c/./../../g', '/a/g'), + array('/b/c/./../../g', '/g'), + array('/b/c/./../../g', '/g'), + array('/c/./../../g', '/g'), + array('/./../../g', '/g'), + array('foo', 'foo'), + ); + } + + /** + * @dataProvider urlProvider + */ + public function testRemoveDotSegments($path, $result) + { + $url = Url::fromString('http://www.example.com'); + $url->setPath($path); + $url->removeDotSegments(); + $this->assertEquals($result, $url->getPath()); + } + + public function testSettingHostWithPortModifiesPort() + { + $url = Url::fromString('http://www.example.com'); + $url->setHost('foo:8983'); + $this->assertEquals('foo', $url->getHost()); + $this->assertEquals(8983, $url->getPort()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesUrlCanBeParsed() + { + Url::fromString('foo:////'); + } + + public function testConvertsSpecialCharsInPathWhenCastingToString() + { + $url = Url::fromString('http://foo.com/baz bar?a=b'); + $url->addPath('?'); + $this->assertEquals('http://foo.com/baz%20bar/%3F?a=b', (string) $url); + } + + public function testCorrectlyEncodesPathWithoutDoubleEncoding() + { + $url = Url::fromString('http://foo.com/baz%20 bar:boo/baz!'); + $this->assertEquals('/baz%20%20bar:boo/baz!', $url->getPath()); + } + + public function testLowercaseScheme() + { + $url = Url::fromString('HTTP://foo.com/'); + $this->assertEquals('http', $url->getScheme()); + $url->setScheme('HTTPS'); + $this->assertEquals('https', $url->getScheme()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UtilsTest.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UtilsTest.php new file mode 100644 index 0000000..10bdc54 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/UtilsTest.php @@ -0,0 +1,40 @@ +assertEquals( + 'foo/123', + Utils::uriTemplate('foo/{bar}', ['bar' => '123']) + ); + } + + public function noBodyProvider() + { + return [['get'], ['head'], ['delete']]; + } + + public function testJsonDecodes() + { + $this->assertTrue(Utils::jsonDecode('true')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unable to parse JSON data: JSON_ERROR_SYNTAX - Syntax error, malformed JSON + */ + public function testJsonDecodesWithErrorMessages() + { + Utils::jsonDecode('!narf!'); + } + + public function testProvidesDefaultUserAgent() + { + $ua = Utils::getDefaultUserAgent(); + $this->assertEquals(1, preg_match('#^Guzzle/.+ curl/.+ PHP/.+$#', $ua)); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/bootstrap.php b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/bootstrap.php new file mode 100644 index 0000000..8713f96 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/guzzle/tests/bootstrap.php @@ -0,0 +1,11 @@ + Server::$url]); + +$t = microtime(true); +for ($i = 0; $i < $total; $i++) { + $client->get('/guzzle-server/perf'); +} +$totalTime = microtime(true) - $t; +$perRequest = ($totalTime / $total) * 1000; +printf("Serial: %f (%f ms / request) %d total\n", + $totalTime, $perRequest, $total); + +// Create a generator used to yield batches of requests +$reqs = function () use ($client, $total) { + for ($i = 0; $i < $total; $i++) { + yield $client->createRequest('GET', '/guzzle-server/perf'); + } +}; + +$t = microtime(true); +Pool::send($client, $reqs(), ['parallel' => $parallel]); +$totalTime = microtime(true) - $t; +$perRequest = ($totalTime / $total) * 1000; +printf("Batch: %f (%f ms / request) %d total with %d in parallel\n", + $totalTime, $perRequest, $total, $parallel); + +$handler = new CurlMultiHandler(['max_handles' => $parallel]); +$client = new Client(['handler' => $handler, 'base_url' => Server::$url]); +$t = microtime(true); +for ($i = 0; $i < $total; $i++) { + $client->get('/guzzle-server/perf'); +} +unset($client); +$totalTime = microtime(true) - $t; +$perRequest = ($totalTime / $total) * 1000; +printf("Future: %f (%f ms / request) %d total\n", + $totalTime, $perRequest, $total); diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/.gitignore b/admin/classes/domain/vendor/guzzlehttp/ringphp/.gitignore new file mode 100644 index 0000000..290a945 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/.gitignore @@ -0,0 +1,4 @@ +vendor +build/artifacts/ +composer.lock +docs/_build/ diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/.travis.yml b/admin/classes/domain/vendor/guzzlehttp/ringphp/.travis.yml new file mode 100644 index 0000000..e43fbdd --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/.travis.yml @@ -0,0 +1,22 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +before_script: + - composer self-update + - composer install --no-interaction --prefer-source --dev + - ~/.nvm/nvm.sh install v0.6.14 + - ~/.nvm/nvm.sh run v0.6.14 + +script: + - make test + +matrix: + allow_failures: + - php: hhvm + fast_finish: true diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/CHANGELOG.md b/admin/classes/domain/vendor/guzzlehttp/ringphp/CHANGELOG.md new file mode 100644 index 0000000..d399d82 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/CHANGELOG.md @@ -0,0 +1,54 @@ +# CHANGELOG + +## 1.1.0 - 2015-05-19 + +* Added `CURL_HTTP_VERSION_2_0` +* The PHP stream wrapper handler now sets `allow_self_signed` to `false` to + match the cURL handler when `verify` is set to `true` or a certificate file. +* Ensuring that a directory exists before using the `save_to` option. +* Response protocol version is now correctly extracted from a response. +* Fixed a bug in which the result of `CurlFactory::retryFailedRewind` did not + return an array. + +## 1.0.7 - 2015-03-29 + +* PHP 7 fixes. + +## 1.0.6 - 2015-02-26 + +* Bug fix: futures now extend from React's PromiseInterface to ensure that they + are properly forwarded down the promise chain. +* The multi handle of the CurlMultiHandler is now created lazily. + +## 1.0.5 - 2014-12-10 + +* Adding more error information to PHP stream wrapper exceptions. +* Added digest auth integration test support to test server. + +## 1.0.4 - 2014-12-01 + +* Added support for older versions of cURL that do not have CURLOPT_TIMEOUT_MS. +* Setting debug to `false` does not enable debug output. +* Added a fix to the StreamHandler to return a `FutureArrayInterface` when an + error occurs. + +## 1.0.3 - 2014-11-03 + +* Setting the `header` stream option as a string to be compatible with GAE. +* Header parsing now ensures that header order is maintained in the parsed + message. + +## 1.0.2 - 2014-10-28 + +* Now correctly honoring a `version` option is supplied in a request. + See https://github.com/guzzle/RingPHP/pull/8 + +## 1.0.1 - 2014-10-26 + +* Fixed a header parsing issue with the `CurlHandler` and `CurlMultiHandler` + that caused cURL requests with multiple responses to merge repsonses together + (e.g., requests with digest authentication). + +## 1.0.0 - 2014-10-12 + +* Initial release. diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/LICENSE b/admin/classes/domain/vendor/guzzlehttp/ringphp/LICENSE new file mode 100644 index 0000000..71d3b78 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/Makefile b/admin/classes/domain/vendor/guzzlehttp/ringphp/Makefile new file mode 100644 index 0000000..21c812e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/Makefile @@ -0,0 +1,46 @@ +all: clean coverage docs + +docs: + cd docs && make html + +view-docs: + open docs/_build/html/index.html + +start-server: stop-server + node tests/Client/server.js &> /dev/null & + +stop-server: + @PID=$(shell ps axo pid,command \ + | grep 'tests/Client/server.js' \ + | grep -v grep \ + | cut -f 1 -d " "\ + ) && [ -n "$$PID" ] && kill $$PID || true + +test: start-server + vendor/bin/phpunit $(TEST) + $(MAKE) stop-server + +coverage: start-server + vendor/bin/phpunit --coverage-html=build/artifacts/coverage $(TEST) + $(MAKE) stop-server + +view-coverage: + open build/artifacts/coverage/index.html + +clean: + rm -rf build/artifacts/* + cd docs && make clean + +tag: + $(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1")) + @echo Tagging $(TAG) + chag update -m '$(TAG) ()' + git add -A + git commit -m '$(TAG) release' + chag tag + +perf: start-server + php tests/perf.php + $(MAKE) stop-server + +.PHONY: docs diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/README.rst b/admin/classes/domain/vendor/guzzlehttp/ringphp/README.rst new file mode 100644 index 0000000..10374e8 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/README.rst @@ -0,0 +1,46 @@ +======= +RingPHP +======= + +Provides a simple API and specification that abstracts away the details of HTTP +into a single PHP function. RingPHP be used to power HTTP clients and servers +through a PHP function that accepts a request hash and returns a response hash +that is fulfilled using a `promise `_, +allowing RingPHP to support both synchronous and asynchronous workflows. + +By abstracting the implementation details of different HTTP clients and +servers, RingPHP allows you to utilize pluggable HTTP clients and servers +without tying your application to a specific implementation. + +.. code-block:: php + + 'GET', + 'uri' => '/', + 'headers' => [ + 'host' => ['www.google.com'], + 'x-foo' => ['baz'] + ] + ]); + + $response->then(function (array $response) { + echo $response['status']; + }); + + $response->wait(); + +RingPHP is inspired by Clojure's `Ring `_, +which, in turn, was inspired by Python's WSGI and Ruby's Rack. RingPHP is +utilized as the handler layer in `Guzzle `_ 5.0+ to send +HTTP requests. + +Documentation +------------- + +See http://ringphp.readthedocs.org/ for the full online documentation. diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/composer.json b/admin/classes/domain/vendor/guzzlehttp/ringphp/composer.json new file mode 100644 index 0000000..22002ef --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/composer.json @@ -0,0 +1,39 @@ +{ + "name": "guzzlehttp/ringphp", + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.4.0", + "guzzlehttp/streams": "~3.0", + "react/promise": "~2.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Ring\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Ring\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/Makefile b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/Makefile new file mode 100644 index 0000000..51270aa --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GuzzleRing.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GuzzleRing.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/GuzzleRing" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GuzzleRing" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/client_handlers.rst b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/client_handlers.rst new file mode 100644 index 0000000..3151f00 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/client_handlers.rst @@ -0,0 +1,173 @@ +=============== +Client Handlers +=============== + +Client handlers accept a request array and return a future response array that +can be used synchronously as an array or asynchronously using a promise. + +Built-In Handlers +----------------- + +RingPHP comes with three built-in client handlers. + +Stream Handler +~~~~~~~~~~~~~~ + +The ``GuzzleHttp\Ring\Client\StreamHandler`` uses PHP's +`http stream wrapper `_ to send +requests. + +.. note:: + + This handler cannot send requests concurrently. + +You can provide an associative array of custom stream context options to the +StreamHandler using the ``stream_context`` key of the ``client`` request +option. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\StreamHandler; + + $response = $handler([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['host' => ['httpbin.org']], + 'client' => [ + 'stream_context' => [ + 'http' => [ + 'request_fulluri' => true, + 'method' => 'HEAD' + ], + 'socket' => [ + 'bindto' => '127.0.0.1:0' + ], + 'ssl' => [ + 'verify_peer' => false + ] + ] + ] + ]); + + // Even though it's already completed, you can still use a promise + $response->then(function ($response) { + echo $response['status']; // 200 + }); + + // Or access the response using the future interface + echo $response['status']; // 200 + +cURL Handler +~~~~~~~~~~~~ + +The ``GuzzleHttp\Ring\Client\CurlHandler`` can be used with PHP 5.5+ to send +requests using cURL easy handles. This handler is great for sending requests +one at a time because the execute and select loop is implemented in C code +which executes faster and consumes less memory than using PHP's +``curl_multi_*`` interface. + +.. note:: + + This handler cannot send requests concurrently. + +When using the CurlHandler, custom curl options can be specified as an +associative array of `cURL option constants `_ +mapping to values in the ``client`` option of a requst using the **curl** key. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\CurlHandler; + + $handler = new CurlHandler(); + + $request = [ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => ['curl' => [CURLOPT_LOW_SPEED_LIMIT => 10]] + ]; + + $response = $handler($request); + + // The response can be used directly as an array. + echo $response['status']; // 200 + + // Or, it can be used as a promise (that has already fulfilled). + $response->then(function ($response) { + echo $response['status']; // 200 + }); + +cURL Multi Handler +~~~~~~~~~~~~~~~~~~ + +The ``GuzzleHttp\Ring\Client\CurlMultiHandler`` transfers requests using +cURL's `multi API `_. The +``CurlMultiHandler`` is great for sending requests concurrently. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\CurlMultiHandler; + + $handler = new CurlMultiHandler(); + + $request = [ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]] + ]; + + // this call returns a future array immediately. + $response = $handler($request); + + // Ideally, you should use the promise API to not block. + $response + ->then(function ($response) { + // Got the response at some point in the future + echo $response['status']; // 200 + // Don't break the chain + return $response; + })->then(function ($response) { + // ... + }); + + // If you really need to block, then you can use the response as an + // associative array. This will block until it has completed. + echo $response['status']; // 200 + +Just like the ``CurlHandler``, the ``CurlMultiHandler`` accepts custom curl +option in the ``curl`` key of the ``client`` request option. + +Mock Handler +~~~~~~~~~~~~ + +The ``GuzzleHttp\Ring\Client\MockHandler`` is used to return mock responses. +When constructed, the handler can be configured to return the same response +array over and over, a future response, or a the evaluation of a callback +function. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\MockHandler; + + // Return a canned response. + $mock = new MockHandler(['status' => 200]); + $response = $mock([]); + assert(200 == $response['status']); + assert([] == $response['headers']); + +Implementing Handlers +--------------------- + +Client handlers are just PHP callables (functions or classes that have the +``__invoke`` magic method). The callable accepts a request array and MUST +return an instance of ``GuzzleHttp\Ring\Future\FutureArrayInterface`` so that +the response can be used by both blocking and non-blocking consumers. + +Handlers need to follow a few simple rules: + +1. Do not throw exceptions. If an error is encountered, return an array that + contains the ``error`` key that maps to an ``\Exception`` value. +2. If the request has a ``delay`` client option, then the handler should only + send the request after the specified delay time in seconds. Blocking + handlers may find it convenient to just let the + ``GuzzleHttp\Ring\Core::doSleep($request)`` function handle this for them. +3. Always return an instance of ``GuzzleHttp\Ring\Future\FutureArrayInterface``. +4. Complete any outstanding requests when the handler is destructed. diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/client_middleware.rst b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/client_middleware.rst new file mode 100644 index 0000000..5a2c1a8 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/client_middleware.rst @@ -0,0 +1,165 @@ +================= +Client Middleware +================= + +Middleware intercepts requests before they are sent over the wire and can be +used to add functionality to handlers. + +Modifying Requests +------------------ + +Let's say you wanted to modify requests before they are sent over the wire +so that they always add specific headers. This can be accomplished by creating +a function that accepts a handler and returns a new function that adds the +composed behavior. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\CurlHandler; + + $handler = new CurlHandler(); + + $addHeaderHandler = function (callable $handler, array $headers = []) { + return function (array $request) use ($handler, $headers) { + // Add our custom headers + foreach ($headers as $key => $value) { + $request['headers'][$key] = $value; + } + + // Send the request using the handler and return the response. + return $handler($request); + } + }; + + // Create a new handler that adds headers to each request. + $handler = $addHeaderHandler($handler, [ + 'X-AddMe' => 'hello', + 'Authorization' => 'Basic xyz' + ]); + + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['Host' => ['httpbin.org']] + ]); + +Modifying Responses +------------------- + +You can change a response as it's returned from a middleware. Remember that +responses returned from an handler (including middleware) must implement +``GuzzleHttp\Ring\Future\FutureArrayInterface``. In order to be a good citizen, +you should not expect that the responses returned through your middleware will +be completed synchronously. Instead, you should use the +``GuzzleHttp\Ring\Core::proxy()`` function to modify the response when the +underlying promise is resolved. This function is a helper function that makes it +easy to create a new instance of ``FutureArrayInterface`` that wraps an existing +``FutureArrayInterface`` object. + +Let's say you wanted to add headers to a response as they are returned from +your middleware, but you want to make sure you aren't causing future +responses to be dereferenced right away. You can achieve this by modifying the +incoming request and using the ``Core::proxy`` function. + +.. code-block:: php + + use GuzzleHttp\Ring\Core; + use GuzzleHttp\Ring\Client\CurlHandler; + + $handler = new CurlHandler(); + + $responseHeaderHandler = function (callable $handler, array $headers) { + return function (array $request) use ($handler, $headers) { + // Send the request using the wrapped handler. + return Core::proxy($handler($request), function ($response) use ($headers) { + // Add the headers to the response when it is available. + foreach ($headers as $key => $value) { + $response['headers'][$key] = (array) $value; + } + // Note that you can return a regular response array when using + // the proxy method. + return $response; + }); + } + }; + + // Create a new handler that adds headers to each response. + $handler = $responseHeaderHandler($handler, ['X-Header' => 'hello!']); + + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['Host' => ['httpbin.org']] + ]); + + assert($response['headers']['X-Header'] == 'hello!'); + +Built-In Middleware +------------------- + +RingPHP comes with a few basic client middlewares that modify requests +and responses. + +Streaming Middleware +~~~~~~~~~~~~~~~~~~~~ + +If you want to send all requests with the ``streaming`` option to a specific +handler but other requests to a different handler, then use the streaming +middleware. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\CurlHandler; + use GuzzleHttp\Ring\Client\StreamHandler; + use GuzzleHttp\Ring\Client\Middleware; + + $defaultHandler = new CurlHandler(); + $streamingHandler = new StreamHandler(); + $streamingHandler = Middleware::wrapStreaming( + $defaultHandler, + $streamingHandler + ); + + // Send the request using the streaming handler. + $response = $streamingHandler([ + 'http_method' => 'GET', + 'headers' => ['Host' => ['www.google.com']], + 'stream' => true + ]); + + // Send the request using the default handler. + $response = $streamingHandler([ + 'http_method' => 'GET', + 'headers' => ['Host' => ['www.google.com']] + ]); + +Future Middleware +~~~~~~~~~~~~~~~~~ + +If you want to send all requests with the ``future`` option to a specific +handler but other requests to a different handler, then use the future +middleware. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\CurlHandler; + use GuzzleHttp\Ring\Client\CurlMultiHandler; + use GuzzleHttp\Ring\Client\Middleware; + + $defaultHandler = new CurlHandler(); + $futureHandler = new CurlMultiHandler(); + $futureHandler = Middleware::wrapFuture( + $defaultHandler, + $futureHandler + ); + + // Send the request using the blocking CurlHandler. + $response = $futureHandler([ + 'http_method' => 'GET', + 'headers' => ['Host' => ['www.google.com']] + ]); + + // Send the request using the non-blocking CurlMultiHandler. + $response = $futureHandler([ + 'http_method' => 'GET', + 'headers' => ['Host' => ['www.google.com']], + 'future' => true + ]); diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/conf.py b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/conf.py new file mode 100644 index 0000000..c6404aa --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/conf.py @@ -0,0 +1,23 @@ +import sys, os +import sphinx_rtd_theme +from sphinx.highlighting import lexers +from pygments.lexers.web import PhpLexer + + +lexers['php'] = PhpLexer(startinline=True, linenos=1) +lexers['php-annotations'] = PhpLexer(startinline=True, linenos=1) +primary_domain = 'php' + +extensions = [] +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' +project = u'RingPHP' +copyright = u'2014, Michael Dowling' +version = '1.0.0-alpha' +exclude_patterns = ['_build'] + +html_title = "RingPHP" +html_short_title = "RingPHP" +html_theme = "sphinx_rtd_theme" +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/futures.rst b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/futures.rst new file mode 100644 index 0000000..af29cb3 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/futures.rst @@ -0,0 +1,164 @@ +======= +Futures +======= + +Futures represent a computation that may have not yet completed. RingPHP +uses hybrid of futures and promises to provide a consistent API that can be +used for both blocking and non-blocking consumers. + +Promises +-------- + +You can get the result of a future when it is ready using the promise interface +of a future. Futures expose a promise API via a ``then()`` method that utilizes +`React's promise library `_. You should +use this API when you do not wish to block. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\CurlMultiHandler; + + $request = [ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['host' => ['httpbin.org']] + ]; + + $response = $handler($request); + + // Use the then() method to use the promise API of the future. + $response->then(function ($response) { + echo $response['status']; + }); + +You can get the promise used by a future, an instance of +``React\Promise\PromiseInterface``, by calling the ``promise()`` method. + +.. code-block:: php + + $response = $handler($request); + $promise = $response->promise(); + $promise->then(function ($response) { + echo $response['status']; + }); + +This promise value can be used with React's +`aggregate promise functions `_. + +Waiting +------- + +You can wait on a future to complete and retrieve the value, or *dereference* +the future, using the ``wait()`` method. Calling the ``wait()`` method of a +future will block until the result is available. The result is then returned or +an exception is thrown if and exception was encountered while waiting on the +the result. Subsequent calls to dereference a future will return the previously +completed result or throw the previously encountered exception. Futures can be +cancelled, which stops the computation if possible. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\CurlMultiHandler; + + $response = $handler([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['host' => ['httpbin.org']] + ]); + + // You can explicitly call block to wait on a result. + $realizedResponse = $response->wait(); + + // Future responses can be used like a regular PHP array. + echo $response['status']; + +In addition to explicitly calling the ``wait()`` function, using a future like +a normal value will implicitly trigger the ``wait()`` function. + +Future Responses +---------------- + +RingPHP uses futures to return asynchronous responses immediately. Client +handlers always return future responses that implement +``GuzzleHttp\Ring\Future\ArrayFutureInterface``. These future responses act +just like normal PHP associative arrays for blocking access and provide a +promise interface for non-blocking access. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\CurlMultiHandler; + + $handler = new CurlMultiHandler(); + + $request = [ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['Host' => ['www.google.com']] + ]; + + $response = $handler($request); + + // Use the promise API for non-blocking access to the response. The actual + // response value will be delivered to the promise. + $response->then(function ($response) { + echo $response['status']; + }); + + // You can wait (block) until the future is completed. + $response->wait(); + + // This will implicitly call wait(), and will block too! + $response['status']; + +.. important:: + + Futures that are not completed by the time the underlying handler is + destructed will be completed when the handler is shutting down. + +Cancelling +---------- + +Futures can be cancelled if they have not already been dereferenced. + +RingPHP futures are typically implemented with the +``GuzzleHttp\Ring\Future\BaseFutureTrait``. This trait provides the cancellation +functionality that should be common to most implementations. Cancelling a +future response will try to prevent the request from sending over the wire. + +When a future is cancelled, the cancellation function is invoked and performs +the actual work needed to cancel the request from sending if possible +(e.g., telling an event loop to stop sending a request or to close a socket). +If no cancellation function is provided, then a request cannot be cancelled. If +a cancel function is provided, then it should accept the future as an argument +and return true if the future was successfully cancelled or false if it could +not be cancelled. + +Wrapping an existing Promise +---------------------------- + +You can easily create a future from any existing promise using the +``GuzzleHttp\Ring\Future\FutureValue`` class. This class's constructor +accepts a promise as the first argument, a wait function as the second +argument, and a cancellation function as the third argument. The dereference +function is used to force the promise to resolve (for example, manually ticking +an event loop). The cancel function is optional and is used to tell the thing +that created the promise that it can stop computing the result (for example, +telling an event loop to stop transferring a request). + +.. code-block:: php + + use GuzzleHttp\Ring\Future\FutureValue; + use React\Promise\Deferred; + + $deferred = new Deferred(); + $promise = $deferred->promise(); + + $f = new FutureValue( + $promise, + function () use ($deferred) { + // This function is responsible for blocking and resolving the + // promise. Here we pass in a reference to the deferred so that + // it can be resolved or rejected. + $deferred->resolve('foo'); + } + ); diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/index.rst b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/index.rst new file mode 100644 index 0000000..4bbce63 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/index.rst @@ -0,0 +1,50 @@ +======= +RingPHP +======= + +Provides a simple API and specification that abstracts away the details of HTTP +into a single PHP function. RingPHP be used to power HTTP clients and servers +through a PHP function that accepts a request hash and returns a response hash +that is fulfilled using a `promise `_, +allowing RingPHP to support both synchronous and asynchronous workflows. + +By abstracting the implementation details of different HTTP clients and +servers, RingPHP allows you to utilize pluggable HTTP clients and servers +without tying your application to a specific implementation. + +.. toctree:: + :maxdepth: 2 + + spec + futures + client_middleware + client_handlers + testing + +.. code-block:: php + + 'GET', + 'uri' => '/', + 'headers' => [ + 'host' => ['www.google.com'], + 'x-foo' => ['baz'] + ] + ]); + + $response->then(function (array $response) { + echo $response['status']; + }); + + $response->wait(); + +RingPHP is inspired by Clojure's `Ring `_, +which, in turn, was inspired by Python's WSGI and Ruby's Rack. RingPHP is +utilized as the handler layer in `Guzzle `_ 5.0+ to send +HTTP requests. diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/requirements.txt b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/requirements.txt new file mode 100644 index 0000000..483a4e9 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/requirements.txt @@ -0,0 +1 @@ +sphinx_rtd_theme diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/spec.rst b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/spec.rst new file mode 100644 index 0000000..bc91078 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/spec.rst @@ -0,0 +1,311 @@ +============= +Specification +============= + +RingPHP applications consist of handlers, requests, responses, and +middleware. + +Handlers +-------- + +Handlers are implemented as a PHP ``callable`` that accept a request array +and return a response array (``GuzzleHttp\Ring\Future\FutureArrayInterface``). + +For example: + +.. code-block:: php + + use GuzzleHttp\Ring\Future\CompletedFutureArray; + + $mockHandler = function (array $request) { + return new CompletedFutureArray([ + 'status' => 200, + 'headers' => ['X-Foo' => ['Bar']], + 'body' => 'Hello!' + ]); + }; + +This handler returns the same response each time it is invoked. All RingPHP +handlers must return a ``GuzzleHttp\Ring\Future\FutureArrayInterface``. Use +``GuzzleHttp\Ring\Future\CompletedFutureArray`` when returning a response that +has already completed. + +Requests +-------- + +A request array is a PHP associative array that contains the configuration +settings need to send a request. + +.. code-block:: php + + $request = [ + 'http_method' => 'GET', + 'scheme' => 'http', + 'uri' => '/', + 'body' => 'hello!', + 'client' => ['timeout' => 1.0], + 'headers' => [ + 'host' => ['httpbin.org'], + 'X-Foo' => ['baz', 'bar'] + ] + ]; + +The request array contains the following key value pairs: + +request_method + (string, required) The HTTP request method, must be all caps corresponding + to a HTTP request method, such as ``GET`` or ``POST``. + +scheme + (string) The transport protocol, must be one of ``http`` or ``https``. + Defaults to ``http``. + +uri + (string, required) The request URI excluding the query string. Must + start with "/". + +query_string + (string) The query string, if present (e.g., ``foo=bar``). + +version + (string) HTTP protocol version. Defaults to ``1.1``. + +headers + (required, array) Associative array of headers. Each key represents the + header name. Each value contains an array of strings where each entry of + the array SHOULD be sent over the wire on a separate header line. + +body + (string, fopen resource, ``Iterator``, ``GuzzleHttp\Stream\StreamInterface``) + The body of the request, if present. Can be a string, resource returned + from fopen, an ``Iterator`` that yields chunks of data, an object that + implemented ``__toString``, or a ``GuzzleHttp\Stream\StreamInterface``. + +future + (bool, string) Controls the asynchronous behavior of a response. + + Set to ``true`` or omit the ``future`` option to *request* that a request + will be completed asynchronously. Keep in mind that your request might not + necessarily be completed asynchronously based on the handler you are using. + Set the ``future`` option to ``false`` to request that a synchronous + response be provided. + + You can provide a string value to specify fine-tuned future behaviors that + may be specific to the underlying handlers you are using. There are, + however, some common future options that handlers should implement if + possible. + + lazy + Requests that the handler does not open and send the request + immediately, but rather only opens and sends the request once the + future is dereferenced. This option is often useful for sending a large + number of requests concurrently to allow handlers to take better + advantage of non-blocking transfers by first building up a pool of + requests. + + If an handler does not implement or understand a provided string value, + then the request MUST be treated as if the user provided ``true`` rather + than the string value. + + Future responses created by asynchronous handlers MUST attempt to complete + any outstanding future responses when they are destructed. Asynchronous + handlers MAY choose to automatically complete responses when the number + of outstanding requests reaches an handler-specific threshold. + +Client Specific Options +~~~~~~~~~~~~~~~~~~~~~~~ + +The following options are only used in ring client handlers. + +.. _client-options: + +client + (array) Associative array of client specific transfer options. The + ``client`` request key value pair can contain the following keys: + + cert + (string, array) Set to a string to specify the path to a file + containing a PEM formatted SSL client side certificate. If a password + is required, then set ``cert`` to an array containing the path to the + PEM file in the first array element followed by the certificate + password in the second array element. + + connect_timeout + (float) Float describing the number of seconds to wait while trying to + connect to a server. Use ``0`` to wait indefinitely (the default + behavior). + + debug + (bool, fopen() resource) Set to true or set to a PHP stream returned by + fopen() to enable debug output with the handler used to send a request. + If set to ``true``, the output is written to PHP's STDOUT. If a PHP + ``fopen`` resource handle is provided, the output is written to the + stream. + + "Debug output" is handler specific: different handlers will yield + different output and various various level of detail. For example, when + using cURL to transfer requests, cURL's `CURLOPT_VERBOSE `_ + will be used. When using the PHP stream wrapper, `stream notifications `_ + will be emitted. + + decode_content + (bool) Specify whether or not ``Content-Encoding`` responses + (gzip, deflate, etc.) are automatically decoded. Set to ``true`` to + automatically decode encoded responses. Set to ``false`` to not decode + responses. By default, content is *not* decoded automatically. + + delay + (int) The number of milliseconds to delay before sending the request. + This is often used for delaying before retrying a request. Handlers + SHOULD implement this if possible, but it is not a strict requirement. + + progress + (function) Defines a function to invoke when transfer progress is made. + The function accepts the following arguments: + + 1. The total number of bytes expected to be downloaded + 2. The number of bytes downloaded so far + 3. The number of bytes expected to be uploaded + 4. The number of bytes uploaded so far + + proxy + (string, array) Pass a string to specify an HTTP proxy, or an + associative array to specify different proxies for different protocols + where the scheme is the key and the value is the proxy address. + + .. code-block:: php + + $request = [ + 'http_method' => 'GET', + 'headers' => ['host' => ['httpbin.org']], + 'client' => [ + // Use different proxies for different URI schemes. + 'proxy' => [ + 'http' => 'http://proxy.example.com:5100', + 'https' => 'https://proxy.example.com:6100' + ] + ] + ]; + + ssl_key + (string, array) Specify the path to a file containing a private SSL key + in PEM format. If a password is required, then set to an array + containing the path to the SSL key in the first array element followed + by the password required for the certificate in the second element. + + save_to + (string, fopen resource, ``GuzzleHttp\Stream\StreamInterface``) + Specifies where the body of the response is downloaded. Pass a string to + open a local file on disk and save the output to the file. Pass an fopen + resource to save the output to a PHP stream resource. Pass a + ``GuzzleHttp\Stream\StreamInterface`` to save the output to a Guzzle + StreamInterface. Omitting this option will typically save the body of a + response to a PHP temp stream. + + stream + (bool) Set to true to stream a response rather than download it all + up-front. This option will only be utilized when the corresponding + handler supports it. + + timeout + (float) Float describing the timeout of the request in seconds. Use 0 to + wait indefinitely (the default behavior). + + verify + (bool, string) Describes the SSL certificate verification behavior of a + request. Set to true to enable SSL certificate verification using the + system CA bundle when available (the default). Set to false to disable + certificate verification (this is insecure!). Set to a string to provide + the path to a CA bundle on disk to enable verification using a custom + certificate. + + version + (string) HTTP protocol version to use with the request. + +Server Specific Options +~~~~~~~~~~~~~~~~~~~~~~~ + +The following options are only used in ring server handlers. + +server_port + (integer) The port on which the request is being handled. This is only + used with ring servers, and is required. + +server_name + (string) The resolved server name, or the server IP address. Required when + using a Ring server. + +remote_addr + (string) The IP address of the client or the last proxy that sent the + request. Required when using a Ring server. + +Responses +--------- + +A response is an array-like object that implements +``GuzzleHttp\Ring\Future\FutureArrayInterface``. Responses contain the +following key value pairs: + +body + (string, fopen resource, ``Iterator``, ``GuzzleHttp\Stream\StreamInterface``) + The body of the response, if present. Can be a string, resource returned + from fopen, an ``Iterator`` that yields chunks of data, an object that + implemented ``__toString``, or a ``GuzzleHttp\Stream\StreamInterface``. + +effective_url + (string) The URL that returned the resulting response. + +error + (``\Exception``) Contains an exception describing any errors that were + encountered during the transfer. + +headers + (Required, array) Associative array of headers. Each key represents the + header name. Each value contains an array of strings where each entry of + the array is a header line. The headers array MAY be an empty array in the + event an error occurred before a response was received. + +reason + (string) Optional reason phrase. This option should be provided when the + reason phrase does not match the typical reason phrase associated with the + ``status`` code. See `RFC 7231 `_ + for a list of HTTP reason phrases mapped to status codes. + +status + (Required, integer) The HTTP status code. The status code MAY be set to + ``null`` in the event an error occurred before a response was received + (e.g., a networking error). + +transfer_stats + (array) Provides an associative array of arbitrary transfer statistics if + provided by the underlying handler. + +version + (string) HTTP protocol version. Defaults to ``1.1``. + +Middleware +---------- + +Ring middleware augments the functionality of handlers by invoking them in the +process of generating responses. Middleware is typically implemented as a +higher-order function that takes one or more handlers as arguments followed by +an optional associative array of options as the last argument, returning a new +handler with the desired compound behavior. + +Here's an example of a middleware that adds a Content-Type header to each +request. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\CurlHandler; + use GuzzleHttp\Ring\Core; + + $contentTypeHandler = function(callable $handler, $contentType) { + return function (array $request) use ($handler, $contentType) { + return $handler(Core::setHeader('Content-Type', $contentType)); + }; + }; + + $baseHandler = new CurlHandler(); + $wrappedHandler = $contentTypeHandler($baseHandler, 'text/html'); + $response = $wrappedHandler([/** request hash **/]); diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/testing.rst b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/testing.rst new file mode 100644 index 0000000..9df2562 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/docs/testing.rst @@ -0,0 +1,74 @@ +======= +Testing +======= + +RingPHP tests client handlers using `PHPUnit `_ and a +built-in node.js web server. + +Running Tests +------------- + +First, install the dependencies using `Composer `_. + + composer.phar install + +Next, run the unit tests using ``Make``. + + make test + +The tests are also run on Travis-CI on each commit: https://travis-ci.org/guzzle/guzzle-ring + +Test Server +----------- + +Testing client handlers usually involves actually sending HTTP requests. +RingPHP provides a node.js web server that returns canned responses and +keep a list of the requests that have been received. The server can then +be queried to get a list of the requests that were sent by the client so that +you can ensure that the client serialized and transferred requests as intended. + +The server keeps a list of queued responses and returns responses that are +popped off of the queue as HTTP requests are received. When there are not +more responses to serve, the server returns a 500 error response. + +The test server uses the ``GuzzleHttp\Tests\Ring\Client\Server`` class to +control the server. + +.. code-block:: php + + use GuzzleHttp\Ring\Client\StreamHandler; + use GuzzleHttp\Tests\Ring\Client\Server; + + // First return a 200 followed by a 404 response. + Server::enqueue([ + ['status' => 200], + ['status' => 404] + ]); + + $handler = new StreamHandler(); + + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'uri' => '/' + ]); + + assert(200 == $response['status']); + + $response = $handler([ + 'http_method' => 'HEAD', + 'headers' => ['host' => [Server::$host]], + 'uri' => '/' + ]); + + assert(404 == $response['status']); + +After requests have been sent, you can get a list of the requests as they +were sent over the wire to ensure they were sent correctly. + +.. code-block:: php + + $received = Server::received(); + + assert('GET' == $received[0]['http_method']); + assert('HEAD' == $received[1]['http_method']); diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/phpunit.xml.dist b/admin/classes/domain/vendor/guzzlehttp/ringphp/phpunit.xml.dist new file mode 100644 index 0000000..1d19290 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + tests + + + + + src + + + diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php new file mode 100644 index 0000000..2acf92e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php @@ -0,0 +1,74 @@ +getDefaultOptions($request, $headers); + $this->applyMethod($request, $options); + + if (isset($request['client'])) { + $this->applyHandlerOptions($request, $options); + } + + $this->applyHeaders($request, $options); + unset($options['_headers']); + + // Add handler options from the request's configuration options + if (isset($request['client']['curl'])) { + $options = $this->applyCustomCurlOptions( + $request['client']['curl'], + $options + ); + } + + if (!$handle) { + $handle = curl_init(); + } + + $body = $this->getOutputBody($request, $options); + curl_setopt_array($handle, $options); + + return [$handle, &$headers, $body]; + } + + /** + * Creates a response hash from a cURL result. + * + * @param callable $handler Handler that was used. + * @param array $request Request that sent. + * @param array $response Response hash to update. + * @param array $headers Headers received during transfer. + * @param resource $body Body fopen response. + * + * @return array + */ + public static function createResponse( + callable $handler, + array $request, + array $response, + array $headers, + $body + ) { + if (isset($response['transfer_stats']['url'])) { + $response['effective_url'] = $response['transfer_stats']['url']; + } + + if (!empty($headers)) { + $startLine = explode(' ', array_shift($headers), 3); + $headerList = Core::headersFromLines($headers); + $response['headers'] = $headerList; + $response['version'] = isset($startLine[0]) ? substr($startLine[0], 5) : null; + $response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null; + $response['reason'] = isset($startLine[2]) ? $startLine[2] : null; + $response['body'] = $body; + Core::rewindBody($response); + } + + return !empty($response['curl']['errno']) || !isset($response['status']) + ? self::createErrorResponse($handler, $request, $response) + : $response; + } + + private static function createErrorResponse( + callable $handler, + array $request, + array $response + ) { + static $connectionErrors = [ + CURLE_OPERATION_TIMEOUTED => true, + CURLE_COULDNT_RESOLVE_HOST => true, + CURLE_COULDNT_CONNECT => true, + CURLE_SSL_CONNECT_ERROR => true, + CURLE_GOT_NOTHING => true, + ]; + + // Retry when nothing is present or when curl failed to rewind. + if (!isset($response['err_message']) + && (empty($response['curl']['errno']) + || $response['curl']['errno'] == 65) + ) { + return self::retryFailedRewind($handler, $request, $response); + } + + $message = isset($response['err_message']) + ? $response['err_message'] + : sprintf('cURL error %s: %s', + $response['curl']['errno'], + isset($response['curl']['error']) + ? $response['curl']['error'] + : 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html'); + + $error = isset($response['curl']['errno']) + && isset($connectionErrors[$response['curl']['errno']]) + ? new ConnectException($message) + : new RingException($message); + + return $response + [ + 'status' => null, + 'reason' => null, + 'body' => null, + 'headers' => [], + 'error' => $error, + ]; + } + + private function getOutputBody(array $request, array &$options) + { + // Determine where the body of the response (if any) will be streamed. + if (isset($options[CURLOPT_WRITEFUNCTION])) { + return $request['client']['save_to']; + } + + if (isset($options[CURLOPT_FILE])) { + return $options[CURLOPT_FILE]; + } + + if ($request['http_method'] != 'HEAD') { + // Create a default body if one was not provided + return $options[CURLOPT_FILE] = fopen('php://temp', 'w+'); + } + + return null; + } + + private function getDefaultOptions(array $request, array &$headers) + { + $url = Core::url($request); + $startingResponse = false; + + $options = [ + '_headers' => $request['headers'], + CURLOPT_CUSTOMREQUEST => $request['http_method'], + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => false, + CURLOPT_HEADER => false, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) { + $value = trim($h); + if ($value === '') { + $startingResponse = true; + } elseif ($startingResponse) { + $startingResponse = false; + $headers = [$value]; + } else { + $headers[] = $value; + } + return strlen($h); + }, + ]; + + if (isset($request['version'])) { + if ($request['version'] == 2.0) { + $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; + } else if ($request['version'] == 1.1) { + $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; + } else { + $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; + } + } + + if (defined('CURLOPT_PROTOCOLS')) { + $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + return $options; + } + + private function applyMethod(array $request, array &$options) + { + if (isset($request['body'])) { + $this->applyBody($request, $options); + return; + } + + switch ($request['http_method']) { + case 'PUT': + case 'POST': + // See http://tools.ietf.org/html/rfc7230#section-3.3.2 + if (!Core::hasHeader($request, 'Content-Length')) { + $options[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + break; + case 'HEAD': + $options[CURLOPT_NOBODY] = true; + unset( + $options[CURLOPT_WRITEFUNCTION], + $options[CURLOPT_READFUNCTION], + $options[CURLOPT_FILE], + $options[CURLOPT_INFILE] + ); + } + } + + private function applyBody(array $request, array &$options) + { + $contentLength = Core::firstHeader($request, 'Content-Length'); + $size = $contentLength !== null ? (int) $contentLength : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [client][curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || + isset($request['client']['curl']['body_as_string']) || + is_string($request['body']) + ) { + $options[CURLOPT_POSTFIELDS] = Core::body($request); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $options); + $this->removeHeader('Transfer-Encoding', $options); + } else { + $options[CURLOPT_UPLOAD] = true; + if ($size !== null) { + // Let cURL handle setting the Content-Length header + $options[CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $options); + } + $this->addStreamingBody($request, $options); + } + + // If the Expect header is not present, prevent curl from adding it + if (!Core::hasHeader($request, 'Expect')) { + $options[CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!Core::hasHeader($request, 'Content-Type')) { + $options[CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function addStreamingBody(array $request, array &$options) + { + $body = $request['body']; + + if ($body instanceof StreamInterface) { + $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + return (string) $body->read($length); + }; + if (!isset($options[CURLOPT_INFILESIZE])) { + if ($size = $body->getSize()) { + $options[CURLOPT_INFILESIZE] = $size; + } + } + } elseif (is_resource($body)) { + $options[CURLOPT_INFILE] = $body; + } elseif ($body instanceof \Iterator) { + $buf = ''; + $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body, &$buf) { + if ($body->valid()) { + $buf .= $body->current(); + $body->next(); + } + $result = (string) substr($buf, 0, $length); + $buf = substr($buf, $length); + return $result; + }; + } else { + throw new \InvalidArgumentException('Invalid request body provided'); + } + } + + private function applyHeaders(array $request, array &$options) + { + foreach ($options['_headers'] as $name => $values) { + foreach ($values as $value) { + $options[CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + + // Remove the Accept header if one was not set + if (!Core::hasHeader($request, 'Accept')) { + $options[CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Takes an array of curl options specified in the 'curl' option of a + * request's configuration array and maps them to CURLOPT_* options. + * + * This method is only called when a request has a 'curl' config setting. + * + * @param array $config Configuration array of custom curl option + * @param array $options Array of existing curl options + * + * @return array Returns a new array of curl options + */ + private function applyCustomCurlOptions(array $config, array $options) + { + $curlOptions = []; + foreach ($config as $key => $value) { + if (is_int($key)) { + $curlOptions[$key] = $value; + } + } + + return $curlOptions + $options; + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader($name, array &$options) + { + foreach (array_keys($options['_headers']) as $key) { + if (!strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + /** + * Applies an array of request client options to a the options array. + * + * This method uses a large switch rather than double-dispatch to save on + * high overhead of calling functions in PHP. + */ + private function applyHandlerOptions(array $request, array &$options) + { + foreach ($request['client'] as $key => $value) { + switch ($key) { + // Violating PSR-4 to provide more room. + case 'verify': + + if ($value === false) { + unset($options[CURLOPT_CAINFO]); + $options[CURLOPT_SSL_VERIFYHOST] = 0; + $options[CURLOPT_SSL_VERIFYPEER] = false; + continue; + } + + $options[CURLOPT_SSL_VERIFYHOST] = 2; + $options[CURLOPT_SSL_VERIFYPEER] = true; + + if (is_string($value)) { + $options[CURLOPT_CAINFO] = $value; + if (!file_exists($value)) { + throw new \InvalidArgumentException( + "SSL CA bundle not found: $value" + ); + } + } + break; + + case 'decode_content': + + if ($value === false) { + continue; + } + + $accept = Core::firstHeader($request, 'Accept-Encoding'); + if ($accept) { + $options[CURLOPT_ENCODING] = $accept; + } else { + $options[CURLOPT_ENCODING] = ''; + // Don't let curl send the header over the wire + $options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + break; + + case 'save_to': + + if (is_string($value)) { + if (!is_dir(dirname($value))) { + throw new \RuntimeException(sprintf( + 'Directory %s does not exist for save_to value of %s', + dirname($value), + $value + )); + } + $value = new LazyOpenStream($value, 'w+'); + } + + if ($value instanceof StreamInterface) { + $options[CURLOPT_WRITEFUNCTION] = + function ($ch, $write) use ($value) { + return $value->write($write); + }; + } elseif (is_resource($value)) { + $options[CURLOPT_FILE] = $value; + } else { + throw new \InvalidArgumentException('save_to must be a ' + . 'GuzzleHttp\Stream\StreamInterface or resource'); + } + break; + + case 'timeout': + + if (defined('CURLOPT_TIMEOUT_MS')) { + $options[CURLOPT_TIMEOUT_MS] = $value * 1000; + } else { + $options[CURLOPT_TIMEOUT] = $value; + } + break; + + case 'connect_timeout': + + if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { + $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000; + } else { + $options[CURLOPT_CONNECTTIMEOUT] = $value; + } + break; + + case 'proxy': + + if (!is_array($value)) { + $options[CURLOPT_PROXY] = $value; + } elseif (isset($request['scheme'])) { + $scheme = $request['scheme']; + if (isset($value[$scheme])) { + $options[CURLOPT_PROXY] = $value[$scheme]; + } + } + break; + + case 'cert': + + if (is_array($value)) { + $options[CURLOPT_SSLCERTPASSWD] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new \InvalidArgumentException( + "SSL certificate not found: {$value}" + ); + } + + $options[CURLOPT_SSLCERT] = $value; + break; + + case 'ssl_key': + + if (is_array($value)) { + $options[CURLOPT_SSLKEYPASSWD] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new \InvalidArgumentException( + "SSL private key not found: {$value}" + ); + } + + $options[CURLOPT_SSLKEY] = $value; + break; + + case 'progress': + + if (!is_callable($value)) { + throw new \InvalidArgumentException( + 'progress client option must be callable' + ); + } + + $options[CURLOPT_NOPROGRESS] = false; + $options[CURLOPT_PROGRESSFUNCTION] = + function () use ($value) { + $args = func_get_args(); + // PHP 5.5 pushed the handle onto the start of the args + if (is_resource($args[0])) { + array_shift($args); + } + call_user_func_array($value, $args); + }; + break; + + case 'debug': + + if ($value) { + $options[CURLOPT_STDERR] = Core::getDebugResource($value); + $options[CURLOPT_VERBOSE] = true; + } + break; + } + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + */ + private static function retryFailedRewind( + callable $handler, + array $request, + array $response + ) { + // If there is no body, then there is some other kind of issue. This + // is weird and should probably never happen. + if (!isset($request['body'])) { + $response['err_message'] = 'No response was received for a request ' + . 'with no body. This could mean that you are saturating your ' + . 'network.'; + return self::createErrorResponse($handler, $request, $response); + } + + if (!Core::rewindBody($request)) { + $response['err_message'] = 'The connection unexpectedly failed ' + . 'without providing an error. The request would have been ' + . 'retried, but attempting to rewind the request body failed.'; + return self::createErrorResponse($handler, $request, $response); + } + + // Retry no more than 3 times before giving up. + if (!isset($request['curl']['retries'])) { + $request['curl']['retries'] = 1; + } elseif ($request['curl']['retries'] == 2) { + $response['err_message'] = 'The cURL request was retried 3 times ' + . 'and did no succeed. cURL was unable to rewind the body of ' + . 'the request and subsequent retries resulted in the same ' + . 'error. Turn on the debug option to see what went wrong. ' + . 'See https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createErrorResponse($handler, $request, $response); + } else { + $request['curl']['retries']++; + } + + return $handler($request); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php new file mode 100644 index 0000000..e00aa4e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php @@ -0,0 +1,135 @@ +handles = $this->ownedHandles = []; + $this->factory = isset($options['handle_factory']) + ? $options['handle_factory'] + : new CurlFactory(); + $this->maxHandles = isset($options['max_handles']) + ? $options['max_handles'] + : 5; + } + + public function __destruct() + { + foreach ($this->handles as $handle) { + if (is_resource($handle)) { + curl_close($handle); + } + } + } + + /** + * @param array $request + * + * @return CompletedFutureArray + */ + public function __invoke(array $request) + { + return new CompletedFutureArray( + $this->_invokeAsArray($request) + ); + } + + /** + * @internal + * + * @param array $request + * + * @return array + */ + public function _invokeAsArray(array $request) + { + $factory = $this->factory; + + // Ensure headers are by reference. They're updated elsewhere. + $result = $factory($request, $this->checkoutEasyHandle()); + $h = $result[0]; + $hd =& $result[1]; + $bd = $result[2]; + Core::doSleep($request); + curl_exec($h); + $response = ['transfer_stats' => curl_getinfo($h)]; + $response['curl']['error'] = curl_error($h); + $response['curl']['errno'] = curl_errno($h); + $response['transfer_stats'] = array_merge($response['transfer_stats'], $response['curl']); + $this->releaseEasyHandle($h); + + return CurlFactory::createResponse([$this, '_invokeAsArray'], $request, $response, $hd, $bd); + } + + private function checkoutEasyHandle() + { + // Find an unused handle in the cache + if (false !== ($key = array_search(false, $this->ownedHandles, true))) { + $this->ownedHandles[$key] = true; + return $this->handles[$key]; + } + + // Add a new handle + $handle = curl_init(); + $id = (int) $handle; + $this->handles[$id] = $handle; + $this->ownedHandles[$id] = true; + + return $handle; + } + + private function releaseEasyHandle($handle) + { + $id = (int) $handle; + if (count($this->ownedHandles) > $this->maxHandles) { + curl_close($this->handles[$id]); + unset($this->handles[$id], $this->ownedHandles[$id]); + } else { + // curl_reset doesn't clear these out for some reason + static $unsetValues = [ + CURLOPT_HEADERFUNCTION => null, + CURLOPT_WRITEFUNCTION => null, + CURLOPT_READFUNCTION => null, + CURLOPT_PROGRESSFUNCTION => null, + ]; + curl_setopt_array($handle, $unsetValues); + curl_reset($handle); + $this->ownedHandles[$id] = false; + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php new file mode 100644 index 0000000..b45f6c3 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php @@ -0,0 +1,250 @@ +_mh = $options['mh']; + } + $this->factory = isset($options['handle_factory']) + ? $options['handle_factory'] : new CurlFactory(); + $this->selectTimeout = isset($options['select_timeout']) + ? $options['select_timeout'] : 1; + $this->maxHandles = isset($options['max_handles']) + ? $options['max_handles'] : 100; + } + + public function __get($name) + { + if ($name === '_mh') { + return $this->_mh = curl_multi_init(); + } + + throw new \BadMethodCallException(); + } + + public function __destruct() + { + // Finish any open connections before terminating the script. + if ($this->handles) { + $this->execute(); + } + + if (isset($this->_mh)) { + curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(array $request) + { + $factory = $this->factory; + $result = $factory($request); + $entry = [ + 'request' => $request, + 'response' => [], + 'handle' => $result[0], + 'headers' => &$result[1], + 'body' => $result[2], + 'deferred' => new Deferred(), + ]; + + $id = (int) $result[0]; + + $future = new FutureArray( + $entry['deferred']->promise(), + [$this, 'execute'], + function () use ($id) { + return $this->cancel($id); + } + ); + + $this->addRequest($entry); + + // Transfer outstanding requests if there are too many open handles. + if (count($this->handles) >= $this->maxHandles) { + $this->execute(); + } + + return $future; + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute() + { + do { + + if ($this->active && + curl_multi_select($this->_mh, $this->selectTimeout) === -1 + ) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + usleep(250); + } + + // Add any delayed futures if needed. + if ($this->delays) { + $this->addDelays(); + } + + do { + $mrc = curl_multi_exec($this->_mh, $this->active); + } while ($mrc === CURLM_CALL_MULTI_PERFORM); + + $this->processMessages(); + + // If there are delays but no transfers, then sleep for a bit. + if (!$this->active && $this->delays) { + usleep(500); + } + + } while ($this->active || $this->handles); + } + + private function addRequest(array &$entry) + { + $id = (int) $entry['handle']; + $this->handles[$id] = $entry; + + // If the request is a delay, then add the reques to the curl multi + // pool only after the specified delay. + if (isset($entry['request']['client']['delay'])) { + $this->delays[$id] = microtime(true) + ($entry['request']['client']['delay'] / 1000); + } elseif (empty($entry['request']['future'])) { + curl_multi_add_handle($this->_mh, $entry['handle']); + } else { + curl_multi_add_handle($this->_mh, $entry['handle']); + // "lazy" futures are only sent once the pool has many requests. + if ($entry['request']['future'] !== 'lazy') { + do { + $mrc = curl_multi_exec($this->_mh, $this->active); + } while ($mrc === CURLM_CALL_MULTI_PERFORM); + $this->processMessages(); + } + } + } + + private function removeProcessed($id) + { + if (isset($this->handles[$id])) { + curl_multi_remove_handle( + $this->_mh, + $this->handles[$id]['handle'] + ); + curl_close($this->handles[$id]['handle']); + unset($this->handles[$id], $this->delays[$id]); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id) + { + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['handle']; + unset($this->delays[$id], $this->handles[$id]); + curl_multi_remove_handle($this->_mh, $handle); + curl_close($handle); + + return true; + } + + private function addDelays() + { + $currentTime = microtime(true); + + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['handle'] + ); + } + } + } + + private function processMessages() + { + while ($done = curl_multi_info_read($this->_mh)) { + $id = (int) $done['handle']; + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + $entry['response']['transfer_stats'] = curl_getinfo($done['handle']); + + if ($done['result'] !== CURLM_OK) { + $entry['response']['curl']['errno'] = $done['result']; + if (function_exists('curl_strerror')) { + $entry['response']['curl']['error'] = curl_strerror($done['result']); + } + } + + $result = CurlFactory::createResponse( + $this, + $entry['request'], + $entry['response'], + $entry['headers'], + $entry['body'] + ); + + $this->removeProcessed($id); + $entry['deferred']->resolve($result); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/Middleware.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/Middleware.php new file mode 100644 index 0000000..6fa7318 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/Middleware.php @@ -0,0 +1,58 @@ +result = $result; + } + + public function __invoke(array $request) + { + Core::doSleep($request); + $response = is_callable($this->result) + ? call_user_func($this->result, $request) + : $this->result; + + if (is_array($response)) { + $response = new CompletedFutureArray($response + [ + 'status' => null, + 'body' => null, + 'headers' => [], + 'reason' => null, + 'effective_url' => null, + ]); + } elseif (!$response instanceof FutureArrayInterface) { + throw new \InvalidArgumentException( + 'Response must be an array or FutureArrayInterface. Found ' + . Core::describeType($request) + ); + } + + return $response; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php new file mode 100644 index 0000000..4bacec1 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php @@ -0,0 +1,414 @@ +options = $options; + } + + public function __invoke(array $request) + { + $url = Core::url($request); + Core::doSleep($request); + + try { + // Does not support the expect header. + $request = Core::removeHeader($request, 'Expect'); + $stream = $this->createStream($url, $request); + return $this->createResponse($request, $url, $stream); + } catch (RingException $e) { + return $this->createErrorResponse($url, $e); + } + } + + private function createResponse(array $request, $url, $stream) + { + $hdrs = $this->lastHeaders; + $this->lastHeaders = null; + $parts = explode(' ', array_shift($hdrs), 3); + $response = [ + 'version' => substr($parts[0], 5), + 'status' => $parts[1], + 'reason' => isset($parts[2]) ? $parts[2] : null, + 'headers' => Core::headersFromLines($hdrs), + 'effective_url' => $url, + ]; + + $stream = $this->checkDecode($request, $response, $stream); + + // If not streaming, then drain the response into a stream. + if (empty($request['client']['stream'])) { + $dest = isset($request['client']['save_to']) + ? $request['client']['save_to'] + : fopen('php://temp', 'r+'); + $stream = $this->drain($stream, $dest); + } + + $response['body'] = $stream; + + return new CompletedFutureArray($response); + } + + private function checkDecode(array $request, array $response, $stream) + { + // Automatically decode responses when instructed. + if (!empty($request['client']['decode_content'])) { + switch (Core::firstHeader($response, 'Content-Encoding', true)) { + case 'gzip': + case 'deflate': + $stream = new InflateStream(Stream::factory($stream)); + break; + } + } + + return $stream; + } + + /** + * Drains the stream into the "save_to" client option. + * + * @param resource $stream + * @param string|resource|StreamInterface $dest + * + * @return Stream + * @throws \RuntimeException when the save_to option is invalid. + */ + private function drain($stream, $dest) + { + if (is_resource($stream)) { + if (!is_resource($dest)) { + $stream = Stream::factory($stream); + } else { + stream_copy_to_stream($stream, $dest); + fclose($stream); + rewind($dest); + return $dest; + } + } + + // Stream the response into the destination stream + $dest = is_string($dest) + ? new Stream(Utils::open($dest, 'r+')) + : Stream::factory($dest); + + Utils::copyToStream($stream, $dest); + $dest->seek(0); + $stream->close(); + + return $dest; + } + + /** + * Creates an error response for the given stream. + * + * @param string $url + * @param RingException $e + * + * @return array + */ + private function createErrorResponse($url, RingException $e) + { + // Determine if the error was a networking error. + $message = $e->getMessage(); + + // This list can probably get more comprehensive. + if (strpos($message, 'getaddrinfo') // DNS lookup failed + || strpos($message, 'Connection refused') + ) { + $e = new ConnectException($e->getMessage(), 0, $e); + } + + return new CompletedFutureArray([ + 'status' => null, + 'body' => null, + 'headers' => [], + 'effective_url' => $url, + 'error' => $e + ]); + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + $resource = $callback(); + restore_error_handler(); + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . PHP_EOL; + } + } + throw new RingException(trim($message)); + } + + return $resource; + } + + private function createStream($url, array $request) + { + static $methods; + if (!$methods) { + $methods = array_flip(get_class_methods(__CLASS__)); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ((!isset($request['version']) || $request['version'] == '1.1') + && !Core::hasHeader($request, 'Connection') + ) { + $request['headers']['Connection'] = ['close']; + } + + // Ensure SSL is verified by default + if (!isset($request['client']['verify'])) { + $request['client']['verify'] = true; + } + + $params = []; + $options = $this->getDefaultOptions($request); + + if (isset($request['client'])) { + foreach ($request['client'] as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $options, $value, $params); + } + } + } + + return $this->createStreamResource( + $url, + $request, + $options, + $this->createContext($request, $options, $params) + ); + } + + private function getDefaultOptions(array $request) + { + $headers = ""; + foreach ($request['headers'] as $name => $value) { + foreach ((array) $value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request['http_method'], + 'header' => $headers, + 'protocol_version' => isset($request['version']) ? $request['version'] : 1.1, + 'ignore_errors' => true, + 'follow_location' => 0, + ], + ]; + + $body = Core::body($request); + if (isset($body)) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!Core::hasHeader($request, 'Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = rtrim($context['http']['header']); + + return $context; + } + + private function add_proxy(array $request, &$options, $value, &$params) + { + if (!is_array($value)) { + $options['http']['proxy'] = $value; + } else { + $scheme = isset($request['scheme']) ? $request['scheme'] : 'http'; + if (isset($value[$scheme])) { + $options['http']['proxy'] = $value[$scheme]; + } + } + } + + private function add_timeout(array $request, &$options, $value, &$params) + { + $options['http']['timeout'] = $value; + } + + private function add_verify(array $request, &$options, $value, &$params) + { + if ($value === true) { + // PHP 5.6 or greater will find the system cert by default. When + // < 5.6, use the Guzzle bundled cacert. + if (PHP_VERSION_ID < 50600) { + $options['ssl']['cafile'] = ClientUtils::getDefaultCaBundle(); + } + } elseif (is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!file_exists($value)) { + throw new RingException("SSL CA bundle not found: $value"); + } + } elseif ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['allow_self_signed'] = true; + return; + } else { + throw new RingException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + private function add_cert(array $request, &$options, $value, &$params) + { + if (is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new RingException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + private function add_progress(array $request, &$options, $value, &$params) + { + $fn = function ($code, $_1, $_2, $_3, $transferred, $total) use ($value) { + if ($code == STREAM_NOTIFY_PROGRESS) { + $value($total, $transferred, null, null); + } + }; + + // Wrap the existing function if needed. + $params['notification'] = isset($params['notification']) + ? Core::callArray([$params['notification'], $fn]) + : $fn; + } + + private function add_debug(array $request, &$options, $value, &$params) + { + if ($value === false) { + return; + } + + static $map = [ + STREAM_NOTIFY_CONNECT => 'CONNECT', + STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + STREAM_NOTIFY_PROGRESS => 'PROGRESS', + STREAM_NOTIFY_FAILURE => 'FAILURE', + STREAM_NOTIFY_COMPLETED => 'COMPLETED', + STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + + static $args = ['severity', 'message', 'message_code', + 'bytes_transferred', 'bytes_max']; + + $value = Core::getDebugResource($value); + $ident = $request['http_method'] . ' ' . Core::url($request); + $fn = function () use ($ident, $value, $map, $args) { + $passed = func_get_args(); + $code = array_shift($passed); + fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (array_filter($passed) as $i => $v) { + fwrite($value, $args[$i] . ': "' . $v . '" '); + } + fwrite($value, "\n"); + }; + + // Wrap the existing function if needed. + $params['notification'] = isset($params['notification']) + ? Core::callArray([$params['notification'], $fn]) + : $fn; + } + + private function applyCustomOptions(array $request, array &$options) + { + if (!isset($request['client']['stream_context'])) { + return; + } + + if (!is_array($request['client']['stream_context'])) { + throw new RingException('stream_context must be an array'); + } + + $options = array_replace_recursive( + $options, + $request['client']['stream_context'] + ); + } + + private function createContext(array $request, array $options, array $params) + { + $this->applyCustomOptions($request, $options); + return $this->createResource( + function () use ($request, $options, $params) { + return stream_context_create($options, $params); + }, + $request, + $options + ); + } + + private function createStreamResource( + $url, + array $request, + array $options, + $context + ) { + return $this->createResource( + function () use ($url, $context) { + if (false === strpos($url, 'http')) { + trigger_error("URL is invalid: {$url}", E_USER_WARNING); + return null; + } + $resource = fopen($url, 'r', null, $context); + $this->lastHeaders = $http_response_header; + return $resource; + }, + $request, + $options + ); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Core.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Core.php new file mode 100644 index 0000000..dd7d1a0 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Core.php @@ -0,0 +1,364 @@ + $value) { + if (!strcasecmp($name, $header)) { + $result = array_merge($result, $value); + } + } + } + + return $result; + } + + /** + * Gets a header value from a message as a string or null + * + * This method searches through the "headers" key of a message for a header + * using a case-insensitive search. The lines of the header are imploded + * using commas into a single string return value. + * + * @param array $message Request or response hash. + * @param string $header Header to retrieve + * + * @return string|null Returns the header string if found, or null if not. + */ + public static function header($message, $header) + { + $match = self::headerLines($message, $header); + return $match ? implode(', ', $match) : null; + } + + /** + * Returns the first header value from a message as a string or null. If + * a header line contains multiple values separated by a comma, then this + * function will return the first value in the list. + * + * @param array $message Request or response hash. + * @param string $header Header to retrieve + * + * @return string|null Returns the value as a string if found. + */ + public static function firstHeader($message, $header) + { + if (!empty($message['headers'])) { + foreach ($message['headers'] as $name => $value) { + if (!strcasecmp($name, $header)) { + // Return the match itself if it is a single value. + $pos = strpos($value[0], ','); + return $pos ? substr($value[0], 0, $pos) : $value[0]; + } + } + } + + return null; + } + + /** + * Returns true if a message has the provided case-insensitive header. + * + * @param array $message Request or response hash. + * @param string $header Header to check + * + * @return bool + */ + public static function hasHeader($message, $header) + { + if (!empty($message['headers'])) { + foreach ($message['headers'] as $name => $value) { + if (!strcasecmp($name, $header)) { + return true; + } + } + } + + return false; + } + + /** + * Parses an array of header lines into an associative array of headers. + * + * @param array $lines Header lines array of strings in the following + * format: "Name: Value" + * @return array + */ + public static function headersFromLines($lines) + { + $headers = []; + + foreach ($lines as $line) { + $parts = explode(':', $line, 2); + $headers[trim($parts[0])][] = isset($parts[1]) + ? trim($parts[1]) + : null; + } + + return $headers; + } + + /** + * Removes a header from a message using a case-insensitive comparison. + * + * @param array $message Message that contains 'headers' + * @param string $header Header to remove + * + * @return array + */ + public static function removeHeader(array $message, $header) + { + if (isset($message['headers'])) { + foreach (array_keys($message['headers']) as $key) { + if (!strcasecmp($header, $key)) { + unset($message['headers'][$key]); + } + } + } + + return $message; + } + + /** + * Replaces any existing case insensitive headers with the given value. + * + * @param array $message Message that contains 'headers' + * @param string $header Header to set. + * @param array $value Value to set. + * + * @return array + */ + public static function setHeader(array $message, $header, array $value) + { + $message = self::removeHeader($message, $header); + $message['headers'][$header] = $value; + + return $message; + } + + /** + * Creates a URL string from a request. + * + * If the "url" key is present on the request, it is returned, otherwise + * the url is built up based on the scheme, host, uri, and query_string + * request values. + * + * @param array $request Request to get the URL from + * + * @return string Returns the request URL as a string. + * @throws \InvalidArgumentException if no Host header is present. + */ + public static function url(array $request) + { + if (isset($request['url'])) { + return $request['url']; + } + + $uri = (isset($request['scheme']) + ? $request['scheme'] : 'http') . '://'; + + if ($host = self::header($request, 'host')) { + $uri .= $host; + } else { + throw new \InvalidArgumentException('No Host header was provided'); + } + + if (isset($request['uri'])) { + $uri .= $request['uri']; + } + + if (isset($request['query_string'])) { + $uri .= '?' . $request['query_string']; + } + + return $uri; + } + + /** + * Reads the body of a message into a string. + * + * @param array|FutureArrayInterface $message Array containing a "body" key + * + * @return null|string Returns the body as a string or null if not set. + * @throws \InvalidArgumentException if a request body is invalid. + */ + public static function body($message) + { + if (!isset($message['body'])) { + return null; + } + + if ($message['body'] instanceof StreamInterface) { + return (string) $message['body']; + } + + switch (gettype($message['body'])) { + case 'string': + return $message['body']; + case 'resource': + return stream_get_contents($message['body']); + case 'object': + if ($message['body'] instanceof \Iterator) { + return implode('', iterator_to_array($message['body'])); + } elseif (method_exists($message['body'], '__toString')) { + return (string) $message['body']; + } + default: + throw new \InvalidArgumentException('Invalid request body: ' + . self::describeType($message['body'])); + } + } + + /** + * Rewind the body of the provided message if possible. + * + * @param array $message Message that contains a 'body' field. + * + * @return bool Returns true on success, false on failure + */ + public static function rewindBody($message) + { + if ($message['body'] instanceof StreamInterface) { + return $message['body']->seek(0); + } + + if ($message['body'] instanceof \Generator) { + return false; + } + + if ($message['body'] instanceof \Iterator) { + $message['body']->rewind(); + return true; + } + + if (is_resource($message['body'])) { + return rewind($message['body']); + } + + return is_string($message['body']) + || (is_object($message['body']) + && method_exists($message['body'], '__toString')); + } + + /** + * Debug function used to describe the provided value type and class. + * + * @param mixed $input + * + * @return string Returns a string containing the type of the variable and + * if a class is provided, the class name. + */ + public static function describeType($input) + { + switch (gettype($input)) { + case 'object': + return 'object(' . get_class($input) . ')'; + case 'array': + return 'array(' . count($input) . ')'; + default: + ob_start(); + var_dump($input); + // normalize float vs double + return str_replace('double(', 'float(', rtrim(ob_get_clean())); + } + } + + /** + * Sleep for the specified amount of time specified in the request's + * ['client']['delay'] option if present. + * + * This function should only be used when a non-blocking sleep is not + * possible. + * + * @param array $request Request to sleep + */ + public static function doSleep(array $request) + { + if (isset($request['client']['delay'])) { + usleep($request['client']['delay'] * 1000); + } + } + + /** + * Returns a proxied future that modifies the dereferenced value of another + * future using a promise. + * + * @param FutureArrayInterface $future Future to wrap with a new future + * @param callable $onFulfilled Invoked when the future fulfilled + * @param callable $onRejected Invoked when the future rejected + * @param callable $onProgress Invoked when the future progresses + * + * @return FutureArray + */ + public static function proxy( + FutureArrayInterface $future, + callable $onFulfilled = null, + callable $onRejected = null, + callable $onProgress = null + ) { + return new FutureArray( + $future->then($onFulfilled, $onRejected, $onProgress), + [$future, 'wait'], + [$future, 'cancel'] + ); + } + + /** + * Returns a debug stream based on the provided variable. + * + * @param mixed $value Optional value + * + * @return resource + */ + public static function getDebugResource($value = null) + { + if (is_resource($value)) { + return $value; + } elseif (defined('STDOUT')) { + return STDOUT; + } else { + return fopen('php://output', 'w'); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php new file mode 100644 index 0000000..95b353a --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php @@ -0,0 +1,7 @@ +wrappedPromise = $promise; + $this->waitfn = $wait; + $this->cancelfn = $cancel; + } + + public function wait() + { + if (!$this->isRealized) { + $this->addShadow(); + if (!$this->isRealized && $this->waitfn) { + $this->invokeWait(); + } + if (!$this->isRealized) { + $this->error = new RingException('Waiting did not resolve future'); + } + } + + if ($this->error) { + throw $this->error; + } + + return $this->result; + } + + public function promise() + { + return $this->wrappedPromise; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null, + callable $onProgress = null + ) { + return $this->wrappedPromise->then($onFulfilled, $onRejected, $onProgress); + } + + public function cancel() + { + if (!$this->isRealized) { + $cancelfn = $this->cancelfn; + $this->waitfn = $this->cancelfn = null; + $this->isRealized = true; + $this->error = new CancelledFutureAccessException(); + if ($cancelfn) { + $cancelfn($this); + } + } + } + + private function addShadow() + { + // Get the result and error when the promise is resolved. Note that + // calling this function might trigger the resolution immediately. + $this->wrappedPromise->then( + function ($value) { + $this->isRealized = true; + $this->result = $value; + $this->waitfn = $this->cancelfn = null; + }, + function ($error) { + $this->isRealized = true; + $this->error = $error; + $this->waitfn = $this->cancelfn = null; + } + ); + } + + private function invokeWait() + { + try { + $wait = $this->waitfn; + $this->waitfn = null; + $wait(); + } catch (\Exception $e) { + // Defer can throw to reject. + $this->error = $e; + $this->isRealized = true; + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php new file mode 100644 index 0000000..0a90c93 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php @@ -0,0 +1,43 @@ +result[$offset]); + } + + public function offsetGet($offset) + { + return $this->result[$offset]; + } + + public function offsetSet($offset, $value) + { + $this->result[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->result[$offset]); + } + + public function count() + { + return count($this->result); + } + + public function getIterator() + { + return new \ArrayIterator($this->result); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php new file mode 100644 index 0000000..0d25af7 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php @@ -0,0 +1,57 @@ +result = $result; + $this->error = $e; + } + + public function wait() + { + if ($this->error) { + throw $this->error; + } + + return $this->result; + } + + public function cancel() {} + + public function promise() + { + if (!$this->cachedPromise) { + $this->cachedPromise = $this->error + ? new RejectedPromise($this->error) + : new FulfilledPromise($this->result); + } + + return $this->cachedPromise; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null, + callable $onProgress = null + ) { + return $this->promise()->then($onFulfilled, $onRejected, $onProgress); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php new file mode 100644 index 0000000..3d64c96 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php @@ -0,0 +1,40 @@ +_value[$offset]); + } + + public function offsetGet($offset) + { + return $this->_value[$offset]; + } + + public function offsetSet($offset, $value) + { + $this->_value[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->_value[$offset]); + } + + public function count() + { + return count($this->_value); + } + + public function getIterator() + { + return new \ArrayIterator($this->_value); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php new file mode 100644 index 0000000..58f5f73 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php @@ -0,0 +1,11 @@ +_value = $this->wait(); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php new file mode 100644 index 0000000..ebde187 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php @@ -0,0 +1,821 @@ + 200, + 'headers' => [ + 'Foo' => ['Bar'], + 'Baz' => ['bam'], + 'Content-Length' => [2], + ], + 'body' => 'hi', + ]]); + + $stream = Stream::factory(); + + $request = [ + 'http_method' => 'PUT', + 'headers' => [ + 'host' => [Server::$url], + 'Hi' => [' 123'], + ], + 'body' => 'testing', + 'client' => ['save_to' => $stream], + ]; + + $f = new CurlFactory(); + $result = $f($request); + $this->assertInternalType('array', $result); + $this->assertCount(3, $result); + $this->assertInternalType('resource', $result[0]); + $this->assertInternalType('array', $result[1]); + $this->assertSame($stream, $result[2]); + curl_close($result[0]); + + $this->assertEquals('PUT', $_SERVER['_curl'][CURLOPT_CUSTOMREQUEST]); + $this->assertEquals( + 'http://http://127.0.0.1:8125/', + $_SERVER['_curl'][CURLOPT_URL] + ); + // Sends via post fields when the request is small enough + $this->assertEquals('testing', $_SERVER['_curl'][CURLOPT_POSTFIELDS]); + $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_RETURNTRANSFER]); + $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_HEADER]); + $this->assertEquals(150, $_SERVER['_curl'][CURLOPT_CONNECTTIMEOUT]); + $this->assertInstanceOf('Closure', $_SERVER['_curl'][CURLOPT_HEADERFUNCTION]); + + if (defined('CURLOPT_PROTOCOLS')) { + $this->assertEquals( + CURLPROTO_HTTP | CURLPROTO_HTTPS, + $_SERVER['_curl'][CURLOPT_PROTOCOLS] + ); + } + + $this->assertContains('Expect:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); + $this->assertContains('Accept:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); + $this->assertContains('Content-Type:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); + $this->assertContains('Hi: 123', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); + $this->assertContains('host: http://127.0.0.1:8125/', $_SERVER['_curl'][CURLOPT_HTTPHEADER]); + } + + public function testSendsHeadRequests() + { + Server::flush(); + Server::enqueue([['status' => 200]]); + $a = new CurlMultiHandler(); + $response = $a([ + 'http_method' => 'HEAD', + 'headers' => ['host' => [Server::$host]], + ]); + $response->wait(); + $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_NOBODY]); + $checks = [CURLOPT_WRITEFUNCTION, CURLOPT_READFUNCTION, CURLOPT_FILE, CURLOPT_INFILE]; + foreach ($checks as $check) { + $this->assertArrayNotHasKey($check, $_SERVER['_curl']); + } + $this->assertEquals('HEAD', Server::received()[0]['http_method']); + } + + public function testCanAddCustomCurlOptions() + { + Server::flush(); + Server::enqueue([['status' => 200]]); + $a = new CurlMultiHandler(); + $a([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => ['curl' => [CURLOPT_LOW_SPEED_LIMIT => 10]], + ]); + $this->assertEquals(10, $_SERVER['_curl'][CURLOPT_LOW_SPEED_LIMIT]); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage SSL CA bundle not found: /does/not/exist + */ + public function testValidatesVerify() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['verify' => '/does/not/exist'], + ]); + } + + public function testCanSetVerifyToFile() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['verify' => __FILE__], + ]); + $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_CAINFO]); + $this->assertEquals(2, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]); + $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]); + } + + public function testAddsVerifyAsTrue() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['verify' => true], + ]); + $this->assertEquals(2, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]); + $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]); + $this->assertArrayNotHasKey(CURLOPT_CAINFO, $_SERVER['_curl']); + } + + public function testCanDisableVerify() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['verify' => false], + ]); + $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]); + $this->assertEquals(false, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]); + } + + public function testAddsProxy() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['proxy' => 'http://bar.com'], + ]); + $this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]); + } + + public function testAddsViaScheme() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'scheme' => 'http', + 'headers' => ['host' => ['foo.com']], + 'client' => [ + 'proxy' => ['http' => 'http://bar.com', 'https' => 'https://t'], + ], + ]); + $this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage SSL private key not found: /does/not/exist + */ + public function testValidatesSslKey() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['ssl_key' => '/does/not/exist'], + ]); + } + + public function testAddsSslKey() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['ssl_key' => __FILE__], + ]); + $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLKEY]); + } + + public function testAddsSslKeyWithPassword() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['ssl_key' => [__FILE__, 'test']], + ]); + $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLKEY]); + $this->assertEquals('test', $_SERVER['_curl'][CURLOPT_SSLKEYPASSWD]); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage SSL certificate not found: /does/not/exist + */ + public function testValidatesCert() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['cert' => '/does/not/exist'], + ]); + } + + public function testAddsCert() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['cert' => __FILE__], + ]); + $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLCERT]); + } + + public function testAddsCertWithPassword() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['cert' => [__FILE__, 'test']], + ]); + $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLCERT]); + $this->assertEquals('test', $_SERVER['_curl'][CURLOPT_SSLCERTPASSWD]); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage progress client option must be callable + */ + public function testValidatesProgress() + { + $f = new CurlFactory(); + $f([ + 'http_method' => 'GET', + 'headers' => ['host' => ['foo.com']], + 'client' => ['progress' => 'foo'], + ]); + } + + public function testEmitsDebugInfoToStream() + { + $res = fopen('php://memory', 'r+'); + Server::flush(); + Server::enqueue([['status' => 200]]); + $a = new CurlMultiHandler(); + $response = $a([ + 'http_method' => 'HEAD', + 'headers' => ['host' => [Server::$host]], + 'client' => ['debug' => $res], + ]); + $response->wait(); + rewind($res); + $output = str_replace("\r", '', stream_get_contents($res)); + $this->assertContains( + "> HEAD / HTTP/1.1\nhost: 127.0.0.1:8125\n\n", + $output + ); + $this->assertContains("< HTTP/1.1 200", $output); + fclose($res); + } + + public function testEmitsProgressToFunction() + { + Server::flush(); + Server::enqueue([['status' => 200]]); + $a = new CurlMultiHandler(); + $called = []; + $response = $a([ + 'http_method' => 'HEAD', + 'headers' => ['host' => [Server::$host]], + 'client' => [ + 'progress' => function () use (&$called) { + $called[] = func_get_args(); + }, + ], + ]); + $response->wait(); + $this->assertNotEmpty($called); + foreach ($called as $call) { + $this->assertCount(4, $call); + } + } + + private function addDecodeResponse($withEncoding = true) + { + $content = gzencode('test'); + $response = [ + 'status' => 200, + 'reason' => 'OK', + 'headers' => ['Content-Length' => [strlen($content)]], + 'body' => $content, + ]; + + if ($withEncoding) { + $response['headers']['Content-Encoding'] = ['gzip']; + } + + Server::flush(); + Server::enqueue([$response]); + + return $content; + } + + public function testDecodesGzippedResponses() + { + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => ['decode_content' => true], + ]); + $response->wait(); + $this->assertEquals('test', Core::body($response)); + $this->assertEquals('', $_SERVER['_curl'][CURLOPT_ENCODING]); + $sent = Server::received()[0]; + $this->assertNull(Core::header($sent, 'Accept-Encoding')); + } + + public function testDecodesGzippedResponsesWithHeader() + { + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => [ + 'host' => [Server::$host], + 'Accept-Encoding' => ['gzip'], + ], + 'client' => ['decode_content' => true], + ]); + $response->wait(); + $this->assertEquals('gzip', $_SERVER['_curl'][CURLOPT_ENCODING]); + $sent = Server::received()[0]; + $this->assertEquals('gzip', Core::header($sent, 'Accept-Encoding')); + $this->assertEquals('test', Core::body($response)); + } + + public function testDoesNotForceDecode() + { + $content = $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => ['decode_content' => false], + ]); + $response->wait(); + $sent = Server::received()[0]; + $this->assertNull(Core::header($sent, 'Accept-Encoding')); + $this->assertEquals($content, Core::body($response)); + } + + public function testProtocolVersion() + { + Server::flush(); + Server::enqueue([['status' => 200]]); + $a = new CurlMultiHandler(); + $a([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'version' => 1.0, + ]); + $this->assertEquals(CURL_HTTP_VERSION_1_0, $_SERVER['_curl'][CURLOPT_HTTP_VERSION]); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesSaveTo() + { + $handler = new CurlMultiHandler(); + $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => ['save_to' => true], + ]); + } + + public function testSavesToStream() + { + $stream = fopen('php://memory', 'r+'); + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => [ + 'decode_content' => true, + 'save_to' => $stream, + ], + ]); + $response->wait(); + rewind($stream); + $this->assertEquals('test', stream_get_contents($stream)); + } + + public function testSavesToGuzzleStream() + { + $stream = Stream::factory(); + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => [ + 'decode_content' => true, + 'save_to' => $stream, + ], + ]); + $response->wait(); + $this->assertEquals('test', (string) $stream); + } + + public function testSavesToFileOnDisk() + { + $tmpfile = tempnam(sys_get_temp_dir(), 'testfile'); + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => [ + 'decode_content' => true, + 'save_to' => $tmpfile, + ], + ]); + $response->wait(); + $this->assertEquals('test', file_get_contents($tmpfile)); + unlink($tmpfile); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesBody() + { + $handler = new CurlMultiHandler(); + $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'body' => false, + ]); + } + + public function testAddsLargePayloadFromStreamWithNoSizeUsingChunked() + { + $stream = Stream::factory('foo'); + $stream = FnStream::decorate($stream, [ + 'getSize' => function () { + return null; + } + ]); + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'body' => $stream, + ]); + $response->wait(); + $sent = Server::received()[0]; + $this->assertEquals('chunked', Core::header($sent, 'Transfer-Encoding')); + $this->assertNull(Core::header($sent, 'Content-Length')); + $this->assertEquals('foo', $sent['body']); + } + + public function testAddsPayloadFromIterator() + { + $iter = new \ArrayIterator(['f', 'o', 'o']); + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'body' => $iter, + ]); + $response->wait(); + $sent = Server::received()[0]; + $this->assertEquals('chunked', Core::header($sent, 'Transfer-Encoding')); + $this->assertNull(Core::header($sent, 'Content-Length')); + $this->assertEquals('foo', $sent['body']); + } + + public function testAddsPayloadFromResource() + { + $res = fopen('php://memory', 'r+'); + $data = str_repeat('.', 1000000); + fwrite($res, $data); + rewind($res); + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => [ + 'host' => [Server::$host], + 'content-length' => [1000000], + ], + 'body' => $res, + ]); + $response->wait(); + $sent = Server::received()[0]; + $this->assertNull(Core::header($sent, 'Transfer-Encoding')); + $this->assertEquals(1000000, Core::header($sent, 'Content-Length')); + $this->assertEquals($data, $sent['body']); + } + + public function testAddsContentLengthFromStream() + { + $stream = Stream::factory('foo'); + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'body' => $stream, + ]); + $response->wait(); + $sent = Server::received()[0]; + $this->assertEquals(3, Core::header($sent, 'Content-Length')); + $this->assertNull(Core::header($sent, 'Transfer-Encoding')); + $this->assertEquals('foo', $sent['body']); + } + + public function testDoesNotAddMultipleContentLengthHeaders() + { + $this->addDecodeResponse(); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => [ + 'host' => [Server::$host], + 'content-length' => [3], + ], + 'body' => 'foo', + ]); + $response->wait(); + $sent = Server::received()[0]; + $this->assertEquals(3, Core::header($sent, 'Content-Length')); + $this->assertNull(Core::header($sent, 'Transfer-Encoding')); + $this->assertEquals('foo', $sent['body']); + } + + public function testSendsPostWithNoBodyOrDefaultContentType() + { + Server::flush(); + Server::enqueue([['status' => 200]]); + $handler = new CurlMultiHandler(); + $response = $handler([ + 'http_method' => 'POST', + 'uri' => '/', + 'headers' => ['host' => [Server::$host]], + ]); + $response->wait(); + $received = Server::received()[0]; + $this->assertEquals('POST', $received['http_method']); + $this->assertNull(Core::header($received, 'content-type')); + $this->assertSame('0', Core::firstHeader($received, 'content-length')); + } + + public function testParseProtocolVersion() + { + $res = CurlFactory::createResponse( + function () {}, + [], + ['curl' => ['errno' => null]], + ['HTTP/1.1 200 Ok'], + null + ); + + $this->assertSame('1.1', $res['version']); + } + + public function testFailsWhenNoResponseAndNoBody() + { + $res = CurlFactory::createResponse(function () {}, [], [], [], null); + $this->assertInstanceOf('GuzzleHttp\Ring\Exception\RingException', $res['error']); + $this->assertContains( + 'No response was received for a request with no body', + $res['error']->getMessage() + ); + } + + public function testFailsWhenCannotRewindRetry() + { + $res = CurlFactory::createResponse(function () {}, [ + 'body' => new NoSeekStream(Stream::factory('foo')) + ], [], [], null); + $this->assertInstanceOf('GuzzleHttp\Ring\Exception\RingException', $res['error']); + $this->assertContains( + 'rewind the request body failed', + $res['error']->getMessage() + ); + } + + public function testRetriesWhenBodyCanBeRewound() + { + $callHandler = $called = false; + $res = CurlFactory::createResponse(function () use (&$callHandler) { + $callHandler = true; + return ['status' => 200]; + }, [ + 'body' => FnStream::decorate(Stream::factory('test'), [ + 'seek' => function () use (&$called) { + $called = true; + return true; + } + ]) + ], [], [], null); + + $this->assertTrue($callHandler); + $this->assertTrue($called); + $this->assertEquals('200', $res['status']); + } + + public function testFailsWhenRetryMoreThanThreeTimes() + { + $call = 0; + $mock = new MockHandler(function (array $request) use (&$mock, &$call) { + $call++; + return CurlFactory::createResponse($mock, $request, [], [], null); + }); + $response = $mock([ + 'http_method' => 'GET', + 'body' => 'test', + ]); + $this->assertEquals(3, $call); + $this->assertArrayHasKey('error', $response); + $this->assertContains( + 'The cURL request was retried 3 times', + $response['error']->getMessage() + ); + } + + public function testHandles100Continue() + { + Server::flush(); + Server::enqueue([ + [ + 'status' => '200', + 'reason' => 'OK', + 'headers' => [ + 'Test' => ['Hello'], + 'Content-Length' => ['4'], + ], + 'body' => 'test', + ], + ]); + + $request = [ + 'http_method' => 'PUT', + 'headers' => [ + 'Host' => [Server::$host], + 'Expect' => ['100-Continue'], + ], + 'body' => 'test', + ]; + + $handler = new CurlMultiHandler(); + $response = $handler($request)->wait(); + $this->assertEquals(200, $response['status']); + $this->assertEquals('OK', $response['reason']); + $this->assertEquals(['Hello'], $response['headers']['Test']); + $this->assertEquals(['4'], $response['headers']['Content-Length']); + $this->assertEquals('test', Core::body($response)); + } + + public function testCreatesConnectException() + { + $m = new \ReflectionMethod('GuzzleHttp\Ring\Client\CurlFactory', 'createErrorResponse'); + $m->setAccessible(true); + $response = $m->invoke( + null, + function () {}, + [], + [ + 'err_message' => 'foo', + 'curl' => [ + 'errno' => CURLE_COULDNT_CONNECT, + ] + ] + ); + $this->assertInstanceOf('GuzzleHttp\Ring\Exception\ConnectException', $response['error']); + } + + public function testParsesLastResponseOnly() + { + $response1 = [ + 'status' => 301, + 'headers' => [ + 'Content-Length' => ['0'], + 'Location' => ['/foo'] + ] + ]; + + $response2 = [ + 'status' => 200, + 'headers' => [ + 'Content-Length' => ['0'], + 'Foo' => ['bar'] + ] + ]; + + Server::flush(); + Server::enqueue([$response1, $response2]); + + $a = new CurlMultiHandler(); + $response = $a([ + 'http_method' => 'GET', + 'headers' => ['Host' => [Server::$host]], + 'client' => [ + 'curl' => [ + CURLOPT_FOLLOWLOCATION => true + ] + ] + ])->wait(); + + $this->assertEquals(1, $response['transfer_stats']['redirect_count']); + $this->assertEquals('http://127.0.0.1:8125/foo', $response['effective_url']); + $this->assertEquals(['bar'], $response['headers']['Foo']); + $this->assertEquals(200, $response['status']); + $this->assertFalse(Core::hasHeader($response, 'Location')); + } + + public function testMaintainsMultiHeaderOrder() + { + Server::flush(); + Server::enqueue([ + [ + 'status' => 200, + 'headers' => [ + 'Content-Length' => ['0'], + 'Foo' => ['a', 'b'], + 'foo' => ['c', 'd'], + ] + ] + ]); + + $a = new CurlMultiHandler(); + $response = $a([ + 'http_method' => 'GET', + 'headers' => ['Host' => [Server::$host]] + ])->wait(); + + $this->assertEquals( + ['a', 'b', 'c', 'd'], + Core::headerLines($response, 'Foo') + ); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Directory /path/to/does/not does not exist for save_to value of /path/to/does/not/exist.txt + */ + public function testThrowsWhenDirNotFound() + { + $request = [ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$url]], + 'client' => ['save_to' => '/path/to/does/not/exist.txt'], + ]; + + $f = new CurlFactory(); + $f($request); + } +} + +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php new file mode 100644 index 0000000..ba03b8c --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php @@ -0,0 +1,96 @@ +markTestSkipped('curl_reset() is not available'); + } + } + + protected function getHandler($factory = null, $options = []) + { + return new CurlHandler($options); + } + + public function testCanSetMaxHandles() + { + $a = new CurlHandler(['max_handles' => 10]); + $this->assertEquals(10, $this->readAttribute($a, 'maxHandles')); + } + + public function testCreatesCurlErrors() + { + $handler = new CurlHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['host' => ['localhost:123']], + 'client' => ['timeout' => 0.001, 'connect_timeout' => 0.001], + ]); + $this->assertNull($response['status']); + $this->assertNull($response['reason']); + $this->assertEquals([], $response['headers']); + $this->assertInstanceOf( + 'GuzzleHttp\Ring\Exception\RingException', + $response['error'] + ); + + $this->assertEquals( + 1, + preg_match('/^cURL error \d+: .*$/', $response['error']->getMessage()) + ); + } + + public function testReleasesAdditionalEasyHandles() + { + Server::flush(); + $response = [ + 'status' => 200, + 'headers' => ['Content-Length' => [4]], + 'body' => 'test', + ]; + + Server::enqueue([$response, $response, $response, $response]); + $a = new CurlHandler(['max_handles' => 2]); + + $fn = function () use (&$calls, $a, &$fn) { + if (++$calls < 4) { + $a([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => ['progress' => $fn], + ]); + } + }; + + $request = [ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => [ + 'progress' => $fn, + ], + ]; + + $a($request); + $this->assertCount(2, $this->readAttribute($a, 'handles')); + } + + public function testReusesHandles() + { + Server::flush(); + $response = ['status' => 200]; + Server::enqueue([$response, $response]); + $a = new CurlHandler(); + $request = [ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + ]; + $a($request); + $a($request); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php new file mode 100644 index 0000000..9228f1c --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php @@ -0,0 +1,165 @@ + 200]]); + $a = new CurlMultiHandler(); + $response = $a([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + ]); + $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); + $this->assertEquals(200, $response['status']); + $this->assertArrayHasKey('transfer_stats', $response); + $realUrl = trim($response['transfer_stats']['url'], '/'); + $this->assertEquals(trim(Server::$url, '/'), $realUrl); + $this->assertArrayHasKey('effective_url', $response); + $this->assertEquals( + trim(Server::$url, '/'), + trim($response['effective_url'], '/') + ); + } + + public function testCreatesErrorResponses() + { + $url = 'http://localhost:123/'; + $a = new CurlMultiHandler(); + $response = $a([ + 'http_method' => 'GET', + 'headers' => ['host' => ['localhost:123']], + ]); + $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); + $this->assertNull($response['status']); + $this->assertNull($response['reason']); + $this->assertEquals([], $response['headers']); + $this->assertArrayHasKey('error', $response); + $this->assertContains('cURL error ', $response['error']->getMessage()); + $this->assertArrayHasKey('transfer_stats', $response); + $this->assertEquals( + trim($url, '/'), + trim($response['transfer_stats']['url'], '/') + ); + $this->assertArrayHasKey('effective_url', $response); + $this->assertEquals( + trim($url, '/'), + trim($response['effective_url'], '/') + ); + } + + public function testSendsFuturesWhenDestructed() + { + Server::enqueue([['status' => 200]]); + $a = new CurlMultiHandler(); + $response = $a([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + ]); + $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); + $a->__destruct(); + $this->assertEquals(200, $response['status']); + } + + public function testCanSetMaxHandles() + { + $a = new CurlMultiHandler(['max_handles' => 2]); + $this->assertEquals(2, $this->readAttribute($a, 'maxHandles')); + } + + public function testCanSetSelectTimeout() + { + $a = new CurlMultiHandler(['select_timeout' => 2]); + $this->assertEquals(2, $this->readAttribute($a, 'selectTimeout')); + } + + public function testSendsFuturesWhenMaxHandlesIsReached() + { + $request = [ + 'http_method' => 'PUT', + 'headers' => ['host' => [Server::$host]], + 'future' => 'lazy', // passing this to control the test + ]; + $response = ['status' => 200]; + Server::flush(); + Server::enqueue([$response, $response, $response]); + $a = new CurlMultiHandler(['max_handles' => 3]); + for ($i = 0; $i < 5; $i++) { + $responses[] = $a($request); + } + $this->assertCount(3, Server::received()); + $responses[3]->cancel(); + $responses[4]->cancel(); + } + + public function testCanCancel() + { + Server::flush(); + $response = ['status' => 200]; + Server::enqueue(array_fill_keys(range(0, 10), $response)); + $a = new CurlMultiHandler(); + $responses = []; + + for ($i = 0; $i < 10; $i++) { + $response = $a([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'future' => 'lazy', + ]); + $response->cancel(); + $responses[] = $response; + } + + $this->assertCount(0, Server::received()); + + foreach ($responses as $response) { + $this->assertTrue($this->readAttribute($response, 'isRealized')); + } + } + + public function testCannotCancelFinished() + { + Server::flush(); + Server::enqueue([['status' => 200]]); + $a = new CurlMultiHandler(); + $response = $a([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + ]); + $response->wait(); + $response->cancel(); + } + + public function testDelaysInParallel() + { + Server::flush(); + Server::enqueue([['status' => 200]]); + $a = new CurlMultiHandler(); + $expected = microtime(true) + (100 / 1000); + $response = $a([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'client' => ['delay' => 100], + ]); + $response->wait(); + $this->assertGreaterThanOrEqual($expected, microtime(true)); + } + + public function testSendsNonLazyFutures() + { + $request = [ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'future' => true, + ]; + Server::flush(); + Server::enqueue([['status' => 202]]); + $a = new CurlMultiHandler(); + $response = $a($request); + $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); + $this->assertEquals(202, $response['status']); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php new file mode 100644 index 0000000..a47bb30 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php @@ -0,0 +1,65 @@ + 200]); + $calledA = false; + $a = function (array $req) use (&$calledA, $future) { + $calledA = true; + return $future; + }; + $calledB = false; + $b = function (array $req) use (&$calledB) { $calledB = true; }; + $s = Middleware::wrapFuture($a, $b); + $s([]); + $this->assertTrue($calledA); + $this->assertFalse($calledB); + } + + public function testFutureCallsStreamingHandler() + { + $future = new CompletedFutureArray(['status' => 200]); + $calledA = false; + $a = function (array $req) use (&$calledA) { $calledA = true; }; + $calledB = false; + $b = function (array $req) use (&$calledB, $future) { + $calledB = true; + return $future; + }; + $s = Middleware::wrapFuture($a, $b); + $result = $s(['client' => ['future' => true]]); + $this->assertFalse($calledA); + $this->assertTrue($calledB); + $this->assertSame($future, $result); + } + + public function testStreamingCallsDefaultHandler() + { + $calledA = false; + $a = function (array $req) use (&$calledA) { $calledA = true; }; + $calledB = false; + $b = function (array $req) use (&$calledB) { $calledB = true; }; + $s = Middleware::wrapStreaming($a, $b); + $s([]); + $this->assertTrue($calledA); + $this->assertFalse($calledB); + } + + public function testStreamingCallsStreamingHandler() + { + $calledA = false; + $a = function (array $req) use (&$calledA) { $calledA = true; }; + $calledB = false; + $b = function (array $req) use (&$calledB) { $calledB = true; }; + $s = Middleware::wrapStreaming($a, $b); + $s(['client' => ['stream' => true]]); + $this->assertFalse($calledA); + $this->assertTrue($calledB); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php new file mode 100644 index 0000000..26bcd6c --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php @@ -0,0 +1,86 @@ + 200]); + $response = $mock([]); + $this->assertEquals(200, $response['status']); + $this->assertEquals([], $response['headers']); + $this->assertNull($response['body']); + $this->assertNull($response['reason']); + $this->assertNull($response['effective_url']); + } + + public function testReturnsFutures() + { + $deferred = new Deferred(); + $future = new FutureArray( + $deferred->promise(), + function () use ($deferred) { + $deferred->resolve(['status' => 200]); + } + ); + $mock = new MockHandler($future); + $response = $mock([]); + $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); + $this->assertEquals(200, $response['status']); + } + + public function testReturnsFuturesWithThenCall() + { + $deferred = new Deferred(); + $future = new FutureArray( + $deferred->promise(), + function () use ($deferred) { + $deferred->resolve(['status' => 200]); + } + ); + $mock = new MockHandler($future); + $response = $mock([]); + $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); + $this->assertEquals(200, $response['status']); + $req = null; + $promise = $response->then(function ($value) use (&$req) { + $req = $value; + $this->assertEquals(200, $req['status']); + }); + $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); + $this->assertEquals(200, $req['status']); + } + + public function testReturnsFuturesAndProxiesCancel() + { + $c = null; + $deferred = new Deferred(); + $future = new FutureArray( + $deferred->promise(), + function () {}, + function () use (&$c) { + $c = true; + return true; + } + ); + $mock = new MockHandler($future); + $response = $mock([]); + $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response); + $response->cancel(); + $this->assertTrue($c); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Response must be an array or FutureArrayInterface. Found + */ + public function testEnsuresMockIsValid() + { + $mock = new MockHandler('foo'); + $mock([]); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/Server.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/Server.php new file mode 100644 index 0000000..14665a5 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/Server.php @@ -0,0 +1,183 @@ + [], 'reason' => '', 'body' => '']; + $data[] = $response; + } + + self::send('PUT', '/guzzle-server/responses', json_encode($data)); + } + + /** + * Get all of the received requests as a RingPHP request structure. + * + * @return array + * @throws \RuntimeException + */ + public static function received() + { + if (!self::$started) { + return []; + } + + $response = self::send('GET', '/guzzle-server/requests'); + $body = Core::body($response); + $result = json_decode($body, true); + if ($result === false) { + throw new \RuntimeException('Error decoding response: ' + . json_last_error()); + } + + foreach ($result as &$res) { + if (isset($res['uri'])) { + $res['resource'] = $res['uri']; + } + if (isset($res['query_string'])) { + $res['resource'] .= '?' . $res['query_string']; + } + if (!isset($res['resource'])) { + $res['resource'] = ''; + } + // Ensure that headers are all arrays + if (isset($res['headers'])) { + foreach ($res['headers'] as &$h) { + $h = (array) $h; + } + unset($h); + } + } + + unset($res); + return $result; + } + + /** + * Stop running the node.js server + */ + public static function stop() + { + if (self::$started) { + self::send('DELETE', '/guzzle-server'); + } + + self::$started = false; + } + + public static function wait($maxTries = 20) + { + $tries = 0; + while (!self::isListening() && ++$tries < $maxTries) { + usleep(100000); + } + + if (!self::isListening()) { + throw new \RuntimeException('Unable to contact node.js server'); + } + } + + public static function start() + { + if (self::$started) { + return; + } + + try { + self::wait(); + } catch (\Exception $e) { + exec('node ' . __DIR__ . \DIRECTORY_SEPARATOR . 'server.js ' + . self::$port . ' >> /tmp/server.log 2>&1 &'); + self::wait(); + } + + self::$started = true; + } + + private static function isListening() + { + $response = self::send('GET', '/guzzle-server/perf', null, [ + 'connect_timeout' => 1, + 'timeout' => 1 + ]); + + return !isset($response['error']); + } + + private static function send( + $method, + $path, + $body = null, + array $client = [] + ) { + $handler = new StreamHandler(); + + $request = [ + 'http_method' => $method, + 'uri' => $path, + 'request_port' => 8125, + 'headers' => ['host' => ['127.0.0.1:8125']], + 'body' => $body, + 'client' => $client, + ]; + + if ($body) { + $request['headers']['content-length'] = [strlen($body)]; + } + + return $handler($request); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php new file mode 100644 index 0000000..3cb9a8e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php @@ -0,0 +1,480 @@ +queueRes(); + $handler = new StreamHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => [ + 'host' => [Server::$host], + 'Foo' => ['Bar'], + ], + ]); + + $this->assertEquals('1.1', $response['version']); + $this->assertEquals(200, $response['status']); + $this->assertEquals('OK', $response['reason']); + $this->assertEquals(['Bar'], $response['headers']['Foo']); + $this->assertEquals(['8'], $response['headers']['Content-Length']); + $this->assertEquals('hi there', Core::body($response)); + + $sent = Server::received()[0]; + $this->assertEquals('GET', $sent['http_method']); + $this->assertEquals('/', $sent['resource']); + $this->assertEquals(['127.0.0.1:8125'], $sent['headers']['host']); + $this->assertEquals('Bar', Core::header($sent, 'foo')); + } + + public function testAddsErrorToResponse() + { + $handler = new StreamHandler(); + $result = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => ['localhost:123']], + 'client' => ['timeout' => 0.01], + ]); + $this->assertInstanceOf( + 'GuzzleHttp\Ring\Future\CompletedFutureArray', + $result + ); + $this->assertNull($result['status']); + $this->assertNull($result['body']); + $this->assertEquals([], $result['headers']); + $this->assertInstanceOf( + 'GuzzleHttp\Ring\Exception\RingException', + $result['error'] + ); + } + + public function testEnsuresTheHttpProtocol() + { + $handler = new StreamHandler(); + $result = $handler([ + 'http_method' => 'GET', + 'url' => 'ftp://localhost:123', + ]); + $this->assertArrayHasKey('error', $result); + $this->assertContains( + 'URL is invalid: ftp://localhost:123', + $result['error']->getMessage() + ); + } + + public function testStreamAttributeKeepsStreamOpen() + { + $this->queueRes(); + $handler = new StreamHandler(); + $response = $handler([ + 'http_method' => 'PUT', + 'uri' => '/foo', + 'query_string' => 'baz=bar', + 'headers' => [ + 'host' => [Server::$host], + 'Foo' => ['Bar'], + ], + 'body' => 'test', + 'client' => ['stream' => true], + ]); + + $this->assertEquals(200, $response['status']); + $this->assertEquals('OK', $response['reason']); + $this->assertEquals('8', Core::header($response, 'Content-Length')); + $body = $response['body']; + $this->assertTrue(is_resource($body)); + $this->assertEquals('http', stream_get_meta_data($body)['wrapper_type']); + $this->assertEquals('hi there', stream_get_contents($body)); + fclose($body); + $sent = Server::received()[0]; + $this->assertEquals('PUT', $sent['http_method']); + $this->assertEquals('/foo', $sent['uri']); + $this->assertEquals('baz=bar', $sent['query_string']); + $this->assertEquals('/foo?baz=bar', $sent['resource']); + $this->assertEquals('127.0.0.1:8125', Core::header($sent, 'host')); + $this->assertEquals('Bar', Core::header($sent, 'foo')); + } + + public function testDrainsResponseIntoTempStream() + { + $this->queueRes(); + $handler = new StreamHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['host' => [Server::$host]], + ]); + $body = $response['body']; + $this->assertEquals('php://temp', stream_get_meta_data($body)['uri']); + $this->assertEquals('hi', fread($body, 2)); + fclose($body); + } + + public function testDrainsResponseIntoSaveToBody() + { + $r = fopen('php://temp', 'r+'); + $this->queueRes(); + $handler = new StreamHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['host' => [Server::$host]], + 'client' => ['save_to' => $r], + ]); + $body = $response['body']; + $this->assertEquals('php://temp', stream_get_meta_data($body)['uri']); + $this->assertEquals('hi', fread($body, 2)); + $this->assertEquals(' there', stream_get_contents($r)); + fclose($r); + } + + public function testDrainsResponseIntoSaveToBodyAtPath() + { + $tmpfname = tempnam('/tmp', 'save_to_path'); + $this->queueRes(); + $handler = new StreamHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['host' => [Server::$host]], + 'client' => ['save_to' => $tmpfname], + ]); + $body = $response['body']; + $this->assertInstanceOf('GuzzleHttp\Stream\StreamInterface', $body); + $this->assertEquals($tmpfname, $body->getMetadata('uri')); + $this->assertEquals('hi', $body->read(2)); + $body->close(); + unlink($tmpfname); + } + + public function testAutomaticallyDecompressGzip() + { + Server::flush(); + $content = gzencode('test'); + Server::enqueue([ + [ + 'status' => 200, + 'reason' => 'OK', + 'headers' => [ + 'Content-Encoding' => ['gzip'], + 'Content-Length' => [strlen($content)], + ], + 'body' => $content, + ], + ]); + + $handler = new StreamHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'uri' => '/', + 'client' => ['decode_content' => true], + ]); + $this->assertEquals('test', Core::body($response)); + } + + public function testDoesNotForceGzipDecode() + { + Server::flush(); + $content = gzencode('test'); + Server::enqueue([ + [ + 'status' => 200, + 'reason' => 'OK', + 'headers' => [ + 'Content-Encoding' => ['gzip'], + 'Content-Length' => [strlen($content)], + ], + 'body' => $content, + ], + ]); + + $handler = new StreamHandler(); + $response = $handler([ + 'http_method' => 'GET', + 'headers' => ['host' => [Server::$host]], + 'uri' => '/', + 'client' => ['stream' => true, 'decode_content' => false], + ]); + $this->assertSame($content, Core::body($response)); + } + + public function testProtocolVersion() + { + $this->queueRes(); + $handler = new StreamHandler(); + $handler([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['host' => [Server::$host]], + 'version' => 1.0, + ]); + + $this->assertEquals(1.0, Server::received()[0]['version']); + } + + protected function getSendResult(array $opts) + { + $this->queueRes(); + $handler = new StreamHandler(); + $opts['stream'] = true; + return $handler([ + 'http_method' => 'GET', + 'uri' => '/', + 'headers' => ['host' => [Server::$host]], + 'client' => $opts, + ]); + } + + public function testAddsProxy() + { + $res = $this->getSendResult(['stream' => true, 'proxy' => '127.0.0.1:8125']); + $opts = stream_context_get_options($res['body']); + $this->assertEquals('127.0.0.1:8125', $opts['http']['proxy']); + } + + public function testAddsTimeout() + { + $res = $this->getSendResult(['stream' => true, 'timeout' => 200]); + $opts = stream_context_get_options($res['body']); + $this->assertEquals(200, $opts['http']['timeout']); + } + + public function testVerifiesVerifyIsValidIfPath() + { + $res = $this->getSendResult(['verify' => '/does/not/exist']); + $this->assertContains( + 'SSL CA bundle not found: /does/not/exist', + (string) $res['error'] + ); + } + + public function testVerifyCanBeDisabled() + { + $res = $this->getSendResult(['verify' => false]); + $this->assertArrayNotHasKey('error', $res); + } + + public function testVerifiesCertIfValidPath() + { + $res = $this->getSendResult(['cert' => '/does/not/exist']); + $this->assertContains( + 'SSL certificate not found: /does/not/exist', + (string) $res['error'] + ); + } + + public function testVerifyCanBeSetToPath() + { + $path = $path = ClientUtils::getDefaultCaBundle(); + $res = $this->getSendResult(['verify' => $path]); + $this->assertArrayNotHasKey('error', $res); + $opts = stream_context_get_options($res['body']); + $this->assertEquals(true, $opts['ssl']['verify_peer']); + $this->assertEquals($path, $opts['ssl']['cafile']); + $this->assertTrue(file_exists($opts['ssl']['cafile'])); + } + + public function testUsesSystemDefaultBundle() + { + $path = $path = ClientUtils::getDefaultCaBundle(); + $res = $this->getSendResult(['verify' => true]); + $this->assertArrayNotHasKey('error', $res); + $opts = stream_context_get_options($res['body']); + if (PHP_VERSION_ID < 50600) { + $this->assertEquals($path, $opts['ssl']['cafile']); + } + } + + public function testEnsuresVerifyOptionIsValid() + { + $res = $this->getSendResult(['verify' => 10]); + $this->assertContains( + 'Invalid verify request option', + (string) $res['error'] + ); + } + + public function testCanSetPasswordWhenSettingCert() + { + $path = __FILE__; + $res = $this->getSendResult(['cert' => [$path, 'foo']]); + $opts = stream_context_get_options($res['body']); + $this->assertEquals($path, $opts['ssl']['local_cert']); + $this->assertEquals('foo', $opts['ssl']['passphrase']); + } + + public function testDebugAttributeWritesToStream() + { + $this->queueRes(); + $f = fopen('php://temp', 'w+'); + $this->getSendResult(['debug' => $f]); + fseek($f, 0); + $contents = stream_get_contents($f); + $this->assertContains(' [CONNECT]', $contents); + $this->assertContains(' [FILE_SIZE_IS]', $contents); + $this->assertContains(' [PROGRESS]', $contents); + } + + public function testDebugAttributeWritesStreamInfoToBuffer() + { + $called = false; + $this->queueRes(); + $buffer = fopen('php://temp', 'r+'); + $this->getSendResult([ + 'progress' => function () use (&$called) { $called = true; }, + 'debug' => $buffer, + ]); + fseek($buffer, 0); + $contents = stream_get_contents($buffer); + $this->assertContains(' [CONNECT]', $contents); + $this->assertContains(' [FILE_SIZE_IS] message: "Content-Length: 8"', $contents); + $this->assertContains(' [PROGRESS] bytes_max: "8"', $contents); + $this->assertTrue($called); + } + + public function testEmitsProgressInformation() + { + $called = []; + $this->queueRes(); + $this->getSendResult([ + 'progress' => function () use (&$called) { + $called[] = func_get_args(); + }, + ]); + $this->assertNotEmpty($called); + $this->assertEquals(8, $called[0][0]); + $this->assertEquals(0, $called[0][1]); + } + + public function testEmitsProgressInformationAndDebugInformation() + { + $called = []; + $this->queueRes(); + $buffer = fopen('php://memory', 'w+'); + $this->getSendResult([ + 'debug' => $buffer, + 'progress' => function () use (&$called) { + $called[] = func_get_args(); + }, + ]); + $this->assertNotEmpty($called); + $this->assertEquals(8, $called[0][0]); + $this->assertEquals(0, $called[0][1]); + rewind($buffer); + $this->assertNotEmpty(stream_get_contents($buffer)); + fclose($buffer); + } + + public function testAddsProxyByProtocol() + { + $url = str_replace('http', 'tcp', Server::$url); + $res = $this->getSendResult(['proxy' => ['http' => $url]]); + $opts = stream_context_get_options($res['body']); + $this->assertEquals($url, $opts['http']['proxy']); + } + + public function testPerformsShallowMergeOfCustomContextOptions() + { + $res = $this->getSendResult([ + 'stream_context' => [ + 'http' => [ + 'request_fulluri' => true, + 'method' => 'HEAD', + ], + 'socket' => [ + 'bindto' => '127.0.0.1:0', + ], + 'ssl' => [ + 'verify_peer' => false, + ], + ], + ]); + + $opts = stream_context_get_options($res['body']); + $this->assertEquals('HEAD', $opts['http']['method']); + $this->assertTrue($opts['http']['request_fulluri']); + $this->assertFalse($opts['ssl']['verify_peer']); + $this->assertEquals('127.0.0.1:0', $opts['socket']['bindto']); + } + + public function testEnsuresThatStreamContextIsAnArray() + { + $res = $this->getSendResult(['stream_context' => 'foo']); + $this->assertContains( + 'stream_context must be an array', + (string) $res['error'] + ); + } + + public function testDoesNotAddContentTypeByDefault() + { + $this->queueRes(); + $handler = new StreamHandler(); + $handler([ + 'http_method' => 'PUT', + 'uri' => '/', + 'headers' => ['host' => [Server::$host], 'content-length' => [3]], + 'body' => 'foo', + ]); + $req = Server::received()[0]; + $this->assertEquals('', Core::header($req, 'Content-Type')); + $this->assertEquals(3, Core::header($req, 'Content-Length')); + } + + private function queueRes() + { + Server::flush(); + Server::enqueue([ + [ + 'status' => 200, + 'reason' => 'OK', + 'headers' => [ + 'Foo' => ['Bar'], + 'Content-Length' => [8], + ], + 'body' => 'hi there', + ], + ]); + } + + public function testSupports100Continue() + { + Server::flush(); + Server::enqueue([ + [ + 'status' => '200', + 'reason' => 'OK', + 'headers' => [ + 'Test' => ['Hello'], + 'Content-Length' => ['4'], + ], + 'body' => 'test', + ], + ]); + + $request = [ + 'http_method' => 'PUT', + 'headers' => [ + 'Host' => [Server::$host], + 'Expect' => ['100-Continue'], + ], + 'body' => 'test', + ]; + + $handler = new StreamHandler(); + $response = $handler($request); + $this->assertEquals(200, $response['status']); + $this->assertEquals('OK', $response['reason']); + $this->assertEquals(['Hello'], $response['headers']['Test']); + $this->assertEquals(['4'], $response['headers']['Content-Length']); + $this->assertEquals('test', Core::body($response)); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/server.js b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/server.js new file mode 100644 index 0000000..6a03e33 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Client/server.js @@ -0,0 +1,241 @@ +/** + * Guzzle node.js test server to return queued responses to HTTP requests and + * expose a RESTful API for enqueueing responses and retrieving the requests + * that have been received. + * + * - Delete all requests that have been received: + * > DELETE /guzzle-server/requests + * > Host: 127.0.0.1:8125 + * + * - Enqueue responses + * > PUT /guzzle-server/responses + * > Host: 127.0.0.1:8125 + * > + * > [{'status': 200, 'reason': 'OK', 'headers': {}, 'body': '' }] + * + * - Get the received requests + * > GET /guzzle-server/requests + * > Host: 127.0.0.1:8125 + * + * < HTTP/1.1 200 OK + * < + * < [{'http_method': 'GET', 'uri': '/', 'headers': {}, 'body': 'string'}] + * + * - Attempt access to the secure area + * > GET /secure/by-digest/qop-auth/guzzle-server/requests + * > Host: 127.0.0.1:8125 + * + * < HTTP/1.1 401 Unauthorized + * < WWW-Authenticate: Digest realm="Digest Test", qop="auth", nonce="0796e98e1aeef43141fab2a66bf4521a", algorithm="MD5", stale="false" + * < + * < 401 Unauthorized + * + * - Shutdown the server + * > DELETE /guzzle-server + * > Host: 127.0.0.1:8125 + * + * @package Guzzle PHP + * @license See the LICENSE file that was distributed with this source code. + */ + +var http = require('http'); +var url = require('url'); + +/** + * Guzzle node.js server + * @class + */ +var GuzzleServer = function(port, log) { + + this.port = port; + this.log = log; + this.responses = []; + this.requests = []; + var that = this; + + var md5 = function(input) { + var crypto = require('crypto'); + var hasher = crypto.createHash('md5'); + hasher.update(input); + return hasher.digest('hex'); + } + + /** + * Node.js HTTP server authentication module. + * + * It is only initialized on demand (by loadAuthentifier). This avoids + * requiring the dependency to http-auth on standard operations, and the + * performance hit at startup. + */ + var auth; + + /** + * Provides authentication handlers (Basic, Digest). + */ + var loadAuthentifier = function(type, options) { + var typeId = type; + if (type == 'digest') { + typeId += '.'+(options && options.qop ? options.qop : 'none'); + } + if (!loadAuthentifier[typeId]) { + if (!auth) { + try { + auth = require('http-auth'); + } catch (e) { + if (e.code == 'MODULE_NOT_FOUND') { + return; + } + } + } + switch (type) { + case 'digest': + var digestParams = { + realm: 'Digest Test', + login: 'me', + password: 'test' + }; + if (options && options.qop) { + digestParams.qop = options.qop; + } + loadAuthentifier[typeId] = auth.digest(digestParams, function(username, callback) { + callback(md5(digestParams.login + ':' + digestParams.realm + ':' + digestParams.password)); + }); + break + } + } + return loadAuthentifier[typeId]; + }; + + var firewallRequest = function(request, req, res, requestHandlerCallback) { + var securedAreaUriParts = request.uri.match(/^\/secure\/by-(digest)(\/qop-([^\/]*))?(\/.*)$/); + if (securedAreaUriParts) { + var authentifier = loadAuthentifier(securedAreaUriParts[1], { qop: securedAreaUriParts[2] }); + if (!authentifier) { + res.writeHead(501, 'HTTP authentication not implemented', { 'Content-Length': 0 }); + res.end(); + return; + } + authentifier.check(req, res, function(req, res) { + req.url = securedAreaUriParts[4]; + requestHandlerCallback(request, req, res); + }); + } else { + requestHandlerCallback(request, req, res); + } + }; + + var controlRequest = function(request, req, res) { + if (req.url == '/guzzle-server/perf') { + res.writeHead(200, 'OK', {'Content-Length': 16}); + res.end('Body of response'); + } else if (req.method == 'DELETE') { + if (req.url == '/guzzle-server/requests') { + // Clear the received requests + that.requests = []; + res.writeHead(200, 'OK', { 'Content-Length': 0 }); + res.end(); + if (that.log) { + console.log('Flushing requests'); + } + } else if (req.url == '/guzzle-server') { + // Shutdown the server + res.writeHead(200, 'OK', { 'Content-Length': 0, 'Connection': 'close' }); + res.end(); + if (that.log) { + console.log('Shutting down'); + } + that.server.close(); + } + } else if (req.method == 'GET') { + if (req.url === '/guzzle-server/requests') { + if (that.log) { + console.log('Sending received requests'); + } + // Get received requests + var body = JSON.stringify(that.requests); + res.writeHead(200, 'OK', { 'Content-Length': body.length }); + res.end(body); + } + } else if (req.method == 'PUT' && req.url == '/guzzle-server/responses') { + if (that.log) { + console.log('Adding responses...'); + } + if (!request.body) { + if (that.log) { + console.log('No response data was provided'); + } + res.writeHead(400, 'NO RESPONSES IN REQUEST', { 'Content-Length': 0 }); + } else { + that.responses = eval('(' + request.body + ')'); + for (var i = 0; i < that.responses.length; i++) { + if (that.responses[i].body) { + that.responses[i].body = new Buffer(that.responses[i].body, 'base64'); + } + } + if (that.log) { + console.log(that.responses); + } + res.writeHead(200, 'OK', { 'Content-Length': 0 }); + } + res.end(); + } + }; + + var receivedRequest = function(request, req, res) { + if (req.url.indexOf('/guzzle-server') === 0) { + controlRequest(request, req, res); + } else if (req.url.indexOf('/guzzle-server') == -1 && !that.responses.length) { + res.writeHead(500); + res.end('No responses in queue'); + } else { + if (that.log) { + console.log('Returning response from queue and adding request'); + } + that.requests.push(request); + var response = that.responses.shift(); + res.writeHead(response.status, response.reason, response.headers); + res.end(response.body); + } + }; + + this.start = function() { + + that.server = http.createServer(function(req, res) { + + var parts = url.parse(req.url, false); + var request = { + http_method: req.method, + scheme: parts.scheme, + uri: parts.pathname, + query_string: parts.query, + headers: req.headers, + version: req.httpVersion, + body: '' + }; + + // Receive each chunk of the request body + req.addListener('data', function(chunk) { + request.body += chunk; + }); + + // Called when the request completes + req.addListener('end', function() { + firewallRequest(request, req, res, receivedRequest); + }); + }); + + that.server.listen(this.port, '127.0.0.1'); + + if (this.log) { + console.log('Server running at http://127.0.0.1:8125/'); + } + }; +}; + +// Get the port from the arguments +port = process.argv.length >= 3 ? process.argv[2] : 8125; +log = process.argv.length >= 4 ? process.argv[3] : false; + +// Start the server +server = new GuzzleServer(port, log); +server.start(); diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/CoreTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/CoreTest.php new file mode 100644 index 0000000..49522f2 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/CoreTest.php @@ -0,0 +1,336 @@ +assertNull(Core::header([], 'Foo')); + $this->assertNull(Core::firstHeader([], 'Foo')); + } + + public function testChecksIfHasHeader() + { + $message = [ + 'headers' => [ + 'Foo' => ['Bar', 'Baz'], + 'foo' => ['hello'], + 'bar' => ['1'] + ] + ]; + $this->assertTrue(Core::hasHeader($message, 'Foo')); + $this->assertTrue(Core::hasHeader($message, 'foo')); + $this->assertTrue(Core::hasHeader($message, 'FoO')); + $this->assertTrue(Core::hasHeader($message, 'bar')); + $this->assertFalse(Core::hasHeader($message, 'barr')); + } + + public function testReturnsFirstHeaderWhenSimple() + { + $this->assertEquals('Bar', Core::firstHeader([ + 'headers' => ['Foo' => ['Bar', 'Baz']], + ], 'Foo')); + } + + public function testReturnsFirstHeaderWhenMultiplePerLine() + { + $this->assertEquals('Bar', Core::firstHeader([ + 'headers' => ['Foo' => ['Bar, Baz']], + ], 'Foo')); + } + + public function testExtractsCaseInsensitiveHeader() + { + $this->assertEquals( + 'hello', + Core::header(['headers' => ['foo' => ['hello']]], 'FoO') + ); + } + + public function testExtractsCaseInsensitiveHeaderLines() + { + $this->assertEquals( + ['a', 'b', 'c', 'd'], + Core::headerLines([ + 'headers' => [ + 'foo' => ['a', 'b'], + 'Foo' => ['c', 'd'] + ] + ], 'foo') + ); + } + + public function testExtractsHeaderLines() + { + $this->assertEquals( + ['bar', 'baz'], + Core::headerLines([ + 'headers' => [ + 'Foo' => ['bar', 'baz'], + ], + ], 'Foo') + ); + } + + public function testExtractsHeaderAsString() + { + $this->assertEquals( + 'bar, baz', + Core::header([ + 'headers' => [ + 'Foo' => ['bar', 'baz'], + ], + ], 'Foo', true) + ); + } + + public function testReturnsNullWhenHeaderNotFound() + { + $this->assertNull(Core::header(['headers' => []], 'Foo')); + } + + public function testRemovesHeaders() + { + $message = [ + 'headers' => [ + 'foo' => ['bar'], + 'Foo' => ['bam'], + 'baz' => ['123'], + ], + ]; + + $this->assertSame($message, Core::removeHeader($message, 'bam')); + $this->assertEquals([ + 'headers' => ['baz' => ['123']], + ], Core::removeHeader($message, 'foo')); + } + + public function testCreatesUrl() + { + $req = [ + 'scheme' => 'http', + 'headers' => ['host' => ['foo.com']], + 'uri' => '/', + ]; + + $this->assertEquals('http://foo.com/', Core::url($req)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage No Host header was provided + */ + public function testEnsuresHostIsAvailableWhenCreatingUrls() + { + Core::url([]); + } + + public function testCreatesUrlWithQueryString() + { + $req = [ + 'scheme' => 'http', + 'headers' => ['host' => ['foo.com']], + 'uri' => '/', + 'query_string' => 'foo=baz', + ]; + + $this->assertEquals('http://foo.com/?foo=baz', Core::url($req)); + } + + public function testUsesUrlIfSet() + { + $req = ['url' => 'http://foo.com']; + $this->assertEquals('http://foo.com', Core::url($req)); + } + + public function testReturnsNullWhenNoBody() + { + $this->assertNull(Core::body([])); + } + + public function testReturnsStreamAsString() + { + $this->assertEquals( + 'foo', + Core::body(['body' => Stream::factory('foo')]) + ); + } + + public function testReturnsString() + { + $this->assertEquals('foo', Core::body(['body' => 'foo'])); + } + + public function testReturnsResourceContent() + { + $r = fopen('php://memory', 'w+'); + fwrite($r, 'foo'); + rewind($r); + $this->assertEquals('foo', Core::body(['body' => $r])); + fclose($r); + } + + public function testReturnsIteratorContent() + { + $a = new \ArrayIterator(['a', 'b', 'cd', '']); + $this->assertEquals('abcd', Core::body(['body' => $a])); + } + + public function testReturnsObjectToString() + { + $this->assertEquals('foo', Core::body(['body' => new StrClass])); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testEnsuresBodyIsValid() + { + Core::body(['body' => false]); + } + + public function testParsesHeadersFromLines() + { + $lines = ['Foo: bar', 'Foo: baz', 'Abc: 123', 'Def: a, b']; + $this->assertEquals([ + 'Foo' => ['bar', 'baz'], + 'Abc' => ['123'], + 'Def' => ['a, b'], + ], Core::headersFromLines($lines)); + } + + public function testParsesHeadersFromLinesWithMultipleLines() + { + $lines = ['Foo: bar', 'Foo: baz', 'Foo: 123']; + $this->assertEquals([ + 'Foo' => ['bar', 'baz', '123'], + ], Core::headersFromLines($lines)); + } + + public function testCreatesArrayCallFunctions() + { + $called = []; + $a = function ($a, $b) use (&$called) { + $called['a'] = func_get_args(); + }; + $b = function ($a, $b) use (&$called) { + $called['b'] = func_get_args(); + }; + $c = Core::callArray([$a, $b]); + $c(1, 2); + $this->assertEquals([1, 2], $called['a']); + $this->assertEquals([1, 2], $called['b']); + } + + public function testRewindsGuzzleStreams() + { + $str = Stream::factory('foo'); + $this->assertTrue(Core::rewindBody(['body' => $str])); + } + + public function testRewindsStreams() + { + $str = Stream::factory('foo')->detach(); + $this->assertTrue(Core::rewindBody(['body' => $str])); + } + + public function testRewindsIterators() + { + $iter = new \ArrayIterator(['foo']); + $this->assertTrue(Core::rewindBody(['body' => $iter])); + } + + public function testRewindsStrings() + { + $this->assertTrue(Core::rewindBody(['body' => 'hi'])); + } + + public function testRewindsToStrings() + { + $this->assertTrue(Core::rewindBody(['body' => new StrClass()])); + } + + public function typeProvider() + { + return [ + ['foo', 'string(3) "foo"'], + [true, 'bool(true)'], + [false, 'bool(false)'], + [10, 'int(10)'], + [1.0, 'float(1)'], + [new StrClass(), 'object(GuzzleHttp\Tests\Ring\StrClass)'], + [['foo'], 'array(1)'] + ]; + } + + /** + * @dataProvider typeProvider + */ + public function testDescribesType($input, $output) + { + $this->assertEquals($output, Core::describeType($input)); + } + + public function testDoesSleep() + { + $t = microtime(true); + $expected = $t + (100 / 1000); + Core::doSleep(['client' => ['delay' => 100]]); + $this->assertGreaterThanOrEqual($expected, microtime(true)); + } + + public function testProxiesFuture() + { + $f = new CompletedFutureArray(['status' => 200]); + $res = null; + $proxied = Core::proxy($f, function ($value) use (&$res) { + $value['foo'] = 'bar'; + $res = $value; + return $value; + }); + $this->assertNotSame($f, $proxied); + $this->assertEquals(200, $f->wait()['status']); + $this->assertArrayNotHasKey('foo', $f->wait()); + $this->assertEquals('bar', $proxied->wait()['foo']); + $this->assertEquals(200, $proxied->wait()['status']); + } + + public function testProxiesDeferredFuture() + { + $d = new Deferred(); + $f = new FutureArray($d->promise()); + $f2 = Core::proxy($f); + $d->resolve(['foo' => 'bar']); + $this->assertEquals('bar', $f['foo']); + $this->assertEquals('bar', $f2['foo']); + } + + public function testProxiesDeferredFutureFailure() + { + $d = new Deferred(); + $f = new FutureArray($d->promise()); + $f2 = Core::proxy($f); + $d->reject(new \Exception('foo')); + try { + $f2['hello?']; + $this->fail('did not throw'); + } catch (\Exception $e) { + $this->assertEquals('foo', $e->getMessage()); + } + + } +} + +final class StrClass +{ + public function __toString() + { + return 'foo'; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php new file mode 100644 index 0000000..82d7efb --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php @@ -0,0 +1,21 @@ + 'bar']); + $this->assertEquals('bar', $f['foo']); + $this->assertFalse(isset($f['baz'])); + $f['abc'] = '123'; + $this->assertTrue(isset($f['abc'])); + $this->assertEquals(['foo' => 'bar', 'abc' => '123'], iterator_to_array($f)); + $this->assertEquals(2, count($f)); + unset($f['abc']); + $this->assertEquals(1, count($f)); + $this->assertEquals(['foo' => 'bar'], iterator_to_array($f)); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php new file mode 100644 index 0000000..6ded40d --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php @@ -0,0 +1,46 @@ +assertEquals('hi', $f->wait()); + $f->cancel(); + + $a = null; + $f->then(function ($v) use (&$a) { + $a = $v; + }); + $this->assertSame('hi', $a); + } + + public function testThrows() + { + $ex = new \Exception('foo'); + $f = new CompletedFutureValue(null, $ex); + $f->cancel(); + try { + $f->wait(); + $this->fail('did not throw'); + } catch (\Exception $e) { + $this->assertSame($e, $ex); + } + } + + public function testMarksAsCancelled() + { + $ex = new CancelledFutureAccessException(); + $f = new CompletedFutureValue(null, $ex); + try { + $f->wait(); + $this->fail('did not throw'); + } catch (\Exception $e) { + $this->assertSame($e, $ex); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php new file mode 100644 index 0000000..0e09f5a --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php @@ -0,0 +1,56 @@ +promise(), + function () use (&$c, $deferred) { + $c = true; + $deferred->resolve(['status' => 200]); + } + ); + $this->assertFalse($c); + $this->assertFalse($this->readAttribute($f, 'isRealized')); + $this->assertEquals(200, $f['status']); + $this->assertTrue($c); + } + + public function testActsLikeArray() + { + $deferred = new Deferred(); + $f = new FutureArray( + $deferred->promise(), + function () use (&$c, $deferred) { + $deferred->resolve(['status' => 200]); + } + ); + + $this->assertTrue(isset($f['status'])); + $this->assertEquals(200, $f['status']); + $this->assertEquals(['status' => 200], $f->wait()); + $this->assertEquals(1, count($f)); + $f['baz'] = 10; + $this->assertEquals(10, $f['baz']); + unset($f['baz']); + $this->assertFalse(isset($f['baz'])); + $this->assertEquals(['status' => 200], iterator_to_array($f)); + } + + /** + * @expectedException \RuntimeException + */ + public function testThrowsWhenAccessingInvalidProperty() + { + $deferred = new Deferred(); + $f = new FutureArray($deferred->promise(), function () {}); + $f->foo; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php new file mode 100644 index 0000000..d59c543 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php @@ -0,0 +1,109 @@ +promise(), + function () use ($deferred, &$called) { + $called++; + $deferred->resolve('foo'); + } + ); + + $this->assertEquals('foo', $f->wait()); + $this->assertEquals(1, $called); + $this->assertEquals('foo', $f->wait()); + $this->assertEquals(1, $called); + $f->cancel(); + $this->assertTrue($this->readAttribute($f, 'isRealized')); + } + + /** + * @expectedException \GuzzleHttp\Ring\Exception\CancelledFutureAccessException + */ + public function testThrowsWhenAccessingCancelled() + { + $f = new FutureValue( + (new Deferred())->promise(), + function () {}, + function () { return true; } + ); + $f->cancel(); + $f->wait(); + } + + /** + * @expectedException \OutOfBoundsException + */ + public function testThrowsWhenDerefFailure() + { + $called = false; + $deferred = new Deferred(); + $f = new FutureValue( + $deferred->promise(), + function () use(&$called) { + $called = true; + } + ); + $deferred->reject(new \OutOfBoundsException()); + $f->wait(); + $this->assertFalse($called); + } + + /** + * @expectedException \GuzzleHttp\Ring\Exception\RingException + * @expectedExceptionMessage Waiting did not resolve future + */ + public function testThrowsWhenDerefDoesNotResolve() + { + $deferred = new Deferred(); + $f = new FutureValue( + $deferred->promise(), + function () use(&$called) { + $called = true; + } + ); + $f->wait(); + } + + public function testThrowingCancelledFutureAccessExceptionCancels() + { + $deferred = new Deferred(); + $f = new FutureValue( + $deferred->promise(), + function () use ($deferred) { + throw new CancelledFutureAccessException(); + } + ); + try { + $f->wait(); + $this->fail('did not throw'); + } catch (CancelledFutureAccessException $e) {} + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage foo + */ + public function testThrowingExceptionInDerefMarksAsFailed() + { + $deferred = new Deferred(); + $f = new FutureValue( + $deferred->promise(), + function () { + throw new \Exception('foo'); + } + ); + $f->wait(); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/bootstrap.php b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/bootstrap.php new file mode 100644 index 0000000..017610f --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/ringphp/tests/bootstrap.php @@ -0,0 +1,11 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/Makefile b/admin/classes/domain/vendor/guzzlehttp/streams/Makefile new file mode 100644 index 0000000..f4d4284 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/Makefile @@ -0,0 +1,19 @@ +all: clean coverage + +release: tag + git push origin --tags + +tag: + chag tag --sign --debug CHANGELOG.rst + +test: + vendor/bin/phpunit + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/README.rst b/admin/classes/domain/vendor/guzzlehttp/streams/README.rst new file mode 100644 index 0000000..baff63b --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/README.rst @@ -0,0 +1,36 @@ +============== +Guzzle Streams +============== + +Provides a simple abstraction over streams of data. + +This library is used in `Guzzle 5 `_, and is +(currently) compatible with the WIP PSR-7. + +Installation +============ + +This package can be installed easily using `Composer `_. +Simply add the following to the composer.json file at the root of your project: + +.. code-block:: javascript + + { + "require": { + "guzzlehttp/streams": "~3.0" + } + } + +Then install your dependencies using ``composer.phar install``. + +Documentation +============= + +The documentation for this package can be found on the main Guzzle website at +http://docs.guzzlephp.org/en/guzzle4/streams.html. + +Testing +======= + +This library is tested using PHPUnit. You'll need to install the dependencies +using `Composer `_ then run ``make test``. diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/composer.json b/admin/classes/domain/vendor/guzzlehttp/streams/composer.json new file mode 100644 index 0000000..6d70343 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/composer.json @@ -0,0 +1,28 @@ +{ + "name": "guzzlehttp/streams", + "description": "Provides a simple abstraction over streams of data", + "homepage": "http://guzzlephp.org/", + "keywords": ["stream", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "autoload": { + "psr-4": { "GuzzleHttp\\Stream\\": "src/" } + }, + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/phpunit.xml.dist b/admin/classes/domain/vendor/guzzlehttp/streams/phpunit.xml.dist new file mode 100644 index 0000000..6e758c1 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + + tests + + + + + src + + src/functions.php + + + + diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/AppendStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/AppendStream.php new file mode 100644 index 0000000..94bda71 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/AppendStream.php @@ -0,0 +1,220 @@ +addStream($stream); + } + } + + public function __toString() + { + try { + $this->seek(0); + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream) + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents() + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + * + * {@inheritdoc} + */ + public function close() + { + $this->pos = $this->current = 0; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream + * + * {@inheritdoc} + */ + public function detach() + { + $this->close(); + $this->detached = true; + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function tell() + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + * + * {@inheritdoc} + */ + public function getSize() + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof() + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + * + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable || $whence !== SEEK_SET) { + return false; + } + + $success = true; + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $stream) { + if (!$stream->seek(0)) { + $success = false; + } + } + + if (!$success) { + return false; + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $this->read(min(8096, $offset - $this->pos)); + } + + return $this->pos == $offset; + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + * + * {@inheritdoc} + */ + public function read($length) + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + + while ($remaining > 0) { + // Progress to the next stream if needed. + if ($this->streams[$this->current]->eof()) { + if ($this->current == $total) { + break; + } + $this->current++; + } + $buffer .= $this->streams[$this->current]->read($remaining); + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function write($string) + { + return false; + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/AsyncReadStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/AsyncReadStream.php new file mode 100644 index 0000000..25ad960 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/AsyncReadStream.php @@ -0,0 +1,207 @@ +isReadable() || !$buffer->isWritable()) { + throw new \InvalidArgumentException( + 'Buffer must be readable and writable' + ); + } + + if (isset($config['size'])) { + $this->size = $config['size']; + } + + static $callables = ['pump', 'drain']; + foreach ($callables as $check) { + if (isset($config[$check])) { + if (!is_callable($config[$check])) { + throw new \InvalidArgumentException( + $check . ' must be callable' + ); + } + $this->{$check} = $config[$check]; + } + } + + $this->hwm = $buffer->getMetadata('hwm'); + + // Cannot drain when there's no high water mark. + if ($this->hwm === null) { + $this->drain = null; + } + + $this->stream = $buffer; + } + + /** + * Factory method used to create new async stream and an underlying buffer + * if no buffer is provided. + * + * This function accepts the same options as AsyncReadStream::__construct, + * but added the following key value pairs: + * + * - buffer: (StreamInterface) Buffer used to buffer data. If none is + * provided, a default buffer is created. + * - hwm: (int) High water mark to use if a buffer is created on your + * behalf. + * - max_buffer: (int) If provided, wraps the utilized buffer in a + * DroppingStream decorator to ensure that buffer does not exceed a given + * length. When exceeded, the stream will begin dropping data. Set the + * max_buffer to 0, to use a NullStream which does not store data. + * - write: (callable) A function that is invoked when data is written + * to the underlying buffer. The function accepts the buffer as the first + * argument, and the data being written as the second. The function MUST + * return the number of bytes that were written or false to let writers + * know to slow down. + * - drain: (callable) See constructor documentation. + * - pump: (callable) See constructor documentation. + * + * @param array $options Associative array of options. + * + * @return array Returns an array containing the buffer used to buffer + * data, followed by the ready to use AsyncReadStream object. + */ + public static function create(array $options = []) + { + $maxBuffer = isset($options['max_buffer']) + ? $options['max_buffer'] + : null; + + if ($maxBuffer === 0) { + $buffer = new NullStream(); + } elseif (isset($options['buffer'])) { + $buffer = $options['buffer']; + } else { + $hwm = isset($options['hwm']) ? $options['hwm'] : 16384; + $buffer = new BufferStream($hwm); + } + + if ($maxBuffer > 0) { + $buffer = new DroppingStream($buffer, $options['max_buffer']); + } + + // Call the on_write callback if an on_write function was provided. + if (isset($options['write'])) { + $onWrite = $options['write']; + $buffer = FnStream::decorate($buffer, [ + 'write' => function ($string) use ($buffer, $onWrite) { + $result = $buffer->write($string); + $onWrite($buffer, $string); + return $result; + } + ]); + } + + return [$buffer, new self($buffer, $options)]; + } + + public function getSize() + { + return $this->size; + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + return false; + } + + public function read($length) + { + if (!$this->needsDrain && $this->drain) { + $this->needsDrain = $this->stream->getSize() >= $this->hwm; + } + + $result = $this->stream->read($length); + + // If we need to drain, then drain when the buffer is empty. + if ($this->needsDrain && $this->stream->getSize() === 0) { + $this->needsDrain = false; + $drainFn = $this->drain; + $drainFn($this->stream); + } + + $resultLen = strlen($result); + + // If a pump was provided, the buffer is still open, and not enough + // data was given, then block until the data is provided. + if ($this->pump && $resultLen < $length) { + $pumpFn = $this->pump; + $result .= $pumpFn($length - $resultLen); + } + + return $result; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/BufferStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/BufferStream.php new file mode 100644 index 0000000..0fffbd6 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/BufferStream.php @@ -0,0 +1,138 @@ +hwm = $hwm; + } + + public function __toString() + { + return $this->getContents(); + } + + public function getContents() + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close() + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function getSize() + { + return strlen($this->buffer); + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function seek($offset, $whence = SEEK_SET) + { + return false; + } + + public function eof() + { + return strlen($this->buffer) === 0; + } + + public function tell() + { + return false; + } + + /** + * Reads data from the buffer. + */ + public function read($length) + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string) + { + $this->buffer .= $string; + + if (strlen($this->buffer) >= $this->hwm) { + return false; + } + + return strlen($string); + } + + public function getMetadata($key = null) + { + if ($key == 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/CachingStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/CachingStream.php new file mode 100644 index 0000000..60bb905 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/CachingStream.php @@ -0,0 +1,122 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); + } + + public function getSize() + { + return max($this->stream->getSize(), $this->remoteStream->getSize()); + } + + /** + * {@inheritdoc} + * @throws SeekException When seeking with SEEK_END or when seeking + * past the total size of the buffer stream + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->tell(); + } else { + return false; + } + + // You cannot skip ahead past where you've read from the remote stream + if ($byte > $this->stream->getSize()) { + throw new SeekException( + $this, + $byte, + sprintf('Cannot seek to byte %d when the buffered stream only' + . ' contains %d bytes', $byte, $this->stream->getSize()) + ); + } + + return $this->stream->seek($byte); + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof() + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + $this->remoteStream->close() && $this->stream->close(); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/DroppingStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/DroppingStream.php new file mode 100644 index 0000000..56ee80c --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/DroppingStream.php @@ -0,0 +1,42 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string) + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning false when the underlying stream is too large. + if ($diff <= 0) { + return false; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + $this->stream->write(substr($string, 0, $diff)); + + return false; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php new file mode 100644 index 0000000..e631b9f --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php @@ -0,0 +1,4 @@ +stream = $stream; + $msg = $msg ?: 'Could not seek the stream to position ' . $pos; + parent::__construct($msg); + } + + /** + * @return StreamInterface + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/FnStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/FnStream.php new file mode 100644 index 0000000..6b5872d --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/FnStream.php @@ -0,0 +1,147 @@ +methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { + $methods[$diff] = [$stream, $diff]; + } + + return new self($methods); + } + + public function __toString() + { + return call_user_func($this->_fn___toString); + } + + public function close() + { + return call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function attach($stream) + { + return call_user_func($this->_fn_attach, $stream); + } + + public function getSize() + { + return call_user_func($this->_fn_getSize); + } + + public function tell() + { + return call_user_func($this->_fn_tell); + } + + public function eof() + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable() + { + return call_user_func($this->_fn_isSeekable); + } + + public function seek($offset, $whence = SEEK_SET) + { + return call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable() + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string) + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable() + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length) + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents() + { + return call_user_func($this->_fn_getContents); + } + + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php new file mode 100644 index 0000000..4d049a6 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php @@ -0,0 +1,117 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, null, stream_context_create([ + 'guzzle' => ['stream' => $stream] + ])); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register() + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read($count) + { + return $this->stream->read($count); + } + + public function stream_write($data) + { + return (int) $this->stream->write($data); + } + + public function stream_tell() + { + return $this->stream->tell(); + } + + public function stream_eof() + { + return $this->stream->eof(); + } + + public function stream_seek($offset, $whence) + { + return $this->stream->seek($offset, $whence); + } + + public function stream_stat() + { + static $modeMap = [ + 'r' => 33060, + 'r+' => 33206, + 'w' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/InflateStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/InflateStream.php new file mode 100644 index 0000000..978af21 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/InflateStream.php @@ -0,0 +1,27 @@ +stream = new Stream($resource); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/LazyOpenStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/LazyOpenStream.php new file mode 100644 index 0000000..6242ee7 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/LazyOpenStream.php @@ -0,0 +1,37 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return Stream::factory(Utils::open($this->filename, $this->mode)); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/LimitStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/LimitStream.php new file mode 100644 index 0000000..e9fad98 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/LimitStream.php @@ -0,0 +1,161 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof() + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit == -1) { + return false; + } + + $tell = $this->stream->tell(); + if ($tell === false) { + return false; + } + + return $tell >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + * {@inheritdoc} + */ + public function getSize() + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit == -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence !== SEEK_SET || $offset < 0) { + return false; + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + return $this->stream->seek($offset); + } + + /** + * Give a relative tell() + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @return self + * @throws SeekException + */ + public function setOffset($offset) + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if (!$this->stream->seek($offset)) { + if ($current > $offset) { + throw new SeekException($this, $offset); + } else { + $this->stream->read($offset - $current); + } + } + } + + $this->offset = $offset; + + return $this; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + * @return self + */ + public function setLimit($limit) + { + $this->limit = $limit; + + return $this; + } + + public function read($length) + { + if ($this->limit == -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } else { + return false; + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php new file mode 100644 index 0000000..c1433ad --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php @@ -0,0 +1,11 @@ +stream->attach($stream); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/NullStream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/NullStream.php new file mode 100644 index 0000000..41ee776 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/NullStream.php @@ -0,0 +1,78 @@ +source = $source; + $this->size = isset($options['size']) ? $options['size'] : null; + $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; + $this->buffer = new BufferStream(); + } + + public function __toString() + { + return Utils::copyToString($this); + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = false; + $this->source = null; + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function getSize() + { + return $this->size; + } + + public function tell() + { + return $this->tellPos; + } + + public function eof() + { + return !$this->source; + } + + public function isSeekable() + { + return false; + } + + public function seek($offset, $whence = SEEK_SET) + { + return false; + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + return false; + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents() + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return isset($this->metadata[$key]) ? $this->metadata[$key] : null; + } + + private function pump($length) + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/Stream.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/Stream.php new file mode 100644 index 0000000..7adbc5e --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/Stream.php @@ -0,0 +1,261 @@ + [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true + ] + ]; + + /** + * Create a new stream based on the input type. + * + * This factory accepts the same associative array of options as described + * in the constructor. + * + * @param resource|string|StreamInterface $resource Entity body data + * @param array $options Additional options + * + * @return Stream + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function factory($resource = '', array $options = []) + { + $type = gettype($resource); + + if ($type == 'string') { + $stream = fopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new self($stream, $options); + } + + if ($type == 'resource') { + return new self($resource, $options); + } + + if ($resource instanceof StreamInterface) { + return $resource; + } + + if ($type == 'object' && method_exists($resource, '__toString')) { + return self::factory((string) $resource, $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + if ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . $type); + } + + /** + * This constructor accepts an associative array of options. + * + * - size: (int) If a read stream would otherwise have an indeterminate + * size, but the size is known due to foreknownledge, then you can + * provide that size, in bytes. + * - metadata: (array) Any additional metadata to return when the metadata + * of the stream is accessed. + * + * @param resource $stream Stream resource to wrap. + * @param array $options Associative array of options. + * + * @throws \InvalidArgumentException if the stream is not a stream resource + */ + public function __construct($stream, $options = []) + { + if (!is_resource($stream)) { + throw new \InvalidArgumentException('Stream must be a resource'); + } + + if (isset($options['size'])) { + $this->size = $options['size']; + } + + $this->customMetadata = isset($options['metadata']) + ? $options['metadata'] + : []; + + $this->attach($stream); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + if (!$this->stream) { + return ''; + } + + $this->seek(0); + + return (string) stream_get_contents($this->stream); + } + + public function getContents() + { + return $this->stream ? stream_get_contents($this->stream) : ''; + } + + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + + $this->detach(); + } + + public function detach() + { + $result = $this->stream; + $this->stream = $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function attach($stream) + { + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); + $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->uri = $this->getMetadata('uri'); + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + if (!$this->stream) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function eof() + { + return !$this->stream || feof($this->stream); + } + + public function tell() + { + return $this->stream ? ftell($this->stream) : false; + } + + public function setSize($size) + { + $this->size = $size; + + return $this; + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->seekable + ? fseek($this->stream, $offset, $whence) === 0 + : false; + } + + public function read($length) + { + return $this->readable ? fread($this->stream, $length) : false; + } + + public function write($string) + { + // We can't know the size after writing anything + $this->size = null; + + return $this->writable ? fwrite($this->stream, $string) : false; + } + + public function getMetadata($key = null) + { + if (!$this->stream) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php new file mode 100644 index 0000000..39c19c5 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php @@ -0,0 +1,143 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + */ + public function __get($name) + { + if ($name == 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString() + { + try { + $this->seek(0); + return $this->getContents(); + } catch (\Exception $e) { + // Really, PHP? https://bugs.php.net/bug.php?id=53648 + trigger_error('StreamDecorator::__toString exception: ' + . (string) $e, E_USER_ERROR); + return ''; + } + } + + public function getContents() + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + $result = call_user_func_array(array($this->stream, $method), $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close() + { + $this->stream->close(); + } + + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function getSize() + { + return $this->stream->getSize(); + } + + public function eof() + { + return $this->stream->eof(); + } + + public function tell() + { + return $this->stream->tell(); + } + + public function isReadable() + { + return $this->stream->isReadable(); + } + + public function isWritable() + { + return $this->stream->isWritable(); + } + + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->stream->seek($offset, $whence); + } + + public function read($length) + { + return $this->stream->read($length); + } + + public function write($string) + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @return StreamInterface + * @throws \BadMethodCallException + */ + protected function createStream() + { + throw new \BadMethodCallException('createStream() not implemented in ' + . get_class($this)); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/src/StreamInterface.php b/admin/classes/domain/vendor/guzzlehttp/streams/src/StreamInterface.php new file mode 100644 index 0000000..fd19c6f --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/src/StreamInterface.php @@ -0,0 +1,159 @@ +eof()) { + $buf = $stream->read(1048576); + if ($buf === false) { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + if ($buf === false) { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + */ + public static function copyToStream( + StreamInterface $source, + StreamInterface $dest, + $maxLen = -1 + ) { + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read(1048576))) { + break; + } + } + return; + } + + $bytes = 0; + while (!$source->eof()) { + $buf = $source->read($maxLen - $bytes); + if (!($len = strlen($buf))) { + break; + } + $bytes += $len; + $dest->write($buf); + if ($bytes == $maxLen) { + break; + } + } + } + + /** + * Calculate a hash of a Stream + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @return string Returns the hash of the stream + * @throws SeekException + */ + public static function hash( + StreamInterface $stream, + $algo, + $rawOutput = false + ) { + $pos = $stream->tell(); + + if ($pos > 0 && !$stream->seek(0)) { + throw new SeekException($stream); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * Read a line from the stream up to the maximum allowed buffer length + * + * @param StreamInterface $stream Stream to read from + * @param int $maxLength Maximum buffer length + * + * @return string|bool + */ + public static function readline(StreamInterface $stream, $maxLength = null) + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + if (false === ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte == PHP_EOL || ++$size == $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * Alias of GuzzleHttp\Stream\Stream::factory. + * + * @param mixed $resource Resource to create + * @param array $options Associative array of stream options defined in + * {@see \GuzzleHttp\Stream\Stream::__construct} + * + * @return StreamInterface + * + * @see GuzzleHttp\Stream\Stream::factory + * @see GuzzleHttp\Stream\Stream::__construct + */ + public static function create($resource, array $options = []) + { + return Stream::factory($resource, $options); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/AppendStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/AppendStreamTest.php new file mode 100644 index 0000000..78798d9 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/AppendStreamTest.php @@ -0,0 +1,178 @@ +getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['isReadable']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(false)); + $a->addStream($s); + } + + public function testValidatesSeekType() + { + $a = new AppendStream(); + $this->assertFalse($a->seek(100, SEEK_CUR)); + } + + public function testTriesToRewindOnSeek() + { + $a = new AppendStream(); + $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['isReadable', 'seek', 'isSeekable']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(true)); + $s->expects($this->once()) + ->method('isSeekable') + ->will($this->returnValue(true)); + $s->expects($this->once()) + ->method('seek') + ->will($this->returnValue(false)); + $a->addStream($s); + $this->assertFalse($a->seek(10)); + } + + public function testSeeksToPositionByReading() + { + $a = new AppendStream([ + Stream::factory('foo'), + Stream::factory('bar'), + Stream::factory('baz'), + ]); + + $this->assertTrue($a->seek(3)); + $this->assertEquals(3, $a->tell()); + $this->assertEquals('bar', $a->read(3)); + $a->seek(6); + $this->assertEquals(6, $a->tell()); + $this->assertEquals('baz', $a->read(3)); + } + + public function testDetachesEachStream() + { + $s1 = Stream::factory('foo'); + $s2 = Stream::factory('foo'); + $a = new AppendStream([$s1, $s2]); + $this->assertSame('foofoo', (string) $a); + $a->detach(); + $this->assertSame('', (string) $a); + $this->assertSame(0, $a->getSize()); + } + + public function testClosesEachStream() + { + $s1 = Stream::factory('foo'); + $a = new AppendStream([$s1]); + $a->close(); + $this->assertSame('', (string) $a); + } + + public function testIsNotWritable() + { + $a = new AppendStream([Stream::factory('foo')]); + $this->assertFalse($a->isWritable()); + $this->assertTrue($a->isSeekable()); + $this->assertTrue($a->isReadable()); + $this->assertFalse($a->write('foo')); + } + + public function testDoesNotNeedStreams() + { + $a = new AppendStream(); + $this->assertEquals('', (string) $a); + } + + public function testCanReadFromMultipleStreams() + { + $a = new AppendStream([ + Stream::factory('foo'), + Stream::factory('bar'), + Stream::factory('baz'), + ]); + $this->assertFalse($a->eof()); + $this->assertSame(0, $a->tell()); + $this->assertEquals('foo', $a->read(3)); + $this->assertEquals('bar', $a->read(3)); + $this->assertEquals('baz', $a->read(3)); + $this->assertTrue($a->eof()); + $this->assertSame(9, $a->tell()); + $this->assertEquals('foobarbaz', (string) $a); + } + + public function testCanDetermineSizeFromMultipleStreams() + { + $a = new AppendStream([ + Stream::factory('foo'), + Stream::factory('bar') + ]); + $this->assertEquals(6, $a->getSize()); + + $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['isSeekable', 'isReadable']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('isSeekable') + ->will($this->returnValue(null)); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(true)); + $a->addStream($s); + $this->assertNull($a->getSize()); + } + + public function testCatchesExceptionsWhenCastingToString() + { + $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['read', 'isReadable', 'eof']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('read') + ->will($this->throwException(new \RuntimeException('foo'))); + $s->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(true)); + $s->expects($this->any()) + ->method('eof') + ->will($this->returnValue(false)); + $a = new AppendStream([$s]); + $this->assertFalse($a->eof()); + $this->assertSame('', (string) $a); + } + + public function testCanDetach() + { + $s = new AppendStream(); + $s->detach(); + } + + public function testReturnsEmptyMetadata() + { + $s = new AppendStream(); + $this->assertEquals([], $s->getMetadata()); + $this->assertNull($s->getMetadata('foo')); + } + + /** + * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException + */ + public function testCannotAttach() + { + $p = new AppendStream(); + $p->attach('a'); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php new file mode 100644 index 0000000..8c78995 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php @@ -0,0 +1,186 @@ + function () { return false; }] + )); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Buffer must be readable and writable + */ + public function testValidatesWritableBuffer() + { + new AsyncReadStream(FnStream::decorate( + Stream::factory(), + ['isWritable' => function () { return false; }] + )); + } + + public function testValidatesHwmMetadata() + { + $a = new AsyncReadStream(Stream::factory(), [ + 'drain' => function() {} + ]); + $this->assertNull($this->readAttribute($a, 'drain')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage pump must be callable + */ + public function testValidatesPumpIsCallable() + { + new AsyncReadStream(new BufferStream(), ['pump' => true]); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage drain must be callable + */ + public function testValidatesDrainIsCallable() + { + new AsyncReadStream(new BufferStream(), ['drain' => true]); + } + + public function testCanInitialize() + { + $buffer = new BufferStream(); + $a = new AsyncReadStream($buffer, [ + 'size' => 10, + 'drain' => function () {}, + 'pump' => function () {}, + ]); + $this->assertSame($buffer, $this->readAttribute($a, 'stream')); + $this->assertTrue(is_callable($this->readAttribute($a, 'drain'))); + $this->assertTrue(is_callable($this->readAttribute($a, 'pump'))); + $this->assertTrue($a->isReadable()); + $this->assertFalse($a->isSeekable()); + $this->assertFalse($a->isWritable()); + $this->assertFalse($a->write('foo')); + $this->assertEquals(10, $a->getSize()); + } + + public function testReadsFromBufferWithNoDrainOrPump() + { + $buffer = new BufferStream(); + $a = new AsyncReadStream($buffer); + $buffer->write('foo'); + $this->assertNull($a->getSize()); + $this->assertEquals('foo', $a->read(10)); + $this->assertEquals('', $a->read(10)); + } + + public function testCallsPumpForMoreDataWhenRequested() + { + $called = 0; + $buffer = new BufferStream(); + $a = new AsyncReadStream($buffer, [ + 'pump' => function ($size) use (&$called) { + $called++; + return str_repeat('.', $size); + } + ]); + $buffer->write('foobar'); + $this->assertEquals('foo', $a->read(3)); + $this->assertEquals(0, $called); + $this->assertEquals('bar.....', $a->read(8)); + $this->assertEquals(1, $called); + $this->assertEquals('..', $a->read(2)); + $this->assertEquals(2, $called); + } + + public function testCallsDrainWhenNeeded() + { + $called = 0; + $buffer = new BufferStream(5); + $a = new AsyncReadStream($buffer, [ + 'drain' => function (BufferStream $b) use (&$called, $buffer) { + $this->assertSame($b, $buffer); + $called++; + } + ]); + + $buffer->write('foobar'); + $this->assertEquals(6, $buffer->getSize()); + $this->assertEquals(0, $called); + + $a->read(3); + $this->assertTrue($this->readAttribute($a, 'needsDrain')); + $this->assertEquals(3, $buffer->getSize()); + $this->assertEquals(0, $called); + + $a->read(3); + $this->assertEquals(0, $buffer->getSize()); + $this->assertFalse($this->readAttribute($a, 'needsDrain')); + $this->assertEquals(1, $called); + } + + public function testCreatesBufferWithNoConfig() + { + list($buffer, $async) = AsyncReadStream::create(); + $this->assertInstanceOf('GuzzleHttp\Stream\BufferStream', $buffer); + $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); + } + + public function testCreatesBufferWithSpecifiedBuffer() + { + $buf = new BufferStream(); + list($buffer, $async) = AsyncReadStream::create(['buffer' => $buf]); + $this->assertSame($buf, $buffer); + $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); + } + + public function testCreatesNullStream() + { + list($buffer, $async) = AsyncReadStream::create(['max_buffer' => 0]); + $this->assertInstanceOf('GuzzleHttp\Stream\NullStream', $buffer); + $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); + } + + public function testCreatesDroppingStream() + { + list($buffer, $async) = AsyncReadStream::create(['max_buffer' => 5]); + $this->assertInstanceOf('GuzzleHttp\Stream\DroppingStream', $buffer); + $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); + $buffer->write('12345678910'); + $this->assertEquals(5, $buffer->getSize()); + } + + public function testCreatesOnWriteStream() + { + $c = 0; + $b = new BufferStream(); + list($buffer, $async) = AsyncReadStream::create([ + 'buffer' => $b, + 'write' => function (BufferStream $buf, $data) use (&$c, $b) { + $this->assertSame($buf, $b); + $this->assertEquals('foo', $data); + $c++; + } + ]); + $this->assertInstanceOf('GuzzleHttp\Stream\FnStream', $buffer); + $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); + $this->assertEquals(0, $c); + $this->assertEquals(3, $buffer->write('foo')); + $this->assertEquals(1, $c); + $this->assertEquals(3, $buffer->write('foo')); + $this->assertEquals(2, $c); + $this->assertEquals('foofoo', (string) $buffer); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/BufferStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/BufferStreamTest.php new file mode 100644 index 0000000..f9bfea2 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/BufferStreamTest.php @@ -0,0 +1,69 @@ +assertTrue($b->isReadable()); + $this->assertTrue($b->isWritable()); + $this->assertFalse($b->isSeekable()); + $this->assertEquals(null, $b->getMetadata('foo')); + $this->assertEquals(10, $b->getMetadata('hwm')); + $this->assertEquals([], $b->getMetadata()); + } + + public function testRemovesReadDataFromBuffer() + { + $b = new BufferStream(); + $this->assertEquals(3, $b->write('foo')); + $this->assertEquals(3, $b->getSize()); + $this->assertFalse($b->eof()); + $this->assertEquals('foo', $b->read(10)); + $this->assertTrue($b->eof()); + $this->assertEquals('', $b->read(10)); + } + + public function testCanCastToStringOrGetContents() + { + $b = new BufferStream(); + $b->write('foo'); + $b->write('baz'); + $this->assertEquals('foo', $b->read(3)); + $b->write('bar'); + $this->assertEquals('bazbar', (string) $b); + $this->assertFalse($b->tell()); + } + + public function testDetachClearsBuffer() + { + $b = new BufferStream(); + $b->write('foo'); + $b->detach(); + $this->assertEquals(0, $b->tell()); + $this->assertTrue($b->eof()); + $this->assertEquals(3, $b->write('abc')); + $this->assertEquals('abc', $b->read(10)); + } + + public function testExceedingHighwaterMarkReturnsFalseButStillBuffers() + { + $b = new BufferStream(5); + $this->assertEquals(3, $b->write('hi ')); + $this->assertFalse($b->write('hello')); + $this->assertEquals('hi hello', (string) $b); + $this->assertEquals(4, $b->write('test')); + } + + /** + * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException + */ + public function testCannotAttach() + { + $p = new BufferStream(); + $p->attach('a'); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/CachingStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/CachingStreamTest.php new file mode 100644 index 0000000..ea969b3 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/CachingStreamTest.php @@ -0,0 +1,136 @@ +decorated = Stream::factory('testing'); + $this->body = new CachingStream($this->decorated); + } + + public function tearDown() + { + $this->decorated->close(); + $this->body->close(); + } + + public function testUsesRemoteSizeIfPossible() + { + $body = Stream::factory('test'); + $caching = new CachingStream($body); + $this->assertEquals(4, $caching->getSize()); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Cannot seek to byte 10 + */ + public function testCannotSeekPastWhatHasBeenRead() + { + $this->body->seek(10); + } + + public function testCannotUseSeekEnd() + { + $this->assertFalse($this->body->seek(2, SEEK_END)); + } + + public function testRewindUsesSeek() + { + $a = Stream::factory('foo'); + $d = $this->getMockBuilder('GuzzleHttp\Stream\CachingStream') + ->setMethods(array('seek')) + ->setConstructorArgs(array($a)) + ->getMock(); + $d->expects($this->once()) + ->method('seek') + ->with(0) + ->will($this->returnValue(true)); + $d->seek(0); + } + + public function testCanSeekToReadBytes() + { + $this->assertEquals('te', $this->body->read(2)); + $this->body->seek(0); + $this->assertEquals('test', $this->body->read(4)); + $this->assertEquals(4, $this->body->tell()); + $this->body->seek(2); + $this->assertEquals(2, $this->body->tell()); + $this->body->seek(2, SEEK_CUR); + $this->assertEquals(4, $this->body->tell()); + $this->assertEquals('ing', $this->body->read(3)); + } + + public function testWritesToBufferStream() + { + $this->body->read(2); + $this->body->write('hi'); + $this->body->seek(0); + $this->assertEquals('tehiing', (string) $this->body); + } + + public function testSkipsOverwrittenBytes() + { + $decorated = Stream::factory( + implode("\n", array_map(function ($n) { + return str_pad($n, 4, '0', STR_PAD_LEFT); + }, range(0, 25))) + ); + + $body = new CachingStream($decorated); + + $this->assertEquals("0000\n", Utils::readline($body)); + $this->assertEquals("0001\n", Utils::readline($body)); + // Write over part of the body yet to be read, so skip some bytes + $this->assertEquals(5, $body->write("TEST\n")); + $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes')); + // Read, which skips bytes, then reads + $this->assertEquals("0003\n", Utils::readline($body)); + $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes')); + $this->assertEquals("0004\n", Utils::readline($body)); + $this->assertEquals("0005\n", Utils::readline($body)); + + // Overwrite part of the cached body (so don't skip any bytes) + $body->seek(5); + $this->assertEquals(5, $body->write("ABCD\n")); + $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes')); + $this->assertEquals("TEST\n", Utils::readline($body)); + $this->assertEquals("0003\n", Utils::readline($body)); + $this->assertEquals("0004\n", Utils::readline($body)); + $this->assertEquals("0005\n", Utils::readline($body)); + $this->assertEquals("0006\n", Utils::readline($body)); + $this->assertEquals(5, $body->write("1234\n")); + $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes')); + + // Seek to 0 and ensure the overwritten bit is replaced + $body->seek(0); + $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50)); + + // Ensure that casting it to a string does not include the bit that was overwritten + $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body); + } + + public function testClosesBothStreams() + { + $s = fopen('php://temp', 'r'); + $a = Stream::factory($s); + $d = new CachingStream($a); + $d->close(); + $this->assertFalse(is_resource($s)); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/DroppingStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/DroppingStreamTest.php new file mode 100644 index 0000000..bb2cb22 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/DroppingStreamTest.php @@ -0,0 +1,26 @@ +assertEquals(3, $drop->write('hel')); + $this->assertFalse($drop->write('lo')); + $this->assertEquals(5, $drop->getSize()); + $this->assertEquals('hello', $drop->read(5)); + $this->assertEquals(0, $drop->getSize()); + $drop->write('12345678910'); + $this->assertEquals(5, $stream->getSize()); + $this->assertEquals(5, $drop->getSize()); + $this->assertEquals('12345', (string) $drop); + $this->assertEquals(0, $drop->getSize()); + $drop->write('hello'); + $this->assertFalse($drop->write('test')); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php new file mode 100644 index 0000000..fd8cd1a --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php @@ -0,0 +1,16 @@ +assertSame($s, $e->getStream()); + $this->assertContains('10', $e->getMessage()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/FnStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/FnStreamTest.php new file mode 100644 index 0000000..6cc336b --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/FnStreamTest.php @@ -0,0 +1,89 @@ +seek(1); + } + + public function testProxiesToFunction() + { + $s = new FnStream([ + 'read' => function ($len) { + $this->assertEquals(3, $len); + return 'foo'; + } + ]); + + $this->assertEquals('foo', $s->read(3)); + } + + public function testCanCloseOnDestruct() + { + $called = false; + $s = new FnStream([ + 'close' => function () use (&$called) { + $called = true; + } + ]); + unset($s); + $this->assertTrue($called); + } + + public function testDoesNotRequireClose() + { + $s = new FnStream([]); + unset($s); + } + + public function testDecoratesStream() + { + $a = Stream::factory('foo'); + $b = FnStream::decorate($a, []); + $this->assertEquals(3, $b->getSize()); + $this->assertEquals($b->isWritable(), true); + $this->assertEquals($b->isReadable(), true); + $this->assertEquals($b->isSeekable(), true); + $this->assertEquals($b->read(3), 'foo'); + $this->assertEquals($b->tell(), 3); + $this->assertEquals($a->tell(), 3); + $this->assertEquals($b->eof(), true); + $this->assertEquals($a->eof(), true); + $b->seek(0); + $this->assertEquals('foo', (string) $b); + $b->seek(0); + $this->assertEquals('foo', $b->getContents()); + $this->assertEquals($a->getMetadata(), $b->getMetadata()); + $b->seek(0, SEEK_END); + $b->write('bar'); + $this->assertEquals('foobar', (string) $b); + $this->assertInternalType('resource', $b->detach()); + $b->close(); + } + + public function testDecoratesWithCustomizations() + { + $called = false; + $a = Stream::factory('foo'); + $b = FnStream::decorate($a, [ + 'read' => function ($len) use (&$called, $a) { + $called = true; + return $a->read($len); + } + ]); + $this->assertEquals('foo', $b->read(3)); + $this->assertTrue($called); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php new file mode 100644 index 0000000..33c3ecc --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php @@ -0,0 +1,99 @@ +assertSame('foo', fread($handle, 3)); + $this->assertSame(3, ftell($handle)); + $this->assertSame(3, fwrite($handle, 'bar')); + $this->assertSame(0, fseek($handle, 0)); + $this->assertSame('foobar', fread($handle, 6)); + $this->assertTrue(feof($handle)); + + // This fails on HHVM for some reason + if (!defined('HHVM_VERSION')) { + $this->assertEquals([ + 'dev' => 0, + 'ino' => 0, + 'mode' => 33206, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 6, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0, + 0 => 0, + 1 => 0, + 2 => 33206, + 3 => 0, + 4 => 0, + 5 => 0, + 6 => 0, + 7 => 6, + 8 => 0, + 9 => 0, + 10 => 0, + 11 => 0, + 12 => 0, + ], fstat($handle)); + } + + $this->assertTrue(fclose($handle)); + $this->assertSame('foobar', (string) $stream); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesStream() + { + $stream = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['isReadable', 'isWritable']) + ->getMockForAbstractClass(); + $stream->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(false)); + $stream->expects($this->once()) + ->method('isWritable') + ->will($this->returnValue(false)); + GuzzleStreamWrapper::getResource($stream); + } + + /** + * @expectedException \PHPUnit_Framework_Error_Warning + */ + public function testReturnsFalseWhenStreamDoesNotExist() + { + fopen('guzzle://foo', 'r'); + } + + public function testCanOpenReadonlyStream() + { + $stream = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['isReadable', 'isWritable']) + ->getMockForAbstractClass(); + $stream->expects($this->once()) + ->method('isReadable') + ->will($this->returnValue(false)); + $stream->expects($this->once()) + ->method('isWritable') + ->will($this->returnValue(true)); + $r = GuzzleStreamWrapper::getResource($stream); + $this->assertInternalType('resource', $r); + fclose($r); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/InflateStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/InflateStreamTest.php new file mode 100644 index 0000000..ead9356 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/InflateStreamTest.php @@ -0,0 +1,16 @@ +assertEquals('test', (string) $b); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php new file mode 100644 index 0000000..79e0078 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php @@ -0,0 +1,64 @@ +fname = tempnam('/tmp', 'tfile'); + + if (file_exists($this->fname)) { + unlink($this->fname); + } + } + + public function tearDown() + { + if (file_exists($this->fname)) { + unlink($this->fname); + } + } + + public function testOpensLazily() + { + $l = new LazyOpenStream($this->fname, 'w+'); + $l->write('foo'); + $this->assertInternalType('array', $l->getMetadata()); + $this->assertFileExists($this->fname); + $this->assertEquals('foo', file_get_contents($this->fname)); + $this->assertEquals('foo', (string) $l); + } + + public function testProxiesToFile() + { + file_put_contents($this->fname, 'foo'); + $l = new LazyOpenStream($this->fname, 'r'); + $this->assertEquals('foo', $l->read(4)); + $this->assertTrue($l->eof()); + $this->assertEquals(3, $l->tell()); + $this->assertTrue($l->isReadable()); + $this->assertTrue($l->isSeekable()); + $this->assertFalse($l->isWritable()); + $l->seek(1); + $this->assertEquals('oo', $l->getContents()); + $this->assertEquals('foo', (string) $l); + $this->assertEquals(3, $l->getSize()); + $this->assertInternalType('array', $l->getMetadata()); + $l->close(); + } + + public function testDetachesUnderlyingStream() + { + file_put_contents($this->fname, 'foo'); + $l = new LazyOpenStream($this->fname, 'r'); + $r = $l->detach(); + $this->assertInternalType('resource', $r); + fseek($r, 0); + $this->assertEquals('foo', stream_get_contents($r)); + fclose($r); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/LimitStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/LimitStreamTest.php new file mode 100644 index 0000000..efb1dc5 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/LimitStreamTest.php @@ -0,0 +1,133 @@ +decorated = Stream::factory(fopen(__FILE__, 'r')); + $this->body = new LimitStream($this->decorated, 10, 3); + } + + public function testReturnsSubset() + { + $body = new LimitStream(Stream::factory('foo'), -1, 1); + $this->assertEquals('oo', (string) $body); + $this->assertTrue($body->eof()); + $body->seek(0); + $this->assertFalse($body->eof()); + $this->assertEquals('oo', $body->read(100)); + $this->assertTrue($body->eof()); + } + + public function testReturnsSubsetWhenCastToString() + { + $body = Stream::factory('foo_baz_bar'); + $limited = new LimitStream($body, 3, 4); + $this->assertEquals('baz', (string) $limited); + } + + public function testReturnsSubsetOfEmptyBodyWhenCastToString() + { + $body = Stream::factory(''); + $limited = new LimitStream($body, 0, 10); + $this->assertEquals('', (string) $limited); + } + + public function testSeeksWhenConstructed() + { + $this->assertEquals(0, $this->body->tell()); + $this->assertEquals(3, $this->decorated->tell()); + } + + public function testAllowsBoundedSeek() + { + $this->assertEquals(true, $this->body->seek(100)); + $this->assertEquals(10, $this->body->tell()); + $this->assertEquals(13, $this->decorated->tell()); + $this->assertEquals(true, $this->body->seek(0)); + $this->assertEquals(0, $this->body->tell()); + $this->assertEquals(3, $this->decorated->tell()); + $this->assertEquals(false, $this->body->seek(-10)); + $this->assertEquals(0, $this->body->tell()); + $this->assertEquals(3, $this->decorated->tell()); + $this->assertEquals(true, $this->body->seek(5)); + $this->assertEquals(5, $this->body->tell()); + $this->assertEquals(8, $this->decorated->tell()); + $this->assertEquals(false, $this->body->seek(1000, SEEK_END)); + } + + public function testReadsOnlySubsetOfData() + { + $data = $this->body->read(100); + $this->assertEquals(10, strlen($data)); + $this->assertFalse($this->body->read(1000)); + + $this->body->setOffset(10); + $newData = $this->body->read(100); + $this->assertEquals(10, strlen($newData)); + $this->assertNotSame($data, $newData); + } + + /** + * @expectedException \GuzzleHttp\Stream\Exception\SeekException + * @expectedExceptionMessage Could not seek the stream to position 2 + */ + public function testThrowsWhenCurrentGreaterThanOffsetSeek() + { + $a = Stream::factory('foo_bar'); + $b = new NoSeekStream($a); + $c = new LimitStream($b); + $a->getContents(); + $c->setOffset(2); + } + + public function testClaimsConsumedWhenReadLimitIsReached() + { + $this->assertFalse($this->body->eof()); + $this->body->read(1000); + $this->assertTrue($this->body->eof()); + } + + public function testContentLengthIsBounded() + { + $this->assertEquals(10, $this->body->getSize()); + } + + public function testGetContentsIsBasedOnSubset() + { + $body = new LimitStream(Stream::factory('foobazbar'), 3, 3); + $this->assertEquals('baz', $body->getContents()); + } + + public function testReturnsNullIfSizeCannotBeDetermined() + { + $a = new FnStream([ + 'getSize' => function () { return null; }, + 'tell' => function () { return 0; }, + ]); + $b = new LimitStream($a); + $this->assertNull($b->getSize()); + } + + public function testLengthLessOffsetWhenNoLimitSize() + { + $a = Stream::factory('foo_bar'); + $b = new LimitStream($a, -1, 4); + $this->assertEquals(3, $b->getSize()); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php new file mode 100644 index 0000000..21b7c6d --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php @@ -0,0 +1,41 @@ +getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['isSeekable', 'seek']) + ->getMockForAbstractClass(); + $s->expects($this->never())->method('seek'); + $s->expects($this->never())->method('isSeekable'); + $wrapped = new NoSeekStream($s); + $this->assertFalse($wrapped->isSeekable()); + $this->assertFalse($wrapped->seek(2)); + } + + public function testHandlesClose() + { + $s = Stream::factory('foo'); + $wrapped = new NoSeekStream($s); + $wrapped->close(); + $this->assertFalse($wrapped->write('foo')); + } + + public function testCanAttach() + { + $s1 = Stream::factory('foo'); + $s2 = Stream::factory('bar'); + $wrapped = new NoSeekStream($s1); + $wrapped->attach($s2->detach()); + $this->assertEquals('bar', (string) $wrapped); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/NullStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/NullStreamTest.php new file mode 100644 index 0000000..8e41431 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/NullStreamTest.php @@ -0,0 +1,39 @@ +assertEquals('', $b->read(10)); + $this->assertEquals(4, $b->write('test')); + $this->assertEquals('', (string) $b); + $this->assertNull($b->getMetadata('a')); + $this->assertEquals([], $b->getMetadata()); + $this->assertEquals(0, $b->getSize()); + $this->assertEquals('', $b->getContents()); + $this->assertEquals(0, $b->tell()); + + $this->assertTrue($b->isReadable()); + $this->assertTrue($b->isWritable()); + $this->assertTrue($b->isSeekable()); + $this->assertFalse($b->seek(10)); + + $this->assertTrue($b->eof()); + $b->detach(); + $this->assertTrue($b->eof()); + $b->close(); + } + + /** + * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException + */ + public function testCannotAttach() + { + $p = new NullStream(); + $p->attach('a'); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/PumpStreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/PumpStreamTest.php new file mode 100644 index 0000000..2d20ce9 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/PumpStreamTest.php @@ -0,0 +1,77 @@ + ['foo' => 'bar'], + 'size' => 100 + ]); + + $this->assertEquals('bar', $p->getMetadata('foo')); + $this->assertEquals(['foo' => 'bar'], $p->getMetadata()); + $this->assertEquals(100, $p->getSize()); + } + + public function testCanReadFromCallable() + { + $p = Stream::factory(function ($size) { + return 'a'; + }); + $this->assertEquals('a', $p->read(1)); + $this->assertEquals(1, $p->tell()); + $this->assertEquals('aaaaa', $p->read(5)); + $this->assertEquals(6, $p->tell()); + } + + public function testStoresExcessDataInBuffer() + { + $called = []; + $p = Stream::factory(function ($size) use (&$called) { + $called[] = $size; + return 'abcdef'; + }); + $this->assertEquals('a', $p->read(1)); + $this->assertEquals('b', $p->read(1)); + $this->assertEquals('cdef', $p->read(4)); + $this->assertEquals('abcdefabc', $p->read(9)); + $this->assertEquals([1, 9, 3], $called); + } + + public function testInifiniteStreamWrappedInLimitStream() + { + $p = Stream::factory(function () { return 'a'; }); + $s = new LimitStream($p, 5); + $this->assertEquals('aaaaa', (string) $s); + } + + public function testDescribesCapabilities() + { + $p = Stream::factory(function () {}); + $this->assertTrue($p->isReadable()); + $this->assertFalse($p->isSeekable()); + $this->assertFalse($p->isWritable()); + $this->assertNull($p->getSize()); + $this->assertFalse($p->write('aa')); + $this->assertEquals('', $p->getContents()); + $this->assertEquals('', (string) $p); + $p->close(); + $this->assertEquals('', $p->read(10)); + $this->assertTrue($p->eof()); + } + + /** + * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException + */ + public function testCannotAttach() + { + $p = Stream::factory(function () {}); + $p->attach('a'); + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php new file mode 100644 index 0000000..2ba79ad --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php @@ -0,0 +1,147 @@ +c = fopen('php://temp', 'r+'); + fwrite($this->c, 'foo'); + fseek($this->c, 0); + $this->a = Stream::factory($this->c); + $this->b = new Str($this->a); + } + + public function testCatchesExceptionsWhenCastingToString() + { + $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') + ->setMethods(['read']) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('read') + ->will($this->throwException(new \Exception('foo'))); + $msg = ''; + set_error_handler(function ($errNo, $str) use (&$msg) { $msg = $str; }); + echo new Str($s); + restore_error_handler(); + $this->assertContains('foo', $msg); + } + + public function testToString() + { + $this->assertEquals('foo', (string) $this->b); + } + + public function testHasSize() + { + $this->assertEquals(3, $this->b->getSize()); + $this->assertSame($this->b, $this->b->setSize(2)); + $this->assertEquals(2, $this->b->getSize()); + } + + public function testReads() + { + $this->assertEquals('foo', $this->b->read(10)); + } + + public function testCheckMethods() + { + $this->assertEquals($this->a->isReadable(), $this->b->isReadable()); + $this->assertEquals($this->a->isWritable(), $this->b->isWritable()); + $this->assertEquals($this->a->isSeekable(), $this->b->isSeekable()); + } + + public function testSeeksAndTells() + { + $this->assertTrue($this->b->seek(1)); + $this->assertEquals(1, $this->a->tell()); + $this->assertEquals(1, $this->b->tell()); + $this->assertTrue($this->b->seek(0)); + $this->assertEquals(0, $this->a->tell()); + $this->assertEquals(0, $this->b->tell()); + $this->assertTrue($this->b->seek(0, SEEK_END)); + $this->assertEquals(3, $this->a->tell()); + $this->assertEquals(3, $this->b->tell()); + } + + public function testGetsContents() + { + $this->assertEquals('foo', $this->b->getContents()); + $this->assertEquals('', $this->b->getContents()); + $this->b->seek(1); + $this->assertEquals('oo', $this->b->getContents(1)); + } + + public function testCloses() + { + $this->b->close(); + $this->assertFalse(is_resource($this->c)); + } + + public function testDetaches() + { + $this->b->detach(); + $this->assertFalse($this->b->isReadable()); + } + + /** + * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException + */ + public function testCannotAttachByDefault() + { + $this->b->attach('a'); + } + + public function testWrapsMetadata() + { + $this->assertSame($this->b->getMetadata(), $this->a->getMetadata()); + $this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri')); + } + + public function testWrapsWrites() + { + $this->b->seek(0, SEEK_END); + $this->b->write('foo'); + $this->assertEquals('foofoo', (string) $this->a); + } + + /** + * @expectedException \UnexpectedValueException + */ + public function testThrowsWithInvalidGetter() + { + $this->b->foo; + } + + /** + * @expectedException \BadMethodCallException + */ + public function testThrowsWhenGetterNotImplemented() + { + $s = new BadStream(); + $s->stream; + } +} + +class BadStream +{ + use StreamDecoratorTrait; + + public function __construct() {} +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/StreamTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/StreamTest.php new file mode 100644 index 0000000..2985bfb --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/StreamTest.php @@ -0,0 +1,252 @@ +assertTrue($stream->isReadable()); + $this->assertTrue($stream->isWritable()); + $this->assertTrue($stream->isSeekable()); + $this->assertEquals('php://temp', $stream->getMetadata('uri')); + $this->assertInternalType('array', $stream->getMetadata()); + $this->assertEquals(4, $stream->getSize()); + $this->assertFalse($stream->eof()); + $stream->close(); + } + + public function testStreamClosesHandleOnDestruct() + { + $handle = fopen('php://temp', 'r'); + $stream = new Stream($handle); + unset($stream); + $this->assertFalse(is_resource($handle)); + } + + public function testConvertsToString() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertEquals('data', (string) $stream); + $this->assertEquals('data', (string) $stream); + $stream->close(); + } + + public function testGetsContents() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertEquals('', $stream->getContents()); + $stream->seek(0); + $this->assertEquals('data', $stream->getContents()); + $this->assertEquals('', $stream->getContents()); + } + + public function testChecksEof() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertFalse($stream->eof()); + $stream->read(4); + $this->assertTrue($stream->eof()); + $stream->close(); + } + + public function testAllowsSettingManualSize() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $stream->setSize(10); + $this->assertEquals(10, $stream->getSize()); + $stream->close(); + } + + public function testGetSize() + { + $size = filesize(__FILE__); + $handle = fopen(__FILE__, 'r'); + $stream = new Stream($handle); + $this->assertEquals($size, $stream->getSize()); + // Load from cache + $this->assertEquals($size, $stream->getSize()); + $stream->close(); + } + + public function testEnsuresSizeIsConsistent() + { + $h = fopen('php://temp', 'w+'); + $this->assertEquals(3, fwrite($h, 'foo')); + $stream = new Stream($h); + $this->assertEquals(3, $stream->getSize()); + $this->assertEquals(4, $stream->write('test')); + $this->assertEquals(7, $stream->getSize()); + $this->assertEquals(7, $stream->getSize()); + $stream->close(); + } + + public function testProvidesStreamPosition() + { + $handle = fopen('php://temp', 'w+'); + $stream = new Stream($handle); + $this->assertEquals(0, $stream->tell()); + $stream->write('foo'); + $this->assertEquals(3, $stream->tell()); + $stream->seek(1); + $this->assertEquals(1, $stream->tell()); + $this->assertSame(ftell($handle), $stream->tell()); + $stream->close(); + } + + public function testKeepsPositionOfResource() + { + $h = fopen(__FILE__, 'r'); + fseek($h, 10); + $stream = Stream::factory($h); + $this->assertEquals(10, $stream->tell()); + $stream->close(); + } + + public function testCanDetachAndAttachStream() + { + $r = fopen('php://temp', 'w+'); + $stream = new Stream($r); + $stream->write('foo'); + $this->assertTrue($stream->isReadable()); + $this->assertSame($r, $stream->detach()); + $this->assertNull($stream->detach()); + + $this->assertFalse($stream->isReadable()); + $this->assertFalse($stream->read(10)); + $this->assertFalse($stream->isWritable()); + $this->assertFalse($stream->write('bar')); + $this->assertFalse($stream->isSeekable()); + $this->assertFalse($stream->seek(10)); + $this->assertFalse($stream->tell()); + $this->assertTrue($stream->eof()); + $this->assertNull($stream->getSize()); + $this->assertSame('', (string) $stream); + $this->assertSame('', $stream->getContents()); + + $stream->attach($r); + $stream->seek(0); + $this->assertEquals('foo', $stream->getContents()); + $this->assertTrue($stream->isReadable()); + $this->assertTrue($stream->isWritable()); + $this->assertTrue($stream->isSeekable()); + + $stream->close(); + } + + public function testCloseClearProperties() + { + $handle = fopen('php://temp', 'r+'); + $stream = new Stream($handle); + $stream->close(); + + $this->assertEmpty($stream->getMetadata()); + $this->assertFalse($stream->isSeekable()); + $this->assertFalse($stream->isReadable()); + $this->assertFalse($stream->isWritable()); + $this->assertNull($stream->getSize()); + } + + public function testCreatesWithFactory() + { + $stream = Stream::factory('foo'); + $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $stream); + $this->assertEquals('foo', $stream->getContents()); + $stream->close(); + } + + public function testFactoryCreatesFromEmptyString() + { + $s = Stream::factory(); + $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s); + } + + public function testFactoryCreatesFromResource() + { + $r = fopen(__FILE__, 'r'); + $s = Stream::factory($r); + $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s); + $this->assertSame(file_get_contents(__FILE__), (string) $s); + } + + public function testFactoryCreatesFromObjectWithToString() + { + $r = new HasToString(); + $s = Stream::factory($r); + $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s); + $this->assertEquals('foo', (string) $s); + } + + public function testCreatePassesThrough() + { + $s = Stream::factory('foo'); + $this->assertSame($s, Stream::factory($s)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testThrowsExceptionForUnknown() + { + Stream::factory(new \stdClass()); + } + + public function testReturnsCustomMetadata() + { + $s = Stream::factory('foo', ['metadata' => ['hwm' => 3]]); + $this->assertEquals(3, $s->getMetadata('hwm')); + $this->assertArrayHasKey('hwm', $s->getMetadata()); + } + + public function testCanSetSize() + { + $s = Stream::factory('', ['size' => 10]); + $this->assertEquals(10, $s->getSize()); + } + + public function testCanCreateIteratorBasedStream() + { + $a = new \ArrayIterator(['foo', 'bar', '123']); + $p = Stream::factory($a); + $this->assertInstanceOf('GuzzleHttp\Stream\PumpStream', $p); + $this->assertEquals('foo', $p->read(3)); + $this->assertFalse($p->eof()); + $this->assertEquals('b', $p->read(1)); + $this->assertEquals('a', $p->read(1)); + $this->assertEquals('r12', $p->read(3)); + $this->assertFalse($p->eof()); + $this->assertEquals('3', $p->getContents()); + $this->assertTrue($p->eof()); + $this->assertEquals(9, $p->tell()); + } +} + +class HasToString +{ + public function __toString() { + return 'foo'; + } +} diff --git a/admin/classes/domain/vendor/guzzlehttp/streams/tests/UtilsTest.php b/admin/classes/domain/vendor/guzzlehttp/streams/tests/UtilsTest.php new file mode 100644 index 0000000..6e3e3b2 --- /dev/null +++ b/admin/classes/domain/vendor/guzzlehttp/streams/tests/UtilsTest.php @@ -0,0 +1,155 @@ +assertEquals('foobaz', Utils::copyToString($s)); + $s->seek(0); + $this->assertEquals('foo', Utils::copyToString($s, 3)); + $this->assertEquals('baz', Utils::copyToString($s, 3)); + $this->assertEquals('', Utils::copyToString($s)); + } + + public function testCopiesToStringStopsWhenReadFails() + { + $s1 = Stream::factory('foobaz'); + $s1 = FnStream::decorate($s1, [ + 'read' => function () { + return false; + } + ]); + $result = Utils::copyToString($s1); + $this->assertEquals('', $result); + } + + public function testCopiesToStream() + { + $s1 = Stream::factory('foobaz'); + $s2 = Stream::factory(''); + Utils::copyToStream($s1, $s2); + $this->assertEquals('foobaz', (string) $s2); + $s2 = Stream::factory(''); + $s1->seek(0); + Utils::copyToStream($s1, $s2, 3); + $this->assertEquals('foo', (string) $s2); + Utils::copyToStream($s1, $s2, 3); + $this->assertEquals('foobaz', (string) $s2); + } + + public function testStopsCopyToStreamWhenWriteFails() + { + $s1 = Stream::factory('foobaz'); + $s2 = Stream::factory(''); + $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]); + Utils::copyToStream($s1, $s2); + $this->assertEquals('', (string) $s2); + } + + public function testStopsCopyToSteamWhenWriteFailsWithMaxLen() + { + $s1 = Stream::factory('foobaz'); + $s2 = Stream::factory(''); + $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]); + Utils::copyToStream($s1, $s2, 10); + $this->assertEquals('', (string) $s2); + } + + public function testStopsCopyToSteamWhenReadFailsWithMaxLen() + { + $s1 = Stream::factory('foobaz'); + $s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]); + $s2 = Stream::factory(''); + Utils::copyToStream($s1, $s2, 10); + $this->assertEquals('', (string) $s2); + } + + public function testReadsLines() + { + $s = Stream::factory("foo\nbaz\nbar"); + $this->assertEquals("foo\n", Utils::readline($s)); + $this->assertEquals("baz\n", Utils::readline($s)); + $this->assertEquals("bar", Utils::readline($s)); + } + + public function testReadsLinesUpToMaxLength() + { + $s = Stream::factory("12345\n"); + $this->assertEquals("123", Utils::readline($s, 4)); + $this->assertEquals("45\n", Utils::readline($s)); + } + + public function testReadsLineUntilFalseReturnedFromRead() + { + $s = $this->getMockBuilder('GuzzleHttp\Stream\Stream') + ->setMethods(['read', 'eof']) + ->disableOriginalConstructor() + ->getMock(); + $s->expects($this->exactly(2)) + ->method('read') + ->will($this->returnCallback(function () { + static $c = false; + if ($c) { + return false; + } + $c = true; + return 'h'; + })); + $s->expects($this->exactly(2)) + ->method('eof') + ->will($this->returnValue(false)); + $this->assertEquals("h", Utils::readline($s)); + } + + public function testCalculatesHash() + { + $s = Stream::factory('foobazbar'); + $this->assertEquals(md5('foobazbar'), Utils::hash($s, 'md5')); + } + + /** + * @expectedException \GuzzleHttp\Stream\Exception\SeekException + */ + public function testCalculatesHashThrowsWhenSeekFails() + { + $s = new NoSeekStream(Stream::factory('foobazbar')); + $s->read(2); + Utils::hash($s, 'md5'); + } + + public function testCalculatesHashSeeksToOriginalPosition() + { + $s = Stream::factory('foobazbar'); + $s->seek(4); + $this->assertEquals(md5('foobazbar'), Utils::hash($s, 'md5')); + $this->assertEquals(4, $s->tell()); + } + + public function testOpensFilesSuccessfully() + { + $r = Utils::open(__FILE__, 'r'); + $this->assertInternalType('resource', $r); + fclose($r); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r + */ + public function testThrowsExceptionNotWarning() + { + Utils::open('/path/to/does/not/exist', 'r'); + } + + public function testProxiesToFactory() + { + $this->assertEquals('foo', (string) Utils::create('foo')); + } +} diff --git a/admin/classes/domain/vendor/react/promise/.gitignore b/admin/classes/domain/vendor/react/promise/.gitignore new file mode 100644 index 0000000..c4bcb78 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/.gitignore @@ -0,0 +1,4 @@ +composer.lock +composer.phar +phpunit.xml +vendor/ diff --git a/admin/classes/domain/vendor/react/promise/.travis.yml b/admin/classes/domain/vendor/react/promise/.travis.yml new file mode 100644 index 0000000..e26d91a --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/.travis.yml @@ -0,0 +1,13 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - hhvm + +before_script: + - composer self-update + - composer install --dev --prefer-source + +script: phpunit --coverage-text diff --git a/admin/classes/domain/vendor/react/promise/CHANGELOG.md b/admin/classes/domain/vendor/react/promise/CHANGELOG.md new file mode 100644 index 0000000..f2d1654 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/CHANGELOG.md @@ -0,0 +1,60 @@ +CHANGELOG +========= + +* 2.2.0 (2014-12-30) + + * Introduce new ExtendedPromiseInterface implemented by all promises + * Add new .done() method (part of the ExtendedPromiseInterface) + * Add new .otherwise() method (part of the ExtendedPromiseInterface) + * Add new .always() method (part of the ExtendedPromiseInterface) + * Add new .progress() method (part of the ExtendedPromiseInterface) + * Rename Deferred::progress to Deferred::notify to avoid confusion with + ExtendedPromiseInterface::progress (a Deferred::progress alias is still + available for backward compatibility) + * resolve() now always returns a ExtendedPromiseInterface + +* 2.1.0 (2014-10-15) + + * Introduce new CancellablePromiseInterface implemented by all promises + * Add new .cancel() method (part of the CancellablePromiseInterface) + +* 2.0.0 (2013-12-10) + + New major release. The goal was to streamline the API and to make it more + compliant with other promise libraries and especially with the new upcoming + [ES6 promises specification](https://github.com/domenic/promises-unwrapping/). + + * Add standalone Promise class. + * Add new React\Promise\race() function. + * BC break: Bump minimum PHP version to PHP 5.4. + * BC break: Remove ResolverInterface and PromiseInterface from Deferred. + * BC break: Change signature of PromiseInterface. + * BC break: Remove When and Util classes and move static methods to functions. + * BC break: FulfilledPromise and RejectedPromise now throw an exception when + initialized with a promise instead of a value/reason. + * BC break: React\Promise\Deferred::resolve() and React\Promise\Deferred::reject() + no longer return a promise. + +* 1.0.4 (2013-04-03) + + * Trigger PHP errors when invalid callback is passed. + * Fully resolve rejection value before calling rejection handler. + * Add When::lazy() to create lazy promises which will be initialized once a + consumer calls the then() method. + +* 1.0.3 (2012-11-17) + + * Add `PromisorInterface` for objects that have a `promise()` method. + +* 1.0.2 (2012-11-14) + + * Fix bug in When::any() not correctly unwrapping to a single result value + * $promiseOrValue argument of When::resolve() and When::reject() is now optional + +* 1.0.1 (2012-11-13) + + * Prevent deep recursion which was reaching `xdebug.max_nesting_level` default of 100 + +* 1.0.0 (2012-11-07) + + * First tagged release diff --git a/admin/classes/domain/vendor/react/promise/LICENSE b/admin/classes/domain/vendor/react/promise/LICENSE new file mode 100644 index 0000000..9bfcd41 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 Jan Sorgalla + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/admin/classes/domain/vendor/react/promise/README.md b/admin/classes/domain/vendor/react/promise/README.md new file mode 100644 index 0000000..0be869e --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/README.md @@ -0,0 +1,824 @@ +React/Promise +============= + +A lightweight implementation of +[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. + +[![Build Status](https://secure.travis-ci.org/reactphp/promise.png?branch=master)](http://travis-ci.org/reactphp/promise) + +Table of Contents +----------------- + +1. [Introduction](#introduction) +2. [Concepts](#concepts) + * [Deferred](#deferred) + * [Promise](#promise) +3. [API](#api) + * [Deferred](#deferred-1) + * [Deferred::promise()](#deferredpromise) + * [Deferred::resolve()](#deferredresolve) + * [Deferred::reject()](#deferredreject) + * [Deferred::notify()](#deferrednotify) + * [PromiseInterface](#promiseinterface) + * [PromiseInterface::then()](#promiseinterfacethen) + * [ExtendedPromiseInterface](#extendedpromiseinterface) + * [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone) + * [ExtendedPromiseInterface::otherwise()](#extendedpromiseinterfaceotherwise) + * [ExtendedPromiseInterface::always()](#extendedpromiseinterfacealways) + * [ExtendedPromiseInterface::progress()](#extendedpromiseinterfaceprogress) + * [CancellablePromiseInterface](#cancellablepromiseinterface) + * [CancellablePromiseInterface::cancel()](#cancellablepromiseinterfacecancel) + * [Promise](#promise-1) + * [FulfilledPromise](#fulfilledpromise) + * [RejectedPromise](#rejectedpromise) + * [LazyPromise](#lazypromise) + * [Functions](#functions) + * [resolve()](#resolve) + * [reject()](#reject) + * [all()](#all) + * [race()](#race) + * [any()](#any) + * [some()](#some) + * [map()](#map) + * [reduce()](#reduce) + * [PromisorInterface](#promisorinterface) +4. [Examples](#examples) + * [How to use Deferred](#how-to-use-deferred) + * [How promise forwarding works](#how-promise-forwarding-works) + * [Resolution forwarding](#resolution-forwarding) + * [Rejection forwarding](#rejection-forwarding) + * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding) + * [Progress event forwarding](#progress-event-forwarding) + * [done() vs. then()](#done-vs-then) +5. [Credits](#credits) +6. [License](#license) + +Introduction +------------ + +React/Promise is a library implementing +[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. + +It also provides several other useful promise-related concepts, such as joining +multiple promises and mapping and reducing collections of promises. + +If you've never heard about promises before, +[read this first](https://gist.github.com/3889970). + +Concepts +-------- + +### Deferred + +A **Deferred** represents a computation or unit of work that may not have +completed yet. Typically (but not always), that computation will be something +that executes asynchronously and completes at some point in the future. + +### Promise + +While a deferred represents the computation itself, a **Promise** represents +the result of that computation. Thus, each deferred has a promise that acts as +a placeholder for its actual result. + +API +--- + +### Deferred + +A deferred represents an operation whose resolution is pending. It has separate +promise and resolver parts. + +```php +$deferred = new React\Promise\Deferred(); + +$promise = $deferred->promise(); + +$deferred->resolve(mixed $value = null); +$deferred->reject(mixed $reason = null); +$deferred->notify(mixed $update = null); +``` + +The `promise` method returns the promise of the deferred. + +The `resolve` and `reject` methods control the state of the deferred. + +The `notify` method is for progress notification. + +The constructor of the `Deferred` accepts an optional `$canceller` argument. +See [Promise](#promise-1) for more information. + +#### Deferred::promise() + +```php +$promise = $deferred->promise(); +``` + +Returns the promise of the deferred, which you can hand out to others while +keeping the authority to modify its state to yourself. + +#### Deferred::resolve() + +```php +$deferred->resolve(mixed $value = null); +``` + +Resolves the promise returned by `promise()`. All consumers are notified by +having `$onFulfilled` (which they registered via `$promise->then()`) called with +`$value`. + +If `$value` itself is a promise, the promise will transition to the state of +this promise once it is resolved. + +#### Deferred::reject() + +```php +$deferred->reject(mixed $reason = null); +``` + +Rejects the promise returned by `promise()`, signalling that the deferred's +computation failed. +All consumers are notified by having `$onRejected` (which they registered via +`$promise->then()`) called with `$reason`. + +If `$reason` itself is a promise, the promise will be rejected with the outcome +of this promise regardless whether it fulfills or rejects. + +#### Deferred::notify() + +```php +$deferred->notify(mixed $update = null); +``` + +Triggers progress notifications, to indicate to consumers that the computation +is making progress toward its result. + +All consumers are notified by having `$onProgress` (which they registered via +`$promise->then()`) called with `$update`. + +### PromiseInterface + +The promise interface provides the common interface for all promise +implementations. + +A promise represents an eventual outcome, which is either fulfillment (success) +and an associated value, or rejection (failure) and an associated reason. + +Once in the fulfilled or rejected state, a promise becomes immutable. +Neither its state nor its result (or error) can be modified. + +#### Implementations + +* [Promise](#promise-1) +* [FulfilledPromise](#fulfilledpromise) +* [RejectedPromise](#rejectedpromise) +* [LazyPromise](#lazypromise) + +#### PromiseInterface::then() + +```php +$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null); +``` + +Transforms a promise's value by applying a function to the promise's fulfillment +or rejection value. Returns a new promise for the transformed result. + +The `then()` method registers new fulfilled, rejection and progress handlers +with a promise (all parameters are optional): + + * `$onFulfilled` will be invoked once the promise is fulfilled and passed + the result as the first argument. + * `$onRejected` will be invoked once the promise is rejected and passed the + reason as the first argument. + * `$onProgress` will be invoked whenever the producer of the promise + triggers progress notifications and passed a single argument (whatever it + wants) to indicate progress. + +It returns a new promise that will fulfill with the return value of either +`$onFulfilled` or `$onRejected`, whichever is called, or will reject with +the thrown exception if either throws. + +A promise makes the following guarantees about handlers registered in +the same call to `then()`: + + 1. Only one of `$onFulfilled` or `$onRejected` will be called, + never both. + 2. `$onFulfilled` and `$onRejected` will never be called more + than once. + 3. `$onProgress` may be called multiple times. + +#### See also + +* [resolve()](#resolve) - Creating a resolved promise +* [reject()](#reject) - Creating a rejected promise +* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone) +* [done() vs. then()](#done-vs-then) + +### ExtendedPromiseInterface + +The ExtendedPromiseInterface extends the PromiseInterface with useful shortcut +and utility methods which are not part of the Promises/A specification. + +#### Implementations + +* [Promise](#promise-1) +* [FulfilledPromise](#fulfilledpromise) +* [RejectedPromise](#rejectedpromise) +* [LazyPromise](#lazypromise) + +#### ExtendedPromiseInterface::done() + +```php +$promise->done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null); +``` + +Consumes the promise's ultimate value if the promise fulfills, or handles the +ultimate error. + +It will cause a fatal error if either `$onFulfilled` or `$onRejected` throw or +return a rejected promise. + +Since the purpose of `done()` is consumption rather than transformation, +`done()` always returns `null`. + +#### See also + +* [PromiseInterface::then()](#promiseinterfacethen) +* [done() vs. then()](#done-vs-then) + +#### ExtendedPromiseInterface::otherwise() + +```php +$promise->otherwise(callable $onRejected); +``` + +Registers a rejection handler for promise. It is a shortcut for: + +```php +$promise->then(null, $onRejected); +``` + +Additionally, you can type hint the `$reason` argument of `$onRejected` to catch +only specific errors. + +```php +$promise + ->otherwise(function (\RuntimeException $reason) { + // Only catch \RuntimeException instances + // All other types of errors will propagate automatically + }) + ->otherwise(function ($reason) { + // Catch other errors + )}; +``` + +#### ExtendedPromiseInterface::always() + +```php +$newPromise = $promise->always(callable $onFulfilledOrRejected); +``` + +Allows you to execute "cleanup" type tasks in a promise chain. + +It arranges for `$onFulfilledOrRejected` to be called, with no arguments, +when the promise is either fulfilled or rejected. + +* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully, + `$newPromise` will fulfill with the same value as `$promise`. +* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a + rejected promise, `$newPromise` will reject with the thrown exception or + rejected promise's reason. +* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully, + `$newPromise` will reject with the same reason as `$promise`. +* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a + rejected promise, `$newPromise` will reject with the thrown exception or + rejected promise's reason. + +`always()` behaves similarly to the synchronous finally statement. When combined +with `otherwise()`, `always()` allows you to write code that is similar to the familiar +synchronous catch/finally pair. + +Consider the following synchronous code: + +```php +try { + return doSomething(); +} catch(\Exception $e) { + return handleError($e); +} finally { + cleanup(); +} +``` + +Similar asynchronous code (with `doSomething()` that returns a promise) can be +written: + +```php +return doSomething() + ->otherwise('handleError') + ->always('cleanup'); +``` + +#### ExtendedPromiseInterface::progress() + +```php +$promise->progress(callable $onProgress); +``` + +Registers a handler for progress updates from promise. It is a shortcut for: + +```php +$promise->then(null, null, $onProgress); +``` + +### CancellablePromiseInterface + +A cancellable promise provides a mechanism for consumers to notify the creator +of the promise that they are not longer interested in the result of an +operation. + +#### CancellablePromiseInterface::cancel() + +``` php +$promise->cancel(); +``` + +The `cancel()` method notifies the creator of the promise that there is no +further interest in the results of the operation. + +Once a promise is settled (either fulfilled or rejected), calling `cancel()` on +a promise has no effect. + +#### Implementations + +* [Promise](#promise-1) +* [FulfilledPromise](#fulfilledpromise) +* [RejectedPromise](#rejectedpromise) +* [LazyPromise](#lazypromise) + +### Promise + +Creates a promise whose state is controlled by the functions passed to +`$resolver`. + +```php +$resolver = function (callable $resolve, callable $reject, callable $notify) { + // Do some work, possibly asynchronously, and then + // resolve or reject. You can notify of progress events + // along the way if you want/need. + + $resolve($awesomeResult); + // or $resolve($anotherPromise); + // or $reject($nastyError); + // or $notify($progressNotification); +}; + +$canceller = function (callable $resolve, callable $reject, callable $progress) { + // Cancel/abort any running operations like network connections, streams etc. + + $reject(new \Exception('Promise cancelled')); +}; + +$promise = new React\Promise\Promise($resolver, $canceller); +``` + +The promise constructor receives a resolver function and an optional canceller +function which both will be called with 3 arguments: + + * `$resolve($value)` - Primary function that seals the fate of the + returned promise. Accepts either a non-promise value, or another promise. + When called with a non-promise value, fulfills promise with that value. + When called with another promise, e.g. `$resolve($otherPromise)`, promise's + fate will be equivalent to that of `$otherPromise`. + * `$reject($reason)` - Function that rejects the promise. + * `$notify($update)` - Function that issues progress events for the promise. + +If the resolver or canceller throw an exception, the promise will be rejected +with that thrown exception as the rejection reason. + +The resolver function will be called immediately, the canceller function only +once all consumers called the `cancel()` method of the promise. + +### FulfilledPromise + +Creates a already fulfilled promise. + +```php +$promise = React\Promise\FulfilledPromise($value); +``` + +Note, that `$value` **cannot** be a promise. It's recommended to use +[resolve()](#resolve) for creating resolved promises. + +### RejectedPromise + +Creates a already rejected promise. + +```php +$promise = React\Promise\RejectedPromise($reason); +``` + +Note, that `$reason` **cannot** be a promise. It's recommended to use +[reject()](#reject) for creating rejected promises. + +### LazyPromise + +Creates a promise which will be lazily initialized by `$factory` once a consumer +calls the `then()` method. + +```php +$factory = function () { + $deferred = new React\Promise\Deferred(); + + // Do some heavy stuff here and resolve the deferred once completed + + return $deferred->promise(); +}; + +$promise = React\Promise\LazyPromise($factory); + +// $factory will only be executed once we call then() +$promise->then(function ($value) { +}); +``` + +### Functions + +Useful functions for creating, joining, mapping and reducing collections of +promises. + +#### resolve() + +```php +$promise = React\Promise\resolve(mixed $promiseOrValue); +``` + +Creates a promise for the supplied `$promiseOrValue`. + +If `$promiseOrValue` is a value, it will be the resolution value of the +returned promise. + +If `$promiseOrValue` is a promise, it will simply be returned. + +Note: The promise returned is always a promise implementing +[ExtendedPromiseInterface](#extendedpromiseinterface). If you pass in a custom +promise which only implements [PromiseInterface](#promiseinterface), this +promise will be assimilated to a extended promise following `$promiseOrValue`. + +#### reject() + +```php +$promise = React\Promise\reject(mixed $promiseOrValue); +``` + +Creates a rejected promise for the supplied `$promiseOrValue`. + +If `$promiseOrValue` is a value, it will be the rejection value of the +returned promise. + +If `$promiseOrValue` is a promise, its completion value will be the rejected +value of the returned promise. + +This can be useful in situations where you need to reject a promise without +throwing an exception. For example, it allows you to propagate a rejection with +the value of another promise. + +#### all() + +```php +$promise = React\Promise\all(array|React\Promise\PromiseInterface $promisesOrValues); +``` + +Returns a promise that will resolve only once all the items in +`$promisesOrValues` have resolved. The resolution value of the returned promise +will be an array containing the resolution values of each of the items in +`$promisesOrValues`. + +#### race() + +```php +$promise = React\Promise\race(array|React\Promise\PromiseInterface $promisesOrValues); +``` + +Initiates a competitive race that allows one winner. Returns a promise which is +resolved in the same way the first settled promise resolves. + +#### any() + +```php +$promise = React\Promise\any(array|React\Promise\PromiseInterface $promisesOrValues); +``` + +Returns a promise that will resolve when any one of the items in +`$promisesOrValues` resolves. The resolution value of the returned promise +will be the resolution value of the triggering item. + +The returned promise will only reject if *all* items in `$promisesOrValues` are +rejected. The rejection value will be an array of all rejection reasons. + +#### some() + +```php +$promise = React\Promise\some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany); +``` + +Returns a promise that will resolve when `$howMany` of the supplied items in +`$promisesOrValues` resolve. The resolution value of the returned promise +will be an array of length `$howMany` containing the resolution values of the +triggering items. + +The returned promise will reject if it becomes impossible for `$howMany` items +to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items +reject). The rejection value will be an array of +`(count($promisesOrValues) - $howMany) + 1` rejection reasons. + +#### map() + +```php +$promise = React\Promise\map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc); +``` + +Traditional map function, similar to `array_map()`, but allows input to contain +promises and/or values, and `$mapFunc` may return either a value or a promise. + +The map function receives each item as argument, where item is a fully resolved +value of a promise or value in `$promisesOrValues`. + +#### reduce() + +```php +$promise = React\Promise\reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null); +``` + +Traditional reduce function, similar to `array_reduce()`, but input may contain +promises and/or values, and `$reduceFunc` may return either a value or a +promise, *and* `$initialValue` may be a promise or a value for the starting +value. + +### PromisorInterface + +The `React\Promise\PromisorInterface` provides a common interface for objects +that provide a promise. `React\Promise\Deferred` implements it, but since it +is part of the public API anyone can implement it. + +Examples +-------- + +### How to use Deferred + +```php +function getAwesomeResultPromise() +{ + $deferred = new React\Promise\Deferred(); + + // Execute a Node.js-style function using the callback pattern + computeAwesomeResultAsynchronously(function ($error, $result) use ($deferred) { + if ($error) { + $deferred->reject($error); + } else { + $deferred->resolve($result); + } + }); + + // Return the promise + return $deferred->promise(); +} + +getAwesomeResultPromise() + ->then( + function ($value) { + // Deferred resolved, do something with $value + }, + function ($reason) { + // Deferred rejected, do something with $reason + }, + function ($update) { + // Progress notification triggered, do something with $update + } + ); +``` + +### How promise forwarding works + +A few simple examples to show how the mechanics of Promises/A forwarding works. +These examples are contrived, of course, and in real usage, promise chains will +typically be spread across several function calls, or even several levels of +your application architecture. + +#### Resolution forwarding + +Resolved promises forward resolution values to the next promise. +The first promise, `$deferred->promise()`, will resolve with the value passed +to `$deferred->resolve()` below. + +Each call to `then()` returns a new promise that will resolve with the return +value of the previous handler. This creates a promise "pipeline". + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->then(function ($x) { + // $x will be the value passed to $deferred->resolve() below + // and returns a *new promise* for $x + 1 + return $x + 1; + }) + ->then(function ($x) { + // $x === 2 + // This handler receives the return value of the + // previous handler. + return $x + 1; + }) + ->then(function ($x) { + // $x === 3 + // This handler receives the return value of the + // previous handler. + return $x + 1; + }) + ->then(function ($x) { + // $x === 4 + // This handler receives the return value of the + // previous handler. + echo 'Resolve ' . $x; + }); + +$deferred->resolve(1); // Prints "Resolve 4" +``` + +#### Rejection forwarding + +Rejected promises behave similarly, and also work similarly to try/catch: +When you catch an exception, you must rethrow for it to propagate. + +Similarly, when you handle a rejected promise, to propagate the rejection, +"rethrow" it by either returning a rejected promise, or actually throwing +(since promise translates thrown exceptions into rejections) + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->then(function ($x) { + throw new \Exception($x + 1); + }) + ->then(null, function (\Exception $x) { + // Propagate the rejection + throw $x; + }) + ->then(null, function (\Exception $x) { + // Can also propagate by returning another rejection + return React\Promise\reject((integer) $x->getMessage() + 1); + }) + ->then(null, function ($x) { + echo 'Reject ' . $x; // 3 + }); + +$deferred->resolve(1); // Prints "Reject 3" +``` + +#### Mixed resolution and rejection forwarding + +Just like try/catch, you can choose to propagate or not. Mixing resolutions and +rejections will still forward handler results in a predictable way. + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->then(function ($x) { + return $x + 1; + }) + ->then(function ($x) { + throw \Exception($x + 1); + }) + ->then(null, function (\Exception $x) { + // Handle the rejection, and don't propagate. + // This is like catch without a rethrow + return (integer) $x->getMessage() + 1; + }) + ->then(function ($x) { + echo 'Mixed ' . $x; // 4 + }); + +$deferred->resolve(1); // Prints "Mixed 4" +``` + +#### Progress event forwarding + +In the same way as resolution and rejection handlers, your progress handler +**MUST** return a progress event to be propagated to the next link in the chain. +If you return nothing, `null` will be propagated. + +Also in the same way as resolutions and rejections, if you don't register a +progress handler, the update will be propagated through. + +If your progress handler throws an exception, the exception will be propagated +to the next link in the chain. The best thing to do is to ensure your progress +handlers do not throw exceptions. + +This gives you the opportunity to transform progress events at each step in the +chain so that they are meaningful to the next step. It also allows you to choose +not to transform them, and simply let them propagate untransformed, by not +registering a progress handler. + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->progress(function ($update) { + return $update + 1; + }) + ->progress(function ($update) { + echo 'Progress ' . $update; // 2 + }); + +$deferred->notify(1); // Prints "Progress 2" +``` + +### done() vs. then() + +The golden rule is: + + Either return your promise, or call done() on it. + +At a first glance, `then()` and `done()` seem very similar. However, there are +important distinctions. + +The intent of `then()` is to transform a promise's value and to pass or return +a new promise for the transformed value along to other parts of your code. + +The intent of `done()` is to consume a promise's value, transferring +responsibility for the value to your code. + +In addition to transforming a value, `then()` allows you to recover from, or +propagate intermediate errors. Any errors that are not handled will be caught +by the promise machinery and used to reject the promise returned by `then()`. + +Calling `done()` transfers all responsibility for errors to your code. If an +error (either a thrown exception or returned rejection) escapes the +`$onFulfilled` or `$onRejected` callbacks you provide to done, it will be +rethrown in an uncatchable way causing a fatal error. + +```php +function getJsonResult() +{ + return queryApi() + ->then( + // Transform API results to an object + function ($jsonResultString) { + return json_decode($jsonResultString); + }, + // Transform API errors to an exception + function ($jsonErrorString) { + $object = json_decode($jsonErrorString); + throw new ApiErrorException($object->errorMessage); + } + ); +} + +// Here we provide no rejection handler. +// If the promise returned has been rejected, +// a React\Promise\UnhandledRejectionException will be thrown +getJsonResult() + ->done( + // Consume transformed object + function ($jsonResultObject) { + // Do something with $jsonObject + } + ); + +// Here we provide a rejection handler which will either throw while debugging +// or log the exception. +getJsonResult() + ->done( + function ($jsonObject) { + // Do something with $jsonObject + }, + function (ApiErrorException $exception) { + if (isDebug()) { + throw $e; + } else { + logException($exception); + } + } + ); +``` + +Note that if a rejection value is not an instance of `\Exception`, it will be +wrapped in an exception of the type `React\Promise\UnhandledRejectionException`. + +You can get the original rejection reason by calling `$exception->getReason()`. + +Credits +------- + +React/Promise is a port of [when.js](https://github.com/cujojs/when) +by [Brian Cavalier](https://github.com/briancavalier). + +Also, large parts of the documentation have been ported from the when.js +[Wiki](https://github.com/cujojs/when/wiki) and the +[API docs](https://github.com/cujojs/when/blob/master/docs/api.md). + +License +------- + +React/Promise is released under the [MIT](https://github.com/reactphp/promise/blob/master/LICENSE) license. diff --git a/admin/classes/domain/vendor/react/promise/composer.json b/admin/classes/domain/vendor/react/promise/composer.json new file mode 100644 index 0000000..c428b7b --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/composer.json @@ -0,0 +1,22 @@ +{ + "name": "react/promise", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "license": "MIT", + "authors": [ + {"name": "Jan Sorgalla", "email": "jsorgalla@googlemail.com"} + ], + "require": { + "php": ">=5.4.0" + }, + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/admin/classes/domain/vendor/react/promise/phpunit.xml.dist b/admin/classes/domain/vendor/react/promise/phpunit.xml.dist new file mode 100644 index 0000000..0200d46 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/ + + + + + + ./src/ + + + diff --git a/admin/classes/domain/vendor/react/promise/src/CancellablePromiseInterface.php b/admin/classes/domain/vendor/react/promise/src/CancellablePromiseInterface.php new file mode 100644 index 0000000..896db2d --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/src/CancellablePromiseInterface.php @@ -0,0 +1,11 @@ +canceller = $canceller; + } + + public function promise() + { + if (null === $this->promise) { + $this->promise = new Promise(function ($resolve, $reject, $notify) { + $this->resolveCallback = $resolve; + $this->rejectCallback = $reject; + $this->notifyCallback = $notify; + }, $this->canceller); + } + + return $this->promise; + } + + public function resolve($value = null) + { + $this->promise(); + + call_user_func($this->resolveCallback, $value); + } + + public function reject($reason = null) + { + $this->promise(); + + call_user_func($this->rejectCallback, $reason); + } + + public function notify($update = null) + { + $this->promise(); + + call_user_func($this->notifyCallback, $update); + } + + /** + * @deprecated 2.2.0 + * @see Deferred::notify() + */ + public function progress($update = null) + { + $this->notify($update); + } +} diff --git a/admin/classes/domain/vendor/react/promise/src/ExtendedPromiseInterface.php b/admin/classes/domain/vendor/react/promise/src/ExtendedPromiseInterface.php new file mode 100644 index 0000000..9cb6435 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/src/ExtendedPromiseInterface.php @@ -0,0 +1,26 @@ +value = $value; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + try { + $value = $this->value; + + if (null !== $onFulfilled) { + $value = $onFulfilled($value); + } + + return resolve($value); + } catch (\Exception $exception) { + return new RejectedPromise($exception); + } + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onFulfilled) { + return; + } + + $result = $onFulfilled($this->value); + + if ($result instanceof ExtendedPromiseInterface) { + $result->done(); + } + } + + public function otherwise(callable $onRejected) + { + return new FulfilledPromise($this->value); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->then(function ($value) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($value) { + return $value; + }); + }); + } + + public function progress(callable $onProgress) + { + return new FulfilledPromise($this->value); + } + + public function cancel() + { + } +} diff --git a/admin/classes/domain/vendor/react/promise/src/LazyPromise.php b/admin/classes/domain/vendor/react/promise/src/LazyPromise.php new file mode 100644 index 0000000..68524a9 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/src/LazyPromise.php @@ -0,0 +1,58 @@ +factory = $factory; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return $this->promise()->then($onFulfilled, $onRejected, $onProgress); + } + + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return $this->promise()->done($onFulfilled, $onRejected, $onProgress); + } + + public function otherwise(callable $onRejected) + { + return $this->promise()->otherwise($onRejected); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->promise()->always($onFulfilledOrRejected); + } + + public function progress(callable $onProgress) + { + return $this->promise()->progress($onProgress); + } + + public function cancel() + { + return $this->promise()->cancel(); + } + + private function promise() + { + if (null === $this->promise) { + try { + $this->promise = resolve(call_user_func($this->factory)); + } catch (\Exception $exception) { + $this->promise = new RejectedPromise($exception); + } + } + + return $this->promise; + } +} diff --git a/admin/classes/domain/vendor/react/promise/src/Promise.php b/admin/classes/domain/vendor/react/promise/src/Promise.php new file mode 100644 index 0000000..b2635bf --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/src/Promise.php @@ -0,0 +1,182 @@ +canceller = $canceller; + $this->call($resolver); + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null !== $this->result) { + return $this->result->then($onFulfilled, $onRejected, $onProgress); + } + + if (null === $this->canceller) { + return new static($this->resolver($onFulfilled, $onRejected, $onProgress)); + } + + $this->requiredCancelRequests++; + + return new static($this->resolver($onFulfilled, $onRejected, $onProgress), function ($resolve, $reject, $progress) { + if (++$this->cancelRequests < $this->requiredCancelRequests) { + return; + } + + $this->cancel(); + }); + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null !== $this->result) { + return $this->result->done($onFulfilled, $onRejected, $onProgress); + } + + $this->handlers[] = function (PromiseInterface $promise) use ($onFulfilled, $onRejected) { + $promise + ->done($onFulfilled, $onRejected); + }; + + if ($onProgress) { + $this->progressHandlers[] = $onProgress; + } + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, function ($reason) use ($onRejected) { + if (!_checkTypehint($onRejected, $reason)) { + return new RejectedPromise($reason); + } + + return $onRejected($reason); + }); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->then(function ($value) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($value) { + return $value; + }); + }, function ($reason) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($reason) { + return new RejectedPromise($reason); + }); + }); + } + + public function progress(callable $onProgress) + { + return $this->then(null, null, $onProgress); + } + + public function cancel() + { + if (null === $this->canceller || null !== $this->result) { + return; + } + + $this->call($this->canceller); + } + + private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) { + if ($onProgress) { + $progressHandler = function ($update) use ($notify, $onProgress) { + try { + $notify($onProgress($update)); + } catch (\Exception $e) { + $notify($e); + } + }; + } else { + $progressHandler = $notify; + } + + $this->handlers[] = function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) { + $promise + ->then($onFulfilled, $onRejected) + ->done($resolve, $reject, $progressHandler); + }; + + $this->progressHandlers[] = $progressHandler; + }; + } + + private function resolve($value = null) + { + if (null !== $this->result) { + return; + } + + $this->settle(resolve($value)); + } + + private function reject($reason = null) + { + if (null !== $this->result) { + return; + } + + $this->settle(reject($reason)); + } + + private function notify($update = null) + { + if (null !== $this->result) { + return; + } + + foreach ($this->progressHandlers as $handler) { + $handler($update); + } + } + + private function settle(ExtendedPromiseInterface $promise) + { + $result = $promise; + + foreach ($this->handlers as $handler) { + $handler($result); + } + + $this->progressHandlers = $this->handlers = []; + + $this->result = $result; + } + + private function call(callable $callback) + { + try { + $callback( + function ($value = null) { + $this->resolve($value); + }, + function ($reason = null) { + $this->reject($reason); + }, + function ($update = null) { + $this->notify($update); + } + ); + } catch (\Exception $e) { + $this->reject($e); + } + } +} diff --git a/admin/classes/domain/vendor/react/promise/src/PromiseInterface.php b/admin/classes/domain/vendor/react/promise/src/PromiseInterface.php new file mode 100644 index 0000000..d80d114 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/src/PromiseInterface.php @@ -0,0 +1,11 @@ +reason = $reason; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + try { + if (null === $onRejected) { + return new RejectedPromise($this->reason); + } + + return resolve($onRejected($this->reason)); + } catch (\Exception $exception) { + return new RejectedPromise($exception); + } + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onRejected) { + throw UnhandledRejectionException::resolve($this->reason); + } + + $result = $onRejected($this->reason); + + if ($result instanceof self) { + throw UnhandledRejectionException::resolve($result->reason); + } + + if ($result instanceof ExtendedPromiseInterface) { + $result->done(); + } + } + + public function otherwise(callable $onRejected) + { + if (!_checkTypehint($onRejected, $this->reason)) { + return new RejectedPromise($this->reason); + } + + return $this->then(null, $onRejected); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->then(null, function ($reason) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($reason) { + return new RejectedPromise($reason); + }); + }); + } + + public function progress(callable $onProgress) + { + return new RejectedPromise($this->reason); + } + + public function cancel() + { + } +} diff --git a/admin/classes/domain/vendor/react/promise/src/UnhandledRejectionException.php b/admin/classes/domain/vendor/react/promise/src/UnhandledRejectionException.php new file mode 100644 index 0000000..ed166b3 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/src/UnhandledRejectionException.php @@ -0,0 +1,31 @@ +reason = $reason; + + $message = sprintf('Unhandled Rejection: %s', json_encode($reason)); + + parent::__construct($message, 0); + } + + public function getReason() + { + return $this->reason; + } +} diff --git a/admin/classes/domain/vendor/react/promise/src/functions.php b/admin/classes/domain/vendor/react/promise/src/functions.php new file mode 100644 index 0000000..2eae605 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/src/functions.php @@ -0,0 +1,196 @@ +then($resolve, $reject, $notify); + }); +} + +function reject($promiseOrValue = null) +{ + if ($promiseOrValue instanceof PromiseInterface) { + return resolve($promiseOrValue)->then(function ($value) { + return new RejectedPromise($value); + }); + } + + return new RejectedPromise($promiseOrValue); +} + +function all($promisesOrValues) +{ + return map($promisesOrValues, function ($val) { + return $val; + }); +} + +function race($promisesOrValues) +{ + return resolve($promisesOrValues) + ->then(function ($array) { + if (!is_array($array) || !$array) { + return resolve(); + } + + return new Promise(function ($resolve, $reject, $notify) use ($array) { + foreach ($array as $promiseOrValue) { + resolve($promiseOrValue) + ->done($resolve, $reject, $notify); + } + }); + }); +} + +function any($promisesOrValues) +{ + return some($promisesOrValues, 1) + ->then(function ($val) { + return array_shift($val); + }); +} + +function some($promisesOrValues, $howMany) +{ + return resolve($promisesOrValues) + ->then(function ($array) use ($howMany) { + if (!is_array($array) || !$array || $howMany < 1) { + return resolve([]); + } + + return new Promise(function ($resolve, $reject, $notify) use ($array, $howMany) { + $len = count($array); + $toResolve = min($howMany, $len); + $toReject = ($len - $toResolve) + 1; + $values = []; + $reasons = []; + + foreach ($array as $i => $promiseOrValue) { + $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) { + if ($toResolve < 1 || $toReject < 1) { + return; + } + + $values[$i] = $val; + + if (0 === --$toResolve) { + $resolve($values); + } + }; + + $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) { + if ($toResolve < 1 || $toReject < 1) { + return; + } + + $reasons[$i] = $reason; + + if (0 === --$toReject) { + $reject($reasons); + } + }; + + resolve($promiseOrValue) + ->done($fulfiller, $rejecter, $notify); + } + }); + }); +} + +function map($promisesOrValues, callable $mapFunc) +{ + return resolve($promisesOrValues) + ->then(function ($array) use ($mapFunc) { + if (!is_array($array) || !$array) { + return resolve([]); + } + + return new Promise(function ($resolve, $reject, $notify) use ($array, $mapFunc) { + $toResolve = count($array); + $values = []; + + foreach ($array as $i => $promiseOrValue) { + resolve($promiseOrValue) + ->then($mapFunc) + ->done( + function ($mapped) use ($i, &$values, &$toResolve, $resolve) { + $values[$i] = $mapped; + + if (0 === --$toResolve) { + $resolve($values); + } + }, + $reject, + $notify + ); + } + }); + }); +} + +function reduce($promisesOrValues, callable $reduceFunc , $initialValue = null) +{ + return resolve($promisesOrValues) + ->then(function ($array) use ($reduceFunc, $initialValue) { + if (!is_array($array)) { + $array = []; + } + + $total = count($array); + $i = 0; + + // Wrap the supplied $reduceFunc with one that handles promises and then + // delegates to the supplied. + $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $total, &$i) { + return resolve($current) + ->then(function ($c) use ($reduceFunc, $total, &$i, $val) { + return resolve($val) + ->then(function ($value) use ($reduceFunc, $total, &$i, $c) { + return $reduceFunc($c, $value, $i++, $total); + }); + }); + }; + + return array_reduce($array, $wrappedReduceFunc, $initialValue); + }); +} + +// Internal functions +function _checkTypehint(callable $callback, $object) +{ + if (!is_object($object)) { + return true; + } + + if (is_array($callback)) { + $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); + } elseif (is_object($callback) && !$callback instanceof \Closure) { + $callbackReflection = new \ReflectionMethod($callback, '__invoke'); + } else { + $callbackReflection = new \ReflectionFunction($callback); + } + + $parameters = $callbackReflection->getParameters(); + + if (!isset($parameters[0])) { + return true; + } + + $expectedException = $parameters[0]; + + if (!$expectedException->getClass()) { + return true; + } + + return $expectedException->getClass()->isInstance($object); +} diff --git a/admin/classes/domain/vendor/react/promise/src/functions_include.php b/admin/classes/domain/vendor/react/promise/src/functions_include.php new file mode 100644 index 0000000..c71decb --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/src/functions_include.php @@ -0,0 +1,5 @@ + [$d, 'promise'], + 'resolve' => [$d, 'resolve'], + 'reject' => [$d, 'reject'], + 'notify' => [$d, 'progress'], + 'settle' => [$d, 'resolve'], + ]); + } + + /** @test */ + public function progressIsAnAliasForNotify() + { + $deferred = new Deferred(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $deferred->promise() + ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock); + + $deferred->progress($sentinel); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FulfilledPromiseTest.php b/admin/classes/domain/vendor/react/promise/tests/FulfilledPromiseTest.php new file mode 100644 index 0000000..97fc8f6 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FulfilledPromiseTest.php @@ -0,0 +1,50 @@ + function () use (&$promise) { + if (!$promise) { + throw new \LogicException('FulfilledPromise must be resolved before obtaining the promise'); + } + + return $promise; + }, + 'resolve' => function ($value = null) use (&$promise) { + if (!$promise) { + $promise = new FulfilledPromise($value); + } + }, + 'reject' => function () { + throw new \LogicException('You cannot call reject() for React\Promise\FulfilledPromise'); + }, + 'notify' => function () { + // no-op + }, + 'settle' => function ($value = null) use (&$promise) { + if (!$promise) { + $promise = new FulfilledPromise($value); + } + }, + ]); + } + + /** @test */ + public function shouldThrowExceptionIfConstructedWithAPromise() + { + $this->setExpectedException('\InvalidArgumentException'); + + return new FulfilledPromise(new FulfilledPromise()); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FunctionAllTest.php b/admin/classes/domain/vendor/react/promise/tests/FunctionAllTest.php new file mode 100644 index 0000000..fcd0a5d --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FunctionAllTest.php @@ -0,0 +1,97 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + all([]) + ->then($mock); + } + + /** @test */ + public function shouldResolveValuesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + all([1, 2, 3]) + ->then($mock); + } + + /** @test */ + public function shouldResolvePromisesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + all([resolve(1), resolve(2), resolve(3)]) + ->then($mock); + } + + /** @test */ + public function shouldResolveSparseArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([null, 1, null, 1, 1])); + + all([null, 1, null, 1, 1]) + ->then($mock); + } + + /** @test */ + public function shouldRejectIfAnyInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + all([resolve(1), reject(2), resolve(3)]) + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + all(resolve([1, 2, 3])) + ->then($mock); + } + + /** @test */ + public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + all(resolve(1)) + ->then($mock); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FunctionAnyTest.php b/admin/classes/domain/vendor/react/promise/tests/FunctionAnyTest.php new file mode 100644 index 0000000..bf8a0db --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FunctionAnyTest.php @@ -0,0 +1,116 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + any([]) + ->then($mock); + } + + /** @test */ + public function shouldResolveWithAnInputValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + any([1, 2, 3]) + ->then($mock); + } + + /** @test */ + public function shouldResolveWithAPromisedInputValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + any([resolve(1), resolve(2), resolve(3)]) + ->then($mock); + } + + /** @test */ + public function shouldRejectWithAllRejectedInputValuesIfAllInputsAreRejected() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([0 => 1, 1 => 2, 2 => 3])); + + any([reject(1), reject(2), reject(3)]) + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldResolveWhenFirstInputPromiseResolves() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + any([resolve(1), reject(2), reject(3)]) + ->then($mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + any(resolve([1, 2, 3])) + ->then($mock); + } + + /** @test */ + public function shouldResolveToNullArrayWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + any(resolve(1)) + ->then($mock); + } + + /** @test */ + public function shouldNotRelyOnArryIndexesWhenUnwrappingToASingleResolutionValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $d1 = new Deferred(); + $d2 = new Deferred(); + + any(['abc' => $d1->promise(), 1 => $d2->promise()]) + ->then($mock); + + $d2->resolve(2); + $d1->resolve(1); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FunctionCheckTypehintTest.php b/admin/classes/domain/vendor/react/promise/tests/FunctionCheckTypehintTest.php new file mode 100644 index 0000000..8449bc1 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FunctionCheckTypehintTest.php @@ -0,0 +1,118 @@ +assertTrue(_checkTypehint(function (\InvalidArgumentException $e) { + }, new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint(function (\InvalidArgumentException $e) { + }, new \Exception())); + } + + /** @test */ + public function shouldAcceptFunctionStringCallbackWithTypehint() + { + $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithTypehint', new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint('React\Promise\testCallbackWithTypehint', new \Exception())); + } + + /** @test */ + public function shouldAcceptInvokableObjectCallbackWithTypehint() + { + $this->assertTrue(_checkTypehint(new TestCallbackWithTypehintClass(), new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint(new TestCallbackWithTypehintClass(), new \Exception())); + } + + /** @test */ + public function shouldAcceptObjectMethodCallbackWithTypehint() + { + $this->assertTrue(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \Exception())); + } + + /** @test */ + public function shouldAcceptStaticClassCallbackWithTypehint() + { + $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \Exception())); + } + + /** @test */ + public function shouldAcceptClosureCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint(function (\InvalidArgumentException $e) { + }, new \InvalidArgumentException())); + } + + /** @test */ + public function shouldAcceptFunctionStringCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithoutTypehint', new \InvalidArgumentException())); + } + + /** @test */ + public function shouldAcceptInvokableObjectCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint(new TestCallbackWithoutTypehintClass(), new \InvalidArgumentException())); + } + + /** @test */ + public function shouldAcceptObjectMethodCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint([new TestCallbackWithoutTypehintClass(), 'testCallback'], new \InvalidArgumentException())); + } + + /** @test */ + public function shouldAcceptStaticClassCallbackWithoutTypehint() + { + $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithoutTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException())); + } +} + +function testCallbackWithTypehint(\InvalidArgumentException $e) +{ +} + +function testCallbackWithoutTypehint() +{ +} + +class TestCallbackWithTypehintClass +{ + public function __invoke(\InvalidArgumentException $e) + { + + } + + public function testCallback(\InvalidArgumentException $e) + { + + } + + public static function testCallbackStatic(\InvalidArgumentException $e) + { + + } +} + +class TestCallbackWithoutTypehintClass +{ + public function __invoke() + { + + } + + public function testCallback() + { + + } + + public static function testCallbackStatic() + { + + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FunctionMapTest.php b/admin/classes/domain/vendor/react/promise/tests/FunctionMapTest.php new file mode 100644 index 0000000..b8bf3a8 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FunctionMapTest.php @@ -0,0 +1,125 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + [1, 2, 3], + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldMapInputPromisesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + [resolve(1), resolve(2), resolve(3)], + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldMapMixedInputArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + [1, resolve(2), 3], + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldMapInputWhenMapperReturnsAPromise() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + [1, 2, 3], + $this->promiseMapper() + )->then($mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + map( + resolve([1, resolve(2), 3]), + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + map( + resolve(1), + $this->mapper() + )->then($mock); + } + + /** @test */ + public function shouldRejectWhenInputContainsRejection() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + map( + [resolve(1), reject(2), resolve(3)], + $this->mapper() + )->then($this->expectCallableNever(), $mock); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FunctionRaceTest.php b/admin/classes/domain/vendor/react/promise/tests/FunctionRaceTest.php new file mode 100644 index 0000000..553220c --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FunctionRaceTest.php @@ -0,0 +1,122 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + [] + )->then($mock); + } + + /** @test */ + public function shouldResolveValuesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + race( + [1, 2, 3] + )->then($mock); + } + + /** @test */ + public function shouldResolvePromisesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $d1 = new Deferred(); + $d2 = new Deferred(); + $d3 = new Deferred(); + + race( + [$d1->promise(), $d2->promise(), $d3->promise()] + )->then($mock); + + $d2->resolve(2); + + $d1->resolve(1); + $d3->resolve(3); + } + + /** @test */ + public function shouldResolveSparseArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + [null, 1, null, 2, 3] + )->then($mock); + } + + /** @test */ + public function shouldRejectIfFirstSettledPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $d1 = new Deferred(); + $d2 = new Deferred(); + $d3 = new Deferred(); + + race( + [$d1->promise(), $d2->promise(), $d3->promise()] + )->then($this->expectCallableNever(), $mock); + + $d2->reject(2); + + $d1->resolve(1); + $d3->resolve(3); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + race( + resolve([1, 2, 3]) + )->then($mock); + } + + /** @test */ + public function shouldResolveToNullWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + resolve(1) + )->then($mock); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FunctionReduceTest.php b/admin/classes/domain/vendor/react/promise/tests/FunctionReduceTest.php new file mode 100644 index 0000000..715e847 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FunctionReduceTest.php @@ -0,0 +1,290 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(6)); + + reduce( + [1, 2, 3], + $this->plus() + )->then($mock); + } + + /** @test */ + public function shouldReduceValuesWithInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(7)); + + reduce( + [1, 2, 3], + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldReduceValuesWithInitialPromise() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(7)); + + reduce( + [1, 2, 3], + $this->plus(), + resolve(1) + )->then($mock); + } + + /** @test */ + public function shouldReducePromisedValuesWithoutInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(6)); + + reduce( + [resolve(1), resolve(2), resolve(3)], + $this->plus() + )->then($mock); + } + + /** @test */ + public function shouldReducePromisedValuesWithInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(7)); + + reduce( + [resolve(1), resolve(2), resolve(3)], + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldReducePromisedValuesWithInitialPromise() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(7)); + + reduce( + [resolve(1), resolve(2), resolve(3)], + $this->plus(), + resolve(1) + )->then($mock); + } + + /** @test */ + public function shouldReduceEmptyInputWithInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + reduce( + [], + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldReduceEmptyInputWithInitialPromise() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + reduce( + [], + $this->plus(), + resolve(1) + )->then($mock); + } + + /** @test */ + public function shouldRejectWhenInputContainsRejection() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + reduce( + [resolve(1), reject(2), resolve(3)], + $this->plus(), + resolve(1) + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldResolveWithNullWhenInputIsEmptyAndNoInitialValueOrPromiseProvided() + { + // Note: this is different from when.js's behavior! + // In when.reduce(), this rejects with a TypeError exception (following + // JavaScript's [].reduce behavior. + // We're following PHP's array_reduce behavior and resolve with NULL. + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + reduce( + [], + $this->plus() + )->then($mock); + } + + /** @test */ + public function shouldAllowSparseArrayInputWithoutInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(3)); + + reduce( + [null, null, 1, null, 1, 1], + $this->plus() + )->then($mock); + } + + /** @test */ + public function shouldAllowSparseArrayInputWithInitialValue() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(4)); + + reduce( + [null, null, 1, null, 1, 1], + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldReduceInInputOrder() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('123')); + + reduce( + [1, 2, 3], + $this->append(), + '' + )->then($mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('123')); + + reduce( + resolve([1, 2, 3]), + $this->append(), + '' + )->then($mock); + } + + /** @test */ + public function shouldResolveToInitialValueWhenInputPromiseDoesNotResolveToAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + reduce( + resolve(1), + $this->plus(), + 1 + )->then($mock); + } + + /** @test */ + public function shouldProvideCorrectBasisValue() + { + $insertIntoArray = function ($arr, $val, $i) { + $arr[$i] = $val; + + return $arr; + }; + + $d1 = new Deferred(); + $d2 = new Deferred(); + $d3 = new Deferred(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + reduce( + [$d1->promise(), $d2->promise(), $d3->promise()], + $insertIntoArray, + [] + )->then($mock); + + $d3->resolve(3); + $d1->resolve(1); + $d2->resolve(2); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FunctionRejectTest.php b/admin/classes/domain/vendor/react/promise/tests/FunctionRejectTest.php new file mode 100644 index 0000000..84b8ec6 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FunctionRejectTest.php @@ -0,0 +1,64 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + reject($expected) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function shouldRejectAFulfilledPromise() + { + $expected = 123; + + $resolved = new FulfilledPromise($expected); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + reject($resolved) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function shouldRejectARejectedPromise() + { + $expected = 123; + + $resolved = new RejectedPromise($expected); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + reject($resolved) + ->then( + $this->expectCallableNever(), + $mock + ); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FunctionResolveTest.php b/admin/classes/domain/vendor/react/promise/tests/FunctionResolveTest.php new file mode 100644 index 0000000..576c309 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FunctionResolveTest.php @@ -0,0 +1,102 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + resolve($expected) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldResolveAFulfilledPromise() + { + $expected = 123; + + $resolved = new FulfilledPromise($expected); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + resolve($resolved) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldRejectARejectedPromise() + { + $expected = 123; + + $resolved = new RejectedPromise($expected); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($expected)); + + resolve($resolved) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function shouldSupportDeepNestingInPromiseChains() + { + $d = new Deferred(); + $d->resolve(false); + + $result = resolve(resolve($d->promise()->then(function ($val) { + $d = new Deferred(); + $d->resolve($val); + + $identity = function ($val) { + return $val; + }; + + return resolve($d->promise()->then($identity))->then( + function ($val) { + return !$val; + } + ); + }))); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(true)); + + $result->then($mock); + } + + /** @test */ + public function returnsExtendePromiseForSimplePromise() + { + $promise = $this->getMock('React\Promise\PromiseInterface'); + + $this->assertInstanceOf('React\Promise\ExtendedPromiseInterface', resolve($promise)); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/FunctionSomeTest.php b/admin/classes/domain/vendor/react/promise/tests/FunctionSomeTest.php new file mode 100644 index 0000000..09e5350 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/FunctionSomeTest.php @@ -0,0 +1,126 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + some( + [], + 1 + )->then($mock); + } + + /** @test */ + public function shouldResolveValuesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2])); + + some( + [1, 2, 3], + 2 + )->then($mock); + } + + /** @test */ + public function shouldResolvePromisesArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2])); + + some( + [resolve(1), resolve(2), resolve(3)], + 2 + )->then($mock); + } + + /** @test */ + public function shouldResolveSparseArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([null, 1])); + + some( + [null, 1, null, 2, 3], + 2 + )->then($mock); + } + + /** @test */ + public function shouldRejectIfAnyInputPromiseRejectsBeforeDesiredNumberOfInputsAreResolved() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1 => 2, 2 => 3])); + + some( + [resolve(1), reject(2), reject(3)], + 2 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldAcceptAPromiseForAnArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2])); + + some( + resolve([1, 2, 3]), + 2 + )->then($mock); + } + + /** @test */ + public function shouldResolveWithEmptyArrayIfHowManyIsLessThanOne() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + some( + [1], + 0 + )->then($mock); + } + + /** @test */ + public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + some( + resolve(1), + 1 + )->then($mock); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/LazyPromiseTest.php b/admin/classes/domain/vendor/react/promise/tests/LazyPromiseTest.php new file mode 100644 index 0000000..b630881 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/LazyPromiseTest.php @@ -0,0 +1,107 @@ +promise(); + }; + + return new CallbackPromiseAdapter([ + 'promise' => function () use ($factory) { + return new LazyPromise($factory); + }, + 'resolve' => [$d, 'resolve'], + 'reject' => [$d, 'reject'], + 'notify' => [$d, 'progress'], + 'settle' => [$d, 'resolve'], + ]); + } + + /** @test */ + public function shouldNotCallFactoryIfThenIsNotInvoked() + { + $factory = $this->createCallableMock(); + $factory + ->expects($this->never()) + ->method('__invoke'); + + new LazyPromise($factory); + } + + /** @test */ + public function shouldCallFactoryIfThenIsInvoked() + { + $factory = $this->createCallableMock(); + $factory + ->expects($this->once()) + ->method('__invoke'); + + $p = new LazyPromise($factory); + $p->then(); + } + + /** @test */ + public function shouldReturnPromiseFromFactory() + { + $factory = $this->createCallableMock(); + $factory + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnValue(new FulfilledPromise(1))); + + $onFulfilled = $this->createCallableMock(); + $onFulfilled + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $p = new LazyPromise($factory); + + $p->then($onFulfilled); + } + + /** @test */ + public function shouldReturnPromiseIfFactoryReturnsNull() + { + $factory = $this->createCallableMock(); + $factory + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnValue(null)); + + $p = new LazyPromise($factory); + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then()); + } + + /** @test */ + public function shouldReturnRejectedPromiseIfFactoryThrowsException() + { + $exception = new \Exception(); + + $factory = $this->createCallableMock(); + $factory + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $onRejected = $this->createCallableMock(); + $onRejected + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $p = new LazyPromise($factory); + + $p->then($this->expectCallableNever(), $onRejected); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php b/admin/classes/domain/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php new file mode 100644 index 0000000..bdedf46 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php @@ -0,0 +1,40 @@ +callbacks = $callbacks; + } + + public function promise() + { + return call_user_func_array($this->callbacks['promise'], func_get_args()); + } + + public function resolve() + { + return call_user_func_array($this->callbacks['resolve'], func_get_args()); + } + + public function reject() + { + return call_user_func_array($this->callbacks['reject'], func_get_args()); + } + + public function notify() + { + return call_user_func_array($this->callbacks['notify'], func_get_args()); + } + + public function settle() + { + return call_user_func_array($this->callbacks['settle'], func_get_args()); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php b/admin/classes/domain/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php new file mode 100644 index 0000000..9157cd4 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php @@ -0,0 +1,14 @@ + function () use ($promise) { + return $promise; + }, + 'resolve' => $resolveCallback, + 'reject' => $rejectCallback, + 'notify' => $progressCallback, + 'settle' => $resolveCallback, + ]); + } + + /** @test */ + public function shouldRejectIfResolverThrowsException() + { + $exception = new \Exception('foo'); + + $promise = new Promise(function () use ($exception) { + throw $exception; + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $promise + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldFulfillIfFullfilledWithSimplePromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('foo')); + + $adapter->promise() + ->then($mock); + + $adapter->resolve(new SimpleFulfilledTestPromise()); + } + + /** @test */ + public function shouldRejectIfRejectedWithSimplePromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('foo')); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->resolve(new SimpleRejectedTestPromise()); + } +} + +class SimpleFulfilledTestPromise implements PromiseInterface +{ + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + try { + if ($onFulfilled) { + $onFulfilled('foo'); + } + + return new self('foo'); + } catch (\Exception $exception) { + return new RejectedPromise($exception); + } + } +} + +class SimpleRejectedTestPromise implements PromiseInterface +{ + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + try { + if ($onRejected) { + $onRejected('foo'); + } + + return new self('foo'); + } catch (\Exception $exception) { + return new RejectedPromise($exception); + } + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php new file mode 100644 index 0000000..d6c0956 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php @@ -0,0 +1,206 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->isType('callable'), $this->isType('callable'), $this->isType('callable')); + + $adapter = $this->getPromiseTestAdapter($mock); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldFulfillPromiseIfCancellerFulfills() + { + $adapter = $this->getPromiseTestAdapter(function ($resolve) { + $resolve(1); + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($mock, $this->expectCallableNever()); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldRejectPromiseIfCancellerRejects() + { + $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject) { + $reject(1); + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldRejectPromiseWithExceptionIfCancellerThrows() + { + $e = new \Exception(); + + $adapter = $this->getPromiseTestAdapter(function () use ($e) { + throw $e; + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($e)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldProgressPromiseIfCancellerNotifies() + { + $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject, $progress) { + $progress(1); + }); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldCallCancellerOnlyOnceIfCancellerResolves() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnCallback(function ($resolve) { + $resolve(); + })); + + $adapter = $this->getPromiseTestAdapter($mock); + + $adapter->promise()->cancel(); + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldHaveNoEffectIfCancellerDoesNothing() + { + $adapter = $this->getPromiseTestAdapter(function () {}); + + $adapter->promise() + ->then($this->expectCallableNever(), $this->expectCallableNever()); + + $adapter->promise()->cancel(); + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldCallCancellerFromDeepNestedPromiseChain() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + $adapter = $this->getPromiseTestAdapter($mock); + + $promise = $adapter->promise() + ->then(function () { + return new Promise\Promise(function () {}); + }) + ->then(function () { + $d = new Promise\Deferred(); + + return $d->promise(); + }) + ->then(function () { + return new Promise\Promise(function () {}); + }); + + $promise->cancel(); + } + + /** @test */ + public function cancelCalledOnChildrenSouldOnlyCancelWhenAllChildrenCancelled() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $child1 = $adapter->promise() + ->then() + ->then(); + + $adapter->promise() + ->then(); + + $child1->cancel(); + } + + /** @test */ + public function cancelShouldTriggerCancellerWhenAllChildrenCancel() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce()); + + $child1 = $adapter->promise() + ->then() + ->then(); + + $child2 = $adapter->promise() + ->then(); + + $child1->cancel(); + $child2->cancel(); + } + + /** @test */ + public function cancelShouldAlwaysTriggerCancellerWhenCalledOnRootPromise() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce()); + + $adapter->promise() + ->then() + ->then(); + + $adapter->promise() + ->then(); + + $adapter->promise()->cancel(); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseTest/FullTestTrait.php b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/FullTestTrait.php new file mode 100644 index 0000000..3ce45d6 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/FullTestTrait.php @@ -0,0 +1,15 @@ +getPromiseTestAdapter(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock); + + $adapter->notify($sentinel); + } + + /** @test */ + public function notifyShouldPropagateProgressToDownstreamPromises() + { + $adapter = $this->getPromiseTestAdapter(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnArgument(0)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock2 + ); + + $adapter->notify($sentinel); + } + + /** @test */ + public function notifyShouldPropagateTransformedProgressToDownstreamPromises() + { + $adapter = $this->getPromiseTestAdapter(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnValue($sentinel)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock2 + ); + + $adapter->notify(1); + } + + /** @test */ + public function notifyShouldPropagateCaughtExceptionValueAsProgress() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock2 + ); + + $adapter->notify(1); + } + + /** @test */ + public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise() + { + $adapter = $this->getPromiseTestAdapter(); + $adapter2 = $this->getPromiseTestAdapter(); + + $promise2 = $adapter2->promise(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + // resolve BEFORE attaching progress handler + $adapter->resolve(); + + $adapter->promise() + ->then(function () use ($promise2) { + return $promise2; + }) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ); + + $adapter2->notify($sentinel); + } + + /** @test */ + public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise() + { + $adapter = $this->getPromiseTestAdapter(); + $adapter2 = $this->getPromiseTestAdapter(); + + $promise2 = $adapter2->promise(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then(function () use ($promise2) { + return $promise2; + }) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ); + + // resolve AFTER attaching progress handler + $adapter->resolve(); + $adapter2->notify($sentinel); + } + + /** @test */ + public function notifyShouldForwardProgressWhenResolvedWithAnotherPromise() + { + $adapter = $this->getPromiseTestAdapter(); + $adapter2 = $this->getPromiseTestAdapter(); + + $sentinel = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->returnValue($sentinel)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($sentinel); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $this->expectCallableNever(), + $mock2 + ); + + $adapter->resolve($adapter2->promise()); + $adapter2->notify($sentinel); + } + + /** @test */ + public function notifyShouldAllowResolveAfterProgress() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->at(0)) + ->method('__invoke') + ->with($this->identicalTo(1)); + $mock + ->expects($this->at(1)) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->promise() + ->then( + $mock, + $this->expectCallableNever(), + $mock + ); + + $adapter->notify(1); + $adapter->resolve(2); + } + + /** @test */ + public function notifyShouldAllowRejectAfterProgress() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->at(0)) + ->method('__invoke') + ->with($this->identicalTo(1)); + $mock + ->expects($this->at(1)) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $mock, + $mock + ); + + $adapter->notify(1); + $adapter->reject(2); + } + + /** @test */ + public function notifyShouldReturnSilentlyOnProgressWhenAlreadyRejected() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->reject(1); + + $this->assertNull($adapter->notify()); + } + + /** @test */ + public function notifyShouldInvokeProgressHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise()->progress($mock); + $adapter->notify(1); + } + + /** @test */ + public function notifyShouldInvokeProgressHandlerFromDone() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $this->assertNull($adapter->promise()->done(null, null, $mock)); + $adapter->notify(1); + } + + /** @test */ + public function notifyShouldThrowExceptionThrownProgressHandlerFromDone() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(null, null, function () { + throw new \Exception('UnhandledRejectionException'); + })); + $adapter->notify(1); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php new file mode 100644 index 0000000..428230b --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php @@ -0,0 +1,351 @@ +getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->resolve(1); + $adapter->resolve(2); + + $adapter->promise() + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function fulfilledPromiseShouldInvokeNewlyAddedCallback() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->resolve(1); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($mock, $this->expectCallableNever()); + } + + /** @test */ + public function thenShouldForwardResultWhenCallbackIsNull() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + null, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function thenShouldForwardCallbackResultToNextCallback() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + function ($val) { + return $val + 1; + }, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function thenShouldForwardPromisedCallbackResultValueToNextCallback() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + function ($val) { + return \React\Promise\resolve($val + 1); + }, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackReturnsARejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + function ($val) { + return \React\Promise\reject($val + 1); + }, + $this->expectCallableNever() + ) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackThrows() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->resolve(1); + $adapter->promise() + ->then( + $mock, + $this->expectCallableNever() + ) + ->then( + $this->expectCallableNever(), + $mock2 + ); + } + + /** @test */ + public function cancelShouldReturnNullForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->resolve(); + + $this->assertNull($adapter->promise()->cancel()); + } + + /** @test */ + public function cancelShouldHaveNoEffectForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $adapter->resolve(); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function doneShouldInvokeFulfillmentHandlerForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->resolve(1); + $this->assertNull($adapter->promise()->done($mock)); + } + + /** @test */ + public function doneShouldThrowExceptionThrownFulfillmentHandlerForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $adapter->resolve(1); + $this->assertNull($adapter->promise()->done(function () { + throw new \Exception('UnhandledRejectionException'); + })); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejectsForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $adapter->resolve(1); + $this->assertNull($adapter->promise()->done(function () { + return \React\Promise\reject(); + })); + } + + /** @test */ + public function otherwiseShouldNotInvokeRejectionHandlerForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->resolve(1); + $adapter->promise()->otherwise($this->expectCallableNever()); + } + + /** @test */ + public function alwaysShouldNotSuppressValueForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->resolve($value); + $adapter->promise() + ->always(function () {}) + ->then($mock); + } + + /** @test */ + public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromiseForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->resolve($value); + $adapter->promise() + ->always(function () { + return 1; + }) + ->then($mock); + } + + /** @test */ + public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromiseForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->resolve($value); + $adapter->promise() + ->always(function () { + return \React\Promise\resolve(1); + }) + ->then($mock); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerThrowsForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->resolve(1); + $adapter->promise() + ->always(function () use ($exception) { + throw $exception; + }) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerRejectsForFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->resolve(1); + $adapter->promise() + ->always(function () use ($exception) { + return \React\Promise\reject($exception); + }) + ->then(null, $mock); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php new file mode 100644 index 0000000..a4f48ee --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php @@ -0,0 +1,68 @@ +getPromiseTestAdapter(); + + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then()); + } + + /** @test */ + public function thenShouldReturnAllowNullForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null)); + } + + /** @test */ + public function cancelShouldReturnNullForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertNull($adapter->promise()->cancel()); + } + + /** @test */ + public function doneShouldReturnNullForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertNull($adapter->promise()->done()); + } + + /** @test */ + public function doneShouldReturnAllowNullForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertNull($adapter->promise()->done(null, null, null)); + } + + /** @test */ + public function otherwiseShouldNotInvokeRejectionHandlerForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $adapter->promise()->otherwise($this->expectCallableNever()); + } + + /** @test */ + public function alwaysShouldReturnAPromiseForPendingPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {})); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php new file mode 100644 index 0000000..64255e8 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php @@ -0,0 +1,492 @@ +getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->reject(1); + $adapter->reject(2); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function rejectedPromiseShouldInvokeNewlyAddedCallback() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->reject(1); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldForwardUndefinedRejectionValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with(null); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + function () { + // Presence of rejection handler is enough to switch back + // to resolve mode, even though it returns undefined. + // The ONLY way to propagate a rejection is to re-throw or + // return a rejected promise; + } + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyPropagate() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + function ($val) { + return $val + 1; + } + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + function ($val) { + return \React\Promise\resolve($val + 1); + } + ) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldPropagateRejectionsWhenErrbackThrows() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->will($this->throwException($exception)); + + $mock2 = $this->createCallableMock(); + $mock2 + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + $mock + ) + ->then( + $this->expectCallableNever(), + $mock2 + ); + } + + /** @test */ + public function shouldPropagateRejectionsWhenErrbackReturnsARejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(2)); + + $adapter->reject(1); + $adapter->promise() + ->then( + $this->expectCallableNever(), + function ($val) { + return \React\Promise\reject($val + 1); + } + ) + ->then( + $this->expectCallableNever(), + $mock + ); + } + + /** @test */ + public function doneShouldInvokeRejectionHandlerForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done(null, $mock)); + } + + /** @test */ + public function doneShouldThrowExceptionThrownByRejectionHandlerForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done(null, function () { + throw new \Exception('UnhandledRejectionException'); + })); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonExceptionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done()); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejectsForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done(null, function () { + return \React\Promise\reject(); + })); + } + + /** @test */ + public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithExceptionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $adapter->reject(1); + $this->assertNull($adapter->promise()->done(null, function () { + return \React\Promise\reject(new \Exception('UnhandledRejectionException')); + })); + } + + /** @test */ + public function doneShouldThrowExceptionProvidedAsRejectionValueForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $adapter->reject(new \Exception('UnhandledRejectionException')); + $this->assertNull($adapter->promise()->done()); + } + + /** @test */ + public function doneShouldThrowWithDeepNestingPromiseChainsForRejectedPromise() + { + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $exception = new \Exception('UnhandledRejectionException'); + + $d = new Deferred(); + $d->resolve(); + + $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) { + $d = new Deferred(); + $d->resolve(); + + return \React\Promise\resolve($d->promise()->then(function () {}))->then( + function () use ($exception) { + throw $exception; + } + ); + }))); + + $result->done(); + } + + /** @test */ + public function doneShouldRecoverWhenRejectionHandlerCatchesExceptionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->reject(new \Exception('UnhandledRejectionException')); + $this->assertNull($adapter->promise()->done(null, function (\Exception $e) { + + })); + } + + /** @test */ + public function otherwiseShouldInvokeRejectionHandlerForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->reject(1); + $adapter->promise()->otherwise($mock); + } + + /** @test */ + public function otherwiseShouldInvokeNonTypeHintedRejectionHandlerIfReasonIsAnExceptionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->otherwise(function ($reason) use ($mock) { + $mock($reason); + }); + } + + /** @test */ + public function otherwiseShouldInvokeRejectionHandlerIfReasonMatchesTypehintForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \InvalidArgumentException(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->otherwise(function (\InvalidArgumentException $reason) use ($mock) { + $mock($reason); + }); + } + + /** @test */ + public function otherwiseShouldNotInvokeRejectionHandlerIfReaonsDoesNotMatchTypehintForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->expectCallableNever(); + + $adapter->reject($exception); + $adapter->promise() + ->otherwise(function (\InvalidArgumentException $reason) use ($mock) { + $mock($reason); + }); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->always(function () {}) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromiseForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->always(function () { + return 1; + }) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromiseForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->reject($exception); + $adapter->promise() + ->always(function () { + return \React\Promise\resolve(1); + }) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerThrowsForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception1 = new \Exception(); + $exception2 = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception2)); + + $adapter->reject($exception1); + $adapter->promise() + ->always(function () use ($exception2) { + throw $exception2; + }) + ->then(null, $mock); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerRejectsForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception1 = new \Exception(); + $exception2 = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception2)); + + $adapter->reject($exception1); + $adapter->promise() + ->always(function () use ($exception2) { + return \React\Promise\reject($exception2); + }) + ->then(null, $mock); + } + + /** @test */ + public function cancelShouldReturnNullForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->reject(); + + $this->assertNull($adapter->promise()->cancel()); + } + + /** @test */ + public function cancelShouldHaveNoEffectForRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $adapter->reject(); + + $adapter->promise()->cancel(); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php new file mode 100644 index 0000000..e363b6d --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php @@ -0,0 +1,86 @@ +getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then()); + } + + /** @test */ + public function thenShouldReturnAllowNullForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null)); + } + + /** @test */ + public function cancelShouldReturnNullForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + + $this->assertNull($adapter->promise()->cancel()); + } + + /** @test */ + public function cancelShouldHaveNoEffectForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $adapter->settle(); + + $adapter->promise()->cancel(); + } + + /** @test */ + public function doneShouldReturnNullForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertNull($adapter->promise()->done(null, function () {})); + } + + /** @test */ + public function doneShouldReturnAllowNullForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertNull($adapter->promise()->done(null, function () {}, null)); + } + + /** @test */ + public function progressShouldNotInvokeProgressHandlerForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $adapter->promise()->progress($this->expectCallableNever()); + $adapter->notify(); + } + + /** @test */ + public function alwaysShouldReturnAPromiseForSettledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $adapter->settle(); + $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {})); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php new file mode 100644 index 0000000..7d6f65f --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php @@ -0,0 +1,363 @@ +getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->reject(1); + } + + /** @test */ + public function rejectShouldRejectWithFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->reject(Promise\resolve(1)); + } + + /** @test */ + public function rejectShouldRejectWithRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->reject(Promise\reject(1)); + } + + /** @test */ + public function rejectShouldForwardReasonWhenCallbackIsNull() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then( + $this->expectCallableNever() + ) + ->then( + $this->expectCallableNever(), + $mock + ); + + $adapter->reject(1); + } + + /** @test */ + public function rejectShouldMakePromiseImmutable() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $mock + ); + + $adapter->reject(1); + $adapter->reject(2); + } + + /** @test */ + public function notifyShouldInvokeOtherwiseHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->otherwise($mock); + + $adapter->reject(1); + } + + /** @test */ + public function doneShouldInvokeRejectionHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $this->assertNull($adapter->promise()->done(null, $mock)); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowExceptionThrownByRejectionHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(null, function () { + throw new \Exception('UnhandledRejectionException'); + })); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonException() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done()); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejects() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(null, function () { + return \React\Promise\reject(); + })); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithException() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(null, function () { + return \React\Promise\reject(new \Exception('UnhandledRejectionException')); + })); + $adapter->reject(1); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRetunsPendingPromiseWhichRejectsLater() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $d = new Deferred(); + $promise = $d->promise(); + + $this->assertNull($adapter->promise()->done(null, function () use ($promise) { + return $promise; + })); + $adapter->reject(1); + $d->reject(1); + } + + /** @test */ + public function doneShouldThrowExceptionProvidedAsRejectionValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done()); + $adapter->reject(new \Exception('UnhandledRejectionException')); + } + + /** @test */ + public function doneShouldThrowWithDeepNestingPromiseChains() + { + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $exception = new \Exception('UnhandledRejectionException'); + + $d = new Deferred(); + + $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) { + $d = new Deferred(); + $d->resolve(); + + return \React\Promise\resolve($d->promise()->then(function () {}))->then( + function () use ($exception) { + throw $exception; + } + ); + }))); + + $result->done(); + + $d->resolve(); + } + + /** @test */ + public function doneShouldRecoverWhenRejectionHandlerCatchesException() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->assertNull($adapter->promise()->done(null, function (\Exception $e) { + + })); + $adapter->reject(new \Exception('UnhandledRejectionException')); + } + + /** @test */ + public function alwaysShouldNotSuppressRejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () {}) + ->then(null, $mock); + + $adapter->reject($exception); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () { + return 1; + }) + ->then(null, $mock); + + $adapter->reject($exception); + } + + /** @test */ + public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () { + return \React\Promise\resolve(1); + }) + ->then(null, $mock); + + $adapter->reject($exception); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerThrowsForRejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () use ($exception) { + throw $exception; + }) + ->then(null, $mock); + + $adapter->reject($exception); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerRejectsForRejection() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () use ($exception) { + return \React\Promise\reject($exception); + }) + ->then(null, $mock); + + $adapter->reject($exception); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php new file mode 100644 index 0000000..b05f7fe --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php @@ -0,0 +1,258 @@ +getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($mock); + + $adapter->resolve(1); + } + + /** @test */ + public function resolveShouldResolveWithPromisedValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($mock); + + $adapter->resolve(Promise\resolve(1)); + } + + /** @test */ + public function resolveShouldRejectWhenResolvedWithRejectedPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then($this->expectCallableNever(), $mock); + + $adapter->resolve(Promise\reject(1)); + } + + /** @test */ + public function resolveShouldForwardValueWhenCallbackIsNull() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then( + null, + $this->expectCallableNever() + ) + ->then( + $mock, + $this->expectCallableNever() + ); + + $adapter->resolve(1); + } + + /** @test */ + public function resolveShouldMakePromiseImmutable() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $adapter->promise() + ->then( + $mock, + $this->expectCallableNever() + ); + + $adapter->resolve(1); + $adapter->resolve(2); + } + + /** @test */ + public function doneShouldInvokeFulfillmentHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(1)); + + $this->assertNull($adapter->promise()->done($mock)); + $adapter->resolve(1); + } + + /** @test */ + public function doneShouldThrowExceptionThrownFulfillmentHandler() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('\Exception', 'UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(function () { + throw new \Exception('UnhandledRejectionException'); + })); + $adapter->resolve(1); + } + + /** @test */ + public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejects() + { + $adapter = $this->getPromiseTestAdapter(); + + $this->setExpectedException('React\\Promise\\UnhandledRejectionException'); + + $this->assertNull($adapter->promise()->done(function () { + return \React\Promise\reject(); + })); + $adapter->resolve(1); + } + + /** @test */ + public function alwaysShouldNotSuppressValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->promise() + ->always(function () {}) + ->then($mock); + + $adapter->resolve($value); + } + + /** @test */ + public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->promise() + ->always(function () { + return 1; + }) + ->then($mock); + + $adapter->resolve($value); + } + + /** @test */ + public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromise() + { + $adapter = $this->getPromiseTestAdapter(); + + $value = new \stdClass(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($value)); + + $adapter->promise() + ->always(function () { + return \React\Promise\resolve(1); + }) + ->then($mock); + + $adapter->resolve($value); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerThrowsForFulfillment() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () use ($exception) { + throw $exception; + }) + ->then(null, $mock); + + $adapter->resolve(1); + } + + /** @test */ + public function alwaysShouldRejectWhenHandlerRejectsForFulfillment() + { + $adapter = $this->getPromiseTestAdapter(); + + $exception = new \Exception(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo($exception)); + + $adapter->promise() + ->always(function () use ($exception) { + return \React\Promise\reject($exception); + }) + ->then(null, $mock); + + $adapter->resolve(1); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/RejectedPromiseTest.php b/admin/classes/domain/vendor/react/promise/tests/RejectedPromiseTest.php new file mode 100644 index 0000000..c886b00 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/RejectedPromiseTest.php @@ -0,0 +1,50 @@ + function () use (&$promise) { + if (!$promise) { + throw new \LogicException('RejectedPromise must be rejected before obtaining the promise'); + } + + return $promise; + }, + 'resolve' => function () { + throw new \LogicException('You cannot call resolve() for React\Promise\RejectedPromise'); + }, + 'reject' => function ($reason = null) use (&$promise) { + if (!$promise) { + $promise = new RejectedPromise($reason); + } + }, + 'notify' => function () { + // no-op + }, + 'settle' => function ($reason = null) use (&$promise) { + if (!$promise) { + $promise = new RejectedPromise($reason); + } + }, + ]); + } + + /** @test */ + public function shouldThrowExceptionIfConstructedWithAPromise() + { + $this->setExpectedException('\InvalidArgumentException'); + + return new RejectedPromise(new RejectedPromise()); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/Stub/CallableStub.php b/admin/classes/domain/vendor/react/promise/tests/Stub/CallableStub.php new file mode 100644 index 0000000..0120893 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/Stub/CallableStub.php @@ -0,0 +1,10 @@ +createCallableMock(); + $mock + ->expects($this->exactly($amount)) + ->method('__invoke'); + + return $mock; + } + + public function expectCallableOnce() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + public function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + public function createCallableMock() + { + return $this->getMock('React\\Promise\Stub\CallableStub'); + } +} diff --git a/admin/classes/domain/vendor/react/promise/tests/bootstrap.php b/admin/classes/domain/vendor/react/promise/tests/bootstrap.php new file mode 100644 index 0000000..9b7f872 --- /dev/null +++ b/admin/classes/domain/vendor/react/promise/tests/bootstrap.php @@ -0,0 +1,7 @@ +addPsr4('React\\Promise\\', __DIR__); diff --git a/ajax_forms/getKBSearchResults.php b/ajax_forms/getKBSearchResults.php new file mode 100644 index 0000000..75f9c37 --- /dev/null +++ b/ajax_forms/getKBSearchResults.php @@ -0,0 +1,47 @@ +'; + +include_once "../admin/classes/domain/GOKbTools.php"; +echo 'DEBUG_ after include ok name = '.$_POST['name'].'
    '; + +$tool = GOKbTools::getInstance(); +//echo 'new tool !! attr = '.$_POST['name'].'
    '; + + +$a = $tool->searchByName($_POST['name'], "package"); +$b = $tool->searchByName($_POST['name'], "title"); + +/* +$a = $tool->searchByName("Arts", "package"); +$b = $tool->searchByName("Arts", "title"); +*/ +echo '

    Search results :

    '; +echo '
    Packages
    '; + +foreach ($a as $key => $value) { +// echo '
    '; + echo ''; + echo ' - '.$value; + echo ' '; + echo ' '; + echo '
    '; +} + + +echo '
    Titles
    '; + +foreach ($b as $key => $value) { + //echo '
    '; + echo ''; + echo ' - '.$value; + echo ' '; + echo ' '; + echo '
    '; +} + + + + + +?> \ No newline at end of file diff --git a/ajax_forms/getNewResourceForm.php b/ajax_forms/getNewResourceForm.php index 585c4a6..1558915 100644 --- a/ajax_forms/getNewResourceForm.php +++ b/ajax_forms/getNewResourceForm.php @@ -82,15 +82,15 @@
    -
     * required fields
    +
     * required fields search fields
    - '; echo ''; echo ""; diff --git a/ajax_processing/customImportedPackageContent.php b/ajax_processing/customImportedPackageContent.php new file mode 100644 index 0000000..155d8df --- /dev/null +++ b/ajax_processing/customImportedPackageContent.php @@ -0,0 +1,62 @@ +
    + Customize imported package content +
    + + $res))); + echo "
    - ".$object->titleText; + $object->removeResource(); + } + +} else { + echo '

    Uncheck titles you want to remove from this package. Unselect all will import package only without any titles.
    + To delete this package and all resources included in Click here

    '; + $resource = new Resource(); + $package = $resource->getResourceByIdentifierAndType($_POST['id'], 'gokb'); + if (count($package) > 1) { + echo "more than 1 resource correspond to this package !! "; //TODO _ DEBUG _ + } else { + $titles = $package[0]->getChildResources(); //return an array of ResourceRelationship + //ajax_processing/customImportedPackageContent.php + echo "
    " + . "" + . "
    " + . "Package content" + . " Select all" + . "
    +   Product   - +
    + @@ -106,9 +107,16 @@ + + + + + + + @@ -130,7 +138,7 @@    *   -
    @@ -99,6 +99,7 @@
    +
    + + +
    @@ -175,7 +183,7 @@   Acquisition Type *   - +
    '; - } elseif ($xml != "") { + } elseif ($xml->getName() == 'TIPPs') { + $string = ""; + } + elseif ($xml != "") { $string = $xml; } else { - $string = "Empty"; + $string = "Empty"; //italic ? } } return $string; } + // ------------------------------------------------------------------------- + /** + * Extract and display TIPPs from record + * @param $record /SimpleXMLElement results of getRecord request + * @param $recordType string type of resource + * @return string HTML content to display + */ + function displayRecordTipps($record, $recordType){ + $string = ""; + $tipps=$record->{'TIPPs'}; + + if ($recordType == 'package') $type = 'title'; + else $type = 'package'; + + $string .= "
    @@ -291,6 +299,7 @@ + diff --git a/ajax_processing/searchResourceFromGokb.php b/ajax_processing/searchResourceFromGokb.php new file mode 100644 index 0000000..74499d1 --- /dev/null +++ b/ajax_processing/searchResourceFromGokb.php @@ -0,0 +1,41 @@ +'; + +include "../admin/classes/domain/GOKbTools.php"; +echo 'after include ok name = '.$_POST['name'].'
    '; + +$tool = GOKbTools::getInstance(); + + +$a = $tool->searchByName($_POST['name'], "package"); +$b = $tool->searchByName($_POST['name'], "title"); + + +echo '

    Search results :

    '; +echo '
    Packages
    '; + +foreach ($a as $key => $value) { + echo ''; + echo ' - '.$value; + echo ''; + echo ' '; + echo ''; +} + + +echo '
    Titles
    '; + +foreach ($b as $key => $value) { + echo ''; + echo ' - '.$value; + echo ''; + echo ' '; + echo ''; +} + + + + + +?> \ No newline at end of file diff --git a/ajax_processing/submitNewResource.php b/ajax_processing/submitNewResource.php index b557ced..d078e0f 100644 --- a/ajax_processing/submitNewResource.php +++ b/ajax_processing/submitNewResource.php @@ -28,7 +28,7 @@ $resource->titleText = $_POST['titleText']; $resource->descriptionText = $_POST['descriptionText']; - $resource->isbnOrISSN = ''; + $resource->isbnOrISSN = ''; //$_POST['ISSNText']; $resource->statusID = $statusID; $resource->orderNumber = ''; $resource->systemNumber = ''; diff --git a/images/loupe.png b/images/loupe.png new file mode 100644 index 0000000000000000000000000000000000000000..67ff3472021e61a778a50f6045aceed785d71a05 GIT binary patch literal 335 zcmV-V0kHmwP)eUdJieCig6c3R*Y1uO(vK8nI;9D`o+RwoL zf&WMkkpV>>Gm+WP5}E5X0-XSSVzn?88bxLTzP;&Q0(&P?4cDKNKKU;?DZlQ(#3fRN hPL4l!FcBG_^9dvPmnn9KY?lB4002ovPDHLkV1lLmf_wk~ literal 0 HcmV?d00001 diff --git a/js/forms/resourceNewForm.js b/js/forms/resourceNewForm.js index 7ed1f13..f22f006 100644 --- a/js/forms/resourceNewForm.js +++ b/js/forms/resourceNewForm.js @@ -16,13 +16,15 @@ */ $(document).ready(function(){ - - + $(".submitResource").click(function () { submitResource($(this).attr("id")); }); - + $("#search").click(function(){ + console.debug("testLog _ searchclick ok"); + searchGokb(); + }); //do submit if enter is hit $('#titleText').keyup(function(e) { @@ -253,6 +255,7 @@ function submitResource(status){ + alert("test"); orderTypeList =''; $(".orderTypeID").each(function(id) { @@ -309,7 +312,28 @@ function submitResource(status){ } - +function searchGokb(){ + var name = $('#titleText').val(); + var issn = $('#ISSNText').val(); + var publisher = $('#providerText').val(); + + if (name != '' && name != null){ + console.debug("name non vide"); + $.ajax({ + type: "POST", + url: "ajax_forms.php?action=getKBSearchResults", + /* url: "ajax_processing.php?action=searchResourceFromGokb",*/ + cache: false, + data: {name:$('#titleText').val() }, + success: function() { + + console.debug("search success"); + + } + }); + } + +} //kill all binds done by jquery live diff --git a/resource.php b/resource.php index 92274ee..0ee41dd 100644 --- a/resource.php +++ b/resource.php @@ -28,7 +28,7 @@ $config = new Configuration(); //set this to turn off displaying the title header in header.php -$pageTitle=$resource->titleText;; +$pageTitle=$resource->titleText; include 'templates/header.php'; From 4c01291dc522c3c338072616631a4d1f037617df Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Tue, 23 Jun 2015 15:35:18 +0200 Subject: [PATCH 02/32] "oui" --- admin/classes/domain/GOKbTools.php | 1 - 1 file changed, 1 deletion(-) diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 6404e54..6b64dd0 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -1,5 +1,4 @@ Date: Fri, 26 Jun 2015 09:18:11 +0200 Subject: [PATCH 03/32] GOKb search + display results --- admin/classes/domain/GOKbTools.php | 101 +++++++++++++++++++---------- ajax_forms/getKBSearchResults.php | 84 ++++++++++++++---------- ajax_forms/getNewResourceForm.php | 3 +- js/forms/resourceNewForm.js | 16 ++--- 4 files changed, 124 insertions(+), 80 deletions(-) diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 6b64dd0..3b83d29 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -50,7 +50,6 @@ class GOKbTools { * Return the unique instance of class (design pattern singleton) */ public static function getInstance(){ - echo 'DEBUG_ getInstance()
    '; if (is_null(self::$instance)){ self::$instance = new GOKbTools(); } @@ -63,7 +62,6 @@ public static function getInstance(){ */ private function __construct() { - echo 'DEBUG_ constructeur tool _ START
    '; $this->titleClient = new Client(OAI_HOST.'titles'); $this->packageClient = new Client(OAI_HOST.'packages'); $this->titleEndpoint = new Endpoint($this->titleClient); @@ -72,55 +70,89 @@ private function __construct() $this->httpClient = (class_exists('GuzzleHttp\Client')) ? new GuzzleAdapter() : new CurlAdapter(); - echo 'DEBUG_ constructeur tools _ END
    '; } -// ------------------------------------------------------------------------- - - /** - * Build and send a search query, return an array - * @param string $name the name searched - * @param string $type the type of searched resource - * @return array an array with all results [GOKb_identifer => name] +// ------------------------------------------------------------------------- +/** + * Build and send search queries, return an array of array. + * @param string $name the name searched + * @param string $issn the type of searched resource + * @param string $publisher the type of searched resource + * @return array multidimensionnal array : $res[0] = array packages, [1]= array titles + * each array is like [GOKb_identifer => name] */ - public function searchByName($name, $type){ //voir comment avoir une unique fonction de génération de requete (prefix etc...), - switch ($type) { - case 'title': - $prefix = ''; - break; - case 'package': - $prefix = ''; - break; - default: - break; + public function searchOnGokb($name, $issn, $publisher){ + + // query construction for packages + if ((!empty($name)) && (empty($issn)) && (empty($publisher))){ + $query = 'select distinct * where {'; + $query .= '?s a .'; + $query .= '?s ?o .'; + $query .= 'FILTER regex(?o, "'.$name.'", "i")} ORDER BY DESC(?o)'; + + //send the request and get results + $tmp = $this->sendSparqlQuery($query); + $res = $tmp->{"results"}->{"result"}; + + foreach ($res as $a => $b ) { + $uri = $b->{'binding'}->{'uri'}; + $id = $this->UriToGokbId($uri); + $prefLabel = $b->{'binding'}[1]->{'literal'}; + $packages["$id"] = $prefLabel; + } } - //query construction - $query = 'select distinct * where {'. - $query .= '?s a '.$prefix.' .'; - $query .= '?s ?o .'; - $query .= 'FILTER regex(?o, "'.$name.'", "i")} LIMIT 100'; + $titles = array(); + + // query construction for titles + + if (!empty($issn)){ //search by issn (or eissn) only + $query = 'select distinct * where {'; + $query .= '?s a .'; + $query .= '?s "'.$issn.'" .'; + $query .= '?s ?o .}'; + } else if (!empty($publisher)){ + $query = 'select ?title ?name where{'; + $query .= '?title a .'; + $query .= '?title ?name .'; + $query .= '?title ?orgID .'; + $query .= '{select ?orgID where {'; + $query .= '?orgID a .'; + $query .= '?orgID ?label .'; + $query .= 'FILTER regex (?label, "'.$publisher.'", "i")} GROUP BY (?orgID)}'; + if (!empty($name)) {$query .= ' FILTER regex(?name, "'.$name.'", "i")';} + $query .= '}'; + + } else { //search by name only + $query = 'select distinct * where {'; + $query .= '?s a .'; + $query .= '?s ?o .'; + $query .= 'FILTER regex(?o, "'.$name.'", "i")} ORDER BY DESC(?o)'; + + } //send the request and get results - $results = $this->sendSparqlQuery($query); - $res = $results->{"results"}->{"result"}; - - $tmp = array(); + $tmp = $this->sendSparqlQuery($query); + $res = $tmp->{"results"}->{"result"}; + foreach ($res as $a => $b ) { $uri = $b->{'binding'}->{'uri'}; $id = $this->UriToGokbId($uri); $prefLabel = $b->{'binding'}[1]->{'literal'}; - $tmp["$id"] = $prefLabel; + $titles["$id"] = $prefLabel; } - return $tmp; - } -// ------------------------------------------------------------------------- - + + + + $results = array($packages, $titles); + return $results; + + } // ------------------------------------------------------------------------- /** @@ -246,6 +278,7 @@ public function displayRecord($xml){ } // ------------------------------------------------------------------------- // ------------------------------------------------------------------------- +// ------------------------------------------------------------------------- } diff --git a/ajax_forms/getKBSearchResults.php b/ajax_forms/getKBSearchResults.php index 75f9c37..39370fb 100644 --- a/ajax_forms/getKBSearchResults.php +++ b/ajax_forms/getKBSearchResults.php @@ -1,47 +1,61 @@ +
    +
    + Add new resource - Search results +
    '; - -include_once "../admin/classes/domain/GOKbTools.php"; -echo 'DEBUG_ after include ok name = '.$_POST['name'].'
    '; +include_once $_SERVER['DOCUMENT_ROOT']."resources/admin/classes/domain/GOKbTools.php"; $tool = GOKbTools::getInstance(); -//echo 'new tool !! attr = '.$_POST['name'].'
    '; - - -$a = $tool->searchByName($_POST['name'], "package"); -$b = $tool->searchByName($_POST['name'], "title"); - -/* -$a = $tool->searchByName("Arts", "package"); -$b = $tool->searchByName("Arts", "title"); -*/ -echo '

    Search results :

    '; -echo '
    Packages
    '; - -foreach ($a as $key => $value) { -// echo '
    '; - echo ''; - echo ' - '.$value; - echo ' '; - echo ' '; - echo ''; + +$results = $tool->searchOnGokb($_POST['name'], $_POST['issn'], $_POST['publisher']); +$nb_packages = count($results[0]); +$nb_titles = count($results[1]); + +//echo "DEBUG _ nb pack = ".$nb_packages." _ nb_titles = ".$nb_titles."
    "; + + +if ($nb_packages > 0){ + echo "
    "; + echo "

    Packages

    ".$nb_packages." results View all packages results
    "; + + $p = array_slice($results[0], 0, 5, true); + echo '
    '; + foreach ($p as $key => $value) { + // echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
    '; + echo ' - '.$value; + echo '
    '; + echo ""; } -echo '
    Titles
    '; -foreach ($b as $key => $value) { - //echo '
    '; - echo ''; - echo ' - '.$value; - echo ' '; - echo ' '; - echo '
    '; -} +if ($nb_titles > 0){ +echo "
    "; +echo "

    Issues

    ".$nb_titles." resultsView all packages results
    "; +$t=array_slice($results[1], 0,5,true); +echo ''; + foreach ($t as $key => $value) { + // echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
    '; + echo ' - '.$value; + echo '
    '; +echo "
    "; +} else { + echo "No results, please check your search fields
    "; +} +?> -?> \ No newline at end of file + diff --git a/ajax_forms/getNewResourceForm.php b/ajax_forms/getNewResourceForm.php index 1558915..d32e4a4 100644 --- a/ajax_forms/getNewResourceForm.php +++ b/ajax_forms/getNewResourceForm.php @@ -299,7 +299,7 @@ - + @@ -312,3 +312,4 @@ + diff --git a/js/forms/resourceNewForm.js b/js/forms/resourceNewForm.js index f22f006..a910192 100644 --- a/js/forms/resourceNewForm.js +++ b/js/forms/resourceNewForm.js @@ -313,26 +313,22 @@ function submitResource(status){ function searchGokb(){ - var name = $('#titleText').val(); - var issn = $('#ISSNText').val(); - var publisher = $('#providerText').val(); - + var name=$('#titleText').val() if (name != '' && name != null){ console.debug("name non vide"); $.ajax({ type: "POST", - url: "ajax_forms.php?action=getKBSearchResults", + url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", /* url: "ajax_processing.php?action=searchResourceFromGokb",*/ cache: false, - data: {name:$('#titleText').val() }, - success: function() { - - console.debug("search success"); + data: {name:$('#titleText').val(), issn:$('#ISSNText').val(), publisher:$('#providerText').val()}, + success: function(res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); } }); } - } From 76599a336f8d24e8b782dfc868808591740fb2b6 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Fri, 26 Jun 2015 16:05:34 +0200 Subject: [PATCH 04/32] display all results + display resource details --- admin/classes/domain/GOKbTools.php | 84 +++++++++++++----------- ajax_forms/getKBSearchResults.php | 50 +++++++------- ajax_htmldata/getGokbResourceDetails.php | 11 ++++ js/KBSearch.js | 31 +++++++++ js/forms/resourceNewForm.js | 27 ++++---- 5 files changed, 125 insertions(+), 78 deletions(-) create mode 100644 ajax_htmldata/getGokbResourceDetails.php create mode 100644 js/KBSearch.js diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 3b83d29..0e8227a 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -78,18 +78,22 @@ private function __construct() * @param string $name the name searched * @param string $issn the type of searched resource * @param string $publisher the type of searched resource + * @param int $searchType type of search ( 0 = search packages + issues, 5 results per type; + * <0 = all results of packages; + * >0 = all results of titles;) * @return array multidimensionnal array : $res[0] = array packages, [1]= array titles * each array is like [GOKb_identifer => name] */ - public function searchOnGokb($name, $issn, $publisher){ + public function searchOnGokb($name, $issn, $publisher, $searchType){ + echo 'DEBUG_ search param = name: '.$name.' issn: '.$issn.' pub: '.$publisher.' type: '.$searchType.'
    '; // query construction for packages - if ((!empty($name)) && (empty($issn)) && (empty($publisher))){ + if ((!empty($name)) && (empty($issn)) && (empty($publisher)) && ($searchType <= 0)){ $query = 'select distinct * where {'; $query .= '?s a .'; $query .= '?s ?o .'; $query .= 'FILTER regex(?o, "'.$name.'", "i")} ORDER BY DESC(?o)'; - + if($searchType == 0) $query .= 'LIMIT 5'; //send the request and get results $tmp = $this->sendSparqlQuery($query); $res = $tmp->{"results"}->{"result"}; @@ -105,47 +109,47 @@ public function searchOnGokb($name, $issn, $publisher){ $titles = array(); // query construction for titles + if($searchType >= 0) { + if (!empty($issn)){ //search by issn (or eissn) only + $query = 'select distinct * where {'; + $query .= '?s a .'; + $query .= '?s "'.$issn.'" .'; + $query .= '?s ?o .}'; + } else if (!empty($publisher)){ + $query = 'select distinct ?title ?name where{'; + $query .= '?title a .'; + $query .= '?title ?name .'; + $query .= '?title ?orgID .'; + $query .= '{select distinct ?orgID where {'; + $query .= '?orgID a .'; + $query .= '?orgID ?label .'; + $query .= 'FILTER regex (?label, "'.$publisher.'", "i")} GROUP BY (?orgID)}'; + if (!empty($name)) {$query .= ' FILTER regex(?name, "'.$name.'", "i")';} + $query .= '}'; + + } else { //search by name only + $query = 'select distinct * where {'; + $query .= '?s a .'; + $query .= '?s ?o .'; + $query .= 'FILTER regex(?o, "'.$name.'", "i")} ORDER BY ASC(?o)'; - if (!empty($issn)){ //search by issn (or eissn) only - $query = 'select distinct * where {'; - $query .= '?s a .'; - $query .= '?s "'.$issn.'" .'; - $query .= '?s ?o .}'; - } else if (!empty($publisher)){ - $query = 'select ?title ?name where{'; - $query .= '?title a .'; - $query .= '?title ?name .'; - $query .= '?title ?orgID .'; - $query .= '{select ?orgID where {'; - $query .= '?orgID a .'; - $query .= '?orgID ?label .'; - $query .= 'FILTER regex (?label, "'.$publisher.'", "i")} GROUP BY (?orgID)}'; - if (!empty($name)) {$query .= ' FILTER regex(?name, "'.$name.'", "i")';} - $query .= '}'; - - } else { //search by name only - $query = 'select distinct * where {'; - $query .= '?s a .'; - $query .= '?s ?o .'; - $query .= 'FILTER regex(?o, "'.$name.'", "i")} ORDER BY DESC(?o)'; - - } + } + if($searchType == 0) $query .= 'LIMIT 5'; + //send the request and get results + $tmp = $this->sendSparqlQuery($query); + $res = $tmp->{"results"}->{"result"}; - //send the request and get results - $tmp = $this->sendSparqlQuery($query); - $res = $tmp->{"results"}->{"result"}; + foreach ($res as $a => $b ) { + $uri = $b->{'binding'}->{'uri'}; + $id = $this->UriToGokbId($uri); + $prefLabel = $b->{'binding'}[1]->{'literal'}; + $titles["$id"] = $prefLabel; + } - foreach ($res as $a => $b ) { - $uri = $b->{'binding'}->{'uri'}; - $id = $this->UriToGokbId($uri); - $prefLabel = $b->{'binding'}[1]->{'literal'}; - $titles["$id"] = $prefLabel; } - - - + @@ -251,9 +255,9 @@ public function getDetails($type, $gokbID) public function displayRecord($xml){ $string = ""; if (count($xml->children()) > 0) { - $string = "
    "; + $string = "
    "; foreach ($xml->children() as $tag => $child) { - $string .= ''; + $string .= ''; $string .= ''; $string .= ''; } diff --git a/ajax_forms/getKBSearchResults.php b/ajax_forms/getKBSearchResults.php index 39370fb..3974946 100644 --- a/ajax_forms/getKBSearchResults.php +++ b/ajax_forms/getKBSearchResults.php @@ -1,32 +1,30 @@
    -
    - Add new resource - Search results -
    +
    + Add new resource - Search results +
    searchOnGokb($_POST['name'], $_POST['issn'], $_POST['publisher']); +$results = $tool->searchOnGokb($_POST['name'], $_POST['issn'], $_POST['publisher'], $_POST['type']); $nb_packages = count($results[0]); $nb_titles = count($results[1]); -//echo "DEBUG _ nb pack = ".$nb_packages." _ nb_titles = ".$nb_titles."
    "; - if ($nb_packages > 0){ echo "
    "; - echo "

    Packages

    ".$nb_packages." results View all packages results
    "; + echo "

    Packages

    ".$nb_packages." results View all packages results
    "; - $p = array_slice($results[0], 0, 5, true); echo '
    '.$tag.'
    '.$tag.''.$this->displayRecord($child).'
    '; - foreach ($p as $key => $value) { - // echo ''; - echo ''; - echo ''; - echo ''; + echo ''; + echo ''; + echo ""; } echo '
    '; + foreach ($results[0] as $key => $value) { + echo "
    "; echo ' - '.$value; - echo '
    '; echo ""; @@ -36,18 +34,18 @@ if ($nb_titles > 0){ echo "
    "; -echo "

    Issues

    ".$nb_titles." resultsView all packages results
    "; +echo '

    Issues

    '.$nb_titles.' resultsView all issues results
    '; -$t=array_slice($results[1], 0,5,true); echo ''; - foreach ($t as $key => $value) { - // echo ''; - echo ''; - echo ''; - echo ''; + echo ''; + echo ''; + echo ""; } echo '
    '; + foreach ($results[1] as $key => $value) { + echo "
    "; echo ' - '.$value; - echo '
    '; @@ -57,5 +55,13 @@ } ?> + +Back + + +
    + + + diff --git a/ajax_htmldata/getGokbResourceDetails.php b/ajax_htmldata/getGokbResourceDetails.php new file mode 100644 index 0000000..91e8192 --- /dev/null +++ b/ajax_htmldata/getGokbResourceDetails.php @@ -0,0 +1,11 @@ +getDetails($_POST['type'], $_POST['id']); +echo $tool->displayRecord($record); + + + ?> \ No newline at end of file diff --git a/js/KBSearch.js b/js/KBSearch.js new file mode 100644 index 0000000..4b350c9 --- /dev/null +++ b/js/KBSearch.js @@ -0,0 +1,31 @@ +$(document).ready(function(){ + console.debug("kbsearch.js is ready"); +}); + +function allResults(s_name, s_pub, s_type){ + $.ajax({ + type: "POST", + url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {name:s_name, issn:'', publisher:s_pub, type:s_type}, + success: function(res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + }); +} + +function getDetails(s_type, s_gokbID){ + console.debug("getDetails !"); + $.ajax({ + type: "POST", + url: "ajax_htmldata.php?action=getGokbResourceDetails&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {type:s_type, id:s_gokbID}, + success: function(res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + }); + +} \ No newline at end of file diff --git a/js/forms/resourceNewForm.js b/js/forms/resourceNewForm.js index a910192..0d5e19b 100644 --- a/js/forms/resourceNewForm.js +++ b/js/forms/resourceNewForm.js @@ -313,22 +313,17 @@ function submitResource(status){ function searchGokb(){ - var name=$('#titleText').val() - if (name != '' && name != null){ - console.debug("name non vide"); - $.ajax({ - type: "POST", - url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", - /* url: "ajax_processing.php?action=searchResourceFromGokb",*/ - cache: false, - data: {name:$('#titleText').val(), issn:$('#ISSNText').val(), publisher:$('#providerText').val()}, - success: function(res) { - document.getElementById("TB_ajaxContent").innerHTML = ""; - $('#TB_ajaxContent').append(res); - - } - }); - } + $.ajax({ + type: "POST", + url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {name:$('#titleText').val(), issn:$('#ISSNText').val(), publisher:$('#providerText').val(), type:0}, + success: function(res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + + } + }); } From 3d07d0588293d02ba276230422e557a7c58a9078 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Fri, 3 Jul 2015 12:29:40 +0200 Subject: [PATCH 05/32] =?UTF-8?q?ajout=20pagination=20et=20mise=20en=20for?= =?UTF-8?q?me,=20pagination=20en=20cours=20(non=20termin=C3=A9e)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/classes/domain/GOKbTools.php | 86 +++++++++--- ajax_forms/getKBSearchResults.php | 87 +++++++----- ajax_forms/getNewResourceForm.php | 3 + ajax_htmldata/getGokbResourceDetails.php | 51 ++++++- ajax_htmldata/getPagination.php | 29 ++++ css/gokb.search.css | 166 +++++++++++++++++++++++ js/KBSearch.js | 97 ++++++++++++- js/forms/resourceNewForm.js | 40 ++++-- templates/header.php | 3 + 9 files changed, 491 insertions(+), 71 deletions(-) create mode 100644 ajax_htmldata/getPagination.php create mode 100644 css/gokb.search.css diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 0e8227a..70fcd08 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -41,6 +41,9 @@ class GOKbTools { */ private $httpClient; + + + /** * @var GOKbTools (Pattern Singleton) */ @@ -85,7 +88,6 @@ private function __construct() * each array is like [GOKb_identifer => name] */ public function searchOnGokb($name, $issn, $publisher, $searchType){ - echo 'DEBUG_ search param = name: '.$name.' issn: '.$issn.' pub: '.$publisher.' type: '.$searchType.'
    '; // query construction for packages if ((!empty($name)) && (empty($issn)) && (empty($publisher)) && ($searchType <= 0)){ @@ -94,6 +96,7 @@ public function searchOnGokb($name, $issn, $publisher, $searchType){ $query .= '?s ?o .'; $query .= 'FILTER regex(?o, "'.$name.'", "i")} ORDER BY DESC(?o)'; if($searchType == 0) $query .= 'LIMIT 5'; + //send the request and get results $tmp = $this->sendSparqlQuery($query); $res = $tmp->{"results"}->{"result"}; @@ -135,6 +138,7 @@ public function searchOnGokb($name, $issn, $publisher, $searchType){ } if($searchType == 0) $query .= 'LIMIT 5'; + //send the request and get results $tmp = $this->sendSparqlQuery($query); $res = $tmp->{"results"}->{"result"}; @@ -148,11 +152,6 @@ public function searchOnGokb($name, $issn, $publisher, $searchType){ } - - - - - $results = array($packages, $titles); return $results; @@ -226,7 +225,6 @@ private function UriToGokbId($uri) */ public function getDetails($type, $gokbID) { - echo 'DEBUG_ getDetails('.$type.','.$gokbID.')
    '; switch ($type) { case 'title': $record = $this->titleEndpoint->getRecord($gokbID, 'gokb'); @@ -235,6 +233,7 @@ public function getDetails($type, $gokbID) $record = $this->packageEndpoint->getRecord($gokbID, 'gokb'); break; default: + return null; break; } @@ -248,18 +247,20 @@ public function getDetails($type, $gokbID) /** * Extract and display the results included in an xml document * _ Recursive function _ - * @param /SimpleXMLElement XML document to treat - * @return string content + * @param $xml /SimpleXMLElement XML document to treat + * @return string content */ public function displayRecord($xml){ $string = ""; - if (count($xml->children()) > 0) { - $string = ""; + if ((count($xml->children()) > 0) && ($xml->getName() != 'TIPPs')) { + $string = "
    "; foreach ($xml->children() as $tag => $child) { - $string .= ''; - $string .= ''; - $string .= ''; + if ($tag != 'TIPPs'){ + $string .= ''; + $string .= ''; + $string .= ''; + } } $string .='
    '.$tag.''.$this->displayRecord($child).'
    '.$tag.''.$this->displayRecord($child).'
    '; } @@ -272,20 +273,71 @@ public function displayRecord($xml){ } $string .= '
    "; + + foreach ($tipps->children() as $child) { + $resource = $child->{$type}; + $resourceAttr = $resource->attributes(); + + $string .= ""; + } + + $string .= "
    "; + + return $string; + } // ------------------------------------------------------------------------- + /** + * Return resource name extract from XML metadata + * @param $record /SimpleXMLElement results of getRecord request + * @return string name of the resource + */ + function getResourceName($record){ + return $record->{'name'}; + } // ------------------------------------------------------------------------- + /** + * Return the number of TIPPs of the resource, extract from XML metadata + * @param $record /SimpleXMLElement results of getRecord request + * @return int number of TIPPs + */ + function getNbTipps($record){ + $tipps = $record->{'TIPPs'}; + $tmp = $tipps->attributes(); + return $tmp[0]; + } -} +// ------------------------------------------------------------------------- +} + ?> \ No newline at end of file diff --git a/ajax_forms/getKBSearchResults.php b/ajax_forms/getKBSearchResults.php index 3974946..ed67b03 100644 --- a/ajax_forms/getKBSearchResults.php +++ b/ajax_forms/getKBSearchResults.php @@ -4,64 +4,89 @@ searchOnGokb($_POST['name'], $_POST['issn'], $_POST['publisher'], $_POST['type']); $nb_packages = count($results[0]); $nb_titles = count($results[1]); +$isPaginated = ((isset($_POST['paginate'])) && ($_POST['paginate'] == true)); - +//Display packages results if ($nb_packages > 0){ - echo "
    "; - echo "

    Packages

    ".$nb_packages." results "; + echo " Packages View all packages results
    "; - echo ''; + echo '
    '; foreach ($results[0] as $key => $value) { - echo "'; - echo ''; + echo ''; + echo ''; echo ""; } echo '
    "; + echo "
    "; echo ' - '.$value; - echo '
    '; echo "
    "; } - - +//Display titles results if ($nb_titles > 0){ -echo "
    "; -echo '

    Issues

    '.$nb_titles.' resultsView all issues results
    '; + echo "
    "; + echo ' Issues View all issues results
    '; -echo ''; - foreach ($results[1] as $key => $value) { - echo "'; - echo ''; - echo ""; - } - echo '
    "; - echo ' - '.$value; - echo '
    '; + echo ''; + foreach ($results[1] as $key => $value) { + echo "'; + echo ''; + echo ""; + } + echo '
    '; -echo "
    "; + echo "
    "; } else { - echo "No results, please check your search fields
    "; + if (!$isPaginated) echo "No results, please check your search fields
    "; } -?> - -Back +//Display pagination +if ($isPaginated) { + switch ($_POST['type']) { + case -1: + $resType=0; + $divId = "div_packagesResults"; + break; + case 1: + $resType = 1; + $divId = "div_titlesResults"; + break; + default: + break; + } + echo paginate(count($results[$resType]), "$divId"); + echo ""; +} - - - +?> +
    + + Back + +
    + diff --git a/ajax_forms/getNewResourceForm.php b/ajax_forms/getNewResourceForm.php index d32e4a4..f5631e2 100644 --- a/ajax_forms/getNewResourceForm.php +++ b/ajax_forms/getNewResourceForm.php @@ -304,6 +304,9 @@
    diff --git a/ajax_htmldata/getGokbResourceDetails.php b/ajax_htmldata/getGokbResourceDetails.php index 91e8192..3e73424 100644 --- a/ajax_htmldata/getGokbResourceDetails.php +++ b/ajax_htmldata/getGokbResourceDetails.php @@ -1,11 +1,54 @@ - + + + +
    +
    + Resource details +
    + +getDetails($_POST['type'], $_POST['id']); -echo $tool->displayRecord($record); +$nbTipps = $tool->getNbTipps($record); + + ?> + +
    + + getResourceName($record);?> + + + () + +
    + +
    +
      +
    • Details
    • +
    • TIPPs ()
    • +
    +
    + +
    +
    + displayRecord($record); ?> +
    + +
    + +
    + + Back + +
    - ?> \ No newline at end of file +
    \ No newline at end of file diff --git a/ajax_htmldata/getPagination.php b/ajax_htmldata/getPagination.php new file mode 100644 index 0000000..4856ff3 --- /dev/null +++ b/ajax_htmldata/getPagination.php @@ -0,0 +1,29 @@ + + + +
      +
    • prev
    • "; + + for ($i=0; $i < $pagesCount ; $i++) { + $content .= "
    • ".($i+1)."
    • "; + } + + $content .= "
    • next
    • +
    + "; + return $content; +} + +?> diff --git a/css/gokb.search.css b/css/gokb.search.css new file mode 100644 index 0000000..e6bde6b --- /dev/null +++ b/css/gokb.search.css @@ -0,0 +1,166 @@ +/******************************************* + **** getKBSearchResults.php **** + *******************************************/ +.div_results{ + margin-top: 10px; + width: 745px; +} +.results_table{ + width: 745px; + height: 190px; + border-bottom: 1px solid black; +} + +.results_table_line{ + height: 20%; +} + +.results_table_title_cell{ + width: 90%; +} + +.results_table_cell{ + width: : 5%; +} + +.results_type_title{ + font-size: 150%; + font-weight: bold; + margin-bottom: 10px; +} + +.moreResults{ + float: right; +} + +.linkStyle{ + color: blue; + text-decoration: underline; +} + + + + +/******************************************* + **** getGokbResourceDetails.php **** + *******************************************/ + +#resourceDetails{ + width: 745px; +} + +#detailsTabs{ + margin-top: 15px; +} + +#detailsTabs ul{ + font: normal 14px arial, sans, sans-serif; + -list-style-type: none; + /*border-bottom: 1px solid gray;*/ + margin: 0; + padding-left:0; + padding-right:0; + padding-bottom: 26px; +} +#detailsTabs ul li{ + display: inline; + float: left; + height: 17px; + min-width:80px; + text-align:center; + text-decoration: none; + padding: 4px; + margin: 1px 0px 0px 0px; + border: 1px solid gray; + color: #666; + background-color:#eee; +} + + +#detailsTabs ul li.selected { + color: #000; + font-weight:bold; + background-color: #fff; + border-bottom: 1px solid #fff; +} + +#detailsTabs ul li:hover{ + color: #000; + font-weight:bold; + background-color: #fff; +} + +#detailsContainer{ + background: white; + border:1px solid gray; + height:350px; + width:100%; + padding:0; + margin:0; + margin-top: 1px; + left:0; + top:0; +} + +#resourceName{ + text-align: center; + font-size: 110%; +} + +#resType{ + text-decoration: italic; + margin-left: 25px; +} + +#resName{ + font-weight: bold; +} + +/******************************************* + **** getPagination.php **** + *******************************************/ +#pageIterator { + margin-top: 10px; + text-align: center; + font-weight: normal; + color: #000; +} + +#pageIterator ul li{ + display: inline; + border: 1px solid black; + padding: 5px; +} + +#pageIterator ul li:hover{ + + font-weight:bold; + background-color: #fff; + border: 2px solid black; +} + +#pageIterator ul li.active{ + background-color: #e9edf0; + border: 1px solid #e9edf0; +} +#pageIterator ul li.active:hover{ + font-weight: normal; +} + +#pageIterator ul li.invisible{ + display: none; +} + + + +/******************************************* + **** Multiple **** + *******************************************/ + +.invisible{ + display: none; +} + +.search_nav_button{ //both + margin-top: 5px; +} \ No newline at end of file diff --git a/js/KBSearch.js b/js/KBSearch.js index 4b350c9..ef1afea 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -1,22 +1,18 @@ -$(document).ready(function(){ - console.debug("kbsearch.js is ready"); -}); function allResults(s_name, s_pub, s_type){ $.ajax({ type: "POST", url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", cache: false, - data: {name:s_name, issn:'', publisher:s_pub, type:s_type}, + data: {name:s_name, issn:'', publisher:s_pub, type:s_type, paginate:true}, success: function(res) { - document.getElementById("TB_ajaxContent").innerHTML = ""; + document.getElementById("TB_ajaxContent").innerHTML = ""; $('#TB_ajaxContent').append(res); } }); } function getDetails(s_type, s_gokbID){ - console.debug("getDetails !"); $.ajax({ type: "POST", url: "ajax_htmldata.php?action=getGokbResourceDetails&height=503&width=775&resourceID=&modal=true", @@ -28,4 +24,93 @@ function getDetails(s_type, s_gokbID){ } }); +} + +function loadDetailsContent(element_nb){ + + var tabs=document.getElementById("detailsTabs").getElementsByTagName("li"); + var divs = document.getElementById("detailsContainer").getElementsByTagName("div"); + + for (var i = 0; i < tabs.length; i++) { + if(i == element_nb){ + tabs[i].className="selected"; + divs[i].className=""; + }else{ + tabs[i].className=""; + divs[i].className="invisible"; + } + + } + +} + +function iterator(page){ + //initialisation + var divId = $("#currentDiv").val(); + var old = parseInt($("#currentPage").val()); + var pagination = document.getElementById("pageIterator").getElementsByTagName("li"); + + var lines = document.getElementById(divId).getElementsByTagName("tr"); + var nbTipps = lines.length; + var nbPages = Math.ceil(nbTipps/10); + + //updating current page + document.getElementById('currentPage').value = page; + pagination[old+1].className = ""; + pagination[page+1].className = "active"; + + + var itStart= page*10; + var stop = itStart+10; + + //Display/hide prev and next buttons + if (itStart == 0) { document.getElementById('previousTipps').className='invisible'; } + else { document.getElementById('previousTipps').className=''; } + + if (page == (nbPages-1)){ document.getElementById('nextTipps').className='invisible'; } + else { document.getElementById('nextTipps').className=''; } + + if (stop >= nbTipps) { stop=nbTipps; } + + + //hide previous page + var disableStart = old*10; + var disableStop = (old+1)*10; + if (disableStop > nbTipps) disableStop=nbTipps; + + console.debug("disable lines "+disableStart+" to "+disableStop); + for (var i = disableStart; i < disableStop; i++) { + lines[i].className="invisible"; + }; + + //display current page + console.debug("display lines "+itStart+" to "+stop); + for (var i=itStart; i truncate + if (nbPages > 15){ + + } + + +} + +function navIterator(op){ + var current = parseInt($("#currentPage").val()); + var param = 0; + + switch(op){ + case '+': + param = current+1; + break; + case '-': + param = current-1; + break; + default: + break; + } + + iterator(param); } \ No newline at end of file diff --git a/js/forms/resourceNewForm.js b/js/forms/resourceNewForm.js index 0d5e19b..551324c 100644 --- a/js/forms/resourceNewForm.js +++ b/js/forms/resourceNewForm.js @@ -22,7 +22,6 @@ }); $("#search").click(function(){ - console.debug("testLog _ searchclick ok"); searchGokb(); }); @@ -255,7 +254,6 @@ function submitResource(status){ - alert("test"); orderTypeList =''; $(".orderTypeID").each(function(id) { @@ -313,19 +311,35 @@ function submitResource(status){ function searchGokb(){ - $.ajax({ - type: "POST", - url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", - cache: false, - data: {name:$('#titleText').val(), issn:$('#ISSNText').val(), publisher:$('#providerText').val(), type:0}, - success: function(res) { - document.getElementById("TB_ajaxContent").innerHTML = ""; - $('#TB_ajaxContent').append(res); - - } - }); + if (validateSearchFields() === true){ + $.ajax({ + type: "POST", + url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {name:$('#titleText').val(), issn:$('#ISSNText').val(), publisher:$('#providerText').val(), type:0}, + success: function(res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + + } + }); + } + else { + $("#span_error_search").html("At least one search field is required to search"); + } } +function validateSearchFields(){ + var name = $('#titleText').val(); + var publisher = $('#providerText').val(); + var issn = $('#ISSNText').val(); + + if (((name != null) && (name != '')) || ((publisher != '') && (publisher != null)) || ((issn != null) && (issn != ''))){ + return true; + } else { + return false; + } +} //kill all binds done by jquery live function kill(){ diff --git a/templates/header.php b/templates/header.php index 6af3bc0..61dae48 100644 --- a/templates/header.php +++ b/templates/header.php @@ -45,6 +45,9 @@ + + + From 2a5f3f7ae1f573278e4be9b8b535e63b2870fb6e Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Fri, 3 Jul 2015 17:03:00 +0200 Subject: [PATCH 06/32] =?UTF-8?q?am=C3=A9lioration=20pagination?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/classes/domain/GOKbTools.php | 8 +++--- ajax_htmldata/getPagination.php | 2 ++ css/gokb.search.css | 23 ++++++++++++++-- js/KBSearch.js | 43 ++++++++++++++++++++++++------ 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 70fcd08..66fae87 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -254,11 +254,11 @@ public function getDetails($type, $gokbID) public function displayRecord($xml){ $string = ""; if ((count($xml->children()) > 0) && ($xml->getName() != 'TIPPs')) { - $string = ""; + $string = "
    "; foreach ($xml->children() as $tag => $child) { if ($tag != 'TIPPs'){ - $string .= ''; - $string .= ''; + $string .= ''; + $string .= ''; $string .= ''; } } @@ -279,7 +279,7 @@ public function displayRecord($xml){ elseif ($xml != "") { $string = $xml; } else { - $string = "Empty"; //italic ? + $string = "Empty"; } } return $string; diff --git a/ajax_htmldata/getPagination.php b/ajax_htmldata/getPagination.php index 4856ff3..64bec73 100644 --- a/ajax_htmldata/getPagination.php +++ b/ajax_htmldata/getPagination.php @@ -15,11 +15,13 @@ function paginate($nbEntry, $divId, $nbPerPage=10){
    • prev
    • "; + $content .= ""; for ($i=0; $i < $pagesCount ; $i++) { $content .= "
    • ".($i+1)."
    • "; } + $content .= ""; $content .= "
    • next
    "; diff --git a/css/gokb.search.css b/css/gokb.search.css index e6bde6b..d7f868a 100644 --- a/css/gokb.search.css +++ b/css/gokb.search.css @@ -116,11 +116,29 @@ font-weight: bold; } +#detailsTab{ + margin: 10px; +} + +#detailsTab tr{ + height: 10%; +} + +.detailsTab_val{ + padding-left: 5px; +} + +.detailsTab_tag{ + font-weight: bold; + padding-right: 10px; + text-align: right; +} + /******************************************* **** getPagination.php **** *******************************************/ #pageIterator { - margin-top: 10px; + margin: 10px; text-align: center; font-weight: normal; color: #000; @@ -129,6 +147,7 @@ #pageIterator ul li{ display: inline; border: 1px solid black; + margin-left: 5px; padding: 5px; } @@ -161,6 +180,6 @@ display: none; } -.search_nav_button{ //both +.search_nav_button{ margin-top: 5px; } \ No newline at end of file diff --git a/js/KBSearch.js b/js/KBSearch.js index ef1afea..db48b26 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -52,12 +52,13 @@ function iterator(page){ var lines = document.getElementById(divId).getElementsByTagName("tr"); var nbTipps = lines.length; - var nbPages = Math.ceil(nbTipps/10); + //var nbPages = Math.ceil(nbTipps/10); + var nbPages = pagination.length - 4; //updating current page document.getElementById('currentPage').value = page; - pagination[old+1].className = ""; - pagination[page+1].className = "active"; + //pagination[old+2].className = ""; + var itStart= page*10; @@ -81,7 +82,7 @@ function iterator(page){ console.debug("disable lines "+disableStart+" to "+disableStop); for (var i = disableStart; i < disableStop; i++) { lines[i].className="invisible"; - }; + } //display current page console.debug("display lines "+itStart+" to "+stop); @@ -89,11 +90,37 @@ function iterator(page){ lines[i].className=""; } - //Display up to 15 pages number, if there are more --> truncate - if (nbPages > 15){ - + //Display up to 13 pages number, if there are more --> truncate + if (nbPages > 13){ + //case 1: current page is at the beginning + if (page < 7){ + document.getElementById("beginning").className = "invisible"; + document.getElementById("end").className = ""; + for (var i=0; inbPages-6) { + document.getElementById("beginning").className = ""; + document.getElementById("end").className = "invisible"; + for (var i = 0; inbPages-14) pagination[i+2].className = ""; + else pagination[i+2].className='invisible'; + } + } + //case 3: current page is in the middle + else { + document.getElementById("beginning").className = ""; + document.getElementById("end").className = ""; + for (var i = 0; i= page-6) && (i <= page+6)) pagination[i+2].className = ""; + else pagination[i+2].className='invisible'; + } + } } - + pagination[page+2].className = "active"; } From 786350bfdb1625e851dfc7ea56ece4f32c1e7700 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Thu, 9 Jul 2015 14:39:04 +0200 Subject: [PATCH 07/32] back button display previous screen --- ajax_forms/getKBSearchResults.php | 5 +- ajax_htmldata/getGokbResourceDetails.php | 3 +- js/KBSearch.js | 113 +++++++++++++++++++++-- js/forms/resourceNewForm.js | 8 +- 4 files changed, 114 insertions(+), 15 deletions(-) diff --git a/ajax_forms/getKBSearchResults.php b/ajax_forms/getKBSearchResults.php index ed67b03..5062b2d 100644 --- a/ajax_forms/getKBSearchResults.php +++ b/ajax_forms/getKBSearchResults.php @@ -82,10 +82,9 @@ ?>
    - - Back - + +
    diff --git a/ajax_htmldata/getGokbResourceDetails.php b/ajax_htmldata/getGokbResourceDetails.php index 3e73424..98fb657 100644 --- a/ajax_htmldata/getGokbResourceDetails.php +++ b/ajax_htmldata/getGokbResourceDetails.php @@ -45,8 +45,7 @@
    - - Back +
    diff --git a/js/KBSearch.js b/js/KBSearch.js index db48b26..5c17cf6 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -1,5 +1,14 @@ +/** +* Send a SPARQL request to filter results with criteria +* @param: s_name string content of "Name" field (new resource form) +* @param: s_pub string content of "Provider" field (new resource form) +* @param: s_type int searchType (0 = all ; -1 = packages only; 1=issues only) +* +* @return: nothing but display results thanks to ajax and php treatment +*/ function allResults(s_name, s_pub, s_type){ + $.ajax({ type: "POST", url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", @@ -10,8 +19,18 @@ function allResults(s_name, s_pub, s_type){ $('#TB_ajaxContent').append(res); } }); + window.history.pushState({funcName:'allResults', param:[s_name,s_pub,s_type]}, 'test', null); } +/*******************************************************************************************************/ + +/** +* Send an OAI GetRecord request and display results +* @param: s_type string type of the resource (title or package) +* @param: s_gokbID string identifier of the searched resource +* +* @return: nothing but display results thanks to ajax and treatment +*/ function getDetails(s_type, s_gokbID){ $.ajax({ type: "POST", @@ -23,9 +42,17 @@ function getDetails(s_type, s_gokbID){ $('#TB_ajaxContent').append(res); } }); - + window.history.pushState({funcName:'getDetails', param:{s_type,s_gokbID}}, 'test', null); } +/*******************************************************************************************************/ + +/** +* Manage the details tabs (global detail or TIPPs) +* @param: element_nb int index of selected tab +* +* @return: nothing but display the right content +*/ function loadDetailsContent(element_nb){ var tabs=document.getElementById("detailsTabs").getElementsByTagName("li"); @@ -44,23 +71,23 @@ function loadDetailsContent(element_nb){ } +/*******************************************************************************************************/ + +/** +* Manage pagination of results +* @param: page int the page of results to display +*/ function iterator(page){ - //initialisation var divId = $("#currentDiv").val(); var old = parseInt($("#currentPage").val()); var pagination = document.getElementById("pageIterator").getElementsByTagName("li"); var lines = document.getElementById(divId).getElementsByTagName("tr"); var nbTipps = lines.length; - //var nbPages = Math.ceil(nbTipps/10); var nbPages = pagination.length - 4; - //updating current page document.getElementById('currentPage').value = page; - //pagination[old+2].className = ""; - - var itStart= page*10; var stop = itStart+10; @@ -123,7 +150,12 @@ function iterator(page){ pagination[page+2].className = "active"; } +/*******************************************************************************************************/ +/** +* Manage the next/prev button of pagination +* @param: op char next = '+', prev = '-' +*/ function navIterator(op){ var current = parseInt($("#currentPage").val()); var param = 0; @@ -140,4 +172,71 @@ function navIterator(op){ } iterator(param); +} +/*******************************************************************************************************/ + +/** +* Recall of the search function when the button 'back' is pressed +* @param: s_name string Name field content +* @param: s_pub string Provoder field content +*/ +function searchGokbBack(s_name,s_pub){ + console.debug("appel à searchGokbBack"); + $.ajax({ + type: "POST", + url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {name:s_name, issn:"", publisher:s_pub, type:0}, + success: function(res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + }); + window.history.pushState(null,null,null); + window.history.pushState({funcName:'searchGokbBack', param:[s_name, s_pub]}, 'test', null); +} +/*******************************************************************************************************/ + +/** +* Manage the 'back' button +*/ +function goBack(){ + //Get the previous state + window.history.go(-1); + var myState = window.history.state; + + if (myState == null){ //Display the "Add New resource form" + console.debug("No previous state"); + $.ajax({ + type: "POST", + url: "ajax_forms.php?action=getNewResourceForm&height=503&width=775&resourceID=&modal=true", + cache: false, + + success: function(res){ + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + }); + + }else{ //display the previous screen by calling the last function + var toCall = myState.funcName; + var funcParam = myState.param; + var max = funcParam.length; + + var toDo = toCall+"("; + + for (var i=0; i Date: Mon, 27 Jul 2015 10:36:49 +0200 Subject: [PATCH 08/32] =?UTF-8?q?EN=20TRAVAUX=20=5F=20Impl=C3=A9mentation?= =?UTF-8?q?=20fonction=20d'import=20commune,=20d=C3=A9but=20code=20sp?= =?UTF-8?q?=C3=A9=20pour=20import=20Gokb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/classes/domain/GOKbTools.php | 33 ++- admin/classes/domain/Identifier.php | 50 ++++ admin/classes/domain/ImportTool.php | 325 +++++++++++++++++++++++ admin/classes/domain/Resource.php | 31 +++ ajax_htmldata/getGokbResourceDetails.php | 38 +-- ajax_htmldata/getKBSearchResults.php | 92 +++++++ ajax_processing/importFromGOKb.php | 70 +++++ css/gokb.search.css | 2 +- index.php | 2 - js/KBSearch.js | 30 ++- js/forms/resourceNewForm.js | 2 +- 11 files changed, 636 insertions(+), 39 deletions(-) create mode 100644 admin/classes/domain/Identifier.php create mode 100644 admin/classes/domain/ImportTool.php create mode 100644 ajax_htmldata/getKBSearchResults.php create mode 100644 ajax_processing/importFromGOKb.php diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 66fae87..037a2ec 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -225,20 +225,9 @@ private function UriToGokbId($uri) */ public function getDetails($type, $gokbID) { - switch ($type) { - case 'title': - $record = $this->titleEndpoint->getRecord($gokbID, 'gokb'); - break; - case 'package': - $record = $this->packageEndpoint->getRecord($gokbID, 'gokb'); - break; - default: - return null; - break; - } - - - $rec = $record->{'GetRecord'}->{'record'}->{'metadata'}->{'gokb'}->{$type}; + $record = $this->getRecord($type, $gokbID); + $rec = $record->{'metadata'}->{'gokb'}->{$type}; + return $rec; } @@ -336,7 +325,23 @@ function getNbTipps($record){ // ------------------------------------------------------------------------- + function getRecord($type, $id){ + + switch ($type) { + case 'title': + $record = $this->titleEndpoint->getRecord($id, 'gokb'); + break; + case 'package': + $record = $this->packageEndpoint->getRecord($id, 'gokb'); + break; + default: + return null; + break; + } + $rec = $record->{'GetRecord'}->{'record'}; + return $rec; + } } diff --git a/admin/classes/domain/Identifier.php b/admin/classes/domain/Identifier.php new file mode 100644 index 0000000..709be30 --- /dev/null +++ b/admin/classes/domain/Identifier.php @@ -0,0 +1,50 @@ +. +** +************************************************************************************************************************** +*/ + +/* Put in admin/classes/domain */ + + +class Identifier extends DatabaseObject { + //protected function overridePrimaryKeyName() {}; //TODO_A quoi ça sert ?? idem pour defineRelationShip etc ... + + + public function getIdentifierTypeID($type){ + $dbName = $config->settings->organizationsDatabaseName; + $query = "SELECT identifierTypeID FROM $dbName.IdentifierType WHERE UPPER(identifierName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; + $result = $this->db->processQuery($query); + + if (count($result) == 0 ) {//this type doesn't exist + //Have we to create this type ? + if (is_numeric($type)){ //this identifier type will be considered as "isxn" (come from csv import) + $id = 1; + } else { //we need to create this type + $query = "INSERT INTO $dbName.IdentifierType SET identifierName='". mysql_escape_string($type) . "'"; //TODO_mysql_escape_string() OBSOLETE --> mysqli_escape_string + $result = $this->db->processQuery($query); + $id = $result[0]; + } + } else { + $id = $result[0]; + } + + return $id ; + + } + +} +?> \ No newline at end of file diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php new file mode 100644 index 0000000..fa33252 --- /dev/null +++ b/admin/classes/domain/ImportTool.php @@ -0,0 +1,325 @@ +getResourceByIsbnOrISSN($identifiers); + $idExist = count($resource); + + if ($idExist == 0) { //resource doesn't exist, we have to create it + $hasToBeInserted = true; + } + elseif ($datas['parentResource']){ //resource exists and got a parent, test title + parent + $res = $resource[0]; + $currentResourceID = $res->resourceID; + $parents = $res->getParentResources(); + $nbParents = count($parents); + + if ($nbParents == 0 ){ //existing resource doesn't have any parent + $hasToBeInserted = true; + } else { + $hasToBeInserted = true; + foreach ($parents as $parentResource) { + if($parentResource->titleText == $datas['parentResource']){ + $hasToBeInserted = false; + } + } + } + } + else{ //Resource exists but doesn't have any parent in $datas (package or orphan resource) + $hasToBeInserted = false; + } + + + /********************************************* + ** Datas insertion ** + *********************************************/ + if ($hasToBeInserted){ + //Resource treatment + foreach ($datas as $key => $value) { + if ($key == "organization") { $org = $value; } + elseif ($key == "parentResource") { $parentName = $value; } + else { $res->$key = $value; } + } + + $res->createLoginID = $loginID; + $res->createDate = date( 'Y-m-d'); + $res->updateLoginID = ''; + $res->updateDate = ''; + $res->statusID = 1; //in progress, don't know why ... + $res->save(); + + //Resource identifiers + $res->setIdentifiers($identifiers); + + //Parent treatment _ see line 219 (import.php) + if ($parentName != null){ + // Search if such parent exists + $numberOfParents = count($resourceObj->getResourceByTitle($parentName)); + $parentID = null; + + if ($numberOfParents == 0) { // If not, create parent + $parentResource = new Resource(); + $parentResource->createLoginID = $loginID; + $parentResource->createDate = date( 'Y-m-d' ); + $parentResource->titleText = $parentName; + $parentResource->statusID = 1; + $parentResource->save(); + + $parentID = $parentResource->resourceID; + self::$parentInserted++; + + } elseif ($numberOfParents == 1) { + // Else, attach the resource to its parent. + $parentResource = $resourceObj->getResourceByTitle($parentName); + $parentID = $parentResource[0]->resourceID; + + self::$parentAttached++; + } + } + + //Save relationship + $resourceRelationship = new ResourceRelationship(); + $resourceRelationship->resourceID = $res->resourceID; + $resourceRelationship->relatedResourceID = $parentID; + $resourceRelationship->relationshipTypeID = '1'; //hardcoded because we're only allowing parent relationships + if (!$resourceRelationship->exists()) { + $resourceRelationship->save(); + } + + if ($org != null) { // Do we have to create an organization or attach the resource to an existing one? + // If we use the Organizations module + if ($config->settings->organizationsModule == 'Y') { //TODO _ hierarchy platform/provider + $dbName = $config->settings->organizationsDatabaseName; + foreach ($org as $role => $orgName) { + $organization = new Organization();//TODO _ instanciation dans une boucle j'aime pas trop ça ;) + $organizationRole = new OrganizationRole(); + $organizationID = false; + // Does the organization already exists? + $query = "SELECT count(*) AS count FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; + $result = $organization->db->processQuery($query, 'assoc'); + + if ($result['count'] == 0){ // If not, we try to create it + + $query = "INSERT INTO $dbName.Organization SET createDate=NOW(), createLoginID='$loginID', name='" . mysql_escape_string($orgName) . "'"; + try { + $result = $organization->db->processQuery($query); + $organizationID = $result; + self::$organizationsInserted++; + array_push(self::$arrayOrganizationsCreated, $orgName); + } catch (Exception $e) { + $htmlContent .= "

    Organization $orgName could not be added.

    "; + } + } + // If yes, we attach it to our resource + elseif ($result['count'] == 1) { + $query = "SELECT name, organizationID FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; + $result = $organization->db->processQuery($query, 'assoc'); + $organizationID = $result['organizationID']; + self::$organizationsAttached++; + } + else { + $htmlContent .= "

    Error: more than one organization is called $organizationName. Please consider deduping.

    "; + } + + if ($organizationID){ + // Get role + $query = "SELECT organizationRoleID from OrganizationRole WHERE shortName='" . mysql_escape_string($role) . "'"; + $result = $organization->db->processQuery($query); + $roleID = ($result[0]) ? $result[0] : 1; + // Does the organizationRole already exists? + $query = "SELECT count(*) AS count FROM $dbName.OrganizationRoleProfile WHERE organizationID=$organizationID AND organizationRoleID=$roleID"; + $result = $organization->db->processQuery($query, 'assoc'); + // If not, we try to create it + if ($result['count'] == 0) { + $query = "INSERT INTO $dbName.OrganizationRoleProfile SET organizationID=$organizationID, organizationRoleID=$roleID"; + try { + $result = $organization->db->processQuery($query); + if (!in_array($orgName, self::$arrayOrganizationsCreated)) { + self::$organizationsInserted++; + array_push(self::$arrayOrganizationsCreated, $orgName); + } + } catch (Exception $e) { + $htmlContent .= "

    Unable to associate organization $orgName with its role.

    "; + } + } + } + + // Let's link the resource and the organization. + // (this has to be done whether the module Organization is in use or not) + if ($organizationID && $roleID) { + $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); + } + } + + //TODO _ hierarchy platform/provider (packages) + if($org['platform']){ + $platformName = $org['platform']; + $providerName = $org['provider']; + } + } + // If we do not use the Organizations module + else { + foreach ($org as $role => $orgName) { + $organization = new Organization();//TODO _ instanciation dans une boucle j'aime pas trop ça ;) + $organizationRole = new OrganizationRole(); + $organizationID = false; + // Search if such organization already exists + $organizationExists = $organization->alreadyExists($orgName); + $parentID = null; + + if (!$organizationExists) { // If not, create it + $organization->shortName = $orgName; + $organization->save(); + $organizationID = $organization->organizationID(); + self::$organizationsInserted++; + array_push(self::$arrayOrganizationsCreated, $orgName); + } elseif ($organizationExists == 1) { // Else, + $organizationID = $organization->getOrganizationIDByName($orgName); + self::$organizationsAttached++; + } else { + $htmlContent .= "

    Error: more than one organization is called $orgName. Please consider deduping.

    "; + } + + $organizationRoles = $organizationRole->getArray(); + if (($roleID = array_search($role, $organizationRoles)) == 0) { + // If role is not found, fallback to the first one. + $roleID = '1'; + } + + // Let's link the resource and the organization. + // (this has to be done whether the module Organization is in use or not) + if ($organizationID && $roleID) { + $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); + } + } + } + } + } + } + +// ------------------------------------------------------------------------- + + private function setResourceOrganizationLink($roleID, $resourceID, $organizationID) { + $organizationLink = new ResourceOrganizationLink(); + $organizationLink->organizationRoleID = $roleID; + $organizationLink->resourceID = $resource->resourceID; + $organizationLink->organizationID = $organizationID; + $organizationLink->save(); + } + + +// ------------------------------------------------------------------------- + + /**************************** + * Accessors * + ****************************/ + public static function getNbRow(){ + return self::$row; + } +// ------------------------------------------------------------------------- + public static function getNbInserted(){ + return self::$inserted; + } +// ------------------------------------------------------------------------- + public static function getNbParentInserted(){ + return self::$parentInserted; + } +// ------------------------------------------------------------------------- + public static function getNbParentAttached(){ + return self::$parentAttached; + } +// ------------------------------------------------------------------------- + public static function getNbOrganizationsInserted(){ + return self::$organizationsInserted; + } +// ------------------------------------------------------------------------- + public static function getNbOrganizationsAttached(){ + return self::$organizationsAttached; + } +// ------------------------------------------------------------------------- + +} + + +?> \ No newline at end of file diff --git a/admin/classes/domain/Resource.php b/admin/classes/domain/Resource.php index 280bb27..612c15b 100644 --- a/admin/classes/domain/Resource.php +++ b/admin/classes/domain/Resource.php @@ -2336,6 +2336,37 @@ public function setIsbnOrIssn($isbnorissns) { } } + + /******************************** + * NEW FUNCTION * + ********************************/ + /* TODO + /!\ /!\ /!\ + /!\ rgrep IsbnOrIssn avant de tout casser !! /!\ + /!\ /!\ /!\ + */ + + + /** + * Fill the Identifier table in DB + * @param $identifiers array array of all identifiers (type => id) (if type isn't known, don't put any key) + * + */ + public function setIdentifiers($identifiers){ + + foreach ($identifiers as $key => $value) { + $identifier = new Identifier(); + $identifier->resourceID = $this->resourceID; + $identifier->identifierTypeID = $identifier->getIdentifierTypeID($key); + $identifier->identifier = $value; + + $identifier->save(); + } + } + + + + } ?> diff --git a/ajax_htmldata/getGokbResourceDetails.php b/ajax_htmldata/getGokbResourceDetails.php index 98fb657..bf9ee7e 100644 --- a/ajax_htmldata/getGokbResourceDetails.php +++ b/ajax_htmldata/getGokbResourceDetails.php @@ -2,35 +2,39 @@ -
    -
    - Resource details -
    getDetails($_POST['type'], $_POST['id']); -$nbTipps = $tool->getNbTipps($record); + $tool = GOKbTools::getInstance(); + $record = $tool->getDetails($_POST['type'], $_POST['id']); + $nbTipps = $tool->getNbTipps($record); - ?> +?> -
    - - getResourceName($record);?> - - - () +
    +
    + + + details for + getResourceName($record);?>
    • Details
    • -
    • TIPPs ()
    • +
    • + +
    diff --git a/ajax_htmldata/getKBSearchResults.php b/ajax_htmldata/getKBSearchResults.php new file mode 100644 index 0000000..8a348c3 --- /dev/null +++ b/ajax_htmldata/getKBSearchResults.php @@ -0,0 +1,92 @@ +
    +
    + Add new resource - Search results +
    +searchOnGokb($_POST['name'], $_POST['issn'], $_POST['publisher'], $_POST['type']); +$nb_packages = count($results[0]); +$nb_titles = count($results[1]); +$isPaginated = ((isset($_POST['paginate'])) && ($_POST['paginate'] == true)); + +//Display packages results +if ($nb_packages > 0){ + echo "
    "; + echo " Packages View all packages results
    "; + + echo '
    '.$tag.''.$this->displayRecord($child).'
    '.$tag.''.$this->displayRecord($child).'
    '; + foreach ($results[0] as $key => $value) { + echo "'; + echo ''; + echo ""; + } + echo '
    "; + echo ' - '.$value; + echo '
    '; + echo ""; +} + +//Display titles results +if ($nb_titles > 0){ + echo "
    "; + echo ' Titles View all titles results
    '; + + + echo ''; + foreach ($results[1] as $key => $value) { + echo "'; + echo ''; + echo ""; + } + echo '
    '; + + echo "
    "; +} else { + if (!$isPaginated) echo "No results, please check your search fields
    "; +} + +//Display pagination +if ($isPaginated) { + switch ($_POST['type']) { + case -1: + $resType=0; + $divId = "div_packagesResults"; + break; + case 1: + $resType = 1; + $divId = "div_titlesResults"; + break; + default: + break; + } + echo paginate(count($results[$resType]), "$divId"); + echo ""; +} + + +?> +
    + + + +
    + + + diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php new file mode 100644 index 0000000..5545be0 --- /dev/null +++ b/ajax_processing/importFromGOKb.php @@ -0,0 +1,70 @@ +
    Import from GOKb . php
    +getRecord($_POST['type'], $_POST['id']); +//echo "DEBUG_ IFG _ request 1 ok\n"; +$nbTipps = $gokbTool->getNbTipps($record); + +//echo "DEBUG_ IFG _ all request ok\n"; +$datas = array(); +$identifiers = array("gokb" => $_POST['id']); + +$recordDetails=$record->{'metadata'}->{'gokb'}->{$_POST['type']}; + +$datas['titleText'] = $recordDetails->{'name'}; + +$string = ""; + + +if ($_POST['type'] == 'package'){ + //resource parent (package lui meme) + + + //ensemble des tipps (boucle) + $tippsDatas = array(); + +} else{ //import title; + //TODO medium = resourceType (ajout manuel only pour l'instant) + + //Organization + $org = $recordDetails->{"publisher"}->{'name'}; + if ($org ){ + $data["organization"]=array("publisher" => $org); + $string .= "insertion de datas['organization'] = ".$org."
    "; + } + + + //Identifiers _ URL ? + $xml = $recordDetails->{'identifiers'}; + $xmlIDs =$xml->children(); + + foreach ($xmlIDs as $key => $value) { + $tmp = $value->attributes(); + $datas[$tmp[0]] = $tmp[1]; + $string .= "insertion de datas[".$tmp[0]."] = ".$tmp[1]."
    "; + } + + + + //History / Aliases + + +echo "$string"; + //$datas['parentResource'] ? + + +} + + + +?> + +Import OK !! \ No newline at end of file diff --git a/css/gokb.search.css b/css/gokb.search.css index d7f868a..d53746b 100644 --- a/css/gokb.search.css +++ b/css/gokb.search.css @@ -20,7 +20,7 @@ } .results_table_cell{ - width: : 5%; + width: 5%; } .results_type_title{ diff --git a/index.php b/index.php index b4d6245..c9a5f79 100644 --- a/index.php +++ b/index.php @@ -38,8 +38,6 @@ $_SESSION['ref_script']=$currentPage; - - ?>
    diff --git a/js/KBSearch.js b/js/KBSearch.js index 5c17cf6..565ed69 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -11,7 +11,7 @@ function allResults(s_name, s_pub, s_type){ $.ajax({ type: "POST", - url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", cache: false, data: {name:s_name, issn:'', publisher:s_pub, type:s_type, paginate:true}, success: function(res) { @@ -46,11 +46,33 @@ function getDetails(s_type, s_gokbID){ } /*******************************************************************************************************/ +function select(s_type, s_gokbID){ + console.debug("fonction select("+s_type+","+s_gokbID+")"); + $.ajax({ + type: "POST", + url: "ajax_processing.php?action=importFromGOKb&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {type:s_type, id:s_gokbID}, + success: function(res){ + console.debug("SELECT: ajax ok"); + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + + + }); + + //TODO history + + +} + +/*******************************************************************************************************/ /** * Manage the details tabs (global detail or TIPPs) * @param: element_nb int index of selected tab -* +* * @return: nothing but display the right content */ function loadDetailsContent(element_nb){ @@ -184,7 +206,7 @@ function searchGokbBack(s_name,s_pub){ console.debug("appel à searchGokbBack"); $.ajax({ type: "POST", - url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", cache: false, data: {name:s_name, issn:"", publisher:s_pub, type:0}, success: function(res) { @@ -239,4 +261,4 @@ function goBack(){ } -} \ No newline at end of file +} diff --git a/js/forms/resourceNewForm.js b/js/forms/resourceNewForm.js index 95df8e0..c7afe4d 100644 --- a/js/forms/resourceNewForm.js +++ b/js/forms/resourceNewForm.js @@ -314,7 +314,7 @@ function searchGokb(s_name, s_issn, s_pub){ if (validateSearchFields() === true){ $.ajax({ type: "POST", - url: "ajax_forms.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", cache: false, data: {name:s_name, issn:s_issn, publisher:s_pub, type:0}, success: function(res) { From d5ef9573a02158b2a4a916d779a5fa6ccb4eeb2f Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Wed, 29 Jul 2015 16:39:22 +0200 Subject: [PATCH 09/32] 0729 --- admin/classes/common/DBService.php | 1 + admin/classes/domain/AcquisitionType.php | 8 + admin/classes/domain/Identifier.php | 52 +- admin/classes/domain/ImportTool.php | 531 ++++----- admin/classes/domain/Resource.php | 1281 ++++++++++------------ ajax_processing/importFromGOKb.php | 33 +- 6 files changed, 935 insertions(+), 971 deletions(-) diff --git a/admin/classes/common/DBService.php b/admin/classes/common/DBService.php index c7f1c5a..0400572 100644 --- a/admin/classes/common/DBService.php +++ b/admin/classes/common/DBService.php @@ -37,6 +37,7 @@ protected function dealloc() { protected function checkForError() { if ($this->error = mysql_error($this->db)) { throw new Exception("There was a problem with the database: " . $this->error); + echo "DEBUG _ ERROR process query : ".$this->error; } } diff --git a/admin/classes/domain/AcquisitionType.php b/admin/classes/domain/AcquisitionType.php index ef3c6a9..bdf6453 100644 --- a/admin/classes/domain/AcquisitionType.php +++ b/admin/classes/domain/AcquisitionType.php @@ -64,6 +64,14 @@ public function getNumberOfChildren(){ } + public static function getAcquisitionTypeID($type){ + $query = "SELECT acquisitionTypeID FROM AcquisitionType WHERE shortName = '".$type."';"; + + $result = $this->db->processQuery($query, 'assoc'); + + return $result[0]; + } + } diff --git a/admin/classes/domain/Identifier.php b/admin/classes/domain/Identifier.php index 709be30..fe4b95b 100644 --- a/admin/classes/domain/Identifier.php +++ b/admin/classes/domain/Identifier.php @@ -1,40 +1,42 @@ . -** -************************************************************************************************************************** -*/ + * ************************************************************************************************************************* + * * CORAL Resources Module v. 1.0 + * * + * * Copyright (c) 2010 University of Notre Dame + * * + * * This file is part of CORAL. + * * + * * CORAL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * * + * * CORAL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License along with CORAL. If not, see . + * * + * ************************************************************************************************************************* + */ /* Put in admin/classes/domain */ - class Identifier extends DatabaseObject { + //protected function overridePrimaryKeyName() {}; //TODO_A quoi ça sert ?? idem pour defineRelationShip etc ... - public function getIdentifierTypeID($type){ - $dbName = $config->settings->organizationsDatabaseName; - $query = "SELECT identifierTypeID FROM $dbName.IdentifierType WHERE UPPER(identifierName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; + public function getIdentifierTypeID($type) { + $config = new Configuration(); + $dbName = $config->settings->resourcesDatabaseName; + $query = "SELECT identifierTypeID FROM IdentifierType WHERE UPPER(identifierName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; +//$query = "SELECT identifierTypeID FROM $dbName.IdentifierType WHERE UPPER(identifierName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; $result = $this->db->processQuery($query); - - if (count($result) == 0 ) {//this type doesn't exist + + if (count($result) == 0) {//this type doesn't exist //Have we to create this type ? - if (is_numeric($type)){ //this identifier type will be considered as "isxn" (come from csv import) + if (is_numeric($type)) { //this identifier type will be considered as "isxn" (come from csv import) $id = 1; } else { //we need to create this type - $query = "INSERT INTO $dbName.IdentifierType SET identifierName='". mysql_escape_string($type) . "'"; //TODO_mysql_escape_string() OBSOLETE --> mysqli_escape_string + $query = "INSERT INTO $dbName.IdentifierType SET identifierName='" . mysql_escape_string($type) . "'"; //TODO_mysql_escape_string() OBSOLETE --> mysqli_escape_string $result = $this->db->processQuery($query); $id = $result[0]; } @@ -42,9 +44,9 @@ public function getIdentifierTypeID($type){ $id = $result[0]; } - return $id ; - + return $id; } } + ?> \ No newline at end of file diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index fa33252..1a52ed6 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -1,325 +1,360 @@ config = new Configuration(); self::$row = 0; - self::$inserted = 0; - self::$parentInserted = 0; - self::$parentAttached = 0; - self::$organizationsInserted = 0; - self::$organizationsAttached = 0; - self::$arrayOrganizationsCreated = array(); + self::$inserted = 0; + self::$parentInserted = 0; + self::$parentAttached = 0; + self::$organizationsInserted = 0; + self::$organizationsAttached = 0; + self::$arrayOrganizationsCreated = array(); } // ------------------------------------------------------------------------- /** - * add a resource to the database - * @param $datas array all datas about the resource - * @param $identifiers array list of identifiers - */ + * add a resource to the database + * @param $datas array all datas about the resource + * @param $identifiers array list of identifiers (type =>identifier) + */ public function addResource($datas, $identifiers) { - $res = new Resource(); - $res_tmp = new Resource(); //useless ? - + $res_tmp = new Resource(); $org = null; $parentName = null; - $hasToBeInserted = false; - $htmlContent = ''; + $htmlContent = ''; //TODO _ DEBUG _ Displaying after select + + /******************************************* + * * Has the resource to be inserted ? ** + * ******************************************/ + $hasToBeInserted = $this->hasResourceToBeInserted($datas, $identifiers); + + /**************************************** + * * Datas insertion ** + * ***************************************/ + if ($hasToBeInserted) { + $res = $res_tmp->getNewInitializedResource(); + echo "DEBUG _ Resource insertion !!
    "; + + //Resource treatment + foreach ($datas as $key => $value) { + if ($key == "organization") { + $org = $value; + } elseif ($key == "parentResource") { + $parentName = $value; + } else { + $res->$key = (string) $value; + } + } + $res->save(); + + //Resource identifiers treatment + $res->setIdentifiers($identifiers); + + //Parent treatment + if ($parentName != null) { + $parentID = $this->parentTreatment($parentName); + $this->setResourcesRelationship($res->resourceID, $parentID); + } + + if ($org != null) { // Do we have to create an organization or attach the resource to an existing one? + $this->organizationTreatment($org, $res->resourceID); + } + } else { + echo "DEBUG _ Resource not inserted !
    "; + } + } + +// ------------------------------------------------------------------------- + + private function setResourceOrganizationLink($roleID, $resourceID, $organizationID) { + $organizationLink = new ResourceOrganizationLink(); + $organizationLink->organizationRoleID = $roleID; + $organizationLink->resourceID = $resourceID; + $organizationLink->organizationID = $organizationID; + $organizationLink->save(); + } + +// ------------------------------------------------------------------------- + + /** + * Check if this resource already exist and if we have to add it in DB + * @param type $datas array, all resource datas + * @param type $identifiers array, all resource's identifiers + * @return boolean true if the resource has to be inserterted, false else + */ + private function hasResourceToBeInserted($datas, $identifiers) { + $res_tmp = new Resource(); + $hasToBeInserted = false; - /********************************************** - ** Has the resource to be inserted ? ** - **********************************************/ - //Identifiers test - $resource = $res_tmp->getResourceByIsbnOrISSN($identifiers); - $idExist = count($resource); + $resource = $res_tmp->getResourceByIdentifiers($identifiers); - if ($idExist == 0) { //resource doesn't exist, we have to create it + if (count($resource) == 0) { //resource doesn't exist, we have to create it $hasToBeInserted = true; - } - elseif ($datas['parentResource']){ //resource exists and got a parent, test title + parent + } elseif ($datas['parentResource']) { //resource exists and got a parent, test title + parent $res = $resource[0]; - $currentResourceID = $res->resourceID; + //$currentResourceID = $res->resourceID; $parents = $res->getParentResources(); $nbParents = count($parents); - if ($nbParents == 0 ){ //existing resource doesn't have any parent + if ($nbParents == 0) { //existing resource doesn't have any parent $hasToBeInserted = true; } else { $hasToBeInserted = true; foreach ($parents as $parentResource) { - if($parentResource->titleText == $datas['parentResource']){ + if ($parentResource->titleText == $datas['parentResource']) { $hasToBeInserted = false; } } } } - else{ //Resource exists but doesn't have any parent in $datas (package or orphan resource) - $hasToBeInserted = false; - } - - /********************************************* - ** Datas insertion ** - *********************************************/ - if ($hasToBeInserted){ - //Resource treatment - foreach ($datas as $key => $value) { - if ($key == "organization") { $org = $value; } - elseif ($key == "parentResource") { $parentName = $value; } - else { $res->$key = $value; } + return $hasToBeInserted; + } + +// ------------------------------------------------------------------------- + private function parentTreatment($parentName) { + $resource = new Resource(); + $parentResource = $resource->getResourceByTitle($parentName); + + // Search if such parent exists + $numberOfParents = count($parentResource); + $parentID = null; + + if ($numberOfParents == 0) { // If not, create parent + $parentResource = $resource->getNewInitializedResource(); + $parentID = $parentResource->resourceID; + self::$parentInserted++; + } elseif ($numberOfParents == 1) {// Else, attach the resource to its parent. + $parentID = $parentResource[0]->resourceID; + self::$parentAttached++; + } + + return $parentID; + } +// ------------------------------------------------------------------------- + private function setResourcesRelationship($resourceID, $parentID){ + if($parentID != NULL){ + $resourceRelationship = new ResourceRelationship(); + $resourceRelationship->resourceID = $resourceID; + $resourceRelationship->relatedResourceID = $parentID; + $resourceRelationship->relationshipTypeID = '1'; //hardcoded because we're only allowing parent relationships + if (!$resourceRelationship->exists()) { + $resourceRelationship->save(); } + } + } +// ------------------------------------------------------------------------- + + private function organizationTreatment($organizations, $resourceID) { + $loginID = $_SESSION['loginID']; + $htmlContent = ''; + //Organizations module is used + if ($this->config->settings->organizationsModule == 'Y') { //TODO _ hierarchy platform/provider + $dbName = $this->config->settings->organizationsDatabaseName; + foreach ($organizations as $role => $orgName) { + $organization = new Organization(); //TODO _ instanciation dans une boucle j'aime pas trop ça ;) + $organizationRole = new OrganizationRole(); + $organizationID = false; + + // Does the organization already exists? + $query = "SELECT count(*) AS count FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; + $result = $organization->db->processQuery($query, 'assoc'); + + if ($result['count'] == 0) { // If not, we try to create it + $query = "INSERT INTO $dbName.Organization SET createDate=NOW(), createLoginID='$loginID', name='" . mysql_escape_string($orgName) . "'"; + try { + $result = $organization->db->processQuery($query); + $organizationID = $result; + self::$organizationsInserted++; + array_push(self::$arrayOrganizationsCreated, $orgName); + } catch (Exception $e) { + $htmlContent .= "

    Organization $orgName could not be added.

    "; + } + } + // If yes, we attach it to our resource + elseif ($result['count'] == 1) { + $query = "SELECT name, organizationID FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; + $result = $organization->db->processQuery($query, 'assoc'); + $organizationID = $result['organizationID']; + self::$organizationsAttached++; + } else { + $htmlContent .= "

    Error: more than one organization is called $orgName. Please consider deduping.

    "; + } - $res->createLoginID = $loginID; - $res->createDate = date( 'Y-m-d'); - $res->updateLoginID = ''; - $res->updateDate = ''; - $res->statusID = 1; //in progress, don't know why ... - $res->save(); - - //Resource identifiers - $res->setIdentifiers($identifiers); - - //Parent treatment _ see line 219 (import.php) - if ($parentName != null){ - // Search if such parent exists - $numberOfParents = count($resourceObj->getResourceByTitle($parentName)); - $parentID = null; - - if ($numberOfParents == 0) { // If not, create parent - $parentResource = new Resource(); - $parentResource->createLoginID = $loginID; - $parentResource->createDate = date( 'Y-m-d' ); - $parentResource->titleText = $parentName; - $parentResource->statusID = 1; - $parentResource->save(); - - $parentID = $parentResource->resourceID; - self::$parentInserted++; - - } elseif ($numberOfParents == 1) { - // Else, attach the resource to its parent. - $parentResource = $resourceObj->getResourceByTitle($parentName); - $parentID = $parentResource[0]->resourceID; + if ($organizationID) { + // Get role + $query = "SELECT organizationRoleID from OrganizationRole WHERE shortName='" . mysql_escape_string($role) . "'"; + $result = $organization->db->processQuery($query); + $roleID = ($result[0]) ? $result[0] : 1; + // Does the organizationRole already exists? + $query = "SELECT count(*) AS count FROM $dbName.OrganizationRoleProfile WHERE organizationID=$organizationID AND organizationRoleID=$roleID"; + $result = $organization->db->processQuery($query, 'assoc'); + // If not, we try to create it + if ($result['count'] == 0) { + $query = "INSERT INTO $dbName.OrganizationRoleProfile SET organizationID=$organizationID, organizationRoleID=$roleID"; + try { + $result = $organization->db->processQuery($query); + if (!in_array($orgName, self::$arrayOrganizationsCreated)) { + self::$organizationsInserted++; + array_push(self::$arrayOrganizationsCreated, $orgName); + } + } catch (Exception $e) { + $htmlContent .= "

    Unable to associate organization $orgName with its role.

    "; + } + } + } - self::$parentAttached++; + // Let's link the resource and the organization. + // (this has to be done whether the module Organization is in use or not) + if ($organizationID && $roleID) { + $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); } } - //Save relationship - $resourceRelationship = new ResourceRelationship(); - $resourceRelationship->resourceID = $res->resourceID; - $resourceRelationship->relatedResourceID = $parentID; - $resourceRelationship->relationshipTypeID = '1'; //hardcoded because we're only allowing parent relationships - if (!$resourceRelationship->exists()) { - $resourceRelationship->save(); + //TODO _ hierarchy platform/provider (packages) + if ($organizations['platform']) { + $platformName = $organizations['platform']; + $providerName = $organizations['provider']; } + } + // If we do not use the Organizations module + else { + foreach ($organizations as $role => $orgName) { + $organization = new Organization(); //TODO _ instanciation dans une boucle j'aime pas trop ça ;) + $organizationRole = new OrganizationRole(); + $organizationID = false; + // Search if such organization already exists + $organizationExists = $organization->alreadyExists($orgName); + $parentID = null; + + if (!$organizationExists) { // If not, create it + $organization->shortName = $orgName; + $organization->save(); + $organizationID = $organization->organizationID(); + self::$organizationsInserted++; + array_push(self::$arrayOrganizationsCreated, $orgName); + } elseif ($organizationExists == 1) { // Else, + $organizationID = $organization->getOrganizationIDByName($orgName); + self::$organizationsAttached++; + } else { + $htmlContent .= "

    Error: more than one organization is called $orgName. Please consider deduping.

    "; + } - if ($org != null) { // Do we have to create an organization or attach the resource to an existing one? - // If we use the Organizations module - if ($config->settings->organizationsModule == 'Y') { //TODO _ hierarchy platform/provider - $dbName = $config->settings->organizationsDatabaseName; - foreach ($org as $role => $orgName) { - $organization = new Organization();//TODO _ instanciation dans une boucle j'aime pas trop ça ;) - $organizationRole = new OrganizationRole(); - $organizationID = false; - // Does the organization already exists? - $query = "SELECT count(*) AS count FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; - $result = $organization->db->processQuery($query, 'assoc'); - - if ($result['count'] == 0){ // If not, we try to create it - - $query = "INSERT INTO $dbName.Organization SET createDate=NOW(), createLoginID='$loginID', name='" . mysql_escape_string($orgName) . "'"; - try { - $result = $organization->db->processQuery($query); - $organizationID = $result; - self::$organizationsInserted++; - array_push(self::$arrayOrganizationsCreated, $orgName); - } catch (Exception $e) { - $htmlContent .= "

    Organization $orgName could not be added.

    "; - } - } - // If yes, we attach it to our resource - elseif ($result['count'] == 1) { - $query = "SELECT name, organizationID FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; - $result = $organization->db->processQuery($query, 'assoc'); - $organizationID = $result['organizationID']; - self::$organizationsAttached++; - } - else { - $htmlContent .= "

    Error: more than one organization is called $organizationName. Please consider deduping.

    "; - } - - if ($organizationID){ - // Get role - $query = "SELECT organizationRoleID from OrganizationRole WHERE shortName='" . mysql_escape_string($role) . "'"; - $result = $organization->db->processQuery($query); - $roleID = ($result[0]) ? $result[0] : 1; - // Does the organizationRole already exists? - $query = "SELECT count(*) AS count FROM $dbName.OrganizationRoleProfile WHERE organizationID=$organizationID AND organizationRoleID=$roleID"; - $result = $organization->db->processQuery($query, 'assoc'); - // If not, we try to create it - if ($result['count'] == 0) { - $query = "INSERT INTO $dbName.OrganizationRoleProfile SET organizationID=$organizationID, organizationRoleID=$roleID"; - try { - $result = $organization->db->processQuery($query); - if (!in_array($orgName, self::$arrayOrganizationsCreated)) { - self::$organizationsInserted++; - array_push(self::$arrayOrganizationsCreated, $orgName); - } - } catch (Exception $e) { - $htmlContent .= "

    Unable to associate organization $orgName with its role.

    "; - } - } - } - - // Let's link the resource and the organization. - // (this has to be done whether the module Organization is in use or not) - if ($organizationID && $roleID) { - $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); - } - } - - //TODO _ hierarchy platform/provider (packages) - if($org['platform']){ - $platformName = $org['platform']; - $providerName = $org['provider']; - } + $organizationRoles = $organizationRole->getArray(); + if (($roleID = array_search($role, $organizationRoles)) == 0) { + // If role is not found, fallback to the first one. + $roleID = '1'; } - // If we do not use the Organizations module - else { - foreach ($org as $role => $orgName) { - $organization = new Organization();//TODO _ instanciation dans une boucle j'aime pas trop ça ;) - $organizationRole = new OrganizationRole(); - $organizationID = false; - // Search if such organization already exists - $organizationExists = $organization->alreadyExists($orgName); - $parentID = null; - - if (!$organizationExists) { // If not, create it - $organization->shortName = $orgName; - $organization->save(); - $organizationID = $organization->organizationID(); - self::$organizationsInserted++; - array_push(self::$arrayOrganizationsCreated, $orgName); - } elseif ($organizationExists == 1) { // Else, - $organizationID = $organization->getOrganizationIDByName($orgName); - self::$organizationsAttached++; - } else { - $htmlContent .= "

    Error: more than one organization is called $orgName. Please consider deduping.

    "; - } - - $organizationRoles = $organizationRole->getArray(); - if (($roleID = array_search($role, $organizationRoles)) == 0) { - // If role is not found, fallback to the first one. - $roleID = '1'; - } - - // Let's link the resource and the organization. - // (this has to be done whether the module Organization is in use or not) - if ($organizationID && $roleID) { - $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); - } - } + + // Let's link the resource and the organization. + // (this has to be done whether the module Organization is in use or not) + if ($organizationID && $roleID) { + $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); } } } + echo $htmlContent; } // ------------------------------------------------------------------------- - private function setResourceOrganizationLink($roleID, $resourceID, $organizationID) { - $organizationLink = new ResourceOrganizationLink(); - $organizationLink->organizationRoleID = $roleID; - $organizationLink->resourceID = $resource->resourceID; - $organizationLink->organizationID = $organizationID; - $organizationLink->save(); - } - - -// ------------------------------------------------------------------------- - - /**************************** - * Accessors * - ****************************/ - public static function getNbRow(){ + /******************************** + * Accessors * + ********************************/ + public static function getNbRow() { return self::$row; } + // ------------------------------------------------------------------------- - public static function getNbInserted(){ + public static function getNbInserted() { return self::$inserted; } + // ------------------------------------------------------------------------- - public static function getNbParentInserted(){ + public static function getNbParentInserted() { return self::$parentInserted; } + // ------------------------------------------------------------------------- - public static function getNbParentAttached(){ + public static function incrementNbParentInserted() { + self::$parentInserted++; + } + +// ------------------------------------------------------------------------- + public static function getNbParentAttached() { return self::$parentAttached; } + // ------------------------------------------------------------------------- - public static function getNbOrganizationsInserted(){ + public static function incrementNbParentAttached() { + self::$parentAttached++; + } + +// ------------------------------------------------------------------------- + public static function getNbOrganizationsInserted() { return self::$organizationsInserted; } + // ------------------------------------------------------------------------- - public static function getNbOrganizationsAttached(){ + public static function getNbOrganizationsAttached() { return self::$organizationsAttached; } + // ------------------------------------------------------------------------- - } - ?> \ No newline at end of file diff --git a/admin/classes/domain/Resource.php b/admin/classes/domain/Resource.php index 612c15b..f886196 100644 --- a/admin/classes/domain/Resource.php +++ b/admin/classes/domain/Resource.php @@ -1,32 +1,40 @@ . -** -************************************************************************************************************************** -*/ + * ************************************************************************************************************************* + * * CORAL Resources Module v. 1.2 + * * + * * Copyright (c) 2010 University of Notre Dame + * * + * * This file is part of CORAL. + * * + * * CORAL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * * + * * CORAL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License along with CORAL. If not, see . + * * + * ************************************************************************************************************************* + */ +include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/Identifier.php"; +include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/IsbnOrIssn.php"; class Resource extends DatabaseObject { - protected function defineRelationships() {} - - protected function defineIsbnOrIssn() {} + protected function defineRelationships() { + + } + + protected function defineIsbnOrIssn() { + + } - protected function overridePrimaryKeyName() {} + protected function overridePrimaryKeyName() { + + } //returns resource objects by title - public function getResourceByTitle($title){ + public function getResourceByTitle($title) { $query = "SELECT * FROM Resource @@ -38,10 +46,10 @@ public function getResourceByTitle($title){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceID'])){ + if (isset($result['resourceID'])) { $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); array_push($objects, $object); @@ -51,36 +59,36 @@ public function getResourceByTitle($title){ return $objects; } - //returns resource objects by title - public function getResourceByIsbnOrISSN($isbnOrISSN){ + //returns resource objects by title + public function getResourceByIsbnOrISSN($isbnOrISSN) { $query = "SELECT DISTINCT(resourceID) FROM IsbnOrIssn"; - $i = 0; + $i = 0; - if (!is_array($isbnOrISSN)) { - $value = $isbnOrISSN; - $isbnOrISSN = array($value); - } + if (!is_array($isbnOrISSN)) { + $value = $isbnOrISSN; + $isbnOrISSN = array($value); + } - foreach ($isbnOrISSN as $value) { - $query .= ($i == 0) ? " WHERE " : " OR "; - $query .= "isbnOrIssn = '" . $this->db->escapeString($value) . "'"; - $i++; - } + foreach ($isbnOrISSN as $value) { + $query .= ($i == 0) ? " WHERE " : " OR "; + $query .= "isbnOrIssn = '" . $this->db->escapeString($value) . "'"; + $i++; + } - $query .= " ORDER BY 1"; + $query .= " ORDER BY 1"; $result = $this->db->processQuery($query, 'assoc'); $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceID'])){ + if (isset($result['resourceID'])) { $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); array_push($objects, $object); @@ -90,8 +98,8 @@ public function getResourceByIsbnOrISSN($isbnOrISSN){ return $objects; } - public function getIsbnOrIssn() { - $query = "SELECT * + public function getIsbnOrIssn() { + $query = "SELECT * FROM IsbnOrIssn WHERE resourceID = '" . $this->resourceID . "' ORDER BY 1"; @@ -101,36 +109,33 @@ public function getIsbnOrIssn() { $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['isbnOrIssnID'])){ + if (isset($result['isbnOrIssnID'])) { $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $result['isbnOrIssnID']))); array_push($objects, $object); } else { - foreach ($result as $row) { + foreach ($result as $row) { $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $row['isbnOrIssnID']))); array_push($objects, $object); } - - } + } return $objects; - - } + } //returns array of parent resource objects - public function getParentResources(){ - return $this->getRelatedResources('resourceID'); + public function getParentResources() { + return $this->getRelatedResources('resourceID'); } - //returns array of child resource objects - public function getChildResources(){ - return $this->getRelatedResources('relatedResourceID'); + public function getChildResources() { + return $this->getRelatedResources('relatedResourceID'); } - - // return array of related resource objects - private function getRelatedResources($key) { - $query = "SELECT * + // return array of related resource objects + private function getRelatedResources($key) { + + $query = "SELECT * FROM ResourceRelationship WHERE $key = '" . $this->resourceID . "' AND relationshipTypeID = '1' @@ -141,10 +146,10 @@ private function getRelatedResources($key) { $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result[$key])){ + if (isset($result[$key])) { $object = new ResourceRelationship(new NamedArguments(array('primaryKey' => $result['resourceRelationshipID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new ResourceRelationship(new NamedArguments(array('primaryKey' => $row['resourceRelationshipID']))); array_push($objects, $object); @@ -152,11 +157,10 @@ private function getRelatedResources($key) { } return $objects; - - } + } //returns array of purchase site objects - public function getResourcePurchaseSites(){ + public function getResourcePurchaseSites() { $query = "SELECT PurchaseSite.* FROM PurchaseSite, ResourcePurchaseSiteLink RPSL where RPSL.purchaseSiteID = PurchaseSite.purchaseSiteID AND resourceID = '" . $this->resourceID . "'"; @@ -165,10 +169,10 @@ public function getResourcePurchaseSites(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['purchaseSiteID'])){ + if (isset($result['purchaseSiteID'])) { $object = new PurchaseSite(new NamedArguments(array('primaryKey' => $result['purchaseSiteID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new PurchaseSite(new NamedArguments(array('primaryKey' => $row['purchaseSiteID']))); array_push($objects, $object); @@ -178,11 +182,8 @@ public function getResourcePurchaseSites(){ return $objects; } - - - //returns array of ResourcePayment objects - public function getResourcePayments(){ + public function getResourcePayments() { $query = "SELECT * FROM ResourcePayment WHERE resourceID = '" . $this->resourceID . "' ORDER BY year DESC, fundName, subscriptionStartDate DESC"; @@ -191,10 +192,10 @@ public function getResourcePayments(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourcePaymentID'])){ + if (isset($result['resourcePaymentID'])) { $object = new ResourcePayment(new NamedArguments(array('primaryKey' => $result['resourcePaymentID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new ResourcePayment(new NamedArguments(array('primaryKey' => $row['resourcePaymentID']))); array_push($objects, $object); @@ -204,13 +205,12 @@ public function getResourcePayments(){ return $objects; } - //returns array of associated licenses - public function getLicenseArray(){ + public function getLicenseArray() { $config = new Configuration; //if the lic module is installed get the lic name from lic database - if ($config->settings->licensingModule == 'Y'){ + if ($config->settings->licensingModule == 'Y') { $dbName = $config->settings->licensingDatabaseName; $resourceLicenseArray = array(); @@ -222,49 +222,44 @@ public function getLicenseArray(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['licenseID'])){ + if (isset($result['licenseID'])) { $licArray = array(); //first, get the license name $query = "SELECT shortName FROM " . $dbName . ".License WHERE licenseID = " . $result['licenseID']; - if ($licResult = mysql_query($query)){ - while ($licRow = mysql_fetch_assoc($licResult)){ + if ($licResult = mysql_query($query)) { + while ($licRow = mysql_fetch_assoc($licResult)) { $licArray['license'] = $licRow['shortName']; $licArray['licenseID'] = $result['licenseID']; } } array_push($resourceLicenseArray, $licArray); - }else{ + } else { foreach ($result as $row) { $licArray = array(); //first, get the license name $query = "SELECT shortName FROM " . $dbName . ".License WHERE licenseID = " . $row['licenseID']; - if ($licResult = mysql_query($query)){ - while ($licRow = mysql_fetch_assoc($licResult)){ + if ($licResult = mysql_query($query)) { + while ($licRow = mysql_fetch_assoc($licResult)) { $licArray['license'] = $licRow['shortName']; $licArray['licenseID'] = $row['licenseID']; } } array_push($resourceLicenseArray, $licArray); - } - } return $resourceLicenseArray; } } - - - //returns array of resource license status objects - public function getResourceLicenseStatuses(){ + public function getResourceLicenseStatuses() { $query = "SELECT * FROM ResourceLicenseStatus WHERE resourceID = '" . $this->resourceID . "' ORDER BY licenseStatusChangeDate desc;"; @@ -273,10 +268,10 @@ public function getResourceLicenseStatuses(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceLicenseStatusID'])){ + if (isset($result['resourceLicenseStatusID'])) { $object = new ResourceLicenseStatus(new NamedArguments(array('primaryKey' => $result['resourceLicenseStatusID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new ResourceLicenseStatus(new NamedArguments(array('primaryKey' => $row['resourceLicenseStatusID']))); array_push($objects, $object); @@ -286,26 +281,21 @@ public function getResourceLicenseStatuses(){ return $objects; } - - - //returns LicenseStatusID of the most recent resource license status - public function getCurrentResourceLicenseStatus(){ + public function getCurrentResourceLicenseStatus() { $query = "SELECT licenseStatusID FROM ResourceLicenseStatus RLS WHERE resourceID = '" . $this->resourceID . "' AND licenseStatusChangeDate = (SELECT MAX(licenseStatusChangeDate) FROM ResourceLicenseStatus WHERE ResourceLicenseStatus.resourceID = '" . $this->resourceID . "') LIMIT 0,1;"; $result = $this->db->processQuery($query, 'assoc'); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['licenseStatusID'])){ + if (isset($result['licenseStatusID'])) { return $result['licenseStatusID']; } - } - //returns array of authorized site objects - public function getResourceAuthorizedSites(){ + public function getResourceAuthorizedSites() { $query = "SELECT AuthorizedSite.* FROM AuthorizedSite, ResourceAuthorizedSiteLink RPSL where RPSL.authorizedSiteID = AuthorizedSite.authorizedSiteID AND resourceID = '" . $this->resourceID . "'"; @@ -314,10 +304,10 @@ public function getResourceAuthorizedSites(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['authorizedSiteID'])){ + if (isset($result['authorizedSiteID'])) { $object = new AuthorizedSite(new NamedArguments(array('primaryKey' => $result['authorizedSiteID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new AuthorizedSite(new NamedArguments(array('primaryKey' => $row['authorizedSiteID']))); array_push($objects, $object); @@ -327,11 +317,8 @@ public function getResourceAuthorizedSites(){ return $objects; } - - - //returns array of administering site objects - public function getResourceAdministeringSites(){ + public function getResourceAdministeringSites() { $query = "SELECT AdministeringSite.* FROM AdministeringSite, ResourceAdministeringSiteLink RPSL where RPSL.administeringSiteID = AdministeringSite.administeringSiteID AND resourceID = '" . $this->resourceID . "'"; @@ -340,10 +327,10 @@ public function getResourceAdministeringSites(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['administeringSiteID'])){ + if (isset($result['administeringSiteID'])) { $object = new AdministeringSite(new NamedArguments(array('primaryKey' => $result['administeringSiteID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new AdministeringSite(new NamedArguments(array('primaryKey' => $row['administeringSiteID']))); array_push($objects, $object); @@ -353,22 +340,16 @@ public function getResourceAdministeringSites(){ return $objects; } - - - //deletes all parent resources associated with this resource - public function removeParentResources(){ + public function removeParentResources() { $query = "DELETE FROM ResourceRelationship WHERE resourceID = '" . $this->resourceID . "'"; return $this->db->processQuery($query); } - - - //returns array of alias objects - public function getAliases(){ + public function getAliases() { $query = "SELECT * FROM Alias WHERE resourceID = '" . $this->resourceID . "' order by shortName"; @@ -377,10 +358,10 @@ public function getAliases(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['aliasID'])){ + if (isset($result['aliasID'])) { $object = new Alias(new NamedArguments(array('primaryKey' => $result['aliasID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new Alias(new NamedArguments(array('primaryKey' => $row['aliasID']))); array_push($objects, $object); @@ -390,14 +371,8 @@ public function getAliases(){ return $objects; } - - - - - - //returns array of contact objects - public function getUnarchivedContacts(){ + public function getUnarchivedContacts() { $config = new Configuration; @@ -417,15 +392,14 @@ public function getUnarchivedContacts(){ //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])){ + if (isset($result['contactID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($contactsArray, $resultArray); - - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -439,7 +413,7 @@ public function getUnarchivedContacts(){ //if the org module is installed also get the org contacts from org database - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $query = "SELECT distinct OC.*, O.name organizationName, GROUP_CONCAT(DISTINCT CR.shortName SEPARATOR '
    ') contactRoles @@ -458,15 +432,14 @@ public function getUnarchivedContacts(){ //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])){ + if (isset($result['contactID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($contactsArray, $resultArray); - - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -476,20 +449,14 @@ public function getUnarchivedContacts(){ array_push($contactsArray, $resultArray); } } - - - } return $contactsArray; } - - - //returns array of contact objects - public function getArchivedContacts(){ + public function getArchivedContacts() { $config = new Configuration; $contactsArray = array(); @@ -508,15 +475,14 @@ public function getArchivedContacts(){ //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])){ + if (isset($result['contactID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($contactsArray, $resultArray); - - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -530,7 +496,7 @@ public function getArchivedContacts(){ //if the org module is installed also get the org contacts from org database - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $query = "SELECT DISTINCT OC.*, O.name organizationName, GROUP_CONCAT(DISTINCT CR.shortName SEPARATOR '
    ') contactRoles @@ -550,15 +516,14 @@ public function getArchivedContacts(){ //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])){ + if (isset($result['contactID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($contactsArray, $resultArray); - - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -568,23 +533,13 @@ public function getArchivedContacts(){ array_push($contactsArray, $resultArray); } } - - - } return $contactsArray; - - - } - - - - //returns array of contact objects - public function getCreatorsArray(){ + public function getCreatorsArray() { $creatorsArray = array(); $resultArray = array(); @@ -599,15 +554,14 @@ public function getCreatorsArray(){ //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['loginID'])){ + if (isset($result['loginID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($creatorsArray, $resultArray); - - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -619,15 +573,10 @@ public function getCreatorsArray(){ } return $creatorsArray; - - } - - - //returns array of external login records - public function getExternalLoginArray(){ + public function getExternalLoginArray() { $config = new Configuration; @@ -644,15 +593,14 @@ public function getExternalLoginArray(){ //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['externalLoginID'])){ + if (isset($result['externalLoginID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($elArray, $resultArray); - - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -666,7 +614,7 @@ public function getExternalLoginArray(){ //if the org module is installed also get the external logins from org database - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $query = "SELECT DISTINCT EL.*, ELT.shortName externalLoginType, O.name organizationName @@ -684,15 +632,14 @@ public function getExternalLoginArray(){ //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['externalLoginID'])){ + if (isset($result['externalLoginID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($elArray, $resultArray); - - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -702,9 +649,6 @@ public function getExternalLoginArray(){ array_push($elArray, $resultArray); } } - - - } @@ -712,17 +656,15 @@ public function getExternalLoginArray(){ return $elArray; } - - //returns array of notes objects - public function getNotes($tabName = NULL){ + public function getNotes($tabName = NULL) { - if ($tabName){ + if ($tabName) { $query = "SELECT * FROM ResourceNote RN WHERE resourceID = '" . $this->resourceID . "' AND UPPER(tabName) = UPPER('" . $tabName . "') ORDER BY updateDate desc"; - }else{ + } else { $query = "SELECT RN.* FROM ResourceNote RN LEFT JOIN NoteType NT ON NT.noteTypeID = RN.noteTypeID @@ -735,10 +677,10 @@ public function getNotes($tabName = NULL){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceNoteID'])){ + if (isset($result['resourceNoteID'])) { $object = new ResourceNote(new NamedArguments(array('primaryKey' => $result['resourceNoteID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new ResourceNote(new NamedArguments(array('primaryKey' => $row['resourceNoteID']))); array_push($objects, $object); @@ -748,13 +690,8 @@ public function getNotes($tabName = NULL){ return $objects; } - - - - - //returns array of the initial note object - public function getInitialNote(){ + public function getInitialNote() { $noteType = new NoteType(); $query = "SELECT * FROM ResourceNote RN @@ -766,24 +703,17 @@ public function getInitialNote(){ $result = $this->db->processQuery($query, 'assoc'); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceNoteID'])){ + if (isset($result['resourceNoteID'])) { $resourceNote = new ResourceNote(new NamedArguments(array('primaryKey' => $result['resourceNoteID']))); return $resourceNote; - } else{ + } else { $resourceNote = new ResourceNote(); return $resourceNote; } - } - - - - - - //returns array of attachments objects - public function getAttachments(){ + public function getAttachments() { $query = "SELECT * FROM Attachment A, AttachmentType AT WHERE AT.attachmentTypeID = A.attachmentTypeID @@ -795,10 +725,10 @@ public function getAttachments(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['attachmentID'])){ + if (isset($result['attachmentID'])) { $object = new Attachment(new NamedArguments(array('primaryKey' => $result['attachmentID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new Attachment(new NamedArguments(array('primaryKey' => $row['attachmentID']))); array_push($objects, $object); @@ -808,11 +738,8 @@ public function getAttachments(){ return $objects; } - - - //returns array of contact objects - public function getContacts(){ + public function getContacts() { $query = "SELECT * FROM Contact WHERE resourceID = '" . $this->resourceID . "'"; @@ -822,10 +749,10 @@ public function getContacts(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])){ + if (isset($result['contactID'])) { $object = new Contact(new NamedArguments(array('primaryKey' => $result['contactID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new Contact(new NamedArguments(array('primaryKey' => $row['contactID']))); array_push($objects, $object); @@ -835,11 +762,8 @@ public function getContacts(){ return $objects; } - - - //returns array of externalLogin objects - public function getExternalLogins(){ + public function getExternalLogins() { $query = "SELECT * FROM ExternalLogin WHERE resourceID = '" . $this->resourceID . "'"; @@ -849,10 +773,10 @@ public function getExternalLogins(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['externalLoginID'])){ + if (isset($result['externalLoginID'])) { $object = new ExternalLogin(new NamedArguments(array('primaryKey' => $result['externalLoginID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new ExternalLogin(new NamedArguments(array('primaryKey' => $row['externalLoginID']))); array_push($objects, $object); @@ -862,49 +786,48 @@ public function getExternalLogins(){ return $objects; } - - public static function setSearch($search) { - $config = new Configuration; - - if ($config->settings->defaultsort) { - $orderBy = $config->settings->defaultsort; - } else { - $orderBy = "R.createDate DESC, TRIM(LEADING 'THE ' FROM UPPER(R.titleText)) asc"; - } - - $defaultSearchParameters = array( - "orderBy" => $orderBy, - "page" => 1, - "recordsPerPage" => 25, - ); - foreach ($defaultSearchParameters as $key => $value) { - if (!$search[$key]) { - $search[$key] = $value; - } - } - foreach ($search as $key => $value) { - $search[$key] = trim($value); - } - $_SESSION['resourceSearch'] = $search; - } - - public static function resetSearch() { - Resource::setSearch(array()); - } - - public static function getSearch() { - if (!isset($_SESSION['resourceSearch'])) { - Resource::resetSearch(); - } - return $_SESSION['resourceSearch']; - } - - public static function getSearchDetails() { - // A successful mysql_connect must be run before mysql_real_escape_string will function. Instantiating a resource model will set up the connection - $resource = new Resource(); - - $search = Resource::getSearch(); - + public static function setSearch($search) { + $config = new Configuration; + + if ($config->settings->defaultsort) { + $orderBy = $config->settings->defaultsort; + } else { + $orderBy = "R.createDate DESC, TRIM(LEADING 'THE ' FROM UPPER(R.titleText)) asc"; + } + + $defaultSearchParameters = array( + "orderBy" => $orderBy, + "page" => 1, + "recordsPerPage" => 25, + ); + foreach ($defaultSearchParameters as $key => $value) { + if (!$search[$key]) { + $search[$key] = $value; + } + } + foreach ($search as $key => $value) { + $search[$key] = trim($value); + } + $_SESSION['resourceSearch'] = $search; + } + + public static function resetSearch() { + Resource::setSearch(array()); + } + + public static function getSearch() { + if (!isset($_SESSION['resourceSearch'])) { + Resource::resetSearch(); + } + return $_SESSION['resourceSearch']; + } + + public static function getSearchDetails() { + // A successful mysql_connect must be run before mysql_real_escape_string will function. Instantiating a resource model will set up the connection + $resource = new Resource(); + + $search = Resource::getSearch(); + $whereAdd = array(); $searchDisplay = array(); $config = new Configuration(); @@ -914,197 +837,195 @@ public static function getSearchDetails() { if ($search['name']) { $nameQueryString = mysql_real_escape_string(strtoupper($search['name'])); $nameQueryString = preg_replace("/ +/", "%", $nameQueryString); - $nameQueryString = "'%" . $nameQueryString . "%'"; + $nameQueryString = "'%" . $nameQueryString . "%'"; - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $whereAdd[] = "((UPPER(R.titleText) LIKE " . $nameQueryString . ") OR (UPPER(A.shortName) LIKE " . $nameQueryString . ") OR (UPPER(O.name) LIKE " . $nameQueryString . ") OR (UPPER(OA.name) LIKE " . $nameQueryString . ") OR (UPPER(RP.titleText) LIKE " . $nameQueryString . ") OR (UPPER(RC.titleText) LIKE " . $nameQueryString . ") OR (UPPER(R.recordSetIdentifier) LIKE " . $nameQueryString . "))"; - - }else{ + } else { $whereAdd[] = "((UPPER(R.titleText) LIKE " . $nameQueryString . ") OR (UPPER(A.shortName) LIKE " . $nameQueryString . ") OR (UPPER(O.shortName) LIKE " . $nameQueryString . ") OR (UPPER(RP.titleText) LIKE " . $nameQueryString . ") OR (UPPER(RC.titleText) LIKE " . $nameQueryString . ") OR (UPPER(R.recordSetIdentifier) LIKE " . $nameQueryString . "))"; - } - + $searchDisplay[] = "Name contains: " . $search['name']; } //get where statements together (and escape single quotes) if ($search['resourceID']) { - $whereAdd[] = "R.resourceID = '" . mysql_real_escape_string($search['resourceID']) . "'"; - $searchDisplay[] = "Resource ID: " . $search['resourceID']; - } + $whereAdd[] = "R.resourceID = '" . mysql_real_escape_string($search['resourceID']) . "'"; + $searchDisplay[] = "Resource ID: " . $search['resourceID']; + } if ($search['resourceISBNOrISSN']) { - $resourceISBNOrISSN = mysql_real_escape_string(str_replace("-","",$search['resourceISBNOrISSN'])); - $whereAdd[] = "REPLACE(I.isbnOrIssn,'-','') = '" . $resourceISBNOrISSN . "'"; - $searchDisplay[] = "ISSN/ISBN: " . $search['resourceISBNOrISSN']; - } + $resourceISBNOrISSN = mysql_real_escape_string(str_replace("-", "", $search['resourceISBNOrISSN'])); + $whereAdd[] = "REPLACE(I.isbnOrIssn,'-','') = '" . $resourceISBNOrISSN . "'"; + $searchDisplay[] = "ISSN/ISBN: " . $search['resourceISBNOrISSN']; + } if ($search['fund']) { - $fund = mysql_real_escape_string(str_replace("-","",$search['fund'])); - $whereAdd[] = "REPLACE(RPAY.fundName,'-','') = '" . $fund . "'"; - $searchDisplay[] = "Fund: " . $search['fund']; - } + $fund = mysql_real_escape_string(str_replace("-", "", $search['fund'])); + $whereAdd[] = "REPLACE(RPAY.fundName,'-','') = '" . $fund . "'"; + $searchDisplay[] = "Fund: " . $search['fund']; + } + + if ($search['stepName']) { + $status = new Status(); + $completedStatusID = $status->getIDFromName('complete'); + $whereAdd[] = "(R.statusID != $completedStatusID AND RS.stepName = '" . mysql_real_escape_string($search['stepName']) . "' AND RS.stepStartDate IS NOT NULL AND RS.stepEndDate IS NULL)"; + $searchDisplay[] = "Routing Step: " . $search['stepName']; + } - if ($search['stepName']) { - $status = new Status(); - $completedStatusID = $status->getIDFromName('complete'); - $whereAdd[] = "(R.statusID != $completedStatusID AND RS.stepName = '" . mysql_real_escape_string($search['stepName']) . "' AND RS.stepStartDate IS NOT NULL AND RS.stepEndDate IS NULL)"; - $searchDisplay[] = "Routing Step: " . $search['stepName']; - } + if ($search['parent'] != null) { + $parentadd = "(" . $search['parent'] . ".relationshipTypeID = 1"; + $parentadd .= ")"; + $whereAdd[] = $parentadd; + } - if ($search['parent'] != null) { - $parentadd = "(" . $search['parent'] . ".relationshipTypeID = 1"; - $parentadd .= ")"; - $whereAdd[] = $parentadd; - } - if ($search['statusID']) { - $whereAdd[] = "R.statusID = '" . mysql_real_escape_string($search['statusID']) . "'"; - $status = new Status(new NamedArguments(array('primaryKey' => $search['statusID']))); - $searchDisplay[] = "Status: " . $status->shortName; - } - + $whereAdd[] = "R.statusID = '" . mysql_real_escape_string($search['statusID']) . "'"; + $status = new Status(new NamedArguments(array('primaryKey' => $search['statusID']))); + $searchDisplay[] = "Status: " . $status->shortName; + } + if ($search['creatorLoginID']) { - $whereAdd[] = "R.createLoginID = '" . mysql_real_escape_string($search['creatorLoginID']) . "'"; - - $createUser = new User(new NamedArguments(array('primaryKey' => $search['creatorLoginID']))); - if ($createUser->firstName){ - $name = $createUser->lastName . ", " . $createUser->firstName; - }else{ - $name = $createUser->loginID; - } - $searchDisplay[] = "Creator: " . $name; - } + $whereAdd[] = "R.createLoginID = '" . mysql_real_escape_string($search['creatorLoginID']) . "'"; + + $createUser = new User(new NamedArguments(array('primaryKey' => $search['creatorLoginID']))); + if ($createUser->firstName) { + $name = $createUser->lastName . ", " . $createUser->firstName; + } else { + $name = $createUser->loginID; + } + $searchDisplay[] = "Creator: " . $name; + } if ($search['resourceFormatID']) { - $whereAdd[] = "R.resourceFormatID = '" . mysql_real_escape_string($search['resourceFormatID']) . "'"; - $resourceFormat = new ResourceFormat(new NamedArguments(array('primaryKey' => $search['resourceFormatID']))); - $searchDisplay[] = "Resource Format: " . $resourceFormat->shortName; - } - + $whereAdd[] = "R.resourceFormatID = '" . mysql_real_escape_string($search['resourceFormatID']) . "'"; + $resourceFormat = new ResourceFormat(new NamedArguments(array('primaryKey' => $search['resourceFormatID']))); + $searchDisplay[] = "Resource Format: " . $resourceFormat->shortName; + } + if ($search['acquisitionTypeID']) { - $whereAdd[] = "R.acquisitionTypeID = '" . mysql_real_escape_string($search['acquisitionTypeID']) . "'"; - $acquisitionType = new AcquisitionType(new NamedArguments(array('primaryKey' => $search['acquisitionTypeID']))); - $searchDisplay[] = "Acquisition Type: " . $acquisitionType->shortName; - } + $whereAdd[] = "R.acquisitionTypeID = '" . mysql_real_escape_string($search['acquisitionTypeID']) . "'"; + $acquisitionType = new AcquisitionType(new NamedArguments(array('primaryKey' => $search['acquisitionTypeID']))); + $searchDisplay[] = "Acquisition Type: " . $acquisitionType->shortName; + } if ($search['resourceNote']) { - $whereAdd[] = "UPPER(RN.noteText) LIKE UPPER('%" . mysql_real_escape_string($search['resourceNote']) . "%')"; - $searchDisplay[] = "Note contains: " . $search['resourceNote']; - } + $whereAdd[] = "UPPER(RN.noteText) LIKE UPPER('%" . mysql_real_escape_string($search['resourceNote']) . "%')"; + $searchDisplay[] = "Note contains: " . $search['resourceNote']; + } if ($search['createDateStart']) { - $whereAdd[] = "R.createDate >= STR_TO_DATE('" . mysql_real_escape_string($search['createDateStart']) . "','%m/%d/%Y')"; - if (!$search['createDateEnd']) { - $searchDisplay[] = "Created on or after: " . $search['createDateStart']; - } else { - $searchDisplay[] = "Created between: " . $search['createDateStart'] . " and " . $search['createDateEnd']; - } - } - + $whereAdd[] = "R.createDate >= STR_TO_DATE('" . mysql_real_escape_string($search['createDateStart']) . "','%m/%d/%Y')"; + if (!$search['createDateEnd']) { + $searchDisplay[] = "Created on or after: " . $search['createDateStart']; + } else { + $searchDisplay[] = "Created between: " . $search['createDateStart'] . " and " . $search['createDateEnd']; + } + } + if ($search['createDateEnd']) { - $whereAdd[] = "R.createDate <= STR_TO_DATE('" . mysql_real_escape_string($search['createDateEnd']) . "','%m/%d/%Y')"; - if (!$search['createDateStart']) { - $searchDisplay[] = "Created on or before: " . $search['createDateEnd']; - } - } + $whereAdd[] = "R.createDate <= STR_TO_DATE('" . mysql_real_escape_string($search['createDateEnd']) . "','%m/%d/%Y')"; + if (!$search['createDateStart']) { + $searchDisplay[] = "Created on or before: " . $search['createDateEnd']; + } + } if ($search['startWith']) { - $whereAdd[] = "TRIM(LEADING 'THE ' FROM UPPER(R.titleText)) LIKE UPPER('" . mysql_real_escape_string($search['startWith']) . "%')"; - $searchDisplay[] = "Starts with: " . $search['startWith']; - } + $whereAdd[] = "TRIM(LEADING 'THE ' FROM UPPER(R.titleText)) LIKE UPPER('" . mysql_real_escape_string($search['startWith']) . "%')"; + $searchDisplay[] = "Starts with: " . $search['startWith']; + } //the following are not-required fields with dropdowns and have "none" as an option - if ($search['resourceTypeID'] == 'none'){ + if ($search['resourceTypeID'] == 'none') { $whereAdd[] = "((R.resourceTypeID IS NULL) OR (R.resourceTypeID = '0'))"; $searchDisplay[] = "Resource Type: none"; - }else if ($search['resourceTypeID']){ + } else if ($search['resourceTypeID']) { $whereAdd[] = "R.resourceTypeID = '" . mysql_real_escape_string($search['resourceTypeID']) . "'"; $resourceType = new ResourceType(new NamedArguments(array('primaryKey' => $search['resourceTypeID']))); - $searchDisplay[] = "Resource Type: " . $resourceType->shortName; + $searchDisplay[] = "Resource Type: " . $resourceType->shortName; } - - - if ($search['generalSubjectID'] == 'none'){ + + + if ($search['generalSubjectID'] == 'none') { $whereAdd[] = "((GDLINK.generalSubjectID IS NULL) OR (GDLINK.generalSubjectID = '0'))"; $searchDisplay[] = "Resource Type: none"; - }else if ($search['generalSubjectID']){ + } else if ($search['generalSubjectID']) { $whereAdd[] = "GDLINK.generalSubjectID = '" . mysql_real_escape_string($search['generalSubjectID']) . "'"; $generalSubject = new GeneralSubject(new NamedArguments(array('primaryKey' => $search['generalSubjectID']))); - $searchDisplay[] = "General Subject: " . $generalSubject->shortName; - } + $searchDisplay[] = "General Subject: " . $generalSubject->shortName; + } - if ($search['detailedSubjectID'] == 'none'){ + if ($search['detailedSubjectID'] == 'none') { $whereAdd[] = "((GDLINK.detailedSubjectID IS NULL) OR (GDLINK.detailedSubjectID = '0') OR (GDLINK.detailedSubjectID = '-1'))"; $searchDisplay[] = "Resource Type: none"; - }else if ($search['detailedSubjectID']){ + } else if ($search['detailedSubjectID']) { $whereAdd[] = "GDLINK.detailedSubjectID = '" . mysql_real_escape_string($search['detailedSubjectID']) . "'"; $detailedSubject = new DetailedSubject(new NamedArguments(array('primaryKey' => $search['detailedSubjectID']))); - $searchDisplay[] = "Detailed Subject: " . $detailedSubject->shortName; - } - - if ($search['noteTypeID'] == 'none'){ + $searchDisplay[] = "Detailed Subject: " . $detailedSubject->shortName; + } + + if ($search['noteTypeID'] == 'none') { $whereAdd[] = "(RN.noteTypeID IS NULL) AND (RN.noteText IS NOT NULL)"; $searchDisplay[] = "Note Type: none"; - }else if ($search['noteTypeID']){ + } else if ($search['noteTypeID']) { $whereAdd[] = "RN.noteTypeID = '" . mysql_real_escape_string($search['noteTypeID']) . "'"; $noteType = new NoteType(new NamedArguments(array('primaryKey' => $search['noteTypeID']))); - $searchDisplay[] = "Note Type: " . $noteType->shortName; + $searchDisplay[] = "Note Type: " . $noteType->shortName; } - if ($search['purchaseSiteID'] == 'none'){ + if ($search['purchaseSiteID'] == 'none') { $whereAdd[] = "RPSL.purchaseSiteID IS NULL"; $searchDisplay[] = "Purchase Site: none"; - }else if ($search['purchaseSiteID']){ + } else if ($search['purchaseSiteID']) { $whereAdd[] = "RPSL.purchaseSiteID = '" . mysql_real_escape_string($search['purchaseSiteID']) . "'"; $purchaseSite = new PurchaseSite(new NamedArguments(array('primaryKey' => $search['purchaseSiteID']))); - $searchDisplay[] = "Purchase Site: " . $purchaseSite->shortName; + $searchDisplay[] = "Purchase Site: " . $purchaseSite->shortName; } - if ($search['authorizedSiteID'] == 'none'){ + if ($search['authorizedSiteID'] == 'none') { $whereAdd[] = "RAUSL.authorizedSiteID IS NULL"; $searchDisplay[] = "Authorized Site: none"; - }else if ($search['authorizedSiteID']){ + } else if ($search['authorizedSiteID']) { $whereAdd[] = "RAUSL.authorizedSiteID = '" . mysql_real_escape_string($search['authorizedSiteID']) . "'"; $authorizedSite = new AuthorizedSite(new NamedArguments(array('primaryKey' => $search['authorizedSiteID']))); - $searchDisplay[] = "Authorized Site: " . $authorizedSite->shortName; + $searchDisplay[] = "Authorized Site: " . $authorizedSite->shortName; } - if ($search['administeringSiteID'] == 'none'){ + if ($search['administeringSiteID'] == 'none') { $whereAdd[] = "RADSL.administeringSiteID IS NULL"; $searchDisplay[] = "Administering Site: none"; - }else if ($search['administeringSiteID']){ + } else if ($search['administeringSiteID']) { $whereAdd[] = "RADSL.administeringSiteID = '" . mysql_real_escape_string($search['administeringSiteID']) . "'"; $administeringSite = new AdministeringSite(new NamedArguments(array('primaryKey' => $search['administeringSiteID']))); - $searchDisplay[] = "Administering Site: " . $administeringSite->shortName; + $searchDisplay[] = "Administering Site: " . $administeringSite->shortName; } - if ($search['authenticationTypeID'] == 'none'){ + if ($search['authenticationTypeID'] == 'none') { $whereAdd[] = "R.authenticationTypeID IS NULL"; $searchDisplay[] = "Authentication Type: none"; - }else if ($search['authenticationTypeID']){ + } else if ($search['authenticationTypeID']) { $whereAdd[] = "R.authenticationTypeID = '" . mysql_real_escape_string($search['authenticationTypeID']) . "'"; $authenticationType = new AuthenticationType(new NamedArguments(array('primaryKey' => $search['authenticationTypeID']))); $searchDisplay[] = "Authentication Type: " . $authenticationType->shortName; } - + if ($search['catalogingStatusID'] == 'none') { - $whereAdd[] = "(R.catalogingStatusID IS NULL)"; - $searchDisplay[] = "Cataloging Status: none"; + $whereAdd[] = "(R.catalogingStatusID IS NULL)"; + $searchDisplay[] = "Cataloging Status: none"; } else if ($search['catalogingStatusID']) { $whereAdd[] = "R.catalogingStatusID = '" . mysql_real_escape_string($search['catalogingStatusID']) . "'"; $catalogingStatus = new CatalogingStatus(new NamedArguments(array('primaryKey' => $search['catalogingStatusID']))); - $searchDisplay[] = "Cataloging Status: " . $catalogingStatus->shortName; - } + $searchDisplay[] = "Cataloging Status: " . $catalogingStatus->shortName; + } @@ -1114,54 +1035,53 @@ public static function getSearchDetails() { $page = $search['page']; $recordsPerPage = $search['recordsPerPage']; return array("where" => $whereAdd, "page" => $page, "order" => $orderBy, "perPage" => $recordsPerPage, "display" => $searchDisplay); - } + } - public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = false) { - $config = new Configuration(); + public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = false) { + $config = new Configuration(); $status = new Status(); - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $orgJoinAdd = "LEFT JOIN " . $dbName . ".Organization O ON O.organizationID = ROL.organizationID LEFT JOIN " . $dbName . ".Alias OA ON OA.organizationID = ROL.organizationID"; - - }else{ + } else { $orgJoinAdd = "LEFT JOIN Organization O ON O.organizationID = ROL.organizationID"; } - - $savedStatusID = intval($status->getIDFromName('saved')); + + $savedStatusID = intval($status->getIDFromName('saved')); //also add to not retrieve saved records $whereAdd[] = "R.statusID != " . $savedStatusID; - if (count($whereAdd) > 0){ + if (count($whereAdd) > 0) { $whereStatement = " WHERE " . implode(" AND ", $whereAdd); - }else{ + } else { $whereStatement = ""; } if ($count) { - $select = "SELECT COUNT(DISTINCT R.resourceID) count"; - $groupBy = ""; - } else { - $select = "SELECT R.resourceID, R.titleText, AT.shortName acquisitionType, R.createLoginID, CU.firstName, CU.lastName, R.createDate, S.shortName status, + $select = "SELECT COUNT(DISTINCT R.resourceID) count"; + $groupBy = ""; + } else { + $select = "SELECT R.resourceID, R.titleText, AT.shortName acquisitionType, R.createLoginID, CU.firstName, CU.lastName, R.createDate, S.shortName status, GROUP_CONCAT(DISTINCT A.shortName, I.isbnOrIssn ORDER BY A.shortName DESC SEPARATOR '
    ') aliases"; - $groupBy = "GROUP BY R.resourceID"; - } + $groupBy = "GROUP BY R.resourceID"; + } - $referenced_tables = array(); + $referenced_tables = array(); - $table_matches = array(); + $table_matches = array(); - // Build a list of tables that are referenced by the select and where statements in order to limit the number of joins performed in the search. - preg_match_all("/[A-Z]+(?=[.][A-Z]+)/i", $select, $table_matches); - $referenced_tables = array_unique($table_matches[0]); + // Build a list of tables that are referenced by the select and where statements in order to limit the number of joins performed in the search. + preg_match_all("/[A-Z]+(?=[.][A-Z]+)/i", $select, $table_matches); + $referenced_tables = array_unique($table_matches[0]); - preg_match_all("/[A-Z]+(?=[.][A-Z]+)/i", $whereStatement, $table_matches); - $referenced_tables = array_unique(array_merge($referenced_tables, $table_matches[0])); + preg_match_all("/[A-Z]+(?=[.][A-Z]+)/i", $whereStatement, $table_matches); + $referenced_tables = array_unique(array_merge($referenced_tables, $table_matches[0])); - // These join statements will only be included in the query if the alias is referenced by the select and/or where. - $conditional_joins = explode("\n", "LEFT JOIN ResourceFormat RF ON R.resourceFormatID = RF.resourceFormatID + // These join statements will only be included in the query if the alias is referenced by the select and/or where. + $conditional_joins = explode("\n", "LEFT JOIN ResourceFormat RF ON R.resourceFormatID = RF.resourceFormatID LEFT JOIN ResourceType RT ON R.resourceTypeID = RT.resourceTypeID LEFT JOIN AcquisitionType AT ON R.acquisitionTypeID = AT.acquisitionTypeID LEFT JOIN Status S ON R.statusID = S.statusID @@ -1177,13 +1097,13 @@ public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = fals $additional_joins = array(); - foreach($conditional_joins as $join) { + foreach ($conditional_joins as $join) { $match = array(); preg_match("/[A-Z]+(?= ON )/i", $join, $match); $table_name = $match[0]; if (in_array($table_name, $referenced_tables)) { - $additional_joins[] = $join; - } + $additional_joins[] = $join; + } } $query = $select . " @@ -1202,17 +1122,17 @@ public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = fals " . $groupBy; if ($orderBy) { - $query .= "\nORDER BY " . $orderBy; + $query .= "\nORDER BY " . $orderBy; } if ($limit) { - $query .= "\nLIMIT " . $limit; + $query .= "\nLIMIT " . $limit; } return $query; - } + } //returns array based on search - public function search($whereAdd, $orderBy, $limit){ + public function search($whereAdd, $orderBy, $limit) { $query = $this->searchQuery($whereAdd, $orderBy, $limit, false); $result = $this->db->processQuery($query, 'assoc'); @@ -1221,14 +1141,14 @@ public function search($whereAdd, $orderBy, $limit){ $resultArray = array(); //need to do this since it could be that there's only one result and this is how the dbservice returns result - if (isset($result['resourceID'])){ + if (isset($result['resourceID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($searchArray, $resultArray); - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -1242,47 +1162,42 @@ public function search($whereAdd, $orderBy, $limit){ } public function searchCount($whereAdd) { - $query = $this->searchQuery($whereAdd, '', '', true); - $result = $this->db->processQuery($query, 'assoc'); - - //echo $query; - - return $result['count']; - } + $query = $this->searchQuery($whereAdd, '', '', true); + $result = $this->db->processQuery($query, 'assoc'); + + //echo $query; + return $result['count']; + } //used for A-Z on search (index) - public function getAlphabeticalList(){ + public function getAlphabeticalList() { $alphArray = array(); $result = mysql_query("SELECT DISTINCT UPPER(SUBSTR(TRIM(LEADING 'The ' FROM titleText),1,1)) letter, COUNT(SUBSTR(TRIM(LEADING 'The ' FROM titleText),1,1)) letter_count FROM Resource R GROUP BY SUBSTR(TRIM(LEADING 'The ' FROM titleText),1,1) ORDER BY 1;"); - while ($row = mysql_fetch_assoc($result)){ + while ($row = mysql_fetch_assoc($result)) { $alphArray[$row['letter']] = $row['letter_count']; } return $alphArray; } - - - - //returns array based on search for excel output (export.php) - public function export($whereAdd, $orderBy){ + public function export($whereAdd, $orderBy) { $config = new Configuration(); - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $orgJoinAdd = "LEFT JOIN " . $dbName . ".Organization O ON O.organizationID = ROL.organizationID LEFT JOIN " . $dbName . ".Alias OA ON OA.organizationID = ROL.organizationID"; $orgSelectAdd = "GROUP_CONCAT(DISTINCT O.name ORDER BY O.name DESC SEPARATOR '; ') organizationNames"; - }else{ + } else { $orgJoinAdd = "LEFT JOIN Organization O ON O.organizationID = ROL.organizationID"; $orgSelectAdd = "GROUP_CONCAT(DISTINCT O.shortName ORDER BY O.shortName DESC SEPARATOR '; ') organizationNames"; @@ -1291,7 +1206,7 @@ public function export($whereAdd, $orderBy){ $licSelectAdd = ''; $licJoinAdd = ''; - if ($config->settings->licensingModule == 'Y'){ + if ($config->settings->licensingModule == 'Y') { $dbName = $config->settings->licensingDatabaseName; $licJoinAdd = " LEFT JOIN ResourceLicenseLink RLL ON RLL.resourceID = R.resourceID @@ -1301,18 +1216,17 @@ public function export($whereAdd, $orderBy){ $licSelectAdd = "GROUP_CONCAT(DISTINCT L.shortName ORDER BY L.shortName DESC SEPARATOR '; ') licenseNames, GROUP_CONCAT(DISTINCT LS.shortName, ': ', DATE_FORMAT(RLS.licenseStatusChangeDate, '%m/%d/%Y') ORDER BY RLS.licenseStatusChangeDate DESC SEPARATOR '; ') licenseStatuses, "; - } - $status = new Status(); + $status = new Status(); //also add to not retrieve saved records $savedStatusID = intval($status->getIDFromName('saved')); $whereAdd[] = "R.statusID != " . $savedStatusID; - - if (count($whereAdd) > 0){ + + if (count($whereAdd) > 0) { $whereStatement = " WHERE " . implode(" AND ", $whereAdd); - }else{ + } else { $whereStatement = ""; } @@ -1373,21 +1287,21 @@ public function export($whereAdd, $orderBy){ " . $whereStatement . " GROUP BY R.resourceID ORDER BY " . $orderBy; - + $result = $this->db->processQuery(stripslashes($query), 'assoc'); $searchArray = array(); $resultArray = array(); //need to do this since it could be that there's only one result and this is how the dbservice returns result - if (isset($result['resourceID'])){ + if (isset($result['resourceID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($searchArray, $resultArray); - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -1400,25 +1314,19 @@ public function export($whereAdd, $orderBy){ return $searchArray; } - - - - - - //search used index page drop down - public function getOrganizationList(){ + public function getOrganizationList() { $config = new Configuration; $orgArray = array(); //if the org module is installed get the org names from org database - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $query = "SELECT name, organizationID FROM " . $dbName . ".Organization ORDER BY 1;"; - //otherwise get the orgs from this database - }else{ + //otherwise get the orgs from this database + } else { $query = "SELECT shortName name, organizationID FROM Organization ORDER BY 1;"; } @@ -1428,14 +1336,14 @@ public function getOrganizationList(){ $resultArray = array(); //need to do this since it could be that there's only one result and this is how the dbservice returns result - if (isset($result['organizationID'])){ + if (isset($result['organizationID'])) { foreach (array_keys($result) as $attributeName) { $resultArray[$attributeName] = $result[$attributeName]; } array_push($orgArray, $resultArray); - }else{ + } else { foreach ($result as $row) { $resultArray = array(); foreach (array_keys($row) as $attributeName) { @@ -1446,17 +1354,14 @@ public function getOrganizationList(){ } return $orgArray; - } - - //gets an array of organizations set up for this resource (organizationID, organization, organizationRole) - public function getOrganizationArray(){ + public function getOrganizationArray() { $config = new Configuration; //if the org module is installed get the org name from org database - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $resourceOrgArray = array(); @@ -1468,14 +1373,14 @@ public function getOrganizationArray(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['organizationID'])){ + if (isset($result['organizationID'])) { $orgArray = array(); //first, get the organization name $query = "SELECT name FROM " . $dbName . ".Organization WHERE organizationID = " . $result['organizationID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organization'] = $orgRow['name']; $orgArray['organizationID'] = $result['organizationID']; } @@ -1484,15 +1389,15 @@ public function getOrganizationArray(){ //then, get the role name $query = "SELECT * FROM " . $dbName . ".OrganizationRole WHERE organizationRoleID = " . $result['organizationRoleID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; $orgArray['organizationRole'] = $orgRow['shortName']; } } array_push($resourceOrgArray, $orgArray); - }else{ + } else { foreach ($result as $row) { $orgArray = array(); @@ -1500,8 +1405,8 @@ public function getOrganizationArray(){ //first, get the organization name $query = "SELECT name FROM " . $dbName . ".Organization WHERE organizationID = " . $row['organizationID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organization'] = $orgRow['name']; $orgArray['organizationID'] = $row['organizationID']; } @@ -1511,17 +1416,15 @@ public function getOrganizationArray(){ $query = "SELECT * FROM " . $dbName . ".OrganizationRole WHERE organizationRoleID = " . $row['organizationRoleID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; $orgArray['organizationRole'] = $orgRow['shortName']; } } array_push($resourceOrgArray, $orgArray); - } - } @@ -1529,8 +1432,8 @@ public function getOrganizationArray(){ - //otherwise if the org module is not installed get the org name from this database - }else{ + //otherwise if the org module is not installed get the org name from this database + } else { @@ -1543,14 +1446,14 @@ public function getOrganizationArray(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['organizationID'])){ + if (isset($result['organizationID'])) { $orgArray = array(); //first, get the organization name $query = "SELECT shortName FROM Organization WHERE organizationID = " . $result['organizationID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organization'] = $orgRow['shortName']; $orgArray['organizationID'] = $result['organizationID']; } @@ -1559,15 +1462,15 @@ public function getOrganizationArray(){ //then, get the role name $query = "SELECT * FROM OrganizationRole WHERE organizationRoleID = " . $result['organizationRoleID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; $orgArray['organizationRole'] = $orgRow['shortName']; } } array_push($resourceOrgArray, $orgArray); - }else{ + } else { foreach ($result as $row) { $orgArray = array(); @@ -1575,8 +1478,8 @@ public function getOrganizationArray(){ //first, get the organization name $query = "SELECT shortName FROM Organization WHERE organizationID = " . $row['organizationID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organization'] = $orgRow['shortName']; $orgArray['organizationID'] = $row['organizationID']; } @@ -1586,39 +1489,28 @@ public function getOrganizationArray(){ $query = "SELECT * FROM OrganizationRole WHERE organizationRoleID = " . $row['organizationRoleID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; $orgArray['organizationRole'] = $orgRow['shortName']; } } array_push($resourceOrgArray, $orgArray); - } - } - - - - - } return $resourceOrgArray; } - - - - //gets an array of distinct organizations set up for this resource (organizationID, organization) - public function getDistinctOrganizationArray(){ + public function getDistinctOrganizationArray() { $config = new Configuration; //if the org module is installed get the org name from org database - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $resourceOrgArray = array(); @@ -1630,21 +1522,21 @@ public function getDistinctOrganizationArray(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['organizationID'])){ + if (isset($result['organizationID'])) { $orgArray = array(); //first, get the organization name $query = "SELECT name FROM " . $dbName . ".Organization WHERE organizationID = " . $result['organizationID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organization'] = $orgRow['name']; $orgArray['organizationID'] = $result['organizationID']; } } array_push($resourceOrgArray, $orgArray); - }else{ + } else { foreach ($result as $row) { $orgArray = array(); @@ -1652,17 +1544,15 @@ public function getDistinctOrganizationArray(){ //first, get the organization name $query = "SELECT DISTINCT name FROM " . $dbName . ".Organization WHERE organizationID = " . $row['organizationID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organization'] = $orgRow['name']; $orgArray['organizationID'] = $row['organizationID']; } } array_push($resourceOrgArray, $orgArray); - } - } @@ -1670,8 +1560,8 @@ public function getDistinctOrganizationArray(){ - //otherwise if the org module is not installed get the org name from this database - }else{ + //otherwise if the org module is not installed get the org name from this database + } else { @@ -1684,21 +1574,21 @@ public function getDistinctOrganizationArray(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['organizationID'])){ + if (isset($result['organizationID'])) { $orgArray = array(); //first, get the organization name $query = "SELECT DISTINCT shortName FROM Organization WHERE organizationID = " . $result['organizationID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organization'] = $orgRow['shortName']; $orgArray['organizationID'] = $result['organizationID']; } } array_push($resourceOrgArray, $orgArray); - }else{ + } else { foreach ($result as $row) { $orgArray = array(); @@ -1706,69 +1596,55 @@ public function getDistinctOrganizationArray(){ //first, get the organization name $query = "SELECT DISTINCT shortName FROM Organization WHERE organizationID = " . $row['organizationID']; - if ($orgResult = mysql_query($query)){ - while ($orgRow = mysql_fetch_assoc($orgResult)){ + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { $orgArray['organization'] = $orgRow['shortName']; $orgArray['organizationID'] = $row['organizationID']; } } array_push($resourceOrgArray, $orgArray); - } - } - - - - - } return $resourceOrgArray; } - - public function hasCatalogingInformation() { - return ($this->recordSetIdentifier || $this->recordSetIdentifier || $this->bibSourceURL || $this->catalogingTypeID || $this->catalogingStatusID || $this->numberRecordsAvailable || $this->numberRecordsLoaded || $this->hasOclcHoldings); - } - - - + public function hasCatalogingInformation() { + return ($this->recordSetIdentifier || $this->recordSetIdentifier || $this->bibSourceURL || $this->catalogingTypeID || $this->catalogingStatusID || $this->numberRecordsAvailable || $this->numberRecordsLoaded || $this->hasOclcHoldings); + } //removes this resource and its children - public function removeResourceAndChildren(){ - - // for each children - foreach ($this->getChildResources() as $instance) { - $removeChild = true; - $child = new Resource(new NamedArguments(array('primaryKey' => $instance->resourceID))); - - // get parents of this children - $parents = $child->getParentResources(); - - // If the child ressource belongs to another parent than the one we're removing - foreach ($parents as $pinstance) { - $parentResourceObj = new Resource(new NamedArguments(array('primaryKey' => $pinstance->relatedResourceID))); - if ($parentResourceObj->resourceID != $this->resourceID) { - // We do not delete this child. - $removeChild = false; - } - } - if ($removeChild == true) { - $child->removeResource(); - } - } - // Finally, we remove the parent - $this->removeResource(); + public function removeResourceAndChildren() { + + // for each children + foreach ($this->getChildResources() as $instance) { + $removeChild = true; + $child = new Resource(new NamedArguments(array('primaryKey' => $instance->resourceID))); + + // get parents of this children + $parents = $child->getParentResources(); + + // If the child ressource belongs to another parent than the one we're removing + foreach ($parents as $pinstance) { + $parentResourceObj = new Resource(new NamedArguments(array('primaryKey' => $pinstance->relatedResourceID))); + if ($parentResourceObj->resourceID != $this->resourceID) { + // We do not delete this child. + $removeChild = false; + } + } + if ($removeChild == true) { + $child->removeResource(); + } + } + // Finally, we remove the parent + $this->removeResource(); } - - - //removes this resource - public function removeResource(){ + public function removeResource() { //delete data from child linked tables $this->removeResourceRelationships(); $this->removePurchaseSites(); @@ -1778,8 +1654,8 @@ public function removeResource(){ $this->removeResourceLicenseStatuses(); $this->removeResourceOrganizations(); $this->removeResourcePayments(); - $this->removeAllSubjects(); - $this->removeAllIsbnOrIssn(); + $this->removeAllSubjects(); + $this->removeAllIsbnOrIssn(); $instance = new Contact(); @@ -1812,10 +1688,8 @@ public function removeResource(){ $this->delete(); } - - //removes resource hierarchy records - public function removeResourceRelationships(){ + public function removeResourceRelationships() { $query = "DELETE FROM ResourceRelationship @@ -1824,10 +1698,8 @@ public function removeResourceRelationships(){ $result = $this->db->processQuery($query); } - - //removes resource purchase sites - public function removePurchaseSites(){ + public function removePurchaseSites() { $query = "DELETE FROM ResourcePurchaseSiteLink @@ -1836,11 +1708,8 @@ public function removePurchaseSites(){ $result = $this->db->processQuery($query); } - - - //removes resource authorized sites - public function removeAuthorizedSites(){ + public function removeAuthorizedSites() { $query = "DELETE FROM ResourceAuthorizedSiteLink @@ -1849,10 +1718,8 @@ public function removeAuthorizedSites(){ $result = $this->db->processQuery($query); } - - //removes resource administering sites - public function removeAdministeringSites(){ + public function removeAdministeringSites() { $query = "DELETE FROM ResourceAdministeringSiteLink @@ -1861,10 +1728,8 @@ public function removeAdministeringSites(){ $result = $this->db->processQuery($query); } - - //removes payment records - public function removeResourcePayments(){ + public function removeResourcePayments() { $query = "DELETE FROM ResourcePayment @@ -1874,7 +1739,7 @@ public function removeResourcePayments(){ } //removes resource licenses - public function removeResourceLicenses(){ + public function removeResourceLicenses() { $query = "DELETE FROM ResourceLicenseLink @@ -1884,7 +1749,7 @@ public function removeResourceLicenses(){ } //removes resource license statuses - public function removeResourceLicenseStatuses(){ + public function removeResourceLicenseStatuses() { $query = "DELETE FROM ResourceLicenseStatus @@ -1894,7 +1759,7 @@ public function removeResourceLicenseStatuses(){ } //removes resource organizations - public function removeResourceOrganizations(){ + public function removeResourceOrganizations() { $query = "DELETE FROM ResourceOrganizationLink @@ -1903,9 +1768,8 @@ public function removeResourceOrganizations(){ $result = $this->db->processQuery($query); } - //removes resource note records - public function removeResourceNotes(){ + public function removeResourceNotes() { $query = "DELETE FROM ResourceNote @@ -1914,11 +1778,8 @@ public function removeResourceNotes(){ $result = $this->db->processQuery($query); } - - - //removes resource steps - public function removeResourceSteps(){ + public function removeResourceSteps() { $query = "DELETE FROM ResourceStep @@ -1927,33 +1788,28 @@ public function removeResourceSteps(){ $result = $this->db->processQuery($query); } - - - - //search used for the resource autocomplete - public function resourceAutocomplete($q){ + public function resourceAutocomplete($q) { $resourceArray = array(); $result = mysql_query("SELECT titleText, resourceID FROM Resource WHERE upper(titleText) like upper('%" . $q . "%') ORDER BY 1;"); - while ($row = mysql_fetch_assoc($result)){ + while ($row = mysql_fetch_assoc($result)) { $resourceArray[] = $row['titleText'] . "|" . $row['resourceID']; } return $resourceArray; } - //search used for the organization autocomplete - public function organizationAutocomplete($q){ + public function organizationAutocomplete($q) { $config = new Configuration; $organizationArray = array(); //if the org module is installed get the org name from org database - if ($config->settings->organizationsModule == 'Y'){ + if ($config->settings->organizationsModule == 'Y') { $dbName = $config->settings->organizationsDatabaseName; $result = mysql_query("SELECT CONCAT(A.name, ' (', O.name, ')') shortName, O.organizationID @@ -1965,18 +1821,16 @@ public function organizationAutocomplete($q){ FROM " . $dbName . ".Organization WHERE upper(name) like upper('%" . $q . "%') ORDER BY 1;"); - - }else{ + } else { $result = mysql_query("SELECT organizationID, shortName FROM Organization O WHERE upper(O.shortName) like upper('%" . $q . "%') ORDER BY shortName;"); - } - while ($row = mysql_fetch_assoc($result)){ + while ($row = mysql_fetch_assoc($result)) { $organizationArray[] = $row['shortName'] . "|" . $row['organizationID']; } @@ -1985,26 +1839,22 @@ public function organizationAutocomplete($q){ return $organizationArray; } - - - //search used for the license autocomplete - public function licenseAutocomplete($q){ + public function licenseAutocomplete($q) { $config = new Configuration; $licenseArray = array(); //if the org module is installed get the org name from org database - if ($config->settings->licensingModule == 'Y'){ + if ($config->settings->licensingModule == 'Y') { $dbName = $config->settings->licensingDatabaseName; $result = mysql_query("SELECT shortName, licenseID FROM " . $dbName . ".License WHERE upper(shortName) like upper('%" . $q . "%') ORDER BY 1;"); - } - while ($row = mysql_fetch_assoc($result)){ + while ($row = mysql_fetch_assoc($result)) { $licenseArray[] = $row['shortName'] . "|" . $row['licenseID']; } @@ -2013,16 +1863,13 @@ public function licenseAutocomplete($q){ return $licenseArray; } - /////////////////////////////////////////////////////////////////////////////////// // // Workflow functions follow // /////////////////////////////////////////////////////////////////////////////////// - - //returns array of ResourceStep objects for this Resource - public function getResourceSteps(){ + public function getResourceSteps() { $query = "SELECT * FROM ResourceStep @@ -2034,10 +1881,10 @@ public function getResourceSteps(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceStepID'])){ + if (isset($result['resourceStepID'])) { $object = new ResourceStep(new NamedArguments(array('primaryKey' => $result['resourceStepID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new ResourceStep(new NamedArguments(array('primaryKey' => $row['resourceStepID']))); array_push($objects, $object); @@ -2045,15 +1892,11 @@ public function getResourceSteps(){ } return $objects; - } - - - //returns current step location in the workflow for this resource //used to display the group on the tabs - public function getCurrentStepGroup(){ + public function getCurrentStepGroup() { $query = "SELECT groupName FROM ResourceStep RS, UserGroup UG @@ -2065,16 +1908,13 @@ public function getCurrentStepGroup(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceStepID'])){ - + if (isset($result['resourceStepID'])) { + } - } - - //returns first steps (object) in the workflow for this resource - public function getFirstSteps(){ + public function getFirstSteps() { $query = "SELECT * FROM ResourceStep WHERE resourceID = '" . $this->resourceID . "' @@ -2086,10 +1926,10 @@ public function getFirstSteps(){ $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceStepID'])){ + if (isset($result['resourceStepID'])) { $object = new ResourceStep(new NamedArguments(array('primaryKey' => $result['resourceStepID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new ResourceStep(new NamedArguments(array('primaryKey' => $row['resourceStepID']))); array_push($objects, $object); @@ -2097,14 +1937,10 @@ public function getFirstSteps(){ } return $objects; - - } - - //enters resource into new workflow - public function enterNewWorkflow(){ + public function enterNewWorkflow() { $config = new Configuration(); //remove any current workflow steps @@ -2120,32 +1956,30 @@ public function enterNewWorkflow(){ $workflowObj = new Workflow(); $workflowID = $workflowObj->getWorkflowID($this->resourceTypeID, $this->resourceFormatID, $this->acquisitionTypeID); - if ($workflowID){ + if ($workflowID) { $workflow = new Workflow(new NamedArguments(array('primaryKey' => $workflowID))); //Copy all of the step attributes for this workflow to a new resource step - foreach ($workflow->getSteps() as $step){ + foreach ($workflow->getSteps() as $step) { $resourceStep = new ResourceStep(); - $resourceStep->resourceStepID = ''; - $resourceStep->resourceID = $this->resourceID; - $resourceStep->stepID = $step->stepID; - $resourceStep->priorStepID = $step->priorStepID; - $resourceStep->stepName = $step->stepName; - $resourceStep->userGroupID = $step->userGroupID; - $resourceStep->displayOrderSequence = $step->displayOrderSequence; + $resourceStep->resourceStepID = ''; + $resourceStep->resourceID = $this->resourceID; + $resourceStep->stepID = $step->stepID; + $resourceStep->priorStepID = $step->priorStepID; + $resourceStep->stepName = $step->stepName; + $resourceStep->userGroupID = $step->userGroupID; + $resourceStep->displayOrderSequence = $step->displayOrderSequence; $resourceStep->save(); - } //Start the first step //this handles updating the db and sending notifications for approval groups - foreach ($this->getFirstSteps() as $resourceStep){ + foreach ($this->getFirstSteps() as $resourceStep) { $resourceStep->startStep(); - } } @@ -2154,54 +1988,49 @@ public function enterNewWorkflow(){ $cUser = new User(new NamedArguments(array('primaryKey' => $this->createLoginID))); $acquisitionType = new AcquisitionType(new NamedArguments(array('primaryKey' => $this->acquisitionTypeID))); - if ($cUser->firstName){ + if ($cUser->firstName) { $creator = $cUser->firstName . " " . $cUser->lastName; - }else if ($this->createLoginID){ //for some reason user isn't set up or their firstname/last name don't exist + } else if ($this->createLoginID) { //for some reason user isn't set up or their firstname/last name don't exist $creator = $this->createLoginID; - }else{ + } else { $creator = "(unknown user)"; } - if (($config->settings->feedbackEmailAddress) || ($cUser->emailAddress)){ + if (($config->settings->feedbackEmailAddress) || ($cUser->emailAddress)) { $email = new Email(); $util = new Utility(); $email->message = $util->createMessageFromTemplate('NewResourceMain', $this->resourceID, $this->titleText, '', '', $creator); - if ($cUser->emailAddress){ - $emailTo[] = $cUser->emailAddress; + if ($cUser->emailAddress) { + $emailTo[] = $cUser->emailAddress; } - if ($config->settings->feedbackEmailAddress != ''){ - $emailTo[] = $config->settings->feedbackEmailAddress; + if ($config->settings->feedbackEmailAddress != '') { + $emailTo[] = $config->settings->feedbackEmailAddress; } $email->to = implode(",", $emailTo); - if ($acquisitionType->shortName){ - $email->subject = "CORAL Alert: New " . $acquisitionType->shortName . " Resource Added: " . $this->titleText; - }else{ - $email->subject = "CORAL Alert: New Resource Added: " . $this->titleText; + if ($acquisitionType->shortName) { + $email->subject = "CORAL Alert: New " . $acquisitionType->shortName . " Resource Added: " . $this->titleText; + } else { + $email->subject = "CORAL Alert: New Resource Added: " . $this->titleText; } $email->send(); - } - } - - - //completes a workflow (changes status to complete and sends notifications to creator and "master email") - public function completeWorkflow(){ + public function completeWorkflow() { $config = new Configuration(); $util = new Utility(); $status = new Status(); $statusID = $status->getIDFromName('complete'); - if ($statusID){ + if ($statusID) { $this->statusID = $statusID; $this->save(); } @@ -2216,25 +2045,25 @@ public function completeWorkflow(){ $email = new Email(); $email->message = $util->createMessageFromTemplate('CompleteResource', $this->resourceID, $this->titleText, '', $this->systemNumber, ''); - if ($cUser->emailAddress){ - $emailTo[] = $cUser->emailAddress; + if ($cUser->emailAddress) { + $emailTo[] = $cUser->emailAddress; } - if ($config->settings->feedbackEmailAddress != ''){ - $emailTo[] = $config->settings->feedbackEmailAddress; + if ($config->settings->feedbackEmailAddress != '') { + $emailTo[] = $config->settings->feedbackEmailAddress; } $email->to = implode(",", $emailTo); - $email->subject = "CORAL Alert: Workflow completion for " . $this->titleText; + $email->subject = "CORAL Alert: Workflow completion for " . $this->titleText; $email->send(); } //returns array of subject objects - public function getGeneralDetailSubjectLinkID(){ - + public function getGeneralDetailSubjectLinkID() { + $query = "SELECT GDL.generalDetailSubjectLinkID FROM @@ -2248,29 +2077,29 @@ public function getGeneralDetailSubjectLinkID(){ ORDER BY GS.shortName, DS.shortName"; - - + + $result = $this->db->processQuery($query, 'assoc'); $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['generalDetailSubjectLinkID'])){ + if (isset($result['generalDetailSubjectLinkID'])) { $object = new GeneralDetailSubjectLink(new NamedArguments(array('primaryKey' => $result['generalDetailSubjectLinkID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new GeneralDetailSubjectLink(new NamedArguments(array('primaryKey' => $row['generalDetailSubjectLinkID']))); array_push($objects, $object); } } - + return $objects; - } + } //returns array of subject objects - public function getDetailedSubjects($resourceID, $generalSubjectID){ - + public function getDetailedSubjects($resourceID, $generalSubjectID) { + $query = "SELECT RSUB.resourceID, GDL.detailedSubjectID, @@ -2284,16 +2113,16 @@ public function getDetailedSubjects($resourceID, $generalSubjectID){ RSUB.resourceID = " . $resourceID . " AND GDL.generalSubjectID = " . $generalSubjectID . " ORDER BY DetailedSubject.shortName"; //echo $query . "
    "; - + $result = $this->db->processQuery($query, 'assoc'); $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['detailedSubjectID'])){ + if (isset($result['detailedSubjectID'])) { $object = new DetailedSubject(new NamedArguments(array('primaryKey' => $result['detailedSubjectID']))); array_push($objects, $object); - }else{ + } else { foreach ($result as $row) { $object = new DetailedSubject(new NamedArguments(array('primaryKey' => $row['detailedSubjectID']))); array_push($objects, $object); @@ -2301,72 +2130,144 @@ public function getDetailedSubjects($resourceID, $generalSubjectID){ } return $objects; - } - + } //removes all resource subjects - public function removeAllSubjects(){ + public function removeAllSubjects() { $query = "DELETE FROM ResourceSubject WHERE resourceID = '" . $this->resourceID . "'"; $result = $this->db->processQuery($query); - - } + } - public function removeAllIsbnOrIssn() { - $query = "DELETE + public function removeAllIsbnOrIssn() { + $query = "DELETE FROM IsbnOrIssn WHERE resourceID = '" . $this->resourceID . "'"; $result = $this->db->processQuery($query); + } + + public function setIsbnOrIssn($isbnorissns) { + $this->removeAllIsbnOrIssn(); + foreach ($isbnorissns as $isbnorissn) { + if (trim($isbnorissn) != '') { + $isbnOrIssn = new IsbnOrIssn(); + $isbnOrIssn->resourceID = $this->resourceID; + $isbnOrIssn->isbnOrIssn = $isbnorissn; + $isbnOrIssn->save(); + } + } + } - } - - public function setIsbnOrIssn($isbnorissns) { - $this->removeAllIsbnOrIssn(); - foreach ($isbnorissns as $isbnorissn) { - if (trim($isbnorissn) != '') { - $isbnOrIssn = new IsbnOrIssn(); - $isbnOrIssn->resourceID = $this->resourceID; - $isbnOrIssn->isbnOrIssn = $isbnorissn; - $isbnOrIssn->save(); - } - } - } - - - /******************************** - * NEW FUNCTION * - ********************************/ - /* TODO - /!\ /!\ /!\ - /!\ rgrep IsbnOrIssn avant de tout casser !! /!\ - /!\ /!\ /!\ - */ - - - /** - * Fill the Identifier table in DB - * @param $identifiers array array of all identifiers (type => id) (if type isn't known, don't put any key) - * - */ - public function setIdentifiers($identifiers){ - - foreach ($identifiers as $key => $value) { - $identifier = new Identifier(); - $identifier->resourceID = $this->resourceID; + /* * ******************************************* + * NEW FUNCTION * + * ******************************************* */ + /* TODO + /!\ /!\ /!\ + /!\ rgrep IsbnOrIssn avant de tout casser !! /!\ + /!\ /!\ /!\ + */ + + /** + * Fill the Identifier table in DB + * @param $identifiers array array of all identifiers (type => id) (if type isn't known, don't put any key) + * + */ + public function setIdentifiers($identifiers) { + $isbnorissns = array(); + foreach ($identifiers as $key => $value) { + $identifier = new Identifier(); + $identifier->resourceID = $this->resourceID; $identifier->identifierTypeID = $identifier->getIdentifierTypeID($key); $identifier->identifier = $value; - - $identifier->save(); - } - } + $identifier->save(); + + //Temporary fill IsbnOrIssn table + if ($identifier->identifierTypeID <= 5) { + array_push($isbnorissns, $value); + } + } + $this->setIsbnOrIssn($isbnorissns); + } + + public function getResourceByIdentifierAndType($identifier, $type = NULL) { //TODO _ $identifier est un tableau !! => do boucle !! + $query = "SELECT resourceID FROM Identifier WHERE upper(identifier) = '" . str_replace("'", "''", strtoupper($identifier)) . "'"; + if ($type != NULL) { + $id = new Identifier(); + $typeID = $id->getIdentifierTypeID($type); + $query .= " AND identifierTypeID = $typeID"; + } + $query .= ";"; + $result = $this->db->processQuery($query, 'assoc'); //TODO _ see param assoc + + $objects = array(); + + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceID'])) { + $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); + array_push($objects, $object); + } + } + + return $objects; + } + + public function getResourceByIdentifiers($identifiers) { + $query = "SELECT DISTINCT(resourceID) FROM Identifier"; + $i = 0; + /* + if (!is_array($identifiers)) { + $value = $identifiers; + $identifiers = array($value); + } + */ + foreach ($identifiers as $value) { + $query .= ($i == 0) ? " WHERE " : " OR "; + $query .= "identifier = '" . $this->db->escapeString($value) . "'"; + $i++; + } + + $query .= " ORDER BY 1"; + $result = $this->db->processQuery($query, 'assoc'); + $objects = array(); + + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceID'])) { + $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); + array_push($objects, $object); + } + } + return $objects; + } + + public function &getNewInitializedResource() { + $loginID = $_SESSION['loginID']; + $res = new Resource(); + $res->createLoginID = $loginID; + $res->createDate = date('Y-m-d'); + $res->updateLoginID = ''; + $res->updateDate = ''; + $res->statusID = 1; //in progress, don't know why ... + //$res->save(); + + return $res; + } + } ?> diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index 5545be0..664d852 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -19,25 +19,39 @@ $recordDetails=$record->{'metadata'}->{'gokb'}->{$_POST['type']}; -$datas['titleText'] = $recordDetails->{'name'}; - +$datas['titleText'] = $gokbTool->getResourceName($recordDetails); $string = ""; if ($_POST['type'] == 'package'){ //resource parent (package lui meme) + $acqID = AcquisitionType::getAcquisitionTypeID($recordDetails->{"paymentType"}); + if ($acqID != null){ + $datas["acquisitionTypeID"] = $acqID; + $string .= "insertion de datas[acquisitionTypeID] = ".$acqID."
    "; + } + + //Organization: + $datas['organization'] = array ( + "platform" => $recordDetails->{"nominalPlatform"}, + "provider" => $recordDetails->{"nominalProvider"} + ); + $string .= "insertion organizations "; + //ensemble des tipps (boucle) - $tippsDatas = array(); + //$tippsDatas = array(); + + } else{ //import title; //TODO medium = resourceType (ajout manuel only pour l'instant) //Organization $org = $recordDetails->{"publisher"}->{'name'}; - if ($org ){ - $data["organization"]=array("publisher" => $org); + if ($org != NULL){ + $datas['organization']=array("publisher" => $org); $string .= "insertion de datas['organization'] = ".$org."
    "; } @@ -48,8 +62,8 @@ foreach ($xmlIDs as $key => $value) { $tmp = $value->attributes(); - $datas[$tmp[0]] = $tmp[1]; - $string .= "insertion de datas[".$tmp[0]."] = ".$tmp[1]."
    "; + $identifiers["$tmp[0]"] = (string) $tmp[1]; + $string .= "insertion de identifiers[".$tmp[0]."] = ".$tmp[1]."
    "; } @@ -57,9 +71,12 @@ //History / Aliases -echo "$string"; + //$datas['parentResource'] ? +//echo "$string"; +$importTool->addResource($datas, $identifiers); +return $string; } From 310e52ce876a00e9542035b30f8a859714424c82 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Thu, 30 Jul 2015 16:06:45 +0200 Subject: [PATCH 10/32] new import csv with common import tool --- admin/classes/domain/GOKbTools.php | 13 +- admin/classes/domain/Identifier.php | 1 - admin/classes/domain/ImportTool.php | 665 +++++++++++++++------------- ajax_processing/importFromGOKb.php | 133 +++--- import.php | 227 ++-------- 5 files changed, 466 insertions(+), 573 deletions(-) diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 037a2ec..b86fb16 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -285,10 +285,13 @@ function displayRecordTipps($record, $recordType){ $string = ""; $tipps=$record->{'TIPPs'}; - if ($recordType == 'package') $type = 'title'; - else $type = 'package'; + if ($recordType == 'package') { + $type = 'title'; + } else { + $type = 'package'; + } - $string .= " "; + $string .= "
    "; foreach ($tipps->children() as $child) { $resource = $child->{$type}; @@ -309,7 +312,7 @@ function displayRecordTipps($record, $recordType){ * @return string name of the resource */ function getResourceName($record){ - return $record->{'name'}; + return (string) $record->{'name'}; } // ------------------------------------------------------------------------- /** @@ -320,7 +323,7 @@ function getResourceName($record){ function getNbTipps($record){ $tipps = $record->{'TIPPs'}; $tmp = $tipps->attributes(); - return $tmp[0]; + return (int) $tmp[0]; } // ------------------------------------------------------------------------- diff --git a/admin/classes/domain/Identifier.php b/admin/classes/domain/Identifier.php index fe4b95b..931f0c6 100644 --- a/admin/classes/domain/Identifier.php +++ b/admin/classes/domain/Identifier.php @@ -28,7 +28,6 @@ public function getIdentifierTypeID($type) { $config = new Configuration(); $dbName = $config->settings->resourcesDatabaseName; $query = "SELECT identifierTypeID FROM IdentifierType WHERE UPPER(identifierName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; -//$query = "SELECT identifierTypeID FROM $dbName.IdentifierType WHERE UPPER(identifierName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; $result = $this->db->processQuery($query); if (count($result) == 0) {//this type doesn't exist diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index 1a52ed6..4dcdfbd 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -6,353 +6,382 @@ class ImportTool { - private $config; - - /** - * @var int - * Number of resources treated - */ - private static $row; - - /** - * @var int - * Number of resources inserted - */ - private static $inserted; - - /** - * @var int - * Number of parents inserted - */ - private static $parentInserted; - - /** - * @var int - * Number of parents which resources were attached - */ - private static $parentAttached; - - /** - * @var int - * Number of organizations created - */ - private static $organizationsInserted; - - /** - * @var int - * Number of organizations which resources were attached - */ - private static $organizationsAttached; - - /** - * @var array - * Organizations created - */ - private static $arrayOrganizationsCreated; + private $config; + + /** + * @var int + * Number of resources treated + */ + private static $row; + + /** + * @var int + * Number of resources inserted + */ + private static $inserted; + + /** + * @var int + * Number of parents inserted + */ + private static $parentInserted; + + /** + * @var int + * Number of parents which resources were attached + */ + private static $parentAttached; + + /** + * @var int + * Number of organizations created + */ + private static $organizationsInserted; + + /** + * @var int + * Number of organizations which resources were attached + */ + private static $organizationsAttached; + + /** + * @var array + * Organizations created + */ + private static $arrayOrganizationsCreated; // ------------------------------------------------------------------------- - function __construct() { - $this->config = new Configuration(); - self::$row = 0; - self::$inserted = 0; - self::$parentInserted = 0; - self::$parentAttached = 0; - self::$organizationsInserted = 0; - self::$organizationsAttached = 0; - self::$arrayOrganizationsCreated = array(); - } + function __construct() { + $this->config = new Configuration(); + self::$row = 0; + self::$inserted = 0; + self::$parentInserted = 0; + self::$parentAttached = 0; + self::$organizationsInserted = 0; + self::$organizationsAttached = 0; + self::$arrayOrganizationsCreated = array(); + } // ------------------------------------------------------------------------- - /** - * add a resource to the database - * @param $datas array all datas about the resource - * @param $identifiers array list of identifiers (type =>identifier) - */ - public function addResource($datas, $identifiers) { - $res_tmp = new Resource(); - $org = null; - $parentName = null; - - $htmlContent = ''; //TODO _ DEBUG _ Displaying after select - - /******************************************* - * * Has the resource to be inserted ? ** - * ******************************************/ - $hasToBeInserted = $this->hasResourceToBeInserted($datas, $identifiers); - - /**************************************** - * * Datas insertion ** - * ***************************************/ - if ($hasToBeInserted) { - $res = $res_tmp->getNewInitializedResource(); - echo "DEBUG _ Resource insertion !!
    "; - - //Resource treatment - foreach ($datas as $key => $value) { - if ($key == "organization") { - $org = $value; - } elseif ($key == "parentResource") { - $parentName = $value; - } else { - $res->$key = (string) $value; - } - } - $res->save(); - - //Resource identifiers treatment - $res->setIdentifiers($identifiers); - - //Parent treatment - if ($parentName != null) { - $parentID = $this->parentTreatment($parentName); - $this->setResourcesRelationship($res->resourceID, $parentID); - } - - if ($org != null) { // Do we have to create an organization or attach the resource to an existing one? - $this->organizationTreatment($org, $res->resourceID); - } - } else { - echo "DEBUG _ Resource not inserted !
    "; - } - } + /** + * add a resource to the database + * @param $datas array all datas about the resource + * @param $identifiers array list of identifiers (type =>identifier) + */ + public function addResource($datas, $identifiers) { + $res_tmp = new Resource(); + $org = null; + $parents = null; + + $htmlContent = ''; //TODO _ DEBUG _ Displaying after select + + /* * ***************************************** + * * Has the resource to be inserted ? ** + * ***************************************** */ + $hasToBeInserted = $this->hasResourceToBeInserted($datas, $identifiers); + + /* * ************************************** + * * Datas insertion ** + * ************************************** */ + if ($hasToBeInserted) { + $res = $res_tmp->getNewInitializedResource(); + echo "DEBUG _ Resource insertion !!
    "; + + //Resource treatment + foreach ($datas as $key => $value) { + if ($key == "organization") { + $org = $value; + } elseif ($key == "parentResource") { + $parentName = $value; + // $parents = $value; + } else { + $res->$key = (string) $value; + } + } + $res->save(); + + //Resource identifiers treatment + $res->setIdentifiers($identifiers); + + //Parent treatment + if ($parentName != null) { + $parentID = $this->parentTreatment($parentName); + $this->setResourcesRelationship($res->resourceID, $parentID); + } + + if ($org != null) { // Do we have to create an organization or attach the resource to an existing one? + $this->organizationTreatment($org, $res->resourceID); + } + } else { + echo "DEBUG _ Resource not inserted !
    "; + } + echo 'DEBUG_ AddResource finished'; + } // ------------------------------------------------------------------------- - private function setResourceOrganizationLink($roleID, $resourceID, $organizationID) { - $organizationLink = new ResourceOrganizationLink(); - $organizationLink->organizationRoleID = $roleID; - $organizationLink->resourceID = $resourceID; - $organizationLink->organizationID = $organizationID; - $organizationLink->save(); - } + private function setResourceOrganizationLink($roleID, $resourceID, $organizationID) { + $organizationLink = new ResourceOrganizationLink(); + $organizationLink->organizationRoleID = $roleID; + $organizationLink->resourceID = $resourceID; + $organizationLink->organizationID = $organizationID; + $organizationLink->save(); + } // ------------------------------------------------------------------------- - /** - * Check if this resource already exist and if we have to add it in DB - * @param type $datas array, all resource datas - * @param type $identifiers array, all resource's identifiers - * @return boolean true if the resource has to be inserterted, false else - */ - private function hasResourceToBeInserted($datas, $identifiers) { - $res_tmp = new Resource(); - $hasToBeInserted = false; - - $resource = $res_tmp->getResourceByIdentifiers($identifiers); - - if (count($resource) == 0) { //resource doesn't exist, we have to create it - $hasToBeInserted = true; - } elseif ($datas['parentResource']) { //resource exists and got a parent, test title + parent - $res = $resource[0]; - //$currentResourceID = $res->resourceID; - $parents = $res->getParentResources(); - $nbParents = count($parents); - - if ($nbParents == 0) { //existing resource doesn't have any parent - $hasToBeInserted = true; - } else { - $hasToBeInserted = true; - foreach ($parents as $parentResource) { - if ($parentResource->titleText == $datas['parentResource']) { - $hasToBeInserted = false; - } - } - } - } - - return $hasToBeInserted; - } - + /** + * Check if this resource already exist and if we have to add it in DB + * @param type $datas array, all resource datas + * @param type $identifiers array, all resource's identifiers + * @return boolean true if the resource has to be inserterted, false else + */ + private function hasResourceToBeInserted($datas, $identifiers) { + $res_tmp = new Resource(); + $hasToBeInserted = false; + + $resource = $res_tmp->getResourceByIdentifiers($identifiers); + + if (count($resource) == 0) { //resource doesn't exist, we have to create it + $hasToBeInserted = true; + } elseif ($datas['parentResource']) { //resource exists and got a parent, test title + parent + $res = $resource[0]; + //$currentResourceID = $res->resourceID; + $parents = $res->getParentResources(); + $nbParents = count($parents); + + if ($nbParents == 0) { //existing resource doesn't have any parent + $hasToBeInserted = true; + } else { + $hasToBeInserted = true; + foreach ($parents as $parentResource) { + if ($parentResource->titleText == $datas['parentResource']) { + $hasToBeInserted = false; + } + } + } + } + + return $hasToBeInserted; + } + // ------------------------------------------------------------------------- - private function parentTreatment($parentName) { - $resource = new Resource(); - $parentResource = $resource->getResourceByTitle($parentName); - - // Search if such parent exists - $numberOfParents = count($parentResource); - $parentID = null; - - if ($numberOfParents == 0) { // If not, create parent - $parentResource = $resource->getNewInitializedResource(); - $parentID = $parentResource->resourceID; - self::$parentInserted++; - } elseif ($numberOfParents == 1) {// Else, attach the resource to its parent. - $parentID = $parentResource[0]->resourceID; - self::$parentAttached++; - } - - return $parentID; - } + private function parentTreatment($parentName) { + $resource = new Resource(); + $parentResource = $resource->getResourceByTitle($parentName); + + // Search if such parent exists + $numberOfParents = count($parentResource); + $parentID = null; + + if ($numberOfParents == 0) { // If not, create parent + $parentResource = $resource->getNewInitializedResource(); + $parentResource->titleText = $parentName; //TODO _ appel à addResource avec $datas compètes + $parentResource->save(); + $parentID = $parentResource->resourceID; + self::$parentInserted++; + } elseif ($numberOfParents == 1) {// Else, attach the resource to its parent. + $parentID = $parentResource[0]->resourceID; + self::$parentAttached++; + } + + return $parentID; + + + } + // ------------------------------------------------------------------------- - private function setResourcesRelationship($resourceID, $parentID){ - if($parentID != NULL){ - $resourceRelationship = new ResourceRelationship(); - $resourceRelationship->resourceID = $resourceID; - $resourceRelationship->relatedResourceID = $parentID; - $resourceRelationship->relationshipTypeID = '1'; //hardcoded because we're only allowing parent relationships - if (!$resourceRelationship->exists()) { - $resourceRelationship->save(); - } - } - } + private function setResourcesRelationship($resourceID, $parentID) { + if ($parentID != NULL) { + $resourceRelationship = new ResourceRelationship(); + $resourceRelationship->resourceID = $resourceID; + $resourceRelationship->relatedResourceID = $parentID; + $resourceRelationship->relationshipTypeID = '1'; //hardcoded because we're only allowing parent relationships + if (!$resourceRelationship->exists()) { + $resourceRelationship->save(); + } + } + } + +// ------------------------------------------------------------------------- + + private function organizationTreatment($organizations, $resourceID) { + $loginID = $_SESSION['loginID']; + $htmlContent = ''; + + //Organizations module is used + if ($this->config->settings->organizationsModule == 'Y') { //TODO _ hierarchy platform/provider + $dbName = $this->config->settings->organizationsDatabaseName; + foreach ($organizations as $role => $orgName) { + $organization = new Organization(); //TODO _ instanciation dans une boucle j'aime pas trop ça ;) + $organizationRole = new OrganizationRole(); + $organizationID = false; + + // Does the organization already exists? + $query = "SELECT count(*) AS count FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; + $result = $organization->db->processQuery($query, 'assoc'); + + if ($result['count'] == 0) { // If not, we try to create it +// $query = "INSERT INTO $dbName.Organization SET createDate=NOW(), createLoginID='$loginID', name='" . mysql_escape_string($orgName) . "'"; +// try { +// $result = $organization->db->processQuery($query); +// $organizationID = $result; +// self::$organizationsInserted++; +// array_push(self::$arrayOrganizationsCreated, $orgName); +// } catch (Exception $e) { +// $htmlContent .= "

    Organization $orgName could not be added.

    "; +// } + $organizationID = $this->createOrgWithOrganizationModule($orgName); + } + // If yes, we attach it to our resource + elseif ($result['count'] == 1) { + $query = "SELECT name, organizationID FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; + $result = $organization->db->processQuery($query, 'assoc'); + $organizationID = $result['organizationID']; + self::$organizationsAttached++; + } else { + $htmlContent .= "

    Error: more than one organization is called $orgName. Please consider deduping.

    "; + } + + if ($organizationID) { + // Get role + $query = "SELECT organizationRoleID from OrganizationRole WHERE shortName='" . mysql_escape_string($role) . "'"; + $result = $organization->db->processQuery($query); + $roleID = ($result[0]) ? $result[0] : 1; + // Does the organizationRole already exists? + $query = "SELECT count(*) AS count FROM $dbName.OrganizationRoleProfile WHERE organizationID=$organizationID AND organizationRoleID=$roleID"; + $result = $organization->db->processQuery($query, 'assoc'); + // If not, we try to create it + if ($result['count'] == 0) { + $query = "INSERT INTO $dbName.OrganizationRoleProfile SET organizationID=$organizationID, organizationRoleID=$roleID"; + try { + $result = $organization->db->processQuery($query); + if (!in_array($orgName, self::$arrayOrganizationsCreated)) { + self::$organizationsInserted++; + array_push(self::$arrayOrganizationsCreated, $orgName); + } + } catch (Exception $e) { + $htmlContent .= "

    Unable to associate organization $orgName with its role.

    "; + } + } + } + + // Let's link the resource and the organization. + // (this has to be done whether the module Organization is in use or not) + if ($organizationID && $roleID) { + $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); + } + } + + //TODO _ hierarchy platform/provider (packages) + if ($organizations['platform']) { + $platformName = $organizations['platform']; + $providerName = $organizations['provider']; + } + } + // If we do not use the Organizations module + else { + foreach ($organizations as $role => $orgName) { + $organization = new Organization(); //TODO _ instanciation dans une boucle j'aime pas trop ça ;) + $organizationRole = new OrganizationRole(); + $organizationID = false; + // Search if such organization already exists + $organizationExists = $organization->alreadyExists($orgName); + $parentID = null; + + if (!$organizationExists) { // If not, create it + $organization->shortName = $orgName; + $organization->save(); + $organizationID = $organization->organizationID(); + self::$organizationsInserted++; + array_push(self::$arrayOrganizationsCreated, $orgName); + } elseif ($organizationExists == 1) { // Else, + $organizationID = $organization->getOrganizationIDByName($orgName); + self::$organizationsAttached++; + } else { + $htmlContent .= "

    Error: more than one organization is called $orgName. Please consider deduping.

    "; + } + + $organizationRoles = $organizationRole->getArray(); + if (($roleID = array_search($role, $organizationRoles)) == 0) { + // If role is not found, fallback to the first one. + $roleID = '1'; + } + + // Let's link the resource and the organization. + // (this has to be done whether the module Organization is in use or not) + if ($organizationID && $roleID) { + $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); + } + } + } + echo $htmlContent; + } + // ------------------------------------------------------------------------- - - private function organizationTreatment($organizations, $resourceID) { - $loginID = $_SESSION['loginID']; - $htmlContent = ''; - //Organizations module is used - if ($this->config->settings->organizationsModule == 'Y') { //TODO _ hierarchy platform/provider - $dbName = $this->config->settings->organizationsDatabaseName; - foreach ($organizations as $role => $orgName) { - $organization = new Organization(); //TODO _ instanciation dans une boucle j'aime pas trop ça ;) - $organizationRole = new OrganizationRole(); - $organizationID = false; - - // Does the organization already exists? - $query = "SELECT count(*) AS count FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; - $result = $organization->db->processQuery($query, 'assoc'); - - if ($result['count'] == 0) { // If not, we try to create it - $query = "INSERT INTO $dbName.Organization SET createDate=NOW(), createLoginID='$loginID', name='" . mysql_escape_string($orgName) . "'"; - try { - $result = $organization->db->processQuery($query); - $organizationID = $result; - self::$organizationsInserted++; - array_push(self::$arrayOrganizationsCreated, $orgName); - } catch (Exception $e) { - $htmlContent .= "

    Organization $orgName could not be added.

    "; - } - } - // If yes, we attach it to our resource - elseif ($result['count'] == 1) { - $query = "SELECT name, organizationID FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; - $result = $organization->db->processQuery($query, 'assoc'); - $organizationID = $result['organizationID']; - self::$organizationsAttached++; - } else { - $htmlContent .= "

    Error: more than one organization is called $orgName. Please consider deduping.

    "; - } - - if ($organizationID) { - // Get role - $query = "SELECT organizationRoleID from OrganizationRole WHERE shortName='" . mysql_escape_string($role) . "'"; - $result = $organization->db->processQuery($query); - $roleID = ($result[0]) ? $result[0] : 1; - // Does the organizationRole already exists? - $query = "SELECT count(*) AS count FROM $dbName.OrganizationRoleProfile WHERE organizationID=$organizationID AND organizationRoleID=$roleID"; - $result = $organization->db->processQuery($query, 'assoc'); - // If not, we try to create it - if ($result['count'] == 0) { - $query = "INSERT INTO $dbName.OrganizationRoleProfile SET organizationID=$organizationID, organizationRoleID=$roleID"; - try { - $result = $organization->db->processQuery($query); - if (!in_array($orgName, self::$arrayOrganizationsCreated)) { - self::$organizationsInserted++; - array_push(self::$arrayOrganizationsCreated, $orgName); - } - } catch (Exception $e) { - $htmlContent .= "

    Unable to associate organization $orgName with its role.

    "; - } - } - } - - // Let's link the resource and the organization. - // (this has to be done whether the module Organization is in use or not) - if ($organizationID && $roleID) { - $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); - } - } - - //TODO _ hierarchy platform/provider (packages) - if ($organizations['platform']) { - $platformName = $organizations['platform']; - $providerName = $organizations['provider']; - } - } - // If we do not use the Organizations module - else { - foreach ($organizations as $role => $orgName) { - $organization = new Organization(); //TODO _ instanciation dans une boucle j'aime pas trop ça ;) - $organizationRole = new OrganizationRole(); - $organizationID = false; - // Search if such organization already exists - $organizationExists = $organization->alreadyExists($orgName); - $parentID = null; - - if (!$organizationExists) { // If not, create it - $organization->shortName = $orgName; - $organization->save(); - $organizationID = $organization->organizationID(); - self::$organizationsInserted++; - array_push(self::$arrayOrganizationsCreated, $orgName); - } elseif ($organizationExists == 1) { // Else, - $organizationID = $organization->getOrganizationIDByName($orgName); - self::$organizationsAttached++; - } else { - $htmlContent .= "

    Error: more than one organization is called $orgName. Please consider deduping.

    "; - } - - $organizationRoles = $organizationRole->getArray(); - if (($roleID = array_search($role, $organizationRoles)) == 0) { - // If role is not found, fallback to the first one. - $roleID = '1'; - } - - // Let's link the resource and the organization. - // (this has to be done whether the module Organization is in use or not) - if ($organizationID && $roleID) { - $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); - } - } - } - echo $htmlContent; - } + + private function createOrgWithOrganizationModule($orgName) { + $dbName = $this->config->settings->organizationsDatabaseName; + $loginID = $_SESSION['loginID']; + + $organization = new Organization(); + $query = "INSERT INTO $dbName.Organization SET createDate=NOW(), createLoginID='$loginID', name='" . mysql_escape_string($orgName) . "'"; + try { + $result = $organization->db->processQuery($query); + $organizationID = $result; + self::$organizationsInserted++; + array_push(self::$arrayOrganizationsCreated, $orgName); + } catch (Exception $e) { + // $htmlContent .= "

    Organization $orgName could not be added.

    "; + } + return $organizationID; + } // ------------------------------------------------------------------------- - /******************************** - * Accessors * - ********************************/ - public static function getNbRow() { - return self::$row; - } + /******************************** + * Accessors * + * ****************************** */ + public static function getNbRow() { + return self::$row; + } // ------------------------------------------------------------------------- - public static function getNbInserted() { - return self::$inserted; - } + public static function getNbInserted() { + return self::$inserted; + } // ------------------------------------------------------------------------- - public static function getNbParentInserted() { - return self::$parentInserted; - } + public static function getNbParentInserted() { + return self::$parentInserted; + } // ------------------------------------------------------------------------- - public static function incrementNbParentInserted() { - self::$parentInserted++; - } - + public static function incrementNbParentInserted() { + self::$parentInserted++; + } + // ------------------------------------------------------------------------- - public static function getNbParentAttached() { - return self::$parentAttached; - } - + public static function getNbParentAttached() { + return self::$parentAttached; + } + // ------------------------------------------------------------------------- - public static function incrementNbParentAttached() { - self::$parentAttached++; - } - + public static function incrementNbParentAttached() { + self::$parentAttached++; + } + // ------------------------------------------------------------------------- - public static function getNbOrganizationsInserted() { - return self::$organizationsInserted; - } + public static function getNbOrganizationsInserted() { + return self::$organizationsInserted; + } // ------------------------------------------------------------------------- - public static function getNbOrganizationsAttached() { - return self::$organizationsAttached; - } + public static function getNbOrganizationsAttached() { + return self::$organizationsAttached; + } // ------------------------------------------------------------------------- } diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index 664d852..a0f7435 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -1,8 +1,7 @@
    Import from GOKb . php
    getRecord($_POST['type'], $_POST['id']); //echo "DEBUG_ IFG _ request 1 ok\n"; -$nbTipps = $gokbTool->getNbTipps($record); +//$nbTipps = $gokbTool->getNbTipps($record); //echo "DEBUG_ IFG _ all request ok\n"; $datas = array(); $identifiers = array("gokb" => $_POST['id']); -$recordDetails=$record->{'metadata'}->{'gokb'}->{$_POST['type']}; - +$recordDetails = $record->{'metadata'}->{'gokb'}->{$_POST['type']}; +$nbTipps = $gokbTool->getNbTipps($recordDetails); $datas['titleText'] = $gokbTool->getResourceName($recordDetails); $string = ""; -if ($_POST['type'] == 'package'){ - //resource parent (package lui meme) - $acqID = AcquisitionType::getAcquisitionTypeID($recordDetails->{"paymentType"}); - if ($acqID != null){ - $datas["acquisitionTypeID"] = $acqID; - $string .= "insertion de datas[acquisitionTypeID] = ".$acqID."
    "; - } - - //Organization: - $datas['organization'] = array ( - "platform" => $recordDetails->{"nominalPlatform"}, - "provider" => $recordDetails->{"nominalProvider"} - ); - $string .= "insertion organizations "; - - - - //ensemble des tipps (boucle) - //$tippsDatas = array(); - - - -} else{ //import title; - //TODO medium = resourceType (ajout manuel only pour l'instant) - - //Organization - $org = $recordDetails->{"publisher"}->{'name'}; - if ($org != NULL){ - $datas['organization']=array("publisher" => $org); - $string .= "insertion de datas['organization'] = ".$org."
    "; - } - - - //Identifiers _ URL ? - $xml = $recordDetails->{'identifiers'}; - $xmlIDs =$xml->children(); - - foreach ($xmlIDs as $key => $value) { - $tmp = $value->attributes(); - $identifiers["$tmp[0]"] = (string) $tmp[1]; - $string .= "insertion de identifiers[".$tmp[0]."] = ".$tmp[1]."
    "; - } - - - - //History / Aliases - - - - //$datas['parentResource'] ? +if ($_POST['type'] == 'package') { + //resource parent (package lui meme) + $acqID = AcquisitionType::getAcquisitionTypeID($recordDetails->{"paymentType"}); + if ($acqID != null) { + $datas["acquisitionTypeID"] = $acqID; + $string .= "insertion de datas[acquisitionTypeID] = " . $acqID . "
    "; + } + + //Organization: + $datas['organization'] = array( + "platform" => $recordDetails->{"nominalPlatform"}, + "provider" => $recordDetails->{"nominalProvider"} + ); + $string .= "insertion organizations "; + + + + //ensemble des tipps (boucle) + //$tippsDatas = array(); +} else { //import title; + //TODO medium = resourceType (ajout manuel only pour l'instant) + //Organization + $org = $recordDetails->{"publisher"}->{'name'}; + if ($org != NULL) { + $datas['organization'] = array("publisher" => $org); + $string .= "insertion de datas['organization'] = " . $org . "
    "; + } + + + //Identifiers _ URL ? + $xml = $recordDetails->{'identifiers'}; + $xmlIDs = $xml->children(); + + foreach ($xmlIDs as $key => $value) { + $tmp = $value->attributes(); + $identifiers["$tmp[0]"] = (string) $tmp[1]; + $string .= "insertion de identifiers[" . $tmp[0] . "] = " . $tmp[1] . "
    "; + } + + + //parents + if ($nbTipps > 0) { + $tipps = $recordDetails->{'TIPPs'}; + $type = "package"; + $parents = array(); + + foreach ($tipps->children() as $child) { + $resource = $child->{$type}; + $resourceAttr = $resource->attributes(); + + $parentDatas = array(); + $parentDatas['titleText'] = $gokbTool->getResourceName($resource); + $parentDatas['identifiers']['gokb'] = $gokbTool->UriToGokbId("$type" . 's/' . $resourceAttr[0]); + $parentDatas['organization']['platform'] = (string) $child->{'platform'}->{'name'}; + $parentDatas['resourceURL'] = (string) $child->{'url'}; + //TODO _ coverage and others + array_push($parents, $parentDatas); + } + } + + $datas['parentResource'] = $parents; //TODO _ modif struct string -> tab + + //History / Aliases + //$datas['parentResource'] ? //echo "$string"; -$importTool->addResource($datas, $identifiers); - -return $string; + // $importTool->addResource($datas, $identifiers); + return $string; } - - - ?> Import OK !! \ No newline at end of file diff --git a/import.php b/import.php index 990cc24..3f6d1e4 100644 --- a/import.php +++ b/import.php @@ -68,195 +68,48 @@ } // Process } elseif ($_POST['matchsubmit']) { - $delimiter = $_POST['delimiter']; - $deduping_config = explode(',', $config->settings->importISBNDedupingColumns); - $uploadfile = $_POST['uploadfile']; - // Let's analyze this file - if (($handle = fopen($uploadfile, "r")) !== FALSE) { - $row = 0; - $inserted = 0; - $parentInserted = 0; - $parentAttached = 0; - $organizationsInserted = 0; - $organizationsAttached = 0; - $arrayOrganizationsCreated = array(); - while (($data = fgetcsv($handle, 0, $delimiter)) !== FALSE) { - // Getting column names again for deduping - if ($row == 0) { - print "

    Settings

    "; - print "

    Importing and deduping isbnOrISSN on the following columns: " ; - foreach ($data as $key => $value) { - if (in_array($value, $deduping_config)) { - $deduping_columns[] = $key; - print $value . " "; - } - } - print ".

    "; - } else { - // Deduping - unset($deduping_values); - $resource = new Resource(); - $resourceObj = new Resource(); - foreach ($deduping_columns as $value) { - $deduping_values[] = $data[$value]; - } - $deduping_count = count($resourceObj->getResourceByIsbnOrISSN($deduping_values)); - if ($deduping_count == 0) { - // Convert to UTF-8 - $data = array_map(function($row) { return mb_convert_encoding($row, 'UTF-8'); }, $data); - - // Let's insert data - $resource->createLoginID = $loginID; - $resource->createDate = date( 'Y-m-d' ); - $resource->updateLoginID = ''; - $resource->updateDate = ''; - $resource->titleText = $data[$_POST['titleText']]; - $resource->resourceURL = $data[$_POST['resourceURL']]; - $resource->resourceAltURL = $data[$_POST['resourceAltURL']]; - $resource->providerText = $data[$_POST['providerText']]; - $resource->statusID = 1; - $resource->save(); - $resource->setIsbnOrIssn($deduping_values); - $inserted++; - // Do we have to create an organization or attach the resource to an existing one? - if ($data[$_POST['organization']]) { - $organizationName = $data[$_POST['organization']]; - $organization = new Organization(); - $organizationRole = new OrganizationRole(); - $organizationID = false; - // If we use the Organizations module - if ($config->settings->organizationsModule == 'Y'){ - - $dbName = $config->settings->organizationsDatabaseName; - // Does the organization already exists? - $query = "SELECT count(*) AS count FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($organizationName)) . "'"; - $result = $organization->db->processQuery($query, 'assoc'); - // If not, we try to create it - if ($result['count'] == 0) { - $query = "INSERT INTO $dbName.Organization SET createDate=NOW(), createLoginID='$loginID', name='" . mysql_escape_string($organizationName) . "'"; - try { - $result = $organization->db->processQuery($query); - $organizationID = $result; - $organizationsInserted++; - array_push($arrayOrganizationsCreated, $organizationName); - } catch (Exception $e) { - print "

    Organization $organizationName could not be added.

    "; - } - // If yes, we attach it to our resource - } elseif ($result['count'] == 1) { - $query = "SELECT name, organizationID FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($organizationName)) . "'"; - $result = $organization->db->processQuery($query, 'assoc'); - $organizationID = $result['organizationID']; - $organizationsAttached++; - } else { - print "

    Error: more than one organization is called $organizationName. Please consider deduping.

    "; - } - if ($organizationID) { - $dbName = $config->settings->organizationsDatabaseName; - // Get role - $query = "SELECT organizationRoleID from OrganizationRole WHERE shortName='" . mysql_escape_string($data[$_POST['role']]) . "'"; - $result = $organization->db->processQuery($query); - // If role is not found, fallback to the first one. - $roleID = ($result[0]) ? $result[0] : 1; - // Does the organizationRole already exists? - $query = "SELECT count(*) AS count FROM $dbName.OrganizationRoleProfile WHERE organizationID=$organizationID AND organizationRoleID=$roleID"; - $result = $organization->db->processQuery($query, 'assoc'); - // If not, we try to create it - if ($result['count'] == 0) { - $query = "INSERT INTO $dbName.OrganizationRoleProfile SET organizationID=$organizationID, organizationRoleID=$roleID"; - try { - $result = $organization->db->processQuery($query); - if (!in_array($organizationName, $arrayOrganizationsCreated)) { - $organizationsInserted++; - array_push($arrayOrganizationsCreated, $organizationName); - } - } catch (Exception $e) { - print "

    Unable to associate organization $organizationName with its role.

    "; + $tool = new ImportTool(); + + $delimiter = $_POST['delimiter']; + $deduping_config = explode(',', $config->settings->importISBNDedupingColumns); + $uploadfile = $_POST['uploadfile']; + + if (($handle = fopen($uploadfile, "r")) !== FALSE) { + $firstLine = true; + while ($line = fgetcsv($handle, 0, $delimiter)) { + if ($firstLine) { + $firstLine = false; + print "

    Settings

    "; + print "

    Importing and deduping isbnOrISSN on the following columns: "; + foreach ($line as $key => $value) { + if (in_array($value, $deduping_config)) { + $deduping_columns[] = $key; + print $value . " "; + } + } + print ".

    "; + } else { + $datas = array(); + $identifiers = array(); + + $datas['titleText'] = $line[$_POST['titleText']]; + $datas['resourceURL'] = $line[$_POST['resourceURL']]; + $datas['resourceAltURL'] = $line[$_POST['resourceAltURL']]; + $datas['parentResource'] = $line[$_POST['parentResource']]; + $org = $_POST['organization']; + if (($line[$org] != null )&&($line[$org] != '')){ + $datas['organization']=array($line[$_POST['role']] =>$line[$org]); + } + + foreach ($deduping_columns as $column) { + array_push($identifiers, $line[$column]); + } + + $tool->addResource($datas, $identifiers); } - } - } - // If we do not use the Organizations module - } else { - // Search if such organization already exists - $organizationExists = $organization->alreadyExists($organizationName); - $parentID = null; - if (!$organizationExists) { - // If not, create it - $organization->shortName = $organizationName; - $organization->save(); - $organizationID = $organization->organizationID(); - $organizationsInserted++; - array_push($arrayOrganizationsCreated, $organizationName); - } elseif ($organizationExists == 1) { - // Else, - $organizationID = $organization->getOrganizationIDByName($organizationName); - $organizationsAttached++; - } else { - print "

    Error: more than one organization is called $organizationName. Please consider deduping.

    "; - } - // Find role - $organizationRoles = $organizationRole->getArray(); - if (($roleID = array_search($data[$_POST['role']], $organizationRoles)) == 0) { - // If role is not found, fallback to the first one. - $roleID = '1'; - } } - // Let's link the resource and the organization. - // (this has to be done whether the module Organization is in use or not) - if ($organizationID && $roleID) { - $organizationLink = new ResourceOrganizationLink(); - $organizationLink->organizationRoleID = $roleID; - $organizationLink->resourceID = $resource->resourceID; - $organizationLink->organizationID = $organizationID; - $organizationLink->save(); - } - } - } elseif ($deduping_count == 1) { - $resources = $resourceObj->getResourceByIsbnOrISSN($deduping_values); - $resource = $resources[0]; - } - // Do we have a parent resource to create? - if ($data[$_POST['parentResource']] && ($deduping_count == 0 || $deduping_count == 1) ) { - // Search if such parent exists - $numberOfParents = count($resourceObj->getResourceByTitle($data[$_POST['parentResource']])); - $parentID = null; - if ($numberOfParents == 0) { - // If not, create parent - $parentResource = new Resource(); - $parentResource->createLoginID = $loginID; - $parentResource->createDate = date( 'Y-m-d' ); - $parentResource->titleText = $data[$_POST['parentResource']]; - $parentResource->statusID = 1; - $parentResource->save(); - $parentID = $parentResource->resourceID; - $parentInserted++; - } elseif ($numberOfParents == 1) { - // Else, attach the resource to its parent. - $parentResource = $resourceObj->getResourceByTitle($data[$_POST['parentResource']]); - $parentID = $parentResource[0]->resourceID; - - $parentAttached++; - } - if ($numberOfParents == 0 || $numberOfParents == 1) { - $resourceRelationship = new ResourceRelationship(); - $resourceRelationship->resourceID = $resource->resourceID; - $resourceRelationship->relatedResourceID = $parentID; - $resourceRelationship->relationshipTypeID = '1'; //hardcoded because we're only allowing parent relationships - if (!$resourceRelationship->exists()) { - $resourceRelationship->save(); - } - } - } - } - $row++; - } - print "

    Results

    "; - print "

    " . ($row - 1) . " rows have been processed. $inserted rows have been inserted.

    "; - print "

    $parentInserted parents have been created. $parentAttached resources have been attached to an existing parent.

    "; - print "

    $organizationsInserted organizations have been created"; - if (count($arrayOrganizationsCreated) > 0) print " (" . implode(',', $arrayOrganizationsCreated) . ")"; - print ". $organizationsAttached resources have been attached to an existing organization.

    "; - } + } + } else { ?> From bf64271391fba5c5dac10e17e250a399cefbdc5e Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Thu, 30 Jul 2015 16:16:08 +0200 Subject: [PATCH 11/32] DB update file (add tables Identifier and IdentifierType) --- install/protected/update_NEXT.sql | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 install/protected/update_NEXT.sql diff --git a/install/protected/update_NEXT.sql b/install/protected/update_NEXT.sql new file mode 100644 index 0000000..31c70ec --- /dev/null +++ b/install/protected/update_NEXT.sql @@ -0,0 +1,36 @@ +DROP TABLE IF EXISTS `_DATABASE_NAME_`.`Identifier`; /*replace IsbnOrIssn table which can be deleted*/ +CREATE TABLE `_DATABASE_NAME_`.`Identifier` ( + `identifierID` int(11) NOT NULL auto_increment, + `resourceID` int(11) NOT NULL, + `identifierTypeID` int(11) default NULL, + `identifier` varchar(45) NOT NULL, + PRIMARY KEY (`identifierID`), + UNIQUE KEY `identifierID` (`identifierID`), + KEY `resourceID` (`resourceID`), + KEY `identifierTypeID` (`identifierTypeID`) +)ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; + +INSERT INTO `Identifier` (`resourceID`, `identifier`) +SELECT `resourceID`, `isbnOrIssn` FROM `IsbnOrIssn`; +UPDATE `Identifier` SET `identifierTypeID`=1; + + + +DROP TABLE IF EXISTS `_DATABASE_NAME_`.`IdentifierType`; +CREATE TABLE `_DATABASE_NAME_`.`IdentifierType` ( + `identifierTypeID` int(11) NOT NULL auto_increment, + `identifierName` varchar(45) NOT NULL, + PRIMARY KEY (`identifierTypeID`), + UNIQUE KEY `identifierTypeID` (`identifierTypeID`) +) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; + +INSERT INTO `_DATABASE_NAME_`.`IdentifierType` (identifierName) values('Isxn'); +INSERT INTO `_DATABASE_NAME_`.`IdentifierType` (identifierName) values('Issn'); +INSERT INTO `_DATABASE_NAME_`.`IdentifierType` (identifierName) values('Isbn'); +INSERT INTO `_DATABASE_NAME_`.`IdentifierType` (identifierName) values('eIssn'); +INSERT INTO `_DATABASE_NAME_`.`IdentifierType` (identifierName) values('eIsbn'); +INSERT INTO `_DATABASE_NAME_`.`IdentifierType` (identifierName) values('Gokb'); + + + + From b6881e46abc6e88cfdcfbca6dfcd3a2dfd51e54d Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Thu, 30 Jul 2015 17:20:02 +0200 Subject: [PATCH 12/32] display csv import stat + removeResourceIdentifiers --- admin/classes/domain/ImportTool.php | 3 + admin/classes/domain/Resource.php | 3108 +++++++++++++-------------- ajax_forms/getKBSearchResults.php | 91 - ajax_processing/importFromGOKb.php | 11 +- import.php | 13 +- 5 files changed, 1569 insertions(+), 1657 deletions(-) delete mode 100644 ajax_forms/getKBSearchResults.php diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index 4dcdfbd..ff8c251 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -384,6 +384,9 @@ public static function getNbOrganizationsAttached() { } // ------------------------------------------------------------------------- + public static function getArrayOrganizationsCreated(){ + return self::$arrayOrganizationsCreated; + } } ?> \ No newline at end of file diff --git a/admin/classes/domain/Resource.php b/admin/classes/domain/Resource.php index f886196..4702f97 100644 --- a/admin/classes/domain/Resource.php +++ b/admin/classes/domain/Resource.php @@ -21,365 +21,365 @@ class Resource extends DatabaseObject { - protected function defineRelationships() { - - } + protected function defineRelationships() { + + } - protected function defineIsbnOrIssn() { - - } + protected function defineIsbnOrIssn() { + + } - protected function overridePrimaryKeyName() { - - } + protected function overridePrimaryKeyName() { + + } - //returns resource objects by title - public function getResourceByTitle($title) { + //returns resource objects by title + public function getResourceByTitle($title) { - $query = "SELECT * + $query = "SELECT * FROM Resource WHERE UPPER(titleText) = '" . str_replace("'", "''", strtoupper($title)) . "' ORDER BY 1"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceID'])) { - $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceID'])) { + $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns resource objects by title - public function getResourceByIsbnOrISSN($isbnOrISSN) { + //returns resource objects by title + public function getResourceByIsbnOrISSN($isbnOrISSN) { - $query = "SELECT DISTINCT(resourceID) + $query = "SELECT DISTINCT(resourceID) FROM IsbnOrIssn"; - $i = 0; + $i = 0; - if (!is_array($isbnOrISSN)) { - $value = $isbnOrISSN; - $isbnOrISSN = array($value); - } + if (!is_array($isbnOrISSN)) { + $value = $isbnOrISSN; + $isbnOrISSN = array($value); + } - foreach ($isbnOrISSN as $value) { - $query .= ($i == 0) ? " WHERE " : " OR "; - $query .= "isbnOrIssn = '" . $this->db->escapeString($value) . "'"; - $i++; - } + foreach ($isbnOrISSN as $value) { + $query .= ($i == 0) ? " WHERE " : " OR "; + $query .= "isbnOrIssn = '" . $this->db->escapeString($value) . "'"; + $i++; + } - $query .= " ORDER BY 1"; + $query .= " ORDER BY 1"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceID'])) { - $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceID'])) { + $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - public function getIsbnOrIssn() { - $query = "SELECT * + public function getIsbnOrIssn() { + $query = "SELECT * FROM IsbnOrIssn WHERE resourceID = '" . $this->resourceID . "' ORDER BY 1"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['isbnOrIssnID'])) { - $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $result['isbnOrIssnID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $row['isbnOrIssnID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['isbnOrIssnID'])) { + $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $result['isbnOrIssnID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $row['isbnOrIssnID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of parent resource objects - public function getParentResources() { - return $this->getRelatedResources('resourceID'); - } + //returns array of parent resource objects + public function getParentResources() { + return $this->getRelatedResources('resourceID'); + } - //returns array of child resource objects - public function getChildResources() { - return $this->getRelatedResources('relatedResourceID'); - } + //returns array of child resource objects + public function getChildResources() { + return $this->getRelatedResources('relatedResourceID'); + } - // return array of related resource objects - private function getRelatedResources($key) { + // return array of related resource objects + private function getRelatedResources($key) { - $query = "SELECT * + $query = "SELECT * FROM ResourceRelationship WHERE $key = '" . $this->resourceID . "' AND relationshipTypeID = '1' ORDER BY 1"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result[$key])) { - $object = new ResourceRelationship(new NamedArguments(array('primaryKey' => $result['resourceRelationshipID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new ResourceRelationship(new NamedArguments(array('primaryKey' => $row['resourceRelationshipID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result[$key])) { + $object = new ResourceRelationship(new NamedArguments(array('primaryKey' => $result['resourceRelationshipID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new ResourceRelationship(new NamedArguments(array('primaryKey' => $row['resourceRelationshipID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of purchase site objects - public function getResourcePurchaseSites() { + //returns array of purchase site objects + public function getResourcePurchaseSites() { - $query = "SELECT PurchaseSite.* FROM PurchaseSite, ResourcePurchaseSiteLink RPSL where RPSL.purchaseSiteID = PurchaseSite.purchaseSiteID AND resourceID = '" . $this->resourceID . "'"; + $query = "SELECT PurchaseSite.* FROM PurchaseSite, ResourcePurchaseSiteLink RPSL where RPSL.purchaseSiteID = PurchaseSite.purchaseSiteID AND resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['purchaseSiteID'])) { - $object = new PurchaseSite(new NamedArguments(array('primaryKey' => $result['purchaseSiteID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new PurchaseSite(new NamedArguments(array('primaryKey' => $row['purchaseSiteID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['purchaseSiteID'])) { + $object = new PurchaseSite(new NamedArguments(array('primaryKey' => $result['purchaseSiteID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new PurchaseSite(new NamedArguments(array('primaryKey' => $row['purchaseSiteID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of ResourcePayment objects - public function getResourcePayments() { + //returns array of ResourcePayment objects + public function getResourcePayments() { - $query = "SELECT * FROM ResourcePayment WHERE resourceID = '" . $this->resourceID . "' ORDER BY year DESC, fundName, subscriptionStartDate DESC"; + $query = "SELECT * FROM ResourcePayment WHERE resourceID = '" . $this->resourceID . "' ORDER BY year DESC, fundName, subscriptionStartDate DESC"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourcePaymentID'])) { - $object = new ResourcePayment(new NamedArguments(array('primaryKey' => $result['resourcePaymentID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new ResourcePayment(new NamedArguments(array('primaryKey' => $row['resourcePaymentID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourcePaymentID'])) { + $object = new ResourcePayment(new NamedArguments(array('primaryKey' => $result['resourcePaymentID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new ResourcePayment(new NamedArguments(array('primaryKey' => $row['resourcePaymentID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of associated licenses - public function getLicenseArray() { - $config = new Configuration; + //returns array of associated licenses + public function getLicenseArray() { + $config = new Configuration; - //if the lic module is installed get the lic name from lic database - if ($config->settings->licensingModule == 'Y') { - $dbName = $config->settings->licensingDatabaseName; + //if the lic module is installed get the lic name from lic database + if ($config->settings->licensingModule == 'Y') { + $dbName = $config->settings->licensingDatabaseName; - $resourceLicenseArray = array(); + $resourceLicenseArray = array(); - $query = "SELECT * FROM ResourceLicenseLink WHERE resourceID = '" . $this->resourceID . "'"; + $query = "SELECT * FROM ResourceLicenseLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['licenseID'])) { - $licArray = array(); + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['licenseID'])) { + $licArray = array(); - //first, get the license name - $query = "SELECT shortName FROM " . $dbName . ".License WHERE licenseID = " . $result['licenseID']; + //first, get the license name + $query = "SELECT shortName FROM " . $dbName . ".License WHERE licenseID = " . $result['licenseID']; - if ($licResult = mysql_query($query)) { - while ($licRow = mysql_fetch_assoc($licResult)) { - $licArray['license'] = $licRow['shortName']; - $licArray['licenseID'] = $result['licenseID']; - } - } + if ($licResult = mysql_query($query)) { + while ($licRow = mysql_fetch_assoc($licResult)) { + $licArray['license'] = $licRow['shortName']; + $licArray['licenseID'] = $result['licenseID']; + } + } - array_push($resourceLicenseArray, $licArray); - } else { - foreach ($result as $row) { - $licArray = array(); + array_push($resourceLicenseArray, $licArray); + } else { + foreach ($result as $row) { + $licArray = array(); - //first, get the license name - $query = "SELECT shortName FROM " . $dbName . ".License WHERE licenseID = " . $row['licenseID']; + //first, get the license name + $query = "SELECT shortName FROM " . $dbName . ".License WHERE licenseID = " . $row['licenseID']; - if ($licResult = mysql_query($query)) { - while ($licRow = mysql_fetch_assoc($licResult)) { - $licArray['license'] = $licRow['shortName']; - $licArray['licenseID'] = $row['licenseID']; - } - } + if ($licResult = mysql_query($query)) { + while ($licRow = mysql_fetch_assoc($licResult)) { + $licArray['license'] = $licRow['shortName']; + $licArray['licenseID'] = $row['licenseID']; + } + } - array_push($resourceLicenseArray, $licArray); - } - } + array_push($resourceLicenseArray, $licArray); + } + } - return $resourceLicenseArray; - } - } + return $resourceLicenseArray; + } + } - //returns array of resource license status objects - public function getResourceLicenseStatuses() { + //returns array of resource license status objects + public function getResourceLicenseStatuses() { - $query = "SELECT * FROM ResourceLicenseStatus WHERE resourceID = '" . $this->resourceID . "' ORDER BY licenseStatusChangeDate desc;"; + $query = "SELECT * FROM ResourceLicenseStatus WHERE resourceID = '" . $this->resourceID . "' ORDER BY licenseStatusChangeDate desc;"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceLicenseStatusID'])) { - $object = new ResourceLicenseStatus(new NamedArguments(array('primaryKey' => $result['resourceLicenseStatusID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new ResourceLicenseStatus(new NamedArguments(array('primaryKey' => $row['resourceLicenseStatusID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceLicenseStatusID'])) { + $object = new ResourceLicenseStatus(new NamedArguments(array('primaryKey' => $result['resourceLicenseStatusID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new ResourceLicenseStatus(new NamedArguments(array('primaryKey' => $row['resourceLicenseStatusID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns LicenseStatusID of the most recent resource license status - public function getCurrentResourceLicenseStatus() { + //returns LicenseStatusID of the most recent resource license status + public function getCurrentResourceLicenseStatus() { - $query = "SELECT licenseStatusID FROM ResourceLicenseStatus RLS WHERE resourceID = '" . $this->resourceID . "' AND licenseStatusChangeDate = (SELECT MAX(licenseStatusChangeDate) FROM ResourceLicenseStatus WHERE ResourceLicenseStatus.resourceID = '" . $this->resourceID . "') LIMIT 0,1;"; + $query = "SELECT licenseStatusID FROM ResourceLicenseStatus RLS WHERE resourceID = '" . $this->resourceID . "' AND licenseStatusChangeDate = (SELECT MAX(licenseStatusChangeDate) FROM ResourceLicenseStatus WHERE ResourceLicenseStatus.resourceID = '" . $this->resourceID . "') LIMIT 0,1;"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['licenseStatusID'])) { - return $result['licenseStatusID']; - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['licenseStatusID'])) { + return $result['licenseStatusID']; + } + } - //returns array of authorized site objects - public function getResourceAuthorizedSites() { + //returns array of authorized site objects + public function getResourceAuthorizedSites() { - $query = "SELECT AuthorizedSite.* FROM AuthorizedSite, ResourceAuthorizedSiteLink RPSL where RPSL.authorizedSiteID = AuthorizedSite.authorizedSiteID AND resourceID = '" . $this->resourceID . "'"; + $query = "SELECT AuthorizedSite.* FROM AuthorizedSite, ResourceAuthorizedSiteLink RPSL where RPSL.authorizedSiteID = AuthorizedSite.authorizedSiteID AND resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['authorizedSiteID'])) { - $object = new AuthorizedSite(new NamedArguments(array('primaryKey' => $result['authorizedSiteID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new AuthorizedSite(new NamedArguments(array('primaryKey' => $row['authorizedSiteID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['authorizedSiteID'])) { + $object = new AuthorizedSite(new NamedArguments(array('primaryKey' => $result['authorizedSiteID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new AuthorizedSite(new NamedArguments(array('primaryKey' => $row['authorizedSiteID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of administering site objects - public function getResourceAdministeringSites() { + //returns array of administering site objects + public function getResourceAdministeringSites() { - $query = "SELECT AdministeringSite.* FROM AdministeringSite, ResourceAdministeringSiteLink RPSL where RPSL.administeringSiteID = AdministeringSite.administeringSiteID AND resourceID = '" . $this->resourceID . "'"; + $query = "SELECT AdministeringSite.* FROM AdministeringSite, ResourceAdministeringSiteLink RPSL where RPSL.administeringSiteID = AdministeringSite.administeringSiteID AND resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['administeringSiteID'])) { - $object = new AdministeringSite(new NamedArguments(array('primaryKey' => $result['administeringSiteID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new AdministeringSite(new NamedArguments(array('primaryKey' => $row['administeringSiteID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['administeringSiteID'])) { + $object = new AdministeringSite(new NamedArguments(array('primaryKey' => $result['administeringSiteID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new AdministeringSite(new NamedArguments(array('primaryKey' => $row['administeringSiteID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //deletes all parent resources associated with this resource - public function removeParentResources() { + //deletes all parent resources associated with this resource + public function removeParentResources() { - $query = "DELETE FROM ResourceRelationship WHERE resourceID = '" . $this->resourceID . "'"; + $query = "DELETE FROM ResourceRelationship WHERE resourceID = '" . $this->resourceID . "'"; - return $this->db->processQuery($query); - } + return $this->db->processQuery($query); + } - //returns array of alias objects - public function getAliases() { + //returns array of alias objects + public function getAliases() { - $query = "SELECT * FROM Alias WHERE resourceID = '" . $this->resourceID . "' order by shortName"; + $query = "SELECT * FROM Alias WHERE resourceID = '" . $this->resourceID . "' order by shortName"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['aliasID'])) { - $object = new Alias(new NamedArguments(array('primaryKey' => $result['aliasID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new Alias(new NamedArguments(array('primaryKey' => $row['aliasID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['aliasID'])) { + $object = new Alias(new NamedArguments(array('primaryKey' => $result['aliasID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new Alias(new NamedArguments(array('primaryKey' => $row['aliasID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of contact objects - public function getUnarchivedContacts() { + //returns array of contact objects + public function getUnarchivedContacts() { - $config = new Configuration; - $contactsArray = array(); + $config = new Configuration; + $contactsArray = array(); - //get resource specific contacts first - $query = "SELECT C.*, GROUP_CONCAT(CR.shortName SEPARATOR '
    ') contactRoles + //get resource specific contacts first + $query = "SELECT C.*, GROUP_CONCAT(CR.shortName SEPARATOR '
    ') contactRoles FROM Contact C, ContactRole CR, ContactRoleProfile CRP WHERE (archiveDate = '0000-00-00' OR archiveDate is null) AND C.contactID = CRP.contactID @@ -388,35 +388,35 @@ public function getUnarchivedContacts() { GROUP BY C.contactID ORDER BY C.name"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])) { + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['contactID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($contactsArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } + array_push($contactsArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } - array_push($contactsArray, $resultArray); - } - } + array_push($contactsArray, $resultArray); + } + } - //if the org module is installed also get the org contacts from org database - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; + //if the org module is installed also get the org contacts from org database + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; - $query = "SELECT distinct OC.*, O.name organizationName, GROUP_CONCAT(DISTINCT CR.shortName SEPARATOR '
    ') contactRoles + $query = "SELECT distinct OC.*, O.name organizationName, GROUP_CONCAT(DISTINCT CR.shortName SEPARATOR '
    ') contactRoles FROM " . $dbName . ".Contact OC, " . $dbName . ".ContactRole CR, " . $dbName . ".ContactRoleProfile CRP, " . $dbName . ".Organization O, Resource R, ResourceOrganizationLink ROL WHERE (OC.archiveDate = '0000-00-00' OR OC.archiveDate is null) AND R.resourceID = ROL.resourceID @@ -428,41 +428,41 @@ public function getUnarchivedContacts() { GROUP BY OC.contactID, O.name ORDER BY OC.name"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])) { + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['contactID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($contactsArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } + array_push($contactsArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } - array_push($contactsArray, $resultArray); - } - } - } + array_push($contactsArray, $resultArray); + } + } + } - return $contactsArray; - } + return $contactsArray; + } - //returns array of contact objects - public function getArchivedContacts() { + //returns array of contact objects + public function getArchivedContacts() { - $config = new Configuration; - $contactsArray = array(); + $config = new Configuration; + $contactsArray = array(); - //get resource specific contacts - $query = "SELECT C.*, GROUP_CONCAT(CR.shortName SEPARATOR '
    ') contactRoles + //get resource specific contacts + $query = "SELECT C.*, GROUP_CONCAT(CR.shortName SEPARATOR '
    ') contactRoles FROM Contact C, ContactRole CR, ContactRoleProfile CRP WHERE (archiveDate != '0000-00-00' && archiveDate != '') AND C.contactID = CRP.contactID @@ -471,35 +471,35 @@ public function getArchivedContacts() { GROUP BY C.contactID ORDER BY C.name"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])) { + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['contactID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($contactsArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } + array_push($contactsArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } - array_push($contactsArray, $resultArray); - } - } + array_push($contactsArray, $resultArray); + } + } - //if the org module is installed also get the org contacts from org database - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; + //if the org module is installed also get the org contacts from org database + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; - $query = "SELECT DISTINCT OC.*, O.name organizationName, GROUP_CONCAT(DISTINCT CR.shortName SEPARATOR '
    ') contactRoles + $query = "SELECT DISTINCT OC.*, O.name organizationName, GROUP_CONCAT(DISTINCT CR.shortName SEPARATOR '
    ') contactRoles FROM " . $dbName . ".Contact OC, " . $dbName . ".ContactRole CR, " . $dbName . ".ContactRoleProfile CRP, " . $dbName . ".Organization O, Resource R, ResourceOrganizationLink ROL WHERE (OC.archiveDate != '0000-00-00' && OC.archiveDate is not null) AND R.resourceID = ROL.resourceID @@ -512,112 +512,112 @@ public function getArchivedContacts() { ORDER BY OC.name"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])) { + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['contactID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($contactsArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } + array_push($contactsArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } - array_push($contactsArray, $resultArray); - } - } - } + array_push($contactsArray, $resultArray); + } + } + } - return $contactsArray; - } + return $contactsArray; + } - //returns array of contact objects - public function getCreatorsArray() { + //returns array of contact objects + public function getCreatorsArray() { - $creatorsArray = array(); - $resultArray = array(); + $creatorsArray = array(); + $resultArray = array(); - //get resource specific creators - $query = "SELECT distinct loginID, firstName, lastName + //get resource specific creators + $query = "SELECT distinct loginID, firstName, lastName FROM Resource R, User U WHERE U.loginID = R.createLoginID ORDER BY lastName, firstName, loginID"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['loginID'])) { + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['loginID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($creatorsArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } + array_push($creatorsArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } - array_push($creatorsArray, $resultArray); - } - } + array_push($creatorsArray, $resultArray); + } + } - return $creatorsArray; - } + return $creatorsArray; + } - //returns array of external login records - public function getExternalLoginArray() { + //returns array of external login records + public function getExternalLoginArray() { - $config = new Configuration; - $elArray = array(); + $config = new Configuration; + $elArray = array(); - //get resource specific accounts first - $query = "SELECT EL.*, ELT.shortName externalLoginType + //get resource specific accounts first + $query = "SELECT EL.*, ELT.shortName externalLoginType FROM ExternalLogin EL, ExternalLoginType ELT WHERE EL.externalLoginTypeID = ELT.externalLoginTypeID AND resourceID = '" . $this->resourceID . "' ORDER BY ELT.shortName;"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['externalLoginID'])) { + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['externalLoginID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($elArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } + array_push($elArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } - array_push($elArray, $resultArray); - } - } + array_push($elArray, $resultArray); + } + } - //if the org module is installed also get the external logins from org database - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; + //if the org module is installed also get the external logins from org database + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; - $query = "SELECT DISTINCT EL.*, ELT.shortName externalLoginType, O.name organizationName + $query = "SELECT DISTINCT EL.*, ELT.shortName externalLoginType, O.name organizationName FROM " . $dbName . ".ExternalLogin EL, " . $dbName . ".ExternalLoginType ELT, " . $dbName . ".Organization O, Resource R, ResourceOrganizationLink ROL WHERE EL.externalLoginTypeID = ELT.externalLoginTypeID @@ -628,460 +628,460 @@ public function getExternalLoginArray() { ORDER BY ELT.shortName;"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['externalLoginID'])) { + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['externalLoginID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($elArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } + array_push($elArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } - array_push($elArray, $resultArray); - } - } - } + array_push($elArray, $resultArray); + } + } + } - return $elArray; - } + return $elArray; + } - //returns array of notes objects - public function getNotes($tabName = NULL) { + //returns array of notes objects + public function getNotes($tabName = NULL) { - if ($tabName) { - $query = "SELECT * FROM ResourceNote RN + if ($tabName) { + $query = "SELECT * FROM ResourceNote RN WHERE resourceID = '" . $this->resourceID . "' AND UPPER(tabName) = UPPER('" . $tabName . "') ORDER BY updateDate desc"; - } else { - $query = "SELECT RN.* + } else { + $query = "SELECT RN.* FROM ResourceNote RN LEFT JOIN NoteType NT ON NT.noteTypeID = RN.noteTypeID WHERE resourceID = '" . $this->resourceID . "' ORDER BY updateDate desc, NT.shortName"; - } + } - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceNoteID'])) { - $object = new ResourceNote(new NamedArguments(array('primaryKey' => $result['resourceNoteID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new ResourceNote(new NamedArguments(array('primaryKey' => $row['resourceNoteID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceNoteID'])) { + $object = new ResourceNote(new NamedArguments(array('primaryKey' => $result['resourceNoteID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new ResourceNote(new NamedArguments(array('primaryKey' => $row['resourceNoteID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of the initial note object - public function getInitialNote() { - $noteType = new NoteType(); + //returns array of the initial note object + public function getInitialNote() { + $noteType = new NoteType(); - $query = "SELECT * FROM ResourceNote RN + $query = "SELECT * FROM ResourceNote RN WHERE resourceID = '" . $this->resourceID . "' AND noteTypeID = " . $noteType->getInitialNoteTypeID . " ORDER BY noteTypeID desc LIMIT 0,1"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceNoteID'])) { - $resourceNote = new ResourceNote(new NamedArguments(array('primaryKey' => $result['resourceNoteID']))); - return $resourceNote; - } else { - $resourceNote = new ResourceNote(); - return $resourceNote; - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceNoteID'])) { + $resourceNote = new ResourceNote(new NamedArguments(array('primaryKey' => $result['resourceNoteID']))); + return $resourceNote; + } else { + $resourceNote = new ResourceNote(); + return $resourceNote; + } + } - //returns array of attachments objects - public function getAttachments() { + //returns array of attachments objects + public function getAttachments() { - $query = "SELECT * FROM Attachment A, AttachmentType AT + $query = "SELECT * FROM Attachment A, AttachmentType AT WHERE AT.attachmentTypeID = A.attachmentTypeID AND resourceID = '" . $this->resourceID . "' ORDER BY AT.shortName"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['attachmentID'])) { - $object = new Attachment(new NamedArguments(array('primaryKey' => $result['attachmentID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new Attachment(new NamedArguments(array('primaryKey' => $row['attachmentID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['attachmentID'])) { + $object = new Attachment(new NamedArguments(array('primaryKey' => $result['attachmentID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new Attachment(new NamedArguments(array('primaryKey' => $row['attachmentID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of contact objects - public function getContacts() { + //returns array of contact objects + public function getContacts() { - $query = "SELECT * FROM Contact + $query = "SELECT * FROM Contact WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['contactID'])) { - $object = new Contact(new NamedArguments(array('primaryKey' => $result['contactID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new Contact(new NamedArguments(array('primaryKey' => $row['contactID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['contactID'])) { + $object = new Contact(new NamedArguments(array('primaryKey' => $result['contactID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new Contact(new NamedArguments(array('primaryKey' => $row['contactID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of externalLogin objects - public function getExternalLogins() { + //returns array of externalLogin objects + public function getExternalLogins() { - $query = "SELECT * FROM ExternalLogin + $query = "SELECT * FROM ExternalLogin WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); - - $objects = array(); - - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['externalLoginID'])) { - $object = new ExternalLogin(new NamedArguments(array('primaryKey' => $result['externalLoginID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new ExternalLogin(new NamedArguments(array('primaryKey' => $row['externalLoginID']))); - array_push($objects, $object); - } - } - - return $objects; - } - - public static function setSearch($search) { - $config = new Configuration; - - if ($config->settings->defaultsort) { - $orderBy = $config->settings->defaultsort; - } else { - $orderBy = "R.createDate DESC, TRIM(LEADING 'THE ' FROM UPPER(R.titleText)) asc"; - } - - $defaultSearchParameters = array( - "orderBy" => $orderBy, - "page" => 1, - "recordsPerPage" => 25, - ); - foreach ($defaultSearchParameters as $key => $value) { - if (!$search[$key]) { - $search[$key] = $value; - } - } - foreach ($search as $key => $value) { - $search[$key] = trim($value); - } - $_SESSION['resourceSearch'] = $search; - } - - public static function resetSearch() { - Resource::setSearch(array()); - } - - public static function getSearch() { - if (!isset($_SESSION['resourceSearch'])) { - Resource::resetSearch(); - } - return $_SESSION['resourceSearch']; - } - - public static function getSearchDetails() { - // A successful mysql_connect must be run before mysql_real_escape_string will function. Instantiating a resource model will set up the connection - $resource = new Resource(); - - $search = Resource::getSearch(); - - $whereAdd = array(); - $searchDisplay = array(); - $config = new Configuration(); - - - //if name is passed in also search alias, organizations and organization aliases - if ($search['name']) { - $nameQueryString = mysql_real_escape_string(strtoupper($search['name'])); - $nameQueryString = preg_replace("/ +/", "%", $nameQueryString); - $nameQueryString = "'%" . $nameQueryString . "%'"; - - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; - - $whereAdd[] = "((UPPER(R.titleText) LIKE " . $nameQueryString . ") OR (UPPER(A.shortName) LIKE " . $nameQueryString . ") OR (UPPER(O.name) LIKE " . $nameQueryString . ") OR (UPPER(OA.name) LIKE " . $nameQueryString . ") OR (UPPER(RP.titleText) LIKE " . $nameQueryString . ") OR (UPPER(RC.titleText) LIKE " . $nameQueryString . ") OR (UPPER(R.recordSetIdentifier) LIKE " . $nameQueryString . "))"; - } else { - - $whereAdd[] = "((UPPER(R.titleText) LIKE " . $nameQueryString . ") OR (UPPER(A.shortName) LIKE " . $nameQueryString . ") OR (UPPER(O.shortName) LIKE " . $nameQueryString . ") OR (UPPER(RP.titleText) LIKE " . $nameQueryString . ") OR (UPPER(RC.titleText) LIKE " . $nameQueryString . ") OR (UPPER(R.recordSetIdentifier) LIKE " . $nameQueryString . "))"; - } - - $searchDisplay[] = "Name contains: " . $search['name']; - } - - //get where statements together (and escape single quotes) - if ($search['resourceID']) { - $whereAdd[] = "R.resourceID = '" . mysql_real_escape_string($search['resourceID']) . "'"; - $searchDisplay[] = "Resource ID: " . $search['resourceID']; - } - if ($search['resourceISBNOrISSN']) { - $resourceISBNOrISSN = mysql_real_escape_string(str_replace("-", "", $search['resourceISBNOrISSN'])); - $whereAdd[] = "REPLACE(I.isbnOrIssn,'-','') = '" . $resourceISBNOrISSN . "'"; - $searchDisplay[] = "ISSN/ISBN: " . $search['resourceISBNOrISSN']; - } - if ($search['fund']) { - $fund = mysql_real_escape_string(str_replace("-", "", $search['fund'])); - $whereAdd[] = "REPLACE(RPAY.fundName,'-','') = '" . $fund . "'"; - $searchDisplay[] = "Fund: " . $search['fund']; - } - - if ($search['stepName']) { - $status = new Status(); - $completedStatusID = $status->getIDFromName('complete'); - $whereAdd[] = "(R.statusID != $completedStatusID AND RS.stepName = '" . mysql_real_escape_string($search['stepName']) . "' AND RS.stepStartDate IS NOT NULL AND RS.stepEndDate IS NULL)"; - $searchDisplay[] = "Routing Step: " . $search['stepName']; - } - - - if ($search['parent'] != null) { - $parentadd = "(" . $search['parent'] . ".relationshipTypeID = 1"; - $parentadd .= ")"; - $whereAdd[] = $parentadd; - } - - - - if ($search['statusID']) { - $whereAdd[] = "R.statusID = '" . mysql_real_escape_string($search['statusID']) . "'"; - $status = new Status(new NamedArguments(array('primaryKey' => $search['statusID']))); - $searchDisplay[] = "Status: " . $status->shortName; - } - - if ($search['creatorLoginID']) { - $whereAdd[] = "R.createLoginID = '" . mysql_real_escape_string($search['creatorLoginID']) . "'"; - - $createUser = new User(new NamedArguments(array('primaryKey' => $search['creatorLoginID']))); - if ($createUser->firstName) { - $name = $createUser->lastName . ", " . $createUser->firstName; - } else { - $name = $createUser->loginID; - } - $searchDisplay[] = "Creator: " . $name; - } - - if ($search['resourceFormatID']) { - $whereAdd[] = "R.resourceFormatID = '" . mysql_real_escape_string($search['resourceFormatID']) . "'"; - $resourceFormat = new ResourceFormat(new NamedArguments(array('primaryKey' => $search['resourceFormatID']))); - $searchDisplay[] = "Resource Format: " . $resourceFormat->shortName; - } - - if ($search['acquisitionTypeID']) { - $whereAdd[] = "R.acquisitionTypeID = '" . mysql_real_escape_string($search['acquisitionTypeID']) . "'"; - $acquisitionType = new AcquisitionType(new NamedArguments(array('primaryKey' => $search['acquisitionTypeID']))); - $searchDisplay[] = "Acquisition Type: " . $acquisitionType->shortName; - } - - - if ($search['resourceNote']) { - $whereAdd[] = "UPPER(RN.noteText) LIKE UPPER('%" . mysql_real_escape_string($search['resourceNote']) . "%')"; - $searchDisplay[] = "Note contains: " . $search['resourceNote']; - } - - if ($search['createDateStart']) { - $whereAdd[] = "R.createDate >= STR_TO_DATE('" . mysql_real_escape_string($search['createDateStart']) . "','%m/%d/%Y')"; - if (!$search['createDateEnd']) { - $searchDisplay[] = "Created on or after: " . $search['createDateStart']; - } else { - $searchDisplay[] = "Created between: " . $search['createDateStart'] . " and " . $search['createDateEnd']; - } - } - - if ($search['createDateEnd']) { - $whereAdd[] = "R.createDate <= STR_TO_DATE('" . mysql_real_escape_string($search['createDateEnd']) . "','%m/%d/%Y')"; - if (!$search['createDateStart']) { - $searchDisplay[] = "Created on or before: " . $search['createDateEnd']; - } - } - - if ($search['startWith']) { - $whereAdd[] = "TRIM(LEADING 'THE ' FROM UPPER(R.titleText)) LIKE UPPER('" . mysql_real_escape_string($search['startWith']) . "%')"; - $searchDisplay[] = "Starts with: " . $search['startWith']; - } - - //the following are not-required fields with dropdowns and have "none" as an option - if ($search['resourceTypeID'] == 'none') { - $whereAdd[] = "((R.resourceTypeID IS NULL) OR (R.resourceTypeID = '0'))"; - $searchDisplay[] = "Resource Type: none"; - } else if ($search['resourceTypeID']) { - $whereAdd[] = "R.resourceTypeID = '" . mysql_real_escape_string($search['resourceTypeID']) . "'"; - $resourceType = new ResourceType(new NamedArguments(array('primaryKey' => $search['resourceTypeID']))); - $searchDisplay[] = "Resource Type: " . $resourceType->shortName; - } - - - if ($search['generalSubjectID'] == 'none') { - $whereAdd[] = "((GDLINK.generalSubjectID IS NULL) OR (GDLINK.generalSubjectID = '0'))"; - $searchDisplay[] = "Resource Type: none"; - } else if ($search['generalSubjectID']) { - $whereAdd[] = "GDLINK.generalSubjectID = '" . mysql_real_escape_string($search['generalSubjectID']) . "'"; - $generalSubject = new GeneralSubject(new NamedArguments(array('primaryKey' => $search['generalSubjectID']))); - $searchDisplay[] = "General Subject: " . $generalSubject->shortName; - } - - if ($search['detailedSubjectID'] == 'none') { - $whereAdd[] = "((GDLINK.detailedSubjectID IS NULL) OR (GDLINK.detailedSubjectID = '0') OR (GDLINK.detailedSubjectID = '-1'))"; - $searchDisplay[] = "Resource Type: none"; - } else if ($search['detailedSubjectID']) { - $whereAdd[] = "GDLINK.detailedSubjectID = '" . mysql_real_escape_string($search['detailedSubjectID']) . "'"; - $detailedSubject = new DetailedSubject(new NamedArguments(array('primaryKey' => $search['detailedSubjectID']))); - $searchDisplay[] = "Detailed Subject: " . $detailedSubject->shortName; - } - - if ($search['noteTypeID'] == 'none') { - $whereAdd[] = "(RN.noteTypeID IS NULL) AND (RN.noteText IS NOT NULL)"; - $searchDisplay[] = "Note Type: none"; - } else if ($search['noteTypeID']) { - $whereAdd[] = "RN.noteTypeID = '" . mysql_real_escape_string($search['noteTypeID']) . "'"; - $noteType = new NoteType(new NamedArguments(array('primaryKey' => $search['noteTypeID']))); - $searchDisplay[] = "Note Type: " . $noteType->shortName; - } - - - if ($search['purchaseSiteID'] == 'none') { - $whereAdd[] = "RPSL.purchaseSiteID IS NULL"; - $searchDisplay[] = "Purchase Site: none"; - } else if ($search['purchaseSiteID']) { - $whereAdd[] = "RPSL.purchaseSiteID = '" . mysql_real_escape_string($search['purchaseSiteID']) . "'"; - $purchaseSite = new PurchaseSite(new NamedArguments(array('primaryKey' => $search['purchaseSiteID']))); - $searchDisplay[] = "Purchase Site: " . $purchaseSite->shortName; - } - - - if ($search['authorizedSiteID'] == 'none') { - $whereAdd[] = "RAUSL.authorizedSiteID IS NULL"; - $searchDisplay[] = "Authorized Site: none"; - } else if ($search['authorizedSiteID']) { - $whereAdd[] = "RAUSL.authorizedSiteID = '" . mysql_real_escape_string($search['authorizedSiteID']) . "'"; - $authorizedSite = new AuthorizedSite(new NamedArguments(array('primaryKey' => $search['authorizedSiteID']))); - $searchDisplay[] = "Authorized Site: " . $authorizedSite->shortName; - } - - - if ($search['administeringSiteID'] == 'none') { - $whereAdd[] = "RADSL.administeringSiteID IS NULL"; - $searchDisplay[] = "Administering Site: none"; - } else if ($search['administeringSiteID']) { - $whereAdd[] = "RADSL.administeringSiteID = '" . mysql_real_escape_string($search['administeringSiteID']) . "'"; - $administeringSite = new AdministeringSite(new NamedArguments(array('primaryKey' => $search['administeringSiteID']))); - $searchDisplay[] = "Administering Site: " . $administeringSite->shortName; - } - - - if ($search['authenticationTypeID'] == 'none') { - $whereAdd[] = "R.authenticationTypeID IS NULL"; - $searchDisplay[] = "Authentication Type: none"; - } else if ($search['authenticationTypeID']) { - $whereAdd[] = "R.authenticationTypeID = '" . mysql_real_escape_string($search['authenticationTypeID']) . "'"; - $authenticationType = new AuthenticationType(new NamedArguments(array('primaryKey' => $search['authenticationTypeID']))); - $searchDisplay[] = "Authentication Type: " . $authenticationType->shortName; - } - - if ($search['catalogingStatusID'] == 'none') { - $whereAdd[] = "(R.catalogingStatusID IS NULL)"; - $searchDisplay[] = "Cataloging Status: none"; - } else if ($search['catalogingStatusID']) { - $whereAdd[] = "R.catalogingStatusID = '" . mysql_real_escape_string($search['catalogingStatusID']) . "'"; - $catalogingStatus = new CatalogingStatus(new NamedArguments(array('primaryKey' => $search['catalogingStatusID']))); - $searchDisplay[] = "Cataloging Status: " . $catalogingStatus->shortName; - } - - - - $orderBy = $search['orderBy']; - - - $page = $search['page']; - $recordsPerPage = $search['recordsPerPage']; - return array("where" => $whereAdd, "page" => $page, "order" => $orderBy, "perPage" => $recordsPerPage, "display" => $searchDisplay); - } - - public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = false) { - $config = new Configuration(); - $status = new Status(); - - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; - - $orgJoinAdd = "LEFT JOIN " . $dbName . ".Organization O ON O.organizationID = ROL.organizationID + $result = $this->db->processQuery($query, 'assoc'); + + $objects = array(); + + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['externalLoginID'])) { + $object = new ExternalLogin(new NamedArguments(array('primaryKey' => $result['externalLoginID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new ExternalLogin(new NamedArguments(array('primaryKey' => $row['externalLoginID']))); + array_push($objects, $object); + } + } + + return $objects; + } + + public static function setSearch($search) { + $config = new Configuration; + + if ($config->settings->defaultsort) { + $orderBy = $config->settings->defaultsort; + } else { + $orderBy = "R.createDate DESC, TRIM(LEADING 'THE ' FROM UPPER(R.titleText)) asc"; + } + + $defaultSearchParameters = array( + "orderBy" => $orderBy, + "page" => 1, + "recordsPerPage" => 25, + ); + foreach ($defaultSearchParameters as $key => $value) { + if (!$search[$key]) { + $search[$key] = $value; + } + } + foreach ($search as $key => $value) { + $search[$key] = trim($value); + } + $_SESSION['resourceSearch'] = $search; + } + + public static function resetSearch() { + Resource::setSearch(array()); + } + + public static function getSearch() { + if (!isset($_SESSION['resourceSearch'])) { + Resource::resetSearch(); + } + return $_SESSION['resourceSearch']; + } + + public static function getSearchDetails() { + // A successful mysql_connect must be run before mysql_real_escape_string will function. Instantiating a resource model will set up the connection + $resource = new Resource(); + + $search = Resource::getSearch(); + + $whereAdd = array(); + $searchDisplay = array(); + $config = new Configuration(); + + + //if name is passed in also search alias, organizations and organization aliases + if ($search['name']) { + $nameQueryString = mysql_real_escape_string(strtoupper($search['name'])); + $nameQueryString = preg_replace("/ +/", "%", $nameQueryString); + $nameQueryString = "'%" . $nameQueryString . "%'"; + + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; + + $whereAdd[] = "((UPPER(R.titleText) LIKE " . $nameQueryString . ") OR (UPPER(A.shortName) LIKE " . $nameQueryString . ") OR (UPPER(O.name) LIKE " . $nameQueryString . ") OR (UPPER(OA.name) LIKE " . $nameQueryString . ") OR (UPPER(RP.titleText) LIKE " . $nameQueryString . ") OR (UPPER(RC.titleText) LIKE " . $nameQueryString . ") OR (UPPER(R.recordSetIdentifier) LIKE " . $nameQueryString . "))"; + } else { + + $whereAdd[] = "((UPPER(R.titleText) LIKE " . $nameQueryString . ") OR (UPPER(A.shortName) LIKE " . $nameQueryString . ") OR (UPPER(O.shortName) LIKE " . $nameQueryString . ") OR (UPPER(RP.titleText) LIKE " . $nameQueryString . ") OR (UPPER(RC.titleText) LIKE " . $nameQueryString . ") OR (UPPER(R.recordSetIdentifier) LIKE " . $nameQueryString . "))"; + } + + $searchDisplay[] = "Name contains: " . $search['name']; + } + + //get where statements together (and escape single quotes) + if ($search['resourceID']) { + $whereAdd[] = "R.resourceID = '" . mysql_real_escape_string($search['resourceID']) . "'"; + $searchDisplay[] = "Resource ID: " . $search['resourceID']; + } + if ($search['resourceISBNOrISSN']) { + $resourceISBNOrISSN = mysql_real_escape_string(str_replace("-", "", $search['resourceISBNOrISSN'])); + $whereAdd[] = "REPLACE(I.isbnOrIssn,'-','') = '" . $resourceISBNOrISSN . "'"; + $searchDisplay[] = "ISSN/ISBN: " . $search['resourceISBNOrISSN']; + } + if ($search['fund']) { + $fund = mysql_real_escape_string(str_replace("-", "", $search['fund'])); + $whereAdd[] = "REPLACE(RPAY.fundName,'-','') = '" . $fund . "'"; + $searchDisplay[] = "Fund: " . $search['fund']; + } + + if ($search['stepName']) { + $status = new Status(); + $completedStatusID = $status->getIDFromName('complete'); + $whereAdd[] = "(R.statusID != $completedStatusID AND RS.stepName = '" . mysql_real_escape_string($search['stepName']) . "' AND RS.stepStartDate IS NOT NULL AND RS.stepEndDate IS NULL)"; + $searchDisplay[] = "Routing Step: " . $search['stepName']; + } + + + if ($search['parent'] != null) { + $parentadd = "(" . $search['parent'] . ".relationshipTypeID = 1"; + $parentadd .= ")"; + $whereAdd[] = $parentadd; + } + + + + if ($search['statusID']) { + $whereAdd[] = "R.statusID = '" . mysql_real_escape_string($search['statusID']) . "'"; + $status = new Status(new NamedArguments(array('primaryKey' => $search['statusID']))); + $searchDisplay[] = "Status: " . $status->shortName; + } + + if ($search['creatorLoginID']) { + $whereAdd[] = "R.createLoginID = '" . mysql_real_escape_string($search['creatorLoginID']) . "'"; + + $createUser = new User(new NamedArguments(array('primaryKey' => $search['creatorLoginID']))); + if ($createUser->firstName) { + $name = $createUser->lastName . ", " . $createUser->firstName; + } else { + $name = $createUser->loginID; + } + $searchDisplay[] = "Creator: " . $name; + } + + if ($search['resourceFormatID']) { + $whereAdd[] = "R.resourceFormatID = '" . mysql_real_escape_string($search['resourceFormatID']) . "'"; + $resourceFormat = new ResourceFormat(new NamedArguments(array('primaryKey' => $search['resourceFormatID']))); + $searchDisplay[] = "Resource Format: " . $resourceFormat->shortName; + } + + if ($search['acquisitionTypeID']) { + $whereAdd[] = "R.acquisitionTypeID = '" . mysql_real_escape_string($search['acquisitionTypeID']) . "'"; + $acquisitionType = new AcquisitionType(new NamedArguments(array('primaryKey' => $search['acquisitionTypeID']))); + $searchDisplay[] = "Acquisition Type: " . $acquisitionType->shortName; + } + + + if ($search['resourceNote']) { + $whereAdd[] = "UPPER(RN.noteText) LIKE UPPER('%" . mysql_real_escape_string($search['resourceNote']) . "%')"; + $searchDisplay[] = "Note contains: " . $search['resourceNote']; + } + + if ($search['createDateStart']) { + $whereAdd[] = "R.createDate >= STR_TO_DATE('" . mysql_real_escape_string($search['createDateStart']) . "','%m/%d/%Y')"; + if (!$search['createDateEnd']) { + $searchDisplay[] = "Created on or after: " . $search['createDateStart']; + } else { + $searchDisplay[] = "Created between: " . $search['createDateStart'] . " and " . $search['createDateEnd']; + } + } + + if ($search['createDateEnd']) { + $whereAdd[] = "R.createDate <= STR_TO_DATE('" . mysql_real_escape_string($search['createDateEnd']) . "','%m/%d/%Y')"; + if (!$search['createDateStart']) { + $searchDisplay[] = "Created on or before: " . $search['createDateEnd']; + } + } + + if ($search['startWith']) { + $whereAdd[] = "TRIM(LEADING 'THE ' FROM UPPER(R.titleText)) LIKE UPPER('" . mysql_real_escape_string($search['startWith']) . "%')"; + $searchDisplay[] = "Starts with: " . $search['startWith']; + } + + //the following are not-required fields with dropdowns and have "none" as an option + if ($search['resourceTypeID'] == 'none') { + $whereAdd[] = "((R.resourceTypeID IS NULL) OR (R.resourceTypeID = '0'))"; + $searchDisplay[] = "Resource Type: none"; + } else if ($search['resourceTypeID']) { + $whereAdd[] = "R.resourceTypeID = '" . mysql_real_escape_string($search['resourceTypeID']) . "'"; + $resourceType = new ResourceType(new NamedArguments(array('primaryKey' => $search['resourceTypeID']))); + $searchDisplay[] = "Resource Type: " . $resourceType->shortName; + } + + + if ($search['generalSubjectID'] == 'none') { + $whereAdd[] = "((GDLINK.generalSubjectID IS NULL) OR (GDLINK.generalSubjectID = '0'))"; + $searchDisplay[] = "Resource Type: none"; + } else if ($search['generalSubjectID']) { + $whereAdd[] = "GDLINK.generalSubjectID = '" . mysql_real_escape_string($search['generalSubjectID']) . "'"; + $generalSubject = new GeneralSubject(new NamedArguments(array('primaryKey' => $search['generalSubjectID']))); + $searchDisplay[] = "General Subject: " . $generalSubject->shortName; + } + + if ($search['detailedSubjectID'] == 'none') { + $whereAdd[] = "((GDLINK.detailedSubjectID IS NULL) OR (GDLINK.detailedSubjectID = '0') OR (GDLINK.detailedSubjectID = '-1'))"; + $searchDisplay[] = "Resource Type: none"; + } else if ($search['detailedSubjectID']) { + $whereAdd[] = "GDLINK.detailedSubjectID = '" . mysql_real_escape_string($search['detailedSubjectID']) . "'"; + $detailedSubject = new DetailedSubject(new NamedArguments(array('primaryKey' => $search['detailedSubjectID']))); + $searchDisplay[] = "Detailed Subject: " . $detailedSubject->shortName; + } + + if ($search['noteTypeID'] == 'none') { + $whereAdd[] = "(RN.noteTypeID IS NULL) AND (RN.noteText IS NOT NULL)"; + $searchDisplay[] = "Note Type: none"; + } else if ($search['noteTypeID']) { + $whereAdd[] = "RN.noteTypeID = '" . mysql_real_escape_string($search['noteTypeID']) . "'"; + $noteType = new NoteType(new NamedArguments(array('primaryKey' => $search['noteTypeID']))); + $searchDisplay[] = "Note Type: " . $noteType->shortName; + } + + + if ($search['purchaseSiteID'] == 'none') { + $whereAdd[] = "RPSL.purchaseSiteID IS NULL"; + $searchDisplay[] = "Purchase Site: none"; + } else if ($search['purchaseSiteID']) { + $whereAdd[] = "RPSL.purchaseSiteID = '" . mysql_real_escape_string($search['purchaseSiteID']) . "'"; + $purchaseSite = new PurchaseSite(new NamedArguments(array('primaryKey' => $search['purchaseSiteID']))); + $searchDisplay[] = "Purchase Site: " . $purchaseSite->shortName; + } + + + if ($search['authorizedSiteID'] == 'none') { + $whereAdd[] = "RAUSL.authorizedSiteID IS NULL"; + $searchDisplay[] = "Authorized Site: none"; + } else if ($search['authorizedSiteID']) { + $whereAdd[] = "RAUSL.authorizedSiteID = '" . mysql_real_escape_string($search['authorizedSiteID']) . "'"; + $authorizedSite = new AuthorizedSite(new NamedArguments(array('primaryKey' => $search['authorizedSiteID']))); + $searchDisplay[] = "Authorized Site: " . $authorizedSite->shortName; + } + + + if ($search['administeringSiteID'] == 'none') { + $whereAdd[] = "RADSL.administeringSiteID IS NULL"; + $searchDisplay[] = "Administering Site: none"; + } else if ($search['administeringSiteID']) { + $whereAdd[] = "RADSL.administeringSiteID = '" . mysql_real_escape_string($search['administeringSiteID']) . "'"; + $administeringSite = new AdministeringSite(new NamedArguments(array('primaryKey' => $search['administeringSiteID']))); + $searchDisplay[] = "Administering Site: " . $administeringSite->shortName; + } + + + if ($search['authenticationTypeID'] == 'none') { + $whereAdd[] = "R.authenticationTypeID IS NULL"; + $searchDisplay[] = "Authentication Type: none"; + } else if ($search['authenticationTypeID']) { + $whereAdd[] = "R.authenticationTypeID = '" . mysql_real_escape_string($search['authenticationTypeID']) . "'"; + $authenticationType = new AuthenticationType(new NamedArguments(array('primaryKey' => $search['authenticationTypeID']))); + $searchDisplay[] = "Authentication Type: " . $authenticationType->shortName; + } + + if ($search['catalogingStatusID'] == 'none') { + $whereAdd[] = "(R.catalogingStatusID IS NULL)"; + $searchDisplay[] = "Cataloging Status: none"; + } else if ($search['catalogingStatusID']) { + $whereAdd[] = "R.catalogingStatusID = '" . mysql_real_escape_string($search['catalogingStatusID']) . "'"; + $catalogingStatus = new CatalogingStatus(new NamedArguments(array('primaryKey' => $search['catalogingStatusID']))); + $searchDisplay[] = "Cataloging Status: " . $catalogingStatus->shortName; + } + + + + $orderBy = $search['orderBy']; + + + $page = $search['page']; + $recordsPerPage = $search['recordsPerPage']; + return array("where" => $whereAdd, "page" => $page, "order" => $orderBy, "perPage" => $recordsPerPage, "display" => $searchDisplay); + } + + public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = false) { + $config = new Configuration(); + $status = new Status(); + + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; + + $orgJoinAdd = "LEFT JOIN " . $dbName . ".Organization O ON O.organizationID = ROL.organizationID LEFT JOIN " . $dbName . ".Alias OA ON OA.organizationID = ROL.organizationID"; - } else { - $orgJoinAdd = "LEFT JOIN Organization O ON O.organizationID = ROL.organizationID"; - } - - $savedStatusID = intval($status->getIDFromName('saved')); - //also add to not retrieve saved records - $whereAdd[] = "R.statusID != " . $savedStatusID; - - if (count($whereAdd) > 0) { - $whereStatement = " WHERE " . implode(" AND ", $whereAdd); - } else { - $whereStatement = ""; - } - - if ($count) { - $select = "SELECT COUNT(DISTINCT R.resourceID) count"; - $groupBy = ""; - } else { - $select = "SELECT R.resourceID, R.titleText, AT.shortName acquisitionType, R.createLoginID, CU.firstName, CU.lastName, R.createDate, S.shortName status, + } else { + $orgJoinAdd = "LEFT JOIN Organization O ON O.organizationID = ROL.organizationID"; + } + + $savedStatusID = intval($status->getIDFromName('saved')); + //also add to not retrieve saved records + $whereAdd[] = "R.statusID != " . $savedStatusID; + + if (count($whereAdd) > 0) { + $whereStatement = " WHERE " . implode(" AND ", $whereAdd); + } else { + $whereStatement = ""; + } + + if ($count) { + $select = "SELECT COUNT(DISTINCT R.resourceID) count"; + $groupBy = ""; + } else { + $select = "SELECT R.resourceID, R.titleText, AT.shortName acquisitionType, R.createLoginID, CU.firstName, CU.lastName, R.createDate, S.shortName status, GROUP_CONCAT(DISTINCT A.shortName, I.isbnOrIssn ORDER BY A.shortName DESC SEPARATOR '
    ') aliases"; - $groupBy = "GROUP BY R.resourceID"; - } + $groupBy = "GROUP BY R.resourceID"; + } - $referenced_tables = array(); + $referenced_tables = array(); - $table_matches = array(); + $table_matches = array(); - // Build a list of tables that are referenced by the select and where statements in order to limit the number of joins performed in the search. - preg_match_all("/[A-Z]+(?=[.][A-Z]+)/i", $select, $table_matches); - $referenced_tables = array_unique($table_matches[0]); + // Build a list of tables that are referenced by the select and where statements in order to limit the number of joins performed in the search. + preg_match_all("/[A-Z]+(?=[.][A-Z]+)/i", $select, $table_matches); + $referenced_tables = array_unique($table_matches[0]); - preg_match_all("/[A-Z]+(?=[.][A-Z]+)/i", $whereStatement, $table_matches); - $referenced_tables = array_unique(array_merge($referenced_tables, $table_matches[0])); + preg_match_all("/[A-Z]+(?=[.][A-Z]+)/i", $whereStatement, $table_matches); + $referenced_tables = array_unique(array_merge($referenced_tables, $table_matches[0])); - // These join statements will only be included in the query if the alias is referenced by the select and/or where. - $conditional_joins = explode("\n", "LEFT JOIN ResourceFormat RF ON R.resourceFormatID = RF.resourceFormatID + // These join statements will only be included in the query if the alias is referenced by the select and/or where. + $conditional_joins = explode("\n", "LEFT JOIN ResourceFormat RF ON R.resourceFormatID = RF.resourceFormatID LEFT JOIN ResourceType RT ON R.resourceTypeID = RT.resourceTypeID LEFT JOIN AcquisitionType AT ON R.acquisitionTypeID = AT.acquisitionTypeID LEFT JOIN Status S ON R.statusID = S.statusID @@ -1095,18 +1095,18 @@ public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = fals LEFT JOIN IsbnOrIssn I ON R.resourceID = I.resourceID "); - $additional_joins = array(); + $additional_joins = array(); - foreach ($conditional_joins as $join) { - $match = array(); - preg_match("/[A-Z]+(?= ON )/i", $join, $match); - $table_name = $match[0]; - if (in_array($table_name, $referenced_tables)) { - $additional_joins[] = $join; - } - } + foreach ($conditional_joins as $join) { + $match = array(); + preg_match("/[A-Z]+(?= ON )/i", $join, $match); + $table_name = $match[0]; + if (in_array($table_name, $referenced_tables)) { + $additional_joins[] = $join; + } + } - $query = $select . " + $query = $select . " FROM Resource R LEFT JOIN Alias A ON R.resourceID = A.resourceID LEFT JOIN ResourceOrganizationLink ROL ON R.resourceID = ROL.resourceID @@ -1121,118 +1121,118 @@ public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = fals " . $whereStatement . " " . $groupBy; - if ($orderBy) { - $query .= "\nORDER BY " . $orderBy; - } + if ($orderBy) { + $query .= "\nORDER BY " . $orderBy; + } - if ($limit) { - $query .= "\nLIMIT " . $limit; - } - return $query; - } + if ($limit) { + $query .= "\nLIMIT " . $limit; + } + return $query; + } - //returns array based on search - public function search($whereAdd, $orderBy, $limit) { - $query = $this->searchQuery($whereAdd, $orderBy, $limit, false); + //returns array based on search + public function search($whereAdd, $orderBy, $limit) { + $query = $this->searchQuery($whereAdd, $orderBy, $limit, false); - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $searchArray = array(); - $resultArray = array(); + $searchArray = array(); + $resultArray = array(); - //need to do this since it could be that there's only one result and this is how the dbservice returns result - if (isset($result['resourceID'])) { + //need to do this since it could be that there's only one result and this is how the dbservice returns result + if (isset($result['resourceID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($searchArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } - array_push($searchArray, $resultArray); - } - } + array_push($searchArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } + array_push($searchArray, $resultArray); + } + } - return $searchArray; - } + return $searchArray; + } - public function searchCount($whereAdd) { - $query = $this->searchQuery($whereAdd, '', '', true); - $result = $this->db->processQuery($query, 'assoc'); + public function searchCount($whereAdd) { + $query = $this->searchQuery($whereAdd, '', '', true); + $result = $this->db->processQuery($query, 'assoc'); - //echo $query; + //echo $query; - return $result['count']; - } + return $result['count']; + } - //used for A-Z on search (index) - public function getAlphabeticalList() { - $alphArray = array(); - $result = mysql_query("SELECT DISTINCT UPPER(SUBSTR(TRIM(LEADING 'The ' FROM titleText),1,1)) letter, COUNT(SUBSTR(TRIM(LEADING 'The ' FROM titleText),1,1)) letter_count + //used for A-Z on search (index) + public function getAlphabeticalList() { + $alphArray = array(); + $result = mysql_query("SELECT DISTINCT UPPER(SUBSTR(TRIM(LEADING 'The ' FROM titleText),1,1)) letter, COUNT(SUBSTR(TRIM(LEADING 'The ' FROM titleText),1,1)) letter_count FROM Resource R GROUP BY SUBSTR(TRIM(LEADING 'The ' FROM titleText),1,1) ORDER BY 1;"); - while ($row = mysql_fetch_assoc($result)) { - $alphArray[$row['letter']] = $row['letter_count']; - } + while ($row = mysql_fetch_assoc($result)) { + $alphArray[$row['letter']] = $row['letter_count']; + } - return $alphArray; - } + return $alphArray; + } - //returns array based on search for excel output (export.php) - public function export($whereAdd, $orderBy) { + //returns array based on search for excel output (export.php) + public function export($whereAdd, $orderBy) { - $config = new Configuration(); + $config = new Configuration(); - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; - $orgJoinAdd = "LEFT JOIN " . $dbName . ".Organization O ON O.organizationID = ROL.organizationID + $orgJoinAdd = "LEFT JOIN " . $dbName . ".Organization O ON O.organizationID = ROL.organizationID LEFT JOIN " . $dbName . ".Alias OA ON OA.organizationID = ROL.organizationID"; - $orgSelectAdd = "GROUP_CONCAT(DISTINCT O.name ORDER BY O.name DESC SEPARATOR '; ') organizationNames"; - } else { - $orgJoinAdd = "LEFT JOIN Organization O ON O.organizationID = ROL.organizationID"; + $orgSelectAdd = "GROUP_CONCAT(DISTINCT O.name ORDER BY O.name DESC SEPARATOR '; ') organizationNames"; + } else { + $orgJoinAdd = "LEFT JOIN Organization O ON O.organizationID = ROL.organizationID"; - $orgSelectAdd = "GROUP_CONCAT(DISTINCT O.shortName ORDER BY O.shortName DESC SEPARATOR '; ') organizationNames"; - } + $orgSelectAdd = "GROUP_CONCAT(DISTINCT O.shortName ORDER BY O.shortName DESC SEPARATOR '; ') organizationNames"; + } - $licSelectAdd = ''; - $licJoinAdd = ''; - if ($config->settings->licensingModule == 'Y') { - $dbName = $config->settings->licensingDatabaseName; + $licSelectAdd = ''; + $licJoinAdd = ''; + if ($config->settings->licensingModule == 'Y') { + $dbName = $config->settings->licensingDatabaseName; - $licJoinAdd = " LEFT JOIN ResourceLicenseLink RLL ON RLL.resourceID = R.resourceID + $licJoinAdd = " LEFT JOIN ResourceLicenseLink RLL ON RLL.resourceID = R.resourceID LEFT JOIN " . $dbName . ".License L ON RLL.licenseID = L.licenseID LEFT JOIN ResourceLicenseStatus RLS ON RLS.resourceID = R.resourceID LEFT JOIN LicenseStatus LS ON LS.licenseStatusID = RLS.licenseStatusID"; - $licSelectAdd = "GROUP_CONCAT(DISTINCT L.shortName ORDER BY L.shortName DESC SEPARATOR '; ') licenseNames, + $licSelectAdd = "GROUP_CONCAT(DISTINCT L.shortName ORDER BY L.shortName DESC SEPARATOR '; ') licenseNames, GROUP_CONCAT(DISTINCT LS.shortName, ': ', DATE_FORMAT(RLS.licenseStatusChangeDate, '%m/%d/%Y') ORDER BY RLS.licenseStatusChangeDate DESC SEPARATOR '; ') licenseStatuses, "; - } + } - $status = new Status(); - //also add to not retrieve saved records - $savedStatusID = intval($status->getIDFromName('saved')); - $whereAdd[] = "R.statusID != " . $savedStatusID; + $status = new Status(); + //also add to not retrieve saved records + $savedStatusID = intval($status->getIDFromName('saved')); + $whereAdd[] = "R.statusID != " . $savedStatusID; - if (count($whereAdd) > 0) { - $whereStatement = " WHERE " . implode(" AND ", $whereAdd); - } else { - $whereStatement = ""; - } + if (count($whereAdd) > 0) { + $whereStatement = " WHERE " . implode(" AND ", $whereAdd); + } else { + $whereStatement = ""; + } - //now actually execute query - $query = "SELECT R.resourceID, R.titleText, AT.shortName acquisitionType, CONCAT_WS(' ', CU.firstName, CU.lastName) createName, + //now actually execute query + $query = "SELECT R.resourceID, R.titleText, AT.shortName acquisitionType, CONCAT_WS(' ', CU.firstName, CU.lastName) createName, R.createDate createDate, CONCAT_WS(' ', UU.firstName, UU.lastName) updateName, R.updateDate updateDate, S.shortName status, RT.shortName resourceType, RF.shortName resourceFormat, R.orderNumber, R.systemNumber, R.resourceURL, R.resourceAltURL, @@ -1288,531 +1288,540 @@ public function export($whereAdd, $orderBy) { GROUP BY R.resourceID ORDER BY " . $orderBy; - $result = $this->db->processQuery(stripslashes($query), 'assoc'); + $result = $this->db->processQuery(stripslashes($query), 'assoc'); - $searchArray = array(); - $resultArray = array(); + $searchArray = array(); + $resultArray = array(); - //need to do this since it could be that there's only one result and this is how the dbservice returns result - if (isset($result['resourceID'])) { + //need to do this since it could be that there's only one result and this is how the dbservice returns result + if (isset($result['resourceID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($searchArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } - array_push($searchArray, $resultArray); - } - } + array_push($searchArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } + array_push($searchArray, $resultArray); + } + } - return $searchArray; - } + return $searchArray; + } - //search used index page drop down - public function getOrganizationList() { - $config = new Configuration; + //search used index page drop down + public function getOrganizationList() { + $config = new Configuration; - $orgArray = array(); + $orgArray = array(); - //if the org module is installed get the org names from org database - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; - $query = "SELECT name, organizationID FROM " . $dbName . ".Organization ORDER BY 1;"; + //if the org module is installed get the org names from org database + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; + $query = "SELECT name, organizationID FROM " . $dbName . ".Organization ORDER BY 1;"; - //otherwise get the orgs from this database - } else { - $query = "SELECT shortName name, organizationID FROM Organization ORDER BY 1;"; - } + //otherwise get the orgs from this database + } else { + $query = "SELECT shortName name, organizationID FROM Organization ORDER BY 1;"; + } - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $resultArray = array(); + $resultArray = array(); - //need to do this since it could be that there's only one result and this is how the dbservice returns result - if (isset($result['organizationID'])) { + //need to do this since it could be that there's only one result and this is how the dbservice returns result + if (isset($result['organizationID'])) { - foreach (array_keys($result) as $attributeName) { - $resultArray[$attributeName] = $result[$attributeName]; - } + foreach (array_keys($result) as $attributeName) { + $resultArray[$attributeName] = $result[$attributeName]; + } - array_push($orgArray, $resultArray); - } else { - foreach ($result as $row) { - $resultArray = array(); - foreach (array_keys($row) as $attributeName) { - $resultArray[$attributeName] = $row[$attributeName]; - } - array_push($orgArray, $resultArray); - } - } + array_push($orgArray, $resultArray); + } else { + foreach ($result as $row) { + $resultArray = array(); + foreach (array_keys($row) as $attributeName) { + $resultArray[$attributeName] = $row[$attributeName]; + } + array_push($orgArray, $resultArray); + } + } - return $orgArray; - } + return $orgArray; + } - //gets an array of organizations set up for this resource (organizationID, organization, organizationRole) - public function getOrganizationArray() { - $config = new Configuration; + //gets an array of organizations set up for this resource (organizationID, organization, organizationRole) + public function getOrganizationArray() { + $config = new Configuration; - //if the org module is installed get the org name from org database - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; + //if the org module is installed get the org name from org database + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; - $resourceOrgArray = array(); + $resourceOrgArray = array(); - $query = "SELECT * FROM ResourceOrganizationLink WHERE resourceID = '" . $this->resourceID . "'"; + $query = "SELECT * FROM ResourceOrganizationLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['organizationID'])) { - $orgArray = array(); + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['organizationID'])) { + $orgArray = array(); - //first, get the organization name - $query = "SELECT name FROM " . $dbName . ".Organization WHERE organizationID = " . $result['organizationID']; + //first, get the organization name + $query = "SELECT name FROM " . $dbName . ".Organization WHERE organizationID = " . $result['organizationID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organization'] = $orgRow['name']; - $orgArray['organizationID'] = $result['organizationID']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organization'] = $orgRow['name']; + $orgArray['organizationID'] = $result['organizationID']; + } + } - //then, get the role name - $query = "SELECT * FROM " . $dbName . ".OrganizationRole WHERE organizationRoleID = " . $result['organizationRoleID']; + //then, get the role name + $query = "SELECT * FROM " . $dbName . ".OrganizationRole WHERE organizationRoleID = " . $result['organizationRoleID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; - $orgArray['organizationRole'] = $orgRow['shortName']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; + $orgArray['organizationRole'] = $orgRow['shortName']; + } + } - array_push($resourceOrgArray, $orgArray); - } else { - foreach ($result as $row) { + array_push($resourceOrgArray, $orgArray); + } else { + foreach ($result as $row) { - $orgArray = array(); + $orgArray = array(); - //first, get the organization name - $query = "SELECT name FROM " . $dbName . ".Organization WHERE organizationID = " . $row['organizationID']; + //first, get the organization name + $query = "SELECT name FROM " . $dbName . ".Organization WHERE organizationID = " . $row['organizationID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organization'] = $orgRow['name']; - $orgArray['organizationID'] = $row['organizationID']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organization'] = $orgRow['name']; + $orgArray['organizationID'] = $row['organizationID']; + } + } - //then, get the role name - $query = "SELECT * FROM " . $dbName . ".OrganizationRole WHERE organizationRoleID = " . $row['organizationRoleID']; + //then, get the role name + $query = "SELECT * FROM " . $dbName . ".OrganizationRole WHERE organizationRoleID = " . $row['organizationRoleID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; - $orgArray['organizationRole'] = $orgRow['shortName']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; + $orgArray['organizationRole'] = $orgRow['shortName']; + } + } - array_push($resourceOrgArray, $orgArray); - } - } + array_push($resourceOrgArray, $orgArray); + } + } - //otherwise if the org module is not installed get the org name from this database - } else { + //otherwise if the org module is not installed get the org name from this database + } else { - $resourceOrgArray = array(); + $resourceOrgArray = array(); - $query = "SELECT * FROM ResourceOrganizationLink WHERE resourceID = '" . $this->resourceID . "'"; + $query = "SELECT * FROM ResourceOrganizationLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['organizationID'])) { - $orgArray = array(); + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['organizationID'])) { + $orgArray = array(); - //first, get the organization name - $query = "SELECT shortName FROM Organization WHERE organizationID = " . $result['organizationID']; + //first, get the organization name + $query = "SELECT shortName FROM Organization WHERE organizationID = " . $result['organizationID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organization'] = $orgRow['shortName']; - $orgArray['organizationID'] = $result['organizationID']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organization'] = $orgRow['shortName']; + $orgArray['organizationID'] = $result['organizationID']; + } + } - //then, get the role name - $query = "SELECT * FROM OrganizationRole WHERE organizationRoleID = " . $result['organizationRoleID']; + //then, get the role name + $query = "SELECT * FROM OrganizationRole WHERE organizationRoleID = " . $result['organizationRoleID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; - $orgArray['organizationRole'] = $orgRow['shortName']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; + $orgArray['organizationRole'] = $orgRow['shortName']; + } + } - array_push($resourceOrgArray, $orgArray); - } else { - foreach ($result as $row) { + array_push($resourceOrgArray, $orgArray); + } else { + foreach ($result as $row) { - $orgArray = array(); + $orgArray = array(); - //first, get the organization name - $query = "SELECT shortName FROM Organization WHERE organizationID = " . $row['organizationID']; + //first, get the organization name + $query = "SELECT shortName FROM Organization WHERE organizationID = " . $row['organizationID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organization'] = $orgRow['shortName']; - $orgArray['organizationID'] = $row['organizationID']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organization'] = $orgRow['shortName']; + $orgArray['organizationID'] = $row['organizationID']; + } + } - //then, get the role name - $query = "SELECT * FROM OrganizationRole WHERE organizationRoleID = " . $row['organizationRoleID']; + //then, get the role name + $query = "SELECT * FROM OrganizationRole WHERE organizationRoleID = " . $row['organizationRoleID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; - $orgArray['organizationRole'] = $orgRow['shortName']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organizationRoleID'] = $orgRow['organizationRoleID']; + $orgArray['organizationRole'] = $orgRow['shortName']; + } + } - array_push($resourceOrgArray, $orgArray); - } - } - } + array_push($resourceOrgArray, $orgArray); + } + } + } - return $resourceOrgArray; - } + return $resourceOrgArray; + } - //gets an array of distinct organizations set up for this resource (organizationID, organization) - public function getDistinctOrganizationArray() { - $config = new Configuration; + //gets an array of distinct organizations set up for this resource (organizationID, organization) + public function getDistinctOrganizationArray() { + $config = new Configuration; - //if the org module is installed get the org name from org database - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; + //if the org module is installed get the org name from org database + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; - $resourceOrgArray = array(); + $resourceOrgArray = array(); - $query = "SELECT DISTINCT organizationID FROM ResourceOrganizationLink WHERE resourceID = '" . $this->resourceID . "'"; + $query = "SELECT DISTINCT organizationID FROM ResourceOrganizationLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['organizationID'])) { - $orgArray = array(); + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['organizationID'])) { + $orgArray = array(); - //first, get the organization name - $query = "SELECT name FROM " . $dbName . ".Organization WHERE organizationID = " . $result['organizationID']; + //first, get the organization name + $query = "SELECT name FROM " . $dbName . ".Organization WHERE organizationID = " . $result['organizationID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organization'] = $orgRow['name']; - $orgArray['organizationID'] = $result['organizationID']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organization'] = $orgRow['name']; + $orgArray['organizationID'] = $result['organizationID']; + } + } - array_push($resourceOrgArray, $orgArray); - } else { - foreach ($result as $row) { + array_push($resourceOrgArray, $orgArray); + } else { + foreach ($result as $row) { - $orgArray = array(); + $orgArray = array(); - //first, get the organization name - $query = "SELECT DISTINCT name FROM " . $dbName . ".Organization WHERE organizationID = " . $row['organizationID']; + //first, get the organization name + $query = "SELECT DISTINCT name FROM " . $dbName . ".Organization WHERE organizationID = " . $row['organizationID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organization'] = $orgRow['name']; - $orgArray['organizationID'] = $row['organizationID']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organization'] = $orgRow['name']; + $orgArray['organizationID'] = $row['organizationID']; + } + } - array_push($resourceOrgArray, $orgArray); - } - } + array_push($resourceOrgArray, $orgArray); + } + } - //otherwise if the org module is not installed get the org name from this database - } else { + //otherwise if the org module is not installed get the org name from this database + } else { - $resourceOrgArray = array(); + $resourceOrgArray = array(); - $query = "SELECT DISTINCT organizationID FROM ResourceOrganizationLink WHERE resourceID = '" . $this->resourceID . "'"; + $query = "SELECT DISTINCT organizationID FROM ResourceOrganizationLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['organizationID'])) { - $orgArray = array(); + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['organizationID'])) { + $orgArray = array(); - //first, get the organization name - $query = "SELECT DISTINCT shortName FROM Organization WHERE organizationID = " . $result['organizationID']; + //first, get the organization name + $query = "SELECT DISTINCT shortName FROM Organization WHERE organizationID = " . $result['organizationID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organization'] = $orgRow['shortName']; - $orgArray['organizationID'] = $result['organizationID']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organization'] = $orgRow['shortName']; + $orgArray['organizationID'] = $result['organizationID']; + } + } - array_push($resourceOrgArray, $orgArray); - } else { - foreach ($result as $row) { + array_push($resourceOrgArray, $orgArray); + } else { + foreach ($result as $row) { - $orgArray = array(); + $orgArray = array(); - //first, get the organization name - $query = "SELECT DISTINCT shortName FROM Organization WHERE organizationID = " . $row['organizationID']; + //first, get the organization name + $query = "SELECT DISTINCT shortName FROM Organization WHERE organizationID = " . $row['organizationID']; - if ($orgResult = mysql_query($query)) { - while ($orgRow = mysql_fetch_assoc($orgResult)) { - $orgArray['organization'] = $orgRow['shortName']; - $orgArray['organizationID'] = $row['organizationID']; - } - } + if ($orgResult = mysql_query($query)) { + while ($orgRow = mysql_fetch_assoc($orgResult)) { + $orgArray['organization'] = $orgRow['shortName']; + $orgArray['organizationID'] = $row['organizationID']; + } + } - array_push($resourceOrgArray, $orgArray); - } - } - } + array_push($resourceOrgArray, $orgArray); + } + } + } - return $resourceOrgArray; - } + return $resourceOrgArray; + } - public function hasCatalogingInformation() { - return ($this->recordSetIdentifier || $this->recordSetIdentifier || $this->bibSourceURL || $this->catalogingTypeID || $this->catalogingStatusID || $this->numberRecordsAvailable || $this->numberRecordsLoaded || $this->hasOclcHoldings); - } + public function hasCatalogingInformation() { + return ($this->recordSetIdentifier || $this->recordSetIdentifier || $this->bibSourceURL || $this->catalogingTypeID || $this->catalogingStatusID || $this->numberRecordsAvailable || $this->numberRecordsLoaded || $this->hasOclcHoldings); + } - //removes this resource and its children - public function removeResourceAndChildren() { + //removes this resource and its children + public function removeResourceAndChildren() { - // for each children - foreach ($this->getChildResources() as $instance) { - $removeChild = true; - $child = new Resource(new NamedArguments(array('primaryKey' => $instance->resourceID))); + // for each children + foreach ($this->getChildResources() as $instance) { + $removeChild = true; + $child = new Resource(new NamedArguments(array('primaryKey' => $instance->resourceID))); - // get parents of this children - $parents = $child->getParentResources(); + // get parents of this children + $parents = $child->getParentResources(); - // If the child ressource belongs to another parent than the one we're removing - foreach ($parents as $pinstance) { - $parentResourceObj = new Resource(new NamedArguments(array('primaryKey' => $pinstance->relatedResourceID))); - if ($parentResourceObj->resourceID != $this->resourceID) { - // We do not delete this child. - $removeChild = false; - } - } - if ($removeChild == true) { - $child->removeResource(); - } - } - // Finally, we remove the parent - $this->removeResource(); - } + // If the child ressource belongs to another parent than the one we're removing + foreach ($parents as $pinstance) { + $parentResourceObj = new Resource(new NamedArguments(array('primaryKey' => $pinstance->relatedResourceID))); + if ($parentResourceObj->resourceID != $this->resourceID) { + // We do not delete this child. + $removeChild = false; + } + } + if ($removeChild == true) { + $child->removeResource(); + } + } + // Finally, we remove the parent + $this->removeResource(); + } - //removes this resource - public function removeResource() { - //delete data from child linked tables - $this->removeResourceRelationships(); - $this->removePurchaseSites(); - $this->removeAuthorizedSites(); - $this->removeAdministeringSites(); - $this->removeResourceLicenses(); - $this->removeResourceLicenseStatuses(); - $this->removeResourceOrganizations(); - $this->removeResourcePayments(); - $this->removeAllSubjects(); - $this->removeAllIsbnOrIssn(); + //removes this resource + public function removeResource() { + //delete data from child linked tables + $this->removeResourceRelationships(); + $this->removePurchaseSites(); + $this->removeAuthorizedSites(); + $this->removeAdministeringSites(); + $this->removeResourceLicenses(); + $this->removeResourceLicenseStatuses(); + $this->removeResourceOrganizations(); + $this->removeResourcePayments(); + $this->removeAllSubjects(); + $this->removeAllIsbnOrIssn(); + $this->removeResourceIdentifiers(); - $instance = new Contact(); - foreach ($this->getContacts() as $instance) { - $instance->removeContactRoles(); - $instance->delete(); - } - - $instance = new ExternalLogin(); - foreach ($this->getExternalLogins() as $instance) { - $instance->delete(); - } - - $instance = new ResourceNote(); - foreach ($this->getNotes() as $instance) { - $instance->delete(); - } - - $instance = new Attachment(); - foreach ($this->getAttachments() as $instance) { - $instance->delete(); - } - - $instance = new Alias(); - foreach ($this->getAliases() as $instance) { - $instance->delete(); - } - - - $this->delete(); - } - - //removes resource hierarchy records - public function removeResourceRelationships() { - - $query = "DELETE + $instance = new Contact(); + foreach ($this->getContacts() as $instance) { + $instance->removeContactRoles(); + $instance->delete(); + } + + $instance = new ExternalLogin(); + foreach ($this->getExternalLogins() as $instance) { + $instance->delete(); + } + + $instance = new ResourceNote(); + foreach ($this->getNotes() as $instance) { + $instance->delete(); + } + + $instance = new Attachment(); + foreach ($this->getAttachments() as $instance) { + $instance->delete(); + } + + $instance = new Alias(); + foreach ($this->getAliases() as $instance) { + $instance->delete(); + } + + + $this->delete(); + } + + //removes resource hierarchy records + public function removeResourceRelationships() { + + $query = "DELETE FROM ResourceRelationship WHERE resourceID = '" . $this->resourceID . "' OR relatedResourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - //removes resource purchase sites - public function removePurchaseSites() { + //removes resource purchase sites + public function removePurchaseSites() { - $query = "DELETE + $query = "DELETE FROM ResourcePurchaseSiteLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - //removes resource authorized sites - public function removeAuthorizedSites() { + //removes resource authorized sites + public function removeAuthorizedSites() { - $query = "DELETE + $query = "DELETE FROM ResourceAuthorizedSiteLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - //removes resource administering sites - public function removeAdministeringSites() { + //removes resource administering sites + public function removeAdministeringSites() { - $query = "DELETE + $query = "DELETE FROM ResourceAdministeringSiteLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - //removes payment records - public function removeResourcePayments() { + //removes payment records + public function removeResourcePayments() { - $query = "DELETE + $query = "DELETE FROM ResourcePayment WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - //removes resource licenses - public function removeResourceLicenses() { + //removes resource licenses + public function removeResourceLicenses() { - $query = "DELETE + $query = "DELETE FROM ResourceLicenseLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - //removes resource license statuses - public function removeResourceLicenseStatuses() { + //removes resource license statuses + public function removeResourceLicenseStatuses() { - $query = "DELETE + $query = "DELETE FROM ResourceLicenseStatus WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - //removes resource organizations - public function removeResourceOrganizations() { + //removes resource organizations + public function removeResourceOrganizations() { - $query = "DELETE + $query = "DELETE FROM ResourceOrganizationLink WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - //removes resource note records - public function removeResourceNotes() { + //removes resource note records + public function removeResourceNotes() { - $query = "DELETE + $query = "DELETE FROM ResourceNote WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - //removes resource steps - public function removeResourceSteps() { + //removes resource steps + public function removeResourceSteps() { - $query = "DELETE + $query = "DELETE FROM ResourceStep WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } - - //search used for the resource autocomplete - public function resourceAutocomplete($q) { - $resourceArray = array(); - $result = mysql_query("SELECT titleText, resourceID + $result = $this->db->processQuery($query); + } + + //remove resource Identifiers + public function removeResourceIdentifiers() { + $query = "DELETE + FROM Identifier + WHERE resourceID = '" . $this->resourceID . "'"; + $this->db->processQuery($query); + } + + //search used for the resource autocomplete + public function resourceAutocomplete($q) { + $resourceArray = array(); + $result = mysql_query("SELECT titleText, resourceID FROM Resource WHERE upper(titleText) like upper('%" . $q . "%') ORDER BY 1;"); - while ($row = mysql_fetch_assoc($result)) { - $resourceArray[] = $row['titleText'] . "|" . $row['resourceID']; - } + while ($row = mysql_fetch_assoc($result)) { + $resourceArray[] = $row['titleText'] . "|" . $row['resourceID']; + } - return $resourceArray; - } + return $resourceArray; + } - //search used for the organization autocomplete - public function organizationAutocomplete($q) { - $config = new Configuration; - $organizationArray = array(); + //search used for the organization autocomplete + public function organizationAutocomplete($q) { + $config = new Configuration; + $organizationArray = array(); - //if the org module is installed get the org name from org database - if ($config->settings->organizationsModule == 'Y') { - $dbName = $config->settings->organizationsDatabaseName; + //if the org module is installed get the org name from org database + if ($config->settings->organizationsModule == 'Y') { + $dbName = $config->settings->organizationsDatabaseName; - $result = mysql_query("SELECT CONCAT(A.name, ' (', O.name, ')') shortName, O.organizationID + $result = mysql_query("SELECT CONCAT(A.name, ' (', O.name, ')') shortName, O.organizationID FROM " . $dbName . ".Alias A, " . $dbName . ".Organization O WHERE A.organizationID=O.organizationID AND upper(A.name) like upper('%" . $q . "%') @@ -1821,250 +1830,250 @@ public function organizationAutocomplete($q) { FROM " . $dbName . ".Organization WHERE upper(name) like upper('%" . $q . "%') ORDER BY 1;"); - } else { + } else { - $result = mysql_query("SELECT organizationID, shortName + $result = mysql_query("SELECT organizationID, shortName FROM Organization O WHERE upper(O.shortName) like upper('%" . $q . "%') ORDER BY shortName;"); - } + } - while ($row = mysql_fetch_assoc($result)) { - $organizationArray[] = $row['shortName'] . "|" . $row['organizationID']; - } + while ($row = mysql_fetch_assoc($result)) { + $organizationArray[] = $row['shortName'] . "|" . $row['organizationID']; + } - return $organizationArray; - } + return $organizationArray; + } - //search used for the license autocomplete - public function licenseAutocomplete($q) { - $config = new Configuration; - $licenseArray = array(); + //search used for the license autocomplete + public function licenseAutocomplete($q) { + $config = new Configuration; + $licenseArray = array(); - //if the org module is installed get the org name from org database - if ($config->settings->licensingModule == 'Y') { - $dbName = $config->settings->licensingDatabaseName; + //if the org module is installed get the org name from org database + if ($config->settings->licensingModule == 'Y') { + $dbName = $config->settings->licensingDatabaseName; - $result = mysql_query("SELECT shortName, licenseID + $result = mysql_query("SELECT shortName, licenseID FROM " . $dbName . ".License WHERE upper(shortName) like upper('%" . $q . "%') ORDER BY 1;"); - } + } - while ($row = mysql_fetch_assoc($result)) { - $licenseArray[] = $row['shortName'] . "|" . $row['licenseID']; - } + while ($row = mysql_fetch_assoc($result)) { + $licenseArray[] = $row['shortName'] . "|" . $row['licenseID']; + } - return $licenseArray; - } + return $licenseArray; + } - /////////////////////////////////////////////////////////////////////////////////// - // + /////////////////////////////////////////////////////////////////////////////////// + // // Workflow functions follow - // + // /////////////////////////////////////////////////////////////////////////////////// - //returns array of ResourceStep objects for this Resource - public function getResourceSteps() { + //returns array of ResourceStep objects for this Resource + public function getResourceSteps() { - $query = "SELECT * FROM ResourceStep + $query = "SELECT * FROM ResourceStep WHERE resourceID = '" . $this->resourceID . "' ORDER BY displayOrderSequence, stepID"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceStepID'])) { - $object = new ResourceStep(new NamedArguments(array('primaryKey' => $result['resourceStepID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new ResourceStep(new NamedArguments(array('primaryKey' => $row['resourceStepID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceStepID'])) { + $object = new ResourceStep(new NamedArguments(array('primaryKey' => $result['resourceStepID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new ResourceStep(new NamedArguments(array('primaryKey' => $row['resourceStepID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns current step location in the workflow for this resource - //used to display the group on the tabs - public function getCurrentStepGroup() { + //returns current step location in the workflow for this resource + //used to display the group on the tabs + public function getCurrentStepGroup() { - $query = "SELECT groupName FROM ResourceStep RS, UserGroup UG + $query = "SELECT groupName FROM ResourceStep RS, UserGroup UG WHERE resourceID = '" . $this->resourceID . "' ORDER BY stepID"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceStepID'])) { - - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceStepID'])) { + + } + } - //returns first steps (object) in the workflow for this resource - public function getFirstSteps() { + //returns first steps (object) in the workflow for this resource + public function getFirstSteps() { - $query = "SELECT * FROM ResourceStep + $query = "SELECT * FROM ResourceStep WHERE resourceID = '" . $this->resourceID . "' AND (priorStepID is null OR priorStepID = '0') ORDER BY stepID"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceStepID'])) { - $object = new ResourceStep(new NamedArguments(array('primaryKey' => $result['resourceStepID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new ResourceStep(new NamedArguments(array('primaryKey' => $row['resourceStepID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceStepID'])) { + $object = new ResourceStep(new NamedArguments(array('primaryKey' => $result['resourceStepID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new ResourceStep(new NamedArguments(array('primaryKey' => $row['resourceStepID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //enters resource into new workflow - public function enterNewWorkflow() { - $config = new Configuration(); + //enters resource into new workflow + public function enterNewWorkflow() { + $config = new Configuration(); - //remove any current workflow steps - $this->removeResourceSteps(); + //remove any current workflow steps + $this->removeResourceSteps(); - //make sure this resource is marked in progress in case it was archived - $status = new Status(); - $this->statusID = $status->getIDFromName('progress'); - $this->save(); + //make sure this resource is marked in progress in case it was archived + $status = new Status(); + $this->statusID = $status->getIDFromName('progress'); + $this->save(); - //Determine the workflow this resource belongs to - $workflowObj = new Workflow(); - $workflowID = $workflowObj->getWorkflowID($this->resourceTypeID, $this->resourceFormatID, $this->acquisitionTypeID); + //Determine the workflow this resource belongs to + $workflowObj = new Workflow(); + $workflowID = $workflowObj->getWorkflowID($this->resourceTypeID, $this->resourceFormatID, $this->acquisitionTypeID); - if ($workflowID) { - $workflow = new Workflow(new NamedArguments(array('primaryKey' => $workflowID))); + if ($workflowID) { + $workflow = new Workflow(new NamedArguments(array('primaryKey' => $workflowID))); - //Copy all of the step attributes for this workflow to a new resource step - foreach ($workflow->getSteps() as $step) { - $resourceStep = new ResourceStep(); + //Copy all of the step attributes for this workflow to a new resource step + foreach ($workflow->getSteps() as $step) { + $resourceStep = new ResourceStep(); - $resourceStep->resourceStepID = ''; - $resourceStep->resourceID = $this->resourceID; - $resourceStep->stepID = $step->stepID; - $resourceStep->priorStepID = $step->priorStepID; - $resourceStep->stepName = $step->stepName; - $resourceStep->userGroupID = $step->userGroupID; - $resourceStep->displayOrderSequence = $step->displayOrderSequence; + $resourceStep->resourceStepID = ''; + $resourceStep->resourceID = $this->resourceID; + $resourceStep->stepID = $step->stepID; + $resourceStep->priorStepID = $step->priorStepID; + $resourceStep->stepName = $step->stepName; + $resourceStep->userGroupID = $step->userGroupID; + $resourceStep->displayOrderSequence = $step->displayOrderSequence; - $resourceStep->save(); - } + $resourceStep->save(); + } - //Start the first step - //this handles updating the db and sending notifications for approval groups - foreach ($this->getFirstSteps() as $resourceStep) { - $resourceStep->startStep(); - } - } + //Start the first step + //this handles updating the db and sending notifications for approval groups + foreach ($this->getFirstSteps() as $resourceStep) { + $resourceStep->startStep(); + } + } - //send an email notification to the feedback email address and the creator - $cUser = new User(new NamedArguments(array('primaryKey' => $this->createLoginID))); - $acquisitionType = new AcquisitionType(new NamedArguments(array('primaryKey' => $this->acquisitionTypeID))); + //send an email notification to the feedback email address and the creator + $cUser = new User(new NamedArguments(array('primaryKey' => $this->createLoginID))); + $acquisitionType = new AcquisitionType(new NamedArguments(array('primaryKey' => $this->acquisitionTypeID))); - if ($cUser->firstName) { - $creator = $cUser->firstName . " " . $cUser->lastName; - } else if ($this->createLoginID) { //for some reason user isn't set up or their firstname/last name don't exist - $creator = $this->createLoginID; - } else { - $creator = "(unknown user)"; - } + if ($cUser->firstName) { + $creator = $cUser->firstName . " " . $cUser->lastName; + } else if ($this->createLoginID) { //for some reason user isn't set up or their firstname/last name don't exist + $creator = $this->createLoginID; + } else { + $creator = "(unknown user)"; + } - if (($config->settings->feedbackEmailAddress) || ($cUser->emailAddress)) { - $email = new Email(); - $util = new Utility(); + if (($config->settings->feedbackEmailAddress) || ($cUser->emailAddress)) { + $email = new Email(); + $util = new Utility(); - $email->message = $util->createMessageFromTemplate('NewResourceMain', $this->resourceID, $this->titleText, '', '', $creator); + $email->message = $util->createMessageFromTemplate('NewResourceMain', $this->resourceID, $this->titleText, '', '', $creator); - if ($cUser->emailAddress) { - $emailTo[] = $cUser->emailAddress; - } + if ($cUser->emailAddress) { + $emailTo[] = $cUser->emailAddress; + } - if ($config->settings->feedbackEmailAddress != '') { - $emailTo[] = $config->settings->feedbackEmailAddress; - } + if ($config->settings->feedbackEmailAddress != '') { + $emailTo[] = $config->settings->feedbackEmailAddress; + } - $email->to = implode(",", $emailTo); + $email->to = implode(",", $emailTo); - if ($acquisitionType->shortName) { - $email->subject = "CORAL Alert: New " . $acquisitionType->shortName . " Resource Added: " . $this->titleText; - } else { - $email->subject = "CORAL Alert: New Resource Added: " . $this->titleText; - } + if ($acquisitionType->shortName) { + $email->subject = "CORAL Alert: New " . $acquisitionType->shortName . " Resource Added: " . $this->titleText; + } else { + $email->subject = "CORAL Alert: New Resource Added: " . $this->titleText; + } - $email->send(); - } - } + $email->send(); + } + } - //completes a workflow (changes status to complete and sends notifications to creator and "master email") - public function completeWorkflow() { - $config = new Configuration(); - $util = new Utility(); - $status = new Status(); - $statusID = $status->getIDFromName('complete'); + //completes a workflow (changes status to complete and sends notifications to creator and "master email") + public function completeWorkflow() { + $config = new Configuration(); + $util = new Utility(); + $status = new Status(); + $statusID = $status->getIDFromName('complete'); - if ($statusID) { - $this->statusID = $statusID; - $this->save(); - } + if ($statusID) { + $this->statusID = $statusID; + $this->save(); + } - //send notification to creator and master email address + //send notification to creator and master email address - $cUser = new User(new NamedArguments(array('primaryKey' => $this->createLoginID))); + $cUser = new User(new NamedArguments(array('primaryKey' => $this->createLoginID))); - //formulate emil to be sent - $email = new Email(); - $email->message = $util->createMessageFromTemplate('CompleteResource', $this->resourceID, $this->titleText, '', $this->systemNumber, ''); + //formulate emil to be sent + $email = new Email(); + $email->message = $util->createMessageFromTemplate('CompleteResource', $this->resourceID, $this->titleText, '', $this->systemNumber, ''); - if ($cUser->emailAddress) { - $emailTo[] = $cUser->emailAddress; - } + if ($cUser->emailAddress) { + $emailTo[] = $cUser->emailAddress; + } - if ($config->settings->feedbackEmailAddress != '') { - $emailTo[] = $config->settings->feedbackEmailAddress; - } + if ($config->settings->feedbackEmailAddress != '') { + $emailTo[] = $config->settings->feedbackEmailAddress; + } - $email->to = implode(",", $emailTo); + $email->to = implode(",", $emailTo); - $email->subject = "CORAL Alert: Workflow completion for " . $this->titleText; + $email->subject = "CORAL Alert: Workflow completion for " . $this->titleText; - $email->send(); - } + $email->send(); + } - //returns array of subject objects - public function getGeneralDetailSubjectLinkID() { + //returns array of subject objects + public function getGeneralDetailSubjectLinkID() { - $query = "SELECT + $query = "SELECT GDL.generalDetailSubjectLinkID FROM Resource R @@ -2079,28 +2088,28 @@ public function getGeneralDetailSubjectLinkID() { DS.shortName"; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['generalDetailSubjectLinkID'])) { - $object = new GeneralDetailSubjectLink(new NamedArguments(array('primaryKey' => $result['generalDetailSubjectLinkID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new GeneralDetailSubjectLink(new NamedArguments(array('primaryKey' => $row['generalDetailSubjectLinkID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['generalDetailSubjectLinkID'])) { + $object = new GeneralDetailSubjectLink(new NamedArguments(array('primaryKey' => $result['generalDetailSubjectLinkID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new GeneralDetailSubjectLink(new NamedArguments(array('primaryKey' => $row['generalDetailSubjectLinkID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //returns array of subject objects - public function getDetailedSubjects($resourceID, $generalSubjectID) { + //returns array of subject objects + public function getDetailedSubjects($resourceID, $generalSubjectID) { - $query = "SELECT + $query = "SELECT RSUB.resourceID, GDL.detailedSubjectID, DetailedSubject.shortName, @@ -2112,161 +2121,152 @@ public function getDetailedSubjects($resourceID, $generalSubjectID) { WHERE RSUB.resourceID = " . $resourceID . " AND GDL.generalSubjectID = " . $generalSubjectID . " ORDER BY DetailedSubject.shortName"; - //echo $query . "
    "; + //echo $query . "
    "; - $result = $this->db->processQuery($query, 'assoc'); + $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); + $objects = array(); - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['detailedSubjectID'])) { - $object = new DetailedSubject(new NamedArguments(array('primaryKey' => $result['detailedSubjectID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new DetailedSubject(new NamedArguments(array('primaryKey' => $row['detailedSubjectID']))); - array_push($objects, $object); - } - } + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['detailedSubjectID'])) { + $object = new DetailedSubject(new NamedArguments(array('primaryKey' => $result['detailedSubjectID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new DetailedSubject(new NamedArguments(array('primaryKey' => $row['detailedSubjectID']))); + array_push($objects, $object); + } + } - return $objects; - } + return $objects; + } - //removes all resource subjects - public function removeAllSubjects() { + //removes all resource subjects + public function removeAllSubjects() { - $query = "DELETE + $query = "DELETE FROM ResourceSubject WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } + $result = $this->db->processQuery($query); + } - public function removeAllIsbnOrIssn() { - $query = "DELETE + public function removeAllIsbnOrIssn() { + $query = "DELETE FROM IsbnOrIssn WHERE resourceID = '" . $this->resourceID . "'"; - $result = $this->db->processQuery($query); - } - - public function setIsbnOrIssn($isbnorissns) { - $this->removeAllIsbnOrIssn(); - foreach ($isbnorissns as $isbnorissn) { - if (trim($isbnorissn) != '') { - $isbnOrIssn = new IsbnOrIssn(); - $isbnOrIssn->resourceID = $this->resourceID; - $isbnOrIssn->isbnOrIssn = $isbnorissn; - $isbnOrIssn->save(); - } - } - } - - /* * ******************************************* - * NEW FUNCTION * - * ******************************************* */ - /* TODO - /!\ /!\ /!\ - /!\ rgrep IsbnOrIssn avant de tout casser !! /!\ - /!\ /!\ /!\ - */ - - /** - * Fill the Identifier table in DB - * @param $identifiers array array of all identifiers (type => id) (if type isn't known, don't put any key) - * - */ - public function setIdentifiers($identifiers) { - $isbnorissns = array(); - foreach ($identifiers as $key => $value) { - $identifier = new Identifier(); - $identifier->resourceID = $this->resourceID; - $identifier->identifierTypeID = $identifier->getIdentifierTypeID($key); - $identifier->identifier = $value; - - $identifier->save(); - - //Temporary fill IsbnOrIssn table - if ($identifier->identifierTypeID <= 5) { - array_push($isbnorissns, $value); - } - } - $this->setIsbnOrIssn($isbnorissns); - } - - public function getResourceByIdentifierAndType($identifier, $type = NULL) { //TODO _ $identifier est un tableau !! => do boucle !! - $query = "SELECT resourceID FROM Identifier WHERE upper(identifier) = '" . str_replace("'", "''", strtoupper($identifier)) . "'"; - if ($type != NULL) { - $id = new Identifier(); - $typeID = $id->getIdentifierTypeID($type); - $query .= " AND identifierTypeID = $typeID"; - } - $query .= ";"; - - $result = $this->db->processQuery($query, 'assoc'); //TODO _ see param assoc - - - - $objects = array(); - - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceID'])) { - $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); - array_push($objects, $object); - } - } - - return $objects; - } - - public function getResourceByIdentifiers($identifiers) { - $query = "SELECT DISTINCT(resourceID) FROM Identifier"; - $i = 0; - /* - if (!is_array($identifiers)) { - $value = $identifiers; - $identifiers = array($value); - } - */ - foreach ($identifiers as $value) { - $query .= ($i == 0) ? " WHERE " : " OR "; - $query .= "identifier = '" . $this->db->escapeString($value) . "'"; - $i++; - } - - $query .= " ORDER BY 1"; - $result = $this->db->processQuery($query, 'assoc'); - $objects = array(); - - //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['resourceID'])) { - $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); - array_push($objects, $object); - } else { - foreach ($result as $row) { - $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); - array_push($objects, $object); - } - } - return $objects; - } - - public function &getNewInitializedResource() { - $loginID = $_SESSION['loginID']; - $res = new Resource(); - $res->createLoginID = $loginID; - $res->createDate = date('Y-m-d'); - $res->updateLoginID = ''; - $res->updateDate = ''; - $res->statusID = 1; //in progress, don't know why ... - //$res->save(); - - return $res; - } + $result = $this->db->processQuery($query); + } + + public function setIsbnOrIssn($isbnorissns) { + $this->removeAllIsbnOrIssn(); + foreach ($isbnorissns as $isbnorissn) { + if (trim($isbnorissn) != '') { + $isbnOrIssn = new IsbnOrIssn(); + $isbnOrIssn->resourceID = $this->resourceID; + $isbnOrIssn->isbnOrIssn = $isbnorissn; + $isbnOrIssn->save(); + } + } + } + + /** + * Fill the Identifier table in DB + * @param $identifiers array array of all identifiers (type => id) (if type isn't known, don't put any key) + * + */ + public function setIdentifiers($identifiers) { + $isbnorissns = array(); + foreach ($identifiers as $key => $value) { + $identifier = new Identifier(); + $identifier->resourceID = $this->resourceID; + $identifier->identifierTypeID = $identifier->getIdentifierTypeID($key); + $identifier->identifier = $value; + + $identifier->save(); + + //Temporary fill IsbnOrIssn table + if ($identifier->identifierTypeID <= 5) { + array_push($isbnorissns, $value); + } + } + $this->setIsbnOrIssn($isbnorissns); + } + + public function getResourceByIdentifierAndType($identifier, $type = NULL) { //TODO _ $identifier est un tableau !! => do boucle !! + $query = "SELECT resourceID FROM Identifier WHERE upper(identifier) = '" . str_replace("'", "''", strtoupper($identifier)) . "'"; + if ($type != NULL) { + $id = new Identifier(); + $typeID = $id->getIdentifierTypeID($type); + $query .= " AND identifierTypeID = $typeID"; + } + $query .= ";"; + + $result = $this->db->processQuery($query, 'assoc'); //TODO _ see param assoc + + + + $objects = array(); + + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceID'])) { + $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); + array_push($objects, $object); + } + } + + return $objects; + } + + public function getResourceByIdentifiers($identifiers) { + $query = "SELECT DISTINCT(resourceID) FROM Identifier"; + $i = 0; + /* + if (!is_array($identifiers)) { + $value = $identifiers; + $identifiers = array($value); + } + */ + foreach ($identifiers as $value) { + $query .= ($i == 0) ? " WHERE " : " OR "; + $query .= "identifier = '" . $this->db->escapeString($value) . "'"; + $i++; + } + + $query .= " ORDER BY 1"; + $result = $this->db->processQuery($query, 'assoc'); + $objects = array(); + + //need to do this since it could be that there's only one request and this is how the dbservice returns result + if (isset($result['resourceID'])) { + $object = new Resource(new NamedArguments(array('primaryKey' => $result['resourceID']))); + array_push($objects, $object); + } else { + foreach ($result as $row) { + $object = new Resource(new NamedArguments(array('primaryKey' => $row['resourceID']))); + array_push($objects, $object); + } + } + return $objects; + } + + public function &getNewInitializedResource() { + $loginID = $_SESSION['loginID']; + $res = new Resource(); + $res->createLoginID = $loginID; + $res->createDate = date('Y-m-d'); + $res->updateLoginID = ''; + $res->updateDate = ''; + $res->statusID = 1; //in progress, don't know why ... + //$res->save(); + + return $res; + } } diff --git a/ajax_forms/getKBSearchResults.php b/ajax_forms/getKBSearchResults.php deleted file mode 100644 index 5062b2d..0000000 --- a/ajax_forms/getKBSearchResults.php +++ /dev/null @@ -1,91 +0,0 @@ -
    -
    - Add new resource - Search results -
    -searchOnGokb($_POST['name'], $_POST['issn'], $_POST['publisher'], $_POST['type']); -$nb_packages = count($results[0]); -$nb_titles = count($results[1]); -$isPaginated = ((isset($_POST['paginate'])) && ($_POST['paginate'] == true)); - -//Display packages results -if ($nb_packages > 0){ - echo "
    "; - echo " Packages View all packages results
    "; - - echo '
    '; - foreach ($results[0] as $key => $value) { - echo "'; - echo ''; - echo ""; - } - echo '
    "; - echo ' - '.$value; - echo '
    '; - echo "
    "; -} - -//Display titles results -if ($nb_titles > 0){ - echo "
    "; - echo ' Issues View all issues results
    '; - - - echo ''; - foreach ($results[1] as $key => $value) { - echo "'; - echo ''; - echo ""; - } - echo '
    '; - - echo "
    "; -} else { - if (!$isPaginated) echo "No results, please check your search fields
    "; -} - -//Display pagination -if ($isPaginated) { - switch ($_POST['type']) { - case -1: - $resType=0; - $divId = "div_packagesResults"; - break; - case 1: - $resType = 1; - $divId = "div_titlesResults"; - break; - default: - break; - } - echo paginate(count($results[$resType]), "$divId"); - echo ""; -} - - -?> -
    - - - -
    - - - diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index a0f7435..be073ac 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -3,21 +3,16 @@ include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/GOKbTools.php"; include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/ImportTool.php"; -/* required $_POST : type, gokbID */ - $gokbTool = GOKbTools::getInstance(); $importTool = new ImportTool(); $record = $gokbTool->getRecord($_POST['type'], $_POST['id']); -//echo "DEBUG_ IFG _ request 1 ok\n"; -//$nbTipps = $gokbTool->getNbTipps($record); +$recordDetails = $record->{'metadata'}->{'gokb'}->{$_POST['type']}; +$nbTipps = $gokbTool->getNbTipps($recordDetails); -//echo "DEBUG_ IFG _ all request ok\n"; $datas = array(); $identifiers = array("gokb" => $_POST['id']); -$recordDetails = $record->{'metadata'}->{'gokb'}->{$_POST['type']}; -$nbTipps = $gokbTool->getNbTipps($recordDetails); $datas['titleText'] = $gokbTool->getResourceName($recordDetails); $string = ""; @@ -29,7 +24,7 @@ $datas["acquisitionTypeID"] = $acqID; $string .= "insertion de datas[acquisitionTypeID] = " . $acqID . "
    "; } - + //Organization: $datas['organization'] = array( "platform" => $recordDetails->{"nominalPlatform"}, diff --git a/import.php b/import.php index 3f6d1e4..fbd3f1e 100644 --- a/import.php +++ b/import.php @@ -75,10 +75,9 @@ $uploadfile = $_POST['uploadfile']; if (($handle = fopen($uploadfile, "r")) !== FALSE) { - $firstLine = true; + $row = 0; while ($line = fgetcsv($handle, 0, $delimiter)) { - if ($firstLine) { - $firstLine = false; + if ($row == 0) { print "

    Settings

    "; print "

    Importing and deduping isbnOrISSN on the following columns: "; foreach ($line as $key => $value) { @@ -107,9 +106,15 @@ $tool->addResource($datas, $identifiers); } + $row++; } } - + print "

    Results

    "; + print "

    " . ($row- 1) . " rows have been processed. ".ImportTool::getNbInserted()." rows have been inserted.

    "; + print "

    ". ImportTool::getNbParentInserted()." parents have been created. ".ImportTool::getNbParentAttached()." resources have been attached to an existing parent.

    "; + print "

    ".ImportTool::getNbOrganizationsInserted()." organizations have been created"; + if (count(ImportTool::getArrayOrganizationsCreated()) > 0) print " (" . implode(',', ImportTool::getArrayOrganizationsCreated()) . ")"; + print ". ".ImportTool::getNbOrganizationsAttached()." resources have been attached to an existing organization.

    "; } else { ?> From e0341290d8ad2ce4a1e6cc484468b30370426f63 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Fri, 31 Jul 2015 09:46:07 +0200 Subject: [PATCH 13/32] Import _ ResourceType treatment --- admin/classes/domain/ImportTool.php | 55 ++++++++++++++++----------- admin/classes/domain/ResourceType.php | 15 ++++++++ ajax_processing/importFromGOKb.php | 37 +++++------------- 3 files changed, 56 insertions(+), 51 deletions(-) diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index ff8c251..eddeab6 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -73,38 +73,54 @@ function __construct() { public function addResource($datas, $identifiers) { $res_tmp = new Resource(); $org = null; - $parents = null; + $parentName = null; + $resourceType = null; $htmlContent = ''; //TODO _ DEBUG _ Displaying after select - /* * ***************************************** - * * Has the resource to be inserted ? ** - * ***************************************** */ + /******************************************* + ** Has the resource to be inserted ? ** + *******************************************/ $hasToBeInserted = $this->hasResourceToBeInserted($datas, $identifiers); - /* * ************************************** - * * Datas insertion ** - * ************************************** */ + /**************************************** + ** Datas insertion ** + ****************************************/ if ($hasToBeInserted) { $res = $res_tmp->getNewInitializedResource(); echo "DEBUG _ Resource insertion !!
    "; //Resource treatment foreach ($datas as $key => $value) { - if ($key == "organization") { - $org = $value; - } elseif ($key == "parentResource") { - $parentName = $value; - // $parents = $value; - } else { - $res->$key = (string) $value; + switch ($key) { + case "organization": + $org = $value; + break; + case "parentResource": + $parentName = $value; + break; + case "resourceType": + $resourceType = $value; + break; + default: + $res->$key = (string) $value; + break; } } + //ResourceType treatment + if($resourceType != NULL){ + $res->resourceTypeID = ResourceType::getResourceTypeID($resourceType); + } + $res->save(); //Resource identifiers treatment $res->setIdentifiers($identifiers); + + + + //Parent treatment if ($parentName != null) { $parentID = $this->parentTreatment($parentName); @@ -224,15 +240,6 @@ private function organizationTreatment($organizations, $resourceID) { $result = $organization->db->processQuery($query, 'assoc'); if ($result['count'] == 0) { // If not, we try to create it -// $query = "INSERT INTO $dbName.Organization SET createDate=NOW(), createLoginID='$loginID', name='" . mysql_escape_string($orgName) . "'"; -// try { -// $result = $organization->db->processQuery($query); -// $organizationID = $result; -// self::$organizationsInserted++; -// array_push(self::$arrayOrganizationsCreated, $orgName); -// } catch (Exception $e) { -// $htmlContent .= "

    Organization $orgName could not be added.

    "; -// } $organizationID = $this->createOrgWithOrganizationModule($orgName); } // If yes, we attach it to our resource @@ -340,6 +347,8 @@ private function createOrgWithOrganizationModule($orgName) { } // ------------------------------------------------------------------------- + + /******************************** * Accessors * diff --git a/admin/classes/domain/ResourceType.php b/admin/classes/domain/ResourceType.php index 3963b54..3bb8a7a 100644 --- a/admin/classes/domain/ResourceType.php +++ b/admin/classes/domain/ResourceType.php @@ -34,6 +34,21 @@ public function getNumberOfChildren(){ return $result['childCount']; } + + public static function getResourceTypeID($type) { + $id = null; + $query = "SELECT resourceTypeID FROM ResourceType WHERE upper(shortName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; + $result = $this->db->processQuery($query); + if (count($result) == 0){ //this type doesn't exist, we create it + $resType = new ResourceType(); + $resType->shortName = $type; + $resType->save(); + $id = $resType->resourceTypeID; + } else { + $id = $result[0]; + } + return $id; + } } diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index be073ac..d34dd33 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -36,8 +36,9 @@ //ensemble des tipps (boucle) //$tippsDatas = array(); + } else { //import title; - //TODO medium = resourceType (ajout manuel only pour l'instant) + //Organization $org = $recordDetails->{"publisher"}->{'name'}; if ($org != NULL) { @@ -45,7 +46,6 @@ $string .= "insertion de datas['organization'] = " . $org . "
    "; } - //Identifiers _ URL ? $xml = $recordDetails->{'identifiers'}; $xmlIDs = $xml->children(); @@ -55,34 +55,15 @@ $identifiers["$tmp[0]"] = (string) $tmp[1]; $string .= "insertion de identifiers[" . $tmp[0] . "] = " . $tmp[1] . "
    "; } - + + //ResourceType + $datas['resourceType'] = (string) $recordDetails->{'medium'} ;//TODO _ ID _ create type if needed + + //History / Aliases - //parents - if ($nbTipps > 0) { - $tipps = $recordDetails->{'TIPPs'}; - $type = "package"; - $parents = array(); - - foreach ($tipps->children() as $child) { - $resource = $child->{$type}; - $resourceAttr = $resource->attributes(); - $parentDatas = array(); - $parentDatas['titleText'] = $gokbTool->getResourceName($resource); - $parentDatas['identifiers']['gokb'] = $gokbTool->UriToGokbId("$type" . 's/' . $resourceAttr[0]); - $parentDatas['organization']['platform'] = (string) $child->{'platform'}->{'name'}; - $parentDatas['resourceURL'] = (string) $child->{'url'}; - //TODO _ coverage and others - array_push($parents, $parentDatas); - } - } - - $datas['parentResource'] = $parents; //TODO _ modif struct string -> tab - - //History / Aliases - //$datas['parentResource'] ? -//echo "$string"; - // $importTool->addResource($datas, $identifiers); + + $importTool->addResource($datas, $identifiers); return $string; } From 999a55bd745c3d92c653d277f22fd5aa5d1bf0b2 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Fri, 31 Jul 2015 16:41:08 +0200 Subject: [PATCH 14/32] Add GOKb Alias/history Treatment --- admin/classes/domain/AliasType.php | 62 +++++++++++++++--------- admin/classes/domain/GOKbTools.php | 18 +++++++ admin/classes/domain/ImportTool.php | 37 ++++++++++++-- admin/classes/domain/ResourceType.php | 5 +- ajax_processing/importFromGOKb.php | 70 +++++++++++++++++++++++---- 5 files changed, 154 insertions(+), 38 deletions(-) diff --git a/admin/classes/domain/AliasType.php b/admin/classes/domain/AliasType.php index e87a98a..fd9d543 100644 --- a/admin/classes/domain/AliasType.php +++ b/admin/classes/domain/AliasType.php @@ -1,40 +1,56 @@ . -** -************************************************************************************************************************** -*/ + * ************************************************************************************************************************* + * * CORAL Resources Module v. 1.0 + * * + * * Copyright (c) 2010 University of Notre Dame + * * + * * This file is part of CORAL. + * * + * * CORAL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * * + * * CORAL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License along with CORAL. If not, see . + * * + * ************************************************************************************************************************* + */ class AliasType extends DatabaseObject { - protected function defineRelationships() {} + protected function defineRelationships() { + + } - protected function overridePrimaryKeyName() {} + protected function overridePrimaryKeyName() { + + } + //returns number of children for this particular alias type + public function getNumberOfChildren() { + $query = "SELECT count(*) childCount FROM Alias WHERE aliasTypeID = '" . $this->aliasTypeID . "';"; - //returns number of children for this particular alias type - public function getNumberOfChildren(){ + $result = $this->db->processQuery($query, 'assoc'); - $query = "SELECT count(*) childCount FROM Alias WHERE aliasTypeID = '" . $this->aliasTypeID . "';"; + return $result['childCount']; + } - $result = $this->db->processQuery($query, 'assoc'); + public static function getAliasTypeID($type) { + $object = new AliasType(); + $query = "SELECT aliasTypeID FROM AliasType WHERE upper(shortName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; - return $result['childCount']; + $result = $object->db->processQUery($query); + + if (count($result) == 0) { + $id = null; + } else { + $id = $result[0]; + } + return $id; + } - } } ?> \ No newline at end of file diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index b86fb16..cbe9cb6 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -345,7 +345,25 @@ function getRecord($type, $id){ $rec = $record->{'GetRecord'}->{'record'}; return $rec; } + + function createIdentifiersArrayToImport($ids) { + foreach ($ids as $key => $value) { + $tmp = $value->attributes(); + $identifiers["$tmp[0]"] = (string) $tmp[1]; + //$string .= "insertion de identifiers[" . $tmp[0] . "] = " . $tmp[1] . "
    "; + } + return $identifiers; + } + + function convertXmlDateToDateTime($xmlDate){ + $format = "Y-m-d H:i:s.u"; + $date = DateTime::createFromFormat($format, $xmlDate); + return $date; + } + } + + ?> \ No newline at end of file diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index eddeab6..e042cda 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -3,6 +3,13 @@ include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/common/Configuration.php"; include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/Resource.php"; include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/ResourceRelationship.php"; +include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/ResourceType.php"; +include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/AliasType.php"; +include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/Alias.php"; +include_once $_SERVER['DOCUMENT_ROOT'] . "organizations/admin/classes/domain/Organization.php"; +include_once $_SERVER['DOCUMENT_ROOT'] . "organizations/admin/classes/domain/OrganizationRole.php"; +include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/ResourceOrganizationLink.php"; + class ImportTool { @@ -75,6 +82,7 @@ public function addResource($datas, $identifiers) { $org = null; $parentName = null; $resourceType = null; + $aliases = null; $htmlContent = ''; //TODO _ DEBUG _ Displaying after select @@ -102,14 +110,18 @@ public function addResource($datas, $identifiers) { case "resourceType": $resourceType = $value; break; + case "alias": + $aliases = $value; + break; default: $res->$key = (string) $value; break; } } + //ResourceType treatment if($resourceType != NULL){ - $res->resourceTypeID = ResourceType::getResourceTypeID($resourceType); + $res->resourceTypeID = ResourceType::getResourceTypeID((string)$resourceType); } $res->save(); @@ -117,7 +129,10 @@ public function addResource($datas, $identifiers) { //Resource identifiers treatment $res->setIdentifiers($identifiers); - + //Aliases treatment (history name change/ variant name) //TODO + if ($aliases != null){ + $this->aliasesTreatment($aliases, $res->resourceID); + } @@ -134,6 +149,7 @@ public function addResource($datas, $identifiers) { echo "DEBUG _ Resource not inserted !
    "; } echo 'DEBUG_ AddResource finished'; + self::$row++; } // ------------------------------------------------------------------------- @@ -194,7 +210,7 @@ private function parentTreatment($parentName) { if ($numberOfParents == 0) { // If not, create parent $parentResource = $resource->getNewInitializedResource(); - $parentResource->titleText = $parentName; //TODO _ appel à addResource avec $datas compètes + $parentResource->titleText = $parentName; //TODO _ appel à addResource avec $datas complètes $parentResource->save(); $parentID = $parentResource->resourceID; self::$parentInserted++; @@ -348,8 +364,19 @@ private function createOrgWithOrganizationModule($orgName) { // ------------------------------------------------------------------------- - - + private function aliasesTreatment($aliases, $resourceID) { + foreach ($aliases as $aliasType => $aliasArray){ + $typeID = AliasType::getAliasTypeID((string)$aliasType); + foreach ($aliasArray as $alias){ + $al = new Alias(); + $al->resourceID = $resourceID; + $al->aliasTypeID = $typeID; + $al->shortName = (string) $alias; + $al->save(); + } + } + } +// ------------------------------------------------------------------------- /******************************** * Accessors * * ****************************** */ diff --git a/admin/classes/domain/ResourceType.php b/admin/classes/domain/ResourceType.php index 3bb8a7a..c845083 100644 --- a/admin/classes/domain/ResourceType.php +++ b/admin/classes/domain/ResourceType.php @@ -36,9 +36,12 @@ public function getNumberOfChildren(){ } public static function getResourceTypeID($type) { + $object = new ResourceType(); $id = null; $query = "SELECT resourceTypeID FROM ResourceType WHERE upper(shortName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; - $result = $this->db->processQuery($query); + + $result =$object->db->processQuery($query); + if (count($result) == 0){ //this type doesn't exist, we create it $resType = new ResourceType(); $resType->shortName = $type; diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index d34dd33..b81b715 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -13,7 +13,8 @@ $datas = array(); $identifiers = array("gokb" => $_POST['id']); -$datas['titleText'] = $gokbTool->getResourceName($recordDetails); +$recordName = $gokbTool->getResourceName($recordDetails); +$datas['titleText'] = $recordName; $string = ""; @@ -46,21 +47,72 @@ $string .= "insertion de datas['organization'] = " . $org . "
    "; } + //ResourceType + $datas['resourceType'] = (string) $recordDetails->{'medium'} ;//TODO _ ID _ create type if needed + + //Identifiers _ URL ? $xml = $recordDetails->{'identifiers'}; - $xmlIDs = $xml->children(); + $idToKeep = $xml->children(); - foreach ($xmlIDs as $key => $value) { - $tmp = $value->attributes(); - $identifiers["$tmp[0]"] = (string) $tmp[1]; - $string .= "insertion de identifiers[" . $tmp[0] . "] = " . $tmp[1] . "
    "; - } +// foreach ($xmlIDs as $key => $value) { +// $tmp = $value->attributes(); +// $identifiers["$tmp[0]"] = (string) $tmp[1]; +// $string .= "insertion de identifiers[" . $tmp[0] . "] = " . $tmp[1] . "
    "; +// } - //ResourceType - $datas['resourceType'] = (string) $recordDetails->{'medium'} ;//TODO _ ID _ create type if needed //History / Aliases + $variantName = $recordDetails->{'variantNames'}; + $variants = $variantName->children(); + + if (count($variants) > 0){ + $datas['alias']['alternate name']=array(); + foreach ($variants as $key => $name) { + array_push($datas['alias']['alternate name'], (string) $name); + } + } + + $history = $recordDetails->{'history'}; + $events = $history->children(); + + // if ($history != null){ //comparer date pour identifiers, (publishedTO, event->date) + if (count($events) > 0){ + $datas['alias'] ['name change']= array(); + $events = $history->children(); + $checkDate = false; + $date = 0; + if ($recordDetails->{'publishedTo'} != null && $recordDetails->{'publishedTo'} != ''){ + $checkDate = true; + $date =(string) $recordDetails->{'publishedTo'} ; + $date = $gokbTool->convertXmlDateToDateTime($date); + } else { + $date = new DateTime(); + } + + foreach ($events as $event) { + $eventDate = (string) $event->{'date'}; + $eventDate = $gokbTool->convertXmlDateToDateTime($eventDate); + $from =$event->{'from'}->{'title'}; + $to = $event->{'to'}->{'title'}; + if ($from != $recordName){ + array_push($datas['alias'] ['name change'], (string)$from); + } + if ($to != $recordName){ + array_push($datas['alias'] ['name change'], (string)$to); + } + + if ($date < $eventDate){ + $date = $eventDate; + $id_tmp = $event->{'to'}->{'identifiers'}; + $idToKeep = $id_tmp->children(); + } + + } + + } + $identifiers = $gokbTool->createIdentifiersArrayToImport($idToKeep); $importTool->addResource($datas, $identifiers); From d202a139250b5fd0f75591a1291305d509558729 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Mon, 3 Aug 2015 11:35:04 +0200 Subject: [PATCH 15/32] add package import --- admin/classes/domain/AcquisitionType.php | 140 +++++++++--------- admin/classes/domain/GOKbTools.php | 2 +- admin/classes/domain/ImportTool.php | 14 +- admin/classes/domain/ResourceFormat.php | 23 ++- ajax_processing/importFromGOKb.php | 173 +++++++++++++++-------- 5 files changed, 216 insertions(+), 136 deletions(-) diff --git a/admin/classes/domain/AcquisitionType.php b/admin/classes/domain/AcquisitionType.php index bdf6453..b8ebcdb 100644 --- a/admin/classes/domain/AcquisitionType.php +++ b/admin/classes/domain/AcquisitionType.php @@ -1,77 +1,81 @@ . -** -************************************************************************************************************************** -*/ + * ************************************************************************************************************************* + * * CORAL Resources Module v. 1.0 + * * + * * Copyright (c) 2010 University of Notre Dame + * * + * * This file is part of CORAL. + * * + * * CORAL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * * + * * CORAL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License along with CORAL. If not, see . + * * + * ************************************************************************************************************************* + */ class AcquisitionType extends DatabaseObject { - protected function defineRelationships() {} - - protected function overridePrimaryKeyName() {} - - - - public function sortedArray() { - $query = "SELECT * FROM AcquisitionType ORDER BY IF(UCASE(shortName)='PAID',1, 2), shortName asc"; - $result = $this->db->processQuery($query, 'assoc'); - - $resultArray = array(); - $rowArray = array(); - - if (isset($result['AcquisitionTypeID'])){ - foreach (array_keys($result) as $attributeName) { - $rowArray[$attributeName] = $result[$attributeName]; - } - array_push($resultArray, $rowArray); - }else{ - foreach ($result as $row) { - foreach (array_keys($this->attributeNames) as $attributeName) { - $rowArray[$attributeName] = $row[$attributeName]; - } - array_push($resultArray, $rowArray); - } - } - - return $resultArray; - } - - - - - - //returns number of children for this particular contact role - public function getNumberOfChildren(){ - - $query = "SELECT count(*) childCount FROM Resource WHERE acquisitionTypeID = '" . $this->acquisitionTypeID . "';"; - - $result = $this->db->processQuery($query, 'assoc'); - - return $result['childCount']; - - } - - public static function getAcquisitionTypeID($type){ - $query = "SELECT acquisitionTypeID FROM AcquisitionType WHERE shortName = '".$type."';"; - - $result = $this->db->processQuery($query, 'assoc'); - - return $result[0]; - } - + protected function defineRelationships() { + + } + + protected function overridePrimaryKeyName() { + + } + + public function sortedArray() { + $query = "SELECT * FROM AcquisitionType ORDER BY IF(UCASE(shortName)='PAID',1, 2), shortName asc"; + $result = $this->db->processQuery($query, 'assoc'); + + $resultArray = array(); + $rowArray = array(); + + if (isset($result['AcquisitionTypeID'])) { + foreach (array_keys($result) as $attributeName) { + $rowArray[$attributeName] = $result[$attributeName]; + } + array_push($resultArray, $rowArray); + } else { + foreach ($result as $row) { + foreach (array_keys($this->attributeNames) as $attributeName) { + $rowArray[$attributeName] = $row[$attributeName]; + } + array_push($resultArray, $rowArray); + } + } + + return $resultArray; + } + + //returns number of children for this particular contact role + public function getNumberOfChildren() { + + $query = "SELECT count(*) childCount FROM Resource WHERE acquisitionTypeID = '" . $this->acquisitionTypeID . "';"; + + $result = $this->db->processQuery($query, 'assoc'); + + return $result['childCount']; + } + + public static function getAcquisitionTypeID($type) { + $object = new AcquisitionType(); + $query = "SELECT acquisitionTypeID FROM AcquisitionType WHERE shortName = '" . $type . "';"; + + $result = $object->db->processQuery($query, 'assoc'); + + if (count($result) == 0) { + $object->shortName = $type; + $object->save(); + $id = $object->acquisitionTypeID; + } else { + $id = $result[0]; + } + return $id; + } } diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index cbe9cb6..2d33a0b 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -190,7 +190,7 @@ private function sendSparqlQuery($query){ */ - private function UriToGokbId($uri) + public function UriToGokbId($uri) { $cut = explode('/', $uri); $nb = count($cut); diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index e042cda..4e35228 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -11,6 +11,8 @@ include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/ResourceOrganizationLink.php"; +//include_once $_SERVER['DOCUMENT_ROOT'] . "resources/directory.php"; + class ImportTool { private $config; @@ -96,7 +98,6 @@ public function addResource($datas, $identifiers) { ****************************************/ if ($hasToBeInserted) { $res = $res_tmp->getNewInitializedResource(); - echo "DEBUG _ Resource insertion !!
    "; //Resource treatment foreach ($datas as $key => $value) { @@ -133,9 +134,7 @@ public function addResource($datas, $identifiers) { if ($aliases != null){ $this->aliasesTreatment($aliases, $res->resourceID); } - - - + //Parent treatment if ($parentName != null) { $parentID = $this->parentTreatment($parentName); @@ -145,10 +144,8 @@ public function addResource($datas, $identifiers) { if ($org != null) { // Do we have to create an organization or attach the resource to an existing one? $this->organizationTreatment($org, $res->resourceID); } - } else { - echo "DEBUG _ Resource not inserted !
    "; - } - echo 'DEBUG_ AddResource finished'; + self::$inserted++; + } self::$row++; } @@ -312,7 +309,6 @@ private function organizationTreatment($organizations, $resourceID) { $organizationID = false; // Search if such organization already exists $organizationExists = $organization->alreadyExists($orgName); - $parentID = null; if (!$organizationExists) { // If not, create it $organization->shortName = $orgName; diff --git a/admin/classes/domain/ResourceFormat.php b/admin/classes/domain/ResourceFormat.php index 4f8475e..bc40a82 100644 --- a/admin/classes/domain/ResourceFormat.php +++ b/admin/classes/domain/ResourceFormat.php @@ -61,7 +61,28 @@ public function getNumberOfChildren(){ } - +public static function getResourceFormatID($format) { + $object = new ResourceFormat(); + + $query = "SELECT resourceFormatID " + . "FROM ResourceFormat " + . "WHERE upper(shortName) = '" . str_replace("'", "''", strtoupper($format)) . "'"; + $result = $object->db->processQuery($query); + + if (count($result) == 0){ //this format doesn't exist, we create it + $object->shortName = $format; + $object->save(); + $id = $object->resourceFormatID; + + } else { + $id = $result[0]; + } + return $id; + + + +} + } diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index b81b715..d38f229 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -2,6 +2,8 @@ getNbTipps($recordDetails); $datas = array(); -$identifiers = array("gokb" => $_POST['id']); +$identifiers = array(); $recordName = $gokbTool->getResourceName($recordDetails); $datas['titleText'] = $recordName; -$string = ""; if ($_POST['type'] == 'package') { @@ -23,102 +24,160 @@ $acqID = AcquisitionType::getAcquisitionTypeID($recordDetails->{"paymentType"}); if ($acqID != null) { $datas["acquisitionTypeID"] = $acqID; - $string .= "insertion de datas[acquisitionTypeID] = " . $acqID . "
    "; } - + //Organization: + $platform = (string)$recordDetails->{"nominalPlatform"}; $datas['organization'] = array( - "platform" => $recordDetails->{"nominalPlatform"}, - "provider" => $recordDetails->{"nominalProvider"} + "platform" => $platform, + "provider" => (string)$recordDetails->{"nominalProvider"} ); - $string .= "insertion organizations "; + //Alternate Names + $variantName = $recordDetails->{'variantNames'}; + $variants = $variantName->children(); + if (count($variants) > 0) { + $datas['alias']['alternate name'] = array(); + foreach ($variants as $key => $name) { + array_push($datas['alias']['alternate name'], (string) $name); + } + } - //ensemble des tipps (boucle) - //$tippsDatas = array(); + $identifiers['gokb']=$_POST['id']; -} else { //import title; - + $importTool->addResource($datas, $identifiers); + + //TIPPs (titles included in package) + $tipps = $recordDetails->{'TIPPs'}; + $tippsList = $tipps->children(); + + foreach ($tippsList as $tipp) { + echo "DEBUG _ breakpoint to check"; + unset($datas); + unset($identifiers); + + //package + $datas['parentResource'] = $recordName; + + //Resource Format + $format = (string) $tipp->{'medium'}; + $datas['resourceFormatID'] = ResourceFormat::getResourceFormatID($format); + + //Resource name + $title = $tipp->{'title'}; + $datas['titleText'] = (string) $title->{'name'}; + + //Resource identifiers + $ids = $title->{'identifiers'}->children(); + $identifiers = $gokbTool->createIdentifiersArrayToImport($ids); + + $titleAttr = $title->attributes(); + $gokbTitleID = $titleAttr['id']; + $gokbID = "org.gokb.cred.TitleInstance:".$gokbTitleID; + $identifiers ['gokb'] = $gokbID; + + //Organization/platform + $datas['organization']['platform'] = $platform; + + //URL + $datas['resourceURL'] = (string)$tipp{'url'}; + + //coverage + $covAttr = $tipp->{'coverage'}->attributes(); + $covText = ""; + foreach ($covAttr as $key=>$value){ + if ($value != ''){ + $covText .= $key."= ".$value."; "; + } + } + $datas['coverageText'] = $covText; + + $importTool->addResource($datas, $identifiers); + + + } +} else { //import single title; //Organization $org = $recordDetails->{"publisher"}->{'name'}; if ($org != NULL) { $datas['organization'] = array("publisher" => $org); - $string .= "insertion de datas['organization'] = " . $org . "
    "; } - //ResourceType - $datas['resourceType'] = (string) $recordDetails->{'medium'} ;//TODO _ ID _ create type if needed - - + //ResourceType + $datas['resourceType'] = (string) $recordDetails->{'medium'}; + + //Identifiers _ URL ? $xml = $recordDetails->{'identifiers'}; $idToKeep = $xml->children(); -// foreach ($xmlIDs as $key => $value) { -// $tmp = $value->attributes(); -// $identifiers["$tmp[0]"] = (string) $tmp[1]; -// $string .= "insertion de identifiers[" . $tmp[0] . "] = " . $tmp[1] . "
    "; -// } - - //History / Aliases $variantName = $recordDetails->{'variantNames'}; $variants = $variantName->children(); - - if (count($variants) > 0){ - $datas['alias']['alternate name']=array(); + + if (count($variants) > 0) { + $datas['alias']['alternate name'] = array(); foreach ($variants as $key => $name) { array_push($datas['alias']['alternate name'], (string) $name); } } - + $history = $recordDetails->{'history'}; $events = $history->children(); - - // if ($history != null){ //comparer date pour identifiers, (publishedTO, event->date) - if (count($events) > 0){ - $datas['alias'] ['name change']= array(); + + // if ($history != null){ //comparer date pour identifiers, (publishedTO, event->date) + if (count($events) > 0) { + $datas['alias'] ['name change'] = array(); $events = $history->children(); $checkDate = false; $date = 0; - if ($recordDetails->{'publishedTo'} != null && $recordDetails->{'publishedTo'} != ''){ + if ($recordDetails->{'publishedTo'} != null && $recordDetails->{'publishedTo'} != '') { $checkDate = true; - $date =(string) $recordDetails->{'publishedTo'} ; + $date = (string) $recordDetails->{'publishedTo'}; $date = $gokbTool->convertXmlDateToDateTime($date); } else { $date = new DateTime(); } - - foreach ($events as $event) { - $eventDate = (string) $event->{'date'}; - $eventDate = $gokbTool->convertXmlDateToDateTime($eventDate); - $from =$event->{'from'}->{'title'}; - $to = $event->{'to'}->{'title'}; - if ($from != $recordName){ - array_push($datas['alias'] ['name change'], (string)$from); - } - if ($to != $recordName){ - array_push($datas['alias'] ['name change'], (string)$to); - } - - if ($date < $eventDate){ - $date = $eventDate; - $id_tmp = $event->{'to'}->{'identifiers'}; - $idToKeep = $id_tmp->children(); - } - + + foreach ($events as $event) { + $eventDate = (string) $event->{'date'}; + $eventDate = $gokbTool->convertXmlDateToDateTime($eventDate); + $from = $event->{'from'}->{'title'}; + $to = $event->{'to'}->{'title'}; + if ($from != $recordName) { + array_push($datas['alias'] ['name change'], (string) $from); + } + if ($to != $recordName) { + array_push($datas['alias'] ['name change'], (string) $to); + } + + if ($date < $eventDate) { + $date = $eventDate; + $id_tmp = $event->{'to'}->{'identifiers'}; + $idToKeep = $id_tmp->children(); + } } - } - + $identifiers = $gokbTool->createIdentifiersArrayToImport($idToKeep); + $identifiers['gokb'] = $_POST['id']; + - $importTool->addResource($datas, $identifiers); +} - return $string; +print "

    Results

    "; +print "

    " . (ImportTool::getNbRow()) . " rows have been processed. " . ImportTool::getNbInserted() . " rows have been inserted.

    "; +print "

    " . ImportTool::getNbParentInserted() . " parents have been created. " . ImportTool::getNbParentAttached() . " resources have been attached to an existing parent.

    "; +print "

    " . ImportTool::getNbOrganizationsInserted() . " organizations have been created"; +if (count(ImportTool::getArrayOrganizationsCreated()) > 0) { + print " (" . implode(',', ImportTool::getArrayOrganizationsCreated()) . ")"; } +print ". " . ImportTool::getNbOrganizationsAttached() . " resources have been attached to an existing organization.

    "; ?> +
    + -Import OK !! \ No newline at end of file + +
    From 5b9bafbe063701c097c0b8388178ada46f5349a3 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Mon, 3 Aug 2015 16:06:56 +0200 Subject: [PATCH 16/32] Add organizations hierarchy (org module) + debug import package(not finished) --- admin/classes/domain/AcquisitionType.php | 2 +- admin/classes/domain/ImportTool.php | 83 +++++++++++++++++------- ajax_processing/importFromGOKb.php | 1 - 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/admin/classes/domain/AcquisitionType.php b/admin/classes/domain/AcquisitionType.php index b8ebcdb..7eff041 100644 --- a/admin/classes/domain/AcquisitionType.php +++ b/admin/classes/domain/AcquisitionType.php @@ -63,7 +63,7 @@ public function getNumberOfChildren() { public static function getAcquisitionTypeID($type) { $object = new AcquisitionType(); - $query = "SELECT acquisitionTypeID FROM AcquisitionType WHERE shortName = '" . $type . "';"; + $query = "SELECT acquisitionTypeID FROM AcquisitionType WHERE upper(shortName) = '" . str_replace("'", "''", strtoupper($type)) . "'"; $result = $object->db->processQuery($query, 'assoc'); diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index 4e35228..fc7c9b8 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -9,7 +9,7 @@ include_once $_SERVER['DOCUMENT_ROOT'] . "organizations/admin/classes/domain/Organization.php"; include_once $_SERVER['DOCUMENT_ROOT'] . "organizations/admin/classes/domain/OrganizationRole.php"; include_once $_SERVER['DOCUMENT_ROOT'] . "resources/admin/classes/domain/ResourceOrganizationLink.php"; - +include_once $_SERVER['DOCUMENT_ROOT'] . "organizations/admin/classes/domain/OrganizationHierarchy.php"; //include_once $_SERVER['DOCUMENT_ROOT'] . "resources/directory.php"; @@ -88,14 +88,14 @@ public function addResource($datas, $identifiers) { $htmlContent = ''; //TODO _ DEBUG _ Displaying after select - /******************************************* - ** Has the resource to be inserted ? ** - *******************************************/ + /* * ***************************************** + * * Has the resource to be inserted ? ** + * ***************************************** */ $hasToBeInserted = $this->hasResourceToBeInserted($datas, $identifiers); - /**************************************** - ** Datas insertion ** - ****************************************/ + /* * ************************************** + * * Datas insertion ** + * ************************************** */ if ($hasToBeInserted) { $res = $res_tmp->getNewInitializedResource(); @@ -119,19 +119,19 @@ public function addResource($datas, $identifiers) { break; } } - + //ResourceType treatment - if($resourceType != NULL){ - $res->resourceTypeID = ResourceType::getResourceTypeID((string)$resourceType); + if ($resourceType != NULL) { + $res->resourceTypeID = ResourceType::getResourceTypeID((string) $resourceType); } - + $res->save(); //Resource identifiers treatment $res->setIdentifiers($identifiers); //Aliases treatment (history name change/ variant name) //TODO - if ($aliases != null){ + if ($aliases != null) { $this->aliasesTreatment($aliases, $res->resourceID); } @@ -145,7 +145,7 @@ public function addResource($datas, $identifiers) { $this->organizationTreatment($org, $res->resourceID); } self::$inserted++; - } + } self::$row++; } @@ -217,8 +217,6 @@ private function parentTreatment($parentName) { } return $parentID; - - } // ------------------------------------------------------------------------- @@ -299,6 +297,7 @@ private function organizationTreatment($organizations, $resourceID) { if ($organizations['platform']) { $platformName = $organizations['platform']; $providerName = $organizations['provider']; + $this->setOrganizationsHierarchy($platformName, $providerName); } } // If we do not use the Organizations module @@ -344,7 +343,7 @@ private function organizationTreatment($organizations, $resourceID) { private function createOrgWithOrganizationModule($orgName) { $dbName = $this->config->settings->organizationsDatabaseName; $loginID = $_SESSION['loginID']; - + $organization = new Organization(); $query = "INSERT INTO $dbName.Organization SET createDate=NOW(), createLoginID='$loginID', name='" . mysql_escape_string($orgName) . "'"; try { @@ -359,11 +358,49 @@ private function createOrgWithOrganizationModule($orgName) { } // ------------------------------------------------------------------------- - + private function setOrganizationsHierarchy($orgName, $parentOrgName) { + $orgID = null; + $parentID = null; + $relation = new OrganizationHierarchy(); + $dbName = $this->config->settings->organizationsDatabaseName; + + $query = "SELECT organizationID " + . "FROM $dbName.Organization " + . "WHERE upper(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; + $result = $relation->db->processQuery($query); + + if (count($result) > 0) + $orgID = $result[0]; + + + $query = "SELECT organizationID " + . "FROM $dbName.Organization " + . "WHERE upper(name) = '" . str_replace("'", "''", strtoupper($parentOrgName)) . "'"; + $result = $relation->db->processQuery($query); + if (count($result) > 0) + $parentID = $result[0]; + + + if ( ($orgID != null) && + ($parentID != null) && + ( !($relation->relationExists($orgID, $parentID))) ) { +// $relation->organizationID = $orgID; +// $relation->parentOrganizationID = $parentID; +// $relation->save(); + //$query = "INSERT INTO $dbName.OrganizationHierarchy SET `organizationID`=$orgID, `parentOrganizationID`=$parentID ;"; + $query = "INSERT INTO $dbName.OrganizationHierarchy VALUES ($orgID, $parentID);"; + $relation->db->processQuery($query); + + $debug = "breakpoint"; + } + } + +// ------------------------------------------------------------------------- + private function aliasesTreatment($aliases, $resourceID) { - foreach ($aliases as $aliasType => $aliasArray){ - $typeID = AliasType::getAliasTypeID((string)$aliasType); - foreach ($aliasArray as $alias){ + foreach ($aliases as $aliasType => $aliasArray) { + $typeID = AliasType::getAliasTypeID((string) $aliasType); + foreach ($aliasArray as $alias) { $al = new Alias(); $al->resourceID = $resourceID; $al->aliasTypeID = $typeID; @@ -372,8 +409,9 @@ private function aliasesTreatment($aliases, $resourceID) { } } } + // ------------------------------------------------------------------------- - /******************************** + /* * ****************************** * Accessors * * ****************************** */ public static function getNbRow() { @@ -416,9 +454,10 @@ public static function getNbOrganizationsAttached() { } // ------------------------------------------------------------------------- - public static function getArrayOrganizationsCreated(){ + public static function getArrayOrganizationsCreated() { return self::$arrayOrganizationsCreated; } + } ?> \ No newline at end of file diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index d38f229..9bec2da 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -53,7 +53,6 @@ $tippsList = $tipps->children(); foreach ($tippsList as $tipp) { - echo "DEBUG _ breakpoint to check"; unset($datas); unset($identifiers); From a6a1723ab5da3277cf6912bc9bac276a39117360 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Mon, 3 Aug 2015 17:02:50 +0200 Subject: [PATCH 17/32] debug history.js (not completed) --- admin/classes/domain/ImportTool.php | 1 - js/KBSearch.js | 470 +++++++++++++++------------- 2 files changed, 245 insertions(+), 226 deletions(-) diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index fc7c9b8..d031342 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -391,7 +391,6 @@ private function setOrganizationsHierarchy($orgName, $parentOrgName) { $query = "INSERT INTO $dbName.OrganizationHierarchy VALUES ($orgID, $parentID);"; $relation->db->processQuery($query); - $debug = "breakpoint"; } } diff --git a/js/KBSearch.js b/js/KBSearch.js index 565ed69..fb39eee 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -1,68 +1,69 @@ /** -* Send a SPARQL request to filter results with criteria -* @param: s_name string content of "Name" field (new resource form) -* @param: s_pub string content of "Provider" field (new resource form) -* @param: s_type int searchType (0 = all ; -1 = packages only; 1=issues only) -* -* @return: nothing but display results thanks to ajax and php treatment -*/ -function allResults(s_name, s_pub, s_type){ - - $.ajax({ - type: "POST", - url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", - cache: false, - data: {name:s_name, issn:'', publisher:s_pub, type:s_type, paginate:true}, - success: function(res) { - document.getElementById("TB_ajaxContent").innerHTML = ""; - $('#TB_ajaxContent').append(res); - } - }); - window.history.pushState({funcName:'allResults', param:[s_name,s_pub,s_type]}, 'test', null); + * Send a SPARQL request to filter results with criteria + * @param: s_name string content of "Name" field (new resource form) + * @param: s_pub string content of "Provider" field (new resource form) + * @param: s_type int searchType (0 = all ; -1 = packages only; 1=issues only) + * + * @return: nothing but display results thanks to ajax and php treatment + */ +function allResults(s_name, s_pub, s_type) { + + $.ajax({ + type: "POST", + url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {name: s_name, issn: '', publisher: s_pub, type: s_type, paginate: true}, + success: function (res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + }); + window.history.pushState({funcName: 'allResults', param:[s_name, s_pub, s_type]}, 'test', null); } /*******************************************************************************************************/ /** -* Send an OAI GetRecord request and display results -* @param: s_type string type of the resource (title or package) -* @param: s_gokbID string identifier of the searched resource -* -* @return: nothing but display results thanks to ajax and treatment -*/ -function getDetails(s_type, s_gokbID){ - $.ajax({ - type: "POST", - url: "ajax_htmldata.php?action=getGokbResourceDetails&height=503&width=775&resourceID=&modal=true", - cache: false, - data: {type:s_type, id:s_gokbID}, - success: function(res) { - document.getElementById("TB_ajaxContent").innerHTML = ""; - $('#TB_ajaxContent').append(res); - } - }); - window.history.pushState({funcName:'getDetails', param:{s_type,s_gokbID}}, 'test', null); + * Send an OAI GetRecord request and display results + * @param: s_type string type of the resource (title or package) + * @param: s_gokbID string identifier of the searched resource + * + * @return: nothing but display results thanks to ajax and treatment + */ +function getDetails(s_type, s_gokbID) { + $.ajax({ + type: "POST", + url: "ajax_htmldata.php?action=getGokbResourceDetails&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {type: s_type, id: s_gokbID}, + success: function (res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + }); + window.history.pushState({funcName: 'getDetails', param: [s_type, s_gokbID]}, 'test', null); + } /*******************************************************************************************************/ -function select(s_type, s_gokbID){ - console.debug("fonction select("+s_type+","+s_gokbID+")"); - $.ajax({ - type: "POST", - url: "ajax_processing.php?action=importFromGOKb&height=503&width=775&resourceID=&modal=true", - cache: false, - data: {type:s_type, id:s_gokbID}, - success: function(res){ - console.debug("SELECT: ajax ok"); - document.getElementById("TB_ajaxContent").innerHTML = ""; - $('#TB_ajaxContent').append(res); - } +function select(s_type, s_gokbID) { + console.debug("fonction select(" + s_type + "," + s_gokbID + ")"); + $.ajax({ + type: "POST", + url: "ajax_processing.php?action=importFromGOKb&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {type: s_type, id: s_gokbID}, + success: function (res) { + console.debug("SELECT: ajax ok"); + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } - }); + }); - //TODO history + //TODO history @@ -70,195 +71,214 @@ function select(s_type, s_gokbID){ /*******************************************************************************************************/ /** -* Manage the details tabs (global detail or TIPPs) -* @param: element_nb int index of selected tab -* -* @return: nothing but display the right content -*/ -function loadDetailsContent(element_nb){ - - var tabs=document.getElementById("detailsTabs").getElementsByTagName("li"); - var divs = document.getElementById("detailsContainer").getElementsByTagName("div"); - - for (var i = 0; i < tabs.length; i++) { - if(i == element_nb){ - tabs[i].className="selected"; - divs[i].className=""; - }else{ - tabs[i].className=""; - divs[i].className="invisible"; - } - - } - + * Manage the details tabs (global detail or TIPPs) + * @param: element_nb int index of selected tab + * + * @return: nothing but display the right content + */ +function loadDetailsContent(element_nb) { + + var tabs = document.getElementById("detailsTabs").getElementsByTagName("li"); + var divs = document.getElementById("detailsContainer").getElementsByTagName("div"); + + for (var i = 0; i < tabs.length; i++) { + if (i == element_nb) { + tabs[i].className = "selected"; + divs[i].className = ""; + } else { + tabs[i].className = ""; + divs[i].className = "invisible"; + } + + } + } /*******************************************************************************************************/ /** -* Manage pagination of results -* @param: page int the page of results to display -*/ -function iterator(page){ - var divId = $("#currentDiv").val(); - var old = parseInt($("#currentPage").val()); - var pagination = document.getElementById("pageIterator").getElementsByTagName("li"); - - var lines = document.getElementById(divId).getElementsByTagName("tr"); - var nbTipps = lines.length; - var nbPages = pagination.length - 4; - - document.getElementById('currentPage').value = page; - - var itStart= page*10; - var stop = itStart+10; - - //Display/hide prev and next buttons - if (itStart == 0) { document.getElementById('previousTipps').className='invisible'; } - else { document.getElementById('previousTipps').className=''; } - - if (page == (nbPages-1)){ document.getElementById('nextTipps').className='invisible'; } - else { document.getElementById('nextTipps').className=''; } - - if (stop >= nbTipps) { stop=nbTipps; } - - - //hide previous page - var disableStart = old*10; - var disableStop = (old+1)*10; - if (disableStop > nbTipps) disableStop=nbTipps; - - console.debug("disable lines "+disableStart+" to "+disableStop); - for (var i = disableStart; i < disableStop; i++) { - lines[i].className="invisible"; - } - - //display current page - console.debug("display lines "+itStart+" to "+stop); - for (var i=itStart; i truncate - if (nbPages > 13){ - //case 1: current page is at the beginning - if (page < 7){ - document.getElementById("beginning").className = "invisible"; - document.getElementById("end").className = ""; - for (var i=0; inbPages-6) { - document.getElementById("beginning").className = ""; - document.getElementById("end").className = "invisible"; - for (var i = 0; inbPages-14) pagination[i+2].className = ""; - else pagination[i+2].className='invisible'; - } - } - //case 3: current page is in the middle - else { - document.getElementById("beginning").className = ""; - document.getElementById("end").className = ""; - for (var i = 0; i= page-6) && (i <= page+6)) pagination[i+2].className = ""; - else pagination[i+2].className='invisible'; - } - } - } - pagination[page+2].className = "active"; + * Manage pagination of results + * @param: page int the page of results to display + */ +function iterator(page) { + var divId = $("#currentDiv").val(); + var old = parseInt($("#currentPage").val()); + var pagination = document.getElementById("pageIterator").getElementsByTagName("li"); + + var lines = document.getElementById(divId).getElementsByTagName("tr"); + var nbTipps = lines.length; + var nbPages = pagination.length - 4; + + document.getElementById('currentPage').value = page; + + var itStart = page * 10; + var stop = itStart + 10; + + //Display/hide prev and next buttons + if (itStart == 0) { + document.getElementById('previousTipps').className = 'invisible'; + } + else { + document.getElementById('previousTipps').className = ''; + } + + if (page == (nbPages - 1)) { + document.getElementById('nextTipps').className = 'invisible'; + } + else { + document.getElementById('nextTipps').className = ''; + } + + if (stop >= nbTipps) { + stop = nbTipps; + } + + + //hide previous page + var disableStart = old * 10; + var disableStop = (old + 1) * 10; + if (disableStop > nbTipps) + disableStop = nbTipps; + + console.debug("disable lines " + disableStart + " to " + disableStop); + for (var i = disableStart; i < disableStop; i++) { + lines[i].className = "invisible"; + } + + //display current page + console.debug("display lines " + itStart + " to " + stop); + for (var i = itStart; i < stop; i++) { + lines[i].className = ""; + } + + //Display up to 13 pages number, if there are more --> truncate + if (nbPages > 13) { + //case 1: current page is at the beginning + if (page < 7) { + document.getElementById("beginning").className = "invisible"; + document.getElementById("end").className = ""; + for (var i = 0; i < nbPages; i++) { + if (i < 13) + pagination[i + 2].className = ''; + else + pagination[i + 2].className = 'invisible'; + } + } + //case 2: current page is at the end + else if (page > nbPages - 6) { + document.getElementById("beginning").className = ""; + document.getElementById("end").className = "invisible"; + for (var i = 0; i < nbPages; i++) { + if (i > nbPages - 14) + pagination[i + 2].className = ""; + else + pagination[i + 2].className = 'invisible'; + } + } + //case 3: current page is in the middle + else { + document.getElementById("beginning").className = ""; + document.getElementById("end").className = ""; + for (var i = 0; i < nbPages; i++) { + if ((i >= page - 6) && (i <= page + 6)) + pagination[i + 2].className = ""; + else + pagination[i + 2].className = 'invisible'; + } + } + } + pagination[page + 2].className = "active"; } /*******************************************************************************************************/ /** -* Manage the next/prev button of pagination -* @param: op char next = '+', prev = '-' -*/ -function navIterator(op){ - var current = parseInt($("#currentPage").val()); - var param = 0; - - switch(op){ - case '+': - param = current+1; - break; - case '-': - param = current-1; - break; - default: - break; - } - - iterator(param); + * Manage the next/prev button of pagination + * @param: op char next = '+', prev = '-' + */ +function navIterator(op) { + var current = parseInt($("#currentPage").val()); + var param = 0; + + switch (op) { + case '+': + param = current + 1; + break; + case '-': + param = current - 1; + break; + default: + break; + } + + iterator(param); } /*******************************************************************************************************/ /** -* Recall of the search function when the button 'back' is pressed -* @param: s_name string Name field content -* @param: s_pub string Provoder field content -*/ -function searchGokbBack(s_name,s_pub){ - console.debug("appel à searchGokbBack"); - $.ajax({ - type: "POST", - url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", - cache: false, - data: {name:s_name, issn:"", publisher:s_pub, type:0}, - success: function(res) { - document.getElementById("TB_ajaxContent").innerHTML = ""; - $('#TB_ajaxContent').append(res); - } - }); - window.history.pushState(null,null,null); - window.history.pushState({funcName:'searchGokbBack', param:[s_name, s_pub]}, 'test', null); + * Recall of the search function when the button 'back' is pressed + * @param: s_name string Name field content + * @param: s_pub string Provoder field content + */ +function searchGokbBack(s_name, s_pub) { + console.debug("appel à searchGokbBack"); + $.ajax({ + type: "POST", + url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {name: s_name, issn: "", publisher: s_pub, type: 0}, + success: function (res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + }); + window.history.pushState(null, null, null); + window.history.pushState({funcName: 'searchGokbBack', param: [s_name, s_pub]}, 'test', null); } /*******************************************************************************************************/ /** -* Manage the 'back' button -*/ -function goBack(){ - //Get the previous state - window.history.go(-1); - var myState = window.history.state; - - if (myState == null){ //Display the "Add New resource form" - console.debug("No previous state"); - $.ajax({ - type: "POST", - url: "ajax_forms.php?action=getNewResourceForm&height=503&width=775&resourceID=&modal=true", - cache: false, - - success: function(res){ - document.getElementById("TB_ajaxContent").innerHTML = ""; - $('#TB_ajaxContent').append(res); - } - }); - - }else{ //display the previous screen by calling the last function - var toCall = myState.funcName; - var funcParam = myState.param; - var max = funcParam.length; - - var toDo = toCall+"("; - - for (var i=0; i + + + "; ?> + \ No newline at end of file diff --git a/ajax_htmldata/getKBSearchResults.php b/ajax_htmldata/getKBSearchResults.php index 8a348c3..2b1b33a 100644 --- a/ajax_htmldata/getKBSearchResults.php +++ b/ajax_htmldata/getKBSearchResults.php @@ -1,92 +1,94 @@
    -
    - Add new resource - Search results -
    - + Add new resource - Search results +
    + searchOnGokb($_POST['name'], $_POST['issn'], $_POST['publisher'], $_POST['type']); -$nb_packages = count($results[0]); -$nb_titles = count($results[1]); -$isPaginated = ((isset($_POST['paginate'])) && ($_POST['paginate'] == true)); + $results = $tool->searchOnGokb($_POST['name'], $_POST['issn'], $_POST['publisher'], $_POST['type']); + $nb_packages = count($results[0]); + $nb_titles = count($results[1]); + $isPaginated = ((isset($_POST['paginate'])) && ($_POST['paginate'] == true)); //Display packages results -if ($nb_packages > 0){ - echo "
    "; - echo " Packages View all packages results
    "; + if ($nb_packages > 0) { + echo "
    "; + echo " Packages View all packages results
    "; - echo ''; - foreach ($results[0] as $key => $value) { - echo "'; - echo ''; - echo ""; - } - echo '
    "; - echo ' - '.$value; - echo '
    '; - echo "
    "; -} + echo ''; + foreach ($results[0] as $key => $value) { + echo "'; + echo ''; + echo ""; + } + echo '
    "; + echo ' - ' . $value; + echo '
    '; + echo "
    "; + } //Display titles results -if ($nb_titles > 0){ - echo "
    "; - echo ' Titles View all titles results
    '; + if ($nb_titles > 0) { + echo "
    "; + echo ' Titles View all titles results
    '; - echo ''; - foreach ($results[1] as $key => $value) { - echo "'; - echo ''; - echo ""; - } - echo '
    '; + echo ''; + foreach ($results[1] as $key => $value) { + echo "'; + echo ''; + echo ""; + } + echo '
    '; - echo "
    "; -} else { - if (!$isPaginated) echo "No results, please check your search fields
    "; -} + echo "
    "; + } else { + if (!$isPaginated) + echo "No results, please check your search fields
    "; + } //Display pagination -if ($isPaginated) { - switch ($_POST['type']) { - case -1: - $resType=0; - $divId = "div_packagesResults"; - break; - case 1: - $resType = 1; - $divId = "div_titlesResults"; - break; - default: - break; - } - echo paginate(count($results[$resType]), "$divId"); - echo ""; -} - - -?> -
    + if ($isPaginated) { + switch ($_POST['type']) { + case -1: + $resType = 0; + $divId = "div_packagesResults"; + break; + case 1: + $resType = 1; + $divId = "div_titlesResults"; + break; + default: + break; + } + echo paginate(count($results[$resType]), "$divId"); + echo ""; + } + ?> +
    - - -
    + + +
    diff --git a/css/gokb.search.css b/css/gokb.search.css index d53746b..7930897 100644 --- a/css/gokb.search.css +++ b/css/gokb.search.css @@ -134,6 +134,10 @@ text-align: right; } +#span_select{ + float: right; +} + /******************************************* **** getPagination.php **** *******************************************/ @@ -182,4 +186,4 @@ .search_nav_button{ margin-top: 5px; -} \ No newline at end of file +} diff --git a/js/KBSearch.js b/js/KBSearch.js index fb39eee..2f4c231 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -20,6 +20,7 @@ function allResults(s_name, s_pub, s_type) { } }); window.history.pushState({funcName: 'allResults', param:[s_name, s_pub, s_type]}, 'test', null); + console.debug("pushState(allResults("+s_name+","+ s_pub+","+ s_type+"))"); } /*******************************************************************************************************/ @@ -43,11 +44,12 @@ function getDetails(s_type, s_gokbID) { } }); window.history.pushState({funcName: 'getDetails', param: [s_type, s_gokbID]}, 'test', null); - + console.debug("pushState(getDetails("+ s_type+","+s_gokbID+"))"); } /*******************************************************************************************************/ -function select(s_type, s_gokbID) { +function selectResource(s_type, s_gokbID) { + alert("FONCTION SELECT !! we are in!"); console.debug("fonction select(" + s_type + "," + s_gokbID + ")"); $.ajax({ type: "POST", @@ -62,7 +64,7 @@ function select(s_type, s_gokbID) { }); - + //TODO history @@ -77,7 +79,7 @@ function select(s_type, s_gokbID) { * @return: nothing but display the right content */ function loadDetailsContent(element_nb) { - + console.debug("loadDetailsContent"); var tabs = document.getElementById("detailsTabs").getElementsByTagName("li"); var divs = document.getElementById("detailsContainer").getElementsByTagName("div"); @@ -233,7 +235,11 @@ function searchGokbBack(s_name, s_pub) { } }); window.history.pushState(null, null, null); + console.debug("pushState NULL"); + window.history.pushState({funcName: 'searchGokbBack', param: [s_name, s_pub]}, 'test', null); + console.debug("pushState searchGokbBack("+s_name+","+s_pub+")"); + } /*******************************************************************************************************/ From edc66b96a49f3f1286827727efeb0d4faf36a598 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Tue, 4 Aug 2015 16:28:44 +0200 Subject: [PATCH 19/32] UI displaying improvement --- admin/classes/domain/GOKbTools.php | 15 +- admin/classes/domain/ImportTool.php | 10 - ajax_htmldata/getGokbResourceDetails.php | 102 ++-- ajax_processing/importFromGOKb.php | 6 +- css/gokb.search.css | 22 +- js/KBSearch.js | 27 +- js/forms/resourceNewForm.js | 562 ++++++++++++----------- 7 files changed, 380 insertions(+), 364 deletions(-) diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 2d33a0b..bd90bff 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -291,16 +291,21 @@ function displayRecordTipps($record, $recordType){ $type = 'package'; } - $string .= " "; + $string .= "
    "; +// $string.= "
      "; foreach ($tipps->children() as $child) { $resource = $child->{$type}; $resourceAttr = $resource->attributes(); - - $string .= "
    "; + $gokbID = $this->UriToGokbId("$type".'s/'.$resourceAttr[0]); + + $string .= '"; } - + +// $string .= ""; $string .= "
    "; return $string; diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index d031342..6815ff9 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -427,21 +427,11 @@ public static function getNbParentInserted() { return self::$parentInserted; } -// ------------------------------------------------------------------------- - public static function incrementNbParentInserted() { - self::$parentInserted++; - } - // ------------------------------------------------------------------------- public static function getNbParentAttached() { return self::$parentAttached; } -// ------------------------------------------------------------------------- - public static function incrementNbParentAttached() { - self::$parentAttached++; - } - // ------------------------------------------------------------------------- public static function getNbOrganizationsInserted() { return self::$organizationsInserted; diff --git a/ajax_htmldata/getGokbResourceDetails.php b/ajax_htmldata/getGokbResourceDetails.php index aa4abb3..ba871e8 100644 --- a/ajax_htmldata/getGokbResourceDetails.php +++ b/ajax_htmldata/getGokbResourceDetails.php @@ -4,58 +4,62 @@ getDetails($_POST['type'], $_POST['id']); +$nbTipps = $tool->getNbTipps($record); +?> - $tool = GOKbTools::getInstance(); - $record = $tool->getDetails($_POST['type'], $_POST['id']); - $nbTipps = $tool->getNbTipps($record); +
    +
    + + + details for + getResourceName($record); ?> + +
    -?> +
    +
      +
    • Details
    • +
    • + +
    • +
    +
    + +
    +
    + displayRecord($record); ?> +
    + +
    + + +
    + + + -
    -
    - - - details for - getResourceName($record);?> - -
    - -
    -
      -
    • Details
    • -
    • - -
    • -
    -
    - -
    -
    - displayRecord($record); ?> -
    - -
    - -
    - - - - - - "; ?> - -
    + +"; ?> + +
    \ No newline at end of file diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index 9bec2da..d6e601d 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -1,4 +1,6 @@ -
    Import from GOKb . php
    +
    + Import from GOKb +
    addResource($datas, $identifiers); } -print "

    Results

    "; +print "

    Results

    "; print "

    " . (ImportTool::getNbRow()) . " rows have been processed. " . ImportTool::getNbInserted() . " rows have been inserted.

    "; print "

    " . ImportTool::getNbParentInserted() . " parents have been created. " . ImportTool::getNbParentAttached() . " resources have been attached to an existing parent.

    "; print "

    " . ImportTool::getNbOrganizationsInserted() . " organizations have been created"; diff --git a/css/gokb.search.css b/css/gokb.search.css index 7930897..555fa21 100644 --- a/css/gokb.search.css +++ b/css/gokb.search.css @@ -1,6 +1,6 @@ -/******************************************* +/************************************** **** getKBSearchResults.php **** - *******************************************/ + **************************************/ .div_results{ margin-top: 10px; width: 745px; @@ -41,9 +41,9 @@ -/******************************************* +/**************************************** **** getGokbResourceDetails.php **** - *******************************************/ + ****************************************/ #resourceDetails{ width: 745px; @@ -93,7 +93,7 @@ #detailsContainer{ background: white; border:1px solid gray; - height:350px; + /* height:350px;*/ width:100%; padding:0; margin:0; @@ -138,9 +138,13 @@ float: right; } -/******************************************* +#tippsTable tr{ + height: 25px; +} + +/******************************** **** getPagination.php **** - *******************************************/ + ********************************/ #pageIterator { margin: 10px; text-align: center; @@ -176,9 +180,9 @@ -/******************************************* +/************************ **** Multiple **** - *******************************************/ + ************************/ .invisible{ display: none; diff --git a/js/KBSearch.js b/js/KBSearch.js index 2f4c231..a42fe04 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -19,8 +19,8 @@ function allResults(s_name, s_pub, s_type) { $('#TB_ajaxContent').append(res); } }); - window.history.pushState({funcName: 'allResults', param:[s_name, s_pub, s_type]}, 'test', null); - console.debug("pushState(allResults("+s_name+","+ s_pub+","+ s_type+"))"); + window.history.pushState({funcName: 'allResults', param: [s_name, s_pub, s_type]}, 'test', null); + console.debug("pushState(allResults(" + s_name + "," + s_pub + "," + s_type + "))"); } /*******************************************************************************************************/ @@ -35,7 +35,7 @@ function allResults(s_name, s_pub, s_type) { function getDetails(s_type, s_gokbID) { $.ajax({ type: "POST", - url: "ajax_htmldata.php?action=getGokbResourceDetails&height=503&width=775&resourceID=&modal=true", + url: "ajax_htmldata.php?action=getGokbResourceDetails&modal=true", cache: false, data: {type: s_type, id: s_gokbID}, success: function (res) { @@ -44,12 +44,11 @@ function getDetails(s_type, s_gokbID) { } }); window.history.pushState({funcName: 'getDetails', param: [s_type, s_gokbID]}, 'test', null); - console.debug("pushState(getDetails("+ s_type+","+s_gokbID+"))"); + console.debug("pushState(getDetails(" + s_type + "," + s_gokbID + "))"); } /*******************************************************************************************************/ function selectResource(s_type, s_gokbID) { - alert("FONCTION SELECT !! we are in!"); console.debug("fonction select(" + s_type + "," + s_gokbID + ")"); $.ajax({ type: "POST", @@ -64,7 +63,7 @@ function selectResource(s_type, s_gokbID) { }); - + //TODO history @@ -94,6 +93,12 @@ function loadDetailsContent(element_nb) { } + if (element_nb == 1) { + document.getElementById("paginationDiv").className = ""; + } else { + document.getElementById("paginationDiv").className = "invisible"; + } + } /*******************************************************************************************************/ @@ -188,6 +193,10 @@ function iterator(page) { pagination[i + 2].className = 'invisible'; } } + } else { + for (var i=0; i. -** -************************************************************************************************************************** -*/ - - $(document).ready(function(){ - - $(".submitResource").click(function () { - submitResource($(this).attr("id")); - }); - - $("#search").click(function(){ - searchGokb($('#titleText').val(), $('#ISSNText').val(), $('#providerText').val() ); - }); - - //do submit if enter is hit - $('#titleText').keyup(function(e) { - if(e.keyCode == 13) { - submitResource('save'); - } - }); - - - - //do submit if enter is hit - $('#providerText').keyup(function(e) { - if(e.keyCode == 13) { - submitResource('save'); - } - }); - - - - //do submit if enter is hit - $('#resourceURL').keyup(function(e) { - if(e.keyCode == 13) { - submitResource('save'); - } - }); - - //do submit if enter is hit - $('#resourceAltURL').keyup(function(e) { - if(e.keyCode == 13) { - submitResource('save'); - } - }); - - //do submit if enter is hit - $('#resourceFormatID').keyup(function(e) { - if(e.keyCode == 13) { - submitResource('save'); - } - }); - - - - //do submit if enter is hit - $('#resourceTypeID').keyup(function(e) { - if(e.keyCode == 13) { - submitResource('save'); - } - }); - + ************************************************************************************************************************** + ** CORAL Resources Module v. 1.2 + ** + ** Copyright (c) 2010 University of Notre Dame + ** + ** This file is part of CORAL. + ** + ** CORAL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + ** + ** CORAL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License along with CORAL. If not, see . + ** + ************************************************************************************************************************** + */ + +$(document).ready(function () { + + $(".submitResource").click(function () { + submitResource($(this).attr("id")); + }); + + $("#search").click(function () { + searchGokb($('#titleText').val(), $('#ISSNText').val(), $('#providerText').val()); + }); + + //do submit if enter is hit + $('#titleText').keyup(function (e) { + if (e.keyCode == 13) { + submitResource('save'); + } + }); - //do submit if enter is hit - $('#acquisitionTypeID').keyup(function(e) { - if(e.keyCode == 13) { - submitResource('save'); - } - }); + //do submit if enter is hit + $('#providerText').keyup(function (e) { + if (e.keyCode == 13) { + submitResource('save'); + } + }); - //check this name/title to make sure it isn't already being used - $("#titleText").keyup(function() { - $.ajax({ - type: "GET", - url: "ajax_processing.php", - cache: false, - async: true, - data: "action=getExistingTitle&name=" + $("#titleText").val(), - success: function(exists) { - if (exists == "0"){ - $("#span_error_titleText").html(""); - }else{ - $("#span_error_titleText").html("
    Warning: this name already exists."); - } - } - }); + //do submit if enter is hit + $('#resourceURL').keyup(function (e) { + if (e.keyCode == 13) { + submitResource('save'); + } + }); + + //do submit if enter is hit + $('#resourceAltURL').keyup(function (e) { + if (e.keyCode == 13) { + submitResource('save'); + } + }); + //do submit if enter is hit + $('#resourceFormatID').keyup(function (e) { + if (e.keyCode == 13) { + submitResource('save'); + } + }); - }); + //do submit if enter is hit + $('#resourceTypeID').keyup(function (e) { + if (e.keyCode == 13) { + submitResource('save'); + } + }); - $("#providerText").autocomplete('ajax_processing.php?action=getOrganizationList', { - minChars: 2, - max: 20, - mustMatch: false, - width: 223, - delay: 10, - matchContains: true, - formatItem: function(row) { - return "" + row[0] + ""; - }, - formatResult: function(row) { - return row[0].replace(/(<.+?>)/gi, ''); - } - }); + //do submit if enter is hit + $('#acquisitionTypeID').keyup(function (e) { + if (e.keyCode == 13) { + submitResource('save'); + } + }); - //once something has been selected, change the hidden input value - $("#providerText").result(function(event, data, formatted) { - $('#organizationID').val(data[1]); - }); + //check this name/title to make sure it isn't already being used + $("#titleText").keyup(function () { + $.ajax({ + type: "GET", + url: "ajax_processing.php", + cache: false, + async: true, + data: "action=getExistingTitle&name=" + $("#titleText").val(), + success: function (exists) { + if (exists == "0") { + $("#span_error_titleText").html(""); + } else { + $("#span_error_titleText").html("
    Warning: this name already exists."); + } + } + }); - //the following are all to change the look of the inputs when they're clicked - $('.changeDefaultWhite').live('focus', function(e) { - if (this.value == this.defaultValue){ - this.value = ''; - } - }); - $('.changeDefaultWhite').live('blur', function() { - if(this.value == ''){ - this.value = this.defaultValue; - } - }); + }); - - $('.changeInput').addClass("idleField"); - - $('.changeInput').live('focus', function() { - $(this).removeClass("idleField").addClass("focusField"); + $("#providerText").autocomplete('ajax_processing.php?action=getOrganizationList', { + minChars: 2, + max: 20, + mustMatch: false, + width: 223, + delay: 10, + matchContains: true, + formatItem: function (row) { + return "" + row[0] + ""; + }, + formatResult: function (row) { + return row[0].replace(/(<.+?>)/gi, ''); + } - if(this.value != this.defaultValue){ - this.select(); - } + }); - }); + //once something has been selected, change the hidden input value + $("#providerText").result(function (event, data, formatted) { + $('#organizationID').val(data[1]); + }); - $('.changeInput').live('blur', function() { - $(this).removeClass("focusField").addClass("idleField"); - }); - $('.changeAutocomplete').live('focus', function() { - if (this.value == this.defaultValue){ - this.value = ''; - } - }); + //the following are all to change the look of the inputs when they're clicked + $('.changeDefaultWhite').live('focus', function (e) { + if (this.value == this.defaultValue) { + this.value = ''; + } + }); + $('.changeDefaultWhite').live('blur', function () { + if (this.value == '') { + this.value = this.defaultValue; + } + }); - $('.changeAutocomplete').live('blur', function() { - if(this.value == ''){ - this.value = this.defaultValue; - } - }); - + $('.changeInput').addClass("idleField"); + $('.changeInput').live('focus', function () { - $('select').addClass("idleField"); - $('select').live('focus', function() { - $(this).removeClass("idleField").addClass("focusField"); - }); + $(this).removeClass("idleField").addClass("focusField"); - $('select').live('blur', function() { - $(this).removeClass("focusField").addClass("idleField"); - }); + if (this.value != this.defaultValue) { + this.select(); + } + }); - $('textarea').addClass("idleField"); - $('textarea').focus(function() { - $(this).removeClass("idleField").addClass("focusField"); - }); - - $('textarea').blur(function() { - $(this).removeClass("focusField").addClass("idleField"); - }); + $('.changeInput').live('blur', function () { + $(this).removeClass("focusField").addClass("idleField"); + }); - $(".remove").live('click', function () { - $(this).parent().parent().parent().fadeTo(400, 0, function () { - $(this).remove(); - }); - return false; - }); + $('.changeAutocomplete').live('focus', function () { + if (this.value == this.defaultValue) { + this.value = ''; + } + }); - }); - + $('.changeAutocomplete').live('blur', function () { + if (this.value == '') { + this.value = this.defaultValue; + } + }); - - function validateNewResource (){ - myReturn=0; - var title = $('#titleText').val(); - var fmtID = $('#resourceFormatID').val(); - var typeID = $('#resourceTypeID').val(); + $('select').addClass("idleField"); + $('select').live('focus', function () { + $(this).removeClass("idleField").addClass("focusField"); - //also perform same checks on the current record in case add button wasn't clicked - if (title == '' || title == null){ - $('#span_error_titleText').html('A title must be entered to continue.'); - myReturn=1; - } - - if (fmtID == '' || fmtID == null){ - $('#span_error_resourceFormatID').html('The resource format is required.'); - myReturn=1; - } - - if (typeID == '' || typeID == null){ - $('#span_error_resourceTypeID').html('The resource type is required.'); - myReturn=1; - } - - if (myReturn == 1){ - return false; - }else{ - return true; - } + }); + + $('select').live('blur', function () { + $(this).removeClass("focusField").addClass("idleField"); + }); + + + + $('textarea').addClass("idleField"); + $('textarea').focus(function () { + $(this).removeClass("idleField").addClass("focusField"); + }); + + $('textarea').blur(function () { + $(this).removeClass("focusField").addClass("idleField"); + }); + + + $(".remove").live('click', function () { + $(this).parent().parent().parent().fadeTo(400, 0, function () { + $(this).remove(); + }); + return false; + }); + + + +}); + + + + + +function validateNewResource() { + myReturn = 0; + + var title = $('#titleText').val(); + var fmtID = $('#resourceFormatID').val(); + var typeID = $('#resourceTypeID').val(); + + //also perform same checks on the current record in case add button wasn't clicked + if (title == '' || title == null) { + $('#span_error_titleText').html('A title must be entered to continue.'); + myReturn = 1; + } + + if (fmtID == '' || fmtID == null) { + $('#span_error_resourceFormatID').html('The resource format is required.'); + myReturn = 1; + } + + if (typeID == '' || typeID == null) { + $('#span_error_resourceTypeID').html('The resource type is required.'); + myReturn = 1; + } + + if (myReturn == 1) { + return false; + } else { + return true; + } } - -function submitResource(status){ - orderTypeList =''; - $(".orderTypeID").each(function(id) { - orderTypeList += $(this).val() + ":::"; - }); +function submitResource(status) { + + orderTypeList = ''; + $(".orderTypeID").each(function (id) { + orderTypeList += $(this).val() + ":::"; + }); + + fundNameList = ''; + $(".fundName").each(function (id) { + fundNameList += $(this).val() + ":::"; + }); + - fundNameList =''; - $(".fundName").each(function(id) { - fundNameList += $(this).val() + ":::"; - }); + paymentAmountList = ''; + $(".paymentAmount").each(function (id) { + paymentAmountList += $(this).val() + ":::"; + }); - paymentAmountList =''; - $(".paymentAmount").each(function(id) { - paymentAmountList += $(this).val() + ":::"; - }); + currencyCodeList = ''; + $(".currencyCode").each(function (id) { + currencyCodeList += $(this).val() + ":::"; + }); - currencyCodeList =''; - $(".currencyCode").each(function(id) { - currencyCodeList += $(this).val() + ":::"; - }); - + if (validateNewResource() === true) { + $('.submitResource').attr("disabled", "disabled"); + $.ajax({ + type: "POST", + url: "ajax_processing.php?action=submitNewResource", + cache: false, + data: {resourceID: $("#editResourceID").val(), resourceTypeID: $("input:radio[name='resourceTypeID']:checked").val(), resourceFormatID: $("input:radio[name='resourceFormatID']:checked").val(), acquisitionTypeID: $("input:radio[name='acquisitionTypeID']:checked").val(), titleText: $("#titleText").val(), descriptionText: $("#descriptionText").val(), providerText: $("#providerText").val(), organizationID: $("#organizationID").val(), resourceURL: $("#resourceURL").val(), resourceAltURL: $("#resourceAltURL").val(), noteText: $("#noteText").val(), orderTypes: orderTypeList, fundNames: fundNameList, paymentAmounts: paymentAmountList, currencyCodes: currencyCodeList, resourceStatus: status}, + success: function (resourceID) { + //go to the new resource page if this was submitted + if (status == 'progress') { + window.parent.location = ("resource.php?ref=new&resourceID=" + resourceID); + tb_remove(); + return false; + //otherwise go to queue + } else { + window.parent.location = ("queue.php?ref=new"); + tb_remove(); + return false; - if (validateNewResource() === true) { - $('.submitResource').attr("disabled", "disabled"); - $.ajax({ - type: "POST", - url: "ajax_processing.php?action=submitNewResource", - cache: false, - data: { resourceID: $("#editResourceID").val(), resourceTypeID: $("input:radio[name='resourceTypeID']:checked").val(), resourceFormatID: $("input:radio[name='resourceFormatID']:checked").val(), acquisitionTypeID: $("input:radio[name='acquisitionTypeID']:checked").val(), titleText: $("#titleText").val(), descriptionText: $("#descriptionText").val(), providerText: $("#providerText").val(), organizationID: $("#organizationID").val(), resourceURL: $("#resourceURL").val(), resourceAltURL: $("#resourceAltURL").val(), noteText: $("#noteText").val(), orderTypes: orderTypeList, fundNames: fundNameList, paymentAmounts: paymentAmountList, currencyCodes: currencyCodeList, resourceStatus: status }, - success: function(resourceID) { - //go to the new resource page if this was submitted - if (status == 'progress'){ - window.parent.location=("resource.php?ref=new&resourceID=" + resourceID); - tb_remove(); - return false; - //otherwise go to queue - }else{ - window.parent.location=("queue.php?ref=new"); - tb_remove(); - return false; - - } + } - } + } - }); + }); - } + } } -function searchGokb(s_name, s_issn, s_pub){ - if (validateSearchFields() === true){ - $.ajax({ - type: "POST", - url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", - cache: false, - data: {name:s_name, issn:s_issn, publisher:s_pub, type:0}, - success: function(res) { - document.getElementById("TB_ajaxContent").innerHTML = ""; - $('#TB_ajaxContent').append(res); - - } - }); - window.history.pushState(null,null,null); - window.history.pushState({funcName:'searchGokbBack', param:[s_name, s_pub]}, 'test', null); - } - else { - $("#span_error_search").html("At least one search field is required to search"); - } +function searchGokb(s_name, s_issn, s_pub) { + if (validateSearchFields() === true) { + $.ajax({ + type: "POST", + url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", + cache: false, + data: {name: s_name, issn: s_issn, publisher: s_pub, type: 0}, + success: function (res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + + } + }); + window.history.pushState(null, null, null); + console.debug("pushState NULL"); + window.history.pushState({funcName: 'searchGokbBack', param: [s_name, s_pub]}, 'test', null); + console.debug("pushState searchGokbBack(" + s_name + "," + s_issn + "," + s_pub + ")"); + } + else { + $("#span_error_search").html("At least one search field is required to search"); + } } -function validateSearchFields(){ - var name = $('#titleText').val(); - var publisher = $('#providerText').val(); - var issn = $('#ISSNText').val(); +function validateSearchFields() { + var name = $('#titleText').val(); + var publisher = $('#providerText').val(); + var issn = $('#ISSNText').val(); - if (((name != null) && (name != '')) || ((publisher != '') && (publisher != null)) || ((issn != null) && (issn != ''))){ - return true; - } else { - return false; - } + if (((name != null) && (name != '')) || ((publisher != '') && (publisher != null)) || ((issn != null) && (issn != ''))) { + return true; + } else { + return false; + } } //kill all binds done by jquery live -function kill(){ - - $('.remove').die('click'); - $('.changeAutocomplete').die('blur'); - $('.changeAutocomplete').die('focus'); - $('.changeDefault').die('blur'); - $('.changeDefault').die('focus'); - $('.changeInput').die('blur'); - $('.changeInput').die('focus'); - $('.select').die('blur'); - $('.select').die('focus'); +function kill() { + + $('.remove').die('click'); + $('.changeAutocomplete').die('blur'); + $('.changeAutocomplete').die('focus'); + $('.changeDefault').die('blur'); + $('.changeDefault').die('focus'); + $('.changeInput').die('blur'); + $('.changeInput').die('focus'); + $('.select').die('blur'); + $('.select').die('focus'); } From 4622a2543037563305a2cbe1dc8d45913058cabc Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Wed, 5 Aug 2015 11:33:10 +0200 Subject: [PATCH 20/32] add loading gif + link tipp/details --- admin/classes/domain/GOKbTools.php | 8 ++++---- css/gokb.search.css | 5 +++++ js/KBSearch.js | 19 ++++++++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index bd90bff..8b42ad0 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -292,20 +292,20 @@ function displayRecordTipps($record, $recordType){ } $string .= " "; -// $string.= "
      "; foreach ($tipps->children() as $child) { $resource = $child->{$type}; $resourceAttr = $resource->attributes(); $gokbID = $this->UriToGokbId("$type".'s/'.$resourceAttr[0]); - $string .= '
    "; + //$string .= $this->getResourceName($resource). ""; + $string .= $this->getResourceName($resource). ""; } -// $string .= ""; $string .= "
    "; return $string; diff --git a/css/gokb.search.css b/css/gokb.search.css index 555fa21..0f035f3 100644 --- a/css/gokb.search.css +++ b/css/gokb.search.css @@ -142,6 +142,11 @@ height: 25px; } +.tippLink{ + text-decoration: underline; + color: blue; +} + /******************************** **** getPagination.php **** ********************************/ diff --git a/js/KBSearch.js b/js/KBSearch.js index a42fe04..1b62560 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -8,13 +8,14 @@ * @return: nothing but display results thanks to ajax and php treatment */ function allResults(s_name, s_pub, s_type) { - + displayLoadBar(); $.ajax({ type: "POST", url: "ajax_htmldata.php?action=getKBSearchResults&height=503&width=775&resourceID=&modal=true", cache: false, data: {name: s_name, issn: '', publisher: s_pub, type: s_type, paginate: true}, success: function (res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; $('#TB_ajaxContent').append(res); } @@ -33,6 +34,7 @@ function allResults(s_name, s_pub, s_type) { * @return: nothing but display results thanks to ajax and treatment */ function getDetails(s_type, s_gokbID) { + displayLoadBar(); $.ajax({ type: "POST", url: "ajax_htmldata.php?action=getGokbResourceDetails&modal=true", @@ -49,6 +51,7 @@ function getDetails(s_type, s_gokbID) { /*******************************************************************************************************/ function selectResource(s_type, s_gokbID) { + displayLoadBar(); console.debug("fonction select(" + s_type + "," + s_gokbID + ")"); $.ajax({ type: "POST", @@ -78,6 +81,7 @@ function selectResource(s_type, s_gokbID) { * @return: nothing but display the right content */ function loadDetailsContent(element_nb) { + displayLoadBar(); console.debug("loadDetailsContent"); var tabs = document.getElementById("detailsTabs").getElementsByTagName("li"); var divs = document.getElementById("detailsContainer").getElementsByTagName("div"); @@ -194,8 +198,8 @@ function iterator(page) { } } } else { - for (var i=0; i

    "); + $('#TB_load').show();//show loader +} + From f2dbf540669469a9dbe8ca0f359cb1a77bf766f9 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Wed, 5 Aug 2015 11:53:00 +0200 Subject: [PATCH 21/32] ajax errors treatment --- js/KBSearch.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/js/KBSearch.js b/js/KBSearch.js index 1b62560..2e8eb5b 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -1,3 +1,8 @@ +$( document ).ajaxError(function( event, request, settings ) { + alert("An error occured with this request, please retry later"); + goBack(); +}); + /** * Send a SPARQL request to filter results with criteria @@ -15,7 +20,7 @@ function allResults(s_name, s_pub, s_type) { cache: false, data: {name: s_name, issn: '', publisher: s_pub, type: s_type, paginate: true}, success: function (res) { - + document.getElementById("TB_ajaxContent").innerHTML = ""; $('#TB_ajaxContent').append(res); } @@ -44,6 +49,7 @@ function getDetails(s_type, s_gokbID) { document.getElementById("TB_ajaxContent").innerHTML = ""; $('#TB_ajaxContent').append(res); } + }); window.history.pushState({funcName: 'getDetails', param: [s_type, s_gokbID]}, 'test', null); console.debug("pushState(getDetails(" + s_type + "," + s_gokbID + "))"); @@ -300,10 +306,10 @@ function goBack() { console.debug("back, new state = " + myState.funcName); } - - } +/*******************************************************************************************************/ + function displayLoadBar() { document.getElementById("TB_ajaxContent").innerHTML = ""; $('#TB_ajaxContent').append("
    "); From 6420193ef278d47fae3155972ed3dff1dbfd5f86 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Wed, 5 Aug 2015 17:04:43 +0200 Subject: [PATCH 22/32] add beginning of package content customization --- admin/classes/domain/Resource.php | 6 +++--- ajax_processing/importFromGOKb.php | 7 ++++++- js/KBSearch.js | 27 +++++++++++++++++++++++---- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/admin/classes/domain/Resource.php b/admin/classes/domain/Resource.php index 4702f97..1919d04 100644 --- a/admin/classes/domain/Resource.php +++ b/admin/classes/domain/Resource.php @@ -2194,7 +2194,7 @@ public function setIdentifiers($identifiers) { $this->setIsbnOrIssn($isbnorissns); } - public function getResourceByIdentifierAndType($identifier, $type = NULL) { //TODO _ $identifier est un tableau !! => do boucle !! + public function getResourceByIdentifierAndType($identifier, $type = NULL) { $query = "SELECT resourceID FROM Identifier WHERE upper(identifier) = '" . str_replace("'", "''", strtoupper($identifier)) . "'"; if ($type != NULL) { $id = new Identifier(); @@ -2266,8 +2266,8 @@ public function &getNewInitializedResource() { //$res->save(); return $res; - } - + } + } ?> diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index d6e601d..33f905b 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -176,9 +176,14 @@ print " (" . implode(',', ImportTool::getArrayOrganizationsCreated()) . ")"; } print ". " . ImportTool::getNbOrganizationsAttached() . " resources have been attached to an existing organization.

    "; +if ($_POST['type'] == 'package') { + print "
    " + . "Personalize this package content " + . "
    "; +} ?> +
    -
    diff --git a/js/KBSearch.js b/js/KBSearch.js index 2e8eb5b..2084f28 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -1,7 +1,7 @@ -$( document ).ajaxError(function( event, request, settings ) { - alert("An error occured with this request, please retry later"); - goBack(); -}); +//$( document ).ajaxError(function( event, request, settings ) { +// alert("An error occured with this request, please retry later"); +// goBack(); +//}); /** @@ -316,3 +316,22 @@ function displayLoadBar() { $('#TB_load').show();//show loader } +/*******************************************************************************************************/ + +function getCustomizationScreen(packageID){ + displayLoadBar(); + console.debug("fonction getCustomizationScreen(" +packageID+ ")"); + $.ajax({ + type: "POST", + url: "ajax_processing.php?action=customImportedPackageContent&modal=true", + cache: false, + data: {id: packageID}, + success: function (res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + +// TODO _ history + + }); +} \ No newline at end of file From dadfd6be233d762208c0c620fde709ac119ac462 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Fri, 7 Aug 2015 12:27:45 +0200 Subject: [PATCH 23/32] Fix bug of resource duplication + customization of package content --- admin/classes/domain/ImportTool.php | 93 +++++++++++++----------- ajax_htmldata/getGokbResourceDetails.php | 5 +- css/gokb.search.css | 1 + js/KBSearch.js | 69 ++++++++++++++++-- js/forms/resourceNewForm.js | 1 - 5 files changed, 114 insertions(+), 55 deletions(-) diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index 6815ff9..93845bf 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -99,7 +99,7 @@ public function addResource($datas, $identifiers) { if ($hasToBeInserted) { $res = $res_tmp->getNewInitializedResource(); - //Resource treatment +//Resource treatment foreach ($datas as $key => $value) { switch ($key) { case "organization": @@ -120,22 +120,22 @@ public function addResource($datas, $identifiers) { } } - //ResourceType treatment +//ResourceType treatment if ($resourceType != NULL) { $res->resourceTypeID = ResourceType::getResourceTypeID((string) $resourceType); } $res->save(); - //Resource identifiers treatment +//Resource identifiers treatment $res->setIdentifiers($identifiers); - //Aliases treatment (history name change/ variant name) //TODO +//Aliases treatment (history name change/ variant name) //TODO if ($aliases != null) { $this->aliasesTreatment($aliases, $res->resourceID); } - //Parent treatment +//Parent treatment if ($parentName != null) { $parentID = $this->parentTreatment($parentName); $this->setResourcesRelationship($res->resourceID, $parentID); @@ -169,30 +169,38 @@ private function setResourceOrganizationLink($roleID, $resourceID, $organization */ private function hasResourceToBeInserted($datas, $identifiers) { $res_tmp = new Resource(); - $hasToBeInserted = false; + $hasToBeInserted = true; $resource = $res_tmp->getResourceByIdentifiers($identifiers); - - if (count($resource) == 0) { //resource doesn't exist, we have to create it + $nbRes = count($resource); + + if ($nbRes == 0) { //resource doesn't exist, we have to create it $hasToBeInserted = true; - } elseif ($datas['parentResource']) { //resource exists and got a parent, test title + parent - $res = $resource[0]; - //$currentResourceID = $res->resourceID; - $parents = $res->getParentResources(); - $nbParents = count($parents); - - if ($nbParents == 0) { //existing resource doesn't have any parent - $hasToBeInserted = true; - } else { - $hasToBeInserted = true; - foreach ($parents as $parentResource) { - if ($parentResource->titleText == $datas['parentResource']) { - $hasToBeInserted = false; + } else { + $ii = 0; + while ($hasToBeInserted && $ii<$nbRes){ + $res = $resource[$ii]; + $parents = $res->getParentResources(); // getParentResources return an array of relationship + $nbParents = count($parents); + + if (($datas['parentResource']) && $nbParents == 0) { + $hasToBeInserted = true; + } elseif ($datas['parentResource']) { //both resources have a parent + $hasToBeInserted = true; + foreach ($parents as $relationship) { + $parentResource = new Resource(new NamedArguments(array('primaryKey' => "$relationship->relatedResourceID"))); + if ($parentResource->titleText == $datas['parentResource']) { + $hasToBeInserted = false; + } } + } elseif ($nbParents != 0) { //existing resource got a parent but not the new one + $hasToBeInserted = true; + } else { + $hasToBeInserted = false; } + $ii++; } } - return $hasToBeInserted; } @@ -201,7 +209,7 @@ private function parentTreatment($parentName) { $resource = new Resource(); $parentResource = $resource->getResourceByTitle($parentName); - // Search if such parent exists +// Search if such parent exists $numberOfParents = count($parentResource); $parentID = null; @@ -238,7 +246,7 @@ private function organizationTreatment($organizations, $resourceID) { $loginID = $_SESSION['loginID']; $htmlContent = ''; - //Organizations module is used +//Organizations module is used if ($this->config->settings->organizationsModule == 'Y') { //TODO _ hierarchy platform/provider $dbName = $this->config->settings->organizationsDatabaseName; foreach ($organizations as $role => $orgName) { @@ -246,14 +254,14 @@ private function organizationTreatment($organizations, $resourceID) { $organizationRole = new OrganizationRole(); $organizationID = false; - // Does the organization already exists? +// Does the organization already exists? $query = "SELECT count(*) AS count FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; $result = $organization->db->processQuery($query, 'assoc'); if ($result['count'] == 0) { // If not, we try to create it $organizationID = $this->createOrgWithOrganizationModule($orgName); } - // If yes, we attach it to our resource +// If yes, we attach it to our resource elseif ($result['count'] == 1) { $query = "SELECT name, organizationID FROM $dbName.Organization WHERE UPPER(name) = '" . str_replace("'", "''", strtoupper($orgName)) . "'"; $result = $organization->db->processQuery($query, 'assoc'); @@ -264,14 +272,14 @@ private function organizationTreatment($organizations, $resourceID) { } if ($organizationID) { - // Get role +// Get role $query = "SELECT organizationRoleID from OrganizationRole WHERE shortName='" . mysql_escape_string($role) . "'"; $result = $organization->db->processQuery($query); $roleID = ($result[0]) ? $result[0] : 1; - // Does the organizationRole already exists? +// Does the organizationRole already exists? $query = "SELECT count(*) AS count FROM $dbName.OrganizationRoleProfile WHERE organizationID=$organizationID AND organizationRoleID=$roleID"; $result = $organization->db->processQuery($query, 'assoc'); - // If not, we try to create it +// If not, we try to create it if ($result['count'] == 0) { $query = "INSERT INTO $dbName.OrganizationRoleProfile SET organizationID=$organizationID, organizationRoleID=$roleID"; try { @@ -286,27 +294,27 @@ private function organizationTreatment($organizations, $resourceID) { } } - // Let's link the resource and the organization. - // (this has to be done whether the module Organization is in use or not) +// Let's link the resource and the organization. +// (this has to be done whether the module Organization is in use or not) if ($organizationID && $roleID) { $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); } } - //TODO _ hierarchy platform/provider (packages) +//TODO _ hierarchy platform/provider (packages) if ($organizations['platform']) { $platformName = $organizations['platform']; $providerName = $organizations['provider']; $this->setOrganizationsHierarchy($platformName, $providerName); } } - // If we do not use the Organizations module +// If we do not use the Organizations module else { foreach ($organizations as $role => $orgName) { $organization = new Organization(); //TODO _ instanciation dans une boucle j'aime pas trop ça ;) $organizationRole = new OrganizationRole(); $organizationID = false; - // Search if such organization already exists +// Search if such organization already exists $organizationExists = $organization->alreadyExists($orgName); if (!$organizationExists) { // If not, create it @@ -324,12 +332,12 @@ private function organizationTreatment($organizations, $resourceID) { $organizationRoles = $organizationRole->getArray(); if (($roleID = array_search($role, $organizationRoles)) == 0) { - // If role is not found, fallback to the first one. +// If role is not found, fallback to the first one. $roleID = '1'; } - // Let's link the resource and the organization. - // (this has to be done whether the module Organization is in use or not) +// Let's link the resource and the organization. +// (this has to be done whether the module Organization is in use or not) if ($organizationID && $roleID) { $this->setResourceOrganizationLink($roleID, $resourceID, $organizationID); } @@ -352,7 +360,7 @@ private function createOrgWithOrganizationModule($orgName) { self::$organizationsInserted++; array_push(self::$arrayOrganizationsCreated, $orgName); } catch (Exception $e) { - // $htmlContent .= "

    Organization $orgName could not be added.

    "; +// $htmlContent .= "

    Organization $orgName could not be added.

    "; } return $organizationID; } @@ -381,16 +389,15 @@ private function setOrganizationsHierarchy($orgName, $parentOrgName) { $parentID = $result[0]; - if ( ($orgID != null) && - ($parentID != null) && - ( !($relation->relationExists($orgID, $parentID))) ) { + if (($orgID != null) && + ($parentID != null) && + (!($relation->relationExists($orgID, $parentID)))) { // $relation->organizationID = $orgID; // $relation->parentOrganizationID = $parentID; // $relation->save(); - //$query = "INSERT INTO $dbName.OrganizationHierarchy SET `organizationID`=$orgID, `parentOrganizationID`=$parentID ;"; +//$query = "INSERT INTO $dbName.OrganizationHierarchy SET `organizationID`=$orgID, `parentOrganizationID`=$parentID ;"; $query = "INSERT INTO $dbName.OrganizationHierarchy VALUES ($orgID, $parentID);"; $relation->db->processQuery($query); - } } diff --git a/ajax_htmldata/getGokbResourceDetails.php b/ajax_htmldata/getGokbResourceDetails.php index ba871e8..5ba991f 100644 --- a/ajax_htmldata/getGokbResourceDetails.php +++ b/ajax_htmldata/getGokbResourceDetails.php @@ -53,13 +53,10 @@
    + "; ?> - - -"; ?> -
    \ No newline at end of file diff --git a/css/gokb.search.css b/css/gokb.search.css index 0f035f3..65e7607 100644 --- a/css/gokb.search.css +++ b/css/gokb.search.css @@ -145,6 +145,7 @@ .tippLink{ text-decoration: underline; color: blue; + cursor: pointer; } /******************************** diff --git a/js/KBSearch.js b/js/KBSearch.js index 2084f28..afabd55 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -1,7 +1,8 @@ -//$( document ).ajaxError(function( event, request, settings ) { -// alert("An error occured with this request, please retry later"); -// goBack(); -//}); +$(document).ajaxError(function (event, request, settings) { + alert("An error occured with this request, please retry later"); + console.debug("Ajax error: event = " + event.toString()); + goBack(); +}); /** @@ -87,7 +88,7 @@ function selectResource(s_type, s_gokbID) { * @return: nothing but display the right content */ function loadDetailsContent(element_nb) { - displayLoadBar(); + //displayLoadBar(); console.debug("loadDetailsContent"); var tabs = document.getElementById("detailsTabs").getElementsByTagName("li"); var divs = document.getElementById("detailsContainer").getElementsByTagName("div"); @@ -318,9 +319,9 @@ function displayLoadBar() { /*******************************************************************************************************/ -function getCustomizationScreen(packageID){ +function getCustomizationScreen(packageID) { displayLoadBar(); - console.debug("fonction getCustomizationScreen(" +packageID+ ")"); + console.debug("fonction getCustomizationScreen(" + packageID + ")"); $.ajax({ type: "POST", url: "ajax_processing.php?action=customImportedPackageContent&modal=true", @@ -334,4 +335,58 @@ function getCustomizationScreen(packageID){ // TODO _ history }); +} + +function submitCustom(packageID) { + + console.debug("function submitCustom"); + var checkboxes = document.getElementById("customPackageTable").getElementsByTagName("input"); + displayLoadBar(); + var max = checkboxes.length; + var resToRemove = new Array(); + for (var i = 0; i < max; i++) { + if (!(checkboxes[i].checked)) { + console.debug("checkboxes " + i + " is unchecked and is " + checkboxes[i].value); + resToRemove.push(checkboxes[i].value); + } + } + console.debug("after displaying cb array"); + + $.ajax({ + type: "POST", + url: "ajax_processing.php?action=customImportedPackageContent&modal=true", + cache: false, + data: {tab: resToRemove, id: packageID}, + success: function (res) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append(res); + } + + }); +} + +function checkAll(source) { + var checkboxes = document.getElementsByName('cbs'); + for (var i = 0, n = checkboxes.length; i < n; i++) { + checkboxes[i].checked = source.checked; + } +} + + +function removeResAndChildren(packageID) { + + if (confirm("Do you really want to delete this resource and all its children?") == true) { + displayLoadBar(); + $.ajax({ + type: "GET", + url: "ajax_processing.php", + cache: false, + data: "action=deleteResourceAndChildren&resourceID=" + packageID, + success: function (html) { + //post return message to index + postwith('index.php', {message: html}); + } + }); + } + //TODO _ history } \ No newline at end of file diff --git a/js/forms/resourceNewForm.js b/js/forms/resourceNewForm.js index 0adcea2..55c36b3 100644 --- a/js/forms/resourceNewForm.js +++ b/js/forms/resourceNewForm.js @@ -297,7 +297,6 @@ function submitResource(status) { window.parent.location = ("queue.php?ref=new"); tb_remove(); return false; - } } From ec3f0568ce1a4d398894c2aa4cc96002d4c85b60 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Tue, 11 Aug 2015 11:21:37 +0200 Subject: [PATCH 24/32] package content customization + little IHM improvements --- admin/classes/domain/GOKbTools.php | 4 +- ajax_forms/getNewResourceForm.php | 19 +- ajax_forms/getUpdateProductForm.php | 793 +++++++++--------- ajax_htmldata/getGokbResourceDetails.php | 2 +- ajax_htmldata/getKBSearchResults.php | 5 +- .../customImportedPackageContent.php | 62 ++ ajax_processing/importFromGOKb.php | 35 +- css/gokb.search.css | 9 + js/KBSearch.js | 4 +- js/forms/resourceNewForm.js | 4 + 10 files changed, 519 insertions(+), 418 deletions(-) create mode 100644 ajax_processing/customImportedPackageContent.php diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 8b42ad0..58e82d5 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -146,8 +146,10 @@ public function searchOnGokb($name, $issn, $publisher, $searchType){ foreach ($res as $a => $b ) { $uri = $b->{'binding'}->{'uri'}; $id = $this->UriToGokbId($uri); - $prefLabel = $b->{'binding'}[1]->{'literal'}; + //$prefLabel = $b->{'binding'}[1]->{'literal'}; + $prefLabel = utf8_encode($b->{'binding'}[1]->{'literal'}); $titles["$id"] = $prefLabel; + // $titles["$id"] = \utf8_encode($prefLabel); } } diff --git a/ajax_forms/getNewResourceForm.php b/ajax_forms/getNewResourceForm.php index f5631e2..d219a2b 100644 --- a/ajax_forms/getNewResourceForm.php +++ b/ajax_forms/getNewResourceForm.php @@ -82,7 +82,7 @@
    -
     * required fields search fields
    +
     * required fields search fields
    @@ -99,7 +99,7 @@ - + @@ -110,13 +110,13 @@ - + - + @@ -250,7 +250,8 @@ if (strtoupper($resourceType['resourceTypeID']) == $resource->resourceTypeID) $checked = 'checked'; } - echo "\n"; +// echo "\n"; + echo ""; if(($i % 3)==0){ echo "\n"; @@ -299,10 +300,10 @@
    " . $resourceType['shortName'] . "" . $resourceType['shortName'] . "
    - - - - + + + + diff --git a/ajax_forms/getUpdateProductForm.php b/ajax_forms/getUpdateProductForm.php index 3f7d112..79dbbad 100644 --- a/ajax_forms/getUpdateProductForm.php +++ b/ajax_forms/getUpdateProductForm.php @@ -1,428 +1,425 @@ $resourceID))); +$resourceID = $_GET['resourceID']; +$resource = new Resource(new NamedArguments(array('primaryKey' => $resourceID))); - if (!is_null_date($resource->archiveDate)) { - $archiveChecked = 'checked'; - }else{ - $archiveChecked = ''; - } +if (!is_null_date($resource->archiveDate)) { + $archiveChecked = 'checked'; +} else { + $archiveChecked = ''; +} - //get all resource formats for output in drop down - $resourceFormatArray = array(); - $resourceFormatObj = new ResourceFormat(); - $resourceFormatArray = $resourceFormatObj->sortedArray(); +//get all resource formats for output in drop down +$resourceFormatArray = array(); +$resourceFormatObj = new ResourceFormat(); +$resourceFormatArray = $resourceFormatObj->sortedArray(); - //get all resource types for output in drop down - $resourceTypeArray = array(); - $resourceTypeObj = new ResourceType(); - $resourceTypeArray = $resourceTypeObj->allAsArray(); +//get all resource types for output in drop down +$resourceTypeArray = array(); +$resourceTypeObj = new ResourceType(); +$resourceTypeArray = $resourceTypeObj->allAsArray(); - //get parents resources - $sanitizedInstance = array(); - $instance = new Resource(); - $parentResourceArray = array(); - foreach ($resource->getParentResources() as $instance) { +//get parents resources +$sanitizedInstance = array(); +$instance = new Resource(); +$parentResourceArray = array(); +foreach ($resource->getParentResources() as $instance) { foreach (array_keys($instance->attributeNames) as $attributeName) { - $sanitizedInstance[$attributeName] = $instance->$attributeName; + $sanitizedInstance[$attributeName] = $instance->$attributeName; } $sanitizedInstance[$instance->primaryKeyName] = $instance->primaryKey; array_push($parentResourceArray, $sanitizedInstance); - } +} - //get all alias types for output in drop down - $aliasTypeArray = array(); - $aliasTypeObj = new AliasType(); - $aliasTypeArray = $aliasTypeObj->allAsArray(); +//get all alias types for output in drop down +$aliasTypeArray = array(); +$aliasTypeObj = new AliasType(); +$aliasTypeArray = $aliasTypeObj->allAsArray(); - //get aliases - $sanitizedInstance = array(); - $instance = new Alias(); - $aliasArray = array(); - foreach ($resource->getAliases() as $instance) { - foreach (array_keys($instance->attributeNames) as $attributeName) { - $sanitizedInstance[$attributeName] = $instance->$attributeName; - } +//get aliases +$sanitizedInstance = array(); +$instance = new Alias(); +$aliasArray = array(); +foreach ($resource->getAliases() as $instance) { + foreach (array_keys($instance->attributeNames) as $attributeName) { + $sanitizedInstance[$attributeName] = $instance->$attributeName; + } - $sanitizedInstance[$instance->primaryKeyName] = $instance->primaryKey; + $sanitizedInstance[$instance->primaryKeyName] = $instance->primaryKey; - $aliasType = new AliasType(new NamedArguments(array('primaryKey' => $instance->aliasTypeID))); - $sanitizedInstance['aliasTypeShortName'] = $aliasType->shortName; + $aliasType = new AliasType(new NamedArguments(array('primaryKey' => $instance->aliasTypeID))); + $sanitizedInstance['aliasTypeShortName'] = $aliasType->shortName; - array_push($aliasArray, $sanitizedInstance); - } + array_push($aliasArray, $sanitizedInstance); +} - //get all organization roles for output in drop down - $organizationRoleArray = array(); - $organizationRoleObj = new OrganizationRole(); - $organizationRoleArray = $organizationRoleObj->getArray(); +//get all organization roles for output in drop down +$organizationRoleArray = array(); +$organizationRoleObj = new OrganizationRole(); +$organizationRoleArray = $organizationRoleObj->getArray(); - //get organizations (already returned in an array) - $orgArray = $resource->getOrganizationArray(); +//get organizations (already returned in an array) +$orgArray = $resource->getOrganizationArray(); ?> -
    -
    - - -
    Edit Resource
    - - - -
    - - - - - - + +
    - - -      - - - - - -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - -
    - - add Parent resource
    -
    -
    -
    - - $parentResource['relatedResourceID']))); - ?> -
    - - - remove parent -
    - -
    -
    - - - add Isbn
    -

    -
    - - getIsbnOrIssn(); - $i = 1; - foreach ($isbnOrIssns as $isbnOrIssn) { - ?>
    -
    -
    - -
    - - -
    - /> -
    -
    -
    - -
     
    - -
    - -      - - - - - -
    - - - - - - - - - - - - - - - -
    Role:Organization: 
    - - - - - - add organization -
    -
    - - - - - - - 0){ - - foreach ($orgArray as $organization){ - ?> - - - - - - - - - - - -
    -
    -
    - - - ' style='width:160px;' class='changeInput' /> - ' /> - - remove organization organization' class='remove' /> -
    - - - -
    - -
    - -      - - - - + +
    - - - - - - - - - - - - - - - - -
    Type:Alias: 
    - - - - - add this alias -
    -
    - - - - - - - - 0){ - - foreach ($aliasArray as $resourceAlias){ - ?> - - - - - - - - - - -
    -
    -
    - - - - ' style='width:125px;' class='changeInput aliasName' /> - - remove this alias -
    +
    + + + +
    Edit Resource
    + + + + + + + + + + - -
    + + +      + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    + + add Parent resource
    +
    +
    +
    + + $parentResource['relatedResourceID']))); + ?> +
    + + + remove parent +
    + +
    +
    + + + add Isbn
    +

    +
    + + getIsbnOrIssn(); + $i = 1; + foreach ($isbnOrIssns as $isbnOrIssn) { + ?>
    +
    +
    + +
    + + +
    + /> +
    +
    +
    + +
     
    + +
    + +      + + + + + +
    + + + + + + + + + + + + + + + +
    Role:Organization: 
    + + + + + + add organization +
    +
    + + + + + + + 0) { + + foreach ($orgArray as $organization) { + ?> + + + + + + + + + + + +
    +
    +
    + + + ' style='width:160px;' class='changeInput' /> + ' /> + + remove organization organization' class='remove' /> +
    + + + +
    + +
    + +      + + + + - -
    + + + + + + + + + + + + + + + + +
    Type:Alias: 
    + + + + + add this alias +
    +
    + + + + + + + + 0) { + + foreach ($aliasArray as $resourceAlias) { + ?> + + + + + + + + +
    +
    +
    + + + + ' style='width:125px;' class='changeInput aliasName' /> + + remove this alias +
    -
    -
    +
    -
    - - - - - - -
    - +
    + + +
    + + + + + + +
    + diff --git a/ajax_htmldata/getGokbResourceDetails.php b/ajax_htmldata/getGokbResourceDetails.php index 5ba991f..7b44d2d 100644 --- a/ajax_htmldata/getGokbResourceDetails.php +++ b/ajax_htmldata/getGokbResourceDetails.php @@ -55,7 +55,7 @@
    "; ?> - +
    diff --git a/ajax_htmldata/getKBSearchResults.php b/ajax_htmldata/getKBSearchResults.php index 2b1b33a..c8ec420 100644 --- a/ajax_htmldata/getKBSearchResults.php +++ b/ajax_htmldata/getKBSearchResults.php @@ -17,7 +17,7 @@ if ($nb_packages > 0) { echo "
    "; echo " Packages View all packages results
    "; @@ -41,7 +41,7 @@ if ($nb_titles > 0) { echo "
    "; echo ' Titles View all titles results
    '; @@ -54,6 +54,7 @@ echo " class='invisible'"; echo ">
    "; echo ' - ' . $value; + //echo ' - '.utf8_encode($value); echo '
    "; + + foreach ($titles as $relationship) { + $title = new Resource(new NamedArguments(array('primaryKey' => $relationship->resourceID))); + echo ""; + } + echo "
    " . $title->titleText."
    " + . "" + . "" + . "" + . ""; + + } + +} + +?> + +
    + + +
    + + + + + + + + \ No newline at end of file diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index 33f905b..b9902dc 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -168,14 +168,37 @@ $importTool->addResource($datas, $identifiers); } +$displayStat = function ($val, $word, $text){ + $string = ""; + $verb = "has been "; + if ($val != 0){ + if ($val > 1) {$word .= "s"; $verb="have been ";} + $string = $val." ".$word." ".$verb.$text; + } + return $string; +}; + print "

    Results

    "; -print "

    " . (ImportTool::getNbRow()) . " rows have been processed. " . ImportTool::getNbInserted() . " rows have been inserted.

    "; -print "

    " . ImportTool::getNbParentInserted() . " parents have been created. " . ImportTool::getNbParentAttached() . " resources have been attached to an existing parent.

    "; -print "

    " . ImportTool::getNbOrganizationsInserted() . " organizations have been created"; + + +//print "

    " . (ImportTool::getNbRow()) . " rows have been processed. " . ImportTool::getNbInserted() . " rows have been inserted.

    "; +print "

    ".$displayStat(ImportTool::getNbRow(), "row", "processed. "); +print $displayStat(ImportTool::getNbInserted(), "row", "inserted. ")."

    "; + +//print "

    " . ImportTool::getNbParentInserted() . " parents have been created. " . ImportTool::getNbParentAttached() . " resources have been attached to an existing parent.

    "; +print "

    ".$displayStat(ImportTool::getNbParentInserted(), "parent", "created. "); +print $displayStat(ImportTool::getNbParentAttached(), "parent", "attached to an existing parent. ")."

    "; + +//print "

    " . ImportTool::getNbOrganizationsInserted() . " organizations have been created"; +print "

    ".$displayStat(ImportTool::getNbOrganizationsInserted(), "organization", "created "); if (count(ImportTool::getArrayOrganizationsCreated()) > 0) { - print " (" . implode(',', ImportTool::getArrayOrganizationsCreated()) . ")"; + print " (" . implode(',', ImportTool::getArrayOrganizationsCreated()) . "). "; } -print ". " . ImportTool::getNbOrganizationsAttached() . " resources have been attached to an existing organization.

    "; +//print ". " . ImportTool::getNbOrganizationsAttached() . " resources have been attached to an existing organization.

    "; +print $displayStat(ImportTool::getNbOrganizationsAttached(), "resource", "attached to an existing organization. ")."

    "; + + + if ($_POST['type'] == 'package') { print "
    " . "Personalize this package content " @@ -185,5 +208,5 @@
    - +
    diff --git a/css/gokb.search.css b/css/gokb.search.css index 65e7607..82925ca 100644 --- a/css/gokb.search.css +++ b/css/gokb.search.css @@ -184,7 +184,16 @@ display: none; } +/***************************************** + **** getResourceNewForm.php **** + *****************************************/ +.loupe{ + cursor: pointer; +} +#searchFieldsText{ + margin: 30px; +} /************************ **** Multiple **** diff --git a/js/KBSearch.js b/js/KBSearch.js index afabd55..9517965 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -9,7 +9,7 @@ $(document).ajaxError(function (event, request, settings) { * Send a SPARQL request to filter results with criteria * @param: s_name string content of "Name" field (new resource form) * @param: s_pub string content of "Provider" field (new resource form) - * @param: s_type int searchType (0 = all ; -1 = packages only; 1=issues only) + * @param: s_type int searchType (0 = all ; -1 = packages only; 1=titles only) * * @return: nothing but display results thanks to ajax and php treatment */ @@ -337,6 +337,8 @@ function getCustomizationScreen(packageID) { }); } +/*******************************************************************************************************/ + function submitCustom(packageID) { console.debug("function submitCustom"); diff --git a/js/forms/resourceNewForm.js b/js/forms/resourceNewForm.js index 55c36b3..9aa9190 100644 --- a/js/forms/resourceNewForm.js +++ b/js/forms/resourceNewForm.js @@ -24,6 +24,10 @@ $(document).ready(function () { $("#search").click(function () { searchGokb($('#titleText').val(), $('#ISSNText').val(), $('#providerText').val()); }); + + $(".loupe").click(function(){ + searchGokb($('#titleText').val(), $('#ISSNText').val(), $('#providerText').val()); + }); //do submit if enter is hit $('#titleText').keyup(function (e) { From 08aeed60616a6848843697885a05dd09ce2f8760 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Tue, 11 Aug 2015 12:22:15 +0200 Subject: [PATCH 25/32] HTTP Client inheritance --- admin/classes/domain/GOKbTools.php | 6 +++--- admin/classes/domain/MyClient.php | 18 ++++++++++++++++++ .../phpoaipmh/src/Phpoaipmh/Client.php | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 admin/classes/domain/MyClient.php diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 58e82d5..831d60b 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -1,7 +1,7 @@ checkForOaipmhException($e); $response = ''; } - - $res = $tmpClient->decodeResponse($response); //decodeResponse protected: passé à public temporairement --> trouver une solution !! (héritage + surcharge ?) + $myTmpClient = new MyClient(); + $res = $myTmpClient->decodeResponse($response); //decodeResponse protected: passé à public temporairement --> trouver une solution !! (héritage + surcharge ?) return $res; } diff --git a/admin/classes/domain/MyClient.php b/admin/classes/domain/MyClient.php new file mode 100644 index 0000000..a5364a6 --- /dev/null +++ b/admin/classes/domain/MyClient.php @@ -0,0 +1,18 @@ + Date: Wed, 12 Aug 2015 10:48:58 +0200 Subject: [PATCH 26/32] Fix back button bug --- js/KBSearch.js | 55 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/js/KBSearch.js b/js/KBSearch.js index 9517965..97de920 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -4,6 +4,7 @@ $(document).ajaxError(function (event, request, settings) { goBack(); }); +/*******************************************************************************************************/ /** * Send a SPARQL request to filter results with criteria @@ -52,11 +53,22 @@ function getDetails(s_type, s_gokbID) { } }); - window.history.pushState({funcName: 'getDetails', param: [s_type, s_gokbID]}, 'test', null); - console.debug("pushState(getDetails(" + s_type + "," + s_gokbID + "))"); + var currentState = window.history.state; + if ((currentState.funcName == 'getDetails') && (currentState.param[1] == s_gokbID)) { + console.debug("Same state as before !!, don't push"); + } else { + window.history.pushState({funcName: 'getDetails', param: [s_type, s_gokbID]}, 'test', null); + console.debug("pushState(getDetails(" + s_type + "," + s_gokbID + "))"); + } } /*******************************************************************************************************/ + +/** + * Import the resource from GOKb to DB + * @param {string} s_type 'package' OR 'title" + * @param {string} s_gokbID GOKb ID of resource + */ function selectResource(s_type, s_gokbID) { displayLoadBar(); console.debug("fonction select(" + s_type + "," + s_gokbID + ")"); @@ -70,17 +82,11 @@ function selectResource(s_type, s_gokbID) { document.getElementById("TB_ajaxContent").innerHTML = ""; $('#TB_ajaxContent').append(res); } - - }); - - //TODO history - - - } /*******************************************************************************************************/ + /** * Manage the details tabs (global detail or TIPPs) * @param: element_nb int index of selected tab @@ -88,7 +94,6 @@ function selectResource(s_type, s_gokbID) { * @return: nothing but display the right content */ function loadDetailsContent(element_nb) { - //displayLoadBar(); console.debug("loadDetailsContent"); var tabs = document.getElementById("detailsTabs").getElementsByTagName("li"); var divs = document.getElementById("detailsContainer").getElementsByTagName("div"); @@ -109,7 +114,6 @@ function loadDetailsContent(element_nb) { } else { document.getElementById("paginationDiv").className = "invisible"; } - } /*******************************************************************************************************/ @@ -311,6 +315,9 @@ function goBack() { /*******************************************************************************************************/ +/** + * Display load bar while treatment + */ function displayLoadBar() { document.getElementById("TB_ajaxContent").innerHTML = ""; $('#TB_ajaxContent').append("
    "); @@ -319,6 +326,10 @@ function displayLoadBar() { /*******************************************************************************************************/ +/** + * Display package content for customization + * @param {string} packageID GOKb package ID + */ function getCustomizationScreen(packageID) { displayLoadBar(); console.debug("fonction getCustomizationScreen(" + packageID + ")"); @@ -339,8 +350,11 @@ function getCustomizationScreen(packageID) { /*******************************************************************************************************/ +/** + * Remove unselected titles from package + * @param {string} packageID GOKb package ID + */ function submitCustom(packageID) { - console.debug("function submitCustom"); var checkboxes = document.getElementById("customPackageTable").getElementsByTagName("input"); displayLoadBar(); @@ -352,7 +366,6 @@ function submitCustom(packageID) { resToRemove.push(checkboxes[i].value); } } - console.debug("after displaying cb array"); $.ajax({ type: "POST", @@ -367,6 +380,12 @@ function submitCustom(packageID) { }); } +/*******************************************************************************************************/ + +/** + * Check / Uncheck all checkboxes (titles included in package) + * @param {checkBox} source main checkbox + */ function checkAll(source) { var checkboxes = document.getElementsByName('cbs'); for (var i = 0, n = checkboxes.length; i < n; i++) { @@ -374,7 +393,12 @@ function checkAll(source) { } } +/*******************************************************************************************************/ +/** + * Remove resource and all its children + * @param {int} packageID DB primaryKey of package + */ function removeResAndChildren(packageID) { if (confirm("Do you really want to delete this resource and all its children?") == true) { @@ -390,5 +414,6 @@ function removeResAndChildren(packageID) { } }); } - //TODO _ history -} \ No newline at end of file +} + +/*******************************************************************************************************/ \ No newline at end of file From 7809fd112ef41d2360c018ecfc11e5a7c8f2588b Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Thu, 13 Aug 2015 15:07:31 +0200 Subject: [PATCH 27/32] filter and css for package customization, add custom link on each package which get children --- ajax_htmldata/getProductDetails.php | 3 +- .../customImportedPackageContent.php | 119 ++++++---- ajax_processing/importFromGOKb.php | 11 +- css/gokb.search.css | 211 +++++++++++------- images/repo.gif | Bin 0 -> 160 bytes js/KBSearch.js | 48 +++- 6 files changed, 257 insertions(+), 135 deletions(-) create mode 100644 images/repo.gif diff --git a/ajax_htmldata/getProductDetails.php b/ajax_htmldata/getProductDetails.php index b746da9..ee93e3e 100644 --- a/ajax_htmldata/getProductDetails.php +++ b/ajax_htmldata/getProductDetails.php @@ -66,7 +66,8 @@ titleText; ?>shortName . " " . $resourceFormat->shortName . " " . $resourceType->shortName; ?> - canEdit()){ ?>edit isAdmin){ ?>remove resource remove resource and its children + canEdit()){ + if(count($resource->getChildResources())>0){ //resource is a package with children ?> Customize package content edit isAdmin){ ?>remove resource remove resource and its children diff --git a/ajax_processing/customImportedPackageContent.php b/ajax_processing/customImportedPackageContent.php index 155d8df..4054f29 100644 --- a/ajax_processing/customImportedPackageContent.php +++ b/ajax_processing/customImportedPackageContent.php @@ -4,59 +4,88 @@ $resource->resourceID))); + echo _("" . $title->titleText . ""); + } +}; + +$customDone = false; + +if ($_POST['tab']) { + $resToRemove = $_POST['tab']; + echo _("

    The " . count($resToRemove) . " following resources have been removed from package :


    "); + echo _(""); foreach ($resToRemove as $res) { - $object = new Resource(new NamedArguments(array('primaryKey' => $res))); - echo "
    - ".$object->titleText; - $object->removeResource(); + $object = new Resource(new NamedArguments(array('primaryKey' => $res))); + echo _(""); + $object->removeResource(); } - + echo _("
    - " . $object->titleText."
    "); + $customDone = true; } else { - echo '

    Uncheck titles you want to remove from this package. Unselect all will import package only without any titles.
    - To delete this package and all resources included in Click here

    '; - $resource = new Resource(); - $package = $resource->getResourceByIdentifierAndType($_POST['id'], 'gokb'); + echo _('

    Uncheck titles you want to remove from this package. Unselect all will import package only without any titles.
    + To delete this package and all resources included in Click here

    '); + + $package = new Resource(new NamedArguments(array('primaryKey' => $resID))); if (count($package) > 1) { - echo "more than 1 resource correspond to this package !! "; //TODO _ DEBUG _ + echo _("more than 1 resource correspond to this package !! "); //TODO _ DEBUG _ } else { - $titles = $package[0]->getChildResources(); //return an array of ResourceRelationship - //ajax_processing/customImportedPackageContent.php - echo "
    " - . "
    " - . "
    " - . "Package content" - . " Select all" - . " "; - - foreach ($titles as $relationship) { - $title = new Resource(new NamedArguments(array('primaryKey' => $relationship->resourceID))); - echo ""; - } - echo "
    " . $title->titleText."
    " - . "
    " - . "" - . "
    " - . "
    "; - - } - -} - -?> - + ?> +
    +
    +
    +

    Package content

    + + + + + + + + + + + + + getChildResources(); //return an array of ResourceRelationship + $displayResourceTabLines($titles); + } + ?> + +
    All Name filter
    + +
    +
    +
    +
    +
    - - + + + + + +
    - - - + + + + - - \ No newline at end of file diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index b9902dc..2afc65a 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -200,8 +200,17 @@ if ($_POST['type'] == 'package') { + $res = new Resource(); + $packID = $res->getResourceByIdentifierAndType($_POST['id'], 'gokb'); + $nb = count($packID); + if(($nb>1) || ($nb == 0) ){ + echo "None or more than one resource correspond => ERROR"; //DEBUG _ TODO + } else { + $p_id = $packID[0]->resourceID; + } + print "
    " - . "Personalize this package content " + . "Personalize this package content " . "
    "; } ?> diff --git a/css/gokb.search.css b/css/gokb.search.css index 82925ca..5a21987 100644 --- a/css/gokb.search.css +++ b/css/gokb.search.css @@ -2,40 +2,40 @@ **** getKBSearchResults.php **** **************************************/ .div_results{ - margin-top: 10px; - width: 745px; + margin-top: 10px; + width: 745px; } .results_table{ - width: 745px; - height: 190px; - border-bottom: 1px solid black; + width: 745px; + height: 190px; + border-bottom: 1px solid black; } .results_table_line{ - height: 20%; + height: 20%; } .results_table_title_cell{ - width: 90%; + width: 90%; } .results_table_cell{ - width: 5%; + width: 5%; } .results_type_title{ - font-size: 150%; - font-weight: bold; - margin-bottom: 10px; + font-size: 150%; + font-weight: bold; + margin-bottom: 10px; } .moreResults{ - float: right; + float: right; } .linkStyle{ - color: blue; - text-decoration: underline; + color: blue; + text-decoration: underline; } @@ -46,92 +46,92 @@ ****************************************/ #resourceDetails{ - width: 745px; + width: 745px; } #detailsTabs{ - margin-top: 15px; + margin-top: 15px; } #detailsTabs ul{ - font: normal 14px arial, sans, sans-serif; - -list-style-type: none; - /*border-bottom: 1px solid gray;*/ - margin: 0; - padding-left:0; - padding-right:0; - padding-bottom: 26px; + font: normal 14px arial, sans, sans-serif; + -list-style-type: none; + /*border-bottom: 1px solid gray;*/ + margin: 0; + padding-left:0; + padding-right:0; + padding-bottom: 26px; } #detailsTabs ul li{ - display: inline; - float: left; - height: 17px; - min-width:80px; - text-align:center; - text-decoration: none; - padding: 4px; - margin: 1px 0px 0px 0px; - border: 1px solid gray; - color: #666; - background-color:#eee; + display: inline; + float: left; + height: 17px; + min-width:80px; + text-align:center; + text-decoration: none; + padding: 4px; + margin: 1px 0px 0px 0px; + border: 1px solid gray; + color: #666; + background-color:#eee; } #detailsTabs ul li.selected { - color: #000; - font-weight:bold; - background-color: #fff; - border-bottom: 1px solid #fff; + color: #000; + font-weight:bold; + background-color: #fff; + border-bottom: 1px solid #fff; } #detailsTabs ul li:hover{ - color: #000; - font-weight:bold; - background-color: #fff; + color: #000; + font-weight:bold; + background-color: #fff; } #detailsContainer{ - background: white; - border:1px solid gray; - /* height:350px;*/ - width:100%; - padding:0; - margin:0; - margin-top: 1px; - left:0; - top:0; + background: white; + border:1px solid gray; + /* height:350px;*/ + width:100%; + padding:0; + margin:0; + margin-top: 1px; + left:0; + top:0; } #resourceName{ - text-align: center; - font-size: 110%; + text-align: center; + font-size: 110%; } #resType{ - text-decoration: italic; - margin-left: 25px; + text-decoration: italic; + margin-left: 25px; } #resName{ - font-weight: bold; + font-weight: bold; } #detailsTab{ - margin: 10px; + margin: 10px; } #detailsTab tr{ - height: 10%; + height: 10%; } .detailsTab_val{ - padding-left: 5px; + padding-left: 5px; } .detailsTab_tag{ - font-weight: bold; - padding-right: 10px; - text-align: right; + font-weight: bold; + padding-right: 10px; + text-align: right; } #span_select{ @@ -144,7 +144,7 @@ .tippLink{ text-decoration: underline; - color: blue; + /*color: blue;*/ cursor: pointer; } @@ -152,36 +152,36 @@ **** getPagination.php **** ********************************/ #pageIterator { - margin: 10px; - text-align: center; - font-weight: normal; - color: #000; + margin: 10px; + text-align: center; + font-weight: normal; + color: #000; } #pageIterator ul li{ - display: inline; - border: 1px solid black; - margin-left: 5px; - padding: 5px; + display: inline; + border: 1px solid black; + margin-left: 5px; + padding: 5px; } #pageIterator ul li:hover{ - - font-weight:bold; - background-color: #fff; - border: 2px solid black; + + font-weight:bold; + background-color: #fff; + border: 2px solid black; } #pageIterator ul li.active{ - background-color: #e9edf0; - border: 1px solid #e9edf0; + background-color: #e9edf0; + border: 1px solid #e9edf0; } #pageIterator ul li.active:hover{ - font-weight: normal; + font-weight: normal; } #pageIterator ul li.invisible{ - display: none; + display: none; } /***************************************** @@ -195,14 +195,65 @@ margin: 30px; } +/*************************************************** + **** customImportedPackageContent.php **** + ***************************************************/ + +#customPackageFieldset{ + height: 375px; +} + +#customTbody{ + height: 290px; + position: absolute; + overflow: auto; + width: 745px; + margin-top: 50px; + border-top: 1px solid black; +} + +#customPackageTable{ + position: fixed; + height: 350px; + width: 750px; + overflow: auto; + border: 1px solid black; +} + +#customPackageTable tr{ + height: 20px; +} + +#customPackageTable thead{ + height: 30px; + width: 100%; +} + +.customCb{ + width: 75px; + text-align: center; +} + +#removedRes +{ + border-left: 2px solid black; + margin-left: 20px; + margin-top: 10px; + padding: 0 5px 5px; + width: 80%; +} + +#removedRes tr{ + height: 30px; +} /************************ **** Multiple **** ************************/ .invisible{ - display: none; + display: none; } .search_nav_button{ - margin-top: 5px; + margin-top: 5px; } diff --git a/images/repo.gif b/images/repo.gif new file mode 100644 index 0000000000000000000000000000000000000000..894862e80cb1dc634d240dd59c64b1dc84a61caa GIT binary patch literal 160 zcmZ?wbhEHb6krfw*v!lD_v6X`zg~B@r98bb=hyecXO0(My-@SucJIrlv%fxB_u
    "); - $('#TB_load').show();//show loader + var element = document.getElementById("TB_ajaxContent"); + if (element != null) { + document.getElementById("TB_ajaxContent").innerHTML = ""; + $('#TB_ajaxContent').append("
    "); + $('#TB_load').show();//show loader + } } /*******************************************************************************************************/ @@ -342,9 +355,6 @@ function getCustomizationScreen(packageID) { document.getElementById("TB_ajaxContent").innerHTML = ""; $('#TB_ajaxContent').append(res); } - -// TODO _ history - }); } @@ -356,7 +366,7 @@ function getCustomizationScreen(packageID) { */ function submitCustom(packageID) { console.debug("function submitCustom"); - var checkboxes = document.getElementById("customPackageTable").getElementsByTagName("input"); + var checkboxes = document.getElementById("customTbody").getElementsByTagName("input"); displayLoadBar(); var max = checkboxes.length; var resToRemove = new Array(); @@ -416,4 +426,26 @@ function removeResAndChildren(packageID) { } } -/*******************************************************************************************************/ \ No newline at end of file +/*******************************************************************************************************/ +function filterCustomContent(){ + console.debug("function filterCustomContent() "); + + var content = document.getElementById("customTbody").getElementsByTagName("tr"); + var textuals; + var textual = ""; + var filterText = document.getElementById("customFilter").value; + filterText = filterText.toLowerCase(); + + for (var i=0; i Date: Mon, 17 Aug 2015 09:47:46 +0200 Subject: [PATCH 28/32] fix allResults back button --- js/KBSearch.js | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/js/KBSearch.js b/js/KBSearch.js index bd2f269..bae96eb 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -37,8 +37,18 @@ function allResults(s_name, s_pub, s_type) { $('#TB_ajaxContent').append(res); } }); - window.history.pushState({funcName: 'allResults', param: [s_name, s_pub, s_type]}, 'test', null); - console.debug("pushState(allResults(" + s_name + "," + s_pub + "," + s_type + "))"); + + var parameters = [s_name, s_pub, s_type]; + var currentState=window.history.state; + + if ((currentState.funcName == 'allResults') && compareParamArray(currentState.param, parameters)) { + console.debug("Same state as before !!, don't push"); + } else { + window.history.pushState({funcName: 'allResults', param: [s_name, s_pub, s_type]}, 'test', null); + console.debug("pushState(allResults(" + s_name + "," + s_pub + "," + s_type + "))"); + } + + } /*******************************************************************************************************/ @@ -325,6 +335,23 @@ function goBack() { /*******************************************************************************************************/ +function compareParamArray(paramArray, arrayToCompare){ + var max=paramArray.length; + var isEqual = true; + + if (max != arrayToCompare.length) return false; + + for (var i=0; i Date: Tue, 18 Aug 2015 12:24:26 +0200 Subject: [PATCH 29/32] add code comments --- admin/classes/domain/GOKbTools.php | 33 ++++++++++------ admin/classes/domain/ImportTool.php | 58 +++++++++++++++++++++++++---- admin/classes/domain/Resource.php | 2 +- 3 files changed, 72 insertions(+), 21 deletions(-) diff --git a/admin/classes/domain/GOKbTools.php b/admin/classes/domain/GOKbTools.php index 831d60b..341629b 100644 --- a/admin/classes/domain/GOKbTools.php +++ b/admin/classes/domain/GOKbTools.php @@ -41,9 +41,6 @@ class GOKbTools { */ private $httpClient; - - - /** * @var GOKbTools (Pattern Singleton) */ @@ -149,7 +146,6 @@ public function searchOnGokb($name, $issn, $publisher, $searchType){ //$prefLabel = $b->{'binding'}[1]->{'literal'}; $prefLabel = utf8_encode($b->{'binding'}[1]->{'literal'}); $titles["$id"] = $prefLabel; - // $titles["$id"] = \utf8_encode($prefLabel); } } @@ -177,7 +173,7 @@ private function sendSparqlQuery($query){ $response = ''; } $myTmpClient = new MyClient(); - $res = $myTmpClient->decodeResponse($response); //decodeResponse protected: passé à public temporairement --> trouver une solution !! (héritage + surcharge ?) + $res = $myTmpClient->decodeResponse($response); return $res; } @@ -190,8 +186,6 @@ private function sendSparqlQuery($query){ * @param string $uri resource's URI * @return string resource's GOKb identifier */ - - public function UriToGokbId($uri) { $cut = explode('/', $uri); @@ -335,6 +329,12 @@ function getNbTipps($record){ // ------------------------------------------------------------------------- + /** + * Perform a 'GetRecord' request + * @param string $type resource's type (title or package) + * @param string $id GOKb ID + * @return XMLElement Record: resource's informations + */ function getRecord($type, $id){ switch ($type) { @@ -346,22 +346,34 @@ function getRecord($type, $id){ break; default: return null; - break; } $rec = $record->{'GetRecord'}->{'record'}; return $rec; } + // ------------------------------------------------------------------------- + + /** + * create an array to stock identifiers for import treatment + * @param array $ids array(XML elements) + * @return type + */ function createIdentifiersArrayToImport($ids) { foreach ($ids as $key => $value) { $tmp = $value->attributes(); $identifiers["$tmp[0]"] = (string) $tmp[1]; - //$string .= "insertion de identifiers[" . $tmp[0] . "] = " . $tmp[1] . "
    "; } return $identifiers; } + // ------------------------------------------------------------------------- + + /** + * Convert a string date (from XML GOKb record) into DateTime object + * @param string $xmlDate the date from XML record + * @return DateTIme + */ function convertXmlDateToDateTime($xmlDate){ $format = "Y-m-d H:i:s.u"; $date = DateTime::createFromFormat($format, $xmlDate); @@ -369,8 +381,5 @@ function convertXmlDateToDateTime($xmlDate){ } } - - - ?> \ No newline at end of file diff --git a/admin/classes/domain/ImportTool.php b/admin/classes/domain/ImportTool.php index 93845bf..da21874 100644 --- a/admin/classes/domain/ImportTool.php +++ b/admin/classes/domain/ImportTool.php @@ -13,8 +13,13 @@ //include_once $_SERVER['DOCUMENT_ROOT'] . "resources/directory.php"; + class ImportTool { + /** + * @var Configuration + * used to know which module is activated and their DB names + */ private $config; /** @@ -86,8 +91,6 @@ public function addResource($datas, $identifiers) { $resourceType = null; $aliases = null; - $htmlContent = ''; //TODO _ DEBUG _ Displaying after select - /* * ***************************************** * * Has the resource to be inserted ? ** * ***************************************** */ @@ -130,7 +133,7 @@ public function addResource($datas, $identifiers) { //Resource identifiers treatment $res->setIdentifiers($identifiers); -//Aliases treatment (history name change/ variant name) //TODO +//Aliases treatment (history name change/ variant name) if ($aliases != null) { $this->aliasesTreatment($aliases, $res->resourceID); } @@ -151,6 +154,12 @@ public function addResource($datas, $identifiers) { // ------------------------------------------------------------------------- + /** + * Link a resource with an organization + * @param int $roleID ID of the organization's role (DB primaryKey) + * @param int $resourceID ID of the resource (DB primaryKey) + * @param int $organizationID ID of the organization (DB primaryKey) + */ private function setResourceOrganizationLink($roleID, $resourceID, $organizationID) { $organizationLink = new ResourceOrganizationLink(); $organizationLink->organizationRoleID = $roleID; @@ -205,6 +214,12 @@ private function hasResourceToBeInserted($datas, $identifiers) { } // ------------------------------------------------------------------------- + + /** + * Create resource's parent or attach it + * @param string $parentName + * @return int ID of the parent resource + */ private function parentTreatment($parentName) { $resource = new Resource(); $parentResource = $resource->getResourceByTitle($parentName); @@ -228,6 +243,12 @@ private function parentTreatment($parentName) { } // ------------------------------------------------------------------------- + + /** + * Link a resource with its parent + * @param int $resourceID ID of the resource + * @param int $parentID ID of the parent resouce + */ private function setResourcesRelationship($resourceID, $parentID) { if ($parentID != NULL) { $resourceRelationship = new ResourceRelationship(); @@ -242,15 +263,20 @@ private function setResourcesRelationship($resourceID, $parentID) { // ------------------------------------------------------------------------- + /** + * create reosurce's organization or attach it + * @param array $organizations array(role => organization) + * @param int $resourceID ID of the resource + */ private function organizationTreatment($organizations, $resourceID) { $loginID = $_SESSION['loginID']; $htmlContent = ''; //Organizations module is used - if ($this->config->settings->organizationsModule == 'Y') { //TODO _ hierarchy platform/provider + if ($this->config->settings->organizationsModule == 'Y') { $dbName = $this->config->settings->organizationsDatabaseName; foreach ($organizations as $role => $orgName) { - $organization = new Organization(); //TODO _ instanciation dans une boucle j'aime pas trop ça ;) + $organization = new Organization(); $organizationRole = new OrganizationRole(); $organizationID = false; @@ -301,7 +327,7 @@ private function organizationTreatment($organizations, $resourceID) { } } -//TODO _ hierarchy platform/provider (packages) +//Hierarchy platform/provider ( gokb packages) if ($organizations['platform']) { $platformName = $organizations['platform']; $providerName = $organizations['provider']; @@ -311,7 +337,7 @@ private function organizationTreatment($organizations, $resourceID) { // If we do not use the Organizations module else { foreach ($organizations as $role => $orgName) { - $organization = new Organization(); //TODO _ instanciation dans une boucle j'aime pas trop ça ;) + $organization = new Organization(); $organizationRole = new OrganizationRole(); $organizationID = false; // Search if such organization already exists @@ -347,7 +373,12 @@ private function organizationTreatment($organizations, $resourceID) { } // ------------------------------------------------------------------------- - + + /** + * create a new organization when Organization module is activated + * @param string $orgName + * @return int ID of the created organization + */ private function createOrgWithOrganizationModule($orgName) { $dbName = $this->config->settings->organizationsDatabaseName; $loginID = $_SESSION['loginID']; @@ -366,6 +397,12 @@ private function createOrgWithOrganizationModule($orgName) { } // ------------------------------------------------------------------------- + + /** + * child/parent organizations treatment + * @param string $orgName + * @param string $parentOrgName + */ private function setOrganizationsHierarchy($orgName, $parentOrgName) { $orgID = null; $parentID = null; @@ -403,6 +440,11 @@ private function setOrganizationsHierarchy($orgName, $parentOrgName) { // ------------------------------------------------------------------------- + /** + * Add resource aliases to resource object in DB + * @param array $aliases array (alias type => array (alias name)) + * @param int $resourceID ID of the resource + */ private function aliasesTreatment($aliases, $resourceID) { foreach ($aliases as $aliasType => $aliasArray) { $typeID = AliasType::getAliasTypeID((string) $aliasType); diff --git a/admin/classes/domain/Resource.php b/admin/classes/domain/Resource.php index 1919d04..4dd8689 100644 --- a/admin/classes/domain/Resource.php +++ b/admin/classes/domain/Resource.php @@ -2203,7 +2203,7 @@ public function getResourceByIdentifierAndType($identifier, $type = NULL) { } $query .= ";"; - $result = $this->db->processQuery($query, 'assoc'); //TODO _ see param assoc + $result = $this->db->processQuery($query, 'assoc'); From ed986023518711afc9c940ff6fadb1a892457b78 Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Tue, 18 Aug 2015 17:25:37 +0200 Subject: [PATCH 30/32] Fix some bugs --- ajax_htmldata/getGokbResourceDetails.php | 45 ++++++++++------ ajax_processing/importFromGOKb.php | 2 +- css/gokb.search.css | 17 ++++-- .../{update_NEXT.sql => update_gokb.sql} | 0 js/KBSearch.js | 54 ++++++++++--------- 5 files changed, 73 insertions(+), 45 deletions(-) rename install/protected/{update_NEXT.sql => update_gokb.sql} (100%) diff --git a/ajax_htmldata/getGokbResourceDetails.php b/ajax_htmldata/getGokbResourceDetails.php index 7b44d2d..a3d35ff 100644 --- a/ajax_htmldata/getGokbResourceDetails.php +++ b/ajax_htmldata/getGokbResourceDetails.php @@ -10,12 +10,13 @@ $tool = GOKbTools::getInstance(); $record = $tool->getDetails($_POST['type'], $_POST['id']); $nbTipps = $tool->getNbTipps($record); +$displayTippsTab = ($nbTipps > 0); ?>
    - + details for getResourceName($record); ?> @@ -24,36 +25,48 @@
    • Details
    • -
    • - "; + // -
    • + + echo ""; + } + ?>
    - displayRecord($record); ?> + No TIPPs for this resource

    "; + } + echo $tool->displayRecord($record); ?>
    - - +
    - + + +
    - "; ?> +"; ?> diff --git a/ajax_processing/importFromGOKb.php b/ajax_processing/importFromGOKb.php index 2afc65a..25be30c 100644 --- a/ajax_processing/importFromGOKb.php +++ b/ajax_processing/importFromGOKb.php @@ -39,7 +39,7 @@ $variantName = $recordDetails->{'variantNames'}; $variants = $variantName->children(); - if (count($variants) > 0) { + if (($variants != NULL) && (count($variants) > 0)) { $datas['alias']['alternate name'] = array(); foreach ($variants as $key => $name) { array_push($datas['alias']['alternate name'], (string) $name); diff --git a/css/gokb.search.css b/css/gokb.search.css index 5a21987..4709f18 100644 --- a/css/gokb.search.css +++ b/css/gokb.search.css @@ -36,6 +36,7 @@ .linkStyle{ color: blue; text-decoration: underline; + cursor: pointer; } @@ -74,6 +75,7 @@ border: 1px solid gray; color: #666; background-color:#eee; + cursor: pointer; } @@ -82,6 +84,7 @@ font-weight:bold; background-color: #fff; border-bottom: 1px solid #fff; + cursor: default; } #detailsTabs ul li:hover{ @@ -144,10 +147,16 @@ .tippLink{ text-decoration: underline; - /*color: blue;*/ cursor: pointer; } +#noTipps{ + margin-bottom: -10px; + width: 100%; + text-align: center; + font-weight: bold; +} + /******************************** **** getPagination.php **** ********************************/ @@ -163,6 +172,7 @@ border: 1px solid black; margin-left: 5px; padding: 5px; + cursor: pointer; } #pageIterator ul li:hover{ @@ -175,6 +185,7 @@ #pageIterator ul li.active{ background-color: #e9edf0; border: 1px solid #e9edf0; + cursor: default; } #pageIterator ul li.active:hover{ font-weight: normal; @@ -204,11 +215,10 @@ } #customTbody{ - height: 290px; + height: 300px; position: absolute; overflow: auto; width: 745px; - margin-top: 50px; border-top: 1px solid black; } @@ -225,7 +235,6 @@ } #customPackageTable thead{ - height: 30px; width: 100%; } diff --git a/install/protected/update_NEXT.sql b/install/protected/update_gokb.sql similarity index 100% rename from install/protected/update_NEXT.sql rename to install/protected/update_gokb.sql diff --git a/js/KBSearch.js b/js/KBSearch.js index bae96eb..7ab6e1b 100644 --- a/js/KBSearch.js +++ b/js/KBSearch.js @@ -5,13 +5,13 @@ $(document).ajaxError(function (event, request, settings) { }); -$(document).ready(function(){ - - $("#customFilter").keyup(function(){ +$(document).ready(function () { + + $("#customFilter").keyup(function () { console.debug("Change custom filter !"); filterCustomContent(); }); - + }); /*******************************************************************************************************/ @@ -37,18 +37,18 @@ function allResults(s_name, s_pub, s_type) { $('#TB_ajaxContent').append(res); } }); - + var parameters = [s_name, s_pub, s_type]; - var currentState=window.history.state; - + var currentState = window.history.state; + if ((currentState.funcName == 'allResults') && compareParamArray(currentState.param, parameters)) { console.debug("Same state as before !!, don't push"); } else { window.history.pushState({funcName: 'allResults', param: [s_name, s_pub, s_type]}, 'test', null); console.debug("pushState(allResults(" + s_name + "," + s_pub + "," + s_type + "))"); } - - + + } /*******************************************************************************************************/ @@ -335,16 +335,18 @@ function goBack() { /*******************************************************************************************************/ -function compareParamArray(paramArray, arrayToCompare){ - var max=paramArray.length; +function compareParamArray(paramArray, arrayToCompare) { + var max = paramArray.length; var isEqual = true; - - if (max != arrayToCompare.length) return false; - - for (var i=0; i Date: Thu, 20 Aug 2015 09:40:12 +0200 Subject: [PATCH 31/32] DB change, drop table IsbnOrIssn --- admin/classes/domain/Resource.php | 81 +++++++++++++------------ ajax_forms/getUpdateProductForm.php | 3 +- ajax_htmldata/getProductDetails.php | 2 +- ajax_processing/submitNewResource.php | 2 +- ajax_processing/submitProductUpdate.php | 2 +- export.php | 2 +- install/protected/update_gokb.sql | 2 +- summary.php | 2 +- 8 files changed, 49 insertions(+), 47 deletions(-) diff --git a/admin/classes/domain/Resource.php b/admin/classes/domain/Resource.php index 4dd8689..05d6142 100644 --- a/admin/classes/domain/Resource.php +++ b/admin/classes/domain/Resource.php @@ -62,9 +62,11 @@ public function getResourceByTitle($title) { //returns resource objects by title public function getResourceByIsbnOrISSN($isbnOrISSN) { - $query = "SELECT DISTINCT(resourceID) - FROM IsbnOrIssn"; - + // $query = "SELECT DISTINCT(resourceID) + //FROM IsbnOrIssn"; + $query = "SELECT DISTINCT(resourceID) + FROM Identifier"; + $i = 0; if (!is_array($isbnOrISSN)) { @@ -74,7 +76,9 @@ public function getResourceByIsbnOrISSN($isbnOrISSN) { foreach ($isbnOrISSN as $value) { $query .= ($i == 0) ? " WHERE " : " OR "; - $query .= "isbnOrIssn = '" . $this->db->escapeString($value) . "'"; + // $query .= "isbnOrIssn = '" . $this->db->escapeString($value) . "'"; + $query .= "identifier = '" . $this->db->escapeString($value) . "'" + . " AND identifierTypeID < 6"; $i++; } @@ -99,25 +103,43 @@ public function getResourceByIsbnOrISSN($isbnOrISSN) { } public function getIsbnOrIssn() { - $query = "SELECT * - FROM IsbnOrIssn +// $query = "SELECT * +// FROM IsbnOrIssn +// WHERE resourceID = '" . $this->resourceID . "' +// ORDER BY 1"; + $query = "SELECT * + FROM Identifier WHERE resourceID = '" . $this->resourceID . "' + AND identifierTypeID < 6 ORDER BY 1"; - + + + $result = $this->db->processQuery($query, 'assoc'); $objects = array(); //need to do this since it could be that there's only one request and this is how the dbservice returns result - if (isset($result['isbnOrIssnID'])) { - $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $result['isbnOrIssnID']))); +// if (isset($result['isbnOrIssnID'])) { +// $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $result['isbnOrIssnID']))); +// array_push($objects, $object); +// } else { +// foreach ($result as $row) { +// $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $row['isbnOrIssnID']))); +// array_push($objects, $object); +// } +// } + + if (isset($result['identifierID'])) { + $object = new Identifier(new NamedArguments(array('primaryKey' => $result['identifierID']))); array_push($objects, $object); } else { foreach ($result as $row) { - $object = new IsbnOrIssn(new NamedArguments(array('primaryKey' => $row['isbnOrIssnID']))); + $object = new Identifier(new NamedArguments(array('primaryKey' => $row['identifierID']))); array_push($objects, $object); } } + return $objects; } @@ -858,7 +880,7 @@ public static function getSearchDetails() { } if ($search['resourceISBNOrISSN']) { $resourceISBNOrISSN = mysql_real_escape_string(str_replace("-", "", $search['resourceISBNOrISSN'])); - $whereAdd[] = "REPLACE(I.isbnOrIssn,'-','') = '" . $resourceISBNOrISSN . "'"; + $whereAdd[] = "REPLACE(I.identifier,'-','') = '" . $resourceISBNOrISSN . "'"; $searchDisplay[] = "ISSN/ISBN: " . $search['resourceISBNOrISSN']; } if ($search['fund']) { @@ -1065,7 +1087,7 @@ public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = fals $groupBy = ""; } else { $select = "SELECT R.resourceID, R.titleText, AT.shortName acquisitionType, R.createLoginID, CU.firstName, CU.lastName, R.createDate, S.shortName status, - GROUP_CONCAT(DISTINCT A.shortName, I.isbnOrIssn ORDER BY A.shortName DESC SEPARATOR '
    ') aliases"; + GROUP_CONCAT(DISTINCT A.shortName, I.identifier ORDER BY A.shortName DESC SEPARATOR '
    ') aliases"; $groupBy = "GROUP BY R.resourceID"; } @@ -1092,7 +1114,7 @@ public function searchQuery($whereAdd, $orderBy = '', $limit = '', $count = fals LEFT JOIN ResourcePayment RPAY ON R.resourceID = RPAY.resourceID LEFT JOIN ResourceNote RN ON R.resourceID = RN.resourceID LEFT JOIN ResourceStep RS ON R.resourceID = RS.resourceID - LEFT JOIN IsbnOrIssn I ON R.resourceID = I.resourceID + LEFT JOIN Identifier I ON R.resourceID = I.resourceID "); $additional_joins = array(); @@ -1239,7 +1261,7 @@ public function export($whereAdd, $orderBy) { R.currentStartDate, R.currentEndDate, R.subscriptionAlertEnabledInd, AUT.shortName authenticationType, AM.shortName accessMethod, SL.shortName storageLocation, UL.shortName userLimit, R.authenticationUserName, R.authenticationPassword, R.coverageText, CT.shortName catalogingType, CS.shortName catalogingStatus, R.recordSetIdentifier, R.bibSourceURL, - R.numberRecordsAvailable, R.numberRecordsLoaded, R.hasOclcHoldings, I.isbnOrIssn, + R.numberRecordsAvailable, R.numberRecordsLoaded, R.hasOclcHoldings, I.identifier, " . $orgSelectAdd . ", " . $licSelectAdd . " GROUP_CONCAT(DISTINCT A.shortName ORDER BY A.shortName DESC SEPARATOR '; ') aliases, @@ -1282,7 +1304,7 @@ public function export($whereAdd, $orderBy) { LEFT JOIN AccessMethod AM ON AM.accessMethodID = R.accessMethodID LEFT JOIN StorageLocation SL ON SL.storageLocationID = R.storageLocationID LEFT JOIN UserLimit UL ON UL.userLimitID = R.userLimitID - LEFT JOIN IsbnOrIssn I ON I.resourceID = R.resourceID + LEFT JOIN Identifier I ON I.resourceID = R.resourceID " . $licJoinAdd . " " . $whereStatement . " GROUP BY R.resourceID @@ -1655,7 +1677,6 @@ public function removeResource() { $this->removeResourceOrganizations(); $this->removeResourcePayments(); $this->removeAllSubjects(); - $this->removeAllIsbnOrIssn(); $this->removeResourceIdentifiers(); @@ -2151,33 +2172,13 @@ public function removeAllSubjects() { $result = $this->db->processQuery($query); } - public function removeAllIsbnOrIssn() { - $query = "DELETE - FROM IsbnOrIssn - WHERE resourceID = '" . $this->resourceID . "'"; - - $result = $this->db->processQuery($query); - } - - public function setIsbnOrIssn($isbnorissns) { - $this->removeAllIsbnOrIssn(); - foreach ($isbnorissns as $isbnorissn) { - if (trim($isbnorissn) != '') { - $isbnOrIssn = new IsbnOrIssn(); - $isbnOrIssn->resourceID = $this->resourceID; - $isbnOrIssn->isbnOrIssn = $isbnorissn; - $isbnOrIssn->save(); - } - } - } - /** * Fill the Identifier table in DB * @param $identifiers array array of all identifiers (type => id) (if type isn't known, don't put any key) * */ public function setIdentifiers($identifiers) { - $isbnorissns = array(); + // $isbnorissns = array(); foreach ($identifiers as $key => $value) { $identifier = new Identifier(); $identifier->resourceID = $this->resourceID; @@ -2185,13 +2186,13 @@ public function setIdentifiers($identifiers) { $identifier->identifier = $value; $identifier->save(); - +/* //Temporary fill IsbnOrIssn table if ($identifier->identifierTypeID <= 5) { array_push($isbnorissns, $value); - } + } */ } - $this->setIsbnOrIssn($isbnorissns); + // $this->setIsbnOrIssn($isbnorissns); } public function getResourceByIdentifierAndType($identifier, $type = NULL) { diff --git a/ajax_forms/getUpdateProductForm.php b/ajax_forms/getUpdateProductForm.php index 79dbbad..a0c5d3a 100644 --- a/ajax_forms/getUpdateProductForm.php +++ b/ajax_forms/getUpdateProductForm.php @@ -157,7 +157,8 @@ $isbnOrIssns = $resource->getIsbnOrIssn(); $i = 1; foreach ($isbnOrIssns as $isbnOrIssn) { - ?>

    diff --git a/ajax_htmldata/getProductDetails.php b/ajax_htmldata/getProductDetails.php index ee93e3e..c11bebb 100644 --- a/ajax_htmldata/getProductDetails.php +++ b/ajax_htmldata/getProductDetails.php @@ -199,7 +199,7 @@ isbnOrIssn . "
    "; + print $isbnOrIssn->identifier . "
    "; } ?> diff --git a/ajax_processing/submitNewResource.php b/ajax_processing/submitNewResource.php index d078e0f..e99b77e 100644 --- a/ajax_processing/submitNewResource.php +++ b/ajax_processing/submitNewResource.php @@ -28,7 +28,7 @@ $resource->titleText = $_POST['titleText']; $resource->descriptionText = $_POST['descriptionText']; - $resource->isbnOrISSN = ''; //$_POST['ISSNText']; + //DEBUG _ $resource->isbnOrISSN = ''; //$_POST['ISSNText']; //this attribute doesn't exist anymore $resource->statusID = $statusID; $resource->orderNumber = ''; $resource->systemNumber = ''; diff --git a/ajax_processing/submitProductUpdate.php b/ajax_processing/submitProductUpdate.php index 75669e8..2bd9b17 100644 --- a/ajax_processing/submitProductUpdate.php +++ b/ajax_processing/submitProductUpdate.php @@ -15,7 +15,7 @@ $resource->resourceAltURL = $_POST['resourceAltURL']; $isbnarray = json_decode($_POST['isbnOrISSN']); - $resource->setIsbnOrIssn($isbnarray); + $resource->setIdentifiers($isbnarray); //to determine status id $status = new Status(); diff --git a/export.php b/export.php index 760fb30..ce54c87 100644 --- a/export.php +++ b/export.php @@ -120,7 +120,7 @@ function array_to_csv_row($array) { $updateDateFormatted, $resource['updateName'], $resource['status'], - $resource['isbnOrISSN'], + $resource['isbnOrISSN'], //DEBUG _ this attribute doesn't exist anymore $resource['resourceURL'], $resource['resourceAltURL'], $resource['organizationNames'], diff --git a/install/protected/update_gokb.sql b/install/protected/update_gokb.sql index 31c70ec..cef5502 100644 --- a/install/protected/update_gokb.sql +++ b/install/protected/update_gokb.sql @@ -14,7 +14,7 @@ INSERT INTO `Identifier` (`resourceID`, `identifier`) SELECT `resourceID`, `isbnOrIssn` FROM `IsbnOrIssn`; UPDATE `Identifier` SET `identifierTypeID`=1; - +DROP TABLE IF EXISTS `_DATABASE_NAME_`.`IsbnOrIssn`; DROP TABLE IF EXISTS `_DATABASE_NAME_`.`IdentifierType`; CREATE TABLE `_DATABASE_NAME_`.`IdentifierType` ( diff --git a/summary.php b/summary.php index 5680c9d..bdd78ba 100644 --- a/summary.php +++ b/summary.php @@ -286,7 +286,7 @@ isbnOrISSN){ + if ($resource->isbnOrISSN){ //this attribute doesn't exist anymore ?> ISSN / ISBN: From 6be47a7c8492b42d8599f01b877e820210a640ef Mon Sep 17 00:00:00 2001 From: Amandine ROGER Date: Thu, 20 Aug 2015 09:54:55 +0200 Subject: [PATCH 32/32] fix display bug --- ajax_htmldata/getKBSearchResults.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ajax_htmldata/getKBSearchResults.php b/ajax_htmldata/getKBSearchResults.php index c8ec420..7ca7760 100644 --- a/ajax_htmldata/getKBSearchResults.php +++ b/ajax_htmldata/getKBSearchResults.php @@ -62,8 +62,7 @@ echo ''; echo "
    "; - } else { - if (!$isPaginated) + } else if(($nb_packages == 0) && ($nb_titles == 0) && (!$isPaginated)){ echo "No results, please check your search fields
    "; }